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

Reply via email to