cmp.c (5271B)
- // utils-std: Collection of commonly available Unix tools
- // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
- // SPDX-License-Identifier: MPL-2.0
- #define _POSIX_C_SOURCE 200809L
- #include "../lib/getopt_nolong.h"
- #include <assert.h>
- #include <errno.h>
- #include <fcntl.h> // open, posix_fadvise
- #include <stdbool.h>
- #include <stdio.h> // fopen, fprintf, getline
- #include <stdlib.h> // abort, strtoul
- #include <string.h> // strerror
- #include <sys/stat.h> // fstat
- #include <unistd.h> // getopt
- static bool opt_s = false, opt_l = false;
- static unsigned long max_bytes = 0;
- const char *argv0 = "cmp";
- #undef MIN
- #define MIN(a, b) (((a) < (b)) ? (a) : (b))
- static int
- do_cmp(FILE *file1, const char *name1, FILE *file2, const char *name2)
- {
- char *line1 = NULL, *line2 = NULL;
- size_t len1 = 0, len2 = 0;
- unsigned long pos = 1, ln = 1;
- errno = 0;
- while(true)
- {
- ssize_t nread1 = getline(&line1, &len1, file1);
- if(nread1 < 0)
- {
- if(!ferror(file1)) return 0;
- fprintf(stderr,
- "%s: error: Failed to read line %ld from file '%s': %s\n",
- argv0,
- ln,
- name1,
- strerror(errno));
- return 2;
- }
- ssize_t nread2 = getline(&line2, &len2, file2);
- if(nread2 < 0)
- {
- if(!ferror(file2))
- {
- if(!opt_s) fprintf(stderr, "%s: error: EOF on %s line %ld\n", argv0, name2, ln);
- return 2;
- }
- fprintf(stderr,
- "%s: error: Failed to read line %ld from file '%s': %s\n",
- argv0,
- ln,
- name1,
- strerror(errno));
- return 2;
- }
- for(ssize_t i = 0; i < MIN(nread1, nread2); i++)
- {
- if(max_bytes != 0 && pos + i >= max_bytes) return 0;
- if(line1[i] != line2[i])
- {
- if(opt_s) return 1;
- if(opt_l)
- printf("%ld %o %o\n", pos + i, line1[i], line2[i]);
- else
- printf("%s %s differ: char %zd, line %ld\n", name1, name2, i + 1, ln);
- return 1;
- }
- }
- assert(nread1 == nread2);
- pos += nread1;
- ln++;
- }
- return 0;
- }
- static bool
- fstat_cmp(int fd1, char *name1, int fd2, char *name2)
- {
- struct stat buf1, buf2;
- errno = 0;
- if(fstat(fd1, &buf1) != 0)
- {
- fprintf(stderr,
- "%s: warning: Failed getting status for file '%s': %s\n",
- argv0,
- name1,
- strerror(errno));
- return false;
- }
- errno = 0;
- if(fstat(fd2, &buf2) != 0)
- {
- fprintf(stderr,
- "%s: warning: Failed getting status for file '%s': %s\n",
- argv0,
- name2,
- strerror(errno));
- return false;
- }
- return buf1.st_ino == buf2.st_ino && buf1.st_dev == buf2.st_dev;
- }
- static void
- usage(void)
- {
- fprintf(stderr, "Usage: cmp [-l|-s] [-n max_bytes] file1 file2\n");
- }
- int
- main(int argc, char *argv[])
- {
- char *endptr = NULL;
- for(int c = -1; (c = getopt_nolong(argc, argv, ":ln:s")) != -1;)
- {
- switch(c)
- {
- case 'l':
- opt_l = true;
- break;
- case 's':
- opt_s = true;
- break;
- case 'n':
- errno = 0;
- max_bytes = strtoul(optarg, &endptr, 0);
- if(errno != 0)
- {
- fprintf(stderr, "%s: error: Failed parsing '-n %s': %s\n", argv0, optarg, strerror(errno));
- return 2;
- }
- if(endptr != NULL && endptr[0] != 0)
- {
- fprintf(stderr,
- "%s: error: Non-numeric characters passed to '-n %s': %s\n",
- argv0,
- optarg,
- endptr);
- return 2;
- }
- break;
- case ':':
- fprintf(stderr, "%s: error: Missing operand for option: '-%c'\n", argv0, optopt);
- usage();
- return 2;
- case '?':
- GETOPT_UNKNOWN_OPT
- usage();
- return 2;
- default:
- abort();
- }
- }
- argc -= optind;
- argv += optind;
- if(argc != 2)
- {
- fprintf(stderr, "%s: error: Expected 2 arguments, got %d arguments\n", argv0, argc);
- return 2;
- }
- if(strcmp(argv[0], argv[1]) == 0) return 0;
- int fd1 = STDIN_FILENO;
- if(!(argv[0][0] == '-' && argv[0][1] == 0))
- {
- errno = 0;
- fd1 = open(argv[0], O_RDONLY);
- if(errno != 0)
- {
- fprintf(stderr, "%s: error: Failed opening file '%s': %s\n", argv0, argv[0], strerror(errno));
- return 2;
- }
- }
- int fd2 = STDIN_FILENO;
- if(!(argv[1][0] == '-' && argv[1][1] == 0))
- {
- errno = 0;
- fd2 = open(argv[1], O_RDONLY);
- if(errno != 0)
- {
- fprintf(stderr, "%s: error: Failed opening file '%s': %s\n", argv0, argv[1], strerror(errno));
- return 2;
- }
- }
- if(fstat_cmp(fd1, argv[0], fd2, argv[1]))
- {
- if(fd1 != STDIN_FILENO) close(fd1);
- if(fd2 != STDIN_FILENO) close(fd2);
- return 0;
- }
- FILE *file1 = stdin;
- FILE *file2 = stdin;
- if(fd1 != STDIN_FILENO)
- {
- errno = 0;
- file1 = fdopen(fd1, "r");
- if(file1 == (FILE *)NULL)
- {
- fprintf(stderr,
- "%s: error: Failed associating stream for file '%s': %s\n",
- argv0,
- argv[0],
- strerror(errno));
- return 2;
- }
- posix_fadvise(fd1, 0, 0, POSIX_FADV_SEQUENTIAL);
- }
- if(fd2 != STDIN_FILENO)
- {
- errno = 0;
- file2 = fdopen(fd2, "r");
- if(file2 == (FILE *)NULL)
- {
- fprintf(stderr,
- "%s: error: Failed associating stream for file '%s': %s\n",
- argv0,
- argv[1],
- strerror(errno));
- return 2;
- }
- posix_fadvise(fd2, 0, 0, POSIX_FADV_SEQUENTIAL);
- }
- int ret = do_cmp(file1, argv[0], file2, argv[1]);
- if(file1 != stdin) fclose(file1);
- if(file2 != stdin) fclose(file2);
- return ret;
- }