* move loop detection routine into separate function
* add a statx-enabled variant that is used when it's available. No need
  for a full stat since all we care about is the dev/ino.
* Since dev/ino should never change, set AT_STATX_DONT_SYNC
  unconditionally.
---
 src/ls.c | 106 +++++++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 84 insertions(+), 22 deletions(-)

diff --git a/src/ls.c b/src/ls.c
index 120ce153e340..8e5015e5ac12 100644
--- a/src/ls.c
+++ b/src/ls.c
@@ -114,6 +114,13 @@
 #include "xgethostname.h"
 #include "c-ctype.h"
 #include "canonicalize.h"
+#include "statx.h"
+
+#if HAVE_STATX && defined STATX_INO
+# define USE_STATX 1
+#else
+# define USE_STATX 0
+#endif
 
 /* Include <sys/capability.h> last to avoid a clash of <sys/types.h>
    include guards with some premature versions of libcap.
@@ -2711,27 +2718,54 @@ queue_directory (char const *name, char const 
*realname, bool command_line_arg)
   pending_dirs = new;
 }
 
-/* Read directory NAME, and list the files in it.
-   If REALNAME is nonzero, print its name instead of NAME;
-   this is used for symbolic links to directories.
-   COMMAND_LINE_ARG means this directory was mentioned on the command line.  */
-
-static void
-print_dir (char const *name, char const *realname, bool command_line_arg)
+#if USE_STATX
+static bool
+loop_detected (const char *name, DIR *dirp, bool command_line_arg)
 {
-  DIR *dirp;
-  struct dirent *next;
-  uintmax_t total_blocks = 0;
-  static bool first = true;
-
-  errno = 0;
-  dirp = opendir (name);
-  if (!dirp)
+  if (LOOP_DETECT)
     {
-      file_failure (command_line_arg, _("cannot open directory %s"), name);
-      return;
-    }
+      struct statx stx;
+      int fd = dirfd (dirp);
+      int flags = AT_STATX_DONT_SYNC;
+      const char *pathname = name;
+
+      if (0 <= fd)
+       {
+         pathname = "";
+         flags |= AT_EMPTY_PATH;
+       }
+      else
+        {
+         /* If dirfd failed, endure the overhead of statx by path. */
+         fd = AT_FDCWD;
+       }
+
+      if (statx (fd, pathname, flags, STATX_INO, &stx) < 0)
+        {
+          file_failure (command_line_arg,
+                        _("cannot determine device and inode of %s"), name);
+          return true;
+        }
 
+      /* If we've already visited this dev/inode pair, warn that
+         we've found a loop, and do not process this directory.  */
+      dev_t dev = makedev (stx.stx_dev_major, stx.stx_dev_minor);
+      if (visit_dir (dev, stx.stx_ino))
+        {
+          error (0, 0, _("%s: not listing already-listed directory"),
+                 quotef (name));
+          set_exit_status (true);
+          return true;
+        }
+
+      dev_ino_push (dev, stx.stx_ino);
+    }
+  return false;
+}
+#else
+static bool
+loop_detected (const char *name, DIR *dirp, bool command_line_arg)
+{
   if (LOOP_DETECT)
     {
       struct stat dir_stat;
@@ -2744,8 +2778,7 @@ print_dir (char const *name, char const *realname, bool 
command_line_arg)
         {
           file_failure (command_line_arg,
                         _("cannot determine device and inode of %s"), name);
-          closedir (dirp);
-          return;
+          return true;
         }
 
       /* If we've already visited this dev/inode pair, warn that
@@ -2754,13 +2787,42 @@ print_dir (char const *name, char const *realname, bool 
command_line_arg)
         {
           error (0, 0, _("%s: not listing already-listed directory"),
                  quotef (name));
-          closedir (dirp);
           set_exit_status (true);
-          return;
+          return true;
         }
 
       dev_ino_push (dir_stat.st_dev, dir_stat.st_ino);
     }
+  return false;
+}
+#endif
+
+/* Read directory NAME, and list the files in it.
+   If REALNAME is nonzero, print its name instead of NAME;
+   this is used for symbolic links to directories.
+   COMMAND_LINE_ARG means this directory was mentioned on the command line.  */
+
+static void
+print_dir (char const *name, char const *realname, bool command_line_arg)
+{
+  DIR *dirp;
+  struct dirent *next;
+  uintmax_t total_blocks = 0;
+  static bool first = true;
+
+  errno = 0;
+  dirp = opendir (name);
+  if (!dirp)
+    {
+      file_failure (command_line_arg, _("cannot open directory %s"), name);
+      return;
+    }
+
+  if (loop_detected(name, dirp, command_line_arg))
+    {
+      closedir (dirp);
+      return;
+    }
 
   clear_files ();
 
-- 
2.21.0


Reply via email to