* Copyright (c) 1991 - 1994, Julianne Frances Haugh
* Copyright (c) 1996 - 2001, Marek Michałkiewicz
* Copyright (c) 2003 - 2006, Tomasz Kłoczko
- * Copyright (c) 2007 - 2008, Nicolas François
+ * Copyright (c) 2007 - 2010, Nicolas François
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
#ident "$Id$"
+#include <assert.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include "defines.h"
#ifdef WITH_SELINUX
#include <selinux/selinux.h>
-#endif
-static const char *src_orig;
-static const char *dst_orig;
+#endif /* WITH_SELINUX */
+#if defined(WITH_ACL) || defined(WITH_ATTR)
+#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 */
+
+#ifdef WITH_SELINUX
+static bool selinux_checked = false;
+static bool selinux_enabled;
+#endif /* WITH_SELINUX */
+
+static /*@null@*/const char *src_orig;
+static /*@null@*/const char *dst_orig;
struct link_name {
dev_t ln_dev;
ino_t ln_ino;
- int ln_count;
+ nlink_t ln_count;
char *ln_name;
- struct link_name *ln_next;
+ /*@dependent@*/struct link_name *ln_next;
};
-static struct link_name *links;
+static /*@exposed@*/struct link_name *links;
static int copy_entry (const char *src, const char *dst,
- long int uid, long int gid);
+ bool reset_selinux,
+ uid_t old_uid, uid_t new_uid,
+ gid_t old_gid, gid_t new_gid);
static int copy_dir (const char *src, const char *dst,
+ bool reset_selinux,
const struct stat *statp, const struct timeval mt[],
- long int uid, long int gid);
+ uid_t old_uid, uid_t new_uid,
+ gid_t old_gid, gid_t new_gid);
#ifdef S_IFLNK
+static /*@null@*/char *readlink_malloc (const char *filename);
static int copy_symlink (const char *src, const char *dst,
+ unused bool reset_selinux,
const struct stat *statp, const struct timeval mt[],
- long int uid, long int gid);
-#endif
-static int copy_hardlink (const char *src, const char *dst,
+ uid_t old_uid, uid_t new_uid,
+ gid_t old_gid, gid_t new_gid);
+#endif /* S_IFLNK */
+static int copy_hardlink (const char *dst,
+ unused bool reset_selinux,
struct link_name *lp);
-static int copy_special (const char *dst,
+static int copy_special (const char *src, const char *dst,
+ bool reset_selinux,
const struct stat *statp, const struct timeval mt[],
- long int uid, long int gid);
+ uid_t old_uid, uid_t new_uid,
+ gid_t old_gid, gid_t new_gid);
static int copy_file (const char *src, const char *dst,
+ bool reset_selinux,
const struct stat *statp, const struct timeval mt[],
- long int uid, long int gid);
+ uid_t old_uid, uid_t new_uid,
+ gid_t old_gid, gid_t new_gid);
+static int chown_if_needed (const char *dst, const struct stat *statp,
+ uid_t old_uid, uid_t new_uid,
+ gid_t old_gid, gid_t new_gid);
+static int lchown_if_needed (const char *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);
#ifdef WITH_SELINUX
/*
- * selinux_file_context - Set the security context before any file or
- * directory creation.
+ * set_selinux_file_context - Set the security context before any file or
+ * directory creation.
*
- * selinux_file_context () should be called before any creation of file,
- * symlink, directory, ...
+ * set_selinux_file_context () should be called before any creation
+ * of file, symlink, directory, ...
*
+ * Callers may have to Reset SELinux to create files with default
+ * contexts with reset_selinux_file_context
*/
-static int selinux_file_context (const char *dst_name)
+int set_selinux_file_context (const char *dst_name)
{
- static bool selinux_checked = false;
- static bool selinux_enabled;
- security_context_t scontext = NULL;
+ /*@null@*/security_context_t scontext = NULL;
if (!selinux_checked) {
selinux_enabled = is_selinux_enabled () > 0;
}
return 0;
}
-#endif
+
+/*
+ * reset_selinux_file_context - Reset the security context to the default
+ * policy behavior
+ *
+ * reset_selinux_file_context () should be called after the context
+ * was changed with set_selinux_file_context ()
+ */
+int reset_selinux_file_context (void)
+{
+ if (!selinux_checked) {
+ selinux_enabled = is_selinux_enabled () > 0;
+ selinux_checked = true;
+ }
+ if (selinux_enabled) {
+ if (setfscreatecon (NULL) != 0) {
+ return 1;
+ }
+ }
+ return 0;
+}
+#endif /* WITH_SELINUX */
+
+#if defined(WITH_ACL) || defined(WITH_ATTR)
+/*
+ * error_acl - format the error messages for the ACL and EQ libraries.
+ */
+static void error_acl (struct error_context *ctx, const char *fmt, ...)
+{
+ va_list ap;
+
+ /* ignore the case when destination does not support ACLs
+ * or extended attributes */
+ if (ENOTSUP == errno) {
+ errno = 0;
+ return;
+ }
+
+ va_start (ap, fmt);
+ (void) fprintf (stderr, _("%s: "), Prog);
+ if (vfprintf (stderr, fmt, ap) != 0) {
+ (void) fputs (_(": "), stderr);
+ }
+ (void) fprintf (stderr, "%s\n", strerror (errno));
+ va_end (ap);
+}
+
+static struct error_context ctx = {
+ error_acl
+};
+#endif /* WITH_ACL || WITH_ATTR */
/*
* remove_link - delete a link from the linked list
*/
-static void remove_link (struct link_name *ln)
+static void remove_link (/*@only@*/struct link_name *ln)
{
struct link_name *lp;
}
if (NULL == lp) {
+ free (ln->ln_name);
+ free (ln);
return;
}
* check_link - see if a file is really a link
*/
-static struct link_name *check_link (const char *name, const struct stat *sb)
+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 name_len;
size_t len;
- for (lp = links; lp; lp = lp->ln_next) {
+ /* 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;
}
lp->ln_count = sb->st_nlink;
len = name_len - src_len + dst_len + 1;
lp->ln_name = (char *) xmalloc (len);
- snprintf (lp->ln_name, len, "%s%s", dst_orig, name + src_len);
+ (void) snprintf (lp->ln_name, len, "%s%s", dst_orig, name + src_len);
lp->ln_next = links;
links = lp;
*
* 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,
- long int uid, long int gid)
+ bool copy_root, bool reset_selinux,
+ uid_t old_uid, uid_t new_uid,
+ gid_t old_gid, gid_t new_gid)
{
- char src_name[1024];
- char dst_name[1024];
int err = 0;
bool set_orig = false;
struct DIRECT *ent;
DIR *dir;
+ if (copy_root) {
+ struct stat sb;
+ if (access (dst_root, F_OK) == 0) {
+ return -1;
+ }
+
+ if (LSTAT (src_root, &sb) == -1) {
+ return -1;
+ }
+
+ if (!S_ISDIR (sb.st_mode)) {
+ fprintf (stderr,
+ "%s: %s is not a directory",
+ Prog, src_root);
+ return -1;
+ }
+
+ return copy_entry (src_root, dst_root, 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
*/
if ((strcmp (ent->d_name, ".") != 0) &&
(strcmp (ent->d_name, "..") != 0)) {
- /*
- * Make sure the resulting source and destination
- * filenames will fit in their buffers.
- */
- if ( (strlen (src_root) + strlen (ent->d_name) + 2 >
- sizeof src_name)
- || (strlen (dst_root) + strlen (ent->d_name) + 2 >
- sizeof dst_name)) {
+ 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_root);
+ dst_len += strlen (dst_root);
+
+ 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.
*/
- snprintf (src_name, sizeof src_name, "%s/%s",
- src_root, ent->d_name);
- snprintf (dst_name, sizeof dst_name, "%s/%s",
- dst_root, ent->d_name);
-
- err = copy_entry (src_name, dst_name, uid, gid);
+ (void) snprintf (src_name, src_len, "%s/%s",
+ src_root, ent->d_name);
+ (void) snprintf (dst_name, dst_len, "%s/%s",
+ dst_root, ent->d_name);
+
+ err = copy_entry (src_name, dst_name,
+ reset_selinux,
+ old_uid, new_uid,
+ old_gid, new_gid);
+ }
+ if (NULL != src_name) {
+ free (src_name);
+ }
+ if (NULL != dst_name) {
+ free (dst_name);
}
}
}
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;
}
*
* The access and modification time will not be modified.
*
- * The permissions will be set to uid/gid.
+ * The permissions will be set to new_uid/new_gid.
*
- * If uid (resp. gid) is equal to -1, the user (resp. group) will
+ * 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 char *src, const char *dst,
- long int uid, long int gid)
+ 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;
#ifdef HAVE_STRUCT_STAT_ST_ATIM
mt[0].tv_sec = sb.st_atim.tv_sec;
mt[0].tv_usec = sb.st_atim.tv_nsec / 1000;
-#else
+#else /* !HAVE_STRUCT_STAT_ST_ATIM */
mt[0].tv_sec = sb.st_atime;
-#ifdef HAVE_STRUCT_STAT_ST_ATIMENSEC
+# ifdef HAVE_STRUCT_STAT_ST_ATIMENSEC
mt[0].tv_usec = sb.st_atimensec / 1000;
-#else
+# else /* !HAVE_STRUCT_STAT_ST_ATIMENSEC */
mt[0].tv_usec = 0;
-#endif
-#endif
+# 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_usec = sb.st_mtim.tv_nsec / 1000;
-#else
+#else /* !HAVE_STRUCT_STAT_ST_MTIM */
mt[1].tv_sec = sb.st_mtime;
-#ifdef HAVE_STRUCT_STAT_ST_MTIMENSEC
+# ifdef HAVE_STRUCT_STAT_ST_MTIMENSEC
mt[1].tv_usec = sb.st_mtimensec / 1000;
-#else
+# else /* !HAVE_STRUCT_STAT_ST_MTIMENSEC */
mt[1].tv_usec = 0;
-#endif
-#endif
+# endif /* !HAVE_STRUCT_STAT_ST_MTIMENSEC */
+#endif /* !HAVE_STRUCT_STAT_ST_MTIM */
if (S_ISDIR (sb.st_mode)) {
- err = copy_dir (src, dst, &sb, mt, uid, gid);
+ err = copy_dir (src, dst, reset_selinux, &sb, mt,
+ old_uid, new_uid, old_gid, new_gid);
}
#ifdef S_IFLNK
*/
else if (S_ISLNK (sb.st_mode)) {
- err = copy_symlink (src, dst, &sb, mt, uid, gid);
+ err = copy_symlink (src, dst, reset_selinux, &sb, mt,
+ old_uid, new_uid, old_gid, new_gid);
}
-#endif
+#endif /* S_IFLNK */
/*
* See if this is a previously copied link
*/
else if ((lp = check_link (src, &sb)) != NULL) {
- err = copy_hardlink (src, dst, lp);
+ err = copy_hardlink (dst, reset_selinux, lp);
}
/*
*/
else if (!S_ISREG (sb.st_mode)) {
- err = copy_special (dst, &sb, mt, uid, gid);
+ err = copy_special (src, dst, reset_selinux, &sb, mt,
+ old_uid, new_uid, old_gid, new_gid);
}
/*
*/
else {
- err = copy_file (src, dst, &sb, mt, uid, gid);
+ err = copy_file (src, dst, reset_selinux, &sb, mt,
+ old_uid, new_uid, old_gid, new_gid);
}
}
*
* Copy a directory (recursively) from src to dst.
*
- * statp, mt, uid, gid are used to set the access and modification and the
- * access rights.
+ * 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 char *src, const char *dst,
+ bool reset_selinux,
const struct stat *statp, const struct timeval mt[],
- long int uid, long int gid)
+ uid_t old_uid, uid_t new_uid,
+ gid_t old_gid, gid_t new_gid)
{
int err = 0;
*/
#ifdef WITH_SELINUX
- selinux_file_context (dst);
-#endif
+ if (set_selinux_file_context (dst) != 0) {
+ return -1;
+ }
+#endif /* WITH_SELINUX */
if ( (mkdir (dst, statp->st_mode) != 0)
- || (chown (dst,
- (uid == - 1) ? statp->st_uid : (uid_t) uid,
- (gid == - 1) ? statp->st_gid : (gid_t) gid) != 0)
+ || (chown_if_needed (dst, statp,
+ old_uid, new_uid, old_gid, new_gid) != 0)
+#ifdef WITH_ACL
+ || ( (perm_copy_file (src, dst, &ctx) != 0)
+ && (errno != 0))
+#else /* !WITH_ACL */
|| (chmod (dst, statp->st_mode) != 0)
- || (copy_tree (src, dst, uid, gid) != 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_file (src, dst, NULL, &ctx) != 0)
+ && (errno != 0))
+#endif /* WITH_ATTR */
+ || (copy_tree (src, dst, false, reset_selinux,
+ old_uid, new_uid, old_gid, new_gid) != 0)
|| (utimes (dst, mt) != 0)) {
err = -1;
}
}
#ifdef S_IFLNK
+/*
+ * 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, uid, gid are used to set the access and modification and the
- * access rights.
+ * 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 char *src, const char *dst,
+ unused bool reset_selinux,
const struct stat *statp, const struct timeval mt[],
- long int uid, long int gid)
+ uid_t old_uid, uid_t new_uid,
+ gid_t old_gid, gid_t new_gid)
{
- char oldlink[1024];
- char dummy[1024];
- int len;
- int err = 0;
+ 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
* destination directory name.
*/
- len = readlink (src, oldlink, sizeof (oldlink) - 1);
- if (len < 0) {
+ oldlink = readlink_malloc (src);
+ if (NULL == oldlink) {
return -1;
}
- oldlink[len] = '\0'; /* readlink() does not NUL-terminate */
+
+ /* 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) {
- snprintf (dummy, sizeof dummy, "%s%s",
- dst_orig,
- oldlink + strlen (src_orig));
- strcpy (oldlink, dummy);
+ 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
- selinux_file_context (dst);
-#endif
+ if (set_selinux_file_context (dst) != 0) {
+ free (oldlink);
+ return -1;
+ }
+#endif /* WITH_SELINUX */
if ( (symlink (oldlink, dst) != 0)
- || (lchown (dst,
- (uid == -1) ? statp->st_uid : (uid_t) uid,
- (gid == -1) ? statp->st_gid : (gid_t) gid) != 0)) {
+ || (lchown_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);
#ifdef HAVE_LUTIMES
/* 2007-10-18: We don't care about
* it returns ENOSYS on many system
* - not implemented
*/
- lutimes (dst, mt);
-#endif
+ (void) lutimes (dst, mt);
+#endif /* HAVE_LUTIMES */
- return err;
+ return 0;
}
-#endif
+#endif /* S_IFLNK */
/*
* copy_hardlink - copy a hardlink
*
* Return 0 on success, -1 on error.
*/
-static int copy_hardlink (const char *src, const char *dst,
+static int copy_hardlink (const char *dst,
+ unused bool reset_selinux,
struct link_name *lp)
{
- /* TODO: selinux needed? */
+ /* FIXME: selinux, ACL, Extended Attributes needed? */
if (link (lp->ln_name, dst) != 0) {
return -1;
}
- if (unlink (src) != 0) {
- return -1;
- }
/* If the file could be unlinked, decrement the links counter,
- * and delete the file if it was the last reference */
+ * and forget about this link if it was the last reference */
lp->ln_count--;
if (lp->ln_count <= 0) {
remove_link (lp);
*
* Copy a special file from src to dst.
*
- * statp, mt, uid, gid are used to set the access and modification and the
- * access rights.
+ * 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 char *dst,
+static int copy_special (const char *src, const char *dst,
+ bool reset_selinux,
const struct stat *statp, const struct timeval mt[],
- long int uid, long int gid)
+ uid_t old_uid, uid_t new_uid,
+ gid_t old_gid, gid_t new_gid)
{
int err = 0;
#ifdef WITH_SELINUX
- selinux_file_context (dst);
-#endif
+ if (set_selinux_file_context (dst) != 0) {
+ return -1;
+ }
+#endif /* WITH_SELINUX */
if ( (mknod (dst, statp->st_mode & ~07777, statp->st_rdev) != 0)
- || (chown (dst,
- (uid == -1) ? statp->st_uid : (uid_t) uid,
- (gid == -1) ? statp->st_gid : (gid_t) gid) != 0)
+ || (chown_if_needed (dst, statp,
+ old_uid, new_uid, old_gid, new_gid) != 0)
+#ifdef WITH_ACL
+ || ( (perm_copy_file (src, dst, &ctx) != 0)
+ && (errno != 0))
+#else /* !WITH_ACL */
|| (chmod (dst, statp->st_mode & 07777) != 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_file (src, dst, NULL, &ctx) != 0)
+ && (errno != 0))
+#endif /* WITH_ATTR */
|| (utimes (dst, mt) != 0)) {
err = -1;
}
*
* Copy a file from src to dst.
*
- * statp, mt, uid, gid are used to set the access and modification and the
- * access rights.
+ * 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 char *src, const char *dst,
+ bool reset_selinux,
const struct stat *statp, const struct timeval mt[],
- long int uid, long int gid)
+ uid_t old_uid, uid_t new_uid,
+ gid_t old_gid, gid_t new_gid)
{
int err = 0;
int ifd;
return -1;
}
#ifdef WITH_SELINUX
- selinux_file_context (dst);
-#endif
- ofd = open (dst, O_WRONLY | O_CREAT | O_TRUNC, 0);
+ if (set_selinux_file_context (dst) != 0) {
+ return -1;
+ }
+#endif /* WITH_SELINUX */
+ ofd = open (dst, O_WRONLY | O_CREAT | O_TRUNC, statp->st_mode & 07777);
if ( (ofd < 0)
- || (chown (dst,
- (uid == -1) ? statp->st_uid : (uid_t) uid,
- (gid == -1) ? statp->st_gid : (gid_t) gid) != 0)
- || (chmod (dst, statp->st_mode & 07777) != 0)) {
+ || (fchown_if_needed (ofd, statp,
+ old_uid, new_uid, old_gid, new_gid) != 0)
+#ifdef WITH_ACL
+ || ( (perm_copy_fd (src, ifd, dst, ofd, &ctx) != 0)
+ && (errno != 0))
+#else /* !WITH_ACL */
+ || (fchmod (ofd, statp->st_mode & 07777) != 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, ifd, dst, ofd, NULL, &ctx) != 0)
+ && (errno != 0))
+#endif /* WITH_ATTR */
+ ) {
(void) close (ifd);
return -1;
}
while ((cnt = read (ifd, buf, sizeof buf)) > 0) {
if (write (ofd, buf, (size_t)cnt) != cnt) {
+ (void) close (ifd);
return -1;
}
}
if (futimes (ofd, mt) != 0) {
return -1;
}
-#else
- if (utimes(dst, mt) != 0) {
- return -1;
- }
-#endif
+#endif /* HAVE_FUTIMES */
if (close (ofd) != 0) {
return -1;
}
- return err;
-}
-
-/*
- * remove_tree - delete a directory tree
- *
- * remove_tree() walks a directory tree and deletes all the files
- * and directories.
- * At the end, it deletes the root directory itself.
- */
-
-int remove_tree (const char *root)
-{
- char new_name[1024];
- int err = 0;
- struct DIRECT *ent;
- struct stat sb;
- DIR *dir;
-
- /*
- * Make certain the directory exists.
- */
-
- if (access (root, F_OK) != 0) {
- 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 = opendir (root);
- if (NULL == dir) {
+#ifndef HAVE_FUTIMES
+ if (utimes(dst, mt) != 0) {
return -1;
}
-
- while ((ent = readdir (dir))) {
-
- /*
- * Skip the "." and ".." entries
- */
-
- if (strcmp (ent->d_name, ".") == 0 ||
- strcmp (ent->d_name, "..") == 0) {
- continue;
- }
-
- /*
- * Make the filename for the current entry.
- */
-
- if (strlen (root) + strlen (ent->d_name) + 2 > sizeof new_name) {
- err = -1;
- break;
- }
- snprintf (new_name, sizeof new_name, "%s/%s", root,
- ent->d_name);
- if (LSTAT (new_name, &sb) == -1) {
- continue;
- }
-
- if (S_ISDIR (sb.st_mode)) {
- /*
- * Recursively delete this directory.
- */
- if (remove_tree (new_name) != 0) {
- err = -1;
- break;
- }
- } else {
- /*
- * Delete the file.
- */
- if (unlink (new_name) != 0) {
- err = -1;
- break;
- }
- }
- }
- (void) closedir (dir);
-
- if (0 == err) {
- if (rmdir (root) != 0) {
- err = -1;
- }
- }
+#endif /* !HAVE_FUTIMES */
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 (chown, const char *)
+def_chown_if_needed (lchown, const char *)
+def_chown_if_needed (fchown, int)
+