logo

utils-std

Collection of commonly available Unix tools

cmp.c (3484B)


  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. #undef MIN
  15. #define MIN(a, b) (((a) < (b)) ? (a) : (b))
  16. static int
  17. do_cmp(FILE *file1, const char *name1, FILE *file2, const char *name2)
  18. {
  19. char *line1 = NULL, *line2 = NULL;
  20. size_t len1 = 0, len2 = 0;
  21. unsigned long pos = 1, ln = 1;
  22. while(true)
  23. {
  24. ssize_t nread1 = getline(&line1, &len1, file1);
  25. if(nread1 < 0)
  26. {
  27. if(!ferror(file1)) return 0;
  28. fprintf(
  29. stderr, "cmp: Failed to read line %ld from file '%s': %s\n", ln, name1, strerror(errno));
  30. return 1;
  31. }
  32. ssize_t nread2 = getline(&line2, &len2, file2);
  33. if(nread2 < 0)
  34. {
  35. if(!ferror(file2))
  36. {
  37. if(!opt_s) fprintf(stderr, "cmp: EOF on %s line %ld\n", name2, ln);
  38. return 1;
  39. }
  40. fprintf(
  41. stderr, "cmp: Failed to read line %ld from file '%s': %s\n", ln, name1, strerror(errno));
  42. return 2;
  43. }
  44. for(ssize_t i = 0; i < MIN(nread1, nread2); i++)
  45. {
  46. if(max_bytes != 0 && pos + i >= max_bytes) return 0;
  47. if(line1[i] != line2[i])
  48. {
  49. if(opt_s) return 1;
  50. if(opt_l)
  51. printf("%ld %o %o\n", pos + i, line1[i], line2[i]);
  52. else
  53. printf("%s %s differ: char %zd, line %ld\n", name1, name2, i + 1, ln);
  54. return 1;
  55. }
  56. }
  57. assert(nread1 == nread2);
  58. pos += nread1;
  59. ln++;
  60. }
  61. return 0;
  62. }
  63. static void
  64. usage()
  65. {
  66. fprintf(stderr, "Usage: cmp [-l|-s] [-n max_bytes] file1 file2\n");
  67. }
  68. int
  69. main(int argc, char *argv[])
  70. {
  71. char *endptr = NULL;
  72. int c = -1;
  73. while((c = getopt(argc, argv, ":ln:s")) != -1)
  74. {
  75. switch(c)
  76. {
  77. case 'l':
  78. opt_l = true;
  79. break;
  80. case 's':
  81. opt_s = true;
  82. break;
  83. case 'n':
  84. errno = 0;
  85. max_bytes = strtoul(optarg, &endptr, 0);
  86. if(errno != 0)
  87. {
  88. fprintf(stderr, "cmp: Error: Failed parsing '-n %s': %s\n", optarg, strerror(errno));
  89. return 1;
  90. }
  91. if(endptr != NULL && endptr[0] != 0)
  92. {
  93. fprintf(
  94. stderr, "cmp: Error: Non-numeric characters passed to '-n %s': %s\n", optarg, endptr);
  95. return 1;
  96. }
  97. break;
  98. case ':':
  99. fprintf(stderr, "cmp: Error: Missing operand for option: '-%c'\n", optopt);
  100. usage();
  101. return 1;
  102. case '?':
  103. fprintf(stderr, "cmp: Error: Unrecognised option: '-%c'\n", optopt);
  104. usage();
  105. return 1;
  106. default:
  107. abort();
  108. }
  109. }
  110. argc -= optind;
  111. argv += optind;
  112. if(argc != 2)
  113. {
  114. fprintf(stderr, "cmp: Expected 2 arguments, got %d arguments\n", argc);
  115. return 1;
  116. }
  117. if(strcmp(argv[0], argv[1]) == 0) return 0;
  118. assert(errno == 0);
  119. FILE *file1 = NULL;
  120. if(argv[0][0] == '-' && argv[0][1] == 0)
  121. file1 = stdin;
  122. else
  123. {
  124. file1 = fopen(argv[0], "r");
  125. if(file1 == NULL)
  126. {
  127. fprintf(stderr, "cmp: Error opening ā€˜%sā€™: %s\n", argv[0], strerror(errno));
  128. return 1;
  129. }
  130. }
  131. FILE *file2 = NULL;
  132. if(argv[1][0] == '-' && argv[1][1] == 0)
  133. file2 = stdin;
  134. else
  135. {
  136. file2 = fopen(argv[1], "r");
  137. if(file2 == NULL)
  138. {
  139. fprintf(stderr, "cmp: Error opening ā€˜%sā€™: %s\n", argv[1], strerror(errno));
  140. return 1;
  141. }
  142. }
  143. int ret = do_cmp(file1, argv[0], file2, argv[1]);
  144. fclose(file1);
  145. fclose(file2);
  146. return ret;
  147. }