# 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;
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;
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)
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);
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)
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.
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,
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;
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;
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;
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,
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,
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;
}
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)
usage (EXIT_FAILURE);
}
+ int target_dirfd = AT_FDCWD;
if (no_target_directory)
{
if (target_directory)
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)
{
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
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);
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'},
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)
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)
{
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;
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':
usage (EXIT_FAILURE);
}
+ int target_dirfd = AT_FDCWD;
if (no_target_directory)
{
if (target_directory)
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;
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
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;