diff --git a/usr.bin/diff/diff.h b/usr.bin/diff/diff.h --- a/usr.bin/diff/diff.h +++ b/usr.bin/diff/diff.h @@ -101,7 +101,7 @@ }; extern bool lflag, Nflag, Pflag, rflag, sflag, Tflag, cflag; -extern bool ignore_file_case, suppress_common, color; +extern bool ignore_file_case, suppress_common, color, noderef; extern int diff_format, diff_context, status; extern int tabsize, width; extern char *start, *ifdefname, *diffargs, *label[2]; diff --git a/usr.bin/diff/diff.c b/usr.bin/diff/diff.c --- a/usr.bin/diff/diff.c +++ b/usr.bin/diff/diff.c @@ -39,7 +39,7 @@ #include "xmalloc.h" bool lflag, Nflag, Pflag, rflag, sflag, Tflag, cflag; -bool ignore_file_case, suppress_common, color; +bool ignore_file_case, suppress_common, color, noderef; int diff_format, diff_context, status; int tabsize = 8, width = 130; static int colorflag = COLORFLAG_NEVER; @@ -62,6 +62,7 @@ OPT_CHANGED_GROUP_FORMAT, OPT_SUPPRESS_COMMON, OPT_COLOR, + OPT_NO_DEREFERENCE, }; static struct option longopts[] = { @@ -97,6 +98,7 @@ { "side-by-side", no_argument, NULL, 'y' }, { "ignore-file-name-case", no_argument, NULL, OPT_IGN_FN_CASE }, { "horizon-lines", required_argument, NULL, OPT_HORIZON_LINES }, + { "no-dereference", no_argument, NULL, OPT_NO_DEREFERENCE}, { "no-ignore-file-name-case", no_argument, NULL, OPT_NO_IGN_FN_CASE }, { "normal", no_argument, NULL, OPT_NORMAL }, { "strip-trailing-cr", no_argument, NULL, OPT_STRIPCR }, @@ -328,6 +330,10 @@ errx(2, "unsupported --color value '%s' (must be always, auto, or never)", optarg); break; + case OPT_NO_DEREFERENCE: + rflag = true; + noderef = true; + break; default: usage(); break; diff --git a/usr.bin/diff/diffdir.c b/usr.bin/diff/diffdir.c --- a/usr.bin/diff/diffdir.c +++ b/usr.bin/diff/diffdir.c @@ -33,6 +33,7 @@ #include #include #include +#include #include "diff.h" @@ -175,28 +176,87 @@ { flags |= D_HEADER; strlcpy(path1 + plen1, dp->d_name, PATH_MAX - plen1); - if (stat(path1, &stb1) != 0) { - if (!(Nflag || Pflag) || errno != ENOENT) { - warn("%s", path1); - return; + strlcpy(path2 + plen2, dp->d_name, PATH_MAX - plen2); + + if (noderef) { + if (lstat(path1, &stb1) != 0) { + if (!(Nflag || Pflag) || errno != ENOENT) { + warn("%s", path1); + return; + } + flags |= D_EMPTY1; + memset(&stb1, 0, sizeof(stb1)); } - flags |= D_EMPTY1; - memset(&stb1, 0, sizeof(stb1)); - } - strlcpy(path2 + plen2, dp->d_name, PATH_MAX - plen2); - if (stat(path2, &stb2) != 0) { - if (!Nflag || errno != ENOENT) { - warn("%s", path2); + if (lstat(path2, &stb2) != 0) { + if (!Nflag || errno != ENOENT) { + warn("%s", path2); + return; + } + flags |= D_EMPTY2; + memset(&stb2, 0, sizeof(stb2)); + stb2.st_mode = stb1.st_mode; + } + if (stb1.st_mode == 0) + stb1.st_mode = stb2.st_mode; + if (S_ISLNK(stb1.st_mode) || S_ISLNK(stb2.st_mode)) { + if (S_ISLNK(stb1.st_mode) && S_ISLNK(stb2.st_mode)) { + char buf1[PATH_MAX]; + char buf2[PATH_MAX]; + ssize_t len1 = 0; + ssize_t len2 = 0; + + len1 = readlink(path1, buf1, sizeof(buf1)); + len2 = readlink(path2, buf2, sizeof(buf2)); + + if (len1 < 0 || len2 < 0) { + perror("reading links"); + return; + } + buf1[len1] = '\0'; + buf2[len2] = '\0'; + + if (len1 != len2 || strncmp(buf1, buf2, len1) != 0) { + printf("Symbolic links %s and %s differ\n", + path1, path2); + status |= 1; + } + + return; + } + + printf("File %s is a %s while file %s is a %s\n", + path1, S_ISLNK(stb1.st_mode) ? "symbolic link" : + (S_ISDIR(stb1.st_mode) ? "directory" : + (S_ISREG(stb1.st_mode) ? "file" : "error")), + path2, S_ISLNK(stb2.st_mode) ? "symbolic link" : + (S_ISDIR(stb2.st_mode) ? "directory" : + (S_ISREG(stb2.st_mode) ? "file" : "error"))); + status |= 1; return; } - flags |= D_EMPTY2; - memset(&stb2, 0, sizeof(stb2)); - stb2.st_mode = stb1.st_mode; - } - if (stb1.st_mode == 0) - stb1.st_mode = stb2.st_mode; + } else { + if (stat(path1, &stb1) != 0) { + if (!(Nflag || Pflag) || errno != ENOENT) { + warn("%s", path1); + return; + } + flags |= D_EMPTY1; + memset(&stb1, 0, sizeof(stb1)); + } + if (stat(path2, &stb2) != 0) { + if (!Nflag || errno != ENOENT) { + warn("%s", path2); + return; + } + flags |= D_EMPTY2; + memset(&stb2, 0, sizeof(stb2)); + stb2.st_mode = stb1.st_mode; + } + if (stb1.st_mode == 0) + stb1.st_mode = stb2.st_mode; + } if (S_ISDIR(stb1.st_mode) && S_ISDIR(stb2.st_mode)) { if (rflag) diffdir(path1, path2, flags); diff --git a/usr.bin/diff/tests/diff_test.sh b/usr.bin/diff/tests/diff_test.sh --- a/usr.bin/diff/tests/diff_test.sh +++ b/usr.bin/diff/tests/diff_test.sh @@ -20,6 +20,7 @@ atf_test_case non_regular_file atf_test_case binary atf_test_case functionname +atf_test_case noderef simple_body() { @@ -296,6 +297,35 @@ "$(atf_get_srcdir)/functionname.in" "$(atf_get_srcdir)/functionname_objcclassm.in" } +noderef_body() +{ + atf_check mkdir A B + + atf_check -x "echo 1 > A/test-file" + atf_check -x "echo 1 > test-file" + atf_check -x "echo 1 > test-file2" + + atf_check ln -s $(pwd)/test-file B/test-file + + atf_check -o empty -s exit:0 diff -r A B + atf_check -o inline:"File A/test-file is a file while file B/test-file is a symbolic link\n" \ + -s exit:1 diff -r --no-dereference A B + + # both test files are now the same symbolic link + atf_check rm A/test-file + + atf_check ln -s $(pwd)/test-file A/test-file + atf_check -o empty -s exit:0 diff -r A B + atf_check -o empty -s exit:0 diff -r --no-dereference A B + + # make test files different symbolic links, but same contents + atf_check unlink A/test-file + atf_check ln -s $(pwd)/test-file2 A/test-file + + atf_check -o empty -s exit:0 diff -r A B + atf_check -o inline:"Symbolic links A/test-file and B/test-file differ\n" -s exit:1 diff -r --no-dereference A B +} + atf_init_test_cases() { atf_add_test_case simple @@ -318,4 +348,5 @@ atf_add_test_case non_regular_file atf_add_test_case binary atf_add_test_case functionname + atf_add_test_case noderef }