logo

utils-std

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

cmp.c (5841B)


  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 "../config.h"
  6. #include "../libutils/getopt_nolong.h"
  7. #include <assert.h>
  8. #include <errno.h>
  9. #include <fcntl.h> // open, posix_fadvise
  10. #include <stdbool.h>
  11. #include <stdio.h> // fopen, fprintf, getline
  12. #include <stdlib.h> // abort, strtoul
  13. #include <string.h> // strerror
  14. #include <sys/stat.h> // fstat
  15. #include <unistd.h> // getopt
  16. #ifdef HAS_GETOPT_LONG
  17. #include <getopt.h>
  18. #endif
  19. static bool opt_s = false, opt_l = false;
  20. static unsigned long max_bytes = 0;
  21. const char *argv0 = "cmp";
  22. #undef MIN
  23. #define MIN(a, b) (((a) < (b)) ? (a) : (b))
  24. static int
  25. do_cmp(FILE *file1, const char *name1, FILE *file2, const char *name2)
  26. {
  27. char *line1 = NULL, *line2 = NULL;
  28. size_t len1 = 0, len2 = 0;
  29. unsigned long pos = 1, ln = 1;
  30. errno = 0;
  31. while(true)
  32. {
  33. ssize_t nread1 = getline(&line1, &len1, file1);
  34. if(nread1 < 0)
  35. {
  36. if(!ferror(file1)) return 0;
  37. fprintf(stderr,
  38. "%s: error: Failed to read line %ld from file '%s': %s\n",
  39. argv0,
  40. ln,
  41. name1,
  42. strerror(errno));
  43. return 2;
  44. }
  45. ssize_t nread2 = getline(&line2, &len2, file2);
  46. if(nread2 < 0)
  47. {
  48. if(!ferror(file2))
  49. {
  50. if(!opt_s) fprintf(stderr, "%s: error: EOF on %s line %ld\n", argv0, name2, ln);
  51. return 2;
  52. }
  53. fprintf(stderr,
  54. "%s: error: Failed to read line %ld from file '%s': %s\n",
  55. argv0,
  56. ln,
  57. name1,
  58. strerror(errno));
  59. return 2;
  60. }
  61. for(ssize_t i = 0; i < MIN(nread1, nread2); i++)
  62. {
  63. if(max_bytes != 0 && pos + i >= max_bytes) return 0;
  64. if(line1[i] != line2[i])
  65. {
  66. if(opt_s) return 1;
  67. if(opt_l)
  68. printf("%ld %o %o\n", pos + i, line1[i], line2[i]);
  69. else
  70. printf("%s %s differ: char %zd, line %ld\n", name1, name2, i + 1, ln);
  71. return 1;
  72. }
  73. }
  74. assert(nread1 == nread2);
  75. pos += nread1;
  76. ln++;
  77. }
  78. return 0;
  79. }
  80. static bool
  81. fstat_cmp(int fd1, char *name1, int fd2, char *name2)
  82. {
  83. struct stat buf1, buf2;
  84. errno = 0;
  85. if(fstat(fd1, &buf1) != 0)
  86. {
  87. fprintf(stderr,
  88. "%s: warning: Failed getting status for file '%s': %s\n",
  89. argv0,
  90. name1,
  91. strerror(errno));
  92. return false;
  93. }
  94. errno = 0;
  95. if(fstat(fd2, &buf2) != 0)
  96. {
  97. fprintf(stderr,
  98. "%s: warning: Failed getting status for file '%s': %s\n",
  99. argv0,
  100. name2,
  101. strerror(errno));
  102. return false;
  103. }
  104. return buf1.st_ino == buf2.st_ino && buf1.st_dev == buf2.st_dev;
  105. }
  106. static void
  107. usage(void)
  108. {
  109. fprintf(stderr, "Usage: cmp [-l|-s] [-n max_bytes] file1 file2\n");
  110. }
  111. int
  112. main(int argc, char *argv[])
  113. {
  114. char *endptr = NULL;
  115. #ifdef HAS_GETOPT_LONG
  116. // Strictly for GNUisms compatibility so no long-only options
  117. // clang-format off
  118. static struct option opts[] = {
  119. {"verbose", no_argument, NULL, 'l'},
  120. {"bytes", required_argument, NULL, 'n'},
  121. {"quiet", no_argument, NULL, 's'},
  122. {"silent", no_argument, NULL, 's'},
  123. {0, 0, 0, 0},
  124. };
  125. // clang-format on
  126. // Need + as first character to get POSIX-style option parsing
  127. for(int c = -1; (c = getopt_long(argc, argv, "+:ln:s", opts, NULL)) != -1;)
  128. #else
  129. for(int c = -1; (c = getopt_nolong(argc, argv, ":ln:s")) != -1;)
  130. #endif
  131. {
  132. switch(c)
  133. {
  134. case 'l':
  135. opt_l = true;
  136. break;
  137. case 's':
  138. opt_s = true;
  139. break;
  140. case 'n':
  141. errno = 0;
  142. max_bytes = strtoul(optarg, &endptr, 0);
  143. if(errno != 0)
  144. {
  145. fprintf(stderr, "%s: error: Failed parsing '-n %s': %s\n", argv0, optarg, strerror(errno));
  146. return 2;
  147. }
  148. if(endptr != NULL && endptr[0] != 0)
  149. {
  150. fprintf(stderr,
  151. "%s: error: Non-numeric characters passed to '-n %s': %s\n",
  152. argv0,
  153. optarg,
  154. endptr);
  155. return 2;
  156. }
  157. break;
  158. case ':':
  159. fprintf(stderr, "%s: error: Missing operand for option: '-%c'\n", argv0, optopt);
  160. usage();
  161. return 2;
  162. case '?':
  163. GETOPT_UNKNOWN_OPT
  164. usage();
  165. return 2;
  166. default:
  167. abort();
  168. }
  169. }
  170. argc -= optind;
  171. argv += optind;
  172. if(argc != 2)
  173. {
  174. fprintf(stderr, "%s: error: Expected 2 arguments, got %d arguments\n", argv0, argc);
  175. return 2;
  176. }
  177. if(strcmp(argv[0], argv[1]) == 0) return 0;
  178. int fd1 = STDIN_FILENO;
  179. if(!(argv[0][0] == '-' && argv[0][1] == 0))
  180. {
  181. errno = 0;
  182. fd1 = open(argv[0], O_RDONLY);
  183. if(errno != 0)
  184. {
  185. fprintf(stderr, "%s: error: Failed opening file '%s': %s\n", argv0, argv[0], strerror(errno));
  186. return 2;
  187. }
  188. }
  189. int fd2 = STDIN_FILENO;
  190. if(!(argv[1][0] == '-' && argv[1][1] == 0))
  191. {
  192. errno = 0;
  193. fd2 = open(argv[1], O_RDONLY);
  194. if(errno != 0)
  195. {
  196. fprintf(stderr, "%s: error: Failed opening file '%s': %s\n", argv0, argv[1], strerror(errno));
  197. return 2;
  198. }
  199. }
  200. if(fstat_cmp(fd1, argv[0], fd2, argv[1]))
  201. {
  202. if(fd1 != STDIN_FILENO) close(fd1);
  203. if(fd2 != STDIN_FILENO) close(fd2);
  204. return 0;
  205. }
  206. FILE *file1 = stdin;
  207. FILE *file2 = stdin;
  208. if(fd1 != STDIN_FILENO)
  209. {
  210. errno = 0;
  211. file1 = fdopen(fd1, "r");
  212. if(file1 == (FILE *)NULL)
  213. {
  214. fprintf(stderr,
  215. "%s: error: Failed associating stream for file '%s': %s\n",
  216. argv0,
  217. argv[0],
  218. strerror(errno));
  219. return 2;
  220. }
  221. posix_fadvise(fd1, 0, 0, POSIX_FADV_SEQUENTIAL);
  222. }
  223. if(fd2 != STDIN_FILENO)
  224. {
  225. errno = 0;
  226. file2 = fdopen(fd2, "r");
  227. if(file2 == (FILE *)NULL)
  228. {
  229. fprintf(stderr,
  230. "%s: error: Failed associating stream for file '%s': %s\n",
  231. argv0,
  232. argv[1],
  233. strerror(errno));
  234. return 2;
  235. }
  236. posix_fadvise(fd2, 0, 0, POSIX_FADV_SEQUENTIAL);
  237. }
  238. int ret = do_cmp(file1, argv[0], file2, argv[1]);
  239. if(file1 != stdin) fclose(file1);
  240. if(file2 != stdin) fclose(file2);
  241. return ret;
  242. }