diff --no-dereference causes diff to not follow symlinks, instead comparing the link name.
Added some basic tests. --- lib/lib.h | 1 + lib/xwrap.c | 5 +++ tests/diff.test | 7 ++++ toys/pending/diff.c | 96 +++++++++++++++++++++++++++++++++++---------- 4 files changed, 88 insertions(+), 21 deletions(-)
From 70b83f76997297b91c11813813989bc07b42739b Mon Sep 17 00:00:00 2001 From: Daniel Rosenberg <dro...@google.com> Date: Tue, 30 Jul 2024 18:05:26 -0700 Subject: [PATCH] Support diff --no-dereference diff --no-dereference causes diff to not follow symlinks, instead comparing the link name. Added some basic tests. --- lib/lib.h | 1 + lib/xwrap.c | 5 +++ tests/diff.test | 7 ++++ toys/pending/diff.c | 96 +++++++++++++++++++++++++++++++++++---------- 4 files changed, 88 insertions(+), 21 deletions(-) diff --git a/lib/lib.h b/lib/lib.h index 23b1e680..167bed59 100644 --- a/lib/lib.h +++ b/lib/lib.h @@ -156,6 +156,7 @@ char *xreadfile(char *name, char *buf, off_t len); int xioctl(int fd, int request, void *data); char *xgetcwd(void); void xstat(char *path, struct stat *st); +void xlstat(char *path, struct stat *st); char *xabspath(char *path, int exact); void xchdir(char *path); void xchroot(char *path); diff --git a/lib/xwrap.c b/lib/xwrap.c index d07f355a..5098553f 100644 --- a/lib/xwrap.c +++ b/lib/xwrap.c @@ -576,6 +576,11 @@ void xstat(char *path, struct stat *st) if(stat(path, st)) perror_exit("Can't stat %s", path); } +void xlstat(char *path, struct stat *st) +{ + if(lstat(path, st)) perror_exit("Can't lstat %s", path); +} + // Canonicalize path, even to file with one or more missing components at end. // Returns allocated string for pathname or NULL if doesn't exist. Flags are: // ABS_PATH:path to last component must exist ABS_FILE: whole path must exist diff --git a/tests/diff.test b/tests/diff.test index 9361a41e..8c5834d3 100644 --- a/tests/diff.test +++ b/tests/diff.test @@ -43,6 +43,13 @@ echo -e "1\n3" > bb testcmd "line format" "--unchanged-line-format=U%l --old-line-format=D%l --new-line-format=A%l aa bb" "U1D2A3" "" "" testcmd "line format empty" "--unchanged-line-format= --old-line-format=D%l --new-line-format=A%l aa bb" "D2A3" "" "" +ln -s aa cc +testcmd "follow symlink" "-q -L aa -L cc aa cc" "" "" "" +testcmd "no follow symlink" "-q --no-dereference -L aa -L cc aa cc" "File aa is a regular file while file cc is a symbolic link\n" "" "" +ln -s ./aa dd +testcmd "symlink differs" "-q -L cc -L dd cc dd" "" "" "" +testcmd "symlink differs no follow" "-q --no-dereference -L cc -L dd cc dd" "Symbolic links cc and dd differ\n" "" "" + mkfifo fifo1 mkfifo fifo2 echo -e "1\n2" > fifo1& diff --git a/toys/pending/diff.c b/toys/pending/diff.c index 27873cfd..34bba904 100644 --- a/toys/pending/diff.c +++ b/toys/pending/diff.c @@ -8,7 +8,7 @@ * * Deviations from posix: always does -u -USE_DIFF(NEWTOY(diff, "<2>2(unchanged-line-format):;(old-line-format):;(new-line-format):;(color)(strip-trailing-cr)B(ignore-blank-lines)d(minimal)b(ignore-space-change)ut(expand-tabs)w(ignore-all-space)i(ignore-case)T(initial-tab)s(report-identical-files)q(brief)a(text)S(starting-file):F(show-function-line):;L(label)*N(new-file)r(recursive)U(unified)#<0=3", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2))) +USE_DIFF(NEWTOY(diff, "<2>2(unchanged-line-format):;(old-line-format):;(no-dereference);(new-line-format):;(color)(strip-trailing-cr)B(ignore-blank-lines)d(minimal)b(ignore-space-change)ut(expand-tabs)w(ignore-all-space)i(ignore-case)T(initial-tab)s(report-identical-files)q(brief)a(text)S(starting-file):F(show-function-line):;L(label)*N(new-file)r(recursive)U(unified)#<0=3", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2))) config DIFF bool "diff" @@ -35,6 +35,7 @@ config DIFF -w Ignore all whitespace --color Color output --strip-trailing-cr Strip '\r' from input lines + --no-dereference Don't follow symbolic links --TYPE-line-format=FORMAT Display TYPE (unchanged/old/new) lines using FORMAT FORMAT uses printf integer escapes (ala %-2.4x) followed by LETTER: FELMNn Supported format specifiers are: @@ -51,7 +52,7 @@ GLOBALS( struct arg_list *L; char *F, *S, *new_line_format, *old_line_format, *unchanged_line_format; - int dir_num, size, is_binary, differ, change, len[2], *offset[2]; + int dir_num, size, is_binary, is_symlink, differ, change, len[2], *offset[2]; struct stat st[2]; struct { char **list; @@ -61,6 +62,10 @@ GLOBALS( FILE *fp; int len; } file[2]; + struct { + char *name; + int len; + } link[2]; ) #define IS_STDIN(s) (*(s)=='-' && !(s)[1]) @@ -512,19 +517,19 @@ static int list_dir(struct dirtree *node) if (S_ISDIR(node->st.st_mode) && !node->parent) { //add root dirs. add_to_list(node); - return (DIRTREE_RECURSE|DIRTREE_SYMFOLLOW); + return (DIRTREE_RECURSE|((FLAG(no_dereference)) ? 0 : DIRTREE_SYMFOLLOW)); } if (S_ISDIR(node->st.st_mode) && FLAG(r)) { if (!FLAG(N)) ret = skip(node); - if (!ret) return DIRTREE_RECURSE|DIRTREE_SYMFOLLOW; + if (!ret) return DIRTREE_RECURSE|((FLAG(no_dereference)) ? 0 : DIRTREE_SYMFOLLOW); else { add_to_list(node); //only at one side. return 0; } } else { add_to_list(node); - return S_ISDIR(node->st.st_mode) ? 0 : (DIRTREE_RECURSE|DIRTREE_SYMFOLLOW); + return S_ISDIR(node->st.st_mode) ? 0 : (DIRTREE_RECURSE|((FLAG(no_dereference)) ? 0 : DIRTREE_SYMFOLLOW)); } } @@ -578,6 +583,32 @@ static void show_label(char *prefix, char *filename, struct stat *sb) free(quoted_file); } +static void do_symlink_diff(char **files) +{ + size_t i; + int s = sizeof(toybuf)/2; + + TT.is_symlink = 1; + for (i = 0; i < 2; i++) { + TT.link[i].name = toybuf + i * s; + TT.link[i].len = readlink0(files[i], TT.link[i].name, s); + if (TT.link[i].len == 0) { + perror_msg("readlink failed"); + TT.differ = 2; + return; + } + } + + if (TT.link[0].len != TT.link[1].len) { + TT.differ = 1; + return; + } + + if (smemcmp(TT.link[0].name, TT.link[1].name, TT.link[0].len)) + TT.differ = 1; + return; +} + static void do_diff(char **files) { long i = 1, size = 1, x = 0, change = 0, ignore_white, @@ -717,9 +748,9 @@ calc_ct: static void show_status(char **files) { if (TT.differ==2) return; // TODO: needed? - if (TT.differ ? FLAG(q) || TT.is_binary : FLAG(s)) - printf("Files %s and %s %s\n", files[0], files[1], - TT.differ ? "differ" : "are identical"); + if (TT.differ ? FLAG(q) || TT.is_binary || TT.is_symlink : FLAG(s)) + printf("%s %s and %s %s\n", TT.is_symlink ? "Symbolic links" : "Files", + files[0], files[1], TT.differ ? "differ" : "are identical"); } static void create_empty_entry(int l , int r, int j) @@ -736,25 +767,33 @@ static void create_empty_entry(int l , int r, int j) f[!i] = "/dev/null"; } path[i] = f[i] = TT.dir[i].list[i ? r : l]; - stat(f[i], st+i); + if (FLAG(no_dereference)) + lstat(f[i], st+i); + else + stat(f[i], st+i); if (j) st[!i] = st[i]; } for (i = 0; i<2; i++) { - if (!S_ISREG(st[i].st_mode) && !S_ISDIR(st[i].st_mode)) { - printf("File %s is not a regular file or directory and was skipped\n", + if (!S_ISREG(st[i].st_mode) && !S_ISDIR(st[i].st_mode) && !S_ISLNK(st[i].st_mode)) { + printf("File %s is not a regular file, symbolic link, or directory and was skipped\n", path[i]); break; } } if (i != 2); - else if (S_ISDIR(st[0].st_mode) && S_ISDIR(st[1].st_mode)) - printf("Common subdirectories: %s and %s\n", path[0], path[1]); - else if ((i = S_ISDIR(st[0].st_mode)) != S_ISDIR(st[1].st_mode)) { - char *fidir[] = {"directory", "regular file"}; + else if ((st[0].st_mode & S_IFMT) != (st[1].st_mode & S_IFMT)) { + i = S_ISREG(st[0].st_mode) + 2 * S_ISLNK(st[0].st_mode); + int k = S_ISREG(st[1].st_mode) + 2 * S_ISLNK(st[1].st_mode); + char *fidir[] = {"directory", "regular file", "symbolic link"}; printf("File %s is a %s while file %s is a %s\n", - path[0], fidir[!i], path[1], fidir[i]); + path[0], fidir[i], path[1], fidir[k]); + } else if (S_ISDIR(st[0].st_mode)) + printf("Common subdirectories: %s and %s\n", path[0], path[1]); + else if (S_ISLNK(st[0].st_mode)) { + do_symlink_diff(f); + show_status(path); } else { do_diff(f); show_status(path); @@ -814,14 +853,17 @@ static void diff_dir(int *start) void diff_main(void) { - int j = 0, k = 1, start[2] = {1, 1}; + int i, j = 0, k = 1, start[2] = {1, 1}; char **files = toys.optargs; toys.exitval = 2; if (FLAG(color) && !isatty(1)) toys.optflags ^= FLAG_color; for (j = 0; j < 2; j++) { + { + } if (IS_STDIN(files[j])) fstat(0, &TT.st[j]); + else if (FLAG(no_dereference)) xlstat(files[j], &TT.st[j]); else xstat(files[j], &TT.st[j]); } @@ -844,7 +886,7 @@ void diff_main(void) if (S_ISDIR(TT.st[0].st_mode) && S_ISDIR(TT.st[1].st_mode)) { for (j = 0; j < 2; j++) { memset(TT.dir+j, 0, sizeof(*TT.dir)); - dirtree_flagread(files[j], DIRTREE_SYMFOLLOW, list_dir); + dirtree_flagread(files[j], (FLAG(no_dereference)) ? 0 : DIRTREE_SYMFOLLOW, list_dir); TT.dir[j].nr_elm = TT.size; //size updated in list_dir qsort(&TT.dir[j].list[1], TT.size-1, sizeof(char *), (void *)cmp); @@ -868,10 +910,22 @@ void diff_main(void) char *slash = strrchr(files[d], '/'); files[!d] = concat_file_path(files[!d], slash ? slash+1 : files[d]); - if (stat(files[!d], &TT.st[!d])) perror_exit("%s", files[!d]); + if (FLAG(no_dereference)) { + if (lstat(files[!d], &TT.st[!d])) perror_exit("%s", files[!d]); + } else { + if (stat(files[!d], &TT.st[!d])) perror_exit("%s", files[!d]); + } + } + if ((i = S_ISREG(TT.st[0].st_mode)) != S_ISREG(TT.st[1].st_mode)) { + char *fidir[] = {"regular file", "symbolic link"}; + printf("File %s is a %s while file %s is a %s\n", + files[0], fidir[!i], files[1], fidir[i]); + TT.differ = 1; + } else { + if (S_ISLNK(TT.st[0].st_mode)) do_symlink_diff(files); + else do_diff(files); + show_status(files); } - do_diff(files); - show_status(files); if (TT.file[0].fp) fclose(TT.file[0].fp); if (TT.file[1].fp) fclose(TT.file[1].fp); } base-commit: 0b2d5c2bb3f198acda0b340ec0e5be4ae75b7914 -- 2.46.0.76.ge559c4bf1a-goog
_______________________________________________ Toybox mailing list Toybox@lists.landley.net http://lists.landley.net/listinfo.cgi/toybox-landley.net