.Fn copyfile_state_get "copyfile_state_t state" "uint32_t flag" "void * dst"
.Ft int
.Fn copyfile_state_set "copyfile_state_t state" "uint32_t flag" "const void * src"
+.Ft typedef int
+.Fn (*copyfile_callback_t) "int what" "int stage" "copyfile_state_t state" "const char * src" "const char * dst" "void * ctx"
.Sh DESCRIPTION
These functions are used to copy a file's data and/or metadata. (Metadata
consists of permissions, extended attributes, access control lists, and so
.Fn fcopyfile
functions can also have their behavior modified by the following flags:
.Bl -tag -width COPYFILE_NOFOLLOW_SRC
+.It Dv COPYFILE_RECURSIVE
+Causes
+.Fn copyfile
+to recursively copy a hierachy.
+This flag is not used by
+.Fn fcopyfile ;
+see below for more information.
.It Dv COPYFILE_CHECK
Return a bitmask (corresponding to the
.Va flags
the returned value is a pointer to the
.Va state 's
copy, and must not be modified or released.
+.It Dv COPYFILE_STATE_STATUS_CB
+Get or set the callback status function (currently
+only used for recursive copies; see below for details).
+The
+.Va src
+parameter is a pointer to a function of type
+.Vt copyfile_callback_t
+(see above).
+.It Dv COPYFILE_STATE_STATUS_CTX
+Get or set the context parameter for the status
+call-back function (see below for details).
+The
+.Va src
+parameter is a
+.Vt void\ * .
.It Dv COPYFILE_STATE_QUARANTINE
Get or set the quarantine information with the source file.
The
.Va src
-parameter is a pointer to a
-.Vt qtn_file_t
-object (see
-.Pa <quarantine.h> ).
-In the case of
-.Dv COPYFILE_STATE_SET ,
-the object is cloned; in the case of
-.Dv COPYFILE_STATE_GET ,
-the object is simply returned, and it is up to the
-caller to clone it if desired.
+parameter is a pointer to an opaque
+object (type
+.Vt void\ *
+).
+.It Dv COPYFILE_STATE_COPIED
+Get the number of data bytes copied so far.
+(Only valid for
+.Fn copyfile_state_get ;
+see below for more details about callbacks.)
+The
+.Va dst
+parameter is a pointer to
+.Vt off_t
+(type
+.Vt off_t\ * ).
+.El
+.Sh Recursive Copies
+When given the
+.Dv COPYFILE_RECURSIVE
+flag,
+.Fn copyfile
+(but not
+.Fn fcopyfile )
+will use the
+.Xr fts 3
+functions to recursively descend into the source file-system object.
+It then calls
+.Fn copyfile
+on each of the entries it finds that way.
+If a call-back function is given (using
+.Fn copyfile_state_set
+and
+.Dv COPYFILE_STATE_STATUS_CB ),
+the call-back function will be called four times for each directory
+object, and twice for all other objects. (Each directory will
+be examined twice, once on entry -- before copying each of the
+objects contained in the directory -- and once on exit -- after
+copying each object contained in the directory, in order to perform
+some final cleanup.)
+.Pp
+The call-back function will have one of the following values
+as the first argument, indicating what is being copied:
+.Bl -tag -width COPYFILE_RECURSE_DIR_CLEANUP
+.It Dv COPYFILE_RECURSE_FILE
+The object being copied is a file (or, rather,
+something other than a directory).
+.It Dv COPYFILE_RECURSE_DIR
+The object being copied is a directory, and is being
+entered. (That is, none of the filesystem objects contained
+within the directory have been copied yet.)
+.It Dv COPYFILE_RECURSE_DIR_CLEANUP
+The object being copied is a directory, and all of the
+objects contained have been copied. At this stage, the destination directory
+being copied will have any extra permissions that were added to
+allow the copying will be removed.
+.It Dv COPYFILE_RECURSE_ERROR
+There was an error in processing an element of the source hierarchy;
+this happens when
+.Xr fts 3
+returns an error or unknown file type.
+(Currently, the second argument to the call-back function will always
+be
+.Dv COPYFILE_ERR
+in this case.)
+.El
+.Pp
+The second argument to the call-back function will indicate
+the stage of the copy, and will be one of the following values:
+.Bl -tag -width COPYFILE_FINISH
+.It Dv COPYFILE_START
+Before copying has begun. The third
+parameter will be a newly-created
+.Vt copyfile_state_t
+object with the call-back function and context pre-loaded.
+.It Dv COPYFILE_FINISH
+After copying has successfully finished.
+.It Dv COPYFILE_ERR
+Indicates an error has happened at some stage. If the
+first argument to the call-back function is
+.Dv COPYFILE_RECURSE_ERROR ,
+then an error occurred while processing the source hierarchy;
+otherwise, it will indicate what type of object was being copied,
+and
+.Dv errno
+will be set to indicate the error.
+.El
+.Pp
+The fourth and fifth
+parameters are the source and destination paths that
+are to be copied (or have been copied, or failed to copy, depending on
+the second argument).
+.Pp
+The last argument to the call-back function will be the value
+set by
+.Dv COPYFILE_STATE_STATUS_CTX ,
+if any.
+.Pp
+The call-back function is required to return one of the following
+values:
+.Bl -tag -width COPYFILE_CONTINUE
+.It Dv COPYFILE_CONTINUE
+The copy will continue as expected.
+.It Dv COPYFILE_SKIP
+This object will be skipped, and the next object will
+be processed. (Note that, when entering a directory.
+returning
+.Dv COPYFILE_SKIP
+from the call-back function will prevent the contents
+of the directory from being copied.)
+.It Dv COPYFILE_QUIT
+The entire copy is aborted at this stage. Any filesystem
+objects created up to this point will remain.
+.Fn copyfile
+will return -1, but
+.Dv errno
+will be unmodified.
+.El
+.Pp
+The call-back function must always return one of the values listed
+above; if not, the results are undefined.
+.Pp
+The call-back function will be called twice for each object
+(and an additional two times for directory cleanup); the first
+call will have a
+.Ar stage
+parameter of
+.Dv COPYFILE_START ;
+the second time, that value will be either
+.Dv COPYFILE_FINISH
+or
+.Dv COPYFILE_ERR
+to indicate a successful completion, or an error during
+processing.
+In the event of an error, the
+.Dv errno
+value will be set appropriately.
+.Pp
+The
+.Dv COPYFILE_PACK ,
+.Dv COPYFILE_UNPACK ,
+.Dv COPYFILE_MOVE ,
+and
+.Dv COPYFILE_UNLINK
+flags are not used during a recursive copy, and will result
+in an error being returned.
+.Sh Progress Callback
+In addition to the recursive callbacks described above,
+.Fn copyfile
+and
+.Fn fcopyfile
+will also use a callback to report data (i.e.,
+.Dv COPYFILE_DATA )
+progress. If given, the callback will be invoked on each
+.Xr write 2
+call. The first argument to the callback function will be
+.Dv COPYFILE_COPY_DATA .
+The second argument will either be
+.Dv COPYFILE_COPY_PROGRESS
+(indicating that the write was successful), or
+.Dv COPYFILE_ERR
+(indicating that there was an error of some sort).
+.Pp
+The amount of data bytes copied so far can be retrieved using
+.Fn copyfile_state_get ,
+with the
+.Dv COPYFILE_STATE_COPIED
+requestor (the argument type is a pointer to
+.Vt off_t ).
+.Pp
+The return value for the data callback must be one of
+.Bl -tag -width COPYFILE_CONTINUE
+.It Dv COPYFILE_CONTINUE
+The copy will continue as expected.
+(In the case of error, it will attempt to write the data again.)
+.It Dv COPYFILE_SKIP
+The data copy will be aborted, but without error.
+.It Dv COPYFILE_QUIT
+The data copy will be aborted; in the case of
+.Dv COPYFILE_COPY_PROGRESS ,
+.Dv errno
+will be set to
+.Dv ECANCELED .
.El
+.Pp
+While the
+.Va src
+and
+.Va dst
+parameters will be passed in, they may be
+.Dv NULL
+in the case of
+.Fn fcopyfile .
.Sh RETURN VALUES
Except when given the
.Dv COPYFILE_CHECK
return less than 0 on error, and 0 on success.
All of the other functions return 0 on success, and less than 0
on error.
+.Sh WARNING
+Both
+.Fn copyfile
+and
+.Fn fcopyfile
+can copy symbolic links; there is a gap between when the source
+link is examnined and the actual copy is started, and this can
+be a potential security risk, especially if the process has
+elevated privileges.
+.Pp
+When performing a recursive copy, if the source hierarchy
+changes while the copy is occurring, the results are undefined.
.Sh ERRORS
.Fn copyfile
and
will fail if:
.Bl -tag -width Er
.It Bq Er EINVAL
-Either the
+An invalid flag was passed in with
+.Dv COPYFILE_RECURSIVE .
+.It Bq Er EINVAL
+The
.Va from
or
.Va to
-parameter was a
+parameter to
+.Fn copyfile
+was a
.Dv NULL
-pointer (
-.Fn copyfile ),
-or a negative number (
-.Fn fcopyfile ).
+pointer.
+.It Bq Er EINVAL
+The
+.Va from
+or
+.Va to
+parameter to
+.Fn copyfile
+was a negative number.
.It Bq Er ENOMEM
A memory allocation failed.
.It Bq Er ENOTSUP
The source file was not a directory, symbolic link, or regular file.
+.It Bq Er ECANCELED
+The copy was cancelled by callback.
.El
In addition, both functions may set
.Dv errno
/* Now copy the same source file to another destination file */
copyfile(NULL, "/tmp/car", s, COPYFILE_ALL);
copyfile_state_free(s);
+/* Remove extended attributes from a file */
+copyfile("/dev/null", "/tmp/bar", NULL, COPYFILE_XATTR);
.Ed
+.Sh SEE ALSO
+.Xr listxattr 2 ,
+.Xr getxattr 2 ,
+.Xr setxattr 2 ,
+.Xr acl 3
.Sh BUGS
Both
.Fn copyfile
functions lack a way to set the input or output block size.
+.Pp
+Recursive copies do not honor hard links.
.Sh HISTORY
The
.Fn copyfile
#include <sys/acl.h>
#include <libkern/OSByteOrder.h>
#include <membership.h>
+#include <fts.h>
+#include <libgen.h>
#include <TargetConditionals.h>
#if !TARGET_OS_EMBEDDED
#include "copyfile.h"
+enum cfInternalFlags {
+ cfDelayAce = 1,
+};
+
/*
* The state structure keeps track of
* the source filename, the destination filename, their
struct stat sb;
filesec_t fsec;
copyfile_flags_t flags;
+ unsigned int internal_flags;
void *stats;
uint32_t debug;
- void *callbacks;
+ copyfile_callback_t statuscb;
+ void *ctx;
qtn_file_t qinfo; /* Quarantine information -- probably NULL */
+ filesec_t original_fsec;
+ filesec_t permissive_fsec;
+ off_t totalCopied;
+ int err;
};
+struct acl_entry {
+ u_int32_t ae_magic;
+#define _ACL_ENTRY_MAGIC 0xac1ac101
+ u_int32_t ae_tag;
+ guid_t ae_applicable;
+ u_int32_t ae_flags;
+ u_int32_t ae_perms;
+};
+
+#define PACE(ace) do { \
+ struct acl_entry *__t = (struct acl_entry*)(ace); \
+ fprintf(stderr, "%s(%d): " #ace " = { flags = %#x, perms = %#x }\n", __FUNCTION__, __LINE__, __t->ae_flags, __t->ae_perms); \
+ } while (0)
+
+#define PACL(ace) \
+ do { \
+ ssize_t __l; char *__cp = acl_to_text(ace, &__l); \
+ fprintf(stderr, "%s(%d): " #ace " = %s\n", __FUNCTION__, __LINE__, __cp ? __cp : "(null)"); \
+ } while (0)
+
+static int
+acl_compare_permset_np(acl_permset_t p1, acl_permset_t p2)
+{
+ struct pm { u_int32_t ap_perms; } *ps1, *ps2;
+ ps1 = (struct pm*) p1;
+ ps2 = (struct pm*) p2;
+
+ return ((ps1->ap_perms == ps2->ap_perms) ? 1 : 0);
+}
+
/*
* Internally, the process is broken into a series of
* private functions.
{
qtn_file_free(s->qinfo);
s->qinfo = NULL;
- errno = error;
rv = -1;
goto done;
}
return rv;
}
+static int
+add_uberace(acl_t *acl)
+{
+ acl_entry_t entry;
+ acl_permset_t permset;
+ uuid_t qual;
+
+ if (mbr_uid_to_uuid(getuid(), qual) != 0)
+ goto error_exit;
+
+ /*
+ * First, we create an entry, and give it the special name
+ * of ACL_FIRST_ENTRY, thus guaranteeing it will be first.
+ * After that, we clear out all the permissions in it, and
+ * add three permissions: WRITE_DATA, WRITE_ATTRIBUTES, and
+ * WRITE_EXTATTRIBUTES. We put these into an ACE that allows
+ * the functionality, and put this into the ACL.
+ */
+ if (acl_create_entry_np(acl, &entry, ACL_FIRST_ENTRY) == -1)
+ goto error_exit;
+ if (acl_get_permset(entry, &permset) == -1)
+ goto error_exit;
+ if (acl_clear_perms(permset) == -1)
+ goto error_exit;
+ if (acl_add_perm(permset, ACL_WRITE_DATA) == -1)
+ goto error_exit;
+ if (acl_add_perm(permset, ACL_WRITE_ATTRIBUTES) == -1)
+ goto error_exit;
+ if (acl_add_perm(permset, ACL_WRITE_EXTATTRIBUTES) == -1)
+ goto error_exit;
+ if (acl_add_perm(permset, ACL_APPEND_DATA) == -1)
+ goto error_exit;
+ if (acl_add_perm(permset, ACL_WRITE_SECURITY) == -1)
+ goto error_exit;
+ if (acl_set_tag_type(entry, ACL_EXTENDED_ALLOW) == -1)
+ goto error_exit;
+
+ if(acl_set_permset(entry, permset) == -1)
+ goto error_exit;
+ if(acl_set_qualifier(entry, qual) == -1)
+ goto error_exit;
+
+ return 0;
+error_exit:
+ return -1;
+}
+
+static int
+is_uberace(acl_entry_t ace)
+{
+ int retval = 0;
+ acl_permset_t perms, tperms;
+ acl_t tacl;
+ acl_entry_t tentry;
+ acl_tag_t tag;
+ guid_t *qual;
+ uuid_t myuuid;
+
+ // Who am I, and who is the ACE for?
+ mbr_uid_to_uuid(geteuid(), myuuid);
+ qual = (guid_t*)acl_get_qualifier(ace);
+
+ // Need to create a temporary acl, so I can get the uberace template.
+ tacl = acl_init(1);
+ if (tacl == NULL) {
+ goto done;
+ }
+ add_uberace(&tacl);
+ if (acl_get_entry(tacl, ACL_FIRST_ENTRY, &tentry) != 0) {
+ goto done;
+ }
+ acl_get_permset(tentry, &tperms);
+
+ // Now I need to get
+ acl_get_tag_type(ace, &tag);
+ acl_get_permset(ace, &perms);
+
+ if (tag == ACL_EXTENDED_ALLOW &&
+ (memcmp(qual, myuuid, sizeof(myuuid)) == 0) &&
+ acl_compare_permset_np(tperms, perms))
+ retval = 1;
+
+done:
+
+ if (tacl)
+ acl_free(tacl);
+
+ return retval;
+}
+
+static void
+remove_uberace(int fd, struct stat *sbuf)
+{
+ filesec_t fsec = NULL;
+ acl_t acl = NULL;
+ acl_entry_t entry;
+ struct stat sb;
+
+ fsec = filesec_init();
+ if (fsec == NULL) {
+ goto noacl;
+ }
+
+ if (fstatx_np(fd, &sb, fsec) != 0) {
+ if (errno == ENOTSUP)
+ goto noacl;
+ goto done;
+ }
+
+ if (filesec_get_property(fsec, FILESEC_ACL, &acl) != 0) {
+ goto done;
+ }
+
+ if (acl_get_entry(acl, ACL_FIRST_ENTRY, &entry) == 0) {
+ if (is_uberace(entry))
+ {
+ mode_t m = sbuf->st_mode & ~S_IFMT;
+
+ if (acl_delete_entry(acl, entry) != 0 ||
+ filesec_set_property(fsec, FILESEC_ACL, &acl) != 0 ||
+ filesec_set_property(fsec, FILESEC_MODE, &m) != 0 ||
+ fchmodx_np(fd, fsec) != 0)
+ goto noacl;
+ }
+ }
+
+done:
+ if (acl)
+ acl_free(acl);
+ if (fsec)
+ filesec_free(fsec);
+ return;
+
+noacl:
+ fchmod(fd, sbuf->st_mode & ~S_IFMT);
+ goto done;
+}
+
+static void
+reset_security(copyfile_state_t s)
+{
+ /* If we haven't reset the file security information
+ * (COPYFILE_SECURITY is not set in flags)
+ * restore back the permissions the file had originally
+ *
+ * One of the reasons this seems so complicated is that
+ * it is partially at odds with copyfile_security().
+ *
+ * Simplisticly, we are simply trying to make sure we
+ * only copy what was requested, and that we don't stomp
+ * on what wasn't requested.
+ */
+
+#ifdef COPYFILE_RECURSIVE
+ if (s->dst_fd > -1) {
+ struct stat sbuf;
+
+ if (s->src_fd > -1 && (s->flags & COPYFILE_STAT))
+ fstat(s->src_fd, &sbuf);
+ else
+ fstat(s->dst_fd, &sbuf);
+
+ if (!(s->internal_flags & cfDelayAce))
+ remove_uberace(s->dst_fd, &sbuf);
+ }
+#else
+ if (s->permissive_fsec && (s->flags & COPYFILE_SECURITY) != COPYFILE_SECURITY) {
+ if (s->flags & COPYFILE_ACL) {
+ /* Just need to reset the BSD information -- mode, owner, group */
+ (void)fchown(s->dst_fd, s->dst_sb.st_uid, s->dst_sb.st_gid);
+ (void)fchmod(s->dst_fd, s->dst_sb.st_mode);
+ } else {
+ /*
+ * flags is either COPYFILE_STAT, or neither; if it's
+ * neither, then we restore both ACL and POSIX permissions;
+ * if it's STAT, however, then we only want to restore the
+ * ACL (which may be empty). We do that by removing the
+ * POSIX information from the filesec object.
+ */
+ if (s->flags & COPYFILE_STAT) {
+ copyfile_unset_posix_fsec(s->original_fsec);
+ }
+ if (fchmodx_np(s->dst_fd, s->original_fsec) < 0 && errno != ENOTSUP)
+ copyfile_warn("restoring security information");
+ }
+ }
+
+ if (s->permissive_fsec) {
+ filesec_free(s->permissive_fsec);
+ s->permissive_fsec = NULL;
+ }
+
+ if (s->original_fsec) {
+ filesec_free(s->original_fsec);
+ s->original_fsec = NULL;
+ }
+#endif
+
+ return;
+}
+
+/*
+ * copytree -- recursively copy a hierarchy.
+ *
+ * Unlike normal copyfile(), copytree() can copy an entire hierarchy.
+ * Care is taken to keep the ACLs set up correctly, in addition to the
+ * normal copying that is done. (When copying a hierarchy, we can't
+ * get rid of the "allow-all-writes" ACE on a directory until we're done
+ * copying the *contents* of the directory.)
+ *
+ * The other big difference from copyfile (for the moment) is that copytree()
+ * will use a call-back function to pass along information about what is
+ * about to be copied, and whether or not it succeeded.
+ *
+ * copytree() is called from copyfile() -- but copytree() itself then calls
+ * copyfile() to copy each individual object.
+ *
+ * XXX - no effort is made to handle overlapping hierarchies at the moment.
+ *
+ */
+
+static int
+copytree(copyfile_state_t s)
+{
+ char *slash;
+ int retval = 0;
+ int (*sfunc)(const char *, struct stat *);
+ copyfile_callback_t status = NULL;
+ char srcisdir = 0, dstisdir = 0, dstexists = 0;
+ struct stat sbuf;
+ char *src, *dst;
+ const char *dstpathsep = "";
+#ifdef NOTYET
+ char srcpath[PATH_MAX * 2 + 1], dstpath[PATH_MAX * 2 + 1];
+#endif
+ char *srcroot;
+ FTS *fts = NULL;
+ FTSENT *ftsent;
+ ssize_t offset = 0;
+ const char *paths[2] = { 0 };
+ unsigned int flags = 0;
+ int fts_flags = FTS_NOCHDIR;
+
+ if (s == NULL) {
+ errno = EINVAL;
+ retval = -1;
+ goto done;
+ }
+ if (s->flags & (COPYFILE_MOVE | COPYFILE_UNLINK | COPYFILE_CHECK | COPYFILE_PACK | COPYFILE_UNPACK)) {
+ errno = EINVAL;
+ retval = -1;
+ goto done;
+ }
+
+ flags = s->flags & (COPYFILE_ALL | COPYFILE_NOFOLLOW | COPYFILE_VERBOSE);
+
+ paths[0] = src = s->src;
+ dst = s->dst;
+
+ if (src == NULL || dst == NULL) {
+ errno = EINVAL;
+ retval = -1;
+ goto done;
+ }
+
+ sfunc = (flags & COPYFILE_NOFOLLOW_SRC) ? lstat : stat;
+ if ((sfunc)(src, &sbuf) == -1) {
+ retval = -1;
+ goto done;
+ }
+ if (sbuf.st_mode & S_IFDIR) {
+ srcisdir = 1;
+ }
+
+ sfunc = (flags & COPYFILE_NOFOLLOW_DST) ? lstat : stat;
+ if ((sfunc)(dst, &sbuf) == -1) {
+ if (errno != ENOENT) {
+ retval = -1;
+ goto done;
+ }
+ } else {
+ dstexists = 1;
+ if ((sbuf.st_mode & S_IFMT) == S_IFDIR) {
+ dstisdir = 1;
+ }
+ }
+
+#ifdef NOTYET
+ // This doesn't handle filesystem crossing and case sensitivity
+ // So there's got to be a better way
+
+ if (realpath(src, srcpath) == NULL) {
+ retval = -1;
+ goto done;
+ }
+
+ if (realpath(dst, dstpath) == NULL &&
+ (errno == ENOENT && realpath(dirname(dst), dstpath) == NULL)) {
+ retval = -1;
+ goto done;
+ }
+ if (strstr(srcpath, dstpath) != NULL) {
+ errno = EINVAL;
+ retval = -1;
+ goto done;
+ }
+#endif
+ srcroot = basename((char*)src);
+ if (srcroot == NULL) {
+ retval = -1;
+ goto done;
+ }
+
+ /*
+ * To work on as well:
+ * We have a few cases when copying a hierarchy:
+ * 1) src is a non-directory, dst is a directory;
+ * 2) src is a non-directory, dst is a non-directory;
+ * 3) src is a non-directory, dst does not exist;
+ * 4) src is a directory, dst is a directory;
+ * 5) src is a directory, dst is a non-directory;
+ * 6) src is a directory, dst does not exist
+ *
+ * (1) copies src to dst/basename(src).
+ * (2) fails if COPYFILE_EXCLUSIVE is set, otherwise copies src to dst.
+ * (3) and (6) copy src to the name dst.
+ * (4) copies the contents of src to the contents of dst.
+ * (5) is an error.
+ */
+
+ if (dstisdir) {
+ // copy /path/to/src to /path/to/dst/src
+ // Append "/" and (fts_path - strlen(basename(src))) to dst?
+ dstpathsep = "/";
+ slash = strrchr(src, '/');
+ if (slash == NULL)
+ offset = 0;
+ else
+ offset = slash - src + 1;
+ } else {
+ // copy /path/to/src to /path/to/dst
+ // append (fts_path + strlen(src)) to dst?
+ dstpathsep = "";
+ offset = strlen(src);
+ }
+
+ if (s->flags | COPYFILE_NOFOLLOW_SRC)
+ fts_flags |= FTS_PHYSICAL;
+ else
+ fts_flags |= FTS_LOGICAL;
+
+ fts = fts_open((char * const *)paths, fts_flags, NULL);
+
+ status = s->statuscb;
+ while ((ftsent = fts_read(fts)) != NULL) {
+ int rv = 0;
+ char *dstfile = NULL;
+ int cmd = 0;
+ copyfile_state_t tstate = copyfile_state_alloc();
+ if (tstate == NULL) {
+ errno = ENOMEM;
+ retval = -1;
+ break;
+ }
+ tstate->statuscb = s->statuscb;
+ tstate->ctx = s->ctx;
+ asprintf(&dstfile, "%s%s%s", dst, dstpathsep, ftsent->fts_path + offset);
+ if (dstfile == NULL) {
+ copyfile_state_free(tstate);
+ errno = ENOMEM;
+ retval = -1;
+ break;
+ }
+ switch (ftsent->fts_info) {
+ case FTS_D:
+ tstate->internal_flags |= cfDelayAce;
+ cmd = COPYFILE_RECURSE_DIR;
+ break;
+ case FTS_SL:
+ case FTS_SLNONE:
+ case FTS_DEFAULT:
+ case FTS_F:
+ cmd = COPYFILE_RECURSE_FILE;
+ break;
+ case FTS_DP:
+ cmd = COPYFILE_RECURSE_DIR_CLEANUP;
+ break;
+ case FTS_DNR:
+ case FTS_ERR:
+ case FTS_NS:
+ case FTS_NSOK:
+ default:
+ errno = ftsent->fts_errno;
+ if (status) {
+ rv = (*status)(COPYFILE_RECURSE_ERROR, COPYFILE_ERR, tstate, ftsent->fts_path, dstfile, s->ctx);
+ if (rv == COPYFILE_SKIP || rv == COPYFILE_CONTINUE) {
+ errno = 0;
+ goto skipit;
+ }
+ if (rv == COPYFILE_QUIT) {
+ retval = -1;
+ goto stopit;
+ }
+ } else {
+ retval = -1;
+ goto stopit;
+ }
+ case FTS_DOT:
+ goto skipit;
+
+ }
+
+ if (cmd == COPYFILE_RECURSE_DIR || cmd == COPYFILE_RECURSE_FILE) {
+ if (status) {
+ rv = (*status)(cmd, COPYFILE_START, tstate, ftsent->fts_path, dstfile, s->ctx);
+ if (rv == COPYFILE_SKIP) {
+ if (cmd == COPYFILE_RECURSE_DIR) {
+ rv = fts_set(fts, ftsent, FTS_SKIP);
+ if (rv == -1) {
+ rv = (*status)(0, COPYFILE_ERR, tstate, ftsent->fts_path, dstfile, s->ctx);
+ if (rv == COPYFILE_QUIT)
+ retval = -1;
+ }
+ }
+ goto skipit;
+ }
+ if (rv == COPYFILE_QUIT) {
+ retval = -1; errno = 0;
+ goto stopit;
+ }
+ }
+ rv = copyfile(ftsent->fts_path, dstfile, tstate, flags);
+ if (rv < 0) {
+ if (status) {
+ rv = (*status)(cmd, COPYFILE_ERR, tstate, ftsent->fts_path, dstfile, s->ctx);
+ if (rv == COPYFILE_QUIT) {
+ retval = -1;
+ goto stopit;
+ } else
+ rv = 0;
+ goto skipit;
+ } else {
+ retval = -1;
+ goto stopit;
+ }
+ }
+ if (status) {
+ rv = (*status)(cmd, COPYFILE_FINISH, tstate, ftsent->fts_path, dstfile, s->ctx);
+ if (rv == COPYFILE_QUIT) {
+ retval = -1; errno = 0;
+ goto stopit;
+ }
+ }
+ } else if (cmd == COPYFILE_RECURSE_DIR_CLEANUP) {
+ int tfd;
+
+ if (status) {
+ rv = (*status)(cmd, COPYFILE_START, tstate, ftsent->fts_path, dstfile, s->ctx);
+ if (rv == COPYFILE_QUIT) {
+ retval = -1; errno = 0;
+ goto stopit;
+ } else if (rv == COPYFILE_SKIP) {
+ rv = 0;
+ goto skipit;
+ }
+ }
+ tfd = open(dstfile, O_RDONLY);
+ if (tfd != -1) {
+ struct stat sb;
+ if (s->flags & COPYFILE_STAT) {
+ (s->flags & COPYFILE_NOFOLLOW_SRC ? lstat : stat)(ftsent->fts_path, &sb);
+ } else {
+ (s->flags & COPYFILE_NOFOLLOW_DST ? lstat : stat)(dstfile, &sb);
+ }
+ remove_uberace(tfd, &sb);
+ close(tfd);
+ if (status) {
+ rv = (*status)(COPYFILE_RECURSE_DIR_CLEANUP, COPYFILE_FINISH, tstate, ftsent->fts_path, dstfile, s->ctx);
+ if (rv == COPYFILE_QUIT) {
+ rv = -1; errno = 0;
+ goto stopit;
+ }
+ }
+ } else {
+ if (status) {
+ rv = (*status)(COPYFILE_RECURSE_DIR_CLEANUP, COPYFILE_ERR, tstate, ftsent->fts_path, dstfile, s->ctx);
+ if (rv == COPYFILE_QUIT) {
+ retval = -1;
+ goto stopit;
+ } else if (rv == COPYFILE_SKIP || rv == COPYFILE_CONTINUE) {
+ if (rv == COPYFILE_CONTINUE)
+ errno = 0;
+ retval = 0;
+ goto skipit;
+ }
+ } else {
+ retval = -1;
+ goto stopit;
+ }
+ }
+ rv = 0;
+ }
+skipit:
+stopit:
+ copyfile_state_free(tstate);
+ free(dstfile);
+ if (retval == -1)
+ break;
+ }
+
+done:
+ if (fts)
+ fts_close(fts);
+
+ return retval;
+}
+
/*
* fcopyfile() is used to copy a source file descriptor to a destination file
* descriptor. This allows an application to figure out how it wants to open
s->src_fd = src_fd;
if (fstatx_np(s->src_fd, &s->sb, s->fsec) != 0)
{
- if (errno == ENOTSUP)
+ if (errno == ENOTSUP || errno == EPERM)
fstat(s->src_fd, &s->sb);
else
{
(void)fchmod(s->dst_fd, dst_sb.st_mode & ~S_IFMT);
}
- if (state == NULL)
+ if (s->err) {
+ errno = s->err;
+ s->err = 0;
+ }
+ if (state == NULL) {
+ int t = errno;
copyfile_state_free(s);
+ errno = t;
+ }
return ret;
int copyfile(const char *src, const char *dst, copyfile_state_t state, copyfile_flags_t flags)
{
int ret = 0;
+ int createdst = 0;
copyfile_state_t s = state;
- filesec_t original_fsec = NULL;
- filesec_t permissive_fsec = NULL;
- struct stat sb;
+ struct stat dst_sb;
if (src == NULL && dst == NULL)
{
COPYFILE_SET_FNAME(src, s);
COPYFILE_SET_FNAME(dst, s);
+ if (s->flags & COPYFILE_RECURSIVE) {
+ ret = copytree(s);
+ goto exit;
+ }
+
/*
* Get a copy of the source file's security settings
*/
- if ((original_fsec = filesec_init()) == NULL)
+ if ((s->original_fsec = filesec_init()) == NULL)
goto error_exit;
- if(statx_np(s->dst, &sb, original_fsec) == 0)
+ if ((s->flags & COPYFILE_NOFOLLOW_DST) && lstat(s->dst, &dst_sb) == 0 &&
+ (dst_sb.st_mode & S_IFLNK)) {
+ if (s->permissive_fsec)
+ free(s->permissive_fsec);
+ s->permissive_fsec = NULL;
+ } else if(statx_np(s->dst, &dst_sb, s->original_fsec) == 0)
{
/*
* copyfile_fix_perms() will make a copy of the permission set,
* to the file and set attributes.
*/
- if((permissive_fsec = copyfile_fix_perms(s, &original_fsec)) != NULL)
+ if((s->permissive_fsec = copyfile_fix_perms(s, &s->original_fsec)) != NULL)
{
/*
* Set the permissions for the destination to our copy.
* We should get ENOTSUP from any filesystem that simply
* doesn't support it.
*/
- if (chmodx_np(s->dst, permissive_fsec) < 0 && errno != ENOTSUP)
+ if (chmodx_np(s->dst, s->permissive_fsec) < 0 && errno != ENOTSUP)
{
copyfile_warn("setting security information");
- filesec_free(permissive_fsec);
- permissive_fsec = NULL;
+ filesec_free(s->permissive_fsec);
+ s->permissive_fsec = NULL;
}
}
+ } else if (errno == ENOENT) {
+ createdst = 1;
}
/*
goto error_exit;
ret = copyfile_internal(s, flags);
+ if (ret == -1)
+ goto error_exit;
- /* If we haven't reset the file security information
- * (COPYFILE_SECURITY is not set in flags)
- * restore back the permissions the file had originally
- *
- * One of the reasons this seems so complicated is that
- * it is partially at odds with copyfile_security().
- *
- * Simplisticly, we are simply trying to make sure we
- * only copy what was requested, and that we don't stomp
- * on what wasn't requsted.
- */
-
- if (permissive_fsec && (s->flags & COPYFILE_SECURITY) != COPYFILE_SECURITY) {
- if (s->flags & COPYFILE_ACL) {
- /* Just need to reset the BSD information -- mode, owner, group */
- (void)fchown(s->dst_fd, sb.st_uid, sb.st_gid);
- (void)fchmod(s->dst_fd, sb.st_mode);
- } else {
- /*
- * flags is either COPYFILE_STAT, or neither; if it's
- * neither, then we restore both ACL and POSIX permissions;
- * if it's STAT, however, then we only want to restore the
- * ACL (which may be empty). We do that by removing the
- * POSIX information from the filesec object.
- */
- if (s->flags & COPYFILE_STAT) {
- copyfile_unset_posix_fsec(original_fsec);
- }
- if (fchmodx_np(s->dst_fd, original_fsec) < 0 && errno != ENOTSUP)
- copyfile_warn("restoring security information");
+#ifdef COPYFILE_RECURSIVE
+ if (!(flags & COPYFILE_STAT)) {
+ if (!createdst)
+ {
+ /* Just need to reset the BSD information -- mode, owner, group */
+ (void)fchown(s->dst_fd, dst_sb.st_uid, dst_sb.st_gid);
+ (void)fchmod(s->dst_fd, dst_sb.st_mode);
}
}
+#endif
+
+ reset_security(s);
+
exit:
- if (state == NULL)
+ if (state == NULL) {
+ int t = errno;
copyfile_state_free(s);
-
- if (original_fsec)
- filesec_free(original_fsec);
- if (permissive_fsec)
- filesec_free(permissive_fsec);
+ errno = t;
+ }
return ret;
error_exit:
ret = -1;
+ if (s->err) {
+ errno = s->err;
+ s->err = 0;
+ }
goto exit;
}
if (s->dst_fd < 0 || s->src_fd < 0)
{
- copyfile_debug(1, "file descriptors not open (src: %d, dst: %d)",
- s->src_fd, s->dst_fd);
- errno = EINVAL;
+ copyfile_debug(1, "file descriptors not open (src: %d, dst: %d)", s->src_fd, s->dst_fd);
+ s->err = EINVAL;
return -1;
}
{
if ((ret = copyfile_pack(s)) < 0)
{
- unlink(s->dst);
+ if (s->dst) unlink(s->dst);
goto exit;
}
goto exit;
{
if ((ret = copyfile_xattr(s)) < 0)
{
- if (errno != ENOTSUP)
+ if (errno != ENOTSUP && errno != EPERM)
copyfile_warn("error processing extended attributes");
goto exit;
}
{
copyfile_warn("error processing data");
if (s->dst && unlink(s->dst))
- copyfile_warn("%s: remove", s->src);
+ copyfile_warn("%s: remove", s->src ? s->src : "(null src)");
goto exit;
}
}
if (s->fsec)
filesec_free(s->fsec);
+ if (s->original_fsec)
+ filesec_free(s->original_fsec);
+
+ if (s->permissive_fsec)
+ filesec_free(s->permissive_fsec);
+
if (s->qinfo)
qtn_file_free(s->qinfo);
if (filesec_get_property(ret_fsec, FILESEC_ACL, &acl) == 0)
{
+#ifdef COPYFILE_RECURSIVE
+ if (add_uberace(&acl))
+ goto error_exit;
+#else
acl_entry_t entry;
acl_permset_t permset;
uuid_t qual;
goto error_exit;
if(acl_set_qualifier(entry, qual) == -1)
goto error_exit;
+#endif
if (filesec_set_property(ret_fsec, FILESEC_ACL, &acl) != 0)
goto error_exit;
int ret = 0;
if (filesec_set_property(s->fsec, FILESEC_ACL, NULL) == -1)
{
- copyfile_debug(5, "unsetting acl attribute on %s", s->dst);
+ copyfile_debug(5, "unsetting acl attribute on %s", s->dst ? s->dst : "(null dst)");
++ret;
}
if (filesec_set_property(s->fsec, FILESEC_UUID, NULL) == -1)
{
- copyfile_debug(5, "unsetting uuid attribute on %s", s->dst);
+ copyfile_debug(5, "unsetting uuid attribute on %s", s->dst ? s->dst : "(null dst)");
++ret;
}
if (filesec_set_property(s->fsec, FILESEC_GRPUUID, NULL) == -1)
{
- copyfile_debug(5, "unsetting group uuid attribute on %s", s->dst);
+ copyfile_debug(5, "unsetting group uuid attribute on %s", s->dst ? s->dst : "(null dst)");
++ret;
}
return ret;
{
case S_IFLNK:
islnk = 1;
- if (s->sb.st_size > (off_t)SIZE_T_MAX) {
- errno = ENOMEM; /* too big for us to copy */
+ if ((size_t)s->sb.st_size > SIZE_T_MAX) {
+ s->err = ENOMEM; /* too big for us to copy */
return -1;
}
osrc = O_SYMLINK;
case S_IFREG:
break;
default:
- errno = ENOTSUP;
- return -1;
+ if (!(strcmp(s->src, "/dev/null") == 0 && (s->flags & COPYFILE_METADATA))) {
+ s->err = ENOTSUP;
+ return -1;
+ }
}
/*
* If we're packing, then we are actually
}
}
- if (s->flags & COPYFILE_NOFOLLOW_DST)
+ if (s->flags & COPYFILE_NOFOLLOW_DST) {
+ struct stat st;
+
dsrc = O_NOFOLLOW;
+ if (lstat(s->dst, &st) != -1) {
+ if ((st.st_mode & S_IFMT) == S_IFLNK)
+ dsrc = O_SYMLINK;
+ }
+ }
if (islnk) {
size_t sz = (size_t)s->sb.st_size + 1;
if(chmod(s->dst, (s->sb.st_mode | S_IWUSR) & ~S_IFMT) == 0)
continue;
else {
+ /*
+ * If we're trying to write to a directory to which we don't
+ * have access, the create above would have failed, but chmod
+ * here would have given us ENOENT. But the real error is
+ * still one of access, so we change the errno we're reporting.
+ * This could cause confusion with a race condition.
+ */
+
+ if (errno == ENOENT)
+ errno = EACCES;
break;
}
case EISDIR:
{
copyfile_debug(1, "file descriptors not open (src: %d, dst: %d)",
s->src_fd, s->dst_fd);
- errno = EINVAL;
+ s->err = EINVAL;
return -1;
}
return 0;
if (!s->src)
{
- errno = EINVAL;
+ s->err = EINVAL;
return -1;
}
ssize_t nread;
int ret = 0;
size_t iBlocksize = 0;
+ size_t oBlocksize = 0;
+ const size_t onegig = 1 << 30;
struct statfs sfs;
+ copyfile_callback_t status = s->statuscb;
+
+ /* Unless it's a normal file, we don't copy. For now, anyway */
+ if ((s->sb.st_mode & S_IFMT) != S_IFREG)
+ return 0;
if (fstatfs(s->src_fd, &sfs) == -1) {
iBlocksize = s->sb.st_blksize;
iBlocksize = sfs.f_iosize;
}
+ /* Work-around for 6453525, limit blocksize to 1G */
+ if (iBlocksize > onegig) {
+ iBlocksize = onegig;
+ }
+
if ((bp = malloc(iBlocksize)) == NULL)
return -1;
+ if (fstatfs(s->dst_fd, &sfs) == -1 || sfs.f_iosize == 0) {
+ oBlocksize = iBlocksize;
+ } else {
+ oBlocksize = sfs.f_iosize;
+ if (oBlocksize > onegig)
+ oBlocksize = onegig;
+ }
+
blen = iBlocksize;
+ s->totalCopied = 0;
/* If supported, do preallocation for Xsan / HFS volumes */
#ifdef F_PREALLOCATE
{
while ((nread = read(s->src_fd, bp, blen)) > 0)
{
- size_t nwritten;
+ ssize_t nwritten;
size_t left = nread;
void *ptr = bp;
+ int loop = 0;
while (left > 0) {
- int loop = 0;
- nwritten = write(s->dst_fd, ptr, left);
+ nwritten = write(s->dst_fd, ptr, MIN(left, oBlocksize));
switch (nwritten) {
case 0:
if (++loop > 5) {
copyfile_warn("writing to output %d times resulted in 0 bytes written", loop);
ret = -1;
- errno = EAGAIN;
+ s->err = EAGAIN;
goto exit;
}
break;
case -1:
copyfile_warn("writing to output file got error");
+ if (status) {
+ int rv = (*status)(COPYFILE_COPY_DATA, COPYFILE_ERR, s, s->src, s->dst, s->ctx);
+ if (rv == COPYFILE_SKIP) { // Skip the data copy
+ ret = 0;
+ goto exit;
+ }
+ if (rv == COPYFILE_CONTINUE) { // Retry the write
+ errno = 0;
+ continue;
+ }
+ }
ret = -1;
goto exit;
default:
left -= nwritten;
ptr = ((char*)ptr) + nwritten;
+ loop = 0;
break;
}
+ s->totalCopied += nwritten;
+ if (status) {
+ int rv = (*status)(COPYFILE_COPY_DATA, COPYFILE_PROGRESS, s, s->src, s->dst, s->ctx);
+ if (rv == COPYFILE_QUIT) {
+ ret = -1; s->err = ECANCELED;
+ goto exit;
+ }
+ }
}
}
if (nread < 0)
{
- copyfile_warn("reading from %s", s->src);
+ copyfile_warn("reading from %s", s->src ? s->src : "(null src)");
+ ret = -1;
goto exit;
}
}
exit:
+ if (ret == -1)
+ {
+ s->err = errno;
+ }
free(bp);
return ret;
}
static int copyfile_security(copyfile_state_t s)
{
int copied = 0;
+ int has_uberace = 0;
acl_flagset_t flags;
struct stat sb;
acl_entry_t entry_src = NULL, entry_dst = NULL;
if (filesec_get_property(s->fsec, FILESEC_ACL, &acl_src))
{
if (errno == ENOENT)
- goto no_acl;
+ acl_src = NULL;
else
goto error_exit;
}
if (filesec_get_property(fsec_dst, FILESEC_ACL, &acl_dst))
{
if (errno == ENOENT)
- acl_dst = acl_init(4);
+ acl_dst = NULL;
else
goto error_exit;
}
-
- for (;acl_get_entry(acl_src,
- entry_src == NULL ? ACL_FIRST_ENTRY : ACL_NEXT_ENTRY,
- &entry_src) == 0;)
+ else
{
- acl_get_flagset_np(entry_src, &flags);
- if (!acl_get_flag_np(flags, ACL_ENTRY_INHERITED))
- {
- if ((ret = acl_create_entry(&acl_dst, &entry_dst)) == -1)
- goto error_exit;
+ acl_t tmp = acl_init(4);
+ acl_entry_t ace = NULL;
+ int count = 0;
- if ((ret = acl_copy_entry(entry_dst, entry_src)) == -1)
- goto error_exit;
+ if (tmp == NULL)
+ goto error_exit;
- copyfile_debug(2, "copied acl entry from %s to %s", s->src, s->dst);
- copied++;
- }
+
+ for (; acl_get_entry(acl_dst,
+ ace == NULL ? ACL_FIRST_ENTRY : ACL_NEXT_ENTRY,
+ &ace) == 0;)
+ {
+ if (count++ == 0 && is_uberace(ace)) {
+ if ((ret = acl_create_entry(&tmp, &entry_dst)) == -1)
+ break;
+ if ((ret = acl_copy_entry(entry_dst, ace)) == -1)
+ break;
+ has_uberace = 1;
+ continue;
+ }
+ acl_get_flagset_np(ace, &flags);
+ if (acl_get_flag_np(flags, ACL_ENTRY_INHERITED))
+ {
+ if ((ret = acl_create_entry(&tmp, &entry_dst)) == -1)
+ break;
+ if ((ret = acl_copy_entry(entry_dst, ace)) == -1)
+ break;
+ }
+ }
+ acl_free(acl_dst);
+ acl_dst = tmp;
+
+ if (ret == -1)
+ goto error_exit;
+ }
+
+ if (acl_src == NULL && acl_dst == NULL)
+ goto no_acl;
+
+ if (acl_src) {
+ if (acl_dst == NULL)
+ acl_dst = acl_init(4);
+ for (copied = 0;acl_get_entry(acl_src,
+ entry_src == NULL ? ACL_FIRST_ENTRY : ACL_NEXT_ENTRY,
+ &entry_src) == 0;)
+ {
+ acl_get_flagset_np(entry_src, &flags);
+ if (!acl_get_flag_np(flags, ACL_ENTRY_INHERITED))
+ {
+ if ((ret = acl_create_entry(&acl_dst, &entry_dst)) == -1)
+ goto error_exit;
+
+ if ((ret = acl_copy_entry(entry_dst, entry_src)) == -1)
+ goto error_exit;
+
+ copyfile_debug(2, "copied acl entry from %s to %s",
+ s->src ? s->src : "(null src)",
+ s->dst ? s->dst : "(null dst)");
+ copied++;
+ }
+ }
+ }
+ if (!has_uberace && (s->internal_flags & cfDelayAce)) {
+ if (add_uberace(&acl_dst))
+ goto error_exit;
}
if (!filesec_set_property(s->fsec, FILESEC_ACL, &acl_dst))
{
/* FALLTHROUGH */
case COPYFILE_ACL | COPYFILE_STAT:
if (fchmodx_np(s->dst_fd, tmp_fsec) < 0) {
- if (errno == ENOTSUP) {
- if (COPYFILE_STAT & s->flags)
- fchmod(s->dst_fd, s->sb.st_mode);
- } else
- copyfile_warn("setting security information: %s", s->dst);
+ acl_t acl = NULL;
+ /*
+ * The call could have failed for a number of reasons, since
+ * it does a number of things: it changes the mode of the file,
+ * sets the owner and group, and applies an ACL (if one exists).
+ * The typical failure is going to be trying to set the group of
+ * the destination file to match the source file, when the process
+ * doesn't have permission to put files in that group. We try to
+ * work around this by breaking the steps out and doing them
+ * discretely. We don't care if the fchown fails, but we do care
+ * if the mode or ACL can't be set. For historical reasons, we
+ * simply log those failures, however.
+ */
+
+#define NS(x) ((x) ? (x) : "(null string)")
+ if (fchmod(s->dst_fd, s->sb.st_mode) == -1) {
+ copyfile_warn("could not change mode of destination file %s to match source file %s", NS(s->dst), NS(s->src));
+ }
+ (void)fchown(s->dst_fd, s->sb.st_uid, s->sb.st_gid);
+ if (filesec_get_property(tmp_fsec, FILESEC_ACL, &acl) == 0) {
+ if (acl_set_fd(s->dst_fd, acl) == -1) {
+ copyfile_warn("could not apply acl to destination file %s from source file %s", NS(s->dst), NS(s->src));
+ }
+ acl_free(acl);
+ }
}
+#undef NS
break;
case COPYFILE_STAT:
- fchmod(s->dst_fd, s->sb.st_mode);
+ (void)fchmod(s->dst_fd, s->sb.st_mode);
break;
}
filesec_free(tmp_fsec);
*/
if (fchflags(s->dst_fd, (u_int)s->sb.st_flags))
if (errno != EOPNOTSUPP || s->sb.st_flags != 0)
- copyfile_warn("%s: set flags (was: 0%07o)", s->dst, s->sb.st_flags);
+ copyfile_warn("%s: set flags (was: 0%07o)", s->dst ? s->dst : "(null dst)", s->sb.st_flags);
/* If this fails, we don't care */
(void)fchown(s->dst_fd, s->sb.st_uid, s->sb.st_gid);
tval[1].tv_sec = s->sb.st_mtime;
tval[0].tv_usec = tval[1].tv_usec = 0;
if (futimes(s->dst_fd, tval))
- copyfile_warn("%s: set times", s->dst);
+ copyfile_warn("%s: set times", s->dst ? s->dst : "(null dst)");
return 0;
}
} else
if (nsize < 0)
{
- if (errno == ENOTSUP)
+ if (errno == ENOTSUP || errno == EPERM)
return 0;
else
return -1;
/* get name list of EAs on source */
if ((nsize = flistxattr(s->src_fd, 0, 0, 0)) < 0)
{
- if (errno == ENOTSUP)
+ if (errno == ENOTSUP || errno == EPERM)
return 0;
else
return -1;
case COPYFILE_STATE_PROGRESS_CB:
ret = s->callbacks.progress;
break;
+#endif
+#ifdef COPYFILE_STATE_STATUS_CB
+ case COPYFILE_STATE_STATUS_CB:
+ *(copyfile_callback_t*)ret = s->statuscb;
+ break;
+ case COPYFILE_STATE_STATUS_CTX:
+ *(void**)ret = s->ctx;
+ break;
+ case COPYFILE_STATE_COPIED:
+ *(off_t*)ret = s->totalCopied;
+ break;
#endif
default:
errno = EINVAL;
case COPYFILE_STATE_PROGRESS_CB:
s->callbacks.progress = thing;
break;
+#endif
+#ifdef COPYFILE_STATE_STATUS_CB
+ case COPYFILE_STATE_STATUS_CB:
+ s->statuscb = (copyfile_callback_t)thing;
+ break;
+ case COPYFILE_STATE_STATUS_CTX:
+ s->ctx = (void*)thing;
+ break;
#endif
default:
errno = EINVAL;
if ((namebuf = (char*) malloc(bytes)) == NULL)
{
- errno = ENOMEM;
+ s->err = ENOMEM;
goto exit;
}
bytes = flistxattr(s->dst_fd, namebuf, bytes, 0);
}
else if (bytes < 0)
{
- if (errno != ENOTSUP)
+ if (errno != ENOTSUP && errno != EPERM)
goto exit;
}
}
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn("Incomplete or corrupt attribute entry");
error = -1;
+ s->err = EINVAL;
goto exit;
}
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn("Incomplete or corrupt attribute entry");
error = -1;
+ s->err = EINVAL;
goto exit;
}
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn("Corrupt attribute entry (only %d bytes)", entry->namelen);
error = -1;
+ s->err = EINVAL;
goto exit;
}
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn("Corrupt attribute entry (name length is %d bytes)", entry->namelen);
error = -1;
+ s->err = EINVAL;
goto exit;
}
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn("Incomplete or corrupt attribute entry");
error = -1;
+ s->err = EINVAL;
goto exit;
}
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn("Corrupt attribute entry (name is not NUL-terminated)");
error = -1;
+ s->err = EINVAL;
goto exit;
}
if (dataptr > endptr || dataptr < buffer) {
copyfile_debug(1, "Entry %d overflows: offset = %u", entry->offset);
error = -1;
+ s->err = EINVAL; /* Invalid buffer */
goto exit;
}
if (((char*)dataptr + entry->length) > (char*)endptr ||
copyfile_debug(1, "Entry %d length overflows: offset = %u, length = %u",
entry->offset, entry->length);
error = -1;
+ s->err = EINVAL; /* Invalid buffer */
goto exit;
}
}
}
/* And, finally, everything else */
- else if (COPYFILE_XATTR & s->flags && (fsetxattr(s->dst_fd, (char *)entry->name, dataptr, entry->length, 0, 0))) {
- if (COPYFILE_VERBOSE & s->flags)
- copyfile_warn("error %d setting attribute %s", error, entry->name);
- break;
+ else if (COPYFILE_XATTR & s->flags) {
+ if (fsetxattr(s->dst_fd, (char *)entry->name, dataptr, entry->length, 0, 0) == -1) {
+ if (COPYFILE_VERBOSE & s->flags)
+ copyfile_warn("error %d setting attribute %s", error, entry->name);
+ error = -1;
+ goto exit;
+ }
}
entry = ATTR_NEXT(entry);
}
tval[0].tv_usec = tval[1].tv_usec = 0;
if (futimes(s->dst_fd, tval))
- copyfile_warn("%s: set times", s->dst);
+ copyfile_warn("%s: set times", s->dst ? s->dst : "(null dst)");
}
bad:
if (rsrcforkdata)
}
if (datasize > INT_MAX) {
- errno = EINVAL;
+ s->err = EINVAL;
ret = -1;
goto done;
}
* Fill in the initial Attribute Header.
*/
filehdr->magic = ATTR_HDR_MAGIC;
- filehdr->debug_tag = s->sb.st_ino;
+ filehdr->debug_tag = (u_int32_t)s->sb.st_ino;
filehdr->data_start = (u_int32_t)sizeof(attr_header_t);
/*