--- /dev/null
+/*
+ * 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);
+}