# define fchown(fd, uid, gid) (-1)
#endif
-#ifndef HAVE_LCHOWN
-# define HAVE_LCHOWN false
-# define lchown(name, uid, gid) chown (name, uid, gid)
-#endif
-
-#ifndef HAVE_MKFIFO
-static int
-rpl_mkfifo (char const *file, mode_t mode)
-{
- errno = ENOTSUP;
- return -1;
-}
-# define mkfifo rpl_mkfifo
-#endif
-
#ifndef USE_ACL
# define USE_ACL 0
#endif
#define DEST_INFO_INITIAL_CAPACITY 61
static bool copy_internal (char const *src_name, char const *dst_name,
+ int dst_dirfd, char const *dst_relname,
bool new_dst, struct stat const *parent,
struct dir_list *ancestors,
const struct cp_options *x,
return result;
}
-/* Set the timestamp of symlink, FILE, to TIMESPEC.
- If this system lacks support for that, simply return 0. */
-static inline int
-utimens_symlink (char const *file, struct timespec const *timespec)
-{
- int err = lutimens (file, timespec);
- /* When configuring on a system with new headers and libraries, and
- running on one with a kernel that is old enough to lack the syscall,
- utimensat fails with ENOSYS. Ignore that. */
- if (err && errno == ENOSYS)
- err = 0;
- return err;
-}
-
/* Attempt to punch a hole to avoid any permanent
speculative preallocation on file systems such as XFS.
Return values as per fallocate(2) except ENOSYS etc. are ignored. */
#endif /* USE_XATTR */
/* Read the contents of the directory SRC_NAME_IN, and recursively
- copy the contents to DST_NAME_IN. NEW_DST is true if
- DST_NAME_IN is a directory that was created previously in the
- recursion. SRC_SB and ANCESTORS describe SRC_NAME_IN.
+ copy the contents to DST_NAME_IN aka DST_DIRFD+DST_RELNAME_IN.
+ NEW_DST is true if DST_NAME_IN is a directory
+ that was created previously in the recursion.
+ SRC_SB and ANCESTORS describe SRC_NAME_IN.
Set *COPY_INTO_SELF if SRC_NAME_IN is a parent of
(or the same as) DST_NAME_IN; otherwise, clear it.
Propagate *FIRST_DIR_CREATED_PER_COMMAND_LINE_ARG from
Return true if successful. */
static bool
-copy_dir (char const *src_name_in, char const *dst_name_in, bool new_dst,
+copy_dir (char const *src_name_in, char const *dst_name_in,
+ int dst_dirfd, char const *dst_relname_in, bool new_dst,
const struct stat *src_sb, struct dir_list *ancestors,
const struct cp_options *x,
bool *first_dir_created_per_command_line_arg,
char *dst_name = file_name_concat (dst_name_in, namep, NULL);
bool first_dir_created = *first_dir_created_per_command_line_arg;
- ok &= copy_internal (src_name, dst_name, new_dst, src_sb,
+ ok &= copy_internal (src_name, dst_name, dst_dirfd,
+ dst_name + (dst_relname_in - dst_name_in),
+ new_dst, src_sb,
ancestors, &non_command_line_options, false,
&first_dir_created,
&local_copy_into_self, NULL);
/* Set the owner and owning group of DEST_DESC to the st_uid and
st_gid fields of SRC_SB. If DEST_DESC is undefined (-1), set
- the owner and owning group of DST_NAME instead; for
- safety prefer lchown if the system supports it since no
+ the owner and owning group of DST_NAME aka DST_DIRFD+DST_RELNAME
+ instead; for safety prefer lchownat since no
symbolic links should be involved. DEST_DESC must
- refer to the same file as DEST_NAME if defined.
+ refer to the same file as DST_NAME if defined.
Upon failure to set both UID and GID, try to set only the GID.
NEW_DST is true if the file was newly created; otherwise,
DST_SB is the status of the destination.
not to preserve ownership, -1 otherwise. */
static int
-set_owner (const struct cp_options *x, char const *dst_name, int dest_desc,
+set_owner (const struct cp_options *x, char const *dst_name,
+ int dst_dirfd, char const *dst_relname, int dest_desc,
struct stat const *src_sb, bool new_dst,
struct stat const *dst_sb)
{
}
else
{
- if (lchown (dst_name, uid, gid) == 0)
+ if (lchownat (dst_dirfd, dst_relname, uid, gid) == 0)
return 1;
if (errno == EPERM || errno == EINVAL)
{
/* We've failed to set *both*. Now, try to set just the group
ID, but ignore any failure here, and don't change errno. */
int saved_errno = errno;
- ignore_value (lchown (dst_name, -1, gid));
+ ignore_value (lchownat (dst_dirfd, dst_relname, -1, gid));
errno = saved_errno;
}
}
/* Set the st_author field of DEST_DESC to the st_author field of
SRC_SB. If DEST_DESC is undefined (-1), set the st_author field
of DST_NAME instead. DEST_DESC must refer to the same file as
- DEST_NAME if defined. */
+ DST_NAME if defined. */
static void
set_author (char const *dst_name, int dest_desc, const struct stat *src_sb)
return true;
}
-/* Change the file mode bits of the file identified by DESC or NAME to MODE.
- Use DESC if DESC is valid and fchmod is available, NAME otherwise. */
+/* Change the file mode bits of the file identified by DESC or
+ DIRFD+NAME to MODE. Use DESC if DESC is valid and fchmod is
+ available, DIRFD+NAME otherwise. */
static int
-fchmod_or_lchmod (int desc, char const *name, mode_t mode)
+fchmod_or_lchmod (int desc, int dirfd, char const *name, mode_t mode)
{
#if HAVE_FCHMOD
if (0 <= desc)
return fchmod (desc, mode);
#endif
- return lchmod (name, mode);
+ return lchmodat (dirfd, name, mode);
}
#ifndef HAVE_STRUCT_STAT_ST_BLOCKS
}
-/* Copy a regular file from SRC_NAME to DST_NAME.
+/* Copy a regular file from SRC_NAME to DST_NAME aka DST_DIRFD+DST_RELNAME.
If the source file contains holes, copies holes and blocks of zeros
in the source file as holes in the destination file.
(Holes are read as zeroes by the 'read' system call.)
static bool
copy_reg (char const *src_name, char const *dst_name,
+ int dst_dirfd, char const *dst_relname,
const struct cp_options *x,
mode_t dst_mode, mode_t omitted_permissions, bool *new_dst,
struct stat const *src_sb)
{
int open_flags =
O_WRONLY | O_BINARY | (data_copy_required ? O_TRUNC : 0);
- dest_desc = open (dst_name, open_flags);
+ dest_desc = openat (dst_dirfd, dst_relname, open_flags);
dest_errno = errno;
/* When using cp --preserve=context to copy to an existing destination,
if (dest_desc < 0 && dest_errno != ENOENT
&& x->unlink_dest_after_failed_open)
{
- if (unlink (dst_name) == 0)
+ if (unlinkat (dst_dirfd, dst_relname, 0) == 0)
{
if (x->verbose)
printf (_("removed %s\n"), quoteaf (dst_name));
int clone_flags = x->preserve_ownership ? 0 : CLONE_NOOWNERCOPY;
if (data_copy_required && x->reflink_mode
&& x->preserve_mode && x->preserve_timestamps
- && fclonefileat (source_desc, AT_FDCWD, dst_name, clone_flags) == 0)
+ && (fclonefileat (source_desc, dst_dirfd, dst_relname, clone_flags)
+ == 0))
goto close_src_desc;
#endif
extra_permissions = open_mode & ~dst_mode; /* either 0 or S_IWUSR */
int open_flags = O_WRONLY | O_CREAT | O_BINARY;
- dest_desc = open (dst_name, open_flags | O_EXCL, open_mode);
+ dest_desc = openat (dst_dirfd, dst_relname, open_flags | O_EXCL,
+ open_mode);
dest_errno = errno;
/* When trying to copy through a dangling destination symlink,
the above open fails with EEXIST. If that happens, and
- lstat'ing the DST_NAME shows that it is a symlink, then we
+ readlinkat shows that it is a symlink, then we
have a problem: trying to resolve this dangling symlink to
a directory/destination-entry pair is fundamentally racy,
so punt. If x->open_dangling_dest_symlink is set (cp sets
only when copying, i.e., not in move_mode. */
if (dest_desc < 0 && dest_errno == EEXIST && ! x->move_mode)
{
- struct stat dangling_link_sb;
- if (lstat (dst_name, &dangling_link_sb) == 0
- && S_ISLNK (dangling_link_sb.st_mode))
+ char dummy[1];
+ if (0 <= readlinkat (dst_dirfd, dst_relname, dummy, sizeof dummy))
{
if (x->open_dangling_dest_symlink)
{
- dest_desc = open (dst_name, open_flags, open_mode);
+ dest_desc = openat (dst_dirfd, dst_relname,
+ open_flags, open_mode);
dest_errno = errno;
}
else
up with extra permissions, letting copy_attr fail later. */
mode_t temporary_mode = sb.st_mode | extra_permissions;
if (temporary_mode != sb.st_mode
- && fchmod_or_lchmod (dest_desc, dst_name, temporary_mode) != 0)
+ && (fchmod_or_lchmod (dest_desc, dst_dirfd, dst_relname, temporary_mode)
+ != 0))
extra_permissions = 0;
if (data_copy_required)
timespec[0] = get_stat_atime (src_sb);
timespec[1] = get_stat_mtime (src_sb);
- if (fdutimens (dest_desc, dst_name, timespec) != 0)
+ if (fdutimensat (dest_desc, dst_dirfd, dst_relname, timespec, 0) != 0)
{
error (0, errno, _("preserving times for %s"), quoteaf (dst_name));
if (x->require_preserve)
clear capabilities. */
if (x->preserve_ownership && ! SAME_OWNER_AND_GROUP (*src_sb, sb))
{
- switch (set_owner (x, dst_name, dest_desc, src_sb, *new_dst, &sb))
+ switch (set_owner (x, dst_name, dst_dirfd, dst_relname, dest_desc,
+ src_sb, *new_dst, &sb))
{
case -1:
return_val = false;
{
omitted_permissions &= ~ cached_umask ();
if ((omitted_permissions | extra_permissions)
- && (fchmod_or_lchmod (dest_desc, dst_name,
+ && (fchmod_or_lchmod (dest_desc, dst_dirfd, dst_relname,
dst_mode & ~ cached_umask ())
!= 0))
{
return return_val;
}
-/* Return true if it's ok that the source and destination
- files are the 'same' by some measure. The goal is to avoid
+/* Return whether it's OK that two files are the "same" by some measure.
+ The first file is SRC_NAME and has status SRC_SB.
+ The second is DST_DIRFD+DST_RELNAME and has status DST_SB.
+ The copying options are X. The goal is to avoid
making the 'copy' operation remove both copies of the file
in that case, while still allowing the user to e.g., move or
copy a regular file onto a symlink that points to it.
static bool
same_file_ok (char const *src_name, struct stat const *src_sb,
- char const *dst_name, struct stat const *dst_sb,
+ int dst_dirfd, char const *dst_relname, struct stat const *dst_sb,
const struct cp_options *x, bool *return_now)
{
const struct stat *src_sb_link;
when they are distinct. */
if (S_ISLNK (src_sb->st_mode) && S_ISLNK (dst_sb->st_mode))
{
- bool sn = same_name (src_name, dst_name);
+ bool sn = same_nameat (AT_FDCWD, src_name, dst_dirfd, dst_relname);
if ( ! sn)
{
/* It's fine when we're making any type of backup. */
if (!same)
return true;
- if (lstat (dst_name, &tmp_dst_sb) != 0
+ if (lstatat (dst_dirfd, dst_relname, &tmp_dst_sb) != 0
|| lstat (src_name, &tmp_src_sb) != 0)
return true;
}
/* FIXME: What about case insensitive file systems ? */
- return ! same_name (src_name, dst_name);
+ return ! same_nameat (AT_FDCWD, src_name, dst_dirfd, dst_relname);
}
#if 0
copy_reg because SRC_NAME will no longer exist. Allowing
the test to be deferred lets cp do some useful things.
But when creating hardlinks and SRC_NAME is a symlink
- but DST_NAME is not we must test anyway. */
+ but DST_RELNAME is not we must test anyway. */
if (x->hard_link
|| !S_ISLNK (src_sb_link->st_mode)
|| S_ISLNK (dst_sb_link->st_mode))
/* They may refer to the same file if we're in move mode and the
target is a symlink. That is ok, since we remove any existing
destination file before opening it -- via 'rename' if they're on
- the same file system, via 'unlink (DST_NAME)' otherwise. */
+ the same file system, via unlinkat otherwise. */
if (S_ISLNK (dst_sb_link->st_mode))
return true;
this causes a race condition and we may lose data in this case. */
if (same_link
&& 1 < dst_sb_link->st_nlink
- && ! same_name (src_name, dst_name))
+ && ! same_nameat (AT_FDCWD, src_name, dst_dirfd, dst_relname))
return ! x->move_mode;
}
char *abs_src = canonicalize_file_name (src_name);
if (abs_src)
{
- bool result = ! same_name (abs_src, dst_name);
+ bool result = ! same_nameat (AT_FDCWD, abs_src,
+ dst_dirfd, dst_relname);
free (abs_src);
return result;
}
if ( ! S_ISLNK (dst_sb_link->st_mode))
tmp_dst_sb = *dst_sb_link;
- else if (stat (dst_name, &tmp_dst_sb) != 0)
+ else if (statat (dst_dirfd, dst_relname, &tmp_dst_sb) != 0)
return true;
if ( ! SAME_INODE (tmp_src_sb, tmp_dst_sb))
return false;
}
-/* Return true if FILE, with mode MODE, is writable in the sense of 'mv'.
+/* Return whether DST_DIRFD+DST_RELNAME, with mode MODE,
+ is writable in the sense of 'mv'.
Always consider a symbolic link to be writable. */
static bool
-writable_destination (char const *file, mode_t mode)
+writable_destination (int dst_dirfd, char const *dst_relname, mode_t mode)
{
return (S_ISLNK (mode)
|| can_write_any_file ()
- || euidaccess (file, W_OK) == 0);
+ || faccessat (dst_dirfd, dst_relname, W_OK, AT_EACCESS) == 0);
}
static bool
overwrite_ok (struct cp_options const *x, char const *dst_name,
+ int dst_dirfd, char const *dst_relname,
struct stat const *dst_sb)
{
- if (! writable_destination (dst_name, dst_sb->st_mode))
+ if (! writable_destination (dst_dirfd, dst_relname, dst_sb->st_mode))
{
char perms[12]; /* "-rwxrwxrwx " ls-style modes. */
strmode (dst_sb->st_mode, perms);
#endif
/* When effecting a move (e.g., for mv(1)), and given the name DST_NAME
+ aka DST_DIRFD+DST_RELNAME
of the destination and a corresponding stat buffer, DST_SB, return
true if the logical 'move' operation should _not_ proceed.
Otherwise, return false.
static bool
abandon_move (const struct cp_options *x,
char const *dst_name,
+ int dst_dirfd, char const *dst_relname,
struct stat const *dst_sb)
{
assert (x->move_mode);
|| ((x->interactive == I_ASK_USER
|| (x->interactive == I_UNSPECIFIED
&& x->stdin_tty
- && ! writable_destination (dst_name, dst_sb->st_mode)))
- && ! overwrite_ok (x, dst_name, dst_sb)));
+ && ! writable_destination (dst_dirfd, dst_relname,
+ dst_sb->st_mode)))
+ && ! overwrite_ok (x, dst_name, dst_dirfd, dst_relname, dst_sb)));
}
/* Print --verbose output on standard output, e.g. 'new' -> 'old'.
_("failed to restore the default file creation context"));
}
-/* Create a hard link DST_NAME to SRC_NAME, honoring the REPLACE, VERBOSE and
- DEREFERENCE settings. Return true upon success. Otherwise, diagnose the
+/* Return a newly-allocated string that is like STR
+ except replace its suffix SUFFIX with NEWSUFFIX. */
+static char *
+subst_suffix (char const *str, char const *suffix, char const *newsuffix)
+{
+ idx_t prefixlen = suffix - str;
+ idx_t newsuffixsize = strlen (newsuffix) + 1;
+ char *r = ximalloc (prefixlen + newsuffixsize);
+ memcpy (r + prefixlen, newsuffix, newsuffixsize);
+ return memcpy (r, str, prefixlen);
+}
+
+/* Create a hard link to SRC_NAME aka SRC_DIRFD+SRC_RELNAME;
+ the new link is at DST_NAME aka DST_DIRFD+DST_RELNAME.
+ A null SRC_NAME stands for the file whose name is like DST_NAME
+ except with DST_RELNAME replaced with SRC_RELNAME.
+ Honor the REPLACE, VERBOSE and DEREFERENCE settings.
+ Return true upon success. Otherwise, diagnose the
failure and return false. If SRC_NAME is a symbolic link, then it will not
be followed unless DEREFERENCE is true.
If the system doesn't support hard links to symbolic links, then DST_NAME
will be created as a symbolic link to SRC_NAME. */
static bool
-create_hard_link (char const *src_name, char const *dst_name,
+create_hard_link (char const *src_name, int src_dirfd, char const *src_relname,
+ char const *dst_name, int dst_dirfd, char const *dst_relname,
bool replace, bool verbose, bool dereference)
{
- int err = force_linkat (AT_FDCWD, src_name, AT_FDCWD, dst_name,
+ int err = force_linkat (src_dirfd, src_relname, dst_dirfd, dst_relname,
dereference ? AT_SYMLINK_FOLLOW : 0,
replace, -1);
if (0 < err)
{
+
+ char *a_src_name = NULL;
+ if (!src_name)
+ src_name = a_src_name = subst_suffix (dst_name, dst_relname,
+ src_relname);
error (0, err, _("cannot create hard link %s to %s"),
quoteaf_n (0, dst_name), quoteaf_n (1, src_name));
+ free (a_src_name);
return false;
}
if (err < 0 && verbose)
}
/* Return true if the source file with basename SRCBASE and status SRC_ST
- is likely to be the simple backup file for DST_NAME. */
+ is likely to be the simple backup file for DST_DIRFD+DST_RELNAME. */
static bool
source_is_dst_backup (char const *srcbase, struct stat const *src_st,
- char const *dst_name)
+ int dst_dirfd, char const *dst_relname)
{
size_t srcbaselen = strlen (srcbase);
- char const *dstbase = last_component (dst_name);
+ char const *dstbase = last_component (dst_relname);
size_t dstbaselen = strlen (dstbase);
size_t suffixlen = strlen (simple_backup_suffix);
if (! (srcbaselen == dstbaselen + suffixlen
&& memcmp (srcbase, dstbase, dstbaselen) == 0
&& STREQ (srcbase + dstbaselen, simple_backup_suffix)))
return false;
- size_t dstlen = strlen (dst_name);
- char *dst_back = xmalloc (dstlen + suffixlen + 1);
- strcpy (mempcpy (dst_back, dst_name, dstlen), simple_backup_suffix);
+ char *dst_back = subst_suffix (dst_relname,
+ dst_relname + strlen (dst_relname),
+ simple_backup_suffix);
struct stat dst_back_sb;
- int dst_back_status = stat (dst_back, &dst_back_sb);
+ int dst_back_status = statat (dst_dirfd, dst_back, &dst_back_sb);
free (dst_back);
return dst_back_status == 0 && SAME_INODE (*src_st, dst_back_sb);
}
-/* Copy the file SRC_NAME to the file DST_NAME. The files may be of
- any type. NEW_DST should be true if the file DST_NAME cannot
- exist because its parent directory was just created; NEW_DST should
- be false if DST_NAME might already exist. A non-null PARENT describes the
+/* Copy the file SRC_NAME to the file DST_NAME aka DST_DIRFD+DST_RELNAME.
+ NEW_DST means DST_NAME is known not to exist (e.g., because its
+ parent directory was just created). A non-null PARENT describes the
parent directory. ANCESTORS points to a linked, null terminated list of
devices and inodes of parent directories of SRC_NAME. COMMAND_LINE_ARG
is true iff SRC_NAME was specified on the command line.
Return true if successful. */
static bool
copy_internal (char const *src_name, char const *dst_name,
+ int dst_dirfd, char const *dst_relname,
bool new_dst,
struct stat const *parent,
struct dir_list *ancestors,
if (x->move_mode)
{
if (rename_errno < 0)
- rename_errno = (renameatu (AT_FDCWD, src_name, AT_FDCWD, dst_name,
+ rename_errno = (renameatu (AT_FDCWD, src_name, dst_dirfd, dst_relname,
RENAME_NOREPLACE)
? errno : 0);
new_dst = rename_errno == 0;
: rename_errno != EEXIST || x->interactive != I_ALWAYS_NO)
{
char const *name = rename_errno == 0 ? dst_name : src_name;
+ int dirfd = rename_errno == 0 ? dst_dirfd : AT_FDCWD;
+ char const *relname = rename_errno == 0 ? dst_relname : src_name;
int fstatat_flags
= x->dereference == DEREF_NEVER ? AT_SYMLINK_NOFOLLOW : 0;
- if (follow_fstatat (AT_FDCWD, name, &src_sb, fstatat_flags) != 0)
+ if (follow_fstatat (dirfd, relname, &src_sb, fstatat_flags) != 0)
{
error (0, errno, _("cannot stat %s"), quoteaf (name));
return false;
|| x->backup_type != no_backups
|| x->unlink_dest_before_opening);
int fstatat_flags = use_lstat ? AT_SYMLINK_NOFOLLOW : 0;
- if (follow_fstatat (AT_FDCWD, dst_name, &dst_sb, fstatat_flags) == 0)
+ if (follow_fstatat (dst_dirfd, dst_relname, &dst_sb, fstatat_flags)
+ == 0)
{
have_dst_lstat = use_lstat;
rename_errno = EEXIST;
bool return_now = false;
if (x->interactive != I_ALWAYS_NO
- && ! same_file_ok (src_name, &src_sb, dst_name, &dst_sb,
- x, &return_now))
+ && ! same_file_ok (src_name, &src_sb, dst_dirfd, dst_relname,
+ &dst_sb, x, &return_now))
{
error (0, 0, _("%s and %s are the same file"),
quoteaf_n (0, src_name), quoteaf_n (1, dst_name));
? UTIMECMP_TRUNCATE_SOURCE
: 0);
- if (0 <= utimecmp (dst_name, &dst_sb, &src_sb, options))
+ if (0 <= utimecmpat (dst_dirfd, dst_relname, &dst_sb,
+ &src_sb, options))
{
/* We're using --update and the destination is not older
than the source, so do not copy or move. Pretend the
hard-linked to another one. In that case, we'll use
the mapping information to link the corresponding
destination names. */
- earlier_file = remember_copied (dst_name, src_sb.st_ino,
+ earlier_file = remember_copied (dst_relname, src_sb.st_ino,
src_sb.st_dev);
if (earlier_file)
{
/* Note we currently replace DST_NAME unconditionally,
even if it was a newer separate file. */
- if (! create_hard_link (earlier_file, dst_name, true,
+ if (! create_hard_link (NULL, dst_dirfd, earlier_file,
+ dst_name, dst_dirfd, dst_relname,
+ true,
x->verbose, dereference))
{
goto un_backup;
cp and mv treat -i and -f differently. */
if (x->move_mode)
{
- if (abandon_move (x, dst_name, &dst_sb))
+ if (abandon_move (x, dst_name, dst_dirfd, dst_relname, &dst_sb))
{
/* Pretend the rename succeeded, so the caller (mv)
doesn't end up removing the source file. */
if (! S_ISDIR (src_mode)
&& (x->interactive == I_ALWAYS_NO
|| (x->interactive == I_ASK_USER
- && ! overwrite_ok (x, dst_name, &dst_sb))))
+ && ! overwrite_ok (x, dst_name, dst_dirfd,
+ dst_relname, &dst_sb))))
return true;
}
Note that it works fine if you use --backup=numbered. */
if (command_line_arg
&& x->backup_type != numbered_backups
- && seen_file (x->dest_info, dst_name, &dst_sb))
+ && seen_file (x->dest_info, dst_relname, &dst_sb))
{
error (0, 0,
_("will not overwrite just-created %s with %s"),
cd /tmp; rm -f a a~; : > a; echo A > a~; cp --b=simple a~ a
would leave two zero-length files: a and a~. */
if (x->backup_type != numbered_backups
- && source_is_dst_backup (srcbase, &src_sb, dst_name))
+ && source_is_dst_backup (srcbase, &src_sb,
+ dst_dirfd, dst_relname))
{
char const *fmt;
fmt = (x->move_mode
return false;
}
- char *tmp_backup = backup_file_rename (AT_FDCWD, dst_name,
+ char *tmp_backup = backup_file_rename (dst_dirfd, dst_relname,
x->backup_type);
/* FIXME: use fts:
to use fts, so using alloca here will be less of a problem. */
if (tmp_backup)
{
- ASSIGN_STRDUPA (dst_backup, tmp_backup);
+ idx_t dirlen = dst_relname - dst_name;
+ idx_t backupsize = strlen (tmp_backup) + 1;
+ dst_backup = alloca (dirlen + backupsize);
+ memcpy (mempcpy (dst_backup, dst_name, dirlen),
+ tmp_backup, backupsize);
free (tmp_backup);
}
else if (errno != ENOENT)
&& ! S_ISREG (src_sb.st_mode))))
))
{
- if (unlink (dst_name) != 0 && errno != ENOENT)
+ if (unlinkat (dst_dirfd, dst_relname, 0) != 0 && errno != ENOENT)
{
error (0, errno, _("cannot remove %s"), quoteaf (dst_name));
return false;
struct stat tmp_buf;
struct stat *dst_lstat_sb;
- /* If we called lstat above, good: use that data.
- Otherwise, call lstat here, in case dst_name is a symlink. */
+ /* If we did not follow symlinks above, good: use that data.
+ Otherwise, call lstatat here, in case dst_name is a symlink. */
if (have_dst_lstat)
dst_lstat_sb = &dst_sb;
else
{
- if (lstat (dst_name, &tmp_buf) == 0)
+ if (lstatat (dst_dirfd, dst_relname, &tmp_buf) == 0)
dst_lstat_sb = &tmp_buf;
else
lstat_ok = false;
/* Never copy through a symlink we've just created. */
if (lstat_ok
&& S_ISLNK (dst_lstat_sb->st_mode)
- && seen_file (x->dest_info, dst_name, dst_lstat_sb))
+ && seen_file (x->dest_info, dst_relname, dst_lstat_sb))
{
error (0, 0,
_("will not copy %s through just-created symlink %s"),
else if (x->recursive && S_ISDIR (src_mode))
{
if (command_line_arg)
- earlier_file = remember_copied (dst_name, src_sb.st_ino, src_sb.st_dev);
+ earlier_file = remember_copied (dst_relname,
+ src_sb.st_ino, src_sb.st_dev);
else
earlier_file = src_to_dest_lookup (src_sb.st_ino, src_sb.st_dev);
}
&& x->dereference == DEREF_COMMAND_LINE_ARGUMENTS)
|| x->dereference == DEREF_ALWAYS))
{
- earlier_file = remember_copied (dst_name, src_sb.st_ino, src_sb.st_dev);
+ earlier_file = remember_copied (dst_relname,
+ src_sb.st_ino, src_sb.st_dev);
}
/* Did we copy this inode somewhere else (in this command line argument)
{
/* If src_name and earlier_file refer to the same directory entry,
then warn about copying a directory into itself. */
- if (same_name (src_name, earlier_file))
+ if (same_nameat (AT_FDCWD, src_name, dst_dirfd, earlier_file))
{
error (0, 0, _("cannot copy a directory, %s, into itself, %s"),
quoteaf_n (0, top_level_src_name),
*copy_into_self = true;
goto un_backup;
}
- else if (same_name (dst_name, earlier_file))
+ else if (same_nameat (dst_dirfd, dst_relname,
+ dst_dirfd, earlier_file))
{
error (0, 0, _("warning: source directory %s "
"specified more than once"),
}
else
{
+ char *earlier = subst_suffix (dst_name, dst_relname,
+ earlier_file);
error (0, 0, _("will not create hard link %s to directory %s"),
- quoteaf_n (0, dst_name), quoteaf_n (1, earlier_file));
+ quoteaf_n (0, dst_name), quoteaf_n (1, earlier));
+ free (earlier);
goto un_backup;
}
}
else
{
- if (! create_hard_link (earlier_file, dst_name, true, x->verbose,
- dereference))
+ if (! create_hard_link (NULL, dst_dirfd, earlier_file,
+ dst_name, dst_dirfd, dst_relname,
+ true, x->verbose, dereference))
goto un_backup;
return true;
if (x->move_mode)
{
if (rename_errno == EEXIST)
- rename_errno = rename (src_name, dst_name) == 0 ? 0 : errno;
+ rename_errno = ((renameat (AT_FDCWD, src_name, dst_dirfd, dst_relname)
+ == 0)
+ ? 0 : errno);
if (rename_errno == 0)
{
changed those, and 'mv' always uses lstat.
We could limit it further by operating
only on non-directories. */
- record_file (x->dest_info, dst_name, &src_sb);
+ record_file (x->dest_info, dst_relname, &src_sb);
}
return true;
or not, and this is enforced above. Therefore we check the src_mode
and operate on dst_name here as a tighter constraint and also because
src_mode is readily available here. */
- if ((S_ISDIR (src_mode) ? rmdir (dst_name) : unlink (dst_name)) != 0
+ if ((unlinkat (dst_dirfd, dst_relname,
+ S_ISDIR (src_mode) ? AT_REMOVEDIR : 0)
+ != 0)
&& errno != ENOENT)
{
error (0, errno,
(src_mode & ~S_IRWXUGO) != 0. However, common practice is
to ask mkdir to copy all the CHMOD_MODE_BITS, letting mkdir
decide what to do with S_ISUID | S_ISGID | S_ISVTX. */
- if (mkdir (dst_name, dst_mode_bits & ~omitted_permissions) != 0)
+ mode_t mode = dst_mode_bits & ~omitted_permissions;
+ if (mkdirat (dst_dirfd, dst_relname, mode) != 0)
{
error (0, errno, _("cannot create directory %s"),
quoteaf (dst_name));
for writing the directory's contents. Check if these
permissions are there. */
- if (lstat (dst_name, &dst_sb) != 0)
+ if (lstatat (dst_dirfd, dst_relname, &dst_sb) != 0)
{
error (0, errno, _("cannot stat %s"), quoteaf (dst_name));
goto un_backup;
dst_mode = dst_sb.st_mode;
restore_dst_mode = true;
- if (lchmod (dst_name, dst_mode | S_IRWXU) != 0)
+ if (lchmodat (dst_dirfd, dst_relname, dst_mode | S_IRWXU) != 0)
{
error (0, errno, _("setting permissions for %s"),
quoteaf (dst_name));
source command line argument. */
if (!*first_dir_created_per_command_line_arg)
{
- remember_copied (dst_name, dst_sb.st_ino, dst_sb.st_dev);
+ remember_copied (dst_relname, dst_sb.st_ino, dst_sb.st_dev);
*first_dir_created_per_command_line_arg = true;
}
this fails -- otherwise, the failure to read a single file
in a source directory would cause the containing destination
directory not to have owner/perms set properly. */
- delayed_ok = copy_dir (src_name, dst_name, new_dst, &src_sb, dir, x,
+ delayed_ok = copy_dir (src_name, dst_name, dst_dirfd, dst_relname,
+ new_dst, &src_sb, dir, x,
first_dir_created_per_command_line_arg,
copy_into_self);
}
char *dst_parent;
bool in_current_dir;
- dst_parent = dir_name (dst_name);
+ dst_parent = dir_name (dst_relname);
- in_current_dir = (STREQ (".", dst_parent)
+ in_current_dir = ((dst_dirfd == AT_FDCWD && STREQ (".", dst_parent))
/* If either stat call fails, it's ok not to report
the failure and say dst_name is in the current
directory. Other things will fail later. */
|| stat (".", &dot_sb) != 0
- || stat (dst_parent, &dst_parent_sb) != 0
+ || (statat (dst_dirfd, dst_parent, &dst_parent_sb)
+ != 0)
|| SAME_INODE (dot_sb, dst_parent_sb));
free (dst_parent);
}
}
- int err = force_symlinkat (src_name, AT_FDCWD, dst_name,
+ int err = force_symlinkat (src_name, dst_dirfd, dst_relname,
x->unlink_dest_after_failed_open, -1);
if (0 < err)
{
{
bool replace = (x->unlink_dest_after_failed_open
|| x->interactive == I_ASK_USER);
- if (! create_hard_link (src_name, dst_name, replace, false, dereference))
+ if (! create_hard_link (src_name, AT_FDCWD, src_name,
+ dst_name, dst_dirfd, dst_relname,
+ replace, false, dereference))
goto un_backup;
}
else if (S_ISREG (src_mode)
normally the same, and the exception (where x->set_mode) is
used only by 'install', which POSIX does not specify and
where DST_MODE_BITS is what's wanted. */
- if (! copy_reg (src_name, dst_name, x, dst_mode_bits & S_IRWXUGO,
+ if (! copy_reg (src_name, dst_name, dst_dirfd, dst_relname,
+ x, dst_mode_bits & S_IRWXUGO,
omitted_permissions, &new_dst, &src_sb))
goto un_backup;
}
else if (S_ISFIFO (src_mode))
{
- /* Use mknod, rather than mkfifo, because the former preserves
- the special mode bits of a fifo on Solaris 10, while mkfifo
- does not. But fall back on mkfifo, because on some BSD systems,
- mknod always fails when asked to create a FIFO. */
- if (mknod (dst_name, src_mode & ~omitted_permissions, 0) != 0)
- if (mkfifo (dst_name, src_mode & ~S_IFIFO & ~omitted_permissions) != 0)
+ /* Use mknodat, rather than mkfifoat, because the former preserves
+ the special mode bits of a fifo on Solaris 10, while mkfifoat
+ does not. But fall back on mkfifoat, because on some BSD systems,
+ mknodat always fails when asked to create a FIFO. */
+ mode_t mode = src_mode & ~omitted_permissions;
+ if (mknodat (dst_dirfd, dst_relname, mode, 0) != 0)
+ if (mkfifoat (dst_dirfd, dst_relname, mode & ~S_IFIFO) != 0)
{
error (0, errno, _("cannot create fifo %s"), quoteaf (dst_name));
goto un_backup;
}
else if (S_ISBLK (src_mode) || S_ISCHR (src_mode) || S_ISSOCK (src_mode))
{
- if (mknod (dst_name, src_mode & ~omitted_permissions, src_sb.st_rdev)
- != 0)
+ mode_t mode = src_mode & ~omitted_permissions;
+ if (mknodat (dst_dirfd, dst_relname, mode, src_sb.st_rdev) != 0)
{
error (0, errno, _("cannot create special file %s"),
quoteaf (dst_name));
goto un_backup;
}
- int symlink_err = force_symlinkat (src_link_val, AT_FDCWD, dst_name,
+ int symlink_err = force_symlinkat (src_link_val, dst_dirfd, dst_relname,
x->unlink_dest_after_failed_open, -1);
if (0 < symlink_err && x->update && !new_dst && S_ISLNK (dst_sb.st_mode)
&& dst_sb.st_size == strlen (src_link_val))
in some cases, e.g., if the destination symlink has the
wrong ownership, permissions, or timestamps. */
char *dest_link_val =
- areadlink_with_size (dst_name, dst_sb.st_size);
+ areadlinkat_with_size (dst_dirfd, dst_relname, dst_sb.st_size);
if (dest_link_val)
{
if (STREQ (dest_link_val, src_link_val))
/* Preserve the owner and group of the just-'copied'
symbolic link, if possible. */
if (HAVE_LCHOWN
- && lchown (dst_name, src_sb.st_uid, src_sb.st_gid) != 0
+ && (lchownat (dst_dirfd, dst_relname,
+ src_sb.st_uid, src_sb.st_gid)
+ != 0)
&& ! chown_failure_ok (x))
{
error (0, errno, _("failed to preserve ownership for %s"),
/* Now that the destination file is very likely to exist,
add its info to the set. */
struct stat sb;
- if (lstat (dst_name, &sb) == 0)
- record_file (x->dest_info, dst_name, &sb);
+ if (lstatat (dst_dirfd, dst_relname, &sb) == 0)
+ record_file (x->dest_info, dst_relname, &sb);
}
/* If we've just created a hard-link due to cp's --link option,
timespec[0] = get_stat_atime (&src_sb);
timespec[1] = get_stat_mtime (&src_sb);
- if ((dest_is_symlink
- ? utimens_symlink (dst_name, timespec)
- : utimens (dst_name, timespec))
- != 0)
+ int utimensat_flags = dest_is_symlink ? AT_SYMLINK_NOFOLLOW : 0;
+ if (utimensat (dst_dirfd, dst_relname, timespec, utimensat_flags) != 0)
{
error (0, errno, _("preserving times for %s"), quoteaf (dst_name));
if (x->require_preserve)
if (!dest_is_symlink && x->preserve_ownership
&& (new_dst || !SAME_OWNER_AND_GROUP (src_sb, dst_sb)))
{
- switch (set_owner (x, dst_name, -1, &src_sb, new_dst, &dst_sb))
+ switch (set_owner (x, dst_name, dst_dirfd, dst_relname, -1,
+ &src_sb, new_dst, &dst_sb))
{
case -1:
return false;
the lstat, but deducing the current destination mode
is tricky in the presence of implementation-defined
rules for special mode bits. */
- if (new_dst && lstat (dst_name, &dst_sb) != 0)
+ if (new_dst && lstatat (dst_dirfd, dst_relname, &dst_sb) != 0)
{
error (0, errno, _("cannot stat %s"), quoteaf (dst_name));
return false;
if (restore_dst_mode)
{
- if (lchmod (dst_name, dst_mode | omitted_permissions) != 0)
+ if (lchmodat (dst_dirfd, dst_relname, dst_mode | omitted_permissions)
+ != 0)
{
error (0, errno, _("preserving permissions for %s"),
quoteaf (dst_name));
if (dst_backup)
{
- if (rename (dst_backup, dst_name) != 0)
+ char const *dst_relbackup = &dst_backup[dst_relname - dst_name];
+ if (renameat (dst_dirfd, dst_relbackup, dst_dirfd, dst_relname) != 0)
error (0, errno, _("cannot un-backup %s"), quoteaf (dst_name));
else
{
return true;
}
-/* Copy the file SRC_NAME to the file DST_NAME. The files may be of
- any type. NONEXISTENT_DST should be true if the file DST_NAME
- is known not to exist (e.g., because its parent directory was just
- created); NONEXISTENT_DST should be false if DST_NAME might already
- exist. OPTIONS is ... FIXME-describe
+/* Copy the file SRC_NAME to the file DST_NAME aka DST_DIRFD+DST_RELNAME.
+ NONEXISTENT_DST means the file DST_NAME is known not to exist
+ (e.g., because its parent directory was just created).
+ OPTIONS is ... FIXME-describe
Set *COPY_INTO_SELF if SRC_NAME is a parent of (or the
same as) DST_NAME; otherwise, set clear it.
Return true if successful. */
extern bool
copy (char const *src_name, char const *dst_name,
+ int dst_dirfd, char const *dst_relname,
bool nonexistent_dst, const struct cp_options *options,
bool *copy_into_self, bool *rename_succeeded)
{
top_level_dst_name = dst_name;
bool first_dir_created_per_command_line_arg = false;
- return copy_internal (src_name, dst_name, nonexistent_dst, NULL, NULL,
+ return copy_internal (src_name, dst_name, dst_dirfd, dst_relname,
+ nonexistent_dst, NULL, NULL,
options, true,
&first_dir_created_per_command_line_arg,
copy_into_self, rename_succeeded);
#include "utimens.h"
#include "acl.h"
-#if ! HAVE_LCHOWN
-# define lchown(name, uid, gid) chown (name, uid, gid)
-#endif
-
/* The official name of this program (e.g., no 'g' prefix). */
#define PROGRAM_NAME "cp"
exit (status);
}
-/* Ensure that the parent directories of CONST_DST_NAME have the
+/* Ensure that parents of CONST_DST_NAME aka DST_DIRFD+DST_RELNAME have the
correct protections, for the --parents option. This is done
after all copying has been completed, to allow permissions
that don't include user write/execute.
- SRC_OFFSET is the index in CONST_DST_NAME of the beginning of the
- source directory name.
-
ATTR_LIST is a null-terminated linked list of structures that
indicates the end of the filename of each intermediate directory
in CONST_DST_NAME that may need to have its attributes changed.
when done. */
static bool
-re_protect (char const *const_dst_name, size_t src_offset,
+re_protect (char const *const_dst_name, int dst_dirfd, char const *dst_relname,
struct dir_attr *attr_list, const struct cp_options *x)
{
struct dir_attr *p;
char *src_name; /* The source name in 'dst_name'. */
ASSIGN_STRDUPA (dst_name, const_dst_name);
- src_name = dst_name + src_offset;
+ src_name = dst_name + (dst_relname - const_dst_name);
for (p = attr_list; p; p = p->next)
{
timespec[0] = get_stat_atime (&p->st);
timespec[1] = get_stat_mtime (&p->st);
- if (utimens (dst_name, timespec))
+ if (utimensat (dst_dirfd, src_name, timespec, 0))
{
error (0, errno, _("failed to preserve times for %s"),
quoteaf (dst_name));
if (x->preserve_ownership)
{
- if (lchown (dst_name, p->st.st_uid, p->st.st_gid) != 0)
+ if (lchownat (dst_dirfd, src_name, p->st.st_uid, p->st.st_gid) != 0)
{
if (! chown_failure_ok (x))
{
}
/* Failing to preserve ownership is OK. Still, try to preserve
the group, but ignore the possible error. */
- ignore_value (lchown (dst_name, -1, p->st.st_gid));
+ ignore_value (lchownat (dst_dirfd, src_name, -1, p->st.st_gid));
}
}
}
else if (p->restore_mode)
{
- if (lchmod (dst_name, p->st.st_mode) != 0)
+ if (lchmodat (dst_dirfd, src_name, p->st.st_mode) != 0)
{
error (0, errno, _("failed to preserve permissions for %s"),
quoteaf (dst_name));
SRC_OFFSET is the index in CONST_DIR (which is a destination
directory) of the beginning of the source directory name.
Create any leading directories that don't already exist.
+ DST_DIRFD is a file descriptor for the target directory.
If VERBOSE_FMT_STRING is nonzero, use it as a printf format
string for printing a message after successfully making a directory.
The format should take two string arguments: the names of the
static bool
make_dir_parents_private (char const *const_dir, size_t src_offset,
+ int dst_dirfd,
char const *verbose_fmt_string,
struct dir_attr **attr_list, bool *new_dst,
const struct cp_options *x)
char *dir; /* A copy of CONST_DIR we can change. */
char *src; /* Source name in DIR. */
char *dst_dir; /* Leading directory of DIR. */
- size_t dirlen; /* Length of DIR. */
+ idx_t dirlen = dir_len (const_dir);
+
+ /* Succeed immediately if the parent of CONST_DIR must already exist,
+ as the target directory has already been checked. */
+ if (dirlen <= src_offset)
+ return true;
ASSIGN_STRDUPA (dir, const_dir);
src = dir + src_offset;
- dirlen = dir_len (dir);
dst_dir = alloca (dirlen + 1);
memcpy (dst_dir, dir, dirlen);
dst_dir[dirlen] = '\0';
+ char const *dst_reldir = dst_dir + src_offset;
+ while (*dst_reldir == '/')
+ dst_reldir++;
*attr_list = NULL;
/* XXX: If all dirs are present at the destination,
no permissions or security contexts will be updated. */
- if (stat (dst_dir, &stats) != 0)
+ if (statat (dst_dirfd, dst_reldir, &stats) != 0)
{
/* A parent of CONST_DIR does not exist.
Make all missing intermediate directories. */
slash = src;
while (*slash == '/')
slash++;
+ dst_reldir = slash;
+
while ((slash = strchr (slash, '/')))
{
struct dir_attr *new IF_LINT ( = NULL);
bool missing_dir;
*slash = '\0';
- missing_dir = (stat (dir, &stats) != 0);
+ missing_dir = statat (dst_dirfd, dst_reldir, &stats) != 0;
if (missing_dir || x->preserve_ownership || x->preserve_mode
|| x->preserve_timestamps)
decide what to do with S_ISUID | S_ISGID | S_ISVTX. */
mkdir_mode = x->explicit_no_preserve_mode ? S_IRWXUGO : src_mode;
mkdir_mode &= CHMOD_MODE_BITS & ~omitted_permissions;
- if (mkdir (dir, mkdir_mode) != 0)
+ if (mkdirat (dst_dirfd, dst_reldir, mkdir_mode) != 0)
{
error (0, errno, _("cannot make directory %s"),
quoteaf (dir));
for writing the directory's contents. Check if these
permissions are there. */
- if (lstat (dir, &stats))
+ if (lstatat (dst_dirfd, dst_reldir, &stats))
{
error (0, errno, _("failed to get attributes of %s"),
quoteaf (dir));
}
}
- if ((stats.st_mode & S_IRWXU) != S_IRWXU)
+ mode_t accessible = stats.st_mode | S_IRWXU;
+ if (stats.st_mode != accessible)
{
/* Make the new directory searchable and writable.
The original permissions will be restored later. */
- if (lchmod (dir, stats.st_mode | S_IRWXU) != 0)
+ if (lchmodat (dst_dirfd, dst_reldir, accessible) != 0)
{
error (0, errno, _("setting permissions for %s"),
quoteaf (dir));
return true;
}
-/* FILE is the last operand of this command.
- Return true if FILE is a directory.
+/* Must F designate the working directory? */
- Without -f, report an error and exit if FILE exists
- but can't be accessed.
+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;
+}
- If the file exists and is accessible store the file's status into *ST.
- Otherwise, set *NEW_DST. */
+/* 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 bool
-target_directory_operand (char const *file, struct stat *st,
- bool *new_dst, bool forcing)
+static int
+target_directory_operand (char const *file)
{
- int err = (stat (file, st) == 0 ? 0 : errno);
- bool is_a_dir = !err && S_ISDIR (st->st_mode);
- if (err)
+ if (must_be_working_directory (file))
+ return AT_FDCWD;
+
+ int fd = open (file, O_PATHSEARCH | O_DIRECTORY);
+
+ if (!O_DIRECTORY && 0 <= fd)
{
- if (err == ENOENT)
- *new_dst = true;
- else if (forcing)
- st->st_mode = 0; /* clear so we don't enter --backup case below. */
- else
- die (EXIT_FAILURE, err, _("failed to access %s"), quoteaf (file));
+ /* On old systems like Solaris 10 that do not support O_DIRECTORY,
+ check by hand whether DIRECTORY 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 is_a_dir;
+
+ 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.
struct stat sb;
bool new_dst = false;
bool ok = true;
- bool forcing = x->unlink_dest_before_opening
- || x->unlink_dest_after_failed_open;
if (n_files <= !target_directory)
{
usage (EXIT_FAILURE);
}
+ sb.st_mode = 0;
+ int target_dirfd = AT_FDCWD;
if (no_target_directory)
{
if (target_directory)
error (0, 0, _("extra operand %s"), quoteaf (file[2]));
usage (EXIT_FAILURE);
}
- /* Update NEW_DST and SB, which may be checked below. */
- ignore_value (target_directory_operand (file[n_files -1], &sb, &new_dst,
- forcing));
}
- else if (!target_directory)
+ else if (target_directory)
{
- if (2 <= n_files
- && target_directory_operand (file[n_files - 1], &sb, &new_dst,
- forcing))
- target_directory = file[--n_files];
- else if (2 < n_files)
- die (EXIT_FAILURE, 0, _("target %s is not a directory"),
- quoteaf (file[n_files - 1]));
+ 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];
+ int fd = target_directory_operand (lastfile);
+ if (target_dirfd_valid (fd))
+ {
+ target_dirfd = fd;
+ target_directory = lastfile;
+ n_files--;
+ }
+ else
+ {
+ int err = errno;
+ if (err == ENOENT)
+ new_dst = true;
+
+ /* 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. */
+ if (2 < n_files
+ || (O_PATHSEARCH == O_SEARCH && err == EACCES
+ && stat (lastfile, &sb) == 0 && S_ISDIR (sb.st_mode)))
+ die (EXIT_FAILURE, err, _("target %s"), quoteaf (lastfile));
+ }
}
if (target_directory)
/* Use 'arg' without trailing slashes in constructing destination
file names. Otherwise, we can end up trying to create a
- directory via 'mkdir ("dst/foo/"...', which is not portable.
- It fails, due to the trailing slash, on at least
+ directory using a name with trailing slash, which fails on
NetBSD 1.[34] systems. */
ASSIGN_STRDUPA (arg_no_trailing_slash, arg);
strip_trailing_slashes (arg_no_trailing_slash);
leading directories. */
parent_exists =
(make_dir_parents_private
- (dst_name, arg_in_concat - dst_name,
+ (dst_name, arg_in_concat - dst_name, target_dirfd,
(x->verbose ? "%s -> %s\n" : NULL),
&attr_list, &new_dst, x));
+
+ while (*arg_in_concat == '/')
+ arg_in_concat++;
}
else
{
ASSIGN_STRDUPA (arg_base, last_component (arg));
strip_trailing_slashes (arg_base);
/* For 'cp -R source/.. dest', don't copy into 'dest/..'. */
- dst_name = (STREQ (arg_base, "..")
- ? xstrdup (target_directory)
- : file_name_concat (target_directory, arg_base,
- NULL));
+ arg_base += STREQ (arg_base, "..");
+ dst_name = file_name_concat (target_directory, arg_base,
+ &arg_in_concat);
}
if (!parent_exists)
else
{
bool copy_into_self;
- ok &= copy (arg, dst_name, new_dst, x, ©_into_self, NULL);
+ ok &= copy (arg, dst_name, target_dirfd, arg_in_concat,
+ new_dst, x, ©_into_self, NULL);
if (parents_option)
- ok &= re_protect (dst_name, arg_in_concat - dst_name,
+ ok &= re_protect (dst_name, target_dirfd, arg_in_concat,
attr_list, x);
}
if (x->unlink_dest_after_failed_open
&& x->backup_type != no_backups
&& STREQ (source, dest)
- && !new_dst && S_ISREG (sb.st_mode))
+ && !new_dst
+ && (sb.st_mode != 0 || stat (dest, &sb) == 0) && S_ISREG (sb.st_mode))
{
static struct cp_options x_tmp;
x = &x_tmp;
}
- ok = copy (source, dest, false, x, &unused, NULL);
+ ok = copy (source, dest, AT_FDCWD, dest, false, x, &unused, NULL);
}
return ok;
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;