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:
M | cmd/cmp.c | 111 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------- |
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;
}