]> git.saurik.com Git - apple/copyfile.git/commitdiff
copyfile-41.tar.gz mac-os-x-105 mac-os-x-1051 mac-os-x-1052 v41
authorApple <opensource@apple.com>
Thu, 27 Sep 2007 21:56:30 +0000 (21:56 +0000)
committerApple <opensource@apple.com>
Thu, 27 Sep 2007 21:56:30 +0000 (21:56 +0000)
Makefile [new file with mode: 0644]
copyfile.3 [new file with mode: 0644]
copyfile.c [new file with mode: 0644]
copyfile.h [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..3d19028
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,79 @@
+##
+# 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
+MAN_DIR = $(DSTROOT)/usr/share/man/man3
+INC_DIR = $(DSTROOT)/usr/include
+
+WFLAGS= -Wno-trigraphs -Wmissing-prototypes -Wreturn-type -Wformat \
+       -Wmissing-braces -Wparentheses -Wswitch -Wunused-function \
+       -Wunused-label -Wunused-variable -Wunused-value -Wshadow \
+       -Wsign-compare -Wall -Wextra -Wpointer-arith -Wreturn-type \
+       -Wwrite-strings -Wcast-align -Wbad-function-cast \
+       -Wchar-subscripts -Winline -Wnested-externs -Wredundant-decls \
+       -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
+
+installhdrs:: $(HDRS)
+       install -d -m 755 $(INC_DIR)
+       install -c -m 444 $(SRCROOT)/copyfile.h $(INC_DIR)
+
+install:: $(LIBS)
+       install -d -m 755 $(MAN_DIR)
+       install -c -m 644 $(SRCROOT)/copyfile.3 $(MAN_DIR)
+       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 $@ $^
diff --git a/copyfile.3 b/copyfile.3
new file mode 100644 (file)
index 0000000..2e78bf3
--- /dev/null
@@ -0,0 +1,300 @@
+.\"
+.\" Copyright (c) 2002 Apple Computer, Inc.  All rights reserved.
+.\"
+.Dd April 27, 2006
+.Dt COPYFILE 3
+.Os
+.Sh NAME
+.Nm copyfile , fcopyfile ,
+.Nm copyfile_state_alloc , copyfile_state_free ,
+.Nm copyfile_state_get , copyfile_state_set
+.Nd copy a file
+.Sh LIBRARY
+.Lb libc
+.Sh SYNOPSIS
+.In copyfile.h
+.Ft int
+.Fn copyfile "const char *from" "const char *to" "copyfile_state_t state" "copyfile_flags_t flags"
+.Ft int
+.Fn fcopyfile "int from" "int to" "copyfile_state_t state" "copyfile_flags_t flags"
+.Ft copyfile_state_t
+.Fn copyfile_state_alloc "void"
+.Ft int
+.Fn copyfile_state_free "copyfile_state_t state"
+.Ft int
+.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"
+.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
+forth.)
+.Pp
+The
+.Fn copyfile_state_alloc
+function initializes a
+.Vt copyfile_state_t
+object (which is an opaque data type).
+This object can be passed to
+.Fn copyfile
+and
+.Fn fcopyfile ;
+.Fn copyfile_state_get
+and
+.Fn copyfile_state_set
+can be used to manipulate the state (see below).
+The
+.Fn copyfile_state_free
+function is used to deallocate the object and its contents.
+.Pp
+The
+.Fn copyfile
+function can copy the named
+.Va from
+file to the named
+.Va to
+file; the
+.Fn fcopyfile
+function does the same, but using the file descriptors of already-opened
+files.
+If the
+.Va state
+parameter is the return value from
+.Fn copyfile_state_alloc ,
+then
+.Fn copyfile
+and
+.Fn fcopyfile
+will use the information from the state object; if it is
+.Dv NULL ,
+then both functions will work normally, but less control will be available to the caller.
+The
+.Va flags
+parameter controls which contents are copied:
+.Bl -tag -width COPYFILE_XATTR
+.It Dv COPYFILE_ACL
+Copy the source file's access control lists.
+.It Dv COPYFILE_STAT
+Copy the source file's POSIX information (mode, modification time, etc.).
+.It Dv COPYFILE_XATTR
+Copy the source file's extended attributes.
+.It Dv COPYFILE_DATA
+Copy the source file's data.
+.El
+.Pp
+These values may be or'd together; several convenience macros are provided:
+.Bl -tag -width COPYFILE_SECURITY
+.It Dv COPYFILE_SECURITY
+Copy the source file's POSIX and ACL information; equivalent to
+.Dv (COPYFILE_STAT|COPYFILE_ACL) .
+.It Dv COPYFILE_METADATA
+Copy the metadata; equivalent to
+.Dv (COPYFILE_SECURITY|COPYFILE_XATTR) .
+.It Dv COPYFILE_ALL
+Copy the entire file; equivalent to
+.Dv (COPYFILE_METADATA|COPYFILE_DATA) .
+.El
+.Pp
+The
+.Fn copyfile
+and
+.Fn fcopyfile
+functions can also have their behavior modified by the following flags:
+.Bl -tag -width COPYFILE_NOFOLLOW_SRC
+.It Dv COPYFILE_CHECK
+Return a bitmask (corresponding to the
+.Va flags
+argument) indicating which contents would be copied; no data are actually
+copied.  (E.g., if
+.Va flags
+was set to
+.Dv COPYFILE_CHECK|COPYFILE_METADATA ,
+and the
+.Va from
+file had extended attributes but no ACLs, the return value would be
+.Dv COPYFILE_XATTR .)
+.It Dv COPYFILE_PACK
+Serialize the
+.Va from
+file.  The
+.Va to
+file is an AppleDouble-format file.
+.It Dv COPYFILE_UNPACK
+Unserialize the
+.Va from
+file.  The
+.Va from
+file is an AppleDouble-format file; the
+.Va to
+file will have the extended attributes, ACLs, resource fork, and
+FinderInfo data from the
+.Va to
+file, regardless of the
+.Va flags
+argument passed in.
+.It Dv COPYFILE_EXCL
+Fail if the
+.Va to
+file already exists.  (This is only applicable for the
+.Fn copyfile
+function.)
+.It Dv COPYFILE_NOFOLLOW_SRC
+Do not follow the
+.Va from
+file, if it is a symbolic link.  (This is only applicable for the
+.Fn copyfile
+function.)
+.It Dv COPYFILE_NOFOLLOW_DST
+Do not follow the
+.Va to
+file, if it is a symbolic link.  (This is only applicable for the
+.Fn copyfile
+function.)
+.It Dv COPYFILE_MOVE
+Unlink (remove) the
+.Fa from
+file.  (This is only applicable for the
+.Fn copyfile
+function.)
+.It Dv COPYFILE_UNLINK
+Unlink the
+.Va to
+file before starting.  (This is only applicable for the
+.Fn copyfile
+function.)
+.It Dv COPYFILE_NOFOLLOW
+This is a convenience macro, equivalent to
+.Dv (COPYFILE_NOFOLLOW_DST|COPYFILE_NOFOLLOW_SRC) .
+.El
+.Pp
+The
+.Fn copyfile_state_get
+and
+.Fn copyfile_state_set
+functions can be used to manipulate the
+.Ft copyfile_state_t
+object returned by
+.Fn copyfile_state_alloc .
+In both functions, the
+.Va dst
+parameter's type depends on the
+.Va flag
+parameter that is passed in.
+.Bl -tag -width COPYFILE_STATE_DST_FILENAME
+.It Dv COPYFILE_STATE_SRC_FD
+.It Dv COPYFILE_STATE_DST_FD
+Get or set the file descriptor associated with the source (or destination)
+file.
+If this has not been initialized yet, the value will be -2.
+The
+.Va dst
+(for
+.Fn copyfile_state_get )
+and
+.Va src
+(for
+.Fn copyfile_state_set )
+parameters are pointers to
+.Vt int .
+.It Dv COPYFILE_STATE_SRC_FILENAME
+.It Dv COPYFILE_STATE_DST_FILENAME
+Get or set the filename associated with the source (or destination)
+file.  If it has not been initialized yet, the value will be
+.Dv NULL .
+For
+.Fn copyfile_state_set ,
+the
+.Va src
+parameter is a pointer to a C string
+(i.e.,
+.Vt char* );
+.Fn copyfile_state_set
+makes a private copy of this string.
+For
+.Fn copyfile_state_get
+function, the
+.Va dst
+parameter is a pointer to a pointer to a C string
+(i.e.,
+.Vt char** );
+the returned value is a pointer to the
+.Va state 's
+copy, and must not be modified or released.
+.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.
+.El
+.Sh RETURN VALUES
+Except when given the
+.Dv COPYFILE_CHECK
+flag,
+.Fn copyfile
+and
+.Fn fcopyfile
+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 ERRORS
+.Fn copyfile
+and
+.Fn fcopyfile
+will fail if:
+.Bl -tag -width Er
+.It Bq Er EINVAL
+Either the
+.Va from
+or
+.Va to
+parameter was a
+.Dv NULL
+pointer (
+.Fn copyfile ),
+or a negative number (
+.Fn fcopyfile ).
+.It Bq Er ENOMEM
+A memory allocation failed.
+.It Bq Er ENOTSUP
+The source file was not a directory, symbolic link, or regular file.
+.El
+In addition, both functions may set
+.Dv errno
+via an underlying library or system call.
+.Sh EXAMPLES
+.Bd -literal -offset indent
+/* Initialize a state variable */
+copyfile_state_t s;
+s = copyfile_state_alloc();
+/* Copy the data and extended attributes of one file to another */
+copyfile("/tmp/f1", "/tmp/f2", s, COPYFILE_DATA | COPYFILE_XATTR);
+/* Convert a file to an AppleDouble file for serialization */
+copyfile("/tmp/f2", "/tmp/tmpfile", NULL, COPYFILE_ALL | COPYFILE_PACK);
+/* Release the state variable */
+copyfile_state_free(s);
+/* A more complex way to call copyfile() */
+s = copyfile_state_alloc();
+copyfile_state_set(s, COPYFILE_STATE_SRC_FILENAME, "/tmp/foo");
+/* One of src or dst must be set... rest can come from the state */
+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);
+.Ed
+.Sh BUGS
+Both
+.Fn copyfile
+functions lack a way to set the input or output block size.
+.Sh HISTORY
+The
+.Fn copyfile
+API was introduced in Mac OS X 10.5.
diff --git a/copyfile.c b/copyfile.c
new file mode 100644 (file)
index 0000000..c7c8207
--- /dev/null
@@ -0,0 +1,2638 @@
+/*
+ * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include <err.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/acl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/errno.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/xattr.h>
+#include <sys/syscall.h>
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/acl.h>
+#include <libkern/OSByteOrder.h>
+#include <membership.h>
+
+#include <quarantine.h>
+
+#define        XATTR_QUARANTINE_NAME qtn_xattr_name
+
+#include "copyfile.h"
+
+/*
+ * The state structure keeps track of
+ * the source filename, the destination filename, their
+ * associated file-descriptors, the stat infomration for the
+ * source file, the security information for the source file,
+ * the flags passed in for the copy, a pointer to place statistics
+ * (not currently implemented), debug flags, and a pointer to callbacks
+ * (not currently implemented).
+ */
+struct _copyfile_state
+{
+    char *src;
+    char *dst;
+    int src_fd;
+    int dst_fd;
+    struct stat sb;
+    filesec_t fsec;
+    copyfile_flags_t flags;
+    void *stats;
+    uint32_t debug;
+    void *callbacks;
+    qtn_file_t qinfo;  /* Quarantine information -- probably NULL */
+};
+
+/*
+ * Internally, the process is broken into a series of
+ * private functions.
+ */
+static int copyfile_open       (copyfile_state_t);
+static int copyfile_close      (copyfile_state_t);
+static int copyfile_data       (copyfile_state_t);
+static int copyfile_stat       (copyfile_state_t);
+static int copyfile_security   (copyfile_state_t);
+static int copyfile_xattr      (copyfile_state_t);
+static int copyfile_pack       (copyfile_state_t);
+static int copyfile_unpack     (copyfile_state_t);
+
+static copyfile_flags_t copyfile_check (copyfile_state_t);
+static filesec_t copyfile_fix_perms(copyfile_state_t, filesec_t *);
+static int copyfile_preamble(copyfile_state_t *s, copyfile_flags_t flags);
+static int copyfile_internal(copyfile_state_t state, copyfile_flags_t flags);
+static int copyfile_unset_posix_fsec(filesec_t);
+static int copyfile_quarantine(copyfile_state_t);
+
+#define COPYFILE_DEBUG (1<<31)
+#define COPYFILE_DEBUG_VAR "COPYFILE_DEBUG"
+
+#ifndef _COPYFILE_TEST
+# define copyfile_warn(str, ...) syslog(LOG_WARNING, str ": %m", ## __VA_ARGS__)
+# define copyfile_debug(d, str, ...) \
+   do { \
+    if (s && (d <= s->debug)) {\
+       syslog(LOG_DEBUG, "%s:%d:%s() " str "\n", __FILE__, __LINE__ , __FUNCTION__, ## __VA_ARGS__); \
+    } \
+   } while (0)
+#else
+#define copyfile_warn(str, ...) \
+    fprintf(stderr, "%s:%d:%s() " str ": %s\n", __FILE__, __LINE__ , __FUNCTION__, ## __VA_ARGS__, (errno) ? strerror(errno) : "")
+# define copyfile_debug(d, str, ...) \
+    do { \
+    if (s && (d <= s->debug)) {\
+       fprintf(stderr, "%s:%d:%s() " str "\n", __FILE__, __LINE__ , __FUNCTION__, ## __VA_ARGS__); \
+    } \
+    } while(0)
+#endif
+
+static int copyfile_quarantine(copyfile_state_t s)
+{
+    int rv = 0;
+    if (s->qinfo == NULL)
+    {
+       int error;
+       s->qinfo = qtn_file_alloc();
+       if (s->qinfo == NULL)
+       {
+           rv = -1;
+           goto done;
+       }
+       if ((error = qtn_file_init_with_fd(s->qinfo, s->src_fd)) != 0)
+       {
+           qtn_file_free(s->qinfo);
+           s->qinfo = NULL;
+           errno = error;
+           rv = -1;
+           goto done;
+       }
+    }
+done:
+    return rv;
+}
+
+/*
+ * 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
+ * the files (doing various security checks, perhaps), and then just pass in
+ * the file descriptors.
+ */
+int fcopyfile(int src_fd, int dst_fd, copyfile_state_t state, copyfile_flags_t flags)
+{
+    int ret = 0;
+    copyfile_state_t s = state;
+    struct stat dst_sb;
+
+    if (src_fd < 0 || dst_fd < 0)
+    {
+       errno = EINVAL;
+       return -1;
+    }
+
+    if (copyfile_preamble(&s, flags) < 0)
+       return -1;
+
+    copyfile_debug(2, "set src_fd <- %d", src_fd);
+    if (s->src_fd == -2 && src_fd > -1)
+    {
+       s->src_fd = src_fd;
+       if (fstatx_np(s->src_fd, &s->sb, s->fsec) != 0)
+       {
+           if (errno == ENOTSUP)
+               fstat(s->src_fd, &s->sb);
+           else
+           {
+               copyfile_warn("fstatx_np on src fd %d", s->src_fd);
+               return -1;
+           }
+       }
+    }
+
+    /* prevent copying on unsupported types */
+    switch (s->sb.st_mode & S_IFMT)
+    {
+       case S_IFLNK:
+       case S_IFDIR:
+       case S_IFREG:
+           break;
+       default:
+           errno = ENOTSUP;
+           return -1;
+    }
+
+    copyfile_debug(2, "set dst_fd <- %d", dst_fd);
+    if (s->dst_fd == -2 && dst_fd > -1)
+       s->dst_fd = dst_fd;
+
+    (void)fstat(s->dst_fd, &dst_sb);
+    (void)fchmod(s->dst_fd, (dst_sb.st_mode & ~S_IFMT) | (S_IRUSR | S_IWUSR));
+
+    (void)copyfile_quarantine(s);
+
+    ret = copyfile_internal(s, flags);
+
+    if (ret >= 0 && !(s->flags & COPYFILE_STAT))
+    {
+       (void)fchmod(s->dst_fd, dst_sb.st_mode & ~S_IFMT);
+    }
+
+    if (state == NULL)
+       copyfile_state_free(s);
+
+    return ret;
+
+}
+
+/*
+ * the original copyfile() routine; this copies a source file to a destination
+ * file.  Note that because we need to set the names in the state variable, this
+ * is not just the same as opening the two files, and then calling fcopyfile().
+ * Oh, if only life were that simple!
+ */
+int copyfile(const char *src, const char *dst, copyfile_state_t state, copyfile_flags_t flags)
+{
+    int ret = 0;
+    copyfile_state_t s = state;
+    filesec_t original_fsec = NULL;
+    filesec_t permissive_fsec = NULL;
+    struct stat sb;
+
+    if (src == NULL && dst == NULL)
+    {
+       errno = EINVAL;
+       return -1;
+    }
+
+    if (copyfile_preamble(&s, flags) < 0)
+    {
+       return -1;
+    }
+
+/*
+ * This macro is... well, it's not the worst thing you can do with cpp, not
+ *  by a long shot.  Essentially, we are setting the filename (src or dst)
+ * in the state structure; since the structure may not have been cleared out
+ * before being used again, we do some of the cleanup here:  if the given
+ * filename (e.g., src) is set, and state->src is not equal to that, then
+ * we need to check to see if the file descriptor had been opened, and if so,
+ * close it.  After that, we set state->src to be a copy of the given filename,
+ * releasing the old copy if necessary.
+ */
+#define COPYFILE_SET_FNAME(NAME, S) \
+  do { \
+    if (NAME != NULL) {                                                                        \
+       if (S->NAME != NULL && strncmp(NAME, S->NAME, MAXPATHLEN)) {                    \
+           copyfile_debug(2, "replacing string %s (%s) -> (%s)", #NAME, NAME, S->NAME);\
+           if (S->NAME##_fd != -2 && S->NAME##_fd > -1) {                              \
+               copyfile_debug(4, "closing %s fd: %d", #NAME, S->NAME##_fd);            \
+               close(S->NAME##_fd);                                                    \
+               S->NAME##_fd = -2;                                                      \
+           }                                                                           \
+       }                                                                               \
+       if (S->NAME) {                                                                  \
+           free(S->NAME);                                                              \
+           S->NAME = NULL;                                                             \
+       }                                                                               \
+       if ((S->NAME = strdup(NAME)) == NULL)                                           \
+           return -1;                                                                  \
+    }                                                                                  \
+  } while (0)
+
+    COPYFILE_SET_FNAME(src, s);
+    COPYFILE_SET_FNAME(dst, s);
+
+    /*
+     * Get a copy of the source file's security settings
+     */
+    if ((original_fsec = filesec_init()) == NULL)
+       goto error_exit;
+
+    if(statx_np(s->dst, &sb, original_fsec) == 0)
+    {
+          /*
+           * copyfile_fix_perms() will make a copy of the permission set,
+           * and insert at the beginning an ACE that ensures we can write
+           * to the file and set attributes.
+           */
+
+       if((permissive_fsec = copyfile_fix_perms(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)
+           {
+               copyfile_warn("setting security information");
+               filesec_free(permissive_fsec);
+               permissive_fsec = NULL;
+           }
+       }
+    }
+
+    /*
+     * If COPYFILE_CHECK is set in flags, then all we are going to do
+     * is see what kinds of things WOULD have been copied (see
+     * copyfile_check() below).  We return that value.
+     */
+    if (COPYFILE_CHECK & flags)
+    {
+       ret = copyfile_check(s);
+       goto exit;
+    } else if ((ret = copyfile_open(s)) < 0)
+       goto error_exit;
+
+    ret = copyfile_internal(s, flags);
+
+    /* 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");
+       }
+    }
+exit:
+    if (state == NULL)
+       copyfile_state_free(s);
+
+    if (original_fsec)
+       filesec_free(original_fsec);
+    if (permissive_fsec)
+       filesec_free(permissive_fsec);
+
+    return ret;
+
+error_exit:
+    ret = -1;
+    goto exit;
+}
+
+/*
+ * Shared prelude to the {f,}copyfile().  This initializes the
+ * state variable, if necessary, and also checks for both debugging
+ * and disabling environment variables.
+ */
+static int copyfile_preamble(copyfile_state_t *state, copyfile_flags_t flags)
+{
+    copyfile_state_t s;
+
+    if (*state == NULL)
+    {
+       if ((*state = copyfile_state_alloc()) == NULL)
+           return -1;
+    }
+
+    s = *state;
+
+    if (COPYFILE_DEBUG & flags)
+    {
+       char *e;
+       if ((e = getenv(COPYFILE_DEBUG_VAR)))
+       {
+           errno = 0;
+           s->debug = (uint32_t)strtol(e, NULL, 0);
+
+           /* clamp s->debug to 1 if the environment variable is not parsable */
+           if (s->debug == 0 && errno != 0)
+               s->debug = 1;
+       }
+       copyfile_debug(2, "debug value set to: %d", s->debug);
+    }
+
+#if 0
+    /* Temporarily disabled */
+    if (getenv(COPYFILE_DISABLE_VAR) != NULL)
+    {
+       copyfile_debug(1, "copyfile disabled");
+       return 2;
+    }
+#endif
+    copyfile_debug(2, "setting flags: %d", s->flags);
+    s->flags = flags;
+
+    return 0;
+}
+
+/*
+ * The guts of {f,}copyfile().
+ * This looks through the flags in a particular order, and calls the
+ * associated functions.
+ */
+static int copyfile_internal(copyfile_state_t s, copyfile_flags_t flags)
+{
+    int ret = 0;
+
+    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;
+       return -1;
+    }
+
+    /*
+     * COPYFILE_PACK causes us to create an Apple Double version of the
+     * source file, and puts it into the destination file.  See
+     * copyfile_pack() below for all the gory details.
+     */
+    if (COPYFILE_PACK & flags)
+    {
+       if ((ret = copyfile_pack(s)) < 0)
+       {
+           unlink(s->dst);
+           goto exit;
+       }
+       goto exit;
+    }
+
+    /*
+     * COPYFILE_UNPACK is the undoing of COPYFILE_PACK, obviously.
+     * The goal there is to take an Apple Double file, and turn it
+     * into a normal file (with data fork, resource fork, modes,
+     * extended attributes, ACLs, etc.).
+     */
+    if (COPYFILE_UNPACK & flags)
+    {
+       if ((ret = copyfile_unpack(s)) < 0)
+           goto error_exit;
+       goto exit;
+    }
+
+    /*
+     * If we have quarantine info set, we attempt
+     * to apply it to dst_fd.  We don't care if
+     * it fails, not yet anyway.
+     */
+    if (s->qinfo)
+       (void)qtn_file_apply_to_fd(s->qinfo, s->dst_fd);
+
+    /*
+     * COPYFILE_XATTR tells us to copy the extended attributes;
+     * this is seperate from the extended security (aka ACLs),
+     * however.  If we succeed in this, we continue to the next
+     * stage; if we fail, we return with an error value.  Note
+     * that we fail if the errno is ENOTSUP, but we don't print
+     * a warning in that case.
+     */
+    if (COPYFILE_XATTR & flags)
+    {
+       if ((ret = copyfile_xattr(s)) < 0)
+       {
+           if (errno != ENOTSUP)
+               copyfile_warn("error processing extended attributes");
+           goto exit;
+       }
+    }
+
+    /*
+     * Simialr to above, this tells us whether or not to copy
+     * the non-meta data portion of the file.  We attempt to
+     * remove (via unlink) the destination file if we fail.
+     */
+    if (COPYFILE_DATA & flags)
+    {
+       if ((ret = copyfile_data(s)) < 0)
+       {
+           copyfile_warn("error processing data");
+           if (s->dst && unlink(s->dst))
+                   copyfile_warn("%s: remove", s->src);
+           goto exit;
+       }
+    }
+
+    /*
+     * COPYFILE_SECURITY requests that we copy the security, both
+     * extended and mundane (that is, ACLs and POSIX).
+     */
+    if (COPYFILE_SECURITY & flags)
+    {
+       if ((ret = copyfile_security(s)) < 0)
+       {
+           copyfile_warn("error processing security information");
+           goto exit;
+       }
+    }
+
+    if (COPYFILE_STAT & flags)
+    {
+       if ((ret = copyfile_stat(s)) < 0)
+       {
+           copyfile_warn("error processing POSIX information");
+           goto exit;
+       }
+    }
+
+exit:
+    return ret;
+
+error_exit:
+    ret = -1;
+    goto exit;
+}
+
+/*
+ * A publicly-visible routine, copyfile_state_alloc() sets up the state variable.
+ */
+copyfile_state_t copyfile_state_alloc(void)
+{
+    copyfile_state_t s = (copyfile_state_t) calloc(1, sizeof(struct _copyfile_state));
+
+    if (s != NULL)
+    {
+       s->src_fd = -2;
+       s->dst_fd = -2;
+       s->fsec = filesec_init();
+    } else
+       errno = ENOMEM;
+
+    return s;
+}
+
+/*
+ * copyfile_state_free() returns the memory allocated to the state structure.
+ * It also closes the file descriptors, if they've been opened.
+ */
+int copyfile_state_free(copyfile_state_t s)
+{
+    if (s != NULL)
+    {
+       if (s->fsec)
+           filesec_free(s->fsec);
+
+       if (s->qinfo)
+           qtn_file_free(s->qinfo);
+
+       if (copyfile_close(s) < 0)
+       {
+           copyfile_warn("error closing files");
+           return -1;
+       }
+       if (s->dst)
+           free(s->dst);
+       if (s->src)
+           free(s->src);
+       free(s);
+    }
+    return 0;
+}
+
+/*
+ * Should we worry if we can't close the source?  NFS says we
+ * should, but it's pretty late for us at this point.
+ */
+static int copyfile_close(copyfile_state_t s)
+{
+    if (s->src && s->src_fd >= 0)
+       close(s->src_fd);
+
+    if (s->dst && s->dst_fd >= 0) {
+       if (close(s->dst_fd))
+           return -1;
+    }
+
+    return 0;
+}
+
+/*
+ * The purpose of this function is to set up a set of permissions
+ * (ACL and traditional) that lets us write to the file.  In the
+ * case of ACLs, we do this by putting in a first entry that lets
+ * us write data, attributes, and extended attributes.  In the case
+ * of traditional permissions, we set the S_IWUSR (user-write)
+ * bit.
+ */
+static filesec_t copyfile_fix_perms(copyfile_state_t s __unused, filesec_t *fsec)
+{
+    filesec_t ret_fsec = NULL;
+    mode_t mode;
+    acl_t acl = NULL;
+
+    if ((ret_fsec = filesec_dup(*fsec)) == NULL)
+       goto error_exit;
+
+    if (filesec_get_property(ret_fsec, FILESEC_ACL, &acl) == 0)
+    {
+       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_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;
+
+       if (filesec_set_property(ret_fsec, FILESEC_ACL, &acl) != 0)
+           goto error_exit;
+    }
+
+    /*
+     * This is for the normal, mundane, POSIX permission model.
+     * We make sure that we can write to the file.
+     */
+    if (filesec_get_property(ret_fsec, FILESEC_MODE, &mode) == 0)
+    {
+       if ((mode & (S_IWUSR | S_IRUSR)) != (S_IWUSR | S_IRUSR))
+       {
+           mode |= S_IWUSR|S_IRUSR;
+           if (filesec_set_property(ret_fsec, FILESEC_MODE, &mode) != 0)
+               goto error_exit;
+       }
+    }
+
+exit:
+    if (acl)
+       acl_free(acl);
+
+    return ret_fsec;
+
+error_exit:
+    if (ret_fsec)
+    {
+       filesec_free(ret_fsec);
+       ret_fsec = NULL;
+    }
+    goto exit;
+}
+
+/*
+ * Used to clear out the BSD/POSIX security information from
+ * a filesec
+ */
+static int
+copyfile_unset_posix_fsec(filesec_t fsec)
+{
+       (void)filesec_set_property(fsec, FILESEC_OWNER, _FILESEC_UNSET_PROPERTY);
+       (void)filesec_set_property(fsec, FILESEC_GROUP, _FILESEC_UNSET_PROPERTY);
+       (void)filesec_set_property(fsec, FILESEC_MODE, _FILESEC_UNSET_PROPERTY);
+       return 0;
+}
+
+/*
+ * Used to remove acl information from a filesec_t
+ * Unsetting the acl alone in Tiger was insufficient
+ */
+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);
+       ++ret;
+    }
+    if (filesec_set_property(s->fsec, FILESEC_UUID, NULL) == -1)
+    {
+       copyfile_debug(5, "unsetting uuid attribute on %s", s->dst);
+       ++ret;
+    }
+    if (filesec_set_property(s->fsec, FILESEC_GRPUUID, NULL) == -1)
+    {
+       copyfile_debug(5, "unsetting group uuid attribute on %s", s->dst);
+       ++ret;
+    }
+    return ret;
+}
+
+/*
+ * copyfile_open() does what one expects:  it opens up the files
+ * given in the state structure, if they're not already open.
+ * It also does some type validation, to ensure that we only
+ * handle file types we know about.
+ */
+static int copyfile_open(copyfile_state_t s)
+{
+    int oflags = O_EXCL | O_CREAT | O_WRONLY;
+    int islnk = 0, isdir = 0;
+    int osrc = 0, dsrc = 0;
+
+    if (s->src && s->src_fd == -2)
+    {
+       if ((COPYFILE_NOFOLLOW_SRC & s->flags ? lstatx_np : statx_np)
+               (s->src, &s->sb, s->fsec))
+       {
+           copyfile_warn("stat on %s", s->src);
+           return -1;
+       }
+
+       /* prevent copying on unsupported types */
+       switch (s->sb.st_mode & S_IFMT)
+       {
+           case S_IFLNK:
+               islnk = 1;
+               if (s->sb.st_size > (off_t)SIZE_T_MAX) {
+                       errno = ENOMEM; /* too big for us to copy */
+                       return -1;
+               }
+               osrc = O_SYMLINK;
+               break;
+           case S_IFDIR:
+               isdir = 1;
+               break;
+           case S_IFREG:
+               break;
+           default:
+               errno = ENOTSUP;
+               return -1;
+       }
+       /*
+        * If we're packing, then we are actually
+        * creating a file, no matter what the source
+        * was.
+        */
+       if (s->flags & COPYFILE_PACK) {
+               /*
+                * O_SYMLINK and O_NOFOLLOW are not compatible options:
+                * if the file is a symlink, and O_NOFOLLOW is specified,
+                * open will return ELOOP, whether or not O_SYMLINK is set.
+                * However, we know whether or not it was a symlink from
+                * the stat above (although there is a potentiaal for a race
+                * condition here, but it will err on the side of returning
+                * ELOOP from open).
+                */
+               if (!islnk)
+                       osrc = (s->flags & COPYFILE_NOFOLLOW_SRC) ? O_NOFOLLOW : 0;
+               isdir = islnk = 0;
+       }
+
+       if ((s->src_fd = open(s->src, O_RDONLY | osrc , 0)) < 0)
+       {
+               copyfile_warn("open on %s", s->src);
+               return -1;
+       } else
+           copyfile_debug(2, "open successful on source (%s)", s->src);
+
+       (void)copyfile_quarantine(s);
+    }
+
+    if (s->dst && s->dst_fd == -2)
+    {
+       /*
+        * COPYFILE_UNLINK tells us to try removing the destination
+        * before we create it.  We don't care if the file doesn't
+        * exist, so we ignore ENOENT.
+        */
+       if (COPYFILE_UNLINK & s->flags)
+       {
+           if (remove(s->dst) < 0 && errno != ENOENT)
+           {
+               copyfile_warn("%s: remove", s->dst);
+               return -1;
+           }
+       }
+
+       if (s->flags & COPYFILE_NOFOLLOW_DST)
+               dsrc = O_NOFOLLOW;
+
+       if (islnk) {
+               size_t sz = (size_t)s->sb.st_size + 1;
+               char *bp;
+
+               bp = calloc(1, sz);
+               if (bp == NULL) {
+                       copyfile_warn("cannot allocate %d bytes", sz);
+                       return -1;
+               }
+               if (readlink(s->src, bp, sz-1) == -1) {
+                       copyfile_warn("cannot readlink %s", s->src);
+                       free(bp);
+                       return -1;
+               }
+               if (symlink(bp, s->dst) == -1) {
+                       if (errno != EEXIST || (s->flags & COPYFILE_EXCL)) {
+                               copyfile_warn("Cannot make symlink %s", s->dst);
+                               free(bp);
+                               return -1;
+                       }
+               }
+               free(bp);
+               s->dst_fd = open(s->dst, O_RDONLY | O_SYMLINK);
+               if (s->dst_fd == -1) {
+                       copyfile_warn("Cannot open symlink %s for reading", s->dst);
+                       return -1;
+               }
+       } else if (isdir) {
+               mode_t mode;
+               mode = s->sb.st_mode & ~S_IFMT;
+
+               if (mkdir(s->dst, mode) == -1) {
+                       if (errno != EEXIST || (s->flags & COPYFILE_EXCL)) {
+                               copyfile_warn("Cannot make directory %s", s->dst);
+                               return -1;
+                       }
+               }
+               s->dst_fd = open(s->dst, O_RDONLY | dsrc);
+               if (s->dst_fd == -1) {
+                       copyfile_warn("Cannot open directory %s for reading", s->dst);
+                       return -1;
+               }
+       } else while((s->dst_fd = open(s->dst, oflags | dsrc, s->sb.st_mode | S_IWUSR)) < 0)
+       {
+           /*
+            * We set S_IWUSR because fsetxattr does not -- at the time this comment
+            * was written -- allow one to set an extended attribute on a file descriptor
+            * for a read-only file, even if the file descriptor is opened for writing.
+            * This will only matter if the file does not already exist.
+            */
+           switch(errno)
+           {
+               case EEXIST:
+                   copyfile_debug(3, "open failed, retrying (%s)", s->dst);
+                   if (s->flags & COPYFILE_EXCL)
+                       break;
+                   oflags = oflags & ~O_CREAT;
+                   if (s->flags & (COPYFILE_PACK | COPYFILE_DATA))
+                   {
+                       copyfile_debug(4, "truncating existing file (%s)", s->dst);
+                       oflags |= O_TRUNC;
+                   }
+                   continue;
+               case EACCES:
+                   if(chmod(s->dst, (s->sb.st_mode | S_IWUSR) & ~S_IFMT) == 0)
+                       continue;
+                   else {
+                       break;
+                   }
+               case EISDIR:
+                   copyfile_debug(3, "open failed because it is a directory (%s)", s->dst);
+                   if ((s->flags & COPYFILE_EXCL) ||
+                       (!isdir && (s->flags & COPYFILE_DATA)))
+                       break;
+                   oflags = (oflags & ~O_WRONLY) | O_RDONLY;
+                   continue;
+           }
+           copyfile_warn("open on %s", s->dst);
+           return -1;
+       }
+       copyfile_debug(2, "open successful on destination (%s)", s->dst);
+    }
+
+    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;
+       return -1;
+    }
+    return 0;
+}
+
+
+/*
+ * copyfile_check(), as described above, essentially tells you
+ * what you'd have to copy, if you wanted it to copy the things
+ * you asked it to copy.
+ * In other words, if you pass in COPYFILE_ALL, and the file in
+ * question had no extended attributes but did have an ACL, you'd
+ * get back COPYFILE_ACL.
+ */
+static copyfile_flags_t copyfile_check(copyfile_state_t s)
+{
+    acl_t acl = NULL;
+    copyfile_flags_t ret = 0;
+    int nofollow = (s->flags & COPYFILE_NOFOLLOW_SRC);
+    qtn_file_t qinfo;
+
+    if (!s->src)
+    {
+       errno = EINVAL;
+       return -1;
+    }
+
+    /* check EAs */
+    if (COPYFILE_XATTR & s->flags)
+       if (listxattr(s->src, 0, 0, nofollow ? XATTR_NOFOLLOW : 0) > 0)
+       {
+           ret |= COPYFILE_XATTR;
+       }
+
+    if (COPYFILE_ACL & s->flags)
+    {
+       (COPYFILE_NOFOLLOW_SRC & s->flags ? lstatx_np : statx_np)
+               (s->src, &s->sb, s->fsec);
+
+       if (filesec_get_property(s->fsec, FILESEC_ACL, &acl) == 0)
+           ret |= COPYFILE_ACL;
+    }
+
+    copyfile_debug(2, "check result: %d (%s)", ret, s->src);
+
+    if (acl)
+       acl_free(acl);
+
+    if (s->qinfo) {
+       /* If the state has had quarantine info set already, we use that */
+       ret |= ((s->flags & COPYFILE_XATTR) ? COPYFILE_XATTR : COPYFILE_ACL);
+    } else {
+       qinfo = qtn_file_alloc();
+       /*
+        * For quarantine information, we need to see if the source file
+        * has any.  Since it may be a symlink, however, and we may, or
+        * not be following, *and* there's no qtn* routine which can optionally
+        * follow or not follow a symlink, we need to instead work around
+        * this limitation.
+       */
+       if (qinfo) {
+               int fd;
+               int qret = 0;
+               struct stat sbuf;
+
+               /*
+                * If we care about not following symlinks, *and* the file exists
+                * (which is to say, lstat doesn't return an error), *and* the file
+                * is a symlink, then we open it up (with O_SYMLINK), and use
+                * qtn_file_init_with_fd(); if none of that is true, however, then
+                * we can simply use qtn_file_init_with_path().
+                */
+               if (nofollow
+                       && lstat(s->src, &sbuf) == 0
+                               && ((sbuf.st_mode & S_IFMT) == S_IFLNK)) {
+                       fd = open(s->src, O_RDONLY | O_SYMLINK);
+                       if (fd != -1) {
+                               if (!qtn_file_init_with_fd(qinfo, fd)) {
+                                       qret |= ((s->flags & COPYFILE_XATTR) ? COPYFILE_XATTR : COPYFILE_ACL);
+                               }
+                               close(fd);
+                       }
+               } else {
+                       if (!qtn_file_init_with_path(qinfo, s->src)) {
+                               qret |= ((s->flags & COPYFILE_XATTR) ? COPYFILE_XATTR : COPYFILE_ACL);
+                       }
+               }
+               qtn_file_free(qinfo);
+               ret |= qret;
+       }
+    }
+    return ret;
+}
+
+/*
+ * Attempt to copy the data section of a file.  Using blockisize
+ * is not necessarily the fastest -- it might be desirable to
+ * specify a blocksize, somehow.  But it's a size that should be
+ * guaranteed to work.
+ */
+static int copyfile_data(copyfile_state_t s)
+{
+    size_t blen;
+    char *bp = 0;
+    ssize_t nread;
+    int ret = 0;
+    size_t iBlocksize = 0;
+    struct statfs sfs;
+
+    if (fstatfs(s->src_fd, &sfs) == -1) {
+       iBlocksize = s->sb.st_blksize;
+    } else {
+       iBlocksize = sfs.f_iosize;
+    }
+
+    if ((bp = malloc(iBlocksize)) == NULL)
+       return -1;
+
+    blen = iBlocksize;
+
+/* If supported, do preallocation for Xsan / HFS volumes */
+#ifdef F_PREALLOCATE
+    {
+       fstore_t fst;
+
+       fst.fst_flags = 0;
+       fst.fst_posmode = F_PEOFPOSMODE;
+       fst.fst_offset = 0;
+       fst.fst_length = s->sb.st_size;
+       /* Ignore errors; this is merely advisory. */
+       (void)fcntl(s->dst_fd, F_PREALLOCATE, &fst);
+    }
+#endif
+
+    while ((nread = read(s->src_fd, bp, blen)) > 0)
+    {
+       size_t nwritten;
+       size_t left = nread;
+       void *ptr = bp;
+
+       while (left > 0) {
+               int loop = 0;
+               nwritten = write(s->dst_fd, ptr, left);
+               switch (nwritten) {
+               case 0:
+                       if (++loop > 5) {
+                               copyfile_warn("writing to output %d times resulted in 0 bytes written", loop);
+                               ret = -1;
+                               errno = EAGAIN;
+                               goto exit;
+                       }
+                       break;
+               case -1:
+                       copyfile_warn("writing to output file got error");
+                       ret = -1;
+                       goto exit;
+               default:
+                       left -= nwritten;
+                       ptr = ((char*)ptr) + nwritten;
+                       break;
+               }
+       }
+    }
+    if (nread < 0)
+    {
+       copyfile_warn("reading from %s", s->src);
+       goto exit;
+    }
+
+    if (ftruncate(s->dst_fd, s->sb.st_size) < 0)
+    {
+       ret = -1;
+       goto exit;
+    }
+
+exit:
+    free(bp);
+    return ret;
+}
+
+/*
+ * copyfile_security() will copy the ACL set, and the
+ * POSIX set.  Complexities come when dealing with
+ * inheritied permissions, and when dealing with both
+ * POSIX and ACL permissions.
+ */
+static int copyfile_security(copyfile_state_t s)
+{
+    int copied = 0;
+    acl_flagset_t flags;
+    struct stat sb;
+    acl_entry_t entry_src = NULL, entry_dst = NULL;
+    acl_t acl_src = NULL, acl_dst = NULL;
+    int ret = 0;
+    filesec_t tmp_fsec = NULL;
+    filesec_t fsec_dst = filesec_init();
+
+    if (fsec_dst == NULL)
+       return -1;
+
+
+    if (COPYFILE_ACL & s->flags)
+    {
+       if (filesec_get_property(s->fsec, FILESEC_ACL, &acl_src))
+       {
+           if (errno == ENOENT)
+               goto no_acl;
+           else
+               goto error_exit;
+       }
+
+/* grab the destination acl
+    cannot assume it's empty due to inheritance
+*/
+       if(fstatx_np(s->dst_fd, &sb, fsec_dst))
+           goto error_exit;
+
+       if (filesec_get_property(fsec_dst, FILESEC_ACL, &acl_dst))
+       {
+           if (errno == ENOENT)
+               acl_dst = acl_init(4);
+           else
+               goto error_exit;
+       }
+
+       for (;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->dst);
+               copied++;
+           }
+       }
+       if (!filesec_set_property(s->fsec, FILESEC_ACL, &acl_dst))
+       {
+           copyfile_debug(3, "altered acl");
+       }
+    }
+no_acl:
+    /*
+     * The following code is attempting to ensure that only the requested
+     * security information gets copied over to the destination file.
+     * We essentially have four cases:  COPYFILE_ACL, COPYFILE_STAT,
+     * COPYFILE_(STAT|ACL), and none (in which case, we wouldn't be in
+     * this function).
+     *
+     * If we have both flags, we copy everything; if we have ACL but not STAT,
+     * we remove the POSIX information from the filesec object, and apply the
+     * ACL; if we have STAT but not ACL, then we just use fchmod(), and ignore
+     * the extended version.
+     */
+    tmp_fsec = filesec_dup(s->fsec);
+    if (tmp_fsec == NULL) {
+       goto error_exit;
+    }
+
+    switch (COPYFILE_SECURITY & s->flags) {
+       case COPYFILE_ACL:
+           copyfile_unset_posix_fsec(tmp_fsec);
+           /* 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);
+           }
+           break;
+       case COPYFILE_STAT:
+           fchmod(s->dst_fd, s->sb.st_mode);
+           break;
+    }
+    filesec_free(tmp_fsec);
+exit:
+    filesec_free(fsec_dst);
+    if (acl_src) acl_free(acl_src);
+    if (acl_dst) acl_free(acl_dst);
+
+    return ret;
+
+error_exit:
+    ret = -1;
+goto exit;
+
+}
+
+/*
+ * Attempt to set the destination file's stat information -- including
+ * flags and time-related fields -- to the source's.
+ */
+static int copyfile_stat(copyfile_state_t s)
+{
+    struct timeval tval[2];
+    /*
+     * NFS doesn't support chflags; ignore errors unless there's reason
+     * to believe we're losing bits.  (Note, this still won't be right
+     * if the server supports flags and we were trying to *remove* flags
+     * on a file that we copied, i.e., that we didn't create.)
+     */
+    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);
+
+    /* If this fails, we don't care */
+    (void)fchown(s->dst_fd, s->sb.st_uid, s->sb.st_gid);
+
+    /* This may have already been done in copyfile_security() */
+    (void)fchmod(s->dst_fd, s->sb.st_mode & ~S_IFMT);
+
+    tval[0].tv_sec = s->sb.st_atime;
+    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);
+    return 0;
+}
+
+/*
+ * Similar to copyfile_security() in some ways; this
+ * routine copies the extended attributes from the source,
+ * and sets them on the destination.
+ * The procedure is pretty simple, even if it is verbose:
+ * for each named attribute on the destination, get its name, and
+ * remove it.  We should have none after that.
+ * For each named attribute on the source, get its name, get its
+ * data, and set it on the destination.
+ */
+static int copyfile_xattr(copyfile_state_t s)
+{
+    char *name;
+    char *namebuf, *end;
+    ssize_t xa_size;
+    void *xa_dataptr;
+    ssize_t bufsize = 4096;
+    ssize_t asize;
+    ssize_t nsize;
+    int ret = 0;
+
+    /* delete EAs on destination */
+    if ((nsize = flistxattr(s->dst_fd, 0, 0, 0)) > 0)
+    {
+       if ((namebuf = (char *) malloc(nsize)) == NULL)
+           return -1;
+       else
+           nsize = flistxattr(s->dst_fd, namebuf, nsize, 0);
+
+       if (nsize > 0) {
+           /*
+            * With this, end points to the last byte of the allocated buffer
+            * This *should* be NUL, from flistxattr, but if it's not, we can
+            * set it anyway -- it'll result in a truncated name, which then
+            * shouldn't match when we get them later.
+            */
+           end = namebuf + nsize - 1;
+           if (*end != 0)
+               *end = 0;
+           for (name = namebuf; name <= end; name += strlen(name) + 1) {
+               /* If the quarantine information shows up as an EA, we skip over it */
+               if (strncmp(name, XATTR_QUARANTINE_NAME, end - name) == 0) {
+                   continue;
+               }
+               fremovexattr(s->dst_fd, name,0);
+           }
+       }
+       free(namebuf);
+    } else
+    if (nsize < 0)
+    {
+       if (errno == ENOTSUP)
+           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)
+           return 0;
+       else
+           return -1;
+    } else
+    if (nsize == 0)
+       return 0;
+
+    if ((namebuf = (char *) malloc(nsize)) == NULL)
+       return -1;
+    else
+       nsize = flistxattr(s->src_fd, namebuf, nsize, 0);
+
+    if (nsize <= 0) {
+       free(namebuf);
+       return (int)nsize;
+    }
+
+    /*
+     * With this, end points to the last byte of the allocated buffer
+     * This *should* be NUL, from flistxattr, but if it's not, we can
+     * set it anyway -- it'll result in a truncated name, which then
+     * shouldn't match when we get them later.
+     */
+    end = namebuf + nsize - 1;
+    if (*end != 0)
+       *end = 0;
+
+    if ((xa_dataptr = (void *) malloc(bufsize)) == NULL) {
+       free(namebuf);
+       return -1;
+    }
+
+    for (name = namebuf; name <= end; name += strlen(name) + 1)
+    {
+       /* If the quarantine information shows up as an EA, we skip over it */
+       if (strncmp(name, XATTR_QUARANTINE_NAME, end - name) == 0)
+           continue;
+
+       if ((xa_size = fgetxattr(s->src_fd, name, 0, 0, 0, 0)) < 0)
+       {
+           ret = -1;
+           continue;
+       }
+
+       if (xa_size > bufsize)
+       {
+           void *tdptr = xa_dataptr;
+           bufsize = xa_size;
+           if ((xa_dataptr =
+               (void *) realloc((void *) xa_dataptr, bufsize)) == NULL)
+           {
+               free(tdptr);
+               ret = -1;
+               continue;
+           }
+       }
+
+       if ((asize = fgetxattr(s->src_fd, name, xa_dataptr, xa_size, 0, 0)) < 0)
+       {
+           ret = -1;
+           continue;
+       }
+
+       if (xa_size != asize)
+           xa_size = asize;
+
+       if (fsetxattr(s->dst_fd, name, xa_dataptr, xa_size, 0, 0) < 0)
+       {
+           ret = -1;
+           continue;
+       }
+    }
+    if (namebuf)
+       free(namebuf);
+    free((void *) xa_dataptr);
+    return ret;
+}
+
+/*
+ * API interface into getting data from the opaque data type.
+ */
+int copyfile_state_get(copyfile_state_t s, uint32_t flag, void *ret)
+{
+    if (ret == NULL)
+    {
+       errno = EFAULT;
+       return -1;
+    }
+
+    switch(flag)
+    {
+       case COPYFILE_STATE_SRC_FD:
+           *(int*)ret = s->src_fd;
+           break;
+       case COPYFILE_STATE_DST_FD:
+           *(int*)ret = s->dst_fd;
+           break;
+       case COPYFILE_STATE_SRC_FILENAME:
+           *(char**)ret = s->src;
+           break;
+       case COPYFILE_STATE_DST_FILENAME:
+           *(char**)ret = s->dst;
+           break;
+       case COPYFILE_STATE_QUARANTINE:
+           *(qtn_file_t*)ret = s->qinfo;
+           break;
+#if 0
+       case COPYFILE_STATE_STATS:
+           ret = s->stats.global;
+           break;
+       case COPYFILE_STATE_PROGRESS_CB:
+           ret = s->callbacks.progress;
+           break;
+#endif
+       default:
+           errno = EINVAL;
+           ret = NULL;
+           return -1;
+    }
+    return 0;
+}
+
+/*
+ * Public API for setting state data (remember that the state is
+ * an opaque data type).
+ */
+int copyfile_state_set(copyfile_state_t s, uint32_t flag, const void * thing)
+{
+#define copyfile_set_string(DST, SRC) \
+    do {                                       \
+       if (SRC != NULL) {                      \
+           DST = strdup((char *)SRC);          \
+       } else {                                \
+           if (DST != NULL) {                  \
+               free(DST);                      \
+           }                                   \
+           DST = NULL;                         \
+       }                                       \
+    } while (0)
+
+    if (thing == NULL)
+    {
+       errno = EFAULT;
+       return  -1;
+    }
+
+    switch(flag)
+    {
+       case COPYFILE_STATE_SRC_FD:
+           s->src_fd = *(int*)thing;
+           break;
+       case COPYFILE_STATE_DST_FD:
+            s->dst_fd = *(int*)thing;
+           break;
+       case COPYFILE_STATE_SRC_FILENAME:
+           copyfile_set_string(s->src, thing);
+           break;
+       case COPYFILE_STATE_DST_FILENAME:
+           copyfile_set_string(s->dst, thing);
+           break;
+       case COPYFILE_STATE_QUARANTINE:
+           if (s->qinfo)
+           {
+               qtn_file_free(s->qinfo);
+               s->qinfo = NULL;
+           }
+           if (*(qtn_file_t*)thing)
+               s->qinfo = qtn_file_clone(*(qtn_file_t*)thing);
+           break;
+#if 0
+       case COPYFILE_STATE_STATS:
+            s->stats.global = thing;
+           break;
+       case COPYFILE_STATE_PROGRESS_CB:
+            s->callbacks.progress = thing;
+           break;
+#endif
+       default:
+           errno = EINVAL;
+           return -1;
+    }
+    return 0;
+#undef copyfile_set_string
+}
+
+
+/*
+ * Make this a standalone program for testing purposes by
+ * defining _COPYFILE_TEST.
+ */
+#ifdef _COPYFILE_TEST
+#define COPYFILE_OPTION(x) { #x, COPYFILE_ ## x },
+
+struct {char *s; int v;} opts[] = {
+    COPYFILE_OPTION(ACL)
+    COPYFILE_OPTION(STAT)
+    COPYFILE_OPTION(XATTR)
+    COPYFILE_OPTION(DATA)
+    COPYFILE_OPTION(SECURITY)
+    COPYFILE_OPTION(METADATA)
+    COPYFILE_OPTION(ALL)
+    COPYFILE_OPTION(NOFOLLOW_SRC)
+    COPYFILE_OPTION(NOFOLLOW_DST)
+    COPYFILE_OPTION(NOFOLLOW)
+    COPYFILE_OPTION(EXCL)
+    COPYFILE_OPTION(MOVE)
+    COPYFILE_OPTION(UNLINK)
+    COPYFILE_OPTION(PACK)
+    COPYFILE_OPTION(UNPACK)
+    COPYFILE_OPTION(CHECK)
+    COPYFILE_OPTION(VERBOSE)
+    COPYFILE_OPTION(DEBUG)
+    {NULL, 0}
+};
+
+int main(int c, char *v[])
+{
+    int i;
+    int flags = 0;
+
+    if (c < 3)
+       errx(1, "insufficient arguments");
+
+    while(c-- > 3)
+    {
+       for (i = 0; opts[i].s != NULL; ++i)
+       {
+           if (strcasecmp(opts[i].s, v[c]) == 0)
+           {
+               printf("option %d: %s <- %d\n", c, opts[i].s, opts[i].v);
+               flags |= opts[i].v;
+               break;
+           }
+       }
+    }
+
+    return copyfile(v[1], v[2], NULL, flags);
+}
+#endif
+/*
+ * Apple Double Create
+ *
+ * Create an Apple Double "._" file from a file's extented attributes
+ *
+ * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
+ */
+
+
+#define offsetof(type, member) ((size_t)(&((type *)0)->member))
+
+#define        XATTR_MAXATTRLEN   (4*1024)
+
+
+/*
+   Typical "._" AppleDouble Header File layout:
+  ------------------------------------------------------------
+         MAGIC          0x00051607
+         VERSION        0x00020000
+         FILLER         0
+         COUNT          2
+     .-- AD ENTRY[0]    Finder Info Entry (must be first)
+  .--+-- AD ENTRY[1]    Resource Fork Entry (must be last)
+  |  '-> FINDER INFO
+  |      /////////////  Fixed Size Data (32 bytes)
+  |      EXT ATTR HDR
+  |      /////////////
+  |      ATTR ENTRY[0] --.
+  |      ATTR ENTRY[1] --+--.
+  |      ATTR ENTRY[2] --+--+--.
+  |         ...          |  |  |
+  |      ATTR ENTRY[N] --+--+--+--.
+  |      ATTR DATA 0   <-'  |  |  |
+  |      ////////////       |  |  |
+  |      ATTR DATA 1   <----'  |  |
+  |      /////////////         |  |
+  |      ATTR DATA 2   <-------'  |
+  |      /////////////            |
+  |         ...                   |
+  |      ATTR DATA N   <----------'
+  |      /////////////
+  |                      Attribute Free Space
+  |
+  '----> RESOURCE FORK
+         /////////////   Variable Sized Data
+         /////////////
+         /////////////
+         /////////////
+         /////////////
+         /////////////
+            ...
+         /////////////
+  ------------------------------------------------------------
+
+   NOTE: The EXT ATTR HDR, ATTR ENTRY's and ATTR DATA's are
+   stored as part of the Finder Info.  The length in the Finder
+   Info AppleDouble entry includes the length of the extended
+   attribute header, attribute entries, and attribute data.
+*/
+
+
+/*
+ * On Disk Data Structures
+ *
+ * Note: Motorola 68K alignment and big-endian.
+ *
+ * See RFC 1740 for additional information about the AppleDouble file format.
+ *
+ */
+
+#define ADH_MAGIC     0x00051607
+#define ADH_VERSION   0x00020000
+#define ADH_MACOSX    "Mac OS X        "
+
+/*
+ * AppleDouble Entry ID's
+ */
+#define AD_DATA          1   /* Data fork */
+#define AD_RESOURCE      2   /* Resource fork */
+#define AD_REALNAME      3   /* File's name on home file system */
+#define AD_COMMENT       4   /* Standard Mac comment */
+#define AD_ICONBW        5   /* Mac black & white icon */
+#define AD_ICONCOLOR     6   /* Mac color icon */
+#define AD_UNUSED        7   /* Not used */
+#define AD_FILEDATES     8   /* File dates; create, modify, etc */
+#define AD_FINDERINFO    9   /* Mac Finder info & extended info */
+#define AD_MACINFO      10   /* Mac file info, attributes, etc */
+#define AD_PRODOSINFO   11   /* Pro-DOS file info, attrib., etc */
+#define AD_MSDOSINFO    12   /* MS-DOS file info, attributes, etc */
+#define AD_AFPNAME      13   /* Short name on AFP server */
+#define AD_AFPINFO      14   /* AFP file info, attrib., etc */
+#define AD_AFPDIRID     15   /* AFP directory ID */ 
+#define AD_ATTRIBUTES   AD_FINDERINFO
+
+
+#define ATTR_FILE_PREFIX   "._"
+#define ATTR_HDR_MAGIC     0x41545452   /* 'ATTR' */
+
+#define ATTR_BUF_SIZE      4096        /* default size of the attr file and how much we'll grow by */
+
+/* Implementation Limits */
+#define ATTR_MAX_SIZE      (128*1024)  /* 128K maximum attribute data size */
+#define ATTR_MAX_NAME_LEN  128
+#define ATTR_MAX_HDR_SIZE  (65536+18)
+
+/*
+ * Note: ATTR_MAX_HDR_SIZE is the largest attribute header
+ * size supported (including the attribute entries). All of
+ * the attribute entries must reside within this limit.
+ */
+
+
+#define FINDERINFOSIZE 32
+
+typedef struct apple_double_entry
+{
+       u_int32_t   type;     /* entry type: see list, 0 invalid */ 
+       u_int32_t   offset;   /* entry data offset from the beginning of the file. */
+       u_int32_t   length;   /* entry data length in bytes. */
+} __attribute__((aligned(2), packed)) apple_double_entry_t;
+
+
+typedef struct apple_double_header
+{
+       u_int32_t   magic;         /* == ADH_MAGIC */
+       u_int32_t   version;       /* format version: 2 = 0x00020000 */ 
+       u_int32_t   filler[4];
+       u_int16_t   numEntries;    /* number of entries which follow */ 
+       apple_double_entry_t   entries[2];  /* 'finfo' & 'rsrc' always exist */
+       u_int8_t    finfo[FINDERINFOSIZE];  /* Must start with Finder Info (32 bytes) */
+       u_int8_t    pad[2];        /* get better alignment inside attr_header */
+} __attribute__((aligned(2), packed)) apple_double_header_t;
+
+
+/* Entries are aligned on 4 byte boundaries */
+typedef struct attr_entry
+{
+       u_int32_t   offset;    /* file offset to data */
+       u_int32_t   length;    /* size of attribute data */
+       u_int16_t   flags;
+       u_int8_t    namelen;   /* length of name including NULL termination char */ 
+       u_int8_t    name[1];   /* NULL-terminated UTF-8 name (up to 128 bytes max) */
+} __attribute__((aligned(2), packed)) attr_entry_t;
+
+
+
+/* Header + entries must fit into 64K */
+typedef struct attr_header
+{
+       apple_double_header_t  appledouble;
+       u_int32_t   magic;        /* == ATTR_HDR_MAGIC */
+       u_int32_t   debug_tag;    /* for debugging == file id of owning file */
+       u_int32_t   total_size;   /* total size of attribute header + entries + data */ 
+       u_int32_t   data_start;   /* file offset to attribute data area */
+       u_int32_t   data_length;  /* length of attribute data area */
+       u_int32_t   reserved[3];
+       u_int16_t   flags;
+       u_int16_t   num_attrs;
+} __attribute__((aligned(2), packed)) attr_header_t;
+
+/* Empty Resource Fork Header */
+/* This comes by way of xnu's vfs_xattr.c */
+typedef struct rsrcfork_header {
+       u_int32_t       fh_DataOffset;
+       u_int32_t       fh_MapOffset;
+       u_int32_t       fh_DataLength;
+       u_int32_t       fh_MapLength;
+       u_int8_t        systemData[112];
+       u_int8_t        appData[128];
+       u_int32_t       mh_DataOffset; 
+       u_int32_t       mh_MapOffset;
+       u_int32_t       mh_DataLength; 
+       u_int32_t       mh_MapLength;
+       u_int32_t       mh_Next;
+       u_int16_t       mh_RefNum;
+       u_int8_t        mh_Attr;
+       u_int8_t        mh_InMemoryAttr; 
+       u_int16_t       mh_Types;
+       u_int16_t       mh_Names;
+       u_int16_t       typeCount;
+} rsrcfork_header_t;
+#define RF_FIRST_RESOURCE    256
+#define RF_NULL_MAP_LENGTH    30   
+#define RF_EMPTY_TAG  "This resource fork intentionally left blank   "
+
+static const rsrcfork_header_t empty_rsrcfork_header = {
+       OSSwapHostToBigInt32(RF_FIRST_RESOURCE),        // fh_DataOffset
+       OSSwapHostToBigInt32(RF_FIRST_RESOURCE),        // fh_MapOffset
+       0,                                              // fh_DataLength
+       OSSwapHostToBigInt32(RF_NULL_MAP_LENGTH),       // fh_MapLength
+       { RF_EMPTY_TAG, },                              // systemData
+       { 0 },                                          // appData
+       OSSwapHostToBigInt32(RF_FIRST_RESOURCE),        // mh_DataOffset
+       OSSwapHostToBigInt32(RF_FIRST_RESOURCE),        // mh_MapOffset
+       0,                                              // mh_DataLength
+       OSSwapHostToBigInt32(RF_NULL_MAP_LENGTH),       // mh_MapLength
+       0,                                              // mh_Next
+       0,                                              // mh_RefNum
+       0,                                              // mh_Attr
+       0,                                              // mh_InMemoryAttr
+       OSSwapHostToBigInt16(RF_NULL_MAP_LENGTH - 2),   // mh_Types
+       OSSwapHostToBigInt16(RF_NULL_MAP_LENGTH),       // mh_Names
+       OSSwapHostToBigInt16(-1),                       // typeCount
+};
+
+#define SWAP16(x)      OSSwapBigToHostInt16(x)
+#define SWAP32(x)      OSSwapBigToHostInt32(x)
+#define SWAP64(x)      OSSwapBigToHostInt64(x)
+
+#define ATTR_ALIGN 3L  /* Use four-byte alignment */
+
+#define ATTR_ENTRY_LENGTH(namelen)  \
+        ((sizeof(attr_entry_t) - 1 + (namelen) + ATTR_ALIGN) & (~ATTR_ALIGN))
+
+#define ATTR_NEXT(ae)  \
+        (attr_entry_t *)((u_int8_t *)(ae) + ATTR_ENTRY_LENGTH((ae)->namelen))
+
+#define        XATTR_SECURITY_NAME       "com.apple.acl.text"
+
+/*
+ * Endian swap Apple Double header 
+ */
+static void
+swap_adhdr(apple_double_header_t *adh)
+{
+#if BYTE_ORDER == LITTLE_ENDIAN
+       int count;
+       int i;
+
+       count = (adh->magic == ADH_MAGIC) ? adh->numEntries : SWAP16(adh->numEntries);
+
+       adh->magic      = SWAP32 (adh->magic);
+       adh->version    = SWAP32 (adh->version);
+       adh->numEntries = SWAP16 (adh->numEntries);
+
+       for (i = 0; i < count; i++)
+       {
+               adh->entries[i].type   = SWAP32 (adh->entries[i].type);
+               adh->entries[i].offset = SWAP32 (adh->entries[i].offset);
+               adh->entries[i].length = SWAP32 (adh->entries[i].length);
+       }
+#else
+       (void)adh;
+#endif
+}
+
+/*
+ * Endian swap extended attributes header 
+ */
+static void
+swap_attrhdr(attr_header_t *ah)
+{
+#if BYTE_ORDER == LITTLE_ENDIAN
+       attr_entry_t *ae;
+       int count;
+       int i;
+
+       count = (ah->magic == ATTR_HDR_MAGIC) ? ah->num_attrs : SWAP16(ah->num_attrs);
+
+       ah->magic       = SWAP32 (ah->magic);
+       ah->debug_tag   = SWAP32 (ah->debug_tag);
+       ah->total_size  = SWAP32 (ah->total_size);
+       ah->data_start  = SWAP32 (ah->data_start);
+       ah->data_length = SWAP32 (ah->data_length);
+       ah->flags       = SWAP16 (ah->flags);
+       ah->num_attrs   = SWAP16 (ah->num_attrs);
+
+       ae = (attr_entry_t *)(&ah[1]);
+       for (i = 0; i < count; i++)
+       {
+               attr_entry_t *next = ATTR_NEXT(ae);
+               ae->offset = SWAP32 (ae->offset);
+               ae->length = SWAP32 (ae->length);
+               ae->flags  = SWAP16 (ae->flags);
+               ae = next;
+       }
+#else
+       (void)ah;
+#endif
+}
+
+static const u_int32_t emptyfinfo[8] = {0};
+
+/*
+ * Given an Apple Double file in src, turn it into a
+ * normal file (possibly with multiple forks, EAs, and
+ * ACLs) in dst.
+ */
+static int copyfile_unpack(copyfile_state_t s)
+{
+    ssize_t bytes;
+    void * buffer, * endptr;
+    apple_double_header_t *adhdr;
+    ssize_t hdrsize;
+    int error = 0;
+
+    if (s->sb.st_size < ATTR_MAX_HDR_SIZE)
+       hdrsize = (ssize_t)s->sb.st_size;
+    else
+       hdrsize = ATTR_MAX_HDR_SIZE;
+
+    buffer = calloc(1, hdrsize);
+    if (buffer == NULL) {
+       copyfile_debug(1, "copyfile_unpack: calloc(1, %u) returned NULL", hdrsize);
+       error = -1;
+       goto exit;
+    } else
+       endptr = (char*)buffer + hdrsize;
+
+    bytes = pread(s->src_fd, buffer, hdrsize, 0);
+
+    if (bytes < 0)
+    {
+       copyfile_debug(1, "pread returned: %d", bytes);
+       error = -1;
+       goto exit;
+    }
+    if (bytes < hdrsize)
+    {
+       copyfile_debug(1,
+           "pread couldn't read entire header: %d of %d",
+           (int)bytes, (int)s->sb.st_size);
+       error = -1;
+       goto exit;
+    }
+    adhdr = (apple_double_header_t *)buffer;
+
+    /*
+     * Check for Apple Double file. 
+     */
+    if ((size_t)bytes < sizeof(apple_double_header_t) - 2 ||
+       SWAP32(adhdr->magic) != ADH_MAGIC ||
+       SWAP32(adhdr->version) != ADH_VERSION ||
+       SWAP16(adhdr->numEntries) != 2 ||
+       SWAP32(adhdr->entries[0].type) != AD_FINDERINFO)
+    {
+       if (COPYFILE_VERBOSE & s->flags)
+           copyfile_warn("Not a valid Apple Double header");
+       error = -1;
+       goto exit;
+    }
+    swap_adhdr(adhdr);
+
+    /*
+     * Remove any extended attributes on the target.
+     */
+
+    if (COPYFILE_XATTR & s->flags)
+    {
+       if ((bytes = flistxattr(s->dst_fd, 0, 0, 0)) > 0)
+       {
+           char *namebuf, *name;
+
+           if ((namebuf = (char*) malloc(bytes)) == NULL)
+           {
+               errno = ENOMEM;
+               goto exit;
+           }
+           bytes = flistxattr(s->dst_fd, namebuf, bytes, 0);
+
+           if (bytes > 0)
+               for (name = namebuf; name < namebuf + bytes; name += strlen(name) + 1)
+                   (void)fremovexattr(s->dst_fd, name, 0);
+
+           free(namebuf);
+       }
+       else if (bytes < 0)
+       {
+           if (errno != ENOTSUP)
+           goto exit;
+       }
+    }
+
+    /*
+     * Extract the extended attributes.
+     *
+     * >>>  WARNING <<<
+     * This assumes that the data is already in memory (not
+     * the case when there are lots of attributes or one of
+     * the attributes is very large.
+     */
+    if (adhdr->entries[0].length > FINDERINFOSIZE)
+    {
+       attr_header_t *attrhdr;
+       attr_entry_t *entry;
+       int count;
+       int i;
+
+       if ((size_t)hdrsize < sizeof(attr_header_t)) {
+               copyfile_warn("bad attribute header:  %u < %u", hdrsize, sizeof(attr_header_t));
+               error = -1;
+               goto exit;
+       }
+
+       attrhdr = (attr_header_t *)buffer;
+       swap_attrhdr(attrhdr);
+       if (attrhdr->magic != ATTR_HDR_MAGIC)
+       {
+           if (COPYFILE_VERBOSE & s->flags)
+                   copyfile_warn("bad attribute header");
+           error = -1;
+           goto exit;
+       }
+       count = attrhdr->num_attrs;
+       entry = (attr_entry_t *)&attrhdr[1];
+
+       for (i = 0; i < count; i++)
+       {
+           void * dataptr;
+
+           /*
+            * First we do some simple sanity checking.
+            * +) See if entry is within the buffer's range;
+            *
+            * +) Check the attribute name length; if it's longer than the
+            * maximum, we truncate it down.  (We could error out as well;
+            * I'm not sure which is the better way to go here.)
+            *
+            * +) If, given the name length, it goes beyond the end of
+            * the buffer, error out.
+            *
+            * +) If the last byte isn't a NUL, make it a NUL.  (Since we
+            * truncated the name length above, we truncate the name here.)
+            *
+            * +) If entry->offset is so large that it causes dataptr to
+            * go beyond the end of the buffer -- or, worse, so large that
+            * it wraps around! -- we error out.
+            *
+            * +) If entry->length would cause the entry to go beyond the
+            * end of the buffer (or, worse, wrap around to before it),
+            * *or* if the length is larger than the hdrsize, we error out.
+            * (An explanation of that:  what we're checking for there is
+            * the small range of values such that offset+length would cause
+            * it to go beyond endptr, and then wrap around past buffer.  We
+            * care about this because we are passing entry->length down to
+            * fgetxattr() below, and an erroneously large value could cause
+            * problems there.  By making sure that it's less than hdrsize,
+            * which has already been sanity-checked above, we're safe.
+            * That may mean that the check against < buffer is unnecessary.)
+            */
+           if ((void*)entry >= endptr || (void*)entry < buffer) {
+               if (COPYFILE_VERBOSE & s->flags)
+                   copyfile_warn("Incomplete or corrupt attribute entry");
+               error = -1;
+               goto exit;
+           }
+
+           if (((char*)entry + sizeof(*entry)) > (char*)endptr) {
+               if (COPYFILE_VERBOSE & s->flags)
+                   copyfile_warn("Incomplete or corrupt attribute entry");
+               error = -1;
+               goto exit;
+           }
+
+           if (entry->namelen < 2) {
+               if (COPYFILE_VERBOSE & s->flags)
+                   copyfile_warn("Corrupt attribute entry (only %d bytes)", entry->namelen);
+                   error = -1;
+                   goto exit;
+           }
+
+           if (entry->namelen > XATTR_MAXNAMELEN + 1) {
+               if (COPYFILE_VERBOSE & s->flags)
+                   copyfile_warn("Corrupt attribute entry (name length is %d bytes)", entry->namelen);
+               error = -1;
+               goto exit;
+           }
+
+           if ((void*)(entry->name + entry->namelen) > endptr) {
+               if (COPYFILE_VERBOSE & s->flags)
+                   copyfile_warn("Incomplete or corrupt attribute entry");
+               error = -1;
+               goto exit;
+           }
+
+           /* Because namelen includes the NUL, we check one byte back */
+           if (entry->name[entry->namelen-1] != 0) {
+               if (COPYFILE_VERBOSE & s->flags)
+                   copyfile_warn("Corrupt attribute entry (name is not NUL-terminated)");
+               error = -1;
+               goto exit;
+           }
+
+           copyfile_debug(3, "extracting \"%s\" (%d bytes) at offset %u",
+               entry->name, entry->length, entry->offset);
+
+           dataptr = (char *)attrhdr + entry->offset;
+
+           if (dataptr > endptr || dataptr < buffer) {
+               copyfile_debug(1, "Entry %d overflows:  offset = %u", entry->offset);
+               error = -1;
+               goto exit;
+           }
+           if (((char*)dataptr + entry->length) > (char*)endptr ||
+               (((char*)dataptr + entry->length) < (char*)buffer) ||
+               (entry->length > (size_t)hdrsize)) {
+               if (COPYFILE_VERBOSE & s->flags)
+                   copyfile_warn("Incomplete or corrupt attribute entry");
+               copyfile_debug(1, "Entry %d length overflows:  offset = %u, length = %u",
+                       entry->offset, entry->length);
+               error = -1;
+               goto exit;
+           }
+
+           if (strcmp((char*)entry->name, XATTR_QUARANTINE_NAME) == 0)
+           {
+               qtn_file_t tqinfo = NULL;
+
+               if (s->qinfo == NULL)
+               {
+                   tqinfo = qtn_file_alloc();
+                   if (tqinfo)
+                   {
+                       int x;
+                       if ((x = qtn_file_init_with_data(tqinfo, dataptr, entry->length)) != 0)
+                       {
+                           copyfile_warn("qtn_file_init_with_data failed: %s", qtn_error(x));
+                           qtn_file_free(tqinfo);
+                           tqinfo = NULL;
+                       }
+                   }
+               }
+               else
+               {
+                   tqinfo = s->qinfo;
+               }
+               if (tqinfo)
+               {
+                       int x;
+                       x = qtn_file_apply_to_fd(tqinfo, s->dst_fd);
+                       if (x != 0)
+                           copyfile_warn("qtn_file_apply_to_fd failed: %s", qtn_error(x));
+               }
+               if (tqinfo && !s->qinfo)
+               {
+                   qtn_file_free(tqinfo);
+               }
+           }
+           /* Look for ACL data */
+           else if (COPYFILE_ACL & s->flags && strcmp((char*)entry->name, XATTR_SECURITY_NAME) == 0)
+           {
+               acl_t acl;
+               struct stat sb;
+               int retry = 1;
+               char *tcp = dataptr;
+
+               /*
+                * acl_from_text() requires a NUL-terminated string.  The ACL EA,
+                * however, may not be NUL-terminated.  So in that case, we need to
+                * copy it to a +1 sized buffer, to ensure it's got a terminated string.
+                */
+               if (tcp[entry->length - 1] != 0) {
+                       char *tmpstr = malloc(entry->length + 1);
+                       if (tmpstr == NULL) {
+                               error = -1;
+                               goto exit;
+                       }
+                       strlcpy(tmpstr, tcp, entry->length + 1);
+                       acl = acl_from_text(tmpstr);
+                       free(tmpstr);
+               } else {
+                       acl = acl_from_text(tcp);
+               }
+
+               if (acl != NULL)
+               {
+                   filesec_t fsec_tmp;
+
+                   if ((fsec_tmp = filesec_init()) == NULL)
+                       error = -1;
+                   else if((error = fstatx_np(s->dst_fd, &sb, fsec_tmp)) < 0)
+                       error = -1;
+                   else if (filesec_set_property(fsec_tmp, FILESEC_ACL, &acl) < 0)
+                       error = -1;
+                   else {
+                       while (fchmodx_np(s->dst_fd, fsec_tmp) < 0)
+                       {
+                           if (errno == ENOTSUP)
+                           {
+                                   if (retry && !copyfile_unset_acl(s))
+                                   {
+                                       retry = 0;
+                                       continue;
+                                   }
+                           }
+                           copyfile_warn("setting security information");
+                           error = -1;
+                           break;
+                       }
+                   }
+                   acl_free(acl);
+                   filesec_free(fsec_tmp);
+
+                   if (error == -1)
+                       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;
+           }
+           entry = ATTR_NEXT(entry);
+       }
+    }
+
+    /*
+     * Extract the Finder Info.
+     */
+    if (adhdr->entries[0].offset > (hdrsize - sizeof(emptyfinfo))) {
+       error = -1;
+       goto exit;
+    }
+
+    if (bcmp((u_int8_t*)buffer + adhdr->entries[0].offset, emptyfinfo, sizeof(emptyfinfo)) != 0)
+    {
+       copyfile_debug(3, " extracting \"%s\" (32 bytes)", XATTR_FINDERINFO_NAME);
+       error = fsetxattr(s->dst_fd, XATTR_FINDERINFO_NAME, (u_int8_t*)buffer + adhdr->entries[0].offset, sizeof(emptyfinfo), 0, 0);
+       if (error)
+           goto exit;
+    }
+
+    /*
+     * Extract the Resource Fork.
+     */
+    if (adhdr->entries[1].type == AD_RESOURCE &&
+       adhdr->entries[1].length > 0)
+    {
+       void * rsrcforkdata = NULL;
+       size_t length;
+       off_t offset;
+       struct stat sb;
+       struct timeval tval[2];
+
+       length = adhdr->entries[1].length;
+       offset = adhdr->entries[1].offset;
+       rsrcforkdata = malloc(length);
+
+       if (rsrcforkdata == NULL) {
+               copyfile_debug(1, "could not allocate %u bytes for rsrcforkdata",
+                       length);
+               error = -1;
+               goto bad;
+       }
+
+       if (fstat(s->dst_fd, &sb) < 0)
+       {
+               copyfile_debug(1, "couldn't stat destination file");
+               error = -1;
+               goto bad;
+       }
+
+       bytes = pread(s->src_fd, rsrcforkdata, length, offset);
+       if (bytes < (ssize_t)length)
+       {
+           if (bytes == -1)
+           {
+               copyfile_debug(1, "couldn't read resource fork");
+           }
+           else
+           {
+               copyfile_debug(1,
+                   "couldn't read resource fork (only read %d bytes of %d)",
+                   (int)bytes, (int)length);
+           }
+           error = -1;
+           goto bad;
+       }
+       error = fsetxattr(s->dst_fd, XATTR_RESOURCEFORK_NAME, rsrcforkdata, bytes, 0, 0);
+       if (error)
+       {
+            /*
+             * For filesystems that do not natively support named attributes,
+             * the kernel creates an AppleDouble file that -- for compatabilty
+             * reasons -- has a resource fork containing nothing but a rsrcfork_header_t
+             * structure that says there are no resources.  So, if fsetxattr has
+             * failed, and the resource fork is that empty structure, *and* the
+             * target file is a directory, then we do nothing with it.
+             */
+           if ((bytes == sizeof(rsrcfork_header_t)) &&
+               ((sb.st_mode & S_IFMT) == S_IFDIR)  &&
+               (memcmp(rsrcforkdata, &empty_rsrcfork_header, bytes) == 0)) {
+                   copyfile_debug(2, "not setting empty resource fork on directory");
+                   error = errno = 0;
+                   goto bad;
+           }
+           copyfile_debug(1, "error %d setting resource fork attribute", error);
+           error = -1;
+           goto bad;
+       }
+       copyfile_debug(3, "extracting \"%s\" (%d bytes)",
+                   XATTR_RESOURCEFORK_NAME, (int)length);
+
+       if (!(s->flags & COPYFILE_STAT))
+       {
+           tval[0].tv_sec = sb.st_atime;
+           tval[1].tv_sec = 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);
+       }
+bad:
+       if (rsrcforkdata)
+           free(rsrcforkdata);
+    }
+
+    if (COPYFILE_STAT & s->flags)
+    {
+       error = copyfile_stat(s);
+    }
+exit:
+    if (buffer) free(buffer);
+    return error;
+}
+
+static int copyfile_pack_quarantine(copyfile_state_t s, void **buf, ssize_t *len)
+{
+    int ret = 0;
+    char qbuf[QTN_SERIALIZED_DATA_MAX];
+    size_t qlen = sizeof(qbuf);
+
+    if (s->qinfo == NULL)
+    {
+       ret = -1;
+       goto done;
+    }
+
+    if (qtn_file_to_data(s->qinfo, qbuf, &qlen) != 0)
+    {
+       ret = -1;
+       goto done;
+    }
+
+    *buf = malloc(qlen);
+    if (*buf)
+    {
+       memcpy(*buf, qbuf, qlen);
+       *len = qlen;
+    }
+done:
+    return ret;
+}
+
+static int copyfile_pack_acl(copyfile_state_t s, void **buf, ssize_t *len)
+{
+    int ret = 0;
+    acl_t acl = NULL;
+    char *acl_text;
+
+    if (filesec_get_property(s->fsec, FILESEC_ACL, &acl) < 0)
+    {
+       if (errno != ENOENT)
+       {
+           ret = -1;
+           if (COPYFILE_VERBOSE & s->flags)
+               copyfile_warn("getting acl");
+       }
+       *len = 0;
+       goto exit;
+    }
+
+    if ((acl_text = acl_to_text(acl, len)) != NULL)
+    {
+       /*
+        * acl_to_text() doesn't include the NUL at the endo
+        * in it's count (*len).  It does, however, promise to
+        * return a valid C string, so we need to up the count
+        * by 1.
+        */
+       *len = *len + 1;
+       *buf = malloc(*len);
+       if (*buf)
+           memcpy(*buf, acl_text, *len);
+       else
+           *len = 0;
+       acl_free(acl_text);
+    }
+    copyfile_debug(2, "copied acl (%ld) %p", *len, *buf);
+exit:
+    if (acl)
+       acl_free(acl);
+    return ret;
+}
+
+static int copyfile_pack_rsrcfork(copyfile_state_t s, attr_header_t *filehdr)
+{
+    ssize_t datasize;
+    char *databuf = NULL;
+    int ret = 0;
+
+    /* Get the resource fork size */
+    if ((datasize = fgetxattr(s->src_fd, XATTR_RESOURCEFORK_NAME, NULL, 0, 0, 0)) < 0)
+    {
+       if (COPYFILE_VERBOSE & s->flags)
+           copyfile_warn("skipping attr \"%s\" due to error %d", XATTR_RESOURCEFORK_NAME, errno);
+       return -1;
+    }
+
+    if (datasize > INT_MAX) {
+       errno = EINVAL;
+       ret = -1;
+       goto done;
+    }
+
+    if ((databuf = malloc(datasize)) == NULL)
+    {
+       copyfile_warn("malloc");
+       ret = -1;
+       goto done;
+    }
+
+    if (fgetxattr(s->src_fd, XATTR_RESOURCEFORK_NAME, databuf, datasize, 0, 0) != datasize)
+    {
+       if (COPYFILE_VERBOSE & s->flags)
+           copyfile_warn("couldn't read entire resource fork");
+       ret = -1;
+       goto done;
+    }
+
+    /* Write the resource fork to disk. */
+    if (pwrite(s->dst_fd, databuf, datasize, filehdr->appledouble.entries[1].offset) != datasize)
+    {
+       if (COPYFILE_VERBOSE & s->flags)
+           copyfile_warn("couldn't write resource fork");
+    }
+    copyfile_debug(3, "copied %d bytes of \"%s\" data @ offset 0x%08x",
+       datasize, XATTR_RESOURCEFORK_NAME, filehdr->appledouble.entries[1].offset);
+    filehdr->appledouble.entries[1].length = (u_int32_t)datasize;
+
+done:
+    if (databuf)
+       free(databuf);
+
+    return ret;
+}
+
+/*
+ * The opposite of copyfile_unpack(), obviously.
+ */
+static int copyfile_pack(copyfile_state_t s)
+{
+    char *attrnamebuf = NULL, *endnamebuf;
+    void *databuf = NULL;
+    attr_header_t *filehdr, *endfilehdr;
+    attr_entry_t *entry;
+    ssize_t listsize = 0;
+    char *nameptr;
+    size_t namelen;
+    size_t entrylen;
+    ssize_t datasize;
+    size_t offset = 0;
+    int hasrsrcfork = 0;
+    int error = 0;
+    int seenq = 0;     // Have we seen any quarantine info already?
+
+    filehdr = (attr_header_t *) calloc(1, ATTR_MAX_SIZE);
+    if (filehdr == NULL) {
+       error = -1;
+       goto exit;
+    } else {
+           endfilehdr = (attr_header_t*)(((char*)filehdr) + ATTR_MAX_SIZE);
+    }
+
+    attrnamebuf = calloc(1, ATTR_MAX_HDR_SIZE);
+    if (attrnamebuf == NULL) {
+       error = -1;
+       goto exit;
+    } else {
+       endnamebuf = ((char*)attrnamebuf) + ATTR_MAX_HDR_SIZE;
+    }
+
+    /*
+     * Fill in the Apple Double Header defaults.
+     */
+    filehdr->appledouble.magic              = ADH_MAGIC;
+    filehdr->appledouble.version            = ADH_VERSION;
+    filehdr->appledouble.numEntries         = 2;
+    filehdr->appledouble.entries[0].type    = AD_FINDERINFO;
+    filehdr->appledouble.entries[0].offset  = (u_int32_t)offsetof(apple_double_header_t, finfo);
+    filehdr->appledouble.entries[0].length  = FINDERINFOSIZE;
+    filehdr->appledouble.entries[1].type    = AD_RESOURCE;
+    filehdr->appledouble.entries[1].offset  = (u_int32_t)offsetof(apple_double_header_t, pad);
+    filehdr->appledouble.entries[1].length  = 0;
+    bcopy(ADH_MACOSX, filehdr->appledouble.filler, sizeof(filehdr->appledouble.filler));
+
+    /*
+     * Fill in the initial Attribute Header.
+     */
+    filehdr->magic       = ATTR_HDR_MAGIC;
+    filehdr->debug_tag   = s->sb.st_ino;
+    filehdr->data_start  = (u_int32_t)sizeof(attr_header_t);
+
+    /*
+     * Collect the attribute names.
+     */
+    entry = (attr_entry_t *)((char *)filehdr + sizeof(attr_header_t));
+
+    /*
+     * Test if there are acls to copy
+     */
+    if (COPYFILE_ACL & s->flags)
+    {
+       acl_t temp_acl = NULL;
+       if (filesec_get_property(s->fsec, FILESEC_ACL, &temp_acl) < 0)
+       {
+           copyfile_debug(2, "no acl entries found (errno = %d)", errno);
+       } else
+       {
+           offset = strlen(XATTR_SECURITY_NAME) + 1;
+           strcpy(attrnamebuf, XATTR_SECURITY_NAME);
+       }
+       if (temp_acl)
+           acl_free(temp_acl);
+    }
+
+    if (COPYFILE_XATTR & s->flags)
+    {
+       ssize_t left = ATTR_MAX_HDR_SIZE - offset;
+        if ((listsize = flistxattr(s->src_fd, attrnamebuf + offset, left, 0)) <= 0)
+       {
+           copyfile_debug(2, "no extended attributes found (%d)", errno);
+       }
+       if (listsize > left)
+       {
+           copyfile_debug(1, "extended attribute list too long");
+           listsize = left;
+       }
+
+       listsize += offset;
+       endnamebuf = attrnamebuf + listsize;
+       if (endnamebuf > (attrnamebuf + ATTR_MAX_HDR_SIZE)) {
+               error = -1;
+               goto exit;
+       }
+
+       for (nameptr = attrnamebuf; nameptr < endnamebuf; nameptr += namelen)
+       {
+           namelen = strlen(nameptr) + 1;
+           /* Skip over FinderInfo or Resource Fork names */
+           if (strcmp(nameptr, XATTR_FINDERINFO_NAME) == 0 ||
+               strcmp(nameptr, XATTR_RESOURCEFORK_NAME) == 0) {
+                   continue;
+           }
+           if (strcmp(nameptr, XATTR_QUARANTINE_NAME) == 0) {
+               seenq = 1;
+           }
+
+           /* The system should prevent this from happening, but... */
+           if (namelen > XATTR_MAXNAMELEN + 1) {
+               namelen = XATTR_MAXNAMELEN + 1;
+           }
+           entry->namelen = namelen;
+           entry->flags = 0;
+           if (nameptr + namelen > endnamebuf) {
+               error = -1;
+               goto exit;
+           }
+           bcopy(nameptr, &entry->name[0], namelen);
+           copyfile_debug(2, "copied name [%s]", entry->name);
+
+           entrylen = ATTR_ENTRY_LENGTH(namelen);
+           entry = (attr_entry_t *)(((char *)entry) + entrylen);
+
+           if ((void*)entry >= (void*)endfilehdr) {
+                   error = -1;
+                   goto exit;
+           }
+
+           /* Update the attributes header. */
+           filehdr->num_attrs++;
+           filehdr->data_start += (u_int32_t)entrylen;
+       }
+    }
+
+    /*
+     * If we have any quarantine data, we always pack it.
+     * But if we've already got it in the EA list, don't put it in again.
+     */
+    if (s->qinfo && !seenq)
+    {
+       ssize_t left = ATTR_MAX_HDR_SIZE - offset;
+       /* strlcpy returns number of bytes copied, but we need offset to point to the next byte */
+       offset += strlcpy(attrnamebuf + offset, XATTR_QUARANTINE_NAME, left) + 1;
+    }
+
+    seenq = 0;
+    /*
+     * Collect the attribute data.
+     */
+    entry = (attr_entry_t *)((char *)filehdr + sizeof(attr_header_t));
+
+    for (nameptr = attrnamebuf; nameptr < attrnamebuf + listsize; nameptr += namelen + 1)
+    {
+       namelen = strlen(nameptr);
+
+       if (strcmp(nameptr, XATTR_SECURITY_NAME) == 0)
+           copyfile_pack_acl(s, &databuf, &datasize);
+       else if (s->qinfo && strcmp(nameptr, XATTR_QUARANTINE_NAME) == 0)
+       {
+           copyfile_pack_quarantine(s, &databuf, &datasize);
+       }
+       /* Check for Finder Info. */
+       else if (strcmp(nameptr, XATTR_FINDERINFO_NAME) == 0)
+       {
+           datasize = fgetxattr(s->src_fd, nameptr, (u_int8_t*)filehdr + filehdr->appledouble.entries[0].offset, 32, 0, 0);
+           if (datasize < 0)
+           {
+                   if (COPYFILE_VERBOSE & s->flags)
+                       copyfile_warn("skipping attr \"%s\" due to error %d", nameptr, errno);
+           } else if (datasize != 32)
+           {
+                   if (COPYFILE_VERBOSE & s->flags)
+                       copyfile_warn("unexpected size (%ld) for \"%s\"", datasize, nameptr);
+           } else
+           {
+                   if (COPYFILE_VERBOSE & s->flags)
+                       copyfile_warn(" copied 32 bytes of \"%s\" data @ offset 0x%08x",
+                           XATTR_FINDERINFO_NAME, filehdr->appledouble.entries[0].offset);
+           }
+           continue;  /* finder info doesn't have an attribute entry */
+       }
+       /* Check for Resource Fork. */
+       else if (strcmp(nameptr, XATTR_RESOURCEFORK_NAME) == 0)
+       {
+           hasrsrcfork = 1;
+           continue;
+       } else
+       {
+           /* Just a normal attribute. */
+           datasize = fgetxattr(s->src_fd, nameptr, NULL, 0, 0, 0);
+           if (datasize == 0)
+               goto next;
+           if (datasize < 0)
+           {
+               if (COPYFILE_VERBOSE & s->flags)
+                   copyfile_warn("skipping attr \"%s\" due to error %d", nameptr, errno);
+               goto next;
+           }
+           if (datasize > XATTR_MAXATTRLEN)
+           {
+               if (COPYFILE_VERBOSE & s->flags)
+                   copyfile_warn("skipping attr \"%s\" (too big)", nameptr);
+               goto next;
+           }
+           databuf = malloc(datasize);
+           if (databuf == NULL) {
+               error = -1;
+               continue;
+           }
+           datasize = fgetxattr(s->src_fd, nameptr, databuf, datasize, 0, 0);
+       }
+
+       entry->length = (u_int32_t)datasize;
+       entry->offset = filehdr->data_start + filehdr->data_length;
+
+       filehdr->data_length += (u_int32_t)datasize;
+       /*
+        * >>>  WARNING <<<
+        * This assumes that the data is fits in memory (not
+        * the case when there are lots of attributes or one of
+        * the attributes is very large.
+        */
+       if (entry->offset > ATTR_MAX_SIZE ||
+               (entry->offset + datasize > ATTR_MAX_SIZE)) {
+               error = 1;
+       } else {
+               bcopy(databuf, (char*)filehdr + entry->offset, datasize);
+       }
+       free(databuf);
+
+       copyfile_debug(3, "copied %ld bytes of \"%s\" data @ offset 0x%08x", datasize, nameptr, entry->offset);
+next:
+       /* bump to next entry */
+       entrylen = ATTR_ENTRY_LENGTH(entry->namelen);
+       entry = (attr_entry_t *)((char *)entry + entrylen);
+    }
+
+    if (filehdr->data_length > 0)
+    {
+       /* Now we know where the resource fork data starts. */
+       filehdr->appledouble.entries[1].offset = (filehdr->data_start + filehdr->data_length);
+
+       /* We also know the size of the "Finder Info entry. */
+       filehdr->appledouble.entries[0].length =
+           filehdr->appledouble.entries[1].offset - filehdr->appledouble.entries[0].offset;
+
+       filehdr->total_size  = filehdr->appledouble.entries[1].offset;
+    }
+
+    /* Copy Resource Fork. */
+    if (hasrsrcfork && (error = copyfile_pack_rsrcfork(s, filehdr)))
+       goto exit;
+
+    /* Write the header to disk. */
+    datasize = filehdr->appledouble.entries[1].offset;
+
+    swap_adhdr(&filehdr->appledouble);
+    swap_attrhdr(filehdr);
+
+    if (pwrite(s->dst_fd, filehdr, datasize, 0) != datasize)
+    {
+       if (COPYFILE_VERBOSE & s->flags)
+           copyfile_warn("couldn't write file header");
+       error = -1;
+       goto exit;
+    }
+exit:
+    if (filehdr) free(filehdr);
+    if (attrnamebuf) free(attrnamebuf);
+
+    if (error)
+       return error;
+    else
+       return copyfile_stat(s);
+}
diff --git a/copyfile.h b/copyfile.h
new file mode 100644 (file)
index 0000000..8a0c6ad
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+#ifndef _COPYFILE_H_ /* version 0.1 */
+#define _COPYFILE_H_
+
+/*
+ * this is an API to faciliatate copying of files and their
+ * associated metadata.  There are several open source projects that
+ * need modifications to support preserving extended attributes and
+ * acls and this API collapses several hundred lines of modifications
+ * into one or two calls.
+ *
+ * This implementation is incomplete and the interface may change in a 
+ * future release.
+ */
+
+/* private */
+#include <sys/cdefs.h>
+#include <stdint.h>
+
+__BEGIN_DECLS
+struct _copyfile_state;
+typedef struct _copyfile_state * copyfile_state_t;
+typedef uint32_t copyfile_flags_t;
+
+/* public */
+
+/* receives:
+ *   from      path to source file system object
+ *   to                path to destination file system object
+ *   state     opaque blob for future extensibility
+ *             Must be NULL in current implementation
+ *   flags     (described below)
+ * returns:
+ *   int       negative for error
+ */
+
+int copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags);
+int fcopyfile(int from_fd, int to_fd, copyfile_state_t, copyfile_flags_t flags);
+
+int copyfile_state_free(copyfile_state_t);
+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);
+
+#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_DISABLE_VAR    "COPYFILE_DISABLE"
+
+/* flags for copyfile */
+
+#define COPYFILE_ACL       (1<<0)
+#define COPYFILE_STAT      (1<<1)
+#define COPYFILE_XATTR     (1<<2)
+#define COPYFILE_DATA      (1<<3)
+
+#define COPYFILE_SECURITY   (COPYFILE_STAT | COPYFILE_ACL)
+#define COPYFILE_METADATA   (COPYFILE_SECURITY | COPYFILE_XATTR)
+#define COPYFILE_ALL       (COPYFILE_METADATA | COPYFILE_DATA)
+
+#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 */
+#define COPYFILE_NOFOLLOW_DST  (1<<19) /* don't follow if dst is a symlink */
+#define COPYFILE_MOVE          (1<<20) /* unlink src after copy */
+#define COPYFILE_UNLINK                (1<<21) /* unlink dst before copy */
+#define COPYFILE_NOFOLLOW      (COPYFILE_NOFOLLOW_SRC | COPYFILE_NOFOLLOW_DST)
+
+#define COPYFILE_PACK          (1<<22)
+#define COPYFILE_UNPACK                (1<<23)
+
+#define COPYFILE_VERBOSE       (1<<30)
+
+__END_DECLS
+
+#endif /* _COPYFILE_H_ */