+ if (name == NULL || name[0] == '\0') {
+ return (EINVAL);
+ }
+ namelen = strnlen(name, XATTR_MAXNAMELEN);
+ if (name[namelen] != '\0')
+ return (ENAMETOOLONG);
+
+ if (utf8_validatestr((const unsigned char *)name, namelen) != 0)
+ return (EINVAL);
+
+ return (0);
+}
+
+
+/*
+ * Determine whether an EA is a protected system attribute.
+ */
+int
+xattr_protected(const char *attrname)
+{
+ return(!strncmp(attrname, "com.apple.system.", 17));
+}
+
+
+#if NAMEDSTREAMS
+/*
+ * Obtain a named stream from vnode vp.
+ */
+errno_t
+vnode_getnamedstream(vnode_t vp, vnode_t *svpp, const char *name, enum nsoperation op, int flags, vfs_context_t context)
+{
+ int error;
+
+ if (vp->v_mount->mnt_kern_flag & MNTK_NAMED_STREAMS)
+ error = VNOP_GETNAMEDSTREAM(vp, svpp, name, op, flags, context);
+ else
+ error = default_getnamedstream(vp, svpp, name, op, context);
+
+ if (error == 0) {
+ uint32_t streamflags = VISNAMEDSTREAM;
+ vnode_t svp = *svpp;
+
+ if ((vp->v_mount->mnt_kern_flag & MNTK_NAMED_STREAMS) == 0) {
+ streamflags |= VISSHADOW;
+ }
+
+ /* Tag the vnode. */
+ vnode_lock_spin(svp);
+ svp->v_flag |= streamflags;
+ vnode_unlock(svp);
+
+ /* Tag the parent so we know to flush credentials for streams on setattr */
+ vnode_lock_spin(vp);
+ vp->v_lflag |= VL_HASSTREAMS;
+ vnode_unlock(vp);
+
+ /* Make the file it's parent.
+ * Note: This parent link helps us distinguish vnodes for
+ * shadow stream files from vnodes for resource fork on file
+ * systems that support namedstream natively (both have
+ * VISNAMEDSTREAM set) by allowing access to mount structure
+ * for checking MNTK_NAMED_STREAMS bit at many places in the
+ * code.
+ */
+ vnode_update_identity(svp, vp, NULL, 0, 0, VNODE_UPDATE_PARENT);
+ }
+
+ return (error);
+}
+
+/*
+ * Make a named stream for vnode vp.
+ */
+errno_t
+vnode_makenamedstream(vnode_t vp, vnode_t *svpp, const char *name, int flags, vfs_context_t context)
+{
+ int error;
+
+ if (vp->v_mount->mnt_kern_flag & MNTK_NAMED_STREAMS)
+ error = VNOP_MAKENAMEDSTREAM(vp, svpp, name, flags, context);
+ else
+ error = default_makenamedstream(vp, svpp, name, context);
+
+ if (error == 0) {
+ uint32_t streamflags = VISNAMEDSTREAM;
+ vnode_t svp = *svpp;
+
+ /* Tag the vnode. */
+ if ((vp->v_mount->mnt_kern_flag & MNTK_NAMED_STREAMS) == 0) {
+ streamflags |= VISSHADOW;
+ }
+
+ /* Tag the vnode. */
+ vnode_lock_spin(svp);
+ svp->v_flag |= streamflags;
+ vnode_unlock(svp);
+
+ /* Tag the parent so we know to flush credentials for streams on setattr */
+ vnode_lock_spin(vp);
+ vp->v_lflag |= VL_HASSTREAMS;
+ vnode_unlock(vp);
+
+ /* Make the file it's parent.
+ * Note: This parent link helps us distinguish vnodes for
+ * shadow stream files from vnodes for resource fork on file
+ * systems that support namedstream natively (both have
+ * VISNAMEDSTREAM set) by allowing access to mount structure
+ * for checking MNTK_NAMED_STREAMS bit at many places in the
+ * code.
+ */
+ vnode_update_identity(svp, vp, NULL, 0, 0, VNODE_UPDATE_PARENT);
+ }
+ return (error);
+}
+
+/*
+ * Remove a named stream from vnode vp.
+ */
+errno_t
+vnode_removenamedstream(vnode_t vp, vnode_t svp, const char *name, int flags, vfs_context_t context)
+{
+ int error;
+
+ if (vp->v_mount->mnt_kern_flag & MNTK_NAMED_STREAMS)
+ error = VNOP_REMOVENAMEDSTREAM(vp, svp, name, flags, context);
+ else
+ error = default_removenamedstream(vp, name, context);
+
+ return (error);
+}
+
+#define NS_IOBUFSIZE (128 * 1024)
+
+/*
+ * Release a named stream shadow file.
+ *
+ * Note: This function is called from two places where we do not need
+ * to check if the vnode has any references held before deleting the
+ * shadow file. Once from vclean() when the vnode is being reclaimed
+ * and we do not hold any references on the vnode. Second time from
+ * default_getnamedstream() when we get an error during shadow stream
+ * file initialization so that other processes who are waiting for the
+ * shadow stream file initialization by the creator will get opportunity
+ * to create and initialize the file again.
+ */
+errno_t
+vnode_relenamedstream(vnode_t vp, vnode_t svp, vfs_context_t context)
+{
+ vnode_t dvp;
+ struct componentname cn;
+ char tmpname[80];
+ errno_t err;
+
+ cache_purge(svp);
+
+ vnode_lock(svp);
+ MAKE_SHADOW_NAME(vp, tmpname);
+ vnode_unlock(svp);
+
+ cn.cn_nameiop = DELETE;
+ cn.cn_flags = ISLASTCN;
+ cn.cn_context = context;
+ cn.cn_pnbuf = tmpname;
+ cn.cn_pnlen = sizeof(tmpname);
+ cn.cn_nameptr = cn.cn_pnbuf;
+ cn.cn_namelen = strlen(tmpname);
+
+ /* Obtain the vnode for the shadow files directory. */
+ err = get_shadow_dir(&dvp, context);
+ if (err != 0) {
+ return err;
+ }
+
+ (void) VNOP_REMOVE(dvp, svp, &cn, 0, context);
+ vnode_put(dvp);
+
+ return (0);
+}
+
+/*
+ * Flush a named stream shadow file.
+ */
+errno_t
+vnode_flushnamedstream(vnode_t vp, vnode_t svp, vfs_context_t context)
+{
+ struct vnode_attr va;
+ uio_t auio = NULL;
+ caddr_t bufptr = NULL;
+ size_t bufsize = 0;
+ size_t offset;
+ size_t iosize;
+ size_t datasize;
+ int error;
+
+ VATTR_INIT(&va);
+ VATTR_WANTED(&va, va_data_size);
+ if (VNOP_GETATTR(svp, &va, context) != 0 ||
+ !VATTR_IS_SUPPORTED(&va, va_data_size)) {
+ return (0);
+ }
+ datasize = va.va_data_size;
+ if ((datasize == 0)) {
+ (void) default_removexattr(vp, XATTR_RESOURCEFORK_NAME, 0, context);
+ return (0);
+ }
+
+ iosize = bufsize = MIN(datasize, NS_IOBUFSIZE);
+ if (kmem_alloc(kernel_map, (vm_offset_t *)&bufptr, bufsize)) {
+ return (ENOMEM);
+ }
+ auio = uio_create(1, 0, UIO_SYSSPACE, UIO_READ);
+ offset = 0;
+
+ /*
+ * Copy the shadow stream file data into the resource fork.
+ */
+ error = VNOP_OPEN(svp, 0, context);
+ if (error) {
+ printf("vnode_flushnamedstream: err %d opening file\n", error);
+ goto out;
+ }
+ while (offset < datasize) {
+ iosize = MIN(datasize - offset, iosize);
+
+ uio_reset(auio, offset, UIO_SYSSPACE, UIO_READ);
+ uio_addiov(auio, (uintptr_t)bufptr, iosize);
+ error = VNOP_READ(svp, auio, 0, context);
+ if (error) {
+ break;
+ }
+ /* Since there's no truncate xattr we must remove the resource fork. */
+ if (offset == 0) {
+ error = default_removexattr(vp, XATTR_RESOURCEFORK_NAME, 0, context);
+ if ((error != 0) && (error != ENOATTR)) {
+ break;
+ }
+ }
+ uio_reset(auio, offset, UIO_SYSSPACE, UIO_WRITE);
+ uio_addiov(auio, (uintptr_t)bufptr, iosize);
+ error = vn_setxattr(vp, XATTR_RESOURCEFORK_NAME, auio, XATTR_NOSECURITY, context);
+ if (error) {
+ break;
+ }
+ offset += iosize;
+ }
+ (void) VNOP_CLOSE(svp, 0, context);
+out:
+ if (bufptr) {
+ kmem_free(kernel_map, (vm_offset_t)bufptr, bufsize);
+ }
+ if (auio) {
+ uio_free(auio);
+ }
+ return (error);
+}
+
+
+static int
+getshadowfile(vnode_t vp, vnode_t *svpp, int makestream, size_t *rsrcsize,
+ int *creator, vfs_context_t context)
+{
+ vnode_t dvp = NULLVP;
+ vnode_t svp = NULLVP;
+ struct componentname cn;
+ struct vnode_attr va;
+ char tmpname[80];
+ size_t datasize = 0;
+ int error = 0;
+
+ *creator = 0;
+
+ /* Establish a unique file name. */
+ MAKE_SHADOW_NAME(vp, tmpname);
+ bzero(&cn, sizeof(cn));
+ cn.cn_nameiop = LOOKUP;
+ cn.cn_flags = ISLASTCN;
+ cn.cn_context = context;
+ cn.cn_pnbuf = tmpname;
+ cn.cn_pnlen = sizeof(tmpname);
+ cn.cn_nameptr = cn.cn_pnbuf;
+ cn.cn_namelen = strlen(tmpname);
+
+ /* Pick up uid, gid, mode and date from original file. */
+ VATTR_INIT(&va);
+ VATTR_WANTED(&va, va_uid);
+ VATTR_WANTED(&va, va_gid);
+ VATTR_WANTED(&va, va_mode);
+ VATTR_WANTED(&va, va_create_time);
+ VATTR_WANTED(&va, va_modify_time);
+ if (VNOP_GETATTR(vp, &va, context) != 0 ||
+ !VATTR_IS_SUPPORTED(&va, va_uid) ||
+ !VATTR_IS_SUPPORTED(&va, va_gid) ||
+ !VATTR_IS_SUPPORTED(&va, va_mode)) {
+ va.va_uid = KAUTH_UID_NONE;
+ va.va_gid = KAUTH_GID_NONE;
+ va.va_mode = S_IRUSR | S_IWUSR;
+ }
+ va.va_vaflags = VA_EXCLUSIVE;
+ VATTR_SET(&va, va_type, VREG);
+ /* We no longer change the access, but we still hide it. */
+ VATTR_SET(&va, va_flags, UF_HIDDEN);
+
+ /* Obtain the vnode for the shadow files directory. */
+ if (get_shadow_dir(&dvp, context) != 0) {
+ error = ENOTDIR;
+ goto out;
+ }
+ if (!makestream) {
+ /* See if someone else already has it open. */
+ if (VNOP_LOOKUP(dvp, &svp, &cn, context) == 0) {
+ /* Double check existence by asking for size. */
+ VATTR_INIT(&va);
+ VATTR_WANTED(&va, va_data_size);
+ if (VNOP_GETATTR(svp, &va, context) == 0 &&
+ VATTR_IS_SUPPORTED(&va, va_data_size)) {
+ goto out; /* OK to use. */
+ }
+ }
+
+ /* Otherwise make sure the resource fork data exists. */
+ error = vn_getxattr(vp, XATTR_RESOURCEFORK_NAME, NULL, &datasize,
+ XATTR_NOSECURITY, context);
+ /*
+ * To maintain binary compatibility with legacy Carbon
+ * emulated resource fork support, if the resource fork
+ * doesn't exist but the Finder Info does, then act as
+ * if an empty resource fork is present (see 4724359).
+ */
+ if ((error == ENOATTR) &&
+ (vn_getxattr(vp, XATTR_FINDERINFO_NAME, NULL, &datasize,
+ XATTR_NOSECURITY, context) == 0)) {
+ datasize = 0;
+ error = 0;
+ } else {
+ if (error) {
+ goto out;
+ }
+
+ /* If the resource fork exists, its size is expected to be non-zero. */
+ if (datasize == 0) {
+ error = ENOATTR;
+ goto out;
+ }
+ }
+ }
+ /* Create the shadow stream file. */
+ error = VNOP_CREATE(dvp, &svp, &cn, &va, context);
+ if (error == 0) {
+ vnode_recycle(svp);
+ *creator = 1;
+ } else if ((error == EEXIST) && !makestream) {
+ error = VNOP_LOOKUP(dvp, &svp, &cn, context);
+ }
+out:
+ if (dvp) {
+ vnode_put(dvp);
+ }
+ if (error) {
+ /* On errors, clean up shadow stream file. */
+ if (svp) {
+ vnode_put(svp);
+ svp = NULLVP;
+ }
+ }
+ *svpp = svp;
+ if (rsrcsize) {
+ *rsrcsize = datasize;
+ }
+ return (error);
+}
+
+
+static int
+default_getnamedstream(vnode_t vp, vnode_t *svpp, const char *name, enum nsoperation op, vfs_context_t context)
+{
+ vnode_t svp = NULLVP;
+ uio_t auio = NULL;
+ caddr_t bufptr = NULL;
+ size_t bufsize = 0;
+ size_t datasize = 0;
+ int creator;
+ int error;
+
+ /*
+ * Only the "com.apple.ResourceFork" stream is supported here.
+ */
+ if (bcmp(name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) != 0) {
+ *svpp = NULLVP;
+ return (ENOATTR);
+ }
+retry:
+ /*
+ * Obtain a shadow file for the resource fork I/O.
+ */
+ error = getshadowfile(vp, &svp, 0, &datasize, &creator, context);
+ if (error) {
+ *svpp = NULLVP;
+ return (error);
+ }
+
+ /*
+ * The creator of the shadow file provides its file data,
+ * all other threads should wait until its ready. In order to
+ * prevent a deadlock during error codepaths, we need to check if the
+ * vnode is being created, or if it has failed out. Regardless of success or
+ * failure, we set the VISSHADOW bit on the vnode, so we check that
+ * if the vnode's flags don't have VISNAMEDSTREAM set. If it doesn't,
+ * then we can infer the creator isn't done yet. If it's there, but
+ * VISNAMEDSTREAM is not set, then we can infer it errored out and we should
+ * try again.
+ */
+ if (!creator) {
+ vnode_lock(svp);
+ if (svp->v_flag & VISNAMEDSTREAM) {
+ /* data is ready, go use it */
+ vnode_unlock(svp);
+ goto out;
+ } else {
+ /* It's not ready, wait for it (sleep using v_parent as channel) */
+ if ((svp->v_flag & VISSHADOW)) {
+ /*
+ * No VISNAMEDSTREAM, but we did see VISSHADOW, indicating that the other
+ * thread is done with this vnode. Just unlock the vnode and try again
+ */
+ vnode_unlock(svp);
+ }
+ else {
+ /* Otherwise, sleep if the shadow file is not created yet */
+ msleep((caddr_t)&svp->v_parent, &svp->v_lock, PINOD | PDROP,
+ "getnamedstream", NULL);
+ }
+ vnode_put(svp);
+ svp = NULLVP;
+ goto retry;
+ }
+ }
+
+ /*
+ * Copy the real resource fork data into shadow stream file.
+ */
+ if (op == NS_OPEN && datasize != 0) {
+ size_t offset;
+ size_t iosize;
+
+ iosize = bufsize = MIN(datasize, NS_IOBUFSIZE);
+ if (kmem_alloc(kernel_map, (vm_offset_t *)&bufptr, bufsize)) {
+ error = ENOMEM;
+ goto out;
+ }
+
+ auio = uio_create(1, 0, UIO_SYSSPACE, UIO_READ);
+ offset = 0;
+
+ error = VNOP_OPEN(svp, 0, context);
+ if (error) {
+ goto out;
+ }
+ while (offset < datasize) {
+ size_t tmpsize;
+
+ iosize = MIN(datasize - offset, iosize);
+
+ uio_reset(auio, offset, UIO_SYSSPACE, UIO_READ);
+ uio_addiov(auio, (uintptr_t)bufptr, iosize);
+ error = vn_getxattr(vp, XATTR_RESOURCEFORK_NAME, auio, &tmpsize,
+ XATTR_NOSECURITY, context);
+ if (error) {
+ break;
+ }
+
+ uio_reset(auio, offset, UIO_SYSSPACE, UIO_WRITE);
+ uio_addiov(auio, (uintptr_t)bufptr, iosize);
+ error = VNOP_WRITE(svp, auio, 0, context);
+ if (error) {
+ break;
+ }
+ offset += iosize;
+ }
+ (void) VNOP_CLOSE(svp, 0, context);
+ }
+out:
+ /* Wake up anyone waiting for svp file content */
+ if (creator) {
+ if (error == 0) {
+ vnode_lock(svp);
+ /* VISSHADOW would be set later on anyway, so we set it now */
+ svp->v_flag |= (VISNAMEDSTREAM | VISSHADOW);
+ wakeup((caddr_t)&svp->v_parent);
+ vnode_unlock(svp);
+ } else {
+ /* On post create errors, get rid of the shadow file. This
+ * way if there is another process waiting for initialization
+ * of the shadowfile by the current process will wake up and
+ * retry by creating and initializing the shadow file again.
+ * Also add the VISSHADOW bit here to indicate we're done operating
+ * on this vnode.
+ */
+ (void)vnode_relenamedstream(vp, svp, context);
+ vnode_lock (svp);
+ svp->v_flag |= VISSHADOW;
+ wakeup((caddr_t)&svp->v_parent);
+ vnode_unlock(svp);
+ }
+ }
+
+ if (bufptr) {
+ kmem_free(kernel_map, (vm_offset_t)bufptr, bufsize);
+ }
+ if (auio) {
+ uio_free(auio);
+ }
+ if (error) {
+ /* On errors, clean up shadow stream file. */
+ if (svp) {
+ vnode_put(svp);
+ svp = NULLVP;
+ }
+ }
+ *svpp = svp;
+ return (error);
+}
+
+static int
+default_makenamedstream(vnode_t vp, vnode_t *svpp, const char *name, vfs_context_t context)
+{
+ int creator;
+ int error;
+
+ /*
+ * Only the "com.apple.ResourceFork" stream is supported here.
+ */
+ if (bcmp(name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) != 0) {
+ *svpp = NULLVP;
+ return (ENOATTR);
+ }
+ error = getshadowfile(vp, svpp, 1, NULL, &creator, context);
+
+ /*
+ * Wake up any waiters over in default_getnamedstream().
+ */
+ if ((error == 0) && (*svpp != NULL) && creator) {
+ vnode_t svp = *svpp;
+
+ vnode_lock(svp);
+ /* If we're the creator, mark it as a named stream */
+ svp->v_flag |= (VISNAMEDSTREAM | VISSHADOW);
+ /* Wakeup any waiters on the v_parent channel */
+ wakeup((caddr_t)&svp->v_parent);
+ vnode_unlock(svp);
+
+ }
+
+ return (error);
+}
+
+static int
+default_removenamedstream(vnode_t vp, const char *name, vfs_context_t context)
+{
+ /*
+ * Only the "com.apple.ResourceFork" stream is supported here.
+ */
+ if (bcmp(name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) != 0) {
+ return (ENOATTR);
+ }
+ /*
+ * XXX - what about other opened instances?
+ */
+ return default_removexattr(vp, XATTR_RESOURCEFORK_NAME, 0, context);
+}
+
+static int
+get_shadow_dir(vnode_t *sdvpp, vfs_context_t context)
+{
+ vnode_t dvp = NULLVP;
+ vnode_t sdvp = NULLVP;
+ struct componentname cn;
+ struct vnode_attr va;
+ char tmpname[80];
+ uint32_t tmp_fsid;
+ int error;
+
+ /* Check if we've already created it. */
+ if (shadow_dvp != NULLVP) {
+ if ((error = vnode_getwithvid(shadow_dvp, shadow_vid))) {
+ shadow_dvp = NULLVP;
+ } else {
+ *sdvpp = shadow_dvp;
+ return (0);
+ }
+ }
+
+ /* Obtain the vnode for "/var/run" directory. */
+ if (vnode_lookup("/var/run", 0, &dvp, context) != 0) {
+ error = ENOTSUP;
+ goto out;
+ }
+
+ /* Create the shadow stream directory. */
+ snprintf(tmpname, sizeof(tmpname), ".vfs_rsrc_streams_%p%x",
+ (void*)rootvnode, shadow_sequence);
+ bzero(&cn, sizeof(cn));
+ cn.cn_nameiop = LOOKUP;
+ cn.cn_flags = ISLASTCN;
+ cn.cn_context = context;
+ cn.cn_pnbuf = tmpname;
+ cn.cn_pnlen = sizeof(tmpname);
+ cn.cn_nameptr = cn.cn_pnbuf;
+ cn.cn_namelen = strlen(tmpname);
+
+ /*
+ * owned by root, only readable by root, hidden
+ */
+ VATTR_INIT(&va);
+ VATTR_SET(&va, va_uid, 0);
+ VATTR_SET(&va, va_gid, 0);
+ VATTR_SET(&va, va_mode, S_IRUSR | S_IXUSR);
+ VATTR_SET(&va, va_type, VDIR);
+ VATTR_SET(&va, va_flags, UF_HIDDEN);
+ va.va_vaflags = VA_EXCLUSIVE;
+
+ error = VNOP_MKDIR(dvp, &sdvp, &cn, &va, context);
+
+ /*
+ * There can be only one winner for an exclusive create.
+ */
+ if (error == 0) {
+ /* Take a long term ref to keep this dir around. */
+ error = vnode_ref(sdvp);
+ if (error == 0) {
+ shadow_dvp = sdvp;
+ shadow_vid = sdvp->v_id;
+ }
+ } else if (error == EEXIST) {
+ /* loser has to look up directory */
+ error = VNOP_LOOKUP(dvp, &sdvp, &cn, context);
+ if (error == 0) {
+ /* Make sure its in fact a directory */
+ if (sdvp->v_type != VDIR) {
+ goto baddir;
+ }
+ /* Obtain the fsid for /var/run directory */
+ VATTR_INIT(&va);
+ VATTR_WANTED(&va, va_fsid);
+ if (VNOP_GETATTR(dvp, &va, context) != 0 ||
+ !VATTR_IS_SUPPORTED(&va, va_fsid)) {
+ goto baddir;
+ }
+ tmp_fsid = va.va_fsid;
+
+ VATTR_INIT(&va);
+ VATTR_WANTED(&va, va_uid);
+ VATTR_WANTED(&va, va_gid);
+ VATTR_WANTED(&va, va_mode);
+ VATTR_WANTED(&va, va_fsid);
+ VATTR_WANTED(&va, va_dirlinkcount);
+ VATTR_WANTED(&va, va_acl);
+ /* Provide defaults for attrs that may not be supported */
+ va.va_dirlinkcount = 1;
+ va.va_acl = (kauth_acl_t) KAUTH_FILESEC_NONE;
+
+ if (VNOP_GETATTR(sdvp, &va, context) != 0 ||
+ !VATTR_IS_SUPPORTED(&va, va_uid) ||
+ !VATTR_IS_SUPPORTED(&va, va_gid) ||
+ !VATTR_IS_SUPPORTED(&va, va_mode) ||
+ !VATTR_IS_SUPPORTED(&va, va_fsid)) {
+ goto baddir;
+ }
+ /*
+ * Make sure its what we want:
+ * - owned by root
+ * - not writable by anyone
+ * - on same file system as /tmp
+ * - not a hard-linked directory
+ * - no ACLs (they might grant write access)
+ */
+ if ((va.va_uid != 0) || (va.va_gid != 0) ||
+ (va.va_mode & (S_IWUSR | S_IRWXG | S_IRWXO)) ||
+ (va.va_fsid != tmp_fsid) ||
+ (va.va_dirlinkcount != 1) ||
+ (va.va_acl != (kauth_acl_t) KAUTH_FILESEC_NONE)) {
+ goto baddir;
+ }
+ }