]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
(save_cwd, restore_cwd): New functions.
authorJim Meyering <jim@meyering.net>
Tue, 21 Feb 1995 05:01:08 +0000 (05:01 +0000)
committerJim Meyering <jim@meyering.net>
Tue, 21 Feb 1995 05:01:08 +0000 (05:01 +0000)
(main): Use them instead of in-line versions.
(count_entry): Use them here instead of relying on chdir ("..") --
but only when necessary.  Before, with --dereference (-L),
traversing a symlink caused failure because chdir ("..") didn't
work as expected.  Reported by Bruno Haible.

src/du.c

index 57134f27ab079aeb127334b4244db5a117604376..7fba843ad7ce7ab34ee877568f3b0f2080c34453 100644 (file)
--- a/src/du.c
+++ b/src/du.c
@@ -81,6 +81,12 @@ struct htab
   struct entry *hash[1];       /* Vector of pointers in `entry_tab'.  */
 };
 
+struct saved_cwd
+  {
+    int desc;
+    char *name;
+  };
+
 
 /* Structure for dynamically resizable strings. */
 
@@ -183,6 +189,76 @@ static struct option const long_options[] =
   {NULL, 0, NULL, 0}
 };
 
+static void
+save_cwd (cwd)
+     struct saved_cwd *cwd;
+{
+  static int have_working_fchdir = 1;
+
+  if (have_working_fchdir)
+    {
+#ifdef HAVE_FCHDIR
+      cwd->desc = open (".", O_RDONLY);
+      if (cwd->desc < 0)
+       error (1, errno, "cannot open current directory");
+
+      /* On SunOS 4, fchdir returns EINVAL if accounting is enabled,
+        so we have to fall back to chdir.  */
+      if (fchdir (cwd->desc))
+       {
+         if (errno == EINVAL)
+           {
+             close (cwd->desc);
+             cwd->desc = -1;
+             have_working_fchdir = 0;
+           }
+         else
+           {
+             error (1, errno, "current directory");
+           }
+       }
+#else
+#define fchdir(x) (abort (), 0)
+      have_working_fchdir = 0;
+#endif
+    }
+
+  if (!have_working_fchdir)
+    {
+      cwd->name = xgetcwd ();
+      if (cwd->name == NULL)
+       error (1, errno, "cannot get current directory");
+    }
+  else
+    {
+      cwd->name = NULL;
+    }
+}
+
+static void
+restore_cwd (cwd, dest, current)
+     const struct saved_cwd *cwd;
+     const char *dest;
+     const char *current;
+{
+
+#ifndef HAVE_FCHDIR
+#define fchdir(x) (abort (), -1)
+#endif
+
+  if (cwd->desc >= 0)
+    {
+      if (fchdir (cwd->desc) < 0)
+       error (1, errno, "cannot return to %s%s%s", dest,
+              (current ? " from " : ""),
+              (current ? current : ""));
+    }
+  else if (chdir (cwd->name) < 0)
+    {
+      error (1, errno, "%s", cwd->name);
+    }
+}
+
 static void
 usage (status, reason)
      int status;
@@ -313,40 +389,12 @@ static void
 du_files (files)
      char **files;
 {
-#ifdef HAVE_FCHDIR
-  int starting_desc;
-#endif
-  char *starting_dir = NULL;
+  struct saved_cwd cwd;
   ino_t initial_ino;           /* Initial directory's inode. */
   dev_t initial_dev;           /* Initial directory's device. */
   int i;                       /* Index in FILES. */
 
-#ifdef HAVE_FCHDIR
-  starting_desc = open (".", O_RDONLY);
-  if (starting_desc < 0)
-    error (1, errno, "cannot open current directory");
-
-  /* On SunOS 4, fchdir returns EINVAL if accounting is enabled,
-     so we have to fall back to chdir.  */
-  if (fchdir (starting_desc))
-    {
-      if (errno == EINVAL)
-       {
-         close (starting_desc);
-         starting_desc = -1;
-       }
-      else
-       {
-         error (1, errno, "current directory");
-       }
-    }
-  if (starting_desc == -1)
-#endif
-  {
-    starting_dir = xgetcwd ();
-    if (starting_dir == NULL)
-      error (1, errno, "cannot get current directory");
-  }
+  save_cwd (&cwd);
 
   /* Remember the inode and device number of the current directory.  */
   if (safe_stat (".", &stat_buf))
@@ -385,16 +433,7 @@ du_files (files)
        error (1, errno, ".");
       if (stat_buf.st_ino != initial_ino || stat_buf.st_dev != initial_dev)
        {
-#ifdef HAVE_FCHDIR
-         if (starting_desc >= 0)
-           {
-             if (fchdir (starting_desc) < 0)
-               error (1, errno, "cannot return to starting directory");
-           }
-         else
-#endif
-         if (chdir (starting_dir) < 0)
-           error (1, errno, "%s", starting_dir);
+         restore_cwd (&cwd, "starting directory", NULL);
        }
     }
 
@@ -405,8 +444,8 @@ du_files (files)
       fflush (stdout);
     }
 
-  if (starting_dir != NULL)
-    free (starting_dir);
+  if (cwd.name != NULL)
+    free (cwd.name);
 }
 \f
 /* Print (if appropriate) and return the size
@@ -449,12 +488,27 @@ count_entry (ent, top, last_dev)
       dev_t dir_dev;
       char *name_space;
       char *namep;
+      struct saved_cwd cwd;
+      int through_symlink;
+      struct stat e_buf;
 
       dir_dev = stat_buf.st_dev;
 
       if (opt_one_file_system && !top && last_dev != dir_dev)
        return 0;               /* Don't enter a new file system.  */
 
+#ifndef S_ISDIR
+# define S_ISDIR(s) 0
+#endif
+      /* If we're dereferencing symlinks and we're about to chdir through
+        a symlink, remember the current directory so we can return to it
+        later.  In other cases, chdir ("..") works fine.  */
+      through_symlink = (xstat == safe_stat
+                        && safe_lstat (ent, &e_buf)
+                        && S_ISLNK (e_buf.st_mode));
+      if (through_symlink)
+       save_cwd (&cwd);
+
       if (chdir (ent) < 0)
        {
          error (0, errno, "cannot change to directory %s", path->text);
@@ -469,9 +523,15 @@ count_entry (ent, top, last_dev)
          if (errno)
            {
              error (0, errno, "%s", path->text);
-             if (chdir ("..") < 0)     /* Try to return to previous dir.  */
-               error (1, errno, "cannot change to `..' from directory %s",
-                      path->text);
+             if (through_symlink)
+               {
+                 restore_cwd (&cwd, "..", path->text);
+                 if (cwd.name != NULL)
+                   free (cwd.name);
+               }
+             else if (chdir ("..") < 0)
+                 error (1, errno, "cannot change to `..' from directory %s",
+                        path->text);
              exit_status = 1;
              return 0;
            }
@@ -495,7 +555,13 @@ count_entry (ent, top, last_dev)
          namep += strlen (namep) + 1;
        }
       free (name_space);
-      if (chdir ("..") < 0)
+      if (through_symlink)
+       {
+         restore_cwd (&cwd, "..", path->text);
+         if (cwd.name != NULL)
+           free (cwd.name);
+       }
+      else if (chdir ("..") < 0)
         error (1, errno, "cannot change to `..' from directory %s", path->text);
 
       str_trunc (path, pathlen - 1); /* Remove the "/" we added.  */