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