logo

utils-std

Collection of commonly available Unix tools git clone https://anongit.hacktivis.me/git/utils-std.git/

cmp.c (3730B)


  1. // utils-std: Collection of commonly available Unix tools
  2. // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
  3. // SPDX-License-Identifier: MPL-2.0
  4. #define _POSIX_C_SOURCE 200809L
  5. #include <assert.h>
  6. #include <errno.h>
  7. #include <stdbool.h>
  8. #include <stdio.h> // fopen, fprintf, getline
  9. #include <stdlib.h> // abort, strtoul
  10. #include <string.h> // strerror
  11. #include <unistd.h> // getopt
  12. static bool opt_s = false, opt_l = false;
  13. static unsigned long max_bytes = 0;
  14. const char *argv0 = "cmp";
  15. #undef MIN
  16. #define MIN(a, b) (((a) < (b)) ? (a) : (b))
  17. static int
  18. do_cmp(FILE *file1, const char *name1, FILE *file2, const char *name2)
  19. {
  20. char *line1 = NULL, *line2 = NULL;
  21. size_t len1 = 0, len2 = 0;
  22. unsigned long pos = 1, ln = 1;
  23. while(true)
  24. {
  25. ssize_t nread1 = getline(&line1, &len1, file1);
  26. if(nread1 < 0)
  27. {
  28. if(!ferror(file1)) return 0;
  29. fprintf(stderr,
  30. "%s: error: Failed to read line %ld from file '%s': %s\n",
  31. argv0,
  32. ln,
  33. name1,
  34. strerror(errno));
  35. return 1;
  36. }
  37. ssize_t nread2 = getline(&line2, &len2, file2);
  38. if(nread2 < 0)
  39. {
  40. if(!ferror(file2))
  41. {
  42. if(!opt_s) fprintf(stderr, "%s: error: EOF on %s line %ld\n", argv0, name2, ln);
  43. return 1;
  44. }
  45. fprintf(stderr,
  46. "%s: error: Failed to read line %ld from file '%s': %s\n",
  47. argv0,
  48. ln,
  49. name1,
  50. strerror(errno));
  51. return 2;
  52. }
  53. for(ssize_t i = 0; i < MIN(nread1, nread2); i++)
  54. {
  55. if(max_bytes != 0 && pos + i >= max_bytes) return 0;
  56. if(line1[i] != line2[i])
  57. {
  58. if(opt_s) return 1;
  59. if(opt_l)
  60. printf("%ld %o %o\n", pos + i, line1[i], line2[i]);
  61. else
  62. printf("%s %s differ: char %zd, line %ld\n", name1, name2, i + 1, ln);
  63. return 1;
  64. }
  65. }
  66. assert(nread1 == nread2);
  67. pos += nread1;
  68. ln++;
  69. }
  70. return 0;
  71. }
  72. static void
  73. usage(void)
  74. {
  75. fprintf(stderr, "Usage: cmp [-l|-s] [-n max_bytes] file1 file2\n");
  76. }
  77. int
  78. main(int argc, char *argv[])
  79. {
  80. char *endptr = NULL;
  81. for(int c = -1; (c = getopt(argc, argv, ":ln:s")) != -1;)
  82. {
  83. switch(c)
  84. {
  85. case 'l':
  86. opt_l = true;
  87. break;
  88. case 's':
  89. opt_s = true;
  90. break;
  91. case 'n':
  92. errno = 0;
  93. max_bytes = strtoul(optarg, &endptr, 0);
  94. if(errno != 0)
  95. {
  96. fprintf(stderr, "%s: error: Failed parsing '-n %s': %s\n", argv0, optarg, strerror(errno));
  97. return 1;
  98. }
  99. if(endptr != NULL && endptr[0] != 0)
  100. {
  101. fprintf(stderr,
  102. "%s: error: Non-numeric characters passed to '-n %s': %s\n",
  103. argv0,
  104. optarg,
  105. endptr);
  106. return 1;
  107. }
  108. break;
  109. case ':':
  110. fprintf(stderr, "%s: error: Missing operand for option: '-%c'\n", argv0, optopt);
  111. usage();
  112. return 1;
  113. case '?':
  114. fprintf(stderr, "%s: error: Unrecognised option: '-%c'\n", argv0, optopt);
  115. usage();
  116. return 1;
  117. default:
  118. abort();
  119. }
  120. }
  121. argc -= optind;
  122. argv += optind;
  123. if(argc != 2)
  124. {
  125. fprintf(stderr, "%s: error: Expected 2 arguments, got %d arguments\n", argv0, argc);
  126. return 1;
  127. }
  128. if(strcmp(argv[0], argv[1]) == 0) return 0;
  129. FILE *file1 = NULL;
  130. if(argv[0][0] == '-' && argv[0][1] == 0)
  131. file1 = stdin;
  132. else
  133. {
  134. file1 = fopen(argv[0], "r");
  135. if(file1 == NULL)
  136. {
  137. fprintf(stderr, "%s: error: Failed opening file '%s': %s\n", argv0, argv[0], strerror(errno));
  138. return 1;
  139. }
  140. }
  141. FILE *file2 = NULL;
  142. if(argv[1][0] == '-' && argv[1][1] == 0)
  143. file2 = stdin;
  144. else
  145. {
  146. file2 = fopen(argv[1], "r");
  147. if(file2 == NULL)
  148. {
  149. fprintf(stderr, "%s: error: Failed opening file '%s': %s\n", argv0, argv[1], strerror(errno));
  150. return 1;
  151. }
  152. }
  153. int ret = do_cmp(file1, argv[0], file2, argv[1]);
  154. fclose(file1);
  155. fclose(file2);
  156. return ret;
  157. }