+static int
+add_uberace(acl_t *acl)
+{
+ 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_add_perm(permset, ACL_APPEND_DATA) == -1)
+ goto error_exit;
+ if (acl_add_perm(permset, ACL_WRITE_SECURITY) == -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;
+
+ return 0;
+error_exit:
+ return -1;
+}
+
+static int
+is_uberace(acl_entry_t ace)
+{
+ int retval = 0;
+ acl_permset_t perms, tperms;
+ acl_t tacl;
+ acl_entry_t tentry;
+ acl_tag_t tag;
+ guid_t *qual;
+ uuid_t myuuid;
+
+ // Who am I, and who is the ACE for?
+ mbr_uid_to_uuid(geteuid(), myuuid);
+ qual = (guid_t*)acl_get_qualifier(ace);
+
+ // Need to create a temporary acl, so I can get the uberace template.
+ tacl = acl_init(1);
+ if (tacl == NULL) {
+ goto done;
+ }
+ add_uberace(&tacl);
+ if (acl_get_entry(tacl, ACL_FIRST_ENTRY, &tentry) != 0) {
+ goto done;
+ }
+ acl_get_permset(tentry, &tperms);
+
+ // Now I need to get
+ acl_get_tag_type(ace, &tag);
+ acl_get_permset(ace, &perms);
+
+ if (tag == ACL_EXTENDED_ALLOW &&
+ (memcmp(qual, myuuid, sizeof(myuuid)) == 0) &&
+ acl_compare_permset_np(tperms, perms))
+ retval = 1;
+
+done:
+
+ if (tacl)
+ acl_free(tacl);
+
+ return retval;
+}
+
+static void
+remove_uberace(int fd, struct stat *sbuf)
+{
+ filesec_t fsec = NULL;
+ acl_t acl = NULL;
+ acl_entry_t entry;
+ struct stat sb;
+
+ fsec = filesec_init();
+ if (fsec == NULL) {
+ goto noacl;
+ }
+
+ if (fstatx_np(fd, &sb, fsec) != 0) {
+ if (errno == ENOTSUP)
+ goto noacl;
+ goto done;
+ }
+
+ if (filesec_get_property(fsec, FILESEC_ACL, &acl) != 0) {
+ goto done;
+ }
+
+ if (acl_get_entry(acl, ACL_FIRST_ENTRY, &entry) == 0) {
+ if (is_uberace(entry))
+ {
+ mode_t m = sbuf->st_mode & ~S_IFMT;
+
+ if (acl_delete_entry(acl, entry) != 0 ||
+ filesec_set_property(fsec, FILESEC_ACL, &acl) != 0 ||
+ filesec_set_property(fsec, FILESEC_MODE, &m) != 0 ||
+ fchmodx_np(fd, fsec) != 0)
+ goto noacl;
+ }
+ }
+
+done:
+ if (acl)
+ acl_free(acl);
+ if (fsec)
+ filesec_free(fsec);
+ return;
+
+noacl:
+ fchmod(fd, sbuf->st_mode & ~S_IFMT);
+ goto done;
+}
+
+static void
+reset_security(copyfile_state_t s)
+{
+ /* 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 requested.
+ */
+
+#ifdef COPYFILE_RECURSIVE
+ if (s->dst_fd > -1) {
+ struct stat sbuf;
+
+ if (s->src_fd > -1 && (s->flags & COPYFILE_STAT))
+ fstat(s->src_fd, &sbuf);
+ else
+ fstat(s->dst_fd, &sbuf);
+
+ if (!(s->internal_flags & cfDelayAce))
+ remove_uberace(s->dst_fd, &sbuf);
+ }
+#else
+ if (s->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, s->dst_sb.st_uid, s->dst_sb.st_gid);
+ (void)fchmod(s->dst_fd, s->dst_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(s->original_fsec);
+ }
+ if (fchmodx_np(s->dst_fd, s->original_fsec) < 0 && errno != ENOTSUP)
+ copyfile_warn("restoring security information");
+ }
+ }
+
+ if (s->permissive_fsec) {
+ filesec_free(s->permissive_fsec);
+ s->permissive_fsec = NULL;
+ }
+
+ if (s->original_fsec) {
+ filesec_free(s->original_fsec);
+ s->original_fsec = NULL;
+ }
+#endif
+
+ return;
+}
+
+/*
+ * copytree -- recursively copy a hierarchy.
+ *
+ * Unlike normal copyfile(), copytree() can copy an entire hierarchy.
+ * Care is taken to keep the ACLs set up correctly, in addition to the
+ * normal copying that is done. (When copying a hierarchy, we can't
+ * get rid of the "allow-all-writes" ACE on a directory until we're done
+ * copying the *contents* of the directory.)
+ *
+ * The other big difference from copyfile (for the moment) is that copytree()
+ * will use a call-back function to pass along information about what is
+ * about to be copied, and whether or not it succeeded.
+ *
+ * copytree() is called from copyfile() -- but copytree() itself then calls
+ * copyfile() to copy each individual object.
+ *
+ * XXX - no effort is made to handle overlapping hierarchies at the moment.
+ *
+ */
+
+static int
+copytree(copyfile_state_t s)
+{
+ char *slash;
+ int retval = 0;
+ int (*sfunc)(const char *, struct stat *);
+ copyfile_callback_t status = NULL;
+ char srcisdir = 0, dstisdir = 0, dstexists = 0;
+ struct stat sbuf;
+ char *src, *dst;
+ const char *dstpathsep = "";
+#ifdef NOTYET
+ char srcpath[PATH_MAX * 2 + 1], dstpath[PATH_MAX * 2 + 1];
+#endif
+ char *srcroot;
+ FTS *fts = NULL;
+ FTSENT *ftsent;
+ ssize_t offset = 0;
+ const char *paths[2] = { 0 };
+ unsigned int flags = 0;
+ int fts_flags = FTS_NOCHDIR;
+
+ if (s == NULL) {
+ errno = EINVAL;
+ retval = -1;
+ goto done;
+ }
+ if (s->flags & (COPYFILE_MOVE | COPYFILE_UNLINK | COPYFILE_CHECK | COPYFILE_PACK | COPYFILE_UNPACK)) {
+ errno = EINVAL;
+ retval = -1;
+ goto done;
+ }
+
+ flags = s->flags & (COPYFILE_ALL | COPYFILE_NOFOLLOW | COPYFILE_VERBOSE);
+
+ paths[0] = src = s->src;
+ dst = s->dst;
+
+ if (src == NULL || dst == NULL) {
+ errno = EINVAL;
+ retval = -1;
+ goto done;
+ }
+
+ sfunc = (flags & COPYFILE_NOFOLLOW_SRC) ? lstat : stat;
+ if ((sfunc)(src, &sbuf) == -1) {
+ retval = -1;
+ goto done;
+ }
+ if (sbuf.st_mode & S_IFDIR) {
+ srcisdir = 1;
+ }
+
+ sfunc = (flags & COPYFILE_NOFOLLOW_DST) ? lstat : stat;
+ if ((sfunc)(dst, &sbuf) == -1) {
+ if (errno != ENOENT) {
+ retval = -1;
+ goto done;
+ }
+ } else {
+ dstexists = 1;
+ if ((sbuf.st_mode & S_IFMT) == S_IFDIR) {
+ dstisdir = 1;
+ }
+ }
+
+#ifdef NOTYET
+ // This doesn't handle filesystem crossing and case sensitivity
+ // So there's got to be a better way
+
+ if (realpath(src, srcpath) == NULL) {
+ retval = -1;
+ goto done;
+ }
+
+ if (realpath(dst, dstpath) == NULL &&
+ (errno == ENOENT && realpath(dirname(dst), dstpath) == NULL)) {
+ retval = -1;
+ goto done;
+ }
+ if (strstr(srcpath, dstpath) != NULL) {
+ errno = EINVAL;
+ retval = -1;
+ goto done;
+ }
+#endif
+ srcroot = basename((char*)src);
+ if (srcroot == NULL) {
+ retval = -1;
+ goto done;
+ }
+
+ /*
+ * To work on as well:
+ * We have a few cases when copying a hierarchy:
+ * 1) src is a non-directory, dst is a directory;
+ * 2) src is a non-directory, dst is a non-directory;
+ * 3) src is a non-directory, dst does not exist;
+ * 4) src is a directory, dst is a directory;
+ * 5) src is a directory, dst is a non-directory;
+ * 6) src is a directory, dst does not exist
+ *
+ * (1) copies src to dst/basename(src).
+ * (2) fails if COPYFILE_EXCLUSIVE is set, otherwise copies src to dst.
+ * (3) and (6) copy src to the name dst.
+ * (4) copies the contents of src to the contents of dst.
+ * (5) is an error.
+ */
+
+ if (dstisdir) {
+ // copy /path/to/src to /path/to/dst/src
+ // Append "/" and (fts_path - strlen(basename(src))) to dst?
+ dstpathsep = "/";
+ slash = strrchr(src, '/');
+ if (slash == NULL)
+ offset = 0;
+ else
+ offset = slash - src + 1;
+ } else {
+ // copy /path/to/src to /path/to/dst
+ // append (fts_path + strlen(src)) to dst?
+ dstpathsep = "";
+ offset = strlen(src);
+ }
+
+ if (s->flags | COPYFILE_NOFOLLOW_SRC)
+ fts_flags |= FTS_PHYSICAL;
+ else
+ fts_flags |= FTS_LOGICAL;
+
+ fts = fts_open((char * const *)paths, fts_flags, NULL);
+
+ status = s->statuscb;
+ while ((ftsent = fts_read(fts)) != NULL) {
+ int rv = 0;
+ char *dstfile = NULL;
+ int cmd = 0;
+ copyfile_state_t tstate = copyfile_state_alloc();
+ if (tstate == NULL) {
+ errno = ENOMEM;
+ retval = -1;
+ break;
+ }
+ tstate->statuscb = s->statuscb;
+ tstate->ctx = s->ctx;
+ asprintf(&dstfile, "%s%s%s", dst, dstpathsep, ftsent->fts_path + offset);
+ if (dstfile == NULL) {
+ copyfile_state_free(tstate);
+ errno = ENOMEM;
+ retval = -1;
+ break;
+ }
+ switch (ftsent->fts_info) {
+ case FTS_D:
+ tstate->internal_flags |= cfDelayAce;
+ cmd = COPYFILE_RECURSE_DIR;
+ break;
+ case FTS_SL:
+ case FTS_SLNONE:
+ case FTS_DEFAULT:
+ case FTS_F:
+ cmd = COPYFILE_RECURSE_FILE;
+ break;
+ case FTS_DP:
+ cmd = COPYFILE_RECURSE_DIR_CLEANUP;
+ break;
+ case FTS_DNR:
+ case FTS_ERR:
+ case FTS_NS:
+ case FTS_NSOK:
+ default:
+ errno = ftsent->fts_errno;
+ if (status) {
+ rv = (*status)(COPYFILE_RECURSE_ERROR, COPYFILE_ERR, tstate, ftsent->fts_path, dstfile, s->ctx);
+ if (rv == COPYFILE_SKIP || rv == COPYFILE_CONTINUE) {
+ errno = 0;
+ goto skipit;
+ }
+ if (rv == COPYFILE_QUIT) {
+ retval = -1;
+ goto stopit;
+ }
+ } else {
+ retval = -1;
+ goto stopit;
+ }
+ case FTS_DOT:
+ goto skipit;
+
+ }
+
+ if (cmd == COPYFILE_RECURSE_DIR || cmd == COPYFILE_RECURSE_FILE) {
+ if (status) {
+ rv = (*status)(cmd, COPYFILE_START, tstate, ftsent->fts_path, dstfile, s->ctx);
+ if (rv == COPYFILE_SKIP) {
+ if (cmd == COPYFILE_RECURSE_DIR) {
+ rv = fts_set(fts, ftsent, FTS_SKIP);
+ if (rv == -1) {
+ rv = (*status)(0, COPYFILE_ERR, tstate, ftsent->fts_path, dstfile, s->ctx);
+ if (rv == COPYFILE_QUIT)
+ retval = -1;
+ }
+ }
+ goto skipit;
+ }
+ if (rv == COPYFILE_QUIT) {
+ retval = -1; errno = 0;
+ goto stopit;
+ }
+ }
+ rv = copyfile(ftsent->fts_path, dstfile, tstate, flags);
+ if (rv < 0) {
+ if (status) {
+ rv = (*status)(cmd, COPYFILE_ERR, tstate, ftsent->fts_path, dstfile, s->ctx);
+ if (rv == COPYFILE_QUIT) {
+ retval = -1;
+ goto stopit;
+ } else
+ rv = 0;
+ goto skipit;
+ } else {
+ retval = -1;
+ goto stopit;
+ }
+ }
+ if (status) {
+ rv = (*status)(cmd, COPYFILE_FINISH, tstate, ftsent->fts_path, dstfile, s->ctx);
+ if (rv == COPYFILE_QUIT) {
+ retval = -1; errno = 0;
+ goto stopit;
+ }
+ }
+ } else if (cmd == COPYFILE_RECURSE_DIR_CLEANUP) {
+ int tfd;
+
+ if (status) {
+ rv = (*status)(cmd, COPYFILE_START, tstate, ftsent->fts_path, dstfile, s->ctx);
+ if (rv == COPYFILE_QUIT) {
+ retval = -1; errno = 0;
+ goto stopit;
+ } else if (rv == COPYFILE_SKIP) {
+ rv = 0;
+ goto skipit;
+ }
+ }
+ tfd = open(dstfile, O_RDONLY);
+ if (tfd != -1) {
+ struct stat sb;
+ if (s->flags & COPYFILE_STAT) {
+ (s->flags & COPYFILE_NOFOLLOW_SRC ? lstat : stat)(ftsent->fts_path, &sb);
+ } else {
+ (s->flags & COPYFILE_NOFOLLOW_DST ? lstat : stat)(dstfile, &sb);
+ }
+ remove_uberace(tfd, &sb);
+ close(tfd);
+ if (status) {
+ rv = (*status)(COPYFILE_RECURSE_DIR_CLEANUP, COPYFILE_FINISH, tstate, ftsent->fts_path, dstfile, s->ctx);
+ if (rv == COPYFILE_QUIT) {
+ rv = -1; errno = 0;
+ goto stopit;
+ }
+ }
+ } else {
+ if (status) {
+ rv = (*status)(COPYFILE_RECURSE_DIR_CLEANUP, COPYFILE_ERR, tstate, ftsent->fts_path, dstfile, s->ctx);
+ if (rv == COPYFILE_QUIT) {
+ retval = -1;
+ goto stopit;
+ } else if (rv == COPYFILE_SKIP || rv == COPYFILE_CONTINUE) {
+ if (rv == COPYFILE_CONTINUE)
+ errno = 0;
+ retval = 0;
+ goto skipit;
+ }
+ } else {
+ retval = -1;
+ goto stopit;
+ }
+ }
+ rv = 0;
+ }
+skipit:
+stopit:
+ copyfile_state_free(tstate);
+ free(dstfile);
+ if (retval == -1)
+ break;
+ }
+
+done:
+ if (fts)
+ fts_close(fts);
+
+ return retval;
+}
+