shadow/libmisc/copydir.c
Alex Colomar 0d9799de04 Don't test for NULL before calling free(3)
free(3) accepts NULL, since the oldest ISO C.  I guess the
paranoid code was taking care of prehistoric implementations of
free(3).  I've never known of an implementation that doesn't
conform to this, so let's simplify this.

Remove xfree(3), which was effectively an equivalent of free(3).

Signed-off-by: Alejandro Colomar <alx@kernel.org>
2022-09-29 16:03:53 +02:00

969 lines
27 KiB
C

/*
* SPDX-FileCopyrightText: 1991 - 1994, Julianne Frances Haugh
* SPDX-FileCopyrightText: 1996 - 2001, Marek Michałkiewicz
* SPDX-FileCopyrightText: 2003 - 2006, Tomasz Kłoczko
* SPDX-FileCopyrightText: 2007 - 2010, Nicolas François
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <config.h>
#ident "$Id$"
#include <assert.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <fcntl.h>
#include <stdio.h>
#include "prototypes.h"
#include "defines.h"
#ifdef WITH_SELINUX
#include <selinux/selinux.h>
#endif /* WITH_SELINUX */
#if defined(WITH_ACL) || defined(WITH_ATTR)
#include <stdarg.h>
#include <attr/error_context.h>
#endif /* WITH_ACL || WITH_ATTR */
#ifdef WITH_ACL
#include <acl/libacl.h>
#endif /* WITH_ACL */
#ifdef WITH_ATTR
#include <attr/libattr.h>
#endif /* WITH_ATTR */
#include "shadowlog.h"
static /*@null@*/const char *src_orig;
static /*@null@*/const char *dst_orig;
struct link_name {
dev_t ln_dev;
ino_t ln_ino;
nlink_t ln_count;
char *ln_name;
/*@dependent@*/struct link_name *ln_next;
};
static /*@exposed@*/struct link_name *links;
struct path_info {
const char *full_path;
int dirfd;
const char *name;
};
static int copy_entry (const struct path_info *src, const struct path_info *dst,
bool reset_selinux,
uid_t old_uid, uid_t new_uid,
gid_t old_gid, gid_t new_gid);
static int copy_dir (const struct path_info *src, const struct path_info *dst,
bool reset_selinux,
const struct stat *statp, const struct timespec mt[],
uid_t old_uid, uid_t new_uid,
gid_t old_gid, gid_t new_gid);
static /*@null@*/char *readlink_malloc (const char *filename);
static int copy_symlink (const struct path_info *src, const struct path_info *dst,
unused bool reset_selinux,
const struct stat *statp, const struct timespec mt[],
uid_t old_uid, uid_t new_uid,
gid_t old_gid, gid_t new_gid);
static int copy_hardlink (const struct path_info *dst,
unused bool reset_selinux,
struct link_name *lp);
static int copy_special (const struct path_info *src, const struct path_info *dst,
bool reset_selinux,
const struct stat *statp, const struct timespec mt[],
uid_t old_uid, uid_t new_uid,
gid_t old_gid, gid_t new_gid);
static int copy_file (const struct path_info *src, const struct path_info *dst,
bool reset_selinux,
const struct stat *statp, const struct timespec mt[],
uid_t old_uid, uid_t new_uid,
gid_t old_gid, gid_t new_gid);
static int chownat_if_needed (const struct path_info *dst, const struct stat *statp,
uid_t old_uid, uid_t new_uid,
gid_t old_gid, gid_t new_gid);
static int fchown_if_needed (int fdst, const struct stat *statp,
uid_t old_uid, uid_t new_uid,
gid_t old_gid, gid_t new_gid);
#if defined(WITH_ACL) || defined(WITH_ATTR)
/*
* error_acl - format the error messages for the ACL and EQ libraries.
*/
format_attr(printf, 2, 3)
static void error_acl (unused struct error_context *ctx, const char *fmt, ...)
{
va_list ap;
FILE *shadow_logfd = log_get_logfd();
/* ignore the case when destination does not support ACLs
* or extended attributes */
if (ENOTSUP == errno) {
errno = 0;
return;
}
va_start (ap, fmt);
(void) fprintf (shadow_logfd, _("%s: "), log_get_progname());
if (vfprintf (shadow_logfd, fmt, ap) != 0) {
(void) fputs (_(": "), shadow_logfd);
}
(void) fprintf (shadow_logfd, "%s\n", strerror (errno));
va_end (ap);
}
static struct error_context ctx = {
error_acl, NULL, NULL
};
#endif /* WITH_ACL || WITH_ATTR */
#ifdef WITH_ACL
static int perm_copy_path(const struct path_info *src,
const struct path_info *dst,
struct error_context *errctx)
{
int src_fd, dst_fd, ret;
src_fd = openat(src->dirfd, src->name, O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
if (src_fd < 0) {
return -1;
}
dst_fd = openat(dst->dirfd, dst->name, O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
if (dst_fd < 0) {
(void) close (src_fd);
return -1;
}
ret = perm_copy_fd(src->full_path, src_fd, dst->full_path, dst_fd, errctx);
(void) close (src_fd);
(void) close (dst_fd);
return ret;
}
#endif /* WITH_ACL */
#ifdef WITH_ATTR
static int attr_copy_path(const struct path_info *src,
const struct path_info *dst,
int (*callback) (const char *, struct error_context *),
struct error_context *errctx)
{
int src_fd, dst_fd, ret;
src_fd = openat(src->dirfd, src->name, O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
if (src_fd < 0) {
return -1;
}
dst_fd = openat(dst->dirfd, dst->name, O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
if (dst_fd < 0) {
(void) close (src_fd);
return -1;
}
ret = attr_copy_fd(src->full_path, src_fd, dst->full_path, dst_fd, callback, errctx);
(void) close (src_fd);
(void) close (dst_fd);
return ret;
}
#endif /* WITH_ATTR */
/*
* remove_link - delete a link from the linked list
*/
static void remove_link (/*@only@*/struct link_name *ln)
{
struct link_name *lp;
if (links == ln) {
links = ln->ln_next;
free (ln->ln_name);
free (ln);
return;
}
for (lp = links; NULL !=lp; lp = lp->ln_next) {
if (lp->ln_next == ln) {
break;
}
}
if (NULL == lp) {
free (ln->ln_name);
free (ln);
return;
}
lp->ln_next = lp->ln_next->ln_next;
free (ln->ln_name);
free (ln);
}
/*
* check_link - see if a file is really a link
*/
static /*@exposed@*/ /*@null@*/struct link_name *check_link (const char *name, const struct stat *sb)
{
struct link_name *lp;
size_t src_len;
size_t dst_len;
size_t name_len;
size_t len;
/* copy_tree () must be the entry point */
assert (NULL != src_orig);
assert (NULL != dst_orig);
for (lp = links; NULL != lp; lp = lp->ln_next) {
if ((lp->ln_dev == sb->st_dev) && (lp->ln_ino == sb->st_ino)) {
return lp;
}
}
if (sb->st_nlink == 1) {
return NULL;
}
lp = (struct link_name *) xmalloc (sizeof *lp);
src_len = strlen (src_orig);
dst_len = strlen (dst_orig);
name_len = strlen (name);
lp->ln_dev = sb->st_dev;
lp->ln_ino = sb->st_ino;
lp->ln_count = sb->st_nlink;
len = name_len - src_len + dst_len + 1;
lp->ln_name = (char *) xmalloc (len);
(void) snprintf (lp->ln_name, len, "%s%s", dst_orig, name + src_len);
lp->ln_next = links;
links = lp;
return NULL;
}
static int copy_tree_impl (const struct path_info *src, const struct path_info *dst,
bool copy_root, bool reset_selinux,
uid_t old_uid, uid_t new_uid,
gid_t old_gid, gid_t new_gid)
{
int dst_fd, src_fd, err = 0;
bool set_orig = false;
const struct dirent *ent;
DIR *dir;
if (copy_root) {
struct stat sb;
if ( fstatat (dst->dirfd, dst->name, &sb, 0) == 0
|| errno != ENOENT) {
return -1;
}
if (fstatat (src->dirfd, src->name, &sb, AT_SYMLINK_NOFOLLOW) == -1) {
return -1;
}
if (!S_ISDIR (sb.st_mode)) {
fprintf (log_get_logfd(),
"%s: %s is not a directory",
log_get_progname(), src->full_path);
return -1;
}
return copy_entry (src, dst, reset_selinux,
old_uid, new_uid, old_gid, new_gid);
}
/*
* Make certain both directories exist. This routine is called
* after the home directory is created, or recursively after the
* target is created. It assumes the target directory exists.
*/
src_fd = openat (src->dirfd, src->name, O_DIRECTORY | O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
if (src_fd < 0) {
return -1;
}
dst_fd = openat (dst->dirfd, dst->name, O_DIRECTORY | O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
if (dst_fd < 0) {
(void) close (src_fd);
return -1;
}
/*
* Open the source directory and read each entry. Every file
* entry in the directory is copied with the UID and GID set
* to the provided values. As an added security feature only
* regular files (and directories ...) are copied, and no file
* is made set-ID.
*/
dir = fdopendir (src_fd);
if (NULL == dir) {
(void) close (src_fd);
(void) close (dst_fd);
return -1;
}
if (src_orig == NULL) {
src_orig = src->full_path;
dst_orig = dst->full_path;
set_orig = true;
}
while ((0 == err) && (ent = readdir (dir)) != NULL) {
/*
* Skip the "." and ".." entries
*/
if ((strcmp (ent->d_name, ".") != 0) &&
(strcmp (ent->d_name, "..") != 0)) {
char *src_name;
char *dst_name;
size_t src_len = strlen (ent->d_name) + 2;
size_t dst_len = strlen (ent->d_name) + 2;
src_len += strlen (src->full_path);
dst_len += strlen (dst->full_path);
src_name = (char *) malloc (src_len);
dst_name = (char *) malloc (dst_len);
if ((NULL == src_name) || (NULL == dst_name)) {
err = -1;
} else {
/*
* Build the filename for both the source and
* the destination files.
*/
struct path_info src_entry, dst_entry;
(void) snprintf (src_name, src_len, "%s/%s",
src->full_path, ent->d_name);
(void) snprintf (dst_name, dst_len, "%s/%s",
dst->full_path, ent->d_name);
src_entry.full_path = src_name;
src_entry.dirfd = dirfd(dir);
src_entry.name = ent->d_name;
dst_entry.full_path = dst_name;
dst_entry.dirfd = dst_fd;
dst_entry.name = ent->d_name;
err = copy_entry (&src_entry, &dst_entry,
reset_selinux,
old_uid, new_uid,
old_gid, new_gid);
}
free (src_name);
free (dst_name);
}
}
(void) closedir (dir);
(void) close (dst_fd);
if (set_orig) {
src_orig = NULL;
dst_orig = NULL;
/* FIXME: clean links
* Since there can be hardlinks elsewhere on the device,
* we cannot check that all the hardlinks were found:
assert (NULL == links);
*/
}
#ifdef WITH_SELINUX
/* Reset SELinux to create files with default contexts.
* Note that the context is only reset on exit of copy_tree (it is
* assumed that the program would quit without needing a restored
* context if copy_tree failed previously), and that copy_tree can
* be called recursively (hence the context is set on the
* sub-functions of copy_entry).
*/
if (reset_selinux_file_context () != 0) {
err = -1;
}
#endif /* WITH_SELINUX */
return err;
}
/*
* copy_entry - copy the entry of a directory
*
* Copy the entry src to dst.
* Depending on the type of entry, this function will forward the
* request to copy_dir(), copy_symlink(), copy_hardlink(),
* copy_special(), or copy_file().
*
* The access and modification time will not be modified.
*
* The permissions will be set to new_uid/new_gid.
*
* If new_uid (resp. new_gid) is equal to -1, the user (resp. group) will
* not be modified.
*
* Only the files owned (resp. group-owned) by old_uid (resp.
* old_gid) will be modified, unless old_uid (resp. old_gid) is set
* to -1.
*/
static int copy_entry (const struct path_info *src, const struct path_info *dst,
bool reset_selinux,
uid_t old_uid, uid_t new_uid,
gid_t old_gid, gid_t new_gid)
{
int err = 0;
struct stat sb;
struct link_name *lp;
struct timespec mt[2];
if (fstatat(src->dirfd, src->name, &sb, AT_SYMLINK_NOFOLLOW) == -1) {
/* If we cannot stat the file, do not care. */
} else {
#ifdef HAVE_STRUCT_STAT_ST_ATIM
mt[0].tv_sec = sb.st_atim.tv_sec;
mt[0].tv_nsec = sb.st_atim.tv_nsec;
#else /* !HAVE_STRUCT_STAT_ST_ATIM */
mt[0].tv_sec = sb.st_atime;
# ifdef HAVE_STRUCT_STAT_ST_ATIMENSEC
mt[0].tv_nsec = sb.st_atimensec;
# else /* !HAVE_STRUCT_STAT_ST_ATIMENSEC */
mt[0].tv_nsec = 0;
# endif /* !HAVE_STRUCT_STAT_ST_ATIMENSEC */
#endif /* !HAVE_STRUCT_STAT_ST_ATIM */
#ifdef HAVE_STRUCT_STAT_ST_MTIM
mt[1].tv_sec = sb.st_mtim.tv_sec;
mt[1].tv_nsec = sb.st_mtim.tv_nsec;
#else /* !HAVE_STRUCT_STAT_ST_MTIM */
mt[1].tv_sec = sb.st_mtime;
# ifdef HAVE_STRUCT_STAT_ST_MTIMENSEC
mt[1].tv_nsec = sb.st_mtimensec;
# else /* !HAVE_STRUCT_STAT_ST_MTIMENSEC */
mt[1].tv_nsec = 0;
# endif /* !HAVE_STRUCT_STAT_ST_MTIMENSEC */
#endif /* !HAVE_STRUCT_STAT_ST_MTIM */
if (S_ISDIR (sb.st_mode)) {
err = copy_dir (src, dst, reset_selinux, &sb, mt,
old_uid, new_uid, old_gid, new_gid);
}
/*
* Copy any symbolic links
*/
else if (S_ISLNK (sb.st_mode)) {
err = copy_symlink (src, dst, reset_selinux, &sb, mt,
old_uid, new_uid, old_gid, new_gid);
}
/*
* See if this is a previously copied link
*/
else if ((lp = check_link (src->full_path, &sb)) != NULL) {
err = copy_hardlink (dst, reset_selinux, lp);
}
/*
* Deal with FIFOs and special files. The user really
* shouldn't have any of these, but it seems like it
* would be nice to copy everything ...
*/
else if (!S_ISREG (sb.st_mode)) {
err = copy_special (src, dst, reset_selinux, &sb, mt,
old_uid, new_uid, old_gid, new_gid);
}
/*
* Create the new file and copy the contents. The new
* file will be owned by the provided UID and GID values.
*/
else {
err = copy_file (src, dst, reset_selinux, &sb, mt,
old_uid, new_uid, old_gid, new_gid);
}
}
return err;
}
/*
* copy_dir - copy a directory
*
* Copy a directory (recursively) from src to dst.
*
* statp, mt, old_uid, new_uid, old_gid, and new_gid are used to set
* the access and modification and the access rights.
*
* Return 0 on success, -1 on error.
*/
static int copy_dir (const struct path_info *src, const struct path_info *dst,
bool reset_selinux,
const struct stat *statp, const struct timespec mt[],
uid_t old_uid, uid_t new_uid,
gid_t old_gid, gid_t new_gid)
{
int err = 0;
/*
* Create a new target directory, make it owned by
* the user and then recursively copy that directory.
*/
#ifdef WITH_SELINUX
if (set_selinux_file_context (dst->full_path, S_IFDIR) != 0) {
return -1;
}
#endif /* WITH_SELINUX */
if ( (mkdirat (dst->dirfd, dst->name, 0700) != 0)
|| (chownat_if_needed (dst, statp,
old_uid, new_uid, old_gid, new_gid) != 0)
|| (fchmodat (dst->dirfd, dst->name, statp->st_mode & 07777, AT_SYMLINK_NOFOLLOW) != 0)
#ifdef WITH_ACL
|| ( (perm_copy_path (src, dst, &ctx) != 0)
&& (errno != 0))
#endif /* WITH_ACL */
#ifdef WITH_ATTR
/*
* If the third parameter is NULL, all extended attributes
* except those that define Access Control Lists are copied.
* ACLs are excluded by default because copying them between
* file systems with and without ACL support needs some
* additional logic so that no unexpected permissions result.
*/
|| ( !reset_selinux
&& (attr_copy_path (src, dst, NULL, &ctx) != 0)
&& (errno != 0))
#endif /* WITH_ATTR */
|| (copy_tree_impl (src, dst, false, reset_selinux,
old_uid, new_uid, old_gid, new_gid) != 0)
|| (utimensat (dst->dirfd, dst->name, mt, AT_SYMLINK_NOFOLLOW) != 0)) {
err = -1;
}
return err;
}
/*
* readlink_malloc - wrapper for readlink
*
* return NULL on error.
* The return string shall be freed by the caller.
*/
static /*@null@*/char *readlink_malloc (const char *filename)
{
size_t size = 1024;
while (true) {
ssize_t nchars;
char *buffer = (char *) malloc (size);
if (NULL == buffer) {
return NULL;
}
nchars = readlink (filename, buffer, size);
if (nchars < 0) {
free(buffer);
return NULL;
}
if ((size_t) nchars < size) { /* The buffer was large enough */
/* readlink does not nul-terminate */
buffer[nchars] = '\0';
return buffer;
}
/* Try again with a bigger buffer */
free (buffer);
size *= 2;
}
}
/*
* copy_symlink - copy a symlink
*
* Copy a symlink from src to dst.
*
* statp, mt, old_uid, new_uid, old_gid, and new_gid are used to set
* the access and modification and the access rights.
*
* Return 0 on success, -1 on error.
*/
static int copy_symlink (const struct path_info *src, const struct path_info *dst,
unused bool reset_selinux,
const struct stat *statp, const struct timespec mt[],
uid_t old_uid, uid_t new_uid,
gid_t old_gid, gid_t new_gid)
{
char *oldlink;
/* copy_tree () must be the entry point */
assert (NULL != src_orig);
assert (NULL != dst_orig);
/*
* Get the name of the file which the link points
* to. If that name begins with the original
* source directory name, that part of the link
* name will be replaced with the original
* destination directory name.
*/
oldlink = readlink_malloc (src->full_path);
if (NULL == oldlink) {
return -1;
}
/* If src was a link to an entry of the src_orig directory itself,
* create a link to the corresponding entry in the dst_orig
* directory.
*/
if (strncmp (oldlink, src_orig, strlen (src_orig)) == 0) {
size_t len = strlen (dst_orig) + strlen (oldlink) - strlen (src_orig) + 1;
char *dummy = (char *) xmalloc (len);
(void) snprintf (dummy, len, "%s%s",
dst_orig,
oldlink + strlen (src_orig));
free (oldlink);
oldlink = dummy;
}
#ifdef WITH_SELINUX
if (set_selinux_file_context (dst->full_path, S_IFLNK) != 0) {
free (oldlink);
return -1;
}
#endif /* WITH_SELINUX */
if ( (symlinkat (oldlink, dst->dirfd, dst->name) != 0)
|| (chownat_if_needed (dst, statp,
old_uid, new_uid, old_gid, new_gid) != 0)) {
/* FIXME: there are no modes on symlinks, right?
* ACL could be copied, but this would be much more
* complex than calling perm_copy_file.
* Ditto for Extended Attributes.
* We currently only document that ACL and Extended
* Attributes are not copied.
*/
free (oldlink);
return -1;
}
free (oldlink);
if (utimensat (dst->dirfd, dst->name, mt, AT_SYMLINK_NOFOLLOW) != 0) {
return -1;
}
return 0;
}
/*
* copy_hardlink - copy a hardlink
*
* Copy a hardlink from src to dst.
*
* Return 0 on success, -1 on error.
*/
static int copy_hardlink (const struct path_info *dst,
unused bool reset_selinux,
struct link_name *lp)
{
/* FIXME: selinux, ACL, Extended Attributes needed? */
if (linkat (AT_FDCWD, lp->ln_name, dst->dirfd, dst->name, 0) != 0) {
return -1;
}
/* If the file could be unlinked, decrement the links counter,
* and forget about this link if it was the last reference */
lp->ln_count--;
if (lp->ln_count <= 0) {
remove_link (lp);
}
return 0;
}
/*
* copy_special - copy a special file
*
* Copy a special file from src to dst.
*
* statp, mt, old_uid, new_uid, old_gid, and new_gid are used to set
* the access and modification and the access rights.
*
* Return 0 on success, -1 on error.
*/
static int copy_special (const struct path_info *src, const struct path_info *dst,
bool reset_selinux,
const struct stat *statp, const struct timespec mt[],
uid_t old_uid, uid_t new_uid,
gid_t old_gid, gid_t new_gid)
{
int err = 0;
#ifdef WITH_SELINUX
if (set_selinux_file_context (dst->full_path, statp->st_mode & S_IFMT) != 0) {
return -1;
}
#endif /* WITH_SELINUX */
if ( (mknodat (dst->dirfd, dst->name, statp->st_mode & ~07777U, statp->st_rdev) != 0)
|| (chownat_if_needed (dst, statp,
old_uid, new_uid, old_gid, new_gid) != 0)
|| (fchmodat (dst->dirfd, dst->name, statp->st_mode & 07777, AT_SYMLINK_NOFOLLOW) != 0)
#ifdef WITH_ACL
|| ( (perm_copy_path (src, dst, &ctx) != 0)
&& (errno != 0))
#endif /* WITH_ACL */
#ifdef WITH_ATTR
/*
* If the third parameter is NULL, all extended attributes
* except those that define Access Control Lists are copied.
* ACLs are excluded by default because copying them between
* file systems with and without ACL support needs some
* additional logic so that no unexpected permissions result.
*/
|| ( !reset_selinux
&& (attr_copy_path (src, dst, NULL, &ctx) != 0)
&& (errno != 0))
#endif /* WITH_ATTR */
|| (utimensat (dst->dirfd, dst->name, mt, AT_SYMLINK_NOFOLLOW) != 0)) {
err = -1;
}
return err;
}
/*
* full_write - write entire buffer
*
* Write up to count bytes from the buffer starting at buf to the
* file referred to by the file descriptor fd.
* Retry in case of a short write.
*
* Returns the number of bytes written on success, -1 on error.
*/
static ssize_t full_write(int fd, const void *buf, size_t count) {
ssize_t written = 0;
while (count > 0) {
ssize_t res;
res = write(fd, buf, count);
if (res < 0) {
if (errno == EINTR) {
continue;
}
return res;
}
if (res == 0) {
break;
}
written += res;
buf = (const unsigned char*)buf + res;
count -= (size_t)res;
}
return written;
}
/*
* copy_file - copy a file
*
* Copy a file from src to dst.
*
* statp, mt, old_uid, new_uid, old_gid, and new_gid are used to set
* the access and modification and the access rights.
*
* Return 0 on success, -1 on error.
*/
static int copy_file (const struct path_info *src, const struct path_info *dst,
bool reset_selinux,
const struct stat *statp, const struct timespec mt[],
uid_t old_uid, uid_t new_uid,
gid_t old_gid, gid_t new_gid)
{
int err = 0;
int ifd;
int ofd;
ifd = openat (src->dirfd, src->name, O_RDONLY|O_NOFOLLOW|O_CLOEXEC);
if (ifd < 0) {
return -1;
}
#ifdef WITH_SELINUX
if (set_selinux_file_context (dst->full_path, S_IFREG) != 0) {
(void) close (ifd);
return -1;
}
#endif /* WITH_SELINUX */
ofd = openat (dst->dirfd, dst->name, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC | O_NOFOLLOW | O_CLOEXEC, 0600);
if ( (ofd < 0)
|| (fchown_if_needed (ofd, statp,
old_uid, new_uid, old_gid, new_gid) != 0)
|| (fchmod (ofd, statp->st_mode & 07777) != 0)
#ifdef WITH_ACL
|| ( (perm_copy_fd (src->full_path, ifd, dst->full_path, ofd, &ctx) != 0)
&& (errno != 0))
#endif /* WITH_ACL */
#ifdef WITH_ATTR
/*
* If the third parameter is NULL, all extended attributes
* except those that define Access Control Lists are copied.
* ACLs are excluded by default because copying them between
* file systems with and without ACL support needs some
* additional logic so that no unexpected permissions result.
*/
|| ( !reset_selinux
&& (attr_copy_fd (src->full_path, ifd, dst->full_path, ofd, NULL, &ctx) != 0)
&& (errno != 0))
#endif /* WITH_ATTR */
) {
if (ofd >= 0) {
(void) close (ofd);
}
(void) close (ifd);
return -1;
}
while (true) {
char buf[8192];
ssize_t cnt;
cnt = read (ifd, buf, sizeof buf);
if (cnt < 0) {
if (errno == EINTR) {
continue;
}
(void) close (ofd);
(void) close (ifd);
return -1;
}
if (cnt == 0) {
break;
}
if (full_write (ofd, buf, (size_t)cnt) < 0) {
(void) close (ofd);
(void) close (ifd);
return -1;
}
}
(void) close (ifd);
if (close (ofd) != 0) {
return -1;
}
if (utimensat (dst->dirfd, dst->name, mt, AT_SYMLINK_NOFOLLOW) != 0) {
return -1;
}
return err;
}
#define def_chown_if_needed(chown_function, type_dst) \
static int chown_function ## _if_needed (type_dst dst, \
const struct stat *statp, \
uid_t old_uid, uid_t new_uid, \
gid_t old_gid, gid_t new_gid) \
{ \
uid_t tmpuid = (uid_t) -1; \
gid_t tmpgid = (gid_t) -1; \
\
/* Use new_uid if old_uid is set to -1 or if the file was \
* owned by the user. */ \
if (((uid_t) -1 == old_uid) || (statp->st_uid == old_uid)) { \
tmpuid = new_uid; \
} \
/* Otherwise, or if new_uid was set to -1, we keep the same \
* owner. */ \
if ((uid_t) -1 == tmpuid) { \
tmpuid = statp->st_uid; \
} \
\
if (((gid_t) -1 == old_gid) || (statp->st_gid == old_gid)) { \
tmpgid = new_gid; \
} \
if ((gid_t) -1 == tmpgid) { \
tmpgid = statp->st_gid; \
} \
\
return chown_function (dst, tmpuid, tmpgid); \
}
def_chown_if_needed (fchown, int)
static int chownat_if_needed (const struct path_info *dst,
const struct stat *statp,
uid_t old_uid, uid_t new_uid,
gid_t old_gid, gid_t new_gid)
{
uid_t tmpuid = (uid_t) -1;
gid_t tmpgid = (gid_t) -1;
/* Use new_uid if old_uid is set to -1 or if the file was
* owned by the user. */
if (((uid_t) -1 == old_uid) || (statp->st_uid == old_uid)) {
tmpuid = new_uid;
}
/* Otherwise, or if new_uid was set to -1, we keep the same
* owner. */
if ((uid_t) -1 == tmpuid) {
tmpuid = statp->st_uid;
}
if (((gid_t) -1 == old_gid) || (statp->st_gid == old_gid)) {
tmpgid = new_gid;
}
if ((gid_t) -1 == tmpgid) {
tmpgid = statp->st_gid;
}
return fchownat (dst->dirfd, dst->name, tmpuid, tmpgid, AT_SYMLINK_NOFOLLOW);
}
/*
* copy_tree - copy files in a directory tree
*
* copy_tree() walks a directory tree and copies ordinary files
* as it goes.
*
* When reset_selinux is enabled, extended attributes (and thus
* SELinux attributes) are not copied.
*
* old_uid and new_uid are used to set the ownership of the copied
* files. Unless old_uid is set to -1, only the files owned by
* old_uid have their ownership changed to new_uid. In addition, if
* new_uid is set to -1, no ownership will be changed.
*
* The same logic applies for the group-ownership and
* old_gid/new_gid.
*/
int copy_tree (const char *src_root, const char *dst_root,
bool copy_root, bool reset_selinux,
uid_t old_uid, uid_t new_uid,
gid_t old_gid, gid_t new_gid)
{
const struct path_info src = {
.full_path = src_root,
.dirfd = AT_FDCWD,
.name = src_root
};
const struct path_info dst = {
.full_path = dst_root,
.dirfd = AT_FDCWD,
.name = dst_root
};
return copy_tree_impl(&src, &dst, copy_root, reset_selinux,
old_uid, new_uid, old_gid, new_gid);
}