logo

utils-std

Collection of commonly available Unix tools git clone https://anongit.hacktivis.me/git/utils-std.git/
commit: 20dc0e9ae15d0a2c8a216803bcdecca343f34423
parent 530f0fc4f8110f461e796e83934ee132893089e5
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Tue, 10 Jun 2025 19:57:39 +0200

cmd/cmp: Use fstat(3) to avoid comparing the same file in case of sym/hard-links

Diffstat:

Mcmd/cmp.c111++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 93 insertions(+), 18 deletions(-)

diff --git a/cmd/cmp.c b/cmd/cmp.c @@ -7,11 +7,13 @@ #include <assert.h> #include <errno.h> +#include <fcntl.h> // open #include <stdbool.h> -#include <stdio.h> // fopen, fprintf, getline -#include <stdlib.h> // abort, strtoul -#include <string.h> // strerror -#include <unistd.h> // getopt +#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; @@ -27,6 +29,8 @@ do_cmp(FILE *file1, const char *name1, FILE *file2, const char *name2) size_t len1 = 0, len2 = 0; unsigned long pos = 1, ln = 1; + errno = 0; + while(true) { ssize_t nread1 = getline(&line1, &len1, file1); @@ -88,6 +92,36 @@ do_cmp(FILE *file1, const char *name1, FILE *file2, const char *name2) 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) { @@ -151,36 +185,77 @@ main(int argc, char *argv[]) if(strcmp(argv[0], argv[1]) == 0) return 0; - FILE *file1 = NULL; - if(argv[0][0] == '-' && argv[0][1] == 0) - file1 = stdin; - else + int fd1 = STDIN_FILENO; + if(!(argv[0][0] == '-' && argv[0][1] == 0)) { - file1 = fopen(argv[0], "r"); - if(file1 == NULL) + 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 1; } } - FILE *file2 = NULL; - if(argv[1][0] == '-' && argv[1][1] == 0) - file2 = stdin; - else + int fd2 = STDIN_FILENO; + if(!(argv[1][0] == '-' && argv[1][1] == 0)) { - file2 = fopen(argv[1], "r"); - if(file2 == NULL) + 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 1; } } + 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 1; + } + + } + + 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 1; + } + + } + int ret = do_cmp(file1, argv[0], file2, argv[1]); - fclose(file1); - fclose(file2); + if(file1 != stdin) fclose(file1); + if(file2 != stdin) fclose(file2); return ret; }