diff options
Diffstat (limited to 'libmisc/copydir.c')
-rw-r--r-- | libmisc/copydir.c | 968 |
1 files changed, 0 insertions, 968 deletions
diff --git a/libmisc/copydir.c b/libmisc/copydir.c deleted file mode 100644 index b692aa9..0000000 --- a/libmisc/copydir.c +++ /dev/null @@ -1,968 +0,0 @@ -/* - * 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); -} |