logo

utils-std

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

cmp.c (5299B)


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