summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJim Meyering <jim@meyering.net>2004-06-28 18:39:28 +0000
committerJim Meyering <jim@meyering.net>2004-06-28 18:39:28 +0000
commit6860c032dac6a807a908125edd1dc2f95549ca5a (patch)
tree9951953e1d5b34f15b21ff517a1992785b76fe6e /src
parent59c3b539268fd48dd5fd9d63eacfd19534cc4146 (diff)
downloadcoreutils-6860c032dac6a807a908125edd1dc2f95549ca5a.tar.xz
Use more-consistent rules among cp, ln, and mv when dealing with
last operands that are (or look like) directories. * src/cp.c (target_directory_operand): New, nearly-common function, It reports an error if the destination appears to be a directory (e.g., because it has a trailing slash) but is not. * src/ln.c, src/mv.c: Likewise. * src/cp.c (do_copy): Use it. * src/ln.c (main): Likewise. * src/mv.c (main): Likewise. * src/cp.c (do_copy): Don't output a usage message because of file problems (e.g., an operand is not a directory). Use it only for syntax. Standardize on "target %s is not a directory" for the diagnostic. * src/ln.c (main): Likewise. * src/mv.c (main): Likewise. * src/cp.c (do_copy): Remove test for trailing slash, since target_directory_operand now does this. * src/ln.c (main): Likewise. * src/mv.c (movefile): Likewise. * src/ln.c (isdir): Remove decl; no longer needed. * src/mv.c (isdir, lstat): Likewise. * src/ln.c (main): Use int to count to argc, not unsigned int. This handles negative operand counts. * src/mv.c (main): Likewise. * src/mv.c (do_move): Don't call hash_init; expect the caller to do it, for consistency with cp.c and ln.c. All callers changed. (movefile): dest_is_dir parameter is now bool, not int. (main): Standardize on "missing destination file operand after %s" for the diagnostic, for consistency with cp.c.
Diffstat (limited to 'src')
-rw-r--r--src/mv.c138
1 files changed, 63 insertions, 75 deletions
diff --git a/src/mv.c b/src/mv.c
index 4a73ae95f..071620602 100644
--- a/src/mv.c
+++ b/src/mv.c
@@ -54,9 +54,6 @@ enum
REPLY_OPTION
};
-int isdir ();
-int lstat ();
-
/* The name this program was run with. */
char *program_name;
@@ -150,36 +147,38 @@ cp_option_init (struct cp_options *x)
x->src_info = NULL;
}
-/* If PATH is an existing directory, return nonzero, else 0. */
+/* 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 int
-is_real_dir (const char *path)
+static bool
+target_directory_operand (char const *file)
{
- struct stat stats;
-
- return lstat (path, &stats) == 0 && S_ISDIR (stats.st_mode);
+ char const *b = base_name (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)
+ error (EXIT_FAILURE, err, _("accessing %s"), quote (file));
+ if (is_a_dir < looks_like_a_dir)
+ error (EXIT_FAILURE, err, _("target %s is not a directory"), quote (file));
+ return is_a_dir;
}
/* Move SOURCE onto DEST. Handles cross-filesystem moves.
If SOURCE is a directory, DEST must not exist.
- Return 0 if successful, non-zero if an error occurred. */
+ Return 0 if successful, 1 if an error occurred. */
static int
do_move (const char *source, const char *dest, const struct cp_options *x)
{
- static int first = 1;
int copy_into_self;
int rename_succeeded;
int fail;
- if (first)
- {
- first = 0;
-
- /* Allocate space for remembering copied and created files. */
- hash_init ();
- }
-
fail = copy (source, dest, 0, x, &copy_into_self, &rename_succeeded);
if (!fail)
@@ -253,15 +252,13 @@ do_move (const char *source, const char *dest, const struct cp_options *x)
}
/* 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.
+ Treat DEST as a directory if DEST_IS_DIR.
Return 0 if successful, non-zero if an error occurred. */
static int
-movefile (char *source, char *dest, int dest_is_dir,
+movefile (char *source, char *dest, bool dest_is_dir,
const struct cp_options *x)
{
- int dest_had_trailing_slash = strip_trailing_slashes (dest);
int fail;
/* This code was introduced to handle the ambiguity in the semantics
@@ -271,19 +268,13 @@ movefile (char *source, char *dest, int dest_is_dir,
function that ignores a trailing slash. I believe the Linux
rename semantics are POSIX and susv2 compliant. */
+ strip_trailing_slashes (dest);
if (remove_trailing_slashes)
strip_trailing_slashes (source);
- /* 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_is_dir || (dest_had_trailing_slash && !is_real_dir (source)))
+ if (dest_is_dir)
{
- /* DEST is a directory; build full target filename. */
+ /* Treat DEST as a directory; build the full filename. */
char const *src_basename = base_name (source);
char *new_dest = path_concat (dest, src_basename, NULL);
if (new_dest == NULL)
@@ -369,13 +360,11 @@ main (int argc, char **argv)
int c;
int errors;
int make_backups = 0;
- int dest_is_dir;
char *backup_suffix_string;
char *version_control_string = NULL;
struct cp_options x;
char *target_directory = NULL;
- int target_directory_specified;
- unsigned int n_files;
+ int n_files;
char **file;
initialize_main (&argc, &argv);
@@ -427,6 +416,17 @@ main (int argc, char **argv)
remove_trailing_slashes = 1;
break;
case TARGET_DIRECTORY_OPTION:
+ if (target_directory)
+ error (EXIT_FAILURE, 0, _("multiple target directories specified"));
+ else
+ {
+ struct stat st;
+ if (stat (optarg, &st) != 0)
+ error (EXIT_FAILURE, errno, _("accessing %s"), quote (optarg));
+ if (! S_ISDIR (st.st_mode))
+ error (EXIT_FAILURE, 0, _("target %s is not a directory"),
+ quote (optarg));
+ }
target_directory = optarg;
break;
case 'u':
@@ -446,39 +446,26 @@ main (int argc, char **argv)
}
}
- n_files = (optind < argc ? argc - optind : 0);
+ n_files = argc - optind;
file = argv + optind;
- target_directory_specified = (target_directory != NULL);
- if (target_directory == NULL && n_files != 0)
- target_directory = file[n_files - 1];
-
- dest_is_dir = (n_files > 0 && isdir (target_directory));
-
- if (n_files == 0 || (n_files == 1 && !target_directory_specified))
+ if (n_files <= !target_directory)
{
- if (n_files == 0)
+ if (n_files <= 0)
error (0, 0, _("missing file operand"));
else
- error (0, 0, _("missing file operand after %s"),
- quote (argv[argc - 1]));
+ error (0, 0, _("missing destination file operand after %s"),
+ quote (file[0]));
usage (EXIT_FAILURE);
}
- if (target_directory_specified)
+ if (!target_directory)
{
- if (!dest_is_dir)
- {
- error (0, 0, _("specified target, %s is not a directory"),
- quote (target_directory));
- usage (EXIT_FAILURE);
- }
- }
- else if (n_files > 2 && !dest_is_dir)
- {
- error (0, 0,
- _("when moving multiple files, last argument must be a directory"));
- usage (EXIT_FAILURE);
+ if (2 <= n_files && target_directory_operand (file[n_files - 1]))
+ target_directory = file[--n_files];
+ else if (2 < n_files)
+ error (EXIT_FAILURE, 0, _("target %s is not a directory"),
+ quote (file[n_files - 1]));
}
if (backup_suffix_string)
@@ -489,22 +476,23 @@ main (int argc, char **argv)
version_control_string)
: none);
- /* Move each arg but the last into the target_directory. */
- {
- unsigned int last_file_idx = (target_directory_specified
- ? n_files - 1
- : n_files - 2);
- unsigned int i;
-
- /* Initialize the hash table only if we'll need it.
- The problem it is used to detect can arise only if there are
- two or more files to move. */
- if (last_file_idx)
- dest_info_init (&x);
-
- for (i = 0; i <= last_file_idx; ++i)
- errors |= movefile (file[i], target_directory, dest_is_dir, &x);
- }
+ hash_init ();
+
+ if (target_directory)
+ {
+ int i;
+
+ /* Initialize the hash table only if we'll need it.
+ The problem it is used to detect can arise only if there are
+ two or more files to move. */
+ if (2 <= n_files)
+ dest_info_init (&x);
+
+ for (i = 0; i < n_files; ++i)
+ errors |= movefile (file[i], target_directory, true, &x);
+ }
+ else
+ errors = movefile (file[0], file[1], false, &x);
exit (errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
}