mirror of
git://git.sv.gnu.org/coreutils.git
synced 2026-02-15 11:52:15 +02:00
mv: when installing to dir use dir-relative names
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.
This commit is contained in:
2
NEWS
2
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
|
||||
|
||||
57
src/cp.c
57
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. */
|
||||
|
||||
|
||||
150
src/install.c
150
src/install.c
@@ -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, ©_into_self, NULL);
|
||||
return copy (from, to, to_dirfd, to_relname, 0, x, ©_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);
|
||||
|
||||
143
src/mv.c
143
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,
|
||||
©_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;
|
||||
|
||||
57
src/system.h
57
src/system.h
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user