]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
mv: when installing to dir use dir-relative names
authorPaul Eggert <eggert@cs.ucla.edu>
Sat, 29 Jan 2022 19:40:17 +0000 (11:40 -0800)
committerPaul Eggert <eggert@cs.ucla.edu>
Sun, 30 Jan 2022 00:29:18 +0000 (16:29 -0800)
When the destination for mv is a directory, use functions like openat
to access the destination files, when such functions are available.
This should be more efficient and should avoid some race conditions.
Likewise for 'install'.
* src/cp.c (must_be_working_directory, target_directory_operand)
(target_dirfd_valid): Move from here ...
* src/system.h: ... to here, so that install and mv can use them.
Make them inline so GCC doesn’t complain.
* src/install.c (lchown) [HAVE_LCHOWN]: Remove; no longer needed.
(need_copy, copy_file, change_attributes, change_timestamps)
(install_file_in_file, install_file_in_dir):
New args for directory-relative names.  All uses changed.
Continue to pass full names as needed, for diagnostics and for
lower-level functions that do not support directory-relative names.
(install_file_in_dir): Update *TARGET_DIRFD as needed.
(main): Handle target-directory in the new, cp-like way.
* src/mv.c (remove_trailing_slashes): Remove static var; now local.
(do_move): New args for directory-relative names.  All uses changed.
Continue to pass full names as needed, for diagnostics and for
lower-level functions that do not support directory-relative names.
(movefile): Remove; no longer needed.
(main): Handle target-directory in the new, cp-like way.
* tests/install/basic-1.sh:
* tests/mv/diag.sh: Adjust to match new diagnostic wording.

NEWS
src/cp.c
src/install.c
src/mv.c
src/system.h
tests/install/basic-1.sh
tests/mv/diag.sh

diff --git a/NEWS b/NEWS
index ebcd5cb2f5072cb5b0e7396c53b0d33fbf04d07c..711f7811a05aca4b6803e73c40371c930acfac39 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -52,7 +52,7 @@ GNU coreutils NEWS                                    -*- outline -*-
 
 ** Improvements
 
-  cp now uses openat and similar syscalls when copying to a directory.
+  cp, mv, and install now use openat-like syscalls when copying to a directory.
   This avoids some race conditions and should be more efficient.
 
   On macOS, cp creates a copy-on-write clone if source and destination
index 3bcc0d681d251b9f3821f8e07d0f421be0ee569f..d680eb01d20ba87d413e9bbd0c372c761f077f1d 100644 (file)
--- a/src/cp.c
+++ b/src/cp.c
@@ -564,63 +564,6 @@ make_dir_parents_private (char const *const_dir, size_t src_offset,
   return true;
 }
 
-/* Must F designate the working directory?  */
-
-ATTRIBUTE_PURE static bool
-must_be_working_directory (char const *f)
-{
-  /* Return true for ".", "./.", ".///./", etc.  */
-  while (*f++ == '.')
-    {
-      if (*f != '/')
-        return !*f;
-      while (*++f == '/')
-        continue;
-      if (!*f)
-        return true;
-    }
-  return false;
-}
-
-/* Return a file descriptor open to FILE, for use in openat.
-   As an optimization, return AT_FDCWD if FILE must be the working directory.
-   Fail if FILE is not a directory.
-   On failure return a negative value; this is -1 unless AT_FDCWD == -1.  */
-
-static int
-target_directory_operand (char const *file)
-{
-  if (must_be_working_directory (file))
-    return AT_FDCWD;
-
-  int fd = open (file, O_PATHSEARCH | O_DIRECTORY);
-
-  if (!O_DIRECTORY && 0 <= fd)
-    {
-      /* On old systems like Solaris 10 that do not support O_DIRECTORY,
-         check by hand whether FILE is a directory.  */
-      struct stat st;
-      int err;
-      if (fstat (fd, &st) != 0 ? (err = errno, true)
-          : !S_ISDIR (st.st_mode) && (err = ENOTDIR, true))
-        {
-          close (fd);
-          errno = err;
-          fd = -1;
-        }
-    }
-
-  return fd - (AT_FDCWD == -1 && fd < 0);
-}
-
-/* Return true if FD represents success for target_directory_operand.  */
-
-static bool
-target_dirfd_valid (int fd)
-{
-  return fd != -1 - (AT_FDCWD == -1);
-}
-
 /* Scan the arguments, and copy each by calling copy.
    Return true if successful.  */
 
