]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
(copy_reg): New parameter, SOURCE_STATS.
authorJim Meyering <jim@meyering.net>
Sun, 29 Jun 1997 22:21:42 +0000 (22:21 +0000)
committerJim Meyering <jim@meyering.net>
Sun, 29 Jun 1997 22:21:42 +0000 (22:21 +0000)
(do_move): Update caller.
(movefile): Take new boolean parameter, DEST_IS_DIR,
to save a stat per moved file when the destination is a directory.
(main): Call movefile with additional argument.
(strip_trailing_slashes_2): New function.
(movefile): Remove trailing slashes from dest.  Otherwise, stat ("b/")
fails with ENOTDIR on systems including Linux w/libc 2.0.30.
Reproduce with `rm -rf a b; mkdir a; touch b; ./mv a b/'.
(do_move): Fix misleading comment.

src/mv.c

index 7acb9ce174059601ac57642fad5cad52de748017..9f30e517855b42f4c3d92bfdf8cb7472c8cc23f2 100644 (file)
--- a/src/mv.c
+++ b/src/mv.c
@@ -65,7 +65,6 @@ int isdir ();
 int yesno ();
 int safe_read ();
 int full_write ();
-void strip_trailing_slashes ();
 int euidaccess ();
 
 /* The name this program was run with. */
@@ -91,9 +90,6 @@ static int stdin_tty;
 /* This process's effective user ID.  */
 static uid_t myeuid;
 
-/* FIXME */
-static struct stat dest_stats, source_stats;
-
 /* If nonzero, display usage information and exit.  */
 static int show_help;
 
@@ -124,18 +120,19 @@ is_real_dir (const char *path)
   return lstat (path, &stats) == 0 && S_ISDIR (stats.st_mode);
 }
 
-/* Copy regular file SOURCE onto file DEST.
+/* Copy regular file SOURCE onto file DEST.  SOURCE_STATS must be
+   the result of calling lstat on SOURCE.
    Return 1 if an error occurred, 0 if successful. */
 
 static int
-copy_reg (const char *source, const char *dest)
+copy_reg (const char *source, const char *dest, const struct stat *source_stats)
 {
   int ifd;
   int ofd;
   char buf[1024 * 8];
   int len;                     /* Number of bytes read into `buf'. */
 
-  if (!S_ISREG (source_stats.st_mode))
+  if (!S_ISREG (source_stats->st_mode))
     {
       error (0, 0,
             _("cannot move `%s' across filesystems: Not a regular file"),
@@ -202,8 +199,8 @@ copy_reg (const char *source, const char *dest)
   {
     struct utimbuf tv;
 
-    tv.actime = source_stats.st_atime;
-    tv.modtime = source_stats.st_mtime;
+    tv.actime = source_stats->st_atime;
+    tv.modtime = source_stats->st_mtime;
     if (utime (dest, &tv))
       {
        error (0, errno, "%s", dest);
@@ -213,14 +210,14 @@ copy_reg (const char *source, const char *dest)
 
   /* Try to preserve ownership.  For non-root it might fail, but that's ok.
      But root probably wants to know, e.g. if NFS disallows it.  */
-  if (chown (dest, source_stats.st_uid, source_stats.st_gid)
+  if (chown (dest, source_stats->st_uid, source_stats->st_gid)
       && (errno != EPERM || myeuid == 0))
     {
       error (0, errno, "%s", dest);
       return 1;
     }
 
-  if (chmod (dest, source_stats.st_mode & 07777))
+  if (chmod (dest, source_stats->st_mode & 07777))
     {
       error (0, errno, "%s", dest);
       return 1;
@@ -230,13 +227,15 @@ copy_reg (const char *source, const char *dest)
 }
 
 /* Move SOURCE onto DEST.  Handles cross-filesystem moves.
-   If DEST is a directory, SOURCE must be also.
+   If SOURCE is a directory, DEST must not exist.
    Return 0 if successful, 1 if an error occurred.  */
 
 static int
 do_move (const char *source, const char *dest)
 {
   char *dest_backup = NULL;
+  struct stat source_stats;
+  struct stat dest_stats;
 
   if (lstat (source, &source_stats) != 0)
     {
@@ -246,6 +245,7 @@ do_move (const char *source, const char *dest)
 
   if (lstat (dest, &dest_stats) == 0)
     {
+
       if (source_stats.st_dev == dest_stats.st_dev
          && source_stats.st_ino == dest_stats.st_ino)
        {
@@ -321,7 +321,7 @@ do_move (const char *source, const char *dest)
 
   /* rename failed on cross-filesystem link.  Copy the file instead. */
 
-  if (copy_reg (source, dest))
+  if (copy_reg (source, dest, &source_stats))
     goto un_backup;
 
   if (unlink (source))
@@ -341,32 +341,59 @@ do_move (const char *source, const char *dest)
   return 1;
 }
 
+static int
+strip_trailing_slashes_2 (char *path)
+{
+  char *end_p = path + strlen (path) - 1;
+  char *slash = end_p;
+
+  while (slash > path && *slash == '/')
+    *slash-- = '\0';
+
+  return slash < end_p;
+}
+
 /* Move file SOURCE onto DEST.  Handles the case when DEST is a directory.
+   DEST_IS_DIR must be nonzero when DEST is a directory or a symlink to a
+   directory and zero otherwise.
    Return 0 if successful, 1 if an error occurred.  */
 
 static int
-movefile (const char *source, const char *dest)
+movefile (char *source, char *dest, int dest_is_dir)
 {
-  strip_trailing_slashes (source);
+  int dest_had_trailing_slash = strip_trailing_slashes_2 (dest);
+  int fail;
+
+  /* In addition to when DEST is a directory, if DEST has a trailing
+     slash and neither SOURCE nor DEST is a directory, presume the target
+     is DEST/`basename source`.  This converts `mv x y/' to `mv x y/x'.
+     This change means that the command `mv any file/' will now fail
+     rather than performing the move.  The case when SOURCE is a
+     directory and DEST is not is properly diagnosed by do_move.  */
 
-  if ((dest[strlen (dest) - 1] == '/' && !is_real_dir (source))
-      || isdir (dest))
+  if (dest_is_dir || (dest_had_trailing_slash && !is_real_dir (source)))
     {
-      /* Target is a directory; build full target filename. */
-      char *base;
+      /* DEST is a directory; build full target filename. */
+      char *src_basename;
       char *new_dest;
-      int fail;
 
-      base = base_name (source);
-      new_dest = path_concat (dest, base, NULL);
+      /* Remove trailing slashes before taking base_name.
+        Otherwise, base_name ("a/") returns "".  */
+      strip_trailing_slashes_2 (source);
+
+      src_basename = base_name (source);
+      new_dest = path_concat (dest, src_basename, NULL);
       if (new_dest == NULL)
        error (1, 0, _("virtual memory exhausted"));
       fail = do_move (source, new_dest);
       free (new_dest);
-      return fail;
     }
   else
-    return do_move (source, dest);
+    {
+      fail = do_move (source, dest);
+    }
+
+  return fail;
 }
 
 static void
@@ -415,6 +442,7 @@ main (int argc, char **argv)
   int c;
   int errors;
   int make_backups = 0;
+  int dest_is_dir;
   char *version;
 
   program_name = argv[0];
@@ -486,14 +514,15 @@ main (int argc, char **argv)
     backup_type = get_version (version);
 
   stdin_tty = isatty (STDIN_FILENO);
+  dest_is_dir = isdir (argv[argc - 1]);
 
-  if (argc > optind + 2 && !isdir (argv[argc - 1]))
+  if (argc > optind + 2 && !dest_is_dir)
     error (1, 0,
           _("when moving multiple files, last argument must be a directory"));
 
   /* Move each arg but the last onto the last. */
   for (; optind < argc - 1; ++optind)
-    errors |= movefile (argv[optind], argv[argc - 1]);
+    errors |= movefile (argv[optind], argv[argc - 1], dest_is_dir);
 
   exit (errors);
 }