+ /* Look up item in shadow dir */
+ bzero(&cn, sizeof(cn));
+ cn.cn_nameiop = LOOKUP;
+ cn.cn_flags = ISLASTCN | CN_ALLOWRSRCFORK;
+ cn.cn_context = kernelctx;
+ cn.cn_pnbuf = tmpname;
+ cn.cn_pnlen = sizeof(tmpname);
+ cn.cn_nameptr = cn.cn_pnbuf;
+ cn.cn_namelen = strlen(tmpname);
+
+ if (VNOP_LOOKUP(shadow_dvp, &shadowfile, &cn, kernelctx) == 0) {
+ /* is the pointer the same? */
+ if (shadowfile == vp) {
+ error = 0;
+ } else {
+ error = EPERM;
+ }
+ /* drop the iocount acquired */
+ vnode_put(shadowfile);
+ }
+
+ /* Drop iocount on shadow dir */
+ vnode_put(shadow_dvp);
+ return error;
+}
+
+/*
+ * Access or create the shadow file as needed.
+ *
+ * 'makestream' with non-zero value means that we need to guarantee we were the
+ * creator of the shadow file.
+ *
+ * 'context' is the user supplied context for the original VFS operation that
+ * caused us to need a shadow file.
+ *
+ * int pointed to by 'creator' is nonzero if we created the shadowfile.
+ */
+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;
+ int retries = 0;
+ vfs_context_t kernelctx = vfs_context_kernel();
+
+retry_create:
+ *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) != 0) {
+ error = ENOTDIR;
+ goto out;
+ }
+ if (!makestream) {
+ /* See if someone else already has it open. */
+ if (VNOP_LOOKUP(dvp, &svp, &cn, kernelctx) == 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.
+ * Use the supplied context for accessing the AD file.
+ */
+ 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, kernelctx);
+ if (error == 0) {
+ vnode_recycle(svp);
+ *creator = 1;
+ } else if ((error == EEXIST) && !makestream) {
+ error = VNOP_LOOKUP(dvp, &svp, &cn, kernelctx);
+ } else if ((error == ENOENT) && !makestream) {
+ /*
+ * We could have raced with a rmdir on the shadow directory
+ * post-lookup. Retry from the beginning, 1x only, to
+ * try and see if we need to re-create the shadow directory
+ * in get_shadow_dir.
+ */
+ if (retries == 0) {
+ retries++;
+ if (dvp) {
+ vnode_put(dvp);
+ dvp = NULLVP;
+ }
+ if (svp) {
+ vnode_put(svp);
+ svp = NULLVP;
+ }
+ goto retry_create;
+ }
+ /* Otherwise, just error out normally below */
+ }
+
+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;
+
+ /* need the kernel context for accessing the shadowfile */
+ vfs_context_t kernelctx = vfs_context_kernel();
+
+ /*
+ * 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.
+ *
+ * Need to pass along the supplied context so that getshadowfile
+ * can access the AD file as needed, using it.
+ */
+ 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, VM_KERN_MEMORY_FILE)) {
+ error = ENOMEM;
+ goto out;
+ }
+
+ auio = uio_create(1, 0, UIO_SYSSPACE, UIO_READ);
+ offset = 0;
+
+ /* open the shadow file */
+ error = VNOP_OPEN(svp, 0, kernelctx);
+ 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);
+ /* use supplied ctx for AD file */
+ 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);
+ /* kernel context for writing shadowfile */
+ error = VNOP_WRITE(svp, auio, 0, kernelctx);
+ if (error) {
+ break;
+ }
+ offset += iosize;
+ }
+
+ /* close shadow file */
+ (void) VNOP_CLOSE(svp, 0, kernelctx);
+ }
+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);
+ 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;
+ }
+
+ /* Supply the context to getshadowfile so it can manipulate the AD file */
+ 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)
+{
+ vnode_t dvp = NULLVP;
+ vnode_t sdvp = NULLVP;
+ struct componentname cn;
+ struct vnode_attr va;
+ char tmpname[80];
+ uint32_t tmp_fsid;
+ int error;
+ vfs_context_t kernelctx = vfs_context_kernel();
+
+ bzero(tmpname, sizeof(tmpname));
+ MAKE_SHADOW_DIRNAME(rootvnode, tmpname);
+ /*
+ * Look up the shadow directory to ensure that it still exists.
+ * By looking it up, we get an iocounted dvp to use, and avoid some coherency issues
+ * in caching it when multiple threads may be trying to manipulate the pointers.
+ *
+ * Make sure to use the kernel context. We want a singular view of
+ * the shadow dir regardless of chrooted processes.
+ */
+ error = vnode_lookup(tmpname, 0, &sdvp, kernelctx);
+ if (error == 0) {
+ /*
+ * If we get here, then we have successfully looked up the shadow dir,
+ * and it has an iocount from the lookup. Return the vp in the output argument.
+ */
+ *sdvpp = sdvp;
+ return 0;
+ }
+ /* In the failure case, no iocount is acquired */
+ sdvp = NULLVP;
+ bzero(tmpname, sizeof(tmpname));
+
+ /*
+ * Obtain the vnode for "/var/run" directory using the kernel
+ * context.
+ *
+ * This is defined in the SHADOW_DIR_CONTAINER macro
+ */
+ if (vnode_lookup(SHADOW_DIR_CONTAINER, 0, &dvp, kernelctx) != 0) {
+ error = ENOTSUP;
+ goto out;
+ }
+
+ /*
+ * Create the shadow stream directory.
+ * 'dvp' below suggests the parent directory so
+ * we only need to provide the leaf entry name
+ */
+ MAKE_SHADOW_DIR_LEAF(rootvnode, tmpname);
+ bzero(&cn, sizeof(cn));
+ cn.cn_nameiop = LOOKUP;
+ cn.cn_flags = ISLASTCN;
+ cn.cn_context = kernelctx;
+ 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, kernelctx);
+
+ /*
+ * There can be only one winner for an exclusive create.
+ */
+ if (error == EEXIST) {
+ /* loser has to look up directory */
+ error = VNOP_LOOKUP(dvp, &sdvp, &cn, kernelctx);
+ 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, kernelctx) != 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, kernelctx) != 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 /var/run
+ * - 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;
+ }
+ }
+ }
+out:
+ if (dvp) {
+ vnode_put(dvp);
+ }
+ if (error) {
+ /* On errors, clean up shadow stream directory. */
+ if (sdvp) {
+ vnode_put(sdvp);
+ sdvp = NULLVP;
+ }
+ }
+ *sdvpp = sdvp;
+ return error;
+
+baddir:
+ /* This is not the dir we're looking for, move along */
+ ++shadow_sequence; /* try something else next time */
+ error = ENOTDIR;
+ goto out;
+}
+#endif /* NAMEDSTREAMS */
+
+
+#if CONFIG_APPLEDOUBLE
+/*
+ * Default Implementation (Non-native EA)
+ */
+
+
+/*
+ * 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.
+ */