]> git.saurik.com Git - apple/libc.git/blobdiff - darwin/copyfile.c
Libc-391.tar.gz
[apple/libc.git] / darwin / copyfile.c
diff --git a/darwin/copyfile.c b/darwin/copyfile.c
new file mode 100644 (file)
index 0000000..bb28d55
--- /dev/null
@@ -0,0 +1,1408 @@
+/*
+ * 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/xattr.h>
+#include <sys/syscall.h>
+#include <sys/param.h>
+#include <sys/acl.h>
+#include <libkern/OSByteOrder.h>
+#include <membership.h>
+
+#include <copyfile.h>
+
+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;
+};
+
+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 int copyfile_fix_perms(copyfile_state_t, filesec_t *, int);
+
+#define COPYFILE_DEBUG (1<<31)
+
+#ifndef _COPYFILE_TEST
+# define copyfile_warn(str, ...) syslog(LOG_WARNING, str ": %m", ## __VA_ARGS__)
+# define copyfile_debug(d, str, ...) \
+    if (s && (d <= s->debug)) {\
+       syslog(LOG_DEBUG, "%s:%d:%s() " str "\n", __FILE__, __LINE__ , __FUNCTION__, ## __VA_ARGS__); \
+    } else
+#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, ...) \
+    if (s && (d <= s->debug)) {\
+       fprintf(stderr, "%s:%d:%s() " str "\n", __FILE__, __LINE__ , __FUNCTION__, ## __VA_ARGS__); \
+    } else
+#endif
+
+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;
+    int fix_perms = 0;
+
+    original_fsec = filesec_init();
+    if (s == NULL && (s = copyfile_init()) == NULL)
+       return -1;
+
+    if (src != NULL)
+    {
+       if (s->src_fd != -2 && s->src && !strncmp(src, s->src, MAXPATHLEN))
+           close(s->src_fd);
+       s->src = strdup(src);
+    }
+
+    if (dst != NULL)
+    {
+       if (s->dst_fd != -2 && s->dst && !strncmp(dst, s->dst, MAXPATHLEN))
+           close(s->dst_fd);
+       s->dst = strdup(dst);
+    }
+
+    s->flags = flags;
+
+    if (COPYFILE_DEBUG & s->flags)
+    {
+       char *e;
+       if ((e = getenv("COPYFILE_DEBUG")))
+       {
+           s->debug = strtol(e, NULL, 0);
+           if (s->debug < 1)
+               s->debug = 1;
+       }
+       copyfile_debug(1, "debug value set to: %d\n", s->debug);
+    }
+
+    if (COPYFILE_CHECK & flags)
+       return copyfile_check(s);
+
+    if (copyfile_open(s) < 0)
+       ret = -1;
+    else
+    {
+       if (s->dst_fd == -2 || s->src_fd == -2)
+           return 0;
+
+       if (COPYFILE_PACK & flags)
+       {
+           if (copyfile_pack(s) < 0)
+           {
+               unlink(s->dst);
+               ret = -1;
+           }
+       } else if (COPYFILE_UNPACK & flags)
+       {
+           if (copyfile_unpack(s) < 0)
+               ret = -1;
+       } else
+       {
+           if (COPYFILE_SECURITY & flags)
+           {
+               if (copyfile_security(s) < 0)
+               {
+                   copyfile_warn("error processing security information");
+                   ret -= 1;
+               }
+           } else if (COPYFILE_UNPACK & flags)
+           {
+               fix_perms = !copyfile_fix_perms(s, &original_fsec, 1);
+               if (copyfile_unpack(s) < 0)
+                   ret = -1;
+           } else
+           {
+               if (COPYFILE_SECURITY & flags)
+               {
+                   copyfile_warn("error processing stat information");
+                   ret -= 1;
+               }
+           }
+           fix_perms = !copyfile_fix_perms(s, &original_fsec, 1);
+
+           if (COPYFILE_XATTR & flags)
+           {
+               if (copyfile_xattr(s) < 0)
+               {
+                   copyfile_warn("error processing extended attributes");
+                   ret -= 1;
+               }
+           }
+           if (COPYFILE_DATA & flags)
+           {
+               if (copyfile_data(s) < 0)
+               {
+                   copyfile_warn("error processing data");
+                   ret = -1;
+                   if (s->dst && unlink(s->dst))
+                           copyfile_warn("%s: remove", s->src);
+                   goto exit;
+               }
+           }
+       }
+    }
+exit:
+    if (fix_perms)
+       copyfile_fix_perms(s, &original_fsec, 0);
+
+    filesec_free(original_fsec);
+
+    if (state == NULL)
+       ret -= copyfile_free(s);
+    return ret;
+}
+
+copyfile_state_t copyfile_init(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();
+    }
+
+    return s;
+}
+
+int copyfile_free(copyfile_state_t s)
+{
+    if (s != NULL)
+    {
+       if (s->fsec)
+           filesec_free(s->fsec);
+
+       if (s->dst)
+           free(s->dst);
+       if (s->src)
+           free(s->src);
+       if (copyfile_close(s) < 0)
+       {
+           copyfile_warn("error closing files");
+           return -1;
+       }
+       free(s);
+    }
+    return 0;
+}
+
+static int copyfile_close(copyfile_state_t s)
+{
+    if (s->src_fd != -2)
+       close(s->src_fd);
+
+    if (s->dst_fd != -2 && close(s->dst_fd))
+    {
+       copyfile_warn("close on %s", s->dst);
+       return -1;
+    }
+    return 0;
+}
+
+static int copyfile_fix_perms(copyfile_state_t s, filesec_t *fsec, int on)
+{
+    filesec_t tmp_fsec;
+    struct stat sb;
+    mode_t mode;
+    acl_t acl;
+
+    if (on)
+    {
+       if(statx_np(s->dst, &sb, *fsec))
+           goto error;
+
+       tmp_fsec = filesec_dup(*fsec);
+
+       if (!filesec_get_property(tmp_fsec, FILESEC_ACL, &acl))
+       {
+           acl_entry_t entry;
+           acl_permset_t permset;
+           uuid_t qual;
+
+           if (mbr_uid_to_uuid(getuid(), qual) != 0)
+               goto error;
+
+           if (acl_create_entry_np(&acl, &entry, ACL_FIRST_ENTRY) == -1)
+               goto error;
+           if (acl_get_permset(entry, &permset) == -1)
+               goto error;
+           if (acl_clear_perms(permset) == -1)
+               goto error;
+           if (acl_add_perm(permset, ACL_WRITE_DATA) == -1)
+               goto error;
+           if (acl_add_perm(permset, ACL_WRITE_ATTRIBUTES) == -1)
+               goto error;
+           if (acl_add_perm(permset, ACL_WRITE_EXTATTRIBUTES) == -1)
+               goto error;
+           if (acl_set_tag_type(entry, ACL_EXTENDED_ALLOW) == -1)
+               goto error;
+
+           if(acl_set_permset(entry, permset) == -1)
+               goto error;
+           if(acl_set_qualifier(entry, qual) == -1)
+               goto error;
+
+           if (filesec_set_property(tmp_fsec, FILESEC_ACL, &acl) != 0)
+               goto error;
+       }
+
+       if (filesec_get_property(tmp_fsec, FILESEC_MODE, &mode) == 0)
+       {
+           if (~mode & S_IWUSR)
+           {
+               mode |= S_IWUSR;
+               if (filesec_set_property(tmp_fsec, FILESEC_MODE, &mode) != 0)
+                   goto error;
+           }
+       }
+       if (fchmodx_np(s->dst_fd, tmp_fsec) < 0 && errno != ENOTSUP)
+           copyfile_warn("setting security information");
+       filesec_free(tmp_fsec);
+    } else
+       if (fchmodx_np(s->dst_fd, *fsec) < 0 && errno != ENOTSUP)
+           copyfile_warn("setting security information");
+
+    return 0;
+error:
+    filesec_free(*fsec);
+    return -1;
+}
+
+
+static int copyfile_open(copyfile_state_t s)
+{
+    int oflags = O_EXCL | O_CREAT;
+
+    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;
+       }
+       if ((s->src_fd = open(s->src, O_RDONLY, 0)) < 0)
+       {
+               copyfile_warn("open on %s", s->src);
+               return -1;
+       }
+    }
+
+    if (s->dst && s->dst_fd == -2)
+    {
+       if (COPYFILE_DATA & s->flags || COPYFILE_PACK & s->flags)
+           oflags |= O_WRONLY;
+
+       if (COPYFILE_ACL & ~s->flags)
+       {
+           if (filesec_set_property(s->fsec, FILESEC_ACL, NULL) == -1)
+           {
+               copyfile_debug(1, "unsetting acl attribute on %s", s->dst);
+           }
+       }
+       if (COPYFILE_STAT & ~s->flags)
+       {
+           if (filesec_set_property(s->fsec, FILESEC_MODE, NULL) == -1)
+           {
+               copyfile_debug(1, "unsetting mode attribute on %s", s->dst);
+           }
+       }
+       if (COPYFILE_PACK & s->flags)
+       {
+           mode_t m = S_IRUSR;
+           if (filesec_set_property(s->fsec, FILESEC_MODE, &m) == -1)
+           {
+               mode_t m = S_IRUSR | S_IWUSR;
+               if (filesec_set_property(s->fsec, FILESEC_MODE, &m) == -1)
+               {
+                   copyfile_debug(1, "setting mode attribute on %s", s->dst);
+               }
+           }
+           if (filesec_set_property(s->fsec, FILESEC_OWNER, NULL) == -1)
+           {
+               copyfile_debug(1, "unsetting uid attribute on %s", s->dst);
+           }
+           if (filesec_set_property(s->fsec, FILESEC_UUID, NULL) == -1)
+           {
+               copyfile_debug(1, "unsetting uuid attribute on %s", s->dst);
+           }
+           if (filesec_set_property(s->fsec, FILESEC_GROUP, NULL) == -1)
+           {
+               copyfile_debug(1, "unsetting gid attribute on %s", s->dst);
+           }
+       }
+
+       if (COPYFILE_UNLINK & s->flags && unlink(s->dst) < 0)
+       {
+           copyfile_warn("%s: remove", s->dst);
+           return -1;
+       }
+
+       while((s->dst_fd = openx_np(s->dst, oflags, s->fsec)) < 0)
+       {
+           if (EEXIST == errno)
+           {
+               oflags = oflags & ~O_CREAT;
+               continue;
+           }
+           copyfile_warn("open on %s", s->dst);
+           return -1;
+       }
+    }
+    return 0;
+}
+
+static copyfile_flags_t copyfile_check(copyfile_state_t s)
+{
+    acl_t acl;
+    copyfile_flags_t ret = 0;
+
+    if (!s->src)
+       return ret;
+
+    // check EAs
+    if (COPYFILE_XATTR & s->flags)
+       if (listxattr(s->src, 0, 0, 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;
+    }
+
+    return ret;
+}
+
+static int copyfile_data(copyfile_state_t s)
+{
+    unsigned int blen;
+    char *bp;
+    int nread;
+    int ret;
+
+    if ((bp = malloc((size_t)s->sb.st_blksize)) == NULL)
+    {
+       blen = 0;
+       warnx("malloc failed");
+       return -1;
+    }
+    blen = s->sb.st_blksize;
+
+    while ((nread = read(s->src_fd, bp, (size_t)blen)) > 0)
+    {
+       if (write(s->dst_fd, bp, (size_t)nread) != nread)
+       {
+           copyfile_warn("writing to %s", s->dst);
+           return -1;
+       }
+    }
+    if (nread < 0)
+    {
+       copyfile_warn("reading from %s", s->src);
+       ret = -1;
+    }
+
+    free(bp);
+
+    if (ftruncate(s->dst_fd, s->sb.st_size) < 0)
+       ret = -1;
+
+    return ret;
+}
+
+static int copyfile_security(copyfile_state_t s)
+{
+    filesec_t fsec_dst = filesec_init();
+
+    int copied = 0;
+    acl_flagset_t flags;
+    struct stat sb;
+    acl_entry_t entry_src = NULL, entry_dst = NULL;
+    acl_t acl_src, acl_dst;
+    int inited_dst = 0, inited_src = 0, ret = 0;
+
+    if (COPYFILE_ACL & s->flags)
+    {
+       if(fstatx_np(s->dst_fd, &sb, fsec_dst))
+       {
+           goto cleanup;
+       }
+
+       if (filesec_get_property(fsec_dst, FILESEC_ACL, &acl_dst))
+       {
+           if (errno == ENOENT)
+           {
+               acl_dst = acl_init(4);
+               inited_dst = 1;
+           }
+           else
+           {
+               ret = -1;
+               goto cleanup;
+           }
+       }
+
+       if (filesec_get_property(s->fsec, FILESEC_ACL, &acl_src))
+       {
+           if (errno == ENOENT)
+           {
+               if (inited_dst)
+                   goto no_acl;
+               acl_dst = acl_init(4);
+               inited_src = 1;
+           }
+           else
+           {
+               ret = -1;
+               goto cleanup;
+           }
+       }
+
+       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 cleanup;
+
+               if ((ret = acl_copy_entry(entry_dst, entry_src)) == -1)
+                   goto cleanup;
+
+               copyfile_debug(1, "copied acl entry from %s to %s", s->src, s->dst);
+               copied++;
+           }
+       }
+
+       if (!filesec_set_property(s->fsec, FILESEC_ACL, &acl_dst))
+       {
+           copyfile_debug(1, "altered acl");
+       }
+    }
+no_acl:
+    if (fchmodx_np(s->dst_fd, s->fsec) < 0 && errno != ENOTSUP)
+       copyfile_warn("setting security information: %s", s->dst);
+
+cleanup:
+    filesec_free(fsec_dst);
+    if (inited_src) acl_free(acl_src);
+    if (inited_dst) acl_free(acl_dst);
+
+    return ret;
+}
+
+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 (chflags(s->dst, (u_long)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);
+
+    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 (utimes(s->dst, tval))
+           copyfile_warn("%s: set times", s->dst);
+    return 0;
+}
+
+static int copyfile_xattr(copyfile_state_t s)
+{
+    char *name;
+    char *namebuf;
+    size_t xa_size;
+    void *xa_dataptr;
+    size_t bufsize = 4096;
+    ssize_t asize;
+    ssize_t nsize;
+    int ret = 0;
+    int flags = 0;
+
+    if (COPYFILE_NOFOLLOW_SRC & s->flags)
+       flags |= XATTR_NOFOLLOW;
+
+    /* 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)
+           for (name = namebuf; name < namebuf + nsize; name += strlen(name) + 1)
+                   fremovexattr(s->dst_fd, name,flags);
+
+       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)
+       return nsize;
+
+    if ((xa_dataptr = (void *) malloc(bufsize)) == NULL)
+       return -1;
+
+    for (name = namebuf; name < namebuf + nsize; name += strlen(name) + 1)
+    {
+       if ((xa_size = fgetxattr(s->src_fd, name, 0, 0, 0, flags)) < 0)
+       {
+           ret = -1;
+           continue;
+       }
+
+       if (xa_size > bufsize)
+       {
+           bufsize = xa_size;
+           if ((xa_dataptr =
+               (void *) realloc((void *) xa_dataptr, bufsize)) == NULL)
+           {
+               ret = -1;
+               continue;
+           }
+       }
+
+       if ((asize = fgetxattr(s->src_fd, name, xa_dataptr, xa_size, 0, flags)) < 0)
+       {
+           ret = -1;
+           continue;
+       }
+
+       if (xa_size != asize)
+           xa_size = asize;
+
+       if (fsetxattr(s->dst_fd, name, xa_dataptr, xa_size, 0, flags) < 0)
+       {
+           ret = -1;
+           continue;
+       }
+    }
+    free((void *) xa_dataptr);
+    return ret;
+}
+
+
+#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.
+ */
+
+
+#pragma options align=mac68k
+
+#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. */
+} 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 */
+} 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) */
+} 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;
+} attr_header_t;
+
+
+#pragma options align=reset
+
+#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);
+       }
+#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++, ae++)
+       {
+               ae->offset = SWAP32 (ae->offset);
+               ae->length = SWAP32 (ae->length);
+               ae->flags  = SWAP16 (ae->flags);
+       }
+#endif
+}
+
+static u_int32_t emptyfinfo[8] = {0};
+
+static int copyfile_unpack(copyfile_state_t s)
+{
+    int bytes;
+    void * buffer;
+    apple_double_header_t *adhdr;
+    size_t hdrsize;
+    int error = 0;
+
+    if (s->sb.st_size < ATTR_MAX_HDR_SIZE)
+       hdrsize = s->sb.st_size;
+    else
+       hdrsize = ATTR_MAX_HDR_SIZE;
+
+    buffer = calloc(1, 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 (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);
+
+    /*
+     * 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;
+
+       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;
+
+           copyfile_debug(2, "extracting \"%s\" (%d bytes)",
+               entry->name, entry->length);
+           dataptr = (char *)attrhdr + entry->offset;
+
+           if (COPYFILE_ACL & s->flags && strncmp(entry->name, XATTR_SECURITY_NAME, strlen(XATTR_SECURITY_NAME)) == 0)
+           {
+               acl_t acl;
+               if ((acl = acl_from_text(dataptr)) != NULL)
+               {
+                   if (filesec_set_property(s->fsec, FILESEC_ACL, &acl) < 0)
+                   {
+                           acl_t acl;
+                           if ((acl = acl_from_text(dataptr)) != NULL)
+                           {
+                               if (filesec_set_property(s->fsec, FILESEC_ACL, &acl) < 0)
+                               {
+                                   copyfile_debug(1, "setting acl");
+                               }
+                               else if (fchmodx_np(s->dst_fd, s->fsec) < 0 && errno != ENOTSUP)
+                                       copyfile_warn("setting security information");
+                               acl_free(acl);
+                           }
+                   } else
+                   if (COPYFILE_XATTR & s->flags && (fsetxattr(s->dst_fd, entry->name, dataptr, entry->length, 0, 0))) {
+                           if (COPYFILE_VERBOSE & s->flags)
+                                   copyfile_warn("error %d setting attribute %s", error, entry->name);
+                           goto exit;
+                   }
+                   else if (fchmodx_np(s->dst_fd, s->fsec) < 0 && errno != ENOTSUP)
+                           copyfile_warn("setting security information");
+                   acl_free(acl);
+               }
+           } else
+           if (COPYFILE_XATTR & s->flags && (fsetxattr(s->dst_fd, 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 (bcmp((u_int8_t*)buffer + adhdr->entries[0].offset, emptyfinfo, sizeof(emptyfinfo)) != 0)
+    {
+       copyfile_debug(1, " 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;
+       size_t length;
+       off_t offset;
+
+       length = adhdr->entries[1].length;
+       offset = adhdr->entries[1].offset;
+       rsrcforkdata = malloc(length);
+
+       bytes = pread(s->src_fd, rsrcforkdata, length, offset);
+       if (bytes < 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 exit;
+       }
+       error = fsetxattr(s->dst_fd, XATTR_RESOURCEFORK_NAME, rsrcforkdata, bytes, 0, 0);
+       if (error)
+       {
+           copyfile_debug(1, "error %d setting resource fork attribute", error);
+           error = -1;
+           goto exit;
+       }
+           copyfile_debug(1, "extracting \"%s\" (%d bytes)",
+                   XATTR_RESOURCEFORK_NAME, (int)length);
+       free(rsrcforkdata);
+    }
+exit:
+    free(buffer);
+    return error;
+}
+
+static int copyfile_pack_acl(copyfile_state_t s, void **buf, ssize_t *len)
+{
+    int ret = 0;
+    acl_t acl;
+    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");
+       }
+       goto err;
+    }
+
+    if ((acl_text = acl_to_text(acl, len)) != NULL)
+    {
+       *buf = malloc(*len);
+       memcpy(*buf, acl_text, *len);
+       acl_free(acl_text);
+    }
+    copyfile_debug(1, "copied acl (%ld) %p", *len, *buf);
+err:
+    return ret;
+}
+
+static int copyfile_pack_rsrcfork(copyfile_state_t s, attr_header_t *filehdr)
+{
+    int datasize;
+    char *databuf;
+
+    /* 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 ((databuf = malloc(datasize)) == NULL)
+    {
+       copyfile_warn("malloc");
+       return -1;
+    }
+
+    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");
+       return -1;
+    }
+
+    /* 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(1, "copied %d bytes of \"%s\" data @ offset 0x%08x",
+       datasize, XATTR_RESOURCEFORK_NAME, filehdr->appledouble.entries[1].offset);
+    filehdr->appledouble.entries[1].length = datasize;
+    free(databuf);
+
+    return 0;
+}
+
+
+static int copyfile_pack(copyfile_state_t s)
+{
+    char *attrnamebuf;
+    void *databuf;
+    attr_header_t *filehdr;
+    attr_entry_t *entry;
+    ssize_t listsize;
+    char *nameptr;
+    int namelen;
+    int entrylen;
+    ssize_t datasize;
+    int offset = 0;
+    int hasrsrcfork = 0;
+    int error = 0;
+
+    filehdr = (attr_header_t *) calloc(1, ATTR_MAX_SIZE);
+    attrnamebuf = calloc(1, ATTR_MAX_HDR_SIZE);
+
+    /*
+     * Fill in the Apple Double Header defaults.
+     */
+    filehdr->appledouble.magic              = SWAP32 (ADH_MAGIC);
+    filehdr->appledouble.version            = SWAP32 (ADH_VERSION);
+    filehdr->appledouble.numEntries         = SWAP16 (2);
+    filehdr->appledouble.entries[0].type    = SWAP32 (AD_FINDERINFO);
+    filehdr->appledouble.entries[0].offset  = SWAP32 (offsetof(apple_double_header_t, finfo));
+    filehdr->appledouble.entries[0].length  = SWAP32 (FINDERINFOSIZE);
+    filehdr->appledouble.entries[1].type    = SWAP32 (AD_RESOURCE);
+    filehdr->appledouble.entries[1].offset  = SWAP32 (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       = SWAP32 (ATTR_HDR_MAGIC);
+    filehdr->debug_tag   = SWAP32 (s->sb.st_ino);
+    filehdr->data_start  = SWAP32 (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)
+    {
+       if (filesec_get_property(s->fsec, FILESEC_ACL, &datasize) < 0)
+       {
+           copyfile_debug(1, "no acl entries found (%d)", datasize < 0 ? errno : 0);
+       } else
+       {
+           offset = strlen(XATTR_SECURITY_NAME) + 1;
+           strcpy(attrnamebuf, XATTR_SECURITY_NAME);
+       }
+    }
+
+    if (COPYFILE_XATTR & s->flags)
+    {
+        if ((listsize = flistxattr(s->src_fd, attrnamebuf + offset, ATTR_MAX_HDR_SIZE, 0)) <= 0)
+       {
+           copyfile_debug(1, "no extended attributes found (%d)", errno);
+       }
+       if (listsize > ATTR_MAX_HDR_SIZE)
+       {
+           copyfile_debug(1, "extended attribute list too long");
+           listsize = ATTR_MAX_HDR_SIZE;
+       }
+
+       listsize += offset;
+
+       for (nameptr = attrnamebuf; nameptr < attrnamebuf + listsize; nameptr += namelen)
+       {
+           namelen = strlen(nameptr) + 1;
+           /* Skip over FinderInfo or Resource Fork names */
+           if (strncmp(nameptr, XATTR_FINDERINFO_NAME, strlen(XATTR_FINDERINFO_NAME)) == 0 ||
+               strncmp(nameptr, XATTR_RESOURCEFORK_NAME, strlen(XATTR_RESOURCEFORK_NAME)) == 0)
+                   continue;
+
+           entry->namelen = namelen;
+           entry->flags = 0;
+           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);
+           
+           /* Update the attributes header. */
+           filehdr->num_attrs++;
+           filehdr->data_start += entrylen;
+       }
+    }
+
+    /*
+     * Collect the attribute data.
+     */
+    entry = (attr_entry_t *)((char *)filehdr + sizeof(attr_header_t));
+
+    for (nameptr = attrnamebuf; nameptr < attrnamebuf + listsize; nameptr += namelen + 1)
+    {
+       nameptr = nameptr;
+       namelen = strlen(nameptr);
+
+       if (strncmp(nameptr, XATTR_SECURITY_NAME, strlen(XATTR_SECURITY_NAME)) == 0)
+           copyfile_pack_acl(s, &databuf, &datasize);
+       else
+       /* Check for Finder Info. */
+       if (strncmp(nameptr, XATTR_FINDERINFO_NAME, strlen(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 */
+       } else
+       /* Check for Resource Fork. */
+       if (strncmp(nameptr, XATTR_RESOURCEFORK_NAME, strlen(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);
+           datasize = fgetxattr(s->src_fd, nameptr, databuf, datasize, 0, 0);
+       }
+
+       entry->length = datasize;
+       entry->offset = filehdr->data_start + filehdr->data_length;
+
+       filehdr->data_length += 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.
+        */
+       bcopy(databuf, (char*)filehdr + entry->offset, datasize);
+       free(databuf);
+
+       copyfile_debug(1, "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  = SWAP32 (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;
+
+    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:
+    free(filehdr);
+    free(attrnamebuf);
+
+    if (error)
+       return error;
+    else
+       return copyfile_stat(s);
+}