index b157f59d28282004cae8ceee86b34b4269467826..a27a5343b4ed2fd2d1af7ea464bbe254b933d539 100644 (file)
@@ -61,10 +61,6 @@ static bool use_default_selinux_context = true;
 # define endpwent() ((void) 0)
 #endif
 
-#if ! HAVE_LCHOWN
-# define lchown(name, uid, gid) chown (name, uid, gid)
-#endif
-
 /* The user name that will own the files, or NULL to make the owner
    the current user ID. */
 static char *owner_name;
@@ -165,9 +161,11 @@ extra_mode (mode_t input)
   return !! (input & ~ mask);
 }
 
-/* Return true if copy of file SRC_NAME to file DEST_NAME is necessary. */
+/* Return true if copy of file SRC_NAME to file DEST_NAME aka
+   DEST_DIRFD+DEST_RELNAME is necessary. */
 static bool
 need_copy (char const *src_name, char const *dest_name,
+           int dest_dirfd, char const *dest_relname,
            const struct cp_options *x)
 {
   struct stat src_sb, dest_sb;
@@ -181,7 +179,7 @@ need_copy (char const *src_name, char const *dest_name,
   if (lstat (src_name, &src_sb) != 0)
     return true;
 
-  if (lstat (dest_name, &dest_sb) != 0)
+  if (lstatat (dest_dirfd, dest_relname, &dest_sb) != 0)
     return true;
 
   if (!S_ISREG (src_sb.st_mode) || !S_ISREG (dest_sb.st_mode)
@@ -241,7 +239,7 @@ need_copy (char const *src_name, char const *dest_name,
   if (src_fd < 0)
     return true;
 
-  dest_fd = open (dest_name, O_RDONLY | O_BINARY);
+  dest_fd = openat (dest_dirfd, dest_relname, O_RDONLY | O_BINARY);
   if (dest_fd < 0)
     {
       close (src_fd);
@@ -353,28 +351,6 @@ setdefaultfilecon (char const *file)
   freecon (scontext);
 }
 
-/* FILE is the last operand of this command.  Return true if FILE is a
-   directory.  But report an error there is a problem accessing FILE,
-   or if FILE does not exist but would have to refer to an existing
-   directory if it referred to anything at all.  */
-
-static bool
-target_directory_operand (char const *file)
-{
-  char const *b = last_component (file);
-  size_t blen = strlen (b);
-  bool looks_like_a_dir = (blen == 0 || ISSLASH (b[blen - 1]));
-  struct stat st;
-  int err = (stat (file, &st) == 0 ? 0 : errno);
-  bool is_a_dir = !err && S_ISDIR (st.st_mode);
-  if (err && err != ENOENT)
-    die (EXIT_FAILURE, err, _("failed to access %s"), quoteaf (file));
-  if (is_a_dir < looks_like_a_dir)
-    die (EXIT_FAILURE, err, _("target %s is not a directory"),
-         quoteaf (file));
-  return is_a_dir;
-}
-
 /* Report that directory DIR was made, if OPTIONS requests this.  */
 static void
 announce_mkdir (char const *dir, void *options)
@@ -431,15 +407,16 @@ process_dir (char *dir, struct savewd *wd, void *options)
   return ret;
 }
 
-/* Copy file FROM onto file TO, creating TO if necessary.
-   Return true if successful.  */
+/* Copy file FROM onto file TO aka TO_DIRFD+TO_RELNAME, creating TO if
+   necessary.  Return true if successful.  */
 
 static bool
-copy_file (char const *from, char const *to, const struct cp_options *x)
+copy_file (char const *from, char const *to,
+           int to_dirfd, char const *to_relname, const struct cp_options *x)
 {
   bool copy_into_self;
 
-  if (copy_only_if_needed && !need_copy (from, to, x))
+  if (copy_only_if_needed && !need_copy (from, to, to_dirfd, to_relname, x))
     return true;
 
   /* Allow installing from non-regular files like /dev/null.
@@ -448,14 +425,14 @@ copy_file (char const *from, char const *to, const struct cp_options *x)
      However, since !x->recursive, the call to "copy" will fail if FROM
      is a directory.  */
 
-  return copy (from, to, AT_FDCWD, to, 0, x, &copy_into_self, NULL);
+  return copy (from, to, to_dirfd, to_relname, 0, x, &copy_into_self, NULL);
 }
 
-/* Set the attributes of file or directory NAME.
+/* Set the attributes of file or directory NAME aka DIRFD+RELNAME.
    Return true if successful.  */
 
 static bool
-change_attributes (char const *name)
+change_attributes (char const *name, int dirfd, char const *relname)
 {
   bool ok = false;
   /* chown must precede chmod because on some systems,
@@ -471,9 +448,9 @@ change_attributes (char const *name)
      want to know.  */
 
   if (! (owner_id == (uid_t) -1 && group_id == (gid_t) -1)
-      && lchown (name, owner_id, group_id) != 0)
+      && lchownat (dirfd, relname, owner_id, group_id) != 0)
     error (0, errno, _("cannot change ownership of %s"), quoteaf (name));
-  else if (chmod (name, mode) != 0)
+  else if (chmodat (dirfd, relname, mode) != 0)
     error (0, errno, _("cannot change permissions of %s"), quoteaf (name));
   else
     ok = true;
@@ -484,17 +461,18 @@ change_attributes (char const *name)
   return ok;
 }
 
-/* Set the timestamps of file DEST to match those of SRC_SB.
+/* Set the timestamps of file DEST aka DIRFD+RELNAME to match those of SRC_SB.
    Return true if successful.  */
 
 static bool
-change_timestamps (struct stat const *src_sb, char const *dest)
+change_timestamps (struct stat const *src_sb, char const *dest,
+                   int dirfd, char const *relname)
 {
   struct timespec timespec[2];
   timespec[0] = get_stat_atime (src_sb);
   timespec[1] = get_stat_mtime (src_sb);
 
-  if (utimens (dest, timespec))
+  if (utimensat (dirfd, relname, timespec, 0))
     {
       error (0, errno, _("cannot set timestamps for %s"), quoteaf (dest));
       return false;
@@ -653,12 +631,13 @@ In the 4th form, create all components of the given DIRECTORY(ies).\n\
   exit (status);
 }
 
-/* Copy file FROM onto file TO and give TO the appropriate
-   attributes.
+/* Copy file FROM onto file TO aka TO_DIRFD+TO_RELNAME and give TO the
+   appropriate attributes.  X gives the command options.
    Return true if successful.  */
 
 static bool
 install_file_in_file (char const *from, char const *to,
+                      int to_dirfd, char const *to_relname,
                       const struct cp_options *x)
 {
   struct stat from_sb;
@@ -667,19 +646,19 @@ install_file_in_file (char const *from, char const *to,
       error (0, errno, _("cannot stat %s"), quoteaf (from));
       return false;
     }
-  if (! copy_file (from, to, x))
+  if (! copy_file (from, to, to_dirfd, to_relname, x))
     return false;
   if (strip_files)
     if (! strip (to))
       {
-        if (unlink (to) != 0)  /* Cleanup.  */
+        if (unlinkat (to_dirfd, to_relname, 0) != 0)  /* Cleanup.  */
           die (EXIT_FAILURE, errno, _("cannot unlink %s"), quoteaf (to));
         return false;
       }
   if (x->preserve_timestamps && (strip_files || ! S_ISREG (from_sb.st_mode))
-      && ! change_timestamps (&from_sb, to))
+      && ! change_timestamps (&from_sb, to, to_dirfd, to_relname))
     return false;
-  return change_attributes (to);
+  return change_attributes (to, to_dirfd, to_relname);
 }
 
 /* Create any missing parent directories of TO,
@@ -731,7 +710,7 @@ install_file_in_file_parents (char const *from, char *to,
                               const struct cp_options *x)
 {
   return (mkancesdirs_safe_wd (from, to, (struct cp_options *)x, false)
-          && install_file_in_file (from, to, x));
+          && install_file_in_file (from, to, AT_FDCWD, to, x));
 }
 
 /* Copy file FROM into directory TO_DIR, keeping its same name,
@@ -740,16 +719,39 @@ install_file_in_file_parents (char const *from, char *to,
 
 static bool
 install_file_in_dir (char const *from, char const *to_dir,
-                     const struct cp_options *x, bool mkdir_and_install)
+                     const struct cp_options *x, bool mkdir_and_install,
+                     int *target_dirfd)
 {
   char const *from_base = last_component (from);
-  char *to = file_name_concat (to_dir, from_base, NULL);
+  char *to_relname;
+  char *to = file_name_concat (to_dir, from_base, &to_relname);
   bool ret = true;
 
-  if (mkdir_and_install)
-    ret = mkancesdirs_safe_wd (from, to, (struct cp_options *)x, true);
+  if (!target_dirfd_valid (*target_dirfd)
+      && (ret = mkdir_and_install)
+      && (ret = mkancesdirs_safe_wd (from, to, (struct cp_options *) x, true)))
+    {
+      int fd = open (to_dir, O_PATHSEARCH | O_DIRECTORY);
+      if (fd < 0)
+        {
+          error (0, errno, _("cannot open %s"), quoteaf (to));
+          ret = false;
+        }
+      else
+        *target_dirfd = fd;
+    }
+
+  if (ret)
+    {
+      int to_dirfd = *target_dirfd;
+      if (!target_dirfd_valid (to_dirfd))
+        {
+          to_dirfd = AT_FDCWD;
+          to_relname = to;
+        }
+      ret = install_file_in_file (from, to, to_dirfd, to_relname, x);
+    }
 
-  ret = ret && install_file_in_file (from, to, x);
   free (to);
   return ret;
 }
@@ -899,18 +901,6 @@ main (int argc, char **argv)
     die (EXIT_FAILURE, 0,
          _("target directory not allowed when installing a directory"));
 
-  if (target_directory)
-    {
-      struct stat st;
-      bool stat_success = stat (target_directory, &st) == 0 ? true : false;
-      if (! mkdir_and_install && ! stat_success)
-        die (EXIT_FAILURE, errno, _("failed to access %s"),
-             quoteaf (target_directory));
-      if (stat_success && ! S_ISDIR (st.st_mode))
-        die (EXIT_FAILURE, 0, _("target %s is not a directory"),
-             quoteaf (target_directory));
-    }
-
   x.backup_type = (make_backups
                    ? xget_version (_("backup type"),
                                    version_control_string)
@@ -939,6 +929,7 @@ main (int argc, char **argv)
       usage (EXIT_FAILURE);
     }
 
+  int target_dirfd = AT_FDCWD;
   if (no_target_directory)
     {
       if (target_directory)
@@ -951,13 +942,26 @@ main (int argc, char **argv)
           usage (EXIT_FAILURE);
         }
     }
-  else if (! (dir_arg || target_directory))
+  else if (target_directory)
     {
-      if (2 <= n_files && target_directory_operand (file[n_files - 1]))
-        target_directory = file[--n_files];
+      target_dirfd = target_directory_operand (target_directory);
+      if (! (target_dirfd_valid (target_dirfd)
+             || (mkdir_and_install && errno == ENOENT)))
+        die (EXIT_FAILURE, errno, _("failed to access %s"),
+             quoteaf (target_directory));
+    }
+  else if (!dir_arg)
+    {
+      char const *lastfile = file[n_files - 1];
+      int fd = target_directory_operand (lastfile);
+      if (target_dirfd_valid (fd))
+        {
+          target_dirfd = fd;
+          target_directory = lastfile;
+          n_files--;
+        }
       else if (2 < n_files)
-        die (EXIT_FAILURE, 0, _("target %s is not a directory"),
-             quoteaf (file[n_files - 1]));
+        die (EXIT_FAILURE, errno, _("target %s"), quoteaf (lastfile));
     }
 
   if (specified_mode)
@@ -1006,7 +1010,8 @@ main (int argc, char **argv)
         {
           if (! (mkdir_and_install
                  ? install_file_in_file_parents (file[0], file[1], &x)
-                 : install_file_in_file (file[0], file[1], &x)))
+                 : install_file_in_file (file[0], file[1], AT_FDCWD,
+                                         file[1], &x)))
             exit_status = EXIT_FAILURE;
         }
       else
@@ -1015,7 +1020,8 @@ main (int argc, char **argv)
           dest_info_init (&x);
           for (i = 0; i < n_files; i++)
             if (! install_file_in_dir (file[i], target_directory, &x,
-                                       i == 0 && mkdir_and_install))
+                                       i == 0 && mkdir_and_install,
+                                       &target_dirfd))
               exit_status = EXIT_FAILURE;
 #ifdef lint
           dest_info_free (&x);
index b5eb169f380b8bc94d849d84dcb042ee0177bac0..fcf32cd43615f012af42ac21bf11d7ea8dbce9e1 100644 (file)
--- a/src/mv.c
+++ b/src/mv.c
@@ -50,9 +50,6 @@ enum
   STRIP_TRAILING_SLASHES_OPTION = CHAR_MAX + 1
 };
 
-/* Remove any trailing slashes from each SOURCE argument.  */
-static bool remove_trailing_slashes;
-
 static struct option const long_options[] =
 {
   {"backup", optional_argument, NULL, 'b'},
@@ -146,31 +143,18 @@ cp_option_init (struct cp_options *x)
   x->src_info = NULL;
 }
 
-/* FILE is the last operand of this command.  Return true if FILE is a
-   directory.  But report an error if there is a problem accessing FILE, other
-   than nonexistence (errno == ENOENT).  */
-
-static bool
-target_directory_operand (char const *file)
-{
-  struct stat st;
-  int err = (stat (file, &st) == 0 ? 0 : errno);
-  bool is_a_dir = !err && S_ISDIR (st.st_mode);
-  if (err && err != ENOENT)
-    die (EXIT_FAILURE, err, _("failed to access %s"), quoteaf (file));
-  return is_a_dir;
-}
-
-/* Move SOURCE onto DEST.  Handles cross-file-system moves.
+/* Move SOURCE onto DEST aka DEST_DIRFD+DEST_RELNAME.
+   Handle cross-file-system moves.
    If SOURCE is a directory, DEST must not exist.
    Return true if successful.  */
 
 static bool
-do_move (char const *source, char const *dest, const struct cp_options *x)
+do_move (char const *source, char const *dest,
+         int dest_dirfd, char const *dest_relname, const struct cp_options *x)
 {
   bool copy_into_self;
   bool rename_succeeded;
-  bool ok = copy (source, dest, AT_FDCWD, dest, 0, x,
+  bool ok = copy (source, dest, dest_dirfd, dest_relname, 0, x,
                   &copy_into_self, &rename_succeeded);
 
   if (ok)
@@ -246,43 +230,6 @@ do_move (char const *source, char const *dest, const struct cp_options *x)
   return ok;
 }
 
-/* Move file SOURCE onto DEST.  Handles the case when DEST is a directory.
-   Treat DEST as a directory if DEST_IS_DIR.
-   Return true if successful.  */
-
-static bool
-movefile (char *source, char *dest, bool dest_is_dir,
-          const struct cp_options *x)
-{
-  bool ok;
-
-  /* This code was introduced to handle the ambiguity in the semantics
-     of mv that is induced by the varying semantics of the rename function.
-     Some systems (e.g., GNU/Linux) have a rename function that honors a
-     trailing slash, while others (like Solaris 5,6,7) have a rename
-     function that ignores a trailing slash.  I believe the GNU/Linux
-     rename semantics are POSIX and susv2 compliant.  */
-
-  if (remove_trailing_slashes)
-    strip_trailing_slashes (source);
-
-  if (dest_is_dir)
-    {
-      /* Treat DEST as a directory; build the full filename.  */
-      char const *src_basename = last_component (source);
-      char *new_dest = file_name_concat (dest, src_basename, NULL);
-      strip_trailing_slashes (new_dest);
-      ok = do_move (source, new_dest, x);
-      free (new_dest);
-    }
-  else
-    {
-      ok = do_move (source, dest, x);
-    }
-
-  return ok;
-}
-
 void
 usage (int status)
 {
@@ -343,7 +290,8 @@ main (int argc, char **argv)
   char const *backup_suffix = NULL;
   char *version_control_string = NULL;
   struct cp_options x;
-  char *target_directory = NULL;
+  bool remove_trailing_slashes = false;
+  char const *target_directory = NULL;
   bool no_target_directory = false;
   int n_files;
   char **file;
@@ -387,16 +335,6 @@ main (int argc, char **argv)
         case 't':
           if (target_directory)
             die (EXIT_FAILURE, 0, _("multiple target directories specified"));
-          else
-            {
-              struct stat st;
-              if (stat (optarg, &st) != 0)
-                die (EXIT_FAILURE, errno, _("failed to access %s"),
-                     quoteaf (optarg));
-              if (! S_ISDIR (st.st_mode))
-                die (EXIT_FAILURE, 0, _("target %s is not a directory"),
-                     quoteaf (optarg));
-            }
           target_directory = optarg;
           break;
         case 'T':
@@ -443,6 +381,7 @@ main (int argc, char **argv)
       usage (EXIT_FAILURE);
     }
 
+  int target_dirfd = AT_FDCWD;
   if (no_target_directory)
     {
       if (target_directory)
@@ -455,23 +394,60 @@ main (int argc, char **argv)
           usage (EXIT_FAILURE);
         }
     }
-  else if (!target_directory)
+  else if (target_directory)
     {
-      assert (2 <= n_files);
+      target_dirfd = target_directory_operand (target_directory);
+      if (! target_dirfd_valid (target_dirfd))
+        die (EXIT_FAILURE, errno, _("target directory %s"),
+             quoteaf (target_directory));
+    }
+  else
+    {
+      char const *lastfile = file[n_files - 1];
       if (n_files == 2)
-        x.rename_errno = (renameatu (AT_FDCWD, file[0], AT_FDCWD, file[1],
+        x.rename_errno = (renameatu (AT_FDCWD, file[0], AT_FDCWD, lastfile,
                                      RENAME_NOREPLACE)
                           ? errno : 0);
-      if (x.rename_errno != 0 && target_directory_operand (file[n_files - 1]))
+      if (x.rename_errno != 0)
         {
-          x.rename_errno = -1;
-          target_directory = file[--n_files];
+          int fd = target_directory_operand (lastfile);
+          if (target_dirfd_valid (fd))
+            {
+              x.rename_errno = -1;
+              target_dirfd = fd;
+              target_directory = lastfile;
+              n_files--;
+            }
+          else
+            {
+              /* The last operand LASTFILE cannot be opened as a directory.
+                 If there are more than two operands, report an error.
+
+                 Also, report an error if LASTFILE is known to be a directory
+                 even though it could not be opened, which can happen if
+                 opening failed with EACCES on a platform lacking O_PATH.
+                 In this case use stat to test whether LASTFILE is a
+                 directory, in case opening a non-directory with (O_SEARCH
+                 | O_DIRECTORY) failed with EACCES not ENOTDIR.  */
+              int err = errno;
+              struct stat st;
+              if (2 < n_files
+                  || (O_PATHSEARCH == O_SEARCH && err == EACCES
+                      && stat (lastfile, &st) == 0 && S_ISDIR (st.st_mode)))
+                die (EXIT_FAILURE, err, _("target %s"), quoteaf (lastfile));
+            }
         }
-      else if (2 < n_files)
-        die (EXIT_FAILURE, 0, _("target %s is not a directory"),
-             quoteaf (file[n_files - 1]));
     }
 
+  /* Handle the ambiguity in the semantics of mv induced by the
+     varying semantics of the rename function.  POSIX-compatible
+     systems (e.g., GNU/Linux) have a rename function that honors a
+     trailing slash in the source, while others (Solaris 9, FreeBSD
+     7.2) have a rename function that ignores it.  */
+  if (remove_trailing_slashes)
+    for (int i = 0; i < n_files; i++)
+      strip_trailing_slashes (file[i]);
+
   if (x.interactive == I_ALWAYS_NO)
     x.update = false;
 
@@ -502,7 +478,14 @@ main (int argc, char **argv)
       for (int i = 0; i < n_files; ++i)
         {
           x.last_file = i + 1 == n_files;
-          ok &= movefile (file[i], target_directory, true, &x);
+          char const *source = file[i];
+          char const *source_basename = last_component (source);
+          char *dest_relname;
+          char *dest = file_name_concat (target_directory, source_basename,
+                                         &dest_relname);
+          strip_trailing_slashes (dest_relname);
+          ok &= do_move (source, dest, target_dirfd, dest_relname, &x);
+          free (dest);
         }
 
 #ifdef lint
@@ -512,7 +495,7 @@ main (int argc, char **argv)
   else
     {
       x.last_file = true;
-      ok = movefile (file[0], file[1], false, &x);
+      ok = do_move (file[0], file[1], AT_FDCWD, file[1], &x);
     }
 
   return ok ? EXIT_SUCCESS : EXIT_FAILURE;
index 6f9ebbc7cd1fbd2a29d041e7085561ee57a57c32..9f10579dc2d9872ba6b32639f5da4734367ea49f 100644 (file)
@@ -107,6 +107,63 @@ enum { O_PATHSEARCH = O_PATH };
 enum { O_PATHSEARCH = O_SEARCH };
 #endif
 
+/* Must F designate the working directory?  */
+
+ATTRIBUTE_PURE static inline bool
+must_be_working_directory (char const *f)
+{
+  /* Return true for ".", "./.", ".///./", etc.  */
+  while (*f++ == '.')
+    {
+      if (*f != '/')
+        return !*f;
+      while (*++f == '/')
+        continue;
+      if (!*f)
+        return true;
+    }
+  return false;
+}
+
+/* Return a file descriptor open to FILE, for use in openat.
+   As an optimization, return AT_FDCWD if FILE must be the working directory.
+   Fail if FILE is not a directory.
+   On failure return a negative value; this is -1 unless AT_FDCWD == -1.  */
+
+static inline int
+target_directory_operand (char const *file)
+{
+  if (must_be_working_directory (file))
+    return AT_FDCWD;
+
+  int fd = open (file, O_PATHSEARCH | O_DIRECTORY);
+
+  if (!O_DIRECTORY && 0 <= fd)
+    {
+      /* On old systems like Solaris 10 that do not support O_DIRECTORY,
+         check by hand whether FILE is a directory.  */
+      struct stat st;
+      int err;
+      if (fstat (fd, &st) != 0 ? (err = errno, true)
+          : !S_ISDIR (st.st_mode) && (err = ENOTDIR, true))
+        {
+          close (fd);
+          errno = err;
+          fd = -1;
+        }
+    }
+
+  return fd - (AT_FDCWD == -1 && fd < 0);
+}
+
+/* Return true if FD represents success for target_directory_operand.  */
+
+static inline bool
+target_dirfd_valid (int fd)
+{
+  return fd != -1 - (AT_FDCWD == -1);
+}
+
 #include <dirent.h>
 #ifndef _D_EXACT_NAMLEN
 # define _D_EXACT_NAMLEN(dp) strlen ((dp)->d_name)
index 83bec639b9a7ce3a30f75ae1156ee110cd575546..690d591e54c82b2d8ccd834c51fe2447206e56a0 100755 (executable)
@@ -131,7 +131,7 @@ EOF
 touch sub4/file_exists || framework_failure_
 ginstall -t sub4/file_exists -Dv file >out 2>&1 && fail=1
 compare - out <<\EOF || fail=1
-ginstall: target 'sub4/file_exists' is not a directory
+ginstall: failed to access 'sub4/file_exists': Not a directory
 EOF
 
 # Ensure that -D with an already existing directory for -t's option argument
index 92410699f16a72f8e7a2ed637548915559701672..c0a558548c5d9c5935dfee0e3ca5e86dd8c7df52 100755 (executable)
@@ -39,8 +39,8 @@ mv: missing file operand
 Try 'mv --help' for more information.
 mv: missing destination file operand after 'no-file'
 Try 'mv --help' for more information.
-mv: target 'f1' is not a directory
-mv: target 'f2' is not a directory
+mv: target 'f1': Not a directory
+mv: target directory 'f2': Not a directory
 EOF
 
 compare exp out || fail=1