#include <err.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/acl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <syslog.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/errno.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/xattr.h>
#include <sys/syscall.h>
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/acl.h>
#include <libkern/OSByteOrder.h>
#include <membership.h>
#include <TargetConditionals.h>
#if !TARGET_OS_EMBEDDED
#include <quarantine.h>
#define XATTR_QUARANTINE_NAME qtn_xattr_name
#else
#define qtn_file_t void *
#define QTN_SERIALIZED_DATA_MAX 0
static void * qtn_file_alloc(void) { return NULL; }
static int qtn_file_init_with_fd(void *x, int y) { return -1; }
static void qtn_file_free(void *x) { return; }
static int qtn_file_apply_to_fd(void *x, int y) { return -1; }
static char *qtn_error(int x) { return NULL; }
static int qtn_file_to_data(void *x, char *y, size_t z) { return -1; }
static void *qtn_file_clone(void *x) { return NULL; }
#define XATTR_QUARANTINE_NAME "figgledidiggledy"
#endif
#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;
void *callbacks;
qtn_file_t qinfo;
};
static int copyfile_open (copyfile_state_t);
static int copyfile_close (copyfile_state_t);
static int copyfile_data (copyfile_state_t);
static int copyfile_stat (copyfile_state_t);
static int copyfile_security (copyfile_state_t);
static int copyfile_xattr (copyfile_state_t);
static int copyfile_pack (copyfile_state_t);
static int copyfile_unpack (copyfile_state_t);
static copyfile_flags_t copyfile_check (copyfile_state_t);
static filesec_t copyfile_fix_perms(copyfile_state_t, filesec_t *);
static int copyfile_preamble(copyfile_state_t *s, copyfile_flags_t flags);
static int copyfile_internal(copyfile_state_t state, copyfile_flags_t flags);
static int copyfile_unset_posix_fsec(filesec_t);
static int copyfile_quarantine(copyfile_state_t);
#define COPYFILE_DEBUG (1<<31)
#define COPYFILE_DEBUG_VAR "COPYFILE_DEBUG"
#ifndef _COPYFILE_TEST
# define copyfile_warn(str, ...) syslog(LOG_WARNING, str ": %m", ## __VA_ARGS__)
# define copyfile_debug(d, str, ...) \
do { \
if (s && (d <= s->debug)) {\
syslog(LOG_DEBUG, "%s:%d:%s() " str "\n", __FILE__, __LINE__ , __FUNCTION__, ## __VA_ARGS__); \
} \
} while (0)
#else
#define copyfile_warn(str, ...) \
fprintf(stderr, "%s:%d:%s() " str ": %s\n", __FILE__, __LINE__ , __FUNCTION__, ## __VA_ARGS__, (errno) ? strerror(errno) : "")
# define copyfile_debug(d, str, ...) \
do { \
if (s && (d <= s->debug)) {\
fprintf(stderr, "%s:%d:%s() " str "\n", __FILE__, __LINE__ , __FUNCTION__, ## __VA_ARGS__); \
} \
} while(0)
#endif
static int copyfile_quarantine(copyfile_state_t s)
{
int rv = 0;
if (s->qinfo == NULL)
{
int error;
s->qinfo = qtn_file_alloc();
if (s->qinfo == NULL)
{
rv = -1;
goto done;
}
if ((error = qtn_file_init_with_fd(s->qinfo, s->src_fd)) != 0)
{
qtn_file_free(s->qinfo);
s->qinfo = NULL;
errno = error;
rv = -1;
goto done;
}
}
done:
return rv;
}
int fcopyfile(int src_fd, int dst_fd, copyfile_state_t state, copyfile_flags_t flags)
{
int ret = 0;
copyfile_state_t s = state;
struct stat dst_sb;
if (src_fd < 0 || dst_fd < 0)
{
errno = EINVAL;
return -1;
}
if (copyfile_preamble(&s, flags) < 0)
return -1;
copyfile_debug(2, "set src_fd <- %d", src_fd);
if (s->src_fd == -2 && src_fd > -1)
{
s->src_fd = src_fd;
if (fstatx_np(s->src_fd, &s->sb, s->fsec) != 0)
{
if (errno == ENOTSUP)
fstat(s->src_fd, &s->sb);
else
{
copyfile_warn("fstatx_np on src fd %d", s->src_fd);
return -1;
}
}
}
switch (s->sb.st_mode & S_IFMT)
{
case S_IFLNK:
case S_IFDIR:
case S_IFREG:
break;
default:
errno = ENOTSUP;
return -1;
}
copyfile_debug(2, "set dst_fd <- %d", dst_fd);
if (s->dst_fd == -2 && dst_fd > -1)
s->dst_fd = dst_fd;
(void)fstat(s->dst_fd, &dst_sb);
(void)fchmod(s->dst_fd, (dst_sb.st_mode & ~S_IFMT) | (S_IRUSR | S_IWUSR));
(void)copyfile_quarantine(s);
ret = copyfile_internal(s, flags);
if (ret >= 0 && !(s->flags & COPYFILE_STAT))
{
(void)fchmod(s->dst_fd, dst_sb.st_mode & ~S_IFMT);
}
if (state == NULL)
copyfile_state_free(s);
return ret;
}
int copyfile(const char *src, const char *dst, copyfile_state_t state, copyfile_flags_t flags)
{
int ret = 0;
copyfile_state_t s = state;
filesec_t original_fsec = NULL;
filesec_t permissive_fsec = NULL;
struct stat sb;
if (src == NULL && dst == NULL)
{
errno = EINVAL;
return -1;
}
if (copyfile_preamble(&s, flags) < 0)
{
return -1;
}
#define COPYFILE_SET_FNAME(NAME, S) \
do { \
if (NAME != NULL) { \
if (S->NAME != NULL && strncmp(NAME, S->NAME, MAXPATHLEN)) { \
copyfile_debug(2, "replacing string %s (%s) -> (%s)", #NAME, NAME, S->NAME);\
if (S->NAME##_fd != -2 && S->NAME##_fd > -1) { \
copyfile_debug(4, "closing %s fd: %d", #NAME, S->NAME##_fd); \
close(S->NAME##_fd); \
S->NAME##_fd = -2; \
} \
} \
if (S->NAME) { \
free(S->NAME); \
S->NAME = NULL; \
} \
if ((S->NAME = strdup(NAME)) == NULL) \
return -1; \
} \
} while (0)
COPYFILE_SET_FNAME(src, s);
COPYFILE_SET_FNAME(dst, s);
if ((original_fsec = filesec_init()) == NULL)
goto error_exit;
if(statx_np(s->dst, &sb, original_fsec) == 0)
{
if((permissive_fsec = copyfile_fix_perms(s, &original_fsec)) != NULL)
{
if (chmodx_np(s->dst, permissive_fsec) < 0 && errno != ENOTSUP)
{
copyfile_warn("setting security information");
filesec_free(permissive_fsec);
permissive_fsec = NULL;
}
}
}
if (COPYFILE_CHECK & flags)
{
ret = copyfile_check(s);
goto exit;
} else if ((ret = copyfile_open(s)) < 0)
goto error_exit;
ret = copyfile_internal(s, flags);
if (permissive_fsec && (s->flags & COPYFILE_SECURITY) != COPYFILE_SECURITY) {
if (s->flags & COPYFILE_ACL) {
(void)fchown(s->dst_fd, sb.st_uid, sb.st_gid);
(void)fchmod(s->dst_fd, sb.st_mode);
} else {
if (s->flags & COPYFILE_STAT) {
copyfile_unset_posix_fsec(original_fsec);
}
if (fchmodx_np(s->dst_fd, original_fsec) < 0 && errno != ENOTSUP)
copyfile_warn("restoring security information");
}
}
exit:
if (state == NULL)
copyfile_state_free(s);
if (original_fsec)
filesec_free(original_fsec);
if (permissive_fsec)
filesec_free(permissive_fsec);
return ret;
error_exit:
ret = -1;
goto exit;
}
static int copyfile_preamble(copyfile_state_t *state, copyfile_flags_t flags)
{
copyfile_state_t s;
if (*state == NULL)
{
if ((*state = copyfile_state_alloc()) == NULL)
return -1;
}
s = *state;
if (COPYFILE_DEBUG & flags)
{
char *e;
if ((e = getenv(COPYFILE_DEBUG_VAR)))
{
errno = 0;
s->debug = (uint32_t)strtol(e, NULL, 0);
if (s->debug == 0 && errno != 0)
s->debug = 1;
}
copyfile_debug(2, "debug value set to: %d", s->debug);
}
#if 0
if (getenv(COPYFILE_DISABLE_VAR) != NULL)
{
copyfile_debug(1, "copyfile disabled");
return 2;
}
#endif
copyfile_debug(2, "setting flags: %d", s->flags);
s->flags = flags;
return 0;
}
static int copyfile_internal(copyfile_state_t s, copyfile_flags_t flags)
{
int ret = 0;
if (s->dst_fd < 0 || s->src_fd < 0)
{
copyfile_debug(1, "file descriptors not open (src: %d, dst: %d)",
s->src_fd, s->dst_fd);
errno = EINVAL;
return -1;
}
if (COPYFILE_PACK & flags)
{
if ((ret = copyfile_pack(s)) < 0)
{
unlink(s->dst);
goto exit;
}
goto exit;
}
if (COPYFILE_UNPACK & flags)
{
if ((ret = copyfile_unpack(s)) < 0)
goto error_exit;
goto exit;
}
if (s->qinfo)
(void)qtn_file_apply_to_fd(s->qinfo, s->dst_fd);
if (COPYFILE_XATTR & flags)
{
if ((ret = copyfile_xattr(s)) < 0)
{
if (errno != ENOTSUP)
copyfile_warn("error processing extended attributes");
goto exit;
}
}
if (COPYFILE_DATA & flags)
{
if ((ret = copyfile_data(s)) < 0)
{
copyfile_warn("error processing data");
if (s->dst && unlink(s->dst))
copyfile_warn("%s: remove", s->src);
goto exit;
}
}
if (COPYFILE_SECURITY & flags)
{
if ((ret = copyfile_security(s)) < 0)
{
copyfile_warn("error processing security information");
goto exit;
}
}
if (COPYFILE_STAT & flags)
{
if ((ret = copyfile_stat(s)) < 0)
{
copyfile_warn("error processing POSIX information");
goto exit;
}
}
exit:
return ret;
error_exit:
ret = -1;
goto exit;
}
copyfile_state_t copyfile_state_alloc(void)
{
copyfile_state_t s = (copyfile_state_t) calloc(1, sizeof(struct _copyfile_state));
if (s != NULL)
{
s->src_fd = -2;
s->dst_fd = -2;
s->fsec = filesec_init();
} else
errno = ENOMEM;
return s;
}
int copyfile_state_free(copyfile_state_t s)
{
if (s != NULL)
{
if (s->fsec)
filesec_free(s->fsec);
if (s->qinfo)
qtn_file_free(s->qinfo);
if (copyfile_close(s) < 0)
{
copyfile_warn("error closing files");
return -1;
}
if (s->dst)
free(s->dst);
if (s->src)
free(s->src);
free(s);
}
return 0;
}
static int copyfile_close(copyfile_state_t s)
{
if (s->src && s->src_fd >= 0)
close(s->src_fd);
if (s->dst && s->dst_fd >= 0) {
if (close(s->dst_fd))
return -1;
}
return 0;
}
static filesec_t copyfile_fix_perms(copyfile_state_t s __unused, filesec_t *fsec)
{
filesec_t ret_fsec = NULL;
mode_t mode;
acl_t acl = NULL;
if ((ret_fsec = filesec_dup(*fsec)) == NULL)
goto error_exit;
if (filesec_get_property(ret_fsec, FILESEC_ACL, &acl) == 0)
{
acl_entry_t entry;
acl_permset_t permset;
uuid_t qual;
if (mbr_uid_to_uuid(getuid(), qual) != 0)
goto error_exit;
if (acl_create_entry_np(&acl, &entry, ACL_FIRST_ENTRY) == -1)
goto error_exit;
if (acl_get_permset(entry, &permset) == -1)
goto error_exit;
if (acl_clear_perms(permset) == -1)
goto error_exit;
if (acl_add_perm(permset, ACL_WRITE_DATA) == -1)
goto error_exit;
if (acl_add_perm(permset, ACL_WRITE_ATTRIBUTES) == -1)
goto error_exit;
if (acl_add_perm(permset, ACL_WRITE_EXTATTRIBUTES) == -1)
goto error_exit;
if (acl_set_tag_type(entry, ACL_EXTENDED_ALLOW) == -1)
goto error_exit;
if(acl_set_permset(entry, permset) == -1)
goto error_exit;
if(acl_set_qualifier(entry, qual) == -1)
goto error_exit;
if (filesec_set_property(ret_fsec, FILESEC_ACL, &acl) != 0)
goto error_exit;
}
if (filesec_get_property(ret_fsec, FILESEC_MODE, &mode) == 0)
{
if ((mode & (S_IWUSR | S_IRUSR)) != (S_IWUSR | S_IRUSR))
{
mode |= S_IWUSR|S_IRUSR;
if (filesec_set_property(ret_fsec, FILESEC_MODE, &mode) != 0)
goto error_exit;
}
}
exit:
if (acl)
acl_free(acl);
return ret_fsec;
error_exit:
if (ret_fsec)
{
filesec_free(ret_fsec);
ret_fsec = NULL;
}
goto exit;
}
static int
copyfile_unset_posix_fsec(filesec_t fsec)
{
(void)filesec_set_property(fsec, FILESEC_OWNER, _FILESEC_UNSET_PROPERTY);
(void)filesec_set_property(fsec, FILESEC_GROUP, _FILESEC_UNSET_PROPERTY);
(void)filesec_set_property(fsec, FILESEC_MODE, _FILESEC_UNSET_PROPERTY);
return 0;
}
static int copyfile_unset_acl(copyfile_state_t s)
{
int ret = 0;
if (filesec_set_property(s->fsec, FILESEC_ACL, NULL) == -1)
{
copyfile_debug(5, "unsetting acl attribute on %s", s->dst);
++ret;
}
if (filesec_set_property(s->fsec, FILESEC_UUID, NULL) == -1)
{
copyfile_debug(5, "unsetting uuid attribute on %s", s->dst);
++ret;
}
if (filesec_set_property(s->fsec, FILESEC_GRPUUID, NULL) == -1)
{
copyfile_debug(5, "unsetting group uuid attribute on %s", s->dst);
++ret;
}
return ret;
}
static int copyfile_open(copyfile_state_t s)
{
int oflags = O_EXCL | O_CREAT | O_WRONLY;
int islnk = 0, isdir = 0;
int osrc = 0, dsrc = 0;
if (s->src && s->src_fd == -2)
{
if ((COPYFILE_NOFOLLOW_SRC & s->flags ? lstatx_np : statx_np)
(s->src, &s->sb, s->fsec))
{
copyfile_warn("stat on %s", s->src);
return -1;
}
switch (s->sb.st_mode & S_IFMT)
{
case S_IFLNK:
islnk = 1;
if (s->sb.st_size > (off_t)SIZE_T_MAX) {
errno = ENOMEM;
return -1;
}
osrc = O_SYMLINK;
break;
case S_IFDIR:
isdir = 1;
break;
case S_IFREG:
break;
default:
errno = ENOTSUP;
return -1;
}
if (s->flags & COPYFILE_PACK) {
if (!islnk)
osrc = (s->flags & COPYFILE_NOFOLLOW_SRC) ? O_NOFOLLOW : 0;
isdir = islnk = 0;
}
if ((s->src_fd = open(s->src, O_RDONLY | osrc , 0)) < 0)
{
copyfile_warn("open on %s", s->src);
return -1;
} else
copyfile_debug(2, "open successful on source (%s)", s->src);
(void)copyfile_quarantine(s);
}
if (s->dst && s->dst_fd == -2)
{
if (COPYFILE_UNLINK & s->flags)
{
if (remove(s->dst) < 0 && errno != ENOENT)
{
copyfile_warn("%s: remove", s->dst);
return -1;
}
}
if (s->flags & COPYFILE_NOFOLLOW_DST)
dsrc = O_NOFOLLOW;
if (islnk) {
size_t sz = (size_t)s->sb.st_size + 1;
char *bp;
bp = calloc(1, sz);
if (bp == NULL) {
copyfile_warn("cannot allocate %d bytes", sz);
return -1;
}
if (readlink(s->src, bp, sz-1) == -1) {
copyfile_warn("cannot readlink %s", s->src);
free(bp);
return -1;
}
if (symlink(bp, s->dst) == -1) {
if (errno != EEXIST || (s->flags & COPYFILE_EXCL)) {
copyfile_warn("Cannot make symlink %s", s->dst);
free(bp);
return -1;
}
}
free(bp);
s->dst_fd = open(s->dst, O_RDONLY | O_SYMLINK);
if (s->dst_fd == -1) {
copyfile_warn("Cannot open symlink %s for reading", s->dst);
return -1;
}
} else if (isdir) {
mode_t mode;
mode = s->sb.st_mode & ~S_IFMT;
if (mkdir(s->dst, mode) == -1) {
if (errno != EEXIST || (s->flags & COPYFILE_EXCL)) {
copyfile_warn("Cannot make directory %s", s->dst);
return -1;
}
}
s->dst_fd = open(s->dst, O_RDONLY | dsrc);
if (s->dst_fd == -1) {
copyfile_warn("Cannot open directory %s for reading", s->dst);
return -1;
}
} else while((s->dst_fd = open(s->dst, oflags | dsrc, s->sb.st_mode | S_IWUSR)) < 0)
{
switch(errno)
{
case EEXIST:
copyfile_debug(3, "open failed, retrying (%s)", s->dst);
if (s->flags & COPYFILE_EXCL)
break;
oflags = oflags & ~O_CREAT;
if (s->flags & (COPYFILE_PACK | COPYFILE_DATA))
{
copyfile_debug(4, "truncating existing file (%s)", s->dst);
oflags |= O_TRUNC;
}
continue;
case EACCES:
if(chmod(s->dst, (s->sb.st_mode | S_IWUSR) & ~S_IFMT) == 0)
continue;
else {
break;
}
case EISDIR:
copyfile_debug(3, "open failed because it is a directory (%s)", s->dst);
if ((s->flags & COPYFILE_EXCL) ||
(!isdir && (s->flags & COPYFILE_DATA)))
break;
oflags = (oflags & ~O_WRONLY) | O_RDONLY;
continue;
}
copyfile_warn("open on %s", s->dst);
return -1;
}
copyfile_debug(2, "open successful on destination (%s)", s->dst);
}
if (s->dst_fd < 0 || s->src_fd < 0)
{
copyfile_debug(1, "file descriptors not open (src: %d, dst: %d)",
s->src_fd, s->dst_fd);
errno = EINVAL;
return -1;
}
return 0;
}
static copyfile_flags_t copyfile_check(copyfile_state_t s)
{
acl_t acl = NULL;
copyfile_flags_t ret = 0;
int nofollow = (s->flags & COPYFILE_NOFOLLOW_SRC);
qtn_file_t qinfo;
if (!s->src)
{
errno = EINVAL;
return -1;
}
if (COPYFILE_XATTR & s->flags)
if (listxattr(s->src, 0, 0, nofollow ? XATTR_NOFOLLOW : 0) > 0)
{
ret |= COPYFILE_XATTR;
}
if (COPYFILE_ACL & s->flags)
{
(COPYFILE_NOFOLLOW_SRC & s->flags ? lstatx_np : statx_np)
(s->src, &s->sb, s->fsec);
if (filesec_get_property(s->fsec, FILESEC_ACL, &acl) == 0)
ret |= COPYFILE_ACL;
}
copyfile_debug(2, "check result: %d (%s)", ret, s->src);
if (acl)
acl_free(acl);
if (s->qinfo) {
ret |= ((s->flags & COPYFILE_XATTR) ? COPYFILE_XATTR : COPYFILE_ACL);
} else {
qinfo = qtn_file_alloc();
if (qinfo) {
int fd;
int qret = 0;
struct stat sbuf;
if (nofollow
&& lstat(s->src, &sbuf) == 0
&& ((sbuf.st_mode & S_IFMT) == S_IFLNK)) {
fd = open(s->src, O_RDONLY | O_SYMLINK);
if (fd != -1) {
if (!qtn_file_init_with_fd(qinfo, fd)) {
qret |= ((s->flags & COPYFILE_XATTR) ? COPYFILE_XATTR : COPYFILE_ACL);
}
close(fd);
}
} else {
if (!qtn_file_init_with_path(qinfo, s->src)) {
qret |= ((s->flags & COPYFILE_XATTR) ? COPYFILE_XATTR : COPYFILE_ACL);
}
}
qtn_file_free(qinfo);
ret |= qret;
}
}
return ret;
}
static int copyfile_data(copyfile_state_t s)
{
size_t blen;
char *bp = 0;
ssize_t nread;
int ret = 0;
size_t iBlocksize = 0;
struct statfs sfs;
if (fstatfs(s->src_fd, &sfs) == -1) {
iBlocksize = s->sb.st_blksize;
} else {
iBlocksize = sfs.f_iosize;
}
if ((bp = malloc(iBlocksize)) == NULL)
return -1;
blen = iBlocksize;
#ifdef F_PREALLOCATE
{
fstore_t fst;
fst.fst_flags = 0;
fst.fst_posmode = F_PEOFPOSMODE;
fst.fst_offset = 0;
fst.fst_length = s->sb.st_size;
(void)fcntl(s->dst_fd, F_PREALLOCATE, &fst);
}
#endif
while ((nread = read(s->src_fd, bp, blen)) > 0)
{
size_t nwritten;
size_t left = nread;
void *ptr = bp;
while (left > 0) {
int loop = 0;
nwritten = write(s->dst_fd, ptr, left);
switch (nwritten) {
case 0:
if (++loop > 5) {
copyfile_warn("writing to output %d times resulted in 0 bytes written", loop);
ret = -1;
errno = EAGAIN;
goto exit;
}
break;
case -1:
copyfile_warn("writing to output file got error");
ret = -1;
goto exit;
default:
left -= nwritten;
ptr = ((char*)ptr) + nwritten;
break;
}
}
}
if (nread < 0)
{
copyfile_warn("reading from %s", s->src);
goto exit;
}
if (ftruncate(s->dst_fd, s->sb.st_size) < 0)
{
ret = -1;
goto exit;
}
exit:
free(bp);
return ret;
}
static int copyfile_security(copyfile_state_t s)
{
int copied = 0;
acl_flagset_t flags;
struct stat sb;
acl_entry_t entry_src = NULL, entry_dst = NULL;
acl_t acl_src = NULL, acl_dst = NULL;
int ret = 0;
filesec_t tmp_fsec = NULL;
filesec_t fsec_dst = filesec_init();
if (fsec_dst == NULL)
return -1;
if (COPYFILE_ACL & s->flags)
{
if (filesec_get_property(s->fsec, FILESEC_ACL, &acl_src))
{
if (errno == ENOENT)
goto no_acl;
else
goto error_exit;
}
if(fstatx_np(s->dst_fd, &sb, fsec_dst))
goto error_exit;
if (filesec_get_property(fsec_dst, FILESEC_ACL, &acl_dst))
{
if (errno == ENOENT)
acl_dst = acl_init(4);
else
goto error_exit;
}
for (;acl_get_entry(acl_src,
entry_src == NULL ? ACL_FIRST_ENTRY : ACL_NEXT_ENTRY,
&entry_src) == 0;)
{
acl_get_flagset_np(entry_src, &flags);
if (!acl_get_flag_np(flags, ACL_ENTRY_INHERITED))
{
if ((ret = acl_create_entry(&acl_dst, &entry_dst)) == -1)
goto error_exit;
if ((ret = acl_copy_entry(entry_dst, entry_src)) == -1)
goto error_exit;
copyfile_debug(2, "copied acl entry from %s to %s", s->src, s->dst);
copied++;
}
}
if (!filesec_set_property(s->fsec, FILESEC_ACL, &acl_dst))
{
copyfile_debug(3, "altered acl");
}
}
no_acl:
tmp_fsec = filesec_dup(s->fsec);
if (tmp_fsec == NULL) {
goto error_exit;
}
switch (COPYFILE_SECURITY & s->flags) {
case COPYFILE_ACL:
copyfile_unset_posix_fsec(tmp_fsec);
case COPYFILE_ACL | COPYFILE_STAT:
if (fchmodx_np(s->dst_fd, tmp_fsec) < 0) {
if (errno == ENOTSUP) {
if (COPYFILE_STAT & s->flags)
fchmod(s->dst_fd, s->sb.st_mode);
} else
copyfile_warn("setting security information: %s", s->dst);
}
break;
case COPYFILE_STAT:
fchmod(s->dst_fd, s->sb.st_mode);
break;
}
filesec_free(tmp_fsec);
exit:
filesec_free(fsec_dst);
if (acl_src) acl_free(acl_src);
if (acl_dst) acl_free(acl_dst);
return ret;
error_exit:
ret = -1;
goto exit;
}
static int copyfile_stat(copyfile_state_t s)
{
struct timeval tval[2];
if (fchflags(s->dst_fd, (u_int)s->sb.st_flags))
if (errno != EOPNOTSUPP || s->sb.st_flags != 0)
copyfile_warn("%s: set flags (was: 0%07o)", s->dst, s->sb.st_flags);
(void)fchown(s->dst_fd, s->sb.st_uid, s->sb.st_gid);
(void)fchmod(s->dst_fd, s->sb.st_mode & ~S_IFMT);
tval[0].tv_sec = s->sb.st_atime;
tval[1].tv_sec = s->sb.st_mtime;
tval[0].tv_usec = tval[1].tv_usec = 0;
if (futimes(s->dst_fd, tval))
copyfile_warn("%s: set times", s->dst);
return 0;
}
static int copyfile_xattr(copyfile_state_t s)
{
char *name;
char *namebuf, *end;
ssize_t xa_size;
void *xa_dataptr;
ssize_t bufsize = 4096;
ssize_t asize;
ssize_t nsize;
int ret = 0;
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) {
end = namebuf + nsize - 1;
if (*end != 0)
*end = 0;
for (name = namebuf; name <= end; name += strlen(name) + 1) {
if (strncmp(name, XATTR_QUARANTINE_NAME, end - name) == 0) {
continue;
}
fremovexattr(s->dst_fd, name,0);
}
}
free(namebuf);
} else
if (nsize < 0)
{
if (errno == ENOTSUP)
return 0;
else
return -1;
}
if ((nsize = flistxattr(s->src_fd, 0, 0, 0)) < 0)
{
if (errno == ENOTSUP)
return 0;
else
return -1;
} else
if (nsize == 0)
return 0;
if ((namebuf = (char *) malloc(nsize)) == NULL)
return -1;
else
nsize = flistxattr(s->src_fd, namebuf, nsize, 0);
if (nsize <= 0) {
free(namebuf);
return (int)nsize;
}
end = namebuf + nsize - 1;
if (*end != 0)
*end = 0;
if ((xa_dataptr = (void *) malloc(bufsize)) == NULL) {
free(namebuf);
return -1;
}
for (name = namebuf; name <= end; name += strlen(name) + 1)
{
if (strncmp(name, XATTR_QUARANTINE_NAME, end - name) == 0)
continue;
if ((xa_size = fgetxattr(s->src_fd, name, 0, 0, 0, 0)) < 0)
{
ret = -1;
continue;
}
if (xa_size > bufsize)
{
void *tdptr = xa_dataptr;
bufsize = xa_size;
if ((xa_dataptr =
(void *) realloc((void *) xa_dataptr, bufsize)) == NULL)
{
free(tdptr);
ret = -1;
continue;
}
}
if ((asize = fgetxattr(s->src_fd, name, xa_dataptr, xa_size, 0, 0)) < 0)
{
ret = -1;
continue;
}
if (xa_size != asize)
xa_size = asize;
if (fsetxattr(s->dst_fd, name, xa_dataptr, xa_size, 0, 0) < 0)
{
ret = -1;
continue;
}
}
if (namebuf)
free(namebuf);
free((void *) xa_dataptr);
return ret;
}
int copyfile_state_get(copyfile_state_t s, uint32_t flag, void *ret)
{
if (ret == NULL)
{
errno = EFAULT;
return -1;
}
switch(flag)
{
case COPYFILE_STATE_SRC_FD:
*(int*)ret = s->src_fd;
break;
case COPYFILE_STATE_DST_FD:
*(int*)ret = s->dst_fd;
break;
case COPYFILE_STATE_SRC_FILENAME:
*(char**)ret = s->src;
break;
case COPYFILE_STATE_DST_FILENAME:
*(char**)ret = s->dst;
break;
case COPYFILE_STATE_QUARANTINE:
*(qtn_file_t*)ret = s->qinfo;
break;
#if 0
case COPYFILE_STATE_STATS:
ret = s->stats.global;
break;
case COPYFILE_STATE_PROGRESS_CB:
ret = s->callbacks.progress;
break;
#endif
default:
errno = EINVAL;
ret = NULL;
return -1;
}
return 0;
}
int copyfile_state_set(copyfile_state_t s, uint32_t flag, const void * thing)
{
#define copyfile_set_string(DST, SRC) \
do { \
if (SRC != NULL) { \
DST = strdup((char *)SRC); \
} else { \
if (DST != NULL) { \
free(DST); \
} \
DST = NULL; \
} \
} while (0)
if (thing == NULL)
{
errno = EFAULT;
return -1;
}
switch(flag)
{
case COPYFILE_STATE_SRC_FD:
s->src_fd = *(int*)thing;
break;
case COPYFILE_STATE_DST_FD:
s->dst_fd = *(int*)thing;
break;
case COPYFILE_STATE_SRC_FILENAME:
copyfile_set_string(s->src, thing);
break;
case COPYFILE_STATE_DST_FILENAME:
copyfile_set_string(s->dst, thing);
break;
case COPYFILE_STATE_QUARANTINE:
if (s->qinfo)
{
qtn_file_free(s->qinfo);
s->qinfo = NULL;
}
if (*(qtn_file_t*)thing)
s->qinfo = qtn_file_clone(*(qtn_file_t*)thing);
break;
#if 0
case COPYFILE_STATE_STATS:
s->stats.global = thing;
break;
case COPYFILE_STATE_PROGRESS_CB:
s->callbacks.progress = thing;
break;
#endif
default:
errno = EINVAL;
return -1;
}
return 0;
#undef copyfile_set_string
}
#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)
#define FINDERINFOSIZE 32
typedef struct apple_double_entry
{
u_int32_t type;
u_int32_t offset;
u_int32_t length;
} __attribute__((aligned(2), packed)) 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];
} __attribute__((aligned(2), packed)) 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];
} __attribute__((aligned(2), packed)) 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;
} __attribute__((aligned(2), packed)) attr_header_t;
typedef struct rsrcfork_header {
u_int32_t fh_DataOffset;
u_int32_t fh_MapOffset;
u_int32_t fh_DataLength;
u_int32_t fh_MapLength;
u_int8_t systemData[112];
u_int8_t appData[128];
u_int32_t mh_DataOffset;
u_int32_t mh_MapOffset;
u_int32_t mh_DataLength;
u_int32_t mh_MapLength;
u_int32_t mh_Next;
u_int16_t mh_RefNum;
u_int8_t mh_Attr;
u_int8_t mh_InMemoryAttr;
u_int16_t mh_Types;
u_int16_t mh_Names;
u_int16_t typeCount;
} rsrcfork_header_t;
#define RF_FIRST_RESOURCE 256
#define RF_NULL_MAP_LENGTH 30
#define RF_EMPTY_TAG "This resource fork intentionally left blank "
static const rsrcfork_header_t empty_rsrcfork_header = {
OSSwapHostToBigInt32(RF_FIRST_RESOURCE), OSSwapHostToBigInt32(RF_FIRST_RESOURCE), 0, OSSwapHostToBigInt32(RF_NULL_MAP_LENGTH), { RF_EMPTY_TAG, }, { 0 }, OSSwapHostToBigInt32(RF_FIRST_RESOURCE), OSSwapHostToBigInt32(RF_FIRST_RESOURCE), 0, OSSwapHostToBigInt32(RF_NULL_MAP_LENGTH), 0, 0, 0, 0, OSSwapHostToBigInt16(RF_NULL_MAP_LENGTH - 2), OSSwapHostToBigInt16(RF_NULL_MAP_LENGTH), OSSwapHostToBigInt16(-1), };
#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);
}
#else
(void)adh;
#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++)
{
attr_entry_t *next = ATTR_NEXT(ae);
ae->offset = SWAP32 (ae->offset);
ae->length = SWAP32 (ae->length);
ae->flags = SWAP16 (ae->flags);
ae = next;
}
#else
(void)ah;
#endif
}
static const u_int32_t emptyfinfo[8] = {0};
static int copyfile_unpack(copyfile_state_t s)
{
ssize_t bytes;
void * buffer, * endptr;
apple_double_header_t *adhdr;
ssize_t hdrsize;
int error = 0;
if (s->sb.st_size < ATTR_MAX_HDR_SIZE)
hdrsize = (ssize_t)s->sb.st_size;
else
hdrsize = ATTR_MAX_HDR_SIZE;
buffer = calloc(1, hdrsize);
if (buffer == NULL) {
copyfile_debug(1, "copyfile_unpack: calloc(1, %u) returned NULL", hdrsize);
error = -1;
goto exit;
} else
endptr = (char*)buffer + hdrsize;
bytes = pread(s->src_fd, buffer, hdrsize, 0);
if (bytes < 0)
{
copyfile_debug(1, "pread returned: %d", bytes);
error = -1;
goto exit;
}
if (bytes < hdrsize)
{
copyfile_debug(1,
"pread couldn't read entire header: %d of %d",
(int)bytes, (int)s->sb.st_size);
error = -1;
goto exit;
}
adhdr = (apple_double_header_t *)buffer;
if ((size_t)bytes < sizeof(apple_double_header_t) - 2 ||
SWAP32(adhdr->magic) != ADH_MAGIC ||
SWAP32(adhdr->version) != ADH_VERSION ||
SWAP16(adhdr->numEntries) != 2 ||
SWAP32(adhdr->entries[0].type) != AD_FINDERINFO)
{
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn("Not a valid Apple Double header");
error = -1;
goto exit;
}
swap_adhdr(adhdr);
if (COPYFILE_XATTR & s->flags)
{
if ((bytes = flistxattr(s->dst_fd, 0, 0, 0)) > 0)
{
char *namebuf, *name;
if ((namebuf = (char*) malloc(bytes)) == NULL)
{
errno = ENOMEM;
goto exit;
}
bytes = flistxattr(s->dst_fd, namebuf, bytes, 0);
if (bytes > 0)
for (name = namebuf; name < namebuf + bytes; name += strlen(name) + 1)
(void)fremovexattr(s->dst_fd, name, 0);
free(namebuf);
}
else if (bytes < 0)
{
if (errno != ENOTSUP)
goto exit;
}
}
if (adhdr->entries[0].length > FINDERINFOSIZE)
{
attr_header_t *attrhdr;
attr_entry_t *entry;
int count;
int i;
if ((size_t)hdrsize < sizeof(attr_header_t)) {
copyfile_warn("bad attribute header: %u < %u", hdrsize, sizeof(attr_header_t));
error = -1;
goto exit;
}
attrhdr = (attr_header_t *)buffer;
swap_attrhdr(attrhdr);
if (attrhdr->magic != ATTR_HDR_MAGIC)
{
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn("bad attribute header");
error = -1;
goto exit;
}
count = attrhdr->num_attrs;
entry = (attr_entry_t *)&attrhdr[1];
for (i = 0; i < count; i++)
{
void * dataptr;
if ((void*)entry >= endptr || (void*)entry < buffer) {
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn("Incomplete or corrupt attribute entry");
error = -1;
goto exit;
}
if (((char*)entry + sizeof(*entry)) > (char*)endptr) {
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn("Incomplete or corrupt attribute entry");
error = -1;
goto exit;
}
if (entry->namelen < 2) {
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn("Corrupt attribute entry (only %d bytes)", entry->namelen);
error = -1;
goto exit;
}
if (entry->namelen > XATTR_MAXNAMELEN + 1) {
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn("Corrupt attribute entry (name length is %d bytes)", entry->namelen);
error = -1;
goto exit;
}
if ((void*)(entry->name + entry->namelen) > endptr) {
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn("Incomplete or corrupt attribute entry");
error = -1;
goto exit;
}
if (entry->name[entry->namelen-1] != 0) {
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn("Corrupt attribute entry (name is not NUL-terminated)");
error = -1;
goto exit;
}
copyfile_debug(3, "extracting \"%s\" (%d bytes) at offset %u",
entry->name, entry->length, entry->offset);
dataptr = (char *)attrhdr + entry->offset;
if (dataptr > endptr || dataptr < buffer) {
copyfile_debug(1, "Entry %d overflows: offset = %u", entry->offset);
error = -1;
goto exit;
}
if (((char*)dataptr + entry->length) > (char*)endptr ||
(((char*)dataptr + entry->length) < (char*)buffer) ||
(entry->length > (size_t)hdrsize)) {
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn("Incomplete or corrupt attribute entry");
copyfile_debug(1, "Entry %d length overflows: offset = %u, length = %u",
entry->offset, entry->length);
error = -1;
goto exit;
}
if (strcmp((char*)entry->name, XATTR_QUARANTINE_NAME) == 0)
{
qtn_file_t tqinfo = NULL;
if (s->qinfo == NULL)
{
tqinfo = qtn_file_alloc();
if (tqinfo)
{
int x;
if ((x = qtn_file_init_with_data(tqinfo, dataptr, entry->length)) != 0)
{
copyfile_warn("qtn_file_init_with_data failed: %s", qtn_error(x));
qtn_file_free(tqinfo);
tqinfo = NULL;
}
}
}
else
{
tqinfo = s->qinfo;
}
if (tqinfo)
{
int x;
x = qtn_file_apply_to_fd(tqinfo, s->dst_fd);
if (x != 0)
copyfile_warn("qtn_file_apply_to_fd failed: %s", qtn_error(x));
}
if (tqinfo && !s->qinfo)
{
qtn_file_free(tqinfo);
}
}
else if (COPYFILE_ACL & s->flags && strcmp((char*)entry->name, XATTR_SECURITY_NAME) == 0)
{
acl_t acl;
struct stat sb;
int retry = 1;
char *tcp = dataptr;
if (tcp[entry->length - 1] != 0) {
char *tmpstr = malloc(entry->length + 1);
if (tmpstr == NULL) {
error = -1;
goto exit;
}
strlcpy(tmpstr, tcp, entry->length + 1);
acl = acl_from_text(tmpstr);
free(tmpstr);
} else {
acl = acl_from_text(tcp);
}
if (acl != NULL)
{
filesec_t fsec_tmp;
if ((fsec_tmp = filesec_init()) == NULL)
error = -1;
else if((error = fstatx_np(s->dst_fd, &sb, fsec_tmp)) < 0)
error = -1;
else if (filesec_set_property(fsec_tmp, FILESEC_ACL, &acl) < 0)
error = -1;
else {
while (fchmodx_np(s->dst_fd, fsec_tmp) < 0)
{
if (errno == ENOTSUP)
{
if (retry && !copyfile_unset_acl(s))
{
retry = 0;
continue;
}
}
copyfile_warn("setting security information");
error = -1;
break;
}
}
acl_free(acl);
filesec_free(fsec_tmp);
if (error == -1)
goto exit;
}
}
else if (COPYFILE_XATTR & s->flags && (fsetxattr(s->dst_fd, (char *)entry->name, dataptr, entry->length, 0, 0))) {
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn("error %d setting attribute %s", error, entry->name);
break;
}
entry = ATTR_NEXT(entry);
}
}
if (adhdr->entries[0].offset > (hdrsize - sizeof(emptyfinfo))) {
error = -1;
goto exit;
}
if (bcmp((u_int8_t*)buffer + adhdr->entries[0].offset, emptyfinfo, sizeof(emptyfinfo)) != 0)
{
copyfile_debug(3, " extracting \"%s\" (32 bytes)", XATTR_FINDERINFO_NAME);
error = fsetxattr(s->dst_fd, XATTR_FINDERINFO_NAME, (u_int8_t*)buffer + adhdr->entries[0].offset, sizeof(emptyfinfo), 0, 0);
if (error)
goto exit;
}
if (adhdr->entries[1].type == AD_RESOURCE &&
adhdr->entries[1].length > 0)
{
void * rsrcforkdata = NULL;
size_t length;
off_t offset;
struct stat sb;
struct timeval tval[2];
length = adhdr->entries[1].length;
offset = adhdr->entries[1].offset;
rsrcforkdata = malloc(length);
if (rsrcforkdata == NULL) {
copyfile_debug(1, "could not allocate %u bytes for rsrcforkdata",
length);
error = -1;
goto bad;
}
if (fstat(s->dst_fd, &sb) < 0)
{
copyfile_debug(1, "couldn't stat destination file");
error = -1;
goto bad;
}
bytes = pread(s->src_fd, rsrcforkdata, length, offset);
if (bytes < (ssize_t)length)
{
if (bytes == -1)
{
copyfile_debug(1, "couldn't read resource fork");
}
else
{
copyfile_debug(1,
"couldn't read resource fork (only read %d bytes of %d)",
(int)bytes, (int)length);
}
error = -1;
goto bad;
}
error = fsetxattr(s->dst_fd, XATTR_RESOURCEFORK_NAME, rsrcforkdata, bytes, 0, 0);
if (error)
{
if ((bytes == sizeof(rsrcfork_header_t)) &&
((sb.st_mode & S_IFMT) == S_IFDIR) &&
(memcmp(rsrcforkdata, &empty_rsrcfork_header, bytes) == 0)) {
copyfile_debug(2, "not setting empty resource fork on directory");
error = errno = 0;
goto bad;
}
copyfile_debug(1, "error %d setting resource fork attribute", error);
error = -1;
goto bad;
}
copyfile_debug(3, "extracting \"%s\" (%d bytes)",
XATTR_RESOURCEFORK_NAME, (int)length);
if (!(s->flags & COPYFILE_STAT))
{
tval[0].tv_sec = sb.st_atime;
tval[1].tv_sec = sb.st_mtime;
tval[0].tv_usec = tval[1].tv_usec = 0;
if (futimes(s->dst_fd, tval))
copyfile_warn("%s: set times", s->dst);
}
bad:
if (rsrcforkdata)
free(rsrcforkdata);
}
if (COPYFILE_STAT & s->flags)
{
error = copyfile_stat(s);
}
exit:
if (buffer) free(buffer);
return error;
}
static int copyfile_pack_quarantine(copyfile_state_t s, void **buf, ssize_t *len)
{
int ret = 0;
char qbuf[QTN_SERIALIZED_DATA_MAX];
size_t qlen = sizeof(qbuf);
if (s->qinfo == NULL)
{
ret = -1;
goto done;
}
if (qtn_file_to_data(s->qinfo, qbuf, &qlen) != 0)
{
ret = -1;
goto done;
}
*buf = malloc(qlen);
if (*buf)
{
memcpy(*buf, qbuf, qlen);
*len = qlen;
}
done:
return ret;
}
static int copyfile_pack_acl(copyfile_state_t s, void **buf, ssize_t *len)
{
int ret = 0;
acl_t acl = NULL;
char *acl_text;
if (filesec_get_property(s->fsec, FILESEC_ACL, &acl) < 0)
{
if (errno != ENOENT)
{
ret = -1;
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn("getting acl");
}
*len = 0;
goto exit;
}
if ((acl_text = acl_to_text(acl, len)) != NULL)
{
*len = *len + 1;
*buf = malloc(*len);
if (*buf)
memcpy(*buf, acl_text, *len);
else
*len = 0;
acl_free(acl_text);
}
copyfile_debug(2, "copied acl (%ld) %p", *len, *buf);
exit:
if (acl)
acl_free(acl);
return ret;
}
static int copyfile_pack_rsrcfork(copyfile_state_t s, attr_header_t *filehdr)
{
ssize_t datasize;
char *databuf = NULL;
int ret = 0;
if ((datasize = fgetxattr(s->src_fd, XATTR_RESOURCEFORK_NAME, NULL, 0, 0, 0)) < 0)
{
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn("skipping attr \"%s\" due to error %d", XATTR_RESOURCEFORK_NAME, errno);
return -1;
}
if (datasize > INT_MAX) {
errno = EINVAL;
ret = -1;
goto done;
}
if ((databuf = malloc(datasize)) == NULL)
{
copyfile_warn("malloc");
ret = -1;
goto done;
}
if (fgetxattr(s->src_fd, XATTR_RESOURCEFORK_NAME, databuf, datasize, 0, 0) != datasize)
{
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn("couldn't read entire resource fork");
ret = -1;
goto done;
}
if (pwrite(s->dst_fd, databuf, datasize, filehdr->appledouble.entries[1].offset) != datasize)
{
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn("couldn't write resource fork");
}
copyfile_debug(3, "copied %d bytes of \"%s\" data @ offset 0x%08x",
datasize, XATTR_RESOURCEFORK_NAME, filehdr->appledouble.entries[1].offset);
filehdr->appledouble.entries[1].length = (u_int32_t)datasize;
done:
if (databuf)
free(databuf);
return ret;
}
static int copyfile_pack(copyfile_state_t s)
{
char *attrnamebuf = NULL, *endnamebuf;
void *databuf = NULL;
attr_header_t *filehdr, *endfilehdr;
attr_entry_t *entry;
ssize_t listsize = 0;
char *nameptr;
size_t namelen;
size_t entrylen;
ssize_t datasize;
size_t offset = 0;
int hasrsrcfork = 0;
int error = 0;
int seenq = 0;
filehdr = (attr_header_t *) calloc(1, ATTR_MAX_SIZE);
if (filehdr == NULL) {
error = -1;
goto exit;
} else {
endfilehdr = (attr_header_t*)(((char*)filehdr) + ATTR_MAX_SIZE);
}
attrnamebuf = calloc(1, ATTR_MAX_HDR_SIZE);
if (attrnamebuf == NULL) {
error = -1;
goto exit;
} else {
endnamebuf = ((char*)attrnamebuf) + ATTR_MAX_HDR_SIZE;
}
filehdr->appledouble.magic = ADH_MAGIC;
filehdr->appledouble.version = ADH_VERSION;
filehdr->appledouble.numEntries = 2;
filehdr->appledouble.entries[0].type = AD_FINDERINFO;
filehdr->appledouble.entries[0].offset = (u_int32_t)offsetof(apple_double_header_t, finfo);
filehdr->appledouble.entries[0].length = FINDERINFOSIZE;
filehdr->appledouble.entries[1].type = AD_RESOURCE;
filehdr->appledouble.entries[1].offset = (u_int32_t)offsetof(apple_double_header_t, pad);
filehdr->appledouble.entries[1].length = 0;
bcopy(ADH_MACOSX, filehdr->appledouble.filler, sizeof(filehdr->appledouble.filler));
filehdr->magic = ATTR_HDR_MAGIC;
filehdr->debug_tag = s->sb.st_ino;
filehdr->data_start = (u_int32_t)sizeof(attr_header_t);
entry = (attr_entry_t *)((char *)filehdr + sizeof(attr_header_t));
if (COPYFILE_ACL & s->flags)
{
acl_t temp_acl = NULL;
if (filesec_get_property(s->fsec, FILESEC_ACL, &temp_acl) < 0)
{
copyfile_debug(2, "no acl entries found (errno = %d)", errno);
} else
{
offset = strlen(XATTR_SECURITY_NAME) + 1;
strcpy(attrnamebuf, XATTR_SECURITY_NAME);
}
if (temp_acl)
acl_free(temp_acl);
}
if (COPYFILE_XATTR & s->flags)
{
ssize_t left = ATTR_MAX_HDR_SIZE - offset;
if ((listsize = flistxattr(s->src_fd, attrnamebuf + offset, left, 0)) <= 0)
{
copyfile_debug(2, "no extended attributes found (%d)", errno);
}
if (listsize > left)
{
copyfile_debug(1, "extended attribute list too long");
listsize = left;
}
listsize += offset;
endnamebuf = attrnamebuf + listsize;
if (endnamebuf > (attrnamebuf + ATTR_MAX_HDR_SIZE)) {
error = -1;
goto exit;
}
for (nameptr = attrnamebuf; nameptr < endnamebuf; nameptr += namelen)
{
namelen = strlen(nameptr) + 1;
if (strcmp(nameptr, XATTR_FINDERINFO_NAME) == 0 ||
strcmp(nameptr, XATTR_RESOURCEFORK_NAME) == 0) {
continue;
}
if (strcmp(nameptr, XATTR_QUARANTINE_NAME) == 0) {
seenq = 1;
}
if (namelen > XATTR_MAXNAMELEN + 1) {
namelen = XATTR_MAXNAMELEN + 1;
}
entry->namelen = namelen;
entry->flags = 0;
if (nameptr + namelen > endnamebuf) {
error = -1;
goto exit;
}
bcopy(nameptr, &entry->name[0], namelen);
copyfile_debug(2, "copied name [%s]", entry->name);
entrylen = ATTR_ENTRY_LENGTH(namelen);
entry = (attr_entry_t *)(((char *)entry) + entrylen);
if ((void*)entry >= (void*)endfilehdr) {
error = -1;
goto exit;
}
filehdr->num_attrs++;
filehdr->data_start += (u_int32_t)entrylen;
}
}
if (s->qinfo && !seenq)
{
ssize_t left = ATTR_MAX_HDR_SIZE - offset;
offset += strlcpy(attrnamebuf + offset, XATTR_QUARANTINE_NAME, left) + 1;
}
seenq = 0;
entry = (attr_entry_t *)((char *)filehdr + sizeof(attr_header_t));
for (nameptr = attrnamebuf; nameptr < attrnamebuf + listsize; nameptr += namelen + 1)
{
namelen = strlen(nameptr);
if (strcmp(nameptr, XATTR_SECURITY_NAME) == 0)
copyfile_pack_acl(s, &databuf, &datasize);
else if (s->qinfo && strcmp(nameptr, XATTR_QUARANTINE_NAME) == 0)
{
copyfile_pack_quarantine(s, &databuf, &datasize);
}
else if (strcmp(nameptr, XATTR_FINDERINFO_NAME) == 0)
{
datasize = fgetxattr(s->src_fd, nameptr, (u_int8_t*)filehdr + filehdr->appledouble.entries[0].offset, 32, 0, 0);
if (datasize < 0)
{
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn("skipping attr \"%s\" due to error %d", nameptr, errno);
} else if (datasize != 32)
{
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn("unexpected size (%ld) for \"%s\"", datasize, nameptr);
} else
{
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn(" copied 32 bytes of \"%s\" data @ offset 0x%08x",
XATTR_FINDERINFO_NAME, filehdr->appledouble.entries[0].offset);
}
continue;
}
else if (strcmp(nameptr, 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);
if (databuf == NULL) {
error = -1;
continue;
}
datasize = fgetxattr(s->src_fd, nameptr, databuf, datasize, 0, 0);
}
entry->length = (u_int32_t)datasize;
entry->offset = filehdr->data_start + filehdr->data_length;
filehdr->data_length += (u_int32_t)datasize;
if (entry->offset > ATTR_MAX_SIZE ||
(entry->offset + datasize > ATTR_MAX_SIZE)) {
error = 1;
} else {
bcopy(databuf, (char*)filehdr + entry->offset, datasize);
}
free(databuf);
copyfile_debug(3, "copied %ld bytes of \"%s\" data @ offset 0x%08x", datasize, nameptr, entry->offset);
next:
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 = filehdr->appledouble.entries[1].offset;
}
if (hasrsrcfork && (error = copyfile_pack_rsrcfork(s, filehdr)))
goto exit;
datasize = filehdr->appledouble.entries[1].offset;
swap_adhdr(&filehdr->appledouble);
swap_attrhdr(filehdr);
if (pwrite(s->dst_fd, filehdr, datasize, 0) != datasize)
{
if (COPYFILE_VERBOSE & s->flags)
copyfile_warn("couldn't write file header");
error = -1;
goto exit;
}
exit:
if (filehdr) free(filehdr);
if (attrnamebuf) free(attrnamebuf);
if (error)
return error;
else
return copyfile_stat(s);
}