#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_STAT & flags || COPYFILE_ACL & flags))
fix_perms = !copyfile_fix_perms(s, &original_fsec, 1);
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;
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];
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;
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;
}
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
#define offsetof(type, member) ((size_t)(&((type *)0)->member))
#define XATTR_MAXATTRLEN (4*1024)
#define ADH_MAGIC 0x00051607
#define ADH_VERSION 0x00020000
#define ADH_MACOSX "Mac OS X "
#define AD_DATA 1
#define AD_RESOURCE 2
#define AD_REALNAME 3
#define AD_COMMENT 4
#define AD_ICONBW 5
#define AD_ICONCOLOR 6
#define AD_UNUSED 7
#define AD_FILEDATES 8
#define AD_FINDERINFO 9
#define AD_MACINFO 10
#define AD_PRODOSINFO 11
#define AD_MSDOSINFO 12
#define AD_AFPNAME 13
#define AD_AFPINFO 14
#define AD_AFPDIRID 15
#define AD_ATTRIBUTES AD_FINDERINFO
#define ATTR_FILE_PREFIX "._"
#define ATTR_HDR_MAGIC 0x41545452
#define ATTR_BUF_SIZE 4096
#define ATTR_MAX_SIZE (128*1024)
#define ATTR_MAX_NAME_LEN 128
#define ATTR_MAX_HDR_SIZE (65536+18)
#pragma options align=mac68k
#define FINDERINFOSIZE 32
typedef struct apple_double_entry
{
u_int32_t type;
u_int32_t offset;
u_int32_t length;
} apple_double_entry_t;
typedef struct apple_double_header
{
u_int32_t magic;
u_int32_t version;
u_int32_t filler[4];
u_int16_t numEntries;
apple_double_entry_t entries[2];
u_int8_t finfo[FINDERINFOSIZE];
u_int8_t pad[2];
} apple_double_header_t;
typedef struct attr_entry
{
u_int32_t offset;
u_int32_t length;
u_int16_t flags;
u_int8_t namelen;
u_int8_t name[1];
} attr_entry_t;
typedef struct attr_header
{
apple_double_header_t appledouble;
u_int32_t magic;
u_int32_t debug_tag;
u_int32_t total_size;
u_int32_t data_start;
u_int32_t data_length;
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
#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"
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
}
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;
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);
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);
}
}
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;
}
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;
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;
}
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);
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));
filehdr->magic = SWAP32 (ATTR_HDR_MAGIC);
filehdr->debug_tag = SWAP32 (s->sb.st_ino);
filehdr->data_start = SWAP32 (sizeof(attr_header_t));
entry = (attr_entry_t *)((char *)filehdr + sizeof(attr_header_t));
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;
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);
filehdr->num_attrs++;
filehdr->data_start += entrylen;
}
}
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
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;
} else
if (strncmp(nameptr, XATTR_RESOURCEFORK_NAME, strlen(XATTR_RESOURCEFORK_NAME)) == 0)
{
hasrsrcfork = 1;
continue;
} else
{
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;
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:
entrylen = ATTR_ENTRY_LENGTH(entry->namelen);
entry = (attr_entry_t *)((char *)entry + entrylen);
}
if (filehdr->data_length > 0)
{
filehdr->appledouble.entries[1].offset = (filehdr->data_start + filehdr->data_length);
filehdr->appledouble.entries[0].length =
filehdr->appledouble.entries[1].offset - filehdr->appledouble.entries[0].offset;
filehdr->total_size = SWAP32 (filehdr->appledouble.entries[1].offset);
}
if (hasrsrcfork && (error = copyfile_pack_rsrcfork(s, filehdr)))
goto exit;
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);
}