summaryrefslogtreecommitdiff
path: root/src/mv.c
diff options
context:
space:
mode:
authorJim Meyering <jim@meyering.net>1997-06-29 22:21:42 +0000
committerJim Meyering <jim@meyering.net>1997-06-29 22:21:42 +0000
commitdc404c4107613bc80de379037516059c674b8648 (patch)
treeb04cfae472c9b3db6a308edeafb3ddd40d72f40f /src/mv.c
parente44678aac267cd8aa3027a8adaa3133b5080d3a6 (diff)
downloadcoreutils-dc404c4107613bc80de379037516059c674b8648.tar.xz
(copy_reg): New parameter, SOURCE_STATS.
(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.
Diffstat (limited to 'src/mv.c')
-rw-r--r--src/mv.c81
1 files changed, 55 insertions, 26 deletions
diff --git a/src/mv.c b/src/mv.c
index 7acb9ce17..9f30e5178 100644
--- 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);
}