]> git.saurik.com Git - apple/copyfile.git/commitdiff
copyfile-66.tar.gz mac-os-x-106 mac-os-x-1061 mac-os-x-1062 mac-os-x-1063 mac-os-x-1064 mac-os-x-1065 mac-os-x-1066 v66
authorApple <opensource@apple.com>
Thu, 25 Jun 2009 22:19:14 +0000 (22:19 +0000)
committerApple <opensource@apple.com>
Thu, 25 Jun 2009 22:19:14 +0000 (22:19 +0000)
Makefile
copyfile.3
copyfile.c
copyfile.h

index 3d19028a31d7e4c32447316e2fc73d08c6a77e1c..1d43cc1513a886aede1459d829cbb6f1d26705ce 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,23 +1,15 @@
-##
-# Makefile for copyfile
-##
-# Project info
-
-Project                        = copyfile
-#Extra_CC_Flags                =
-
-#lazy_install_source:: shadow_source
-
-include $(MAKEFILEPATH)/CoreOS/ReleaseControl/Common.make
-
-SRCROOT ?= .
-OBJROOT ?= .
-SYMROOT ?= .
-DSTROOT ?= .
-
-LIB_DIR = $(DSTROOT)/usr/local/lib/system
+Project = copyfile
+Install_Dir = /usr/local/lib/system
+ProductType = staticlib
+BuildProfile = YES
+BuildDebug = YES
+
+CFILES = copyfile.c $(OBJROOT)/$(Project)/_version.c
+MANPAGES = copyfile.3
 MAN_DIR = $(DSTROOT)/usr/share/man/man3
-INC_DIR = $(DSTROOT)/usr/include
+
+Install_Headers_Directory = /usr/include
+Install_Headers = copyfile.h
 
 WFLAGS= -Wno-trigraphs -Wmissing-prototypes -Wreturn-type -Wformat \
        -Wmissing-braces -Wparentheses -Wswitch -Wunused-function \
@@ -28,52 +20,18 @@ WFLAGS= -Wno-trigraphs -Wmissing-prototypes -Wreturn-type -Wformat \
        -Wno-parentheses -Wformat=2 -Wimplicit-function-declaration \
        -Wshorten-64-to-32 -Wformat-security
 
-CFLAGS +=      -D__DARWIN_NON_CANCELABLE=1 $(WFLAGS)
-SRC = copyfile.c
-VERSOBJ=       $(OBJROOT)/__version.o
-OBJ = $(SRC:.c=.o)
-HDRS= copyfile.h
-LIBS = $(SYMROOT)/libcopyfile.a $(SYMROOT)/libcopyfile_profile.a $(SYMROOT)/libcopyfile_debug.a
+SDKROOT ?= /
 
-installhdrs:: $(HDRS)
-       install -d -m 755 $(INC_DIR)
-       install -c -m 444 $(SRCROOT)/copyfile.h $(INC_DIR)
+Extra_CC_Flags = ${WFLAGS} -fno-common \
+       -D__DARWIN_NOW_CANCELABLE=1 -I.
 
-install:: $(LIBS)
-       install -d -m 755 $(MAN_DIR)
-       install -c -m 644 $(SRCROOT)/copyfile.3 $(MAN_DIR)
+include $(MAKEFILEPATH)/CoreOS/ReleaseControl/BSDCommon.make
+  
+$(OBJROOT)/$(Project)/_version.c:
+       /Developer/Makefiles/bin/version.pl copyfile > $@
+after_install:
        for a in fcopyfile copyfile_state_alloc copyfile_state_free \
                copyfile_state_get copyfile_state_set ; do \
                        ln $(MAN_DIR)/copyfile.3 $(MAN_DIR)/$$a.3 ; \
                done
-       install -d -m 755 $(LIB_DIR)
-       install -c -m 644 $(LIBS) $(LIB_DIR)
-       install -d -m 755 $(INC_DIR)
-       install -c -m 444 $(SRCROOT)/copyfile.h $(INC_DIR)
-
-$(OBJROOT)/__version.c:
-       /Developer/Makefiles/bin/version.pl $(Project) > $@
-
-$(VERSOBJ):    $(OBJROOT)/__version.c
-       $(CC) -c -Os $(CFLAGS) $(RC_CFLAGS) -o $@ $^
-
-$(OBJROOT)/%.o: $(SRCROOT)/%.c
-       $(CC) -c -Os $(CFLAGS) $(RC_CFLAGS) -o $@ $^
-
-$(OBJROOT)/%-profile.o: $(SRCROOT)/%.c
-       $(CC) -c -pg $(CFLAGS) $(RC_CFLAGS) -o $@ $^
-
-$(OBJROOT)/%-debug.o: $(SRCROOT)/%.c
-       $(CC) -c -g $(CFLAGS) $(RC_CFLAGS) -o $@ $^
-
-$(SYMROOT)/libcopyfile.a:: $(OBJROOT)/$(OBJ) $(VERSOBJ)
-       libtool -static -o $@ $^
-
-$(SYMROOT)/libcopyfile_profile.a:: $(OBJROOT)/copyfile-profile.o $(VERSOBJ)
-       libtool -static -o $@ $^
-
-$(SYMROOT)/libcopyfile.a:: $(OBJROOT)/$(OBJ) $(VERSOBJ)
-       libtool -static -o $@ $^
-
-$(SYMROOT)/libcopyfile_debug.a:: $(OBJROOT)/copyfile-debug.o $(VERSOBJ)
-       libtool -static -o $@ $^
index 2e78bf3ea5ec1b15e7ba29aa1202cff150a922ac..c2a30dd5f8902fb906a0d6b1defbea86f3330200 100644 (file)
@@ -25,6 +25,8 @@
 .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
@@ -101,6 +103,13 @@ and
 .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
@@ -220,21 +229,217 @@ parameter is a pointer to a pointer to a C string
 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
@@ -245,6 +450,18 @@ and
 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
@@ -252,20 +469,32 @@ 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
@@ -289,11 +518,20 @@ copyfile(NULL, "/tmp/bar", s, COPYFILE_ALL);
 /* 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
index c64d291e5ba029f4ec0db15b53a63554384608f0..aa68b5c27f7d1e755526e5501d453fc3878aea1c 100644 (file)
@@ -42,6 +42,8 @@
 #include <sys/acl.h>
 #include <libkern/OSByteOrder.h>
 #include <membership.h>
+#include <fts.h>
+#include <libgen.h>
 
 #include <TargetConditionals.h>
 #if !TARGET_OS_EMBEDDED
@@ -63,6 +65,10 @@ static void *qtn_file_clone(void *x) { return NULL; }
 
 #include "copyfile.h"
 
+enum cfInternalFlags {
+       cfDelayAce = 1,
+};
+
 /*
  * The state structure keeps track of
  * the source filename, the destination filename, their
@@ -81,12 +87,48 @@ struct _copyfile_state
     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.
@@ -145,7 +187,6 @@ static int copyfile_quarantine(copyfile_state_t s)
        {
            qtn_file_free(s->qinfo);
            s->qinfo = NULL;
-           errno = error;
            rv = -1;
            goto done;
        }
@@ -154,6 +195,523 @@ 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
@@ -181,7 +739,7 @@ int fcopyfile(int src_fd, int dst_fd, copyfile_state_t state, copyfile_flags_t f
        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
            {
@@ -219,8 +777,15 @@ int fcopyfile(int src_fd, int dst_fd, copyfile_state_t state, copyfile_flags_t f
        (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;
 
@@ -235,10 +800,9 @@ int fcopyfile(int src_fd, int dst_fd, copyfile_state_t state, copyfile_flags_t f
 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)
     {
@@ -284,13 +848,23 @@ int copyfile(const char *src, const char *dst, copyfile_state_t state, copyfile_
     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,
@@ -298,20 +872,22 @@ int copyfile(const char *src, const char *dst, copyfile_state_t state, copyfile_
            * 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;
     }
 
     /*
@@ -327,52 +903,37 @@ int copyfile(const char *src, const char *dst, copyfile_state_t state, copyfile_
        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;
 }
 
@@ -433,9 +994,8 @@ static int copyfile_internal(copyfile_state_t s, copyfile_flags_t flags)
 
     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;
     }
 
@@ -448,7 +1008,7 @@ static int copyfile_internal(copyfile_state_t s, copyfile_flags_t flags)
     {
        if ((ret = copyfile_pack(s)) < 0)
        {
-           unlink(s->dst);
+           if (s->dst) unlink(s->dst);
            goto exit;
        }
        goto exit;
@@ -487,7 +1047,7 @@ static int copyfile_internal(copyfile_state_t s, copyfile_flags_t flags)
     {
        if ((ret = copyfile_xattr(s)) < 0)
        {
-           if (errno != ENOTSUP)
+           if (errno != ENOTSUP && errno != EPERM)
                copyfile_warn("error processing extended attributes");
            goto exit;
        }
@@ -504,7 +1064,7 @@ static int copyfile_internal(copyfile_state_t s, copyfile_flags_t flags)
        {
            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;
        }
     }
@@ -568,6 +1128,12 @@ int copyfile_state_free(copyfile_state_t s)
        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);
 
@@ -621,6 +1187,10 @@ static filesec_t copyfile_fix_perms(copyfile_state_t s __unused, filesec_t *fsec
 
     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;
@@ -655,6 +1225,7 @@ static filesec_t copyfile_fix_perms(copyfile_state_t s __unused, filesec_t *fsec
            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;
@@ -711,17 +1282,17 @@ static int copyfile_unset_acl(copyfile_state_t s)
     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;
@@ -753,8 +1324,8 @@ static int copyfile_open(copyfile_state_t s)
        {
            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;
@@ -765,8 +1336,10 @@ static int copyfile_open(copyfile_state_t s)
            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
@@ -814,8 +1387,15 @@ static int copyfile_open(copyfile_state_t s)
            }
        }
 
-       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;
@@ -884,6 +1464,16 @@ static int copyfile_open(copyfile_state_t s)
                    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:
@@ -904,7 +1494,7 @@ static int copyfile_open(copyfile_state_t s)
     {
        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;
@@ -928,7 +1518,7 @@ static copyfile_flags_t copyfile_check(copyfile_state_t s)
 
     if (!s->src)
     {
-       errno = EINVAL;
+       s->err = EINVAL;
        return -1;
     }
 
@@ -1012,7 +1602,14 @@ static int copyfile_data(copyfile_state_t s)
     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;
@@ -1020,11 +1617,25 @@ static int copyfile_data(copyfile_state_t s)
        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
     {
@@ -1041,36 +1652,57 @@ static int copyfile_data(copyfile_state_t s)
 
     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;
     }
 
@@ -1081,6 +1713,10 @@ static int copyfile_data(copyfile_state_t s)
     }
 
 exit:
+    if (ret == -1)
+    {
+       s->err = errno;
+    }
     free(bp);
     return ret;
 }
@@ -1094,6 +1730,7 @@ exit:
 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;
@@ -1111,7 +1748,7 @@ static int copyfile_security(copyfile_state_t s)
        if (filesec_get_property(s->fsec, FILESEC_ACL, &acl_src))
        {
            if (errno == ENOENT)
-               goto no_acl;
+               acl_src = NULL;
            else
                goto error_exit;
        }
@@ -1125,27 +1762,77 @@ static int copyfile_security(copyfile_state_t s)
        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))
        {
@@ -1176,15 +1863,36 @@ no_acl:
            /* 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);
@@ -1216,7 +1924,7 @@ static int copyfile_stat(copyfile_state_t s)
      */
     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);
@@ -1228,7 +1936,7 @@ static int copyfile_stat(copyfile_state_t s)
     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;
 }
 
@@ -1283,7 +1991,7 @@ static int copyfile_xattr(copyfile_state_t s)
     } else
     if (nsize < 0)
     {
-       if (errno == ENOTSUP)
+       if (errno == ENOTSUP || errno == EPERM)
            return 0;
        else
            return -1;
@@ -1292,7 +2000,7 @@ static int copyfile_xattr(copyfile_state_t s)
     /* 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;
@@ -1406,6 +2114,17 @@ int copyfile_state_get(copyfile_state_t s, uint32_t flag, void *ret)
        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;
@@ -1469,6 +2188,14 @@ int copyfile_state_set(copyfile_state_t s, uint32_t flag, const void * thing)
        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;
@@ -1888,7 +2615,7 @@ static int copyfile_unpack(copyfile_state_t s)
 
            if ((namebuf = (char*) malloc(bytes)) == NULL)
            {
-               errno = ENOMEM;
+               s->err = ENOMEM;
                goto exit;
            }
            bytes = flistxattr(s->dst_fd, namebuf, bytes, 0);
@@ -1901,7 +2628,7 @@ static int copyfile_unpack(copyfile_state_t s)
        }
        else if (bytes < 0)
        {
-           if (errno != ENOTSUP)
+           if (errno != ENOTSUP && errno != EPERM)
            goto exit;
        }
     }
@@ -1977,6 +2704,7 @@ static int copyfile_unpack(copyfile_state_t s)
                if (COPYFILE_VERBOSE & s->flags)
                    copyfile_warn("Incomplete or corrupt attribute entry");
                error = -1;
+               s->err = EINVAL;
                goto exit;
            }
 
@@ -1984,6 +2712,7 @@ static int copyfile_unpack(copyfile_state_t s)
                if (COPYFILE_VERBOSE & s->flags)
                    copyfile_warn("Incomplete or corrupt attribute entry");
                error = -1;
+               s->err = EINVAL;
                goto exit;
            }
 
@@ -1991,6 +2720,7 @@ static int copyfile_unpack(copyfile_state_t s)
                if (COPYFILE_VERBOSE & s->flags)
                    copyfile_warn("Corrupt attribute entry (only %d bytes)", entry->namelen);
                    error = -1;
+                   s->err = EINVAL;
                    goto exit;
            }
 
@@ -1998,6 +2728,7 @@ static int copyfile_unpack(copyfile_state_t s)
                if (COPYFILE_VERBOSE & s->flags)
                    copyfile_warn("Corrupt attribute entry (name length is %d bytes)", entry->namelen);
                error = -1;
+               s->err = EINVAL;
                goto exit;
            }
 
@@ -2005,6 +2736,7 @@ static int copyfile_unpack(copyfile_state_t s)
                if (COPYFILE_VERBOSE & s->flags)
                    copyfile_warn("Incomplete or corrupt attribute entry");
                error = -1;
+               s->err = EINVAL;
                goto exit;
            }
 
@@ -2013,6 +2745,7 @@ static int copyfile_unpack(copyfile_state_t s)
                if (COPYFILE_VERBOSE & s->flags)
                    copyfile_warn("Corrupt attribute entry (name is not NUL-terminated)");
                error = -1;
+               s->err = EINVAL;
                goto exit;
            }
 
@@ -2024,6 +2757,7 @@ static int copyfile_unpack(copyfile_state_t s)
            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 ||
@@ -2034,6 +2768,7 @@ static int copyfile_unpack(copyfile_state_t s)
                copyfile_debug(1, "Entry %d length overflows:  offset = %u, length = %u",
                        entry->offset, entry->length);
                error = -1;
+               s->err = EINVAL;        /* Invalid buffer */
                goto exit;
            }
 
@@ -2131,10 +2866,13 @@ static int copyfile_unpack(copyfile_state_t s)
                }
            }
            /* 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);
        }
@@ -2234,7 +2972,7 @@ static int copyfile_unpack(copyfile_state_t s)
            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)
@@ -2334,7 +3072,7 @@ static int copyfile_pack_rsrcfork(copyfile_state_t s, attr_header_t *filehdr)
     }
 
     if (datasize > INT_MAX) {
-       errno = EINVAL;
+       s->err = EINVAL;
        ret = -1;
        goto done;
     }
@@ -2424,7 +3162,7 @@ static int copyfile_pack(copyfile_state_t s)
      * 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);
 
     /*
index 8a0c6adb7e678fa41371edc77f2ed1585ee0686a..0cb493b87f7ff3fc64491063ebb396185019054c 100644 (file)
@@ -65,11 +65,16 @@ copyfile_state_t copyfile_state_alloc(void);
 int copyfile_state_get(copyfile_state_t s, uint32_t flag, void * dst);
 int copyfile_state_set(copyfile_state_t s, uint32_t flag, const void * src);
 
+typedef int (*copyfile_callback_t)(int, int, copyfile_state_t, const char *, const char *, void *);
+
 #define COPYFILE_STATE_SRC_FD          1
 #define COPYFILE_STATE_SRC_FILENAME    2
 #define COPYFILE_STATE_DST_FD          3
 #define COPYFILE_STATE_DST_FILENAME    4
 #define COPYFILE_STATE_QUARANTINE      5
+#define        COPYFILE_STATE_STATUS_CB        6
+#define        COPYFILE_STATE_STATUS_CTX       7
+#define        COPYFILE_STATE_COPIED           8
 
 #define        COPYFILE_DISABLE_VAR    "COPYFILE_DISABLE"
 
@@ -84,6 +89,7 @@ int copyfile_state_set(copyfile_state_t s, uint32_t flag, const void * src);
 #define COPYFILE_METADATA   (COPYFILE_SECURITY | COPYFILE_XATTR)
 #define COPYFILE_ALL       (COPYFILE_METADATA | COPYFILE_DATA)
 
+#define        COPYFILE_RECURSIVE      (1<<15) /* Descend into hierarchies */
 #define COPYFILE_CHECK         (1<<16) /* return flags for xattr or acls if set */
 #define COPYFILE_EXCL          (1<<17) /* fail if destination exists */
 #define COPYFILE_NOFOLLOW_SRC  (1<<18) /* don't follow if source is a symlink */
@@ -97,6 +103,21 @@ int copyfile_state_set(copyfile_state_t s, uint32_t flag, const void * src);
 
 #define COPYFILE_VERBOSE       (1<<30)
 
+#define        COPYFILE_RECURSE_ERROR  0
+#define        COPYFILE_RECURSE_FILE   1
+#define        COPYFILE_RECURSE_DIR    2
+#define        COPYFILE_RECURSE_DIR_CLEANUP    3
+#define        COPYFILE_COPY_DATA      4
+
+#define        COPYFILE_START          1
+#define        COPYFILE_FINISH         2
+#define        COPYFILE_ERR            3
+#define        COPYFILE_PROGRESS       4
+
+#define        COPYFILE_CONTINUE       0
+#define        COPYFILE_SKIP   1
+#define        COPYFILE_QUIT   2
+
 __END_DECLS
 
 #endif /* _COPYFILE_H_ */