static errno_t fuse_vfsop_sync(mount_t mp, int waitfor, vfs_context_t context) { uint64_t mntflags; struct fuse_sync_cargs args; int allerror = 0; fuse_trace_printf_vfsop(); mntflags = vfs_flags(mp); if (fuse_isdeadfs_mp(mp)) { return 0; } if (vfs_isupdate(mp)) { return 0; } if (vfs_isrdonly(mp)) { return EROFS; // should panic!? } /* * Write back each (modified) fuse node. */ args.context = context; args.waitfor = waitfor; args.error = 0; #if M_FUSE4X_ENABLE_BIGLOCK struct fuse_data *data = fuse_get_mpdata(mp); fuse_biglock_unlock(data->biglock); #endif vnode_iterate(mp, 0, fuse_sync_callback, (void *)&args); #if M_FUSE4X_ENABLE_BIGLOCK fuse_biglock_lock(data->biglock); #endif if (args.error) { allerror = args.error; } /* * For other types of stale file system information, such as: * * - fs control info * - quota information * - modified superblock */ return allerror; }
static errno_t fuse_vfsop_mount(mount_t mp, __unused vnode_t devvp, user_addr_t udata, vfs_context_t context) { int err = 0; int mntopts = 0; bool mounted = false; uint32_t max_read = ~0; size_t len; fuse_device_t fdev = NULL; struct fuse_data *data = NULL; fuse_mount_args fusefs_args; struct vfsstatfs *vfsstatfsp = vfs_statfs(mp); #if M_FUSE4X_ENABLE_BIGLOCK lck_mtx_t *biglock; #endif fuse_trace_printf_vfsop(); if (vfs_isupdate(mp)) { return ENOTSUP; } err = copyin(udata, &fusefs_args, sizeof(fusefs_args)); if (err) { return EINVAL; } /* * Interesting flags that we can receive from mount or may want to * otherwise forcibly set include: * * MNT_ASYNC * MNT_AUTOMOUNTED * MNT_DEFWRITE * MNT_DONTBROWSE * MNT_IGNORE_OWNERSHIP * MNT_JOURNALED * MNT_NODEV * MNT_NOEXEC * MNT_NOSUID * MNT_NOUSERXATTR * MNT_RDONLY * MNT_SYNCHRONOUS * MNT_UNION */ err = ENOTSUP; #if M_FUSE4X_ENABLE_UNSUPPORTED vfs_setlocklocal(mp); #endif /* M_FUSE4X_ENABLE_UNSUPPORTED */ /** Option Processing. **/ if (*fusefs_args.fstypename) { size_t typenamelen = strlen(fusefs_args.fstypename); if (typenamelen > FUSE_FSTYPENAME_MAXLEN) { return EINVAL; } snprintf(vfsstatfsp->f_fstypename, MFSTYPENAMELEN, "%s%s", FUSE_FSTYPENAME_PREFIX, fusefs_args.fstypename); } if (!*fusefs_args.fsname) return EINVAL; if ((fusefs_args.daemon_timeout > FUSE_MAX_DAEMON_TIMEOUT) || (fusefs_args.daemon_timeout < FUSE_MIN_DAEMON_TIMEOUT)) { return EINVAL; } if ((fusefs_args.init_timeout > FUSE_MAX_INIT_TIMEOUT) || (fusefs_args.init_timeout < FUSE_MIN_INIT_TIMEOUT)) { return EINVAL; } if (fusefs_args.altflags & FUSE_MOPT_SPARSE) { mntopts |= FSESS_SPARSE; } if (fusefs_args.altflags & FUSE_MOPT_AUTO_CACHE) { mntopts |= FSESS_AUTO_CACHE; } if (fusefs_args.altflags & FUSE_MOPT_AUTO_XATTR) { if (fusefs_args.altflags & FUSE_MOPT_NATIVE_XATTR) { return EINVAL; } mntopts |= FSESS_AUTO_XATTR; } else if (fusefs_args.altflags & FUSE_MOPT_NATIVE_XATTR) { mntopts |= FSESS_NATIVE_XATTR; } if (fusefs_args.altflags & FUSE_MOPT_JAIL_SYMLINKS) { mntopts |= FSESS_JAIL_SYMLINKS; } /* * Note that unlike Linux, which keeps allow_root in user-space and * passes allow_other in that case to the kernel, we let allow_root * reach the kernel. The 'if' ordering is important here. */ if (fusefs_args.altflags & FUSE_MOPT_ALLOW_ROOT) { int is_member = 0; if ((kauth_cred_ismember_gid(kauth_cred_get(), fuse_admin_group, &is_member) != 0) || !is_member) { log("fuse4x: caller is not a member of fuse4x admin group. " "Either add user (id=%d) to group (id=%d), " "or set correct '" SYSCTL_FUSE4X_TUNABLES_ADMIN "' sysctl value.\n", kauth_cred_getuid(kauth_cred_get()), fuse_admin_group); return EPERM; } mntopts |= FSESS_ALLOW_ROOT; } else if (fusefs_args.altflags & FUSE_MOPT_ALLOW_OTHER) { if (!fuse_allow_other && !fuse_vfs_context_issuser(context)) { int is_member = 0; if ((kauth_cred_ismember_gid(kauth_cred_get(), fuse_admin_group, &is_member) != 0) || !is_member) { log("fuse4x: caller is not a member of fuse4x admin group. " "Either add user (id=%d) to group (id=%d), " "or set correct '" SYSCTL_FUSE4X_TUNABLES_ADMIN "' sysctl value.\n", kauth_cred_getuid(kauth_cred_get()), fuse_admin_group); return EPERM; } } mntopts |= FSESS_ALLOW_OTHER; } if (fusefs_args.altflags & FUSE_MOPT_NO_APPLEDOUBLE) { mntopts |= FSESS_NO_APPLEDOUBLE; } if (fusefs_args.altflags & FUSE_MOPT_NO_APPLEXATTR) { mntopts |= FSESS_NO_APPLEXATTR; } if ((fusefs_args.altflags & FUSE_MOPT_FSID) && (fusefs_args.fsid != 0)) { fsid_t fsid; mount_t other_mp; uint32_t target_dev; target_dev = FUSE_MAKEDEV(FUSE_CUSTOM_FSID_DEVICE_MAJOR, fusefs_args.fsid); fsid.val[0] = target_dev; fsid.val[1] = FUSE_CUSTOM_FSID_VAL1; other_mp = vfs_getvfs(&fsid); if (other_mp != NULL) { return EPERM; } vfsstatfsp->f_fsid.val[0] = target_dev; vfsstatfsp->f_fsid.val[1] = FUSE_CUSTOM_FSID_VAL1; } else { vfs_getnewfsid(mp); } if (fusefs_args.altflags & FUSE_MOPT_NO_ATTRCACHE) { mntopts |= FSESS_NO_ATTRCACHE; } if (fusefs_args.altflags & FUSE_MOPT_NO_READAHEAD) { mntopts |= FSESS_NO_READAHEAD; } if (fusefs_args.altflags & (FUSE_MOPT_NO_UBC | FUSE_MOPT_DIRECT_IO)) { mntopts |= FSESS_NO_UBC; } if (fusefs_args.altflags & FUSE_MOPT_NO_VNCACHE) { mntopts |= FSESS_NO_VNCACHE; } if (fusefs_args.altflags & FUSE_MOPT_NEGATIVE_VNCACHE) { if (mntopts & FSESS_NO_VNCACHE) { return EINVAL; } mntopts |= FSESS_NEGATIVE_VNCACHE; } if (fusefs_args.altflags & FUSE_MOPT_NO_SYNCWRITES) { /* Cannot mix 'nosyncwrites' with 'noubc' or 'noreadahead'. */ if (mntopts & (FSESS_NO_READAHEAD | FSESS_NO_UBC)) { log("fuse4x: cannot mix 'nosyncwrites' with 'noubc' or 'noreadahead'\n"); return EINVAL; } mntopts |= FSESS_NO_SYNCWRITES; vfs_clearflags(mp, MNT_SYNCHRONOUS); vfs_setflags(mp, MNT_ASYNC); /* We check for this only if we have nosyncwrites in the first place. */ if (fusefs_args.altflags & FUSE_MOPT_NO_SYNCONCLOSE) { mntopts |= FSESS_NO_SYNCONCLOSE; } } else { vfs_clearflags(mp, MNT_ASYNC); vfs_setflags(mp, MNT_SYNCHRONOUS); } if (mntopts & FSESS_NO_UBC) { /* If no buffer cache, disallow exec from file system. */ vfs_setflags(mp, MNT_NOEXEC); } vfs_setauthopaque(mp); vfs_setauthopaqueaccess(mp); if ((fusefs_args.altflags & FUSE_MOPT_DEFAULT_PERMISSIONS) && (fusefs_args.altflags & FUSE_MOPT_DEFER_PERMISSIONS)) { return EINVAL; } if (fusefs_args.altflags & FUSE_MOPT_DEFAULT_PERMISSIONS) { mntopts |= FSESS_DEFAULT_PERMISSIONS; vfs_clearauthopaque(mp); } if (fusefs_args.altflags & FUSE_MOPT_DEFER_PERMISSIONS) { mntopts |= FSESS_DEFER_PERMISSIONS; } if (fusefs_args.altflags & FUSE_MOPT_EXTENDED_SECURITY) { mntopts |= FSESS_EXTENDED_SECURITY; vfs_setextendedsecurity(mp); } if (fusefs_args.altflags & FUSE_MOPT_LOCALVOL) { vfs_setflags(mp, MNT_LOCAL); } /* done checking incoming option bits */ err = 0; vfs_setfsprivate(mp, NULL); fdev = fuse_device_get(fusefs_args.rdev); if (!fdev) { log("fuse4x: invalid device file (number=%d)\n", fusefs_args.rdev); return EINVAL; } fuse_lck_mtx_lock(fdev->mtx); data = fdev->data; if (!data) { fuse_lck_mtx_unlock(fdev->mtx); return ENXIO; } #if M_FUSE4X_ENABLE_BIGLOCK biglock = data->biglock; fuse_biglock_lock(biglock); #endif if (data->dataflags & FSESS_MOUNTED) { #if M_FUSE4X_ENABLE_BIGLOCK fuse_biglock_unlock(biglock); #endif fuse_lck_mtx_unlock(fdev->mtx); return EALREADY; } if (!(data->dataflags & FSESS_OPENED)) { fuse_lck_mtx_unlock(fdev->mtx); err = ENXIO; goto out; } data->dataflags |= FSESS_MOUNTED; OSAddAtomic(1, (SInt32 *)&fuse_mount_count); mounted = true; if (fdata_dead_get(data)) { fuse_lck_mtx_unlock(fdev->mtx); err = ENOTCONN; goto out; } if (!data->daemoncred) { panic("fuse4x: daemon found but identity unknown"); } if (fuse_vfs_context_issuser(context) && kauth_cred_getuid(vfs_context_ucred(context)) != kauth_cred_getuid(data->daemoncred)) { fuse_lck_mtx_unlock(fdev->mtx); err = EPERM; log("fuse4x: fuse daemon running by user_id=%d does not have privileges to mount on directory %s owned by user_id=%d\n", kauth_cred_getuid(data->daemoncred), vfsstatfsp->f_mntonname, kauth_cred_getuid(vfs_context_ucred(context))); goto out; } data->mp = mp; data->fdev = fdev; data->dataflags |= mntopts; data->daemon_timeout.tv_sec = fusefs_args.daemon_timeout; data->daemon_timeout.tv_nsec = 0; if (data->daemon_timeout.tv_sec) { data->daemon_timeout_p = &(data->daemon_timeout); } else { data->daemon_timeout_p = NULL; } data->init_timeout.tv_sec = fusefs_args.init_timeout; data->init_timeout.tv_nsec = 0; data->max_read = max_read; data->fssubtype = fusefs_args.fssubtype; data->mountaltflags = fusefs_args.altflags; data->noimplflags = (uint64_t)0; data->blocksize = fuse_round_size(fusefs_args.blocksize, FUSE_MIN_BLOCKSIZE, FUSE_MAX_BLOCKSIZE); data->iosize = fuse_round_size(fusefs_args.iosize, FUSE_MIN_IOSIZE, FUSE_MAX_IOSIZE); if (data->iosize < data->blocksize) { data->iosize = data->blocksize; } data->userkernel_bufsize = FUSE_DEFAULT_USERKERNEL_BUFSIZE; copystr(fusefs_args.fsname, vfsstatfsp->f_mntfromname, MNAMELEN - 1, &len); bzero(vfsstatfsp->f_mntfromname + len, MNAMELEN - len); copystr(fusefs_args.volname, data->volname, MAXPATHLEN - 1, &len); bzero(data->volname + len, MAXPATHLEN - len); /* previous location of vfs_setioattr() */ vfs_setfsprivate(mp, data); fuse_lck_mtx_unlock(fdev->mtx); /* Send a handshake message to the daemon. */ fuse_send_init(data, context); struct vfs_attr vfs_attr; VFSATTR_INIT(&vfs_attr); // Our vfs_getattr() doesn't look at most *_IS_ACTIVE()'s err = fuse_vfsop_getattr(mp, &vfs_attr, context); if (!err) { vfsstatfsp->f_bsize = vfs_attr.f_bsize; vfsstatfsp->f_iosize = vfs_attr.f_iosize; vfsstatfsp->f_blocks = vfs_attr.f_blocks; vfsstatfsp->f_bfree = vfs_attr.f_bfree; vfsstatfsp->f_bavail = vfs_attr.f_bavail; vfsstatfsp->f_bused = vfs_attr.f_bused; vfsstatfsp->f_files = vfs_attr.f_files; vfsstatfsp->f_ffree = vfs_attr.f_ffree; // vfsstatfsp->f_fsid already handled above vfsstatfsp->f_owner = kauth_cred_getuid(data->daemoncred); vfsstatfsp->f_flags = vfs_flags(mp); // vfsstatfsp->f_fstypename already handled above // vfsstatfsp->f_mntonname handled elsewhere // vfsstatfsp->f_mnfromname already handled above vfsstatfsp->f_fssubtype = data->fssubtype; } if (fusefs_args.altflags & FUSE_MOPT_BLOCKSIZE) { vfsstatfsp->f_bsize = data->blocksize; } else { //data->blocksize = vfsstatfsp->f_bsize; } if (fusefs_args.altflags & FUSE_MOPT_IOSIZE) { vfsstatfsp->f_iosize = data->iosize; } else { //data->iosize = (uint32_t)vfsstatfsp->f_iosize; vfsstatfsp->f_iosize = data->iosize; } out: if (err) { vfs_setfsprivate(mp, NULL); fuse_lck_mtx_lock(fdev->mtx); data = fdev->data; /* again */ if (mounted) { OSAddAtomic(-1, (SInt32 *)&fuse_mount_count); } if (data) { data->dataflags &= ~FSESS_MOUNTED; if (!(data->dataflags & FSESS_OPENED)) { #if M_FUSE4X_ENABLE_BIGLOCK assert(biglock == data->biglock); fuse_biglock_unlock(biglock); #endif fuse_device_close_final(fdev); /* data is gone now */ } } fuse_lck_mtx_unlock(fdev->mtx); } else { vnode_t fuse_rootvp = NULLVP; err = fuse_vfsop_root(mp, &fuse_rootvp, context); if (err) { goto out; /* go back and follow error path */ } err = vnode_ref(fuse_rootvp); (void)vnode_put(fuse_rootvp); if (err) { goto out; /* go back and follow error path */ } else { struct vfsioattr ioattr; vfs_ioattr(mp, &ioattr); ioattr.io_devblocksize = data->blocksize; vfs_setioattr(mp, &ioattr); } } #if M_FUSE4X_ENABLE_BIGLOCK fuse_lck_mtx_lock(fdev->mtx); data = fdev->data; /* ...and again */ if(data) { assert(data->biglock == biglock); fuse_biglock_unlock(biglock); } fuse_lck_mtx_unlock(fdev->mtx); #endif return err; }
/* * iocounts in: * --One on ni_vp. One on ni_dvp if there is more path, or we didn't come through the * cache, or we came through the cache and the caller doesn't want the parent. * * iocounts out: * --Leaves us in the correct state for the next step, whatever that might be. * --If we find a symlink, returns with iocounts on both ni_vp and ni_dvp. * --If we are to look up another component, then we have an iocount on ni_vp and * nothing else. * --If we are done, returns an iocount on ni_vp, and possibly on ni_dvp depending on nameidata flags. * --In the event of an error, may return with ni_dvp NULL'ed out (in which case, iocount * was dropped). */ int lookup_handle_found_vnode(struct nameidata *ndp, struct componentname *cnp, int rdonly, int vbusyflags, int *keep_going, int nc_generation, int wantparent, int atroot, vfs_context_t ctx) { vnode_t dp; int error; char *cp; dp = ndp->ni_vp; *keep_going = 0; if (ndp->ni_vp == NULLVP) { panic("NULL ni_vp in %s\n", __FUNCTION__); } if (atroot) { goto nextname; } #if CONFIG_TRIGGERS if (dp->v_resolve) { error = vnode_trigger_resolve(dp, ndp, ctx); if (error) { goto out; } } #endif /* CONFIG_TRIGGERS */ /* * Take into account any additional components consumed by * the underlying filesystem. */ if (cnp->cn_consume > 0) { cnp->cn_nameptr += cnp->cn_consume; ndp->ni_next += cnp->cn_consume; ndp->ni_pathlen -= cnp->cn_consume; cnp->cn_consume = 0; } else { lookup_consider_update_cache(ndp->ni_dvp, dp, cnp, nc_generation); } /* * Check to see if the vnode has been mounted on... * if so find the root of the mounted file system. * Updates ndp->ni_vp. */ error = lookup_traverse_mountpoints(ndp, cnp, dp, vbusyflags, ctx); dp = ndp->ni_vp; if (error) { goto out; } #if CONFIG_MACF if (vfs_flags(vnode_mount(dp)) & MNT_MULTILABEL) { error = vnode_label(vnode_mount(dp), NULL, dp, NULL, 0, ctx); if (error) goto out; } #endif /* * Check for symbolic link */ if ((dp->v_type == VLNK) && ((cnp->cn_flags & FOLLOW) || (ndp->ni_flag & NAMEI_TRAILINGSLASH) || *ndp->ni_next == '/')) { cnp->cn_flags |= ISSYMLINK; *keep_going = 1; return (0); } /* * Check for bogus trailing slashes. */ if ((ndp->ni_flag & NAMEI_TRAILINGSLASH)) { if (dp->v_type != VDIR) { error = ENOTDIR; goto out; } ndp->ni_flag &= ~(NAMEI_TRAILINGSLASH); } nextname: /* * Not a symbolic link. If more pathname, * continue at next component, else return. * * Definitely have a dvp if there's another slash */ if (*ndp->ni_next == '/') { cnp->cn_nameptr = ndp->ni_next + 1; ndp->ni_pathlen--; while (*cnp->cn_nameptr == '/') { cnp->cn_nameptr++; ndp->ni_pathlen--; } cp = cnp->cn_nameptr; vnode_put(ndp->ni_dvp); ndp->ni_dvp = NULLVP; if (*cp == '\0') { goto emptyname; } *keep_going = 1; return 0; } /* * Disallow directory write attempts on read-only file systems. */ if (rdonly && (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)) { error = EROFS; goto out; } /* If SAVESTART is set, we should have a dvp */ if (cnp->cn_flags & SAVESTART) { /* * note that we already hold a reference * on both dp and ni_dvp, but for some reason * can't get another one... in this case we * need to do vnode_put on dp in 'bad2' */ if ( (vnode_get(ndp->ni_dvp)) ) { error = ENOENT; goto out; } ndp->ni_startdir = ndp->ni_dvp; } if (!wantparent && ndp->ni_dvp) { vnode_put(ndp->ni_dvp); ndp->ni_dvp = NULLVP; } if (cnp->cn_flags & AUDITVNPATH1) AUDIT_ARG(vnpath, dp, ARG_VNODE1); else if (cnp->cn_flags & AUDITVNPATH2) AUDIT_ARG(vnpath, dp, ARG_VNODE2); #if NAMEDRSRCFORK /* * Caller wants the resource fork. */ if ((cnp->cn_flags & CN_WANTSRSRCFORK) && (dp != NULLVP)) { error = lookup_handle_rsrc_fork(dp, ndp, cnp, wantparent, ctx); if (error != 0) goto out; dp = ndp->ni_vp; } #endif if (kdebug_enable) kdebug_lookup(dp, cnp); return 0; emptyname: error = lookup_handle_emptyname(ndp, cnp, wantparent); if (error != 0) goto out; return 0; out: return error; }
/* * Free reference to null layer */ static int nullfs_unmount(struct mount * mp, int mntflags, __unused vfs_context_t ctx) { struct null_mount * mntdata; struct vnode * vp; int error, flags; NULLFSDEBUG("nullfs_unmount: mp = %p\n", (void *)mp); /* check entitlement or superuser*/ if (!IOTaskHasEntitlement(current_task(), NULLFS_ENTITLEMENT) && vfs_context_suser(ctx) != 0) { return EPERM; } if (mntflags & MNT_FORCE) { flags = FORCECLOSE; } else { flags = 0; } mntdata = MOUNTTONULLMOUNT(mp); vp = mntdata->nullm_rootvp; // release our reference on the root before flushing. // it will get pulled out of the mount structure by reclaim vnode_getalways(vp); error = vflush(mp, vp, flags); if (error) { vnode_put(vp); return (error); } if (vnode_isinuse(vp,1) && flags == 0) { vnode_put(vp); return EBUSY; } vnode_rele(vp); // Drop reference taken by nullfs_mount vnode_put(vp); // Drop ref taken above //Force close to get rid of the last vnode (void)vflush(mp, NULL, FORCECLOSE); /* no more vnodes, so tear down the mountpoint */ lck_mtx_lock(&mntdata->nullm_lock); vfs_setfsprivate(mp, NULL); vnode_getalways(mntdata->nullm_lowerrootvp); vnode_rele(mntdata->nullm_lowerrootvp); vnode_put(mntdata->nullm_lowerrootvp); lck_mtx_unlock(&mntdata->nullm_lock); nullfs_destroy_lck(&mntdata->nullm_lock); FREE(mntdata, M_TEMP); uint64_t vflags = vfs_flags(mp); vfs_setflags(mp, vflags & ~MNT_LOCAL); return (0); }
/* * Mount null layer */ static int nullfs_mount(struct mount * mp, __unused vnode_t devvp, user_addr_t user_data, vfs_context_t ctx) { int error = 0; struct vnode *lowerrootvp = NULL, *vp = NULL; struct vfsstatfs * sp = NULL; struct null_mount * xmp = NULL; char data[MAXPATHLEN]; size_t count; struct vfs_attr vfa; /* set defaults (arbitrary since this file system is readonly) */ uint32_t bsize = BLKDEV_IOSIZE; size_t iosize = BLKDEV_IOSIZE; uint64_t blocks = 4711 * 4711; uint64_t bfree = 0; uint64_t bavail = 0; uint64_t bused = 4711; uint64_t files = 4711; uint64_t ffree = 0; kauth_cred_t cred = vfs_context_ucred(ctx); NULLFSDEBUG("nullfs_mount(mp = %p) %llx\n", (void *)mp, vfs_flags(mp)); if (vfs_flags(mp) & MNT_ROOTFS) return (EOPNOTSUPP); /* * Update is a no-op */ if (vfs_isupdate(mp)) { return ENOTSUP; } /* check entitlement */ if (!IOTaskHasEntitlement(current_task(), NULLFS_ENTITLEMENT)) { return EPERM; } /* * Get argument */ error = copyinstr(user_data, data, MAXPATHLEN - 1, &count); if (error) { NULLFSDEBUG("nullfs: error copying data form user %d\n", error); goto error; } /* This could happen if the system is configured for 32 bit inodes instead of * 64 bit */ if (count > MAX_MNT_FROM_LENGTH) { error = EINVAL; NULLFSDEBUG("nullfs: path to translocate too large for this system %d vs %d\n", count, MAX_MNT_FROM_LENGTH); goto error; } error = vnode_lookup(data, 0, &lowerrootvp, ctx); if (error) { NULLFSDEBUG("lookup %s -> %d\n", data, error); goto error; } /* lowervrootvp has an iocount after vnode_lookup, drop that for a usecount. Keep this to signal what we want to keep around the thing we are mirroring. Drop it in unmount.*/ error = vnode_ref(lowerrootvp); vnode_put(lowerrootvp); if (error) { // If vnode_ref failed, then null it out so it can't be used anymore in cleanup. lowerrootvp = NULL; goto error; } NULLFSDEBUG("mount %s\n", data); MALLOC(xmp, struct null_mount *, sizeof(*xmp), M_TEMP, M_WAITOK | M_ZERO); if (xmp == NULL) { error = ENOMEM; goto error; } /* * Save reference to underlying FS */ xmp->nullm_lowerrootvp = lowerrootvp; xmp->nullm_lowerrootvid = vnode_vid(lowerrootvp); error = null_getnewvnode(mp, NULL, NULL, &vp, NULL, 1); if (error) { goto error; } /* vp has an iocount on it from vnode_create. drop that for a usecount. This * is our root vnode so we drop the ref in unmount * * Assuming for now that because we created this vnode and we aren't finished mounting we can get a ref*/ vnode_ref(vp); vnode_put(vp); error = nullfs_init_lck(&xmp->nullm_lock); if (error) { goto error; } xmp->nullm_rootvp = vp; /* read the flags the user set, but then ignore some of them, we will only allow them if they are set on the lower file system */ uint64_t flags = vfs_flags(mp) & (~(MNT_IGNORE_OWNERSHIP | MNT_LOCAL)); uint64_t lowerflags = vfs_flags(vnode_mount(lowerrootvp)) & (MNT_LOCAL | MNT_QUARANTINE | MNT_IGNORE_OWNERSHIP | MNT_NOEXEC); if (lowerflags) { flags |= lowerflags; } /* force these flags */ flags |= (MNT_DONTBROWSE | MNT_MULTILABEL | MNT_NOSUID | MNT_RDONLY); vfs_setflags(mp, flags); vfs_setfsprivate(mp, xmp); vfs_getnewfsid(mp); vfs_setlocklocal(mp); /* fill in the stat block */ sp = vfs_statfs(mp); strlcpy(sp->f_mntfromname, data, MAX_MNT_FROM_LENGTH); sp->f_flags = flags; xmp->nullm_flags = NULLM_CASEINSENSITIVE; /* default to case insensitive */ error = nullfs_vfs_getlowerattr(vnode_mount(lowerrootvp), &vfa, ctx); if (error == 0) { if (VFSATTR_IS_SUPPORTED(&vfa, f_bsize)) { bsize = vfa.f_bsize; } if (VFSATTR_IS_SUPPORTED(&vfa, f_iosize)) { iosize = vfa.f_iosize; } if (VFSATTR_IS_SUPPORTED(&vfa, f_blocks)) { blocks = vfa.f_blocks; } if (VFSATTR_IS_SUPPORTED(&vfa, f_bfree)) { bfree = vfa.f_bfree; } if (VFSATTR_IS_SUPPORTED(&vfa, f_bavail)) { bavail = vfa.f_bavail; } if (VFSATTR_IS_SUPPORTED(&vfa, f_bused)) { bused = vfa.f_bused; } if (VFSATTR_IS_SUPPORTED(&vfa, f_files)) { files = vfa.f_files; } if (VFSATTR_IS_SUPPORTED(&vfa, f_ffree)) { ffree = vfa.f_ffree; } if (VFSATTR_IS_SUPPORTED(&vfa, f_capabilities)) { if ((vfa.f_capabilities.capabilities[VOL_CAPABILITIES_FORMAT] & (VOL_CAP_FMT_CASE_SENSITIVE)) && (vfa.f_capabilities.valid[VOL_CAPABILITIES_FORMAT] & (VOL_CAP_FMT_CASE_SENSITIVE))) { xmp->nullm_flags &= ~NULLM_CASEINSENSITIVE; } } } else { goto error; } sp->f_bsize = bsize; sp->f_iosize = iosize; sp->f_blocks = blocks; sp->f_bfree = bfree; sp->f_bavail = bavail; sp->f_bused = bused; sp->f_files = files; sp->f_ffree = ffree; /* Associate the mac label information from the mirrored filesystem with the * mirror */ MAC_PERFORM(mount_label_associate, cred, vnode_mount(lowerrootvp), vfs_mntlabel(mp)); NULLFSDEBUG("nullfs_mount: lower %s, alias at %s\n", sp->f_mntfromname, sp->f_mntonname); return (0); error: if (xmp) { FREE(xmp, M_TEMP); } if (lowerrootvp) { vnode_getwithref(lowerrootvp); vnode_rele(lowerrootvp); vnode_put(lowerrootvp); } if (vp) { /* we made the root vnode but the mount is failed, so clean it up */ vnode_getwithref(vp); vnode_rele(vp); /* give vp back */ vnode_recycle(vp); vnode_put(vp); } return error; }
void commonattrpack(attrinfo_t *aip, zfsvfs_t *zfsvfs, znode_t *zp, const char *name, ino64_t objnum, enum vtype vtype, boolean_t user64) { attrgroup_t commonattr = aip->ai_attrlist->commonattr; void *attrbufptr = *aip->ai_attrbufpp; void *varbufptr = *aip->ai_varbufpp; struct mount *mp = zfsvfs->z_vfs; cred_t *cr = (cred_t *)vfs_context_ucred(aip->ai_context); finderinfo_t finderinfo; /* * We should probably combine all the sa_lookup into a bulk * lookup operand. */ finderinfo.fi_flags = 0; if (ATTR_CMN_NAME & commonattr) { nameattrpack(aip, name, strlen(name)); attrbufptr = *aip->ai_attrbufpp; varbufptr = *aip->ai_varbufpp; } if (ATTR_CMN_DEVID & commonattr) { *((dev_t *)attrbufptr) = vfs_statfs(mp)->f_fsid.val[0]; attrbufptr = ((dev_t *)attrbufptr) + 1; } if (ATTR_CMN_FSID & commonattr) { *((fsid_t *)attrbufptr) = vfs_statfs(mp)->f_fsid; attrbufptr = ((fsid_t *)attrbufptr) + 1; } if (ATTR_CMN_OBJTYPE & commonattr) { *((fsobj_type_t *)attrbufptr) = vtype; attrbufptr = ((fsobj_type_t *)attrbufptr) + 1; } if (ATTR_CMN_OBJTAG & commonattr) { *((fsobj_tag_t *)attrbufptr) = VT_ZFS; attrbufptr = ((fsobj_tag_t *)attrbufptr) + 1; } /* * Note: ATTR_CMN_OBJID is lossy (only 32 bits). */ if ((ATTR_CMN_OBJID | ATTR_CMN_OBJPERMANENTID) & commonattr) { u_int32_t fileid; /* * On Mac OS X we always export the root directory id as 2 */ fileid = (objnum == zfsvfs->z_root) ? 2 : objnum; if (ATTR_CMN_OBJID & commonattr) { ((fsobj_id_t *)attrbufptr)->fid_objno = fileid; ((fsobj_id_t *)attrbufptr)->fid_generation = 0; attrbufptr = ((fsobj_id_t *)attrbufptr) + 1; } if (ATTR_CMN_OBJPERMANENTID & commonattr) { ((fsobj_id_t *)attrbufptr)->fid_objno = fileid; ((fsobj_id_t *)attrbufptr)->fid_generation = 0; attrbufptr = ((fsobj_id_t *)attrbufptr) + 1; } } /* * Note: ATTR_CMN_PAROBJID is lossy (only 32 bits). */ if (ATTR_CMN_PAROBJID & commonattr) { uint64_t parentid; VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_PARENT(zfsvfs), &parentid, sizeof (parentid)) == 0); /* * On Mac OS X we always export the root * directory id as 2 and its parent as 1 */ if (zp && zp->z_id == zfsvfs->z_root) parentid = 1; else if (parentid == zfsvfs->z_root) parentid = 2; ASSERT(parentid != 0); ((fsobj_id_t *)attrbufptr)->fid_objno = (uint32_t)parentid; ((fsobj_id_t *)attrbufptr)->fid_generation = 0; attrbufptr = ((fsobj_id_t *)attrbufptr) + 1; } if (ATTR_CMN_SCRIPT & commonattr) { *((text_encoding_t *)attrbufptr) = kTextEncodingMacUnicode; attrbufptr = ((text_encoding_t *)attrbufptr) + 1; } if (ATTR_CMN_CRTIME & commonattr) { uint64_t times[2]; VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_CRTIME(zfsvfs), times, sizeof(times)) == 0); if (user64) { ZFS_TIME_DECODE((timespec_user64_t *)attrbufptr, times); attrbufptr = ((timespec_user64_t *)attrbufptr) + 1; } else { ZFS_TIME_DECODE((timespec_user32_t *)attrbufptr, times); attrbufptr = ((timespec_user32_t *)attrbufptr) + 1; } } if (ATTR_CMN_MODTIME & commonattr) { uint64_t times[2]; VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_MTIME(zfsvfs), times, sizeof(times)) == 0); if (user64) { ZFS_TIME_DECODE((timespec_user64_t *)attrbufptr, times); attrbufptr = ((timespec_user64_t *)attrbufptr) + 1; } else { ZFS_TIME_DECODE((timespec_user32_t *)attrbufptr, times); attrbufptr = ((timespec_user32_t *)attrbufptr) + 1; } } if (ATTR_CMN_CHGTIME & commonattr) { uint64_t times[2]; VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_CTIME(zfsvfs), times, sizeof(times)) == 0); if (user64) { ZFS_TIME_DECODE((timespec_user64_t *)attrbufptr, times); attrbufptr = ((timespec_user64_t *)attrbufptr) + 1; } else { ZFS_TIME_DECODE((timespec_user32_t *)attrbufptr, times); attrbufptr = ((timespec_user32_t *)attrbufptr) + 1; } } if (ATTR_CMN_ACCTIME & commonattr) { uint64_t times[2]; VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_ATIME(zfsvfs), times, sizeof(times)) == 0); if (user64) { ZFS_TIME_DECODE((timespec_user64_t *)attrbufptr, times); attrbufptr = ((timespec_user64_t *)attrbufptr) + 1; } else { ZFS_TIME_DECODE((timespec_user32_t *)attrbufptr, times); attrbufptr = ((timespec_user32_t *)attrbufptr) + 1; } } if (ATTR_CMN_BKUPTIME & commonattr) { /* legacy attribute -- just pass zero */ if (user64) { ((timespec_user64_t *)attrbufptr)->tv_sec = 0; ((timespec_user64_t *)attrbufptr)->tv_nsec = 0; attrbufptr = ((timespec_user64_t *)attrbufptr) + 1; } else { ((timespec_user32_t *)attrbufptr)->tv_sec = 0; ((timespec_user32_t *)attrbufptr)->tv_nsec = 0; attrbufptr = ((timespec_user32_t *)attrbufptr) + 1; } } if (ATTR_CMN_FNDRINFO & commonattr) { uint64_t val; VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_FLAGS(zfsvfs), &val, sizeof(val)) == 0); getfinderinfo(zp, cr, &finderinfo); /* Shadow ZFS_HIDDEN to Finder Info's invisible bit */ if (val & ZFS_HIDDEN) { finderinfo.fi_flags |= OSSwapHostToBigConstInt16(kIsInvisible); } bcopy(&finderinfo, attrbufptr, sizeof (finderinfo)); attrbufptr = (char *)attrbufptr + 32; } if (ATTR_CMN_OWNERID & commonattr) { uint64_t val; VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_UID(zfsvfs), &val, sizeof(val)) == 0); *((uid_t *)attrbufptr) = val; attrbufptr = ((uid_t *)attrbufptr) + 1; } if (ATTR_CMN_GRPID & commonattr) { uint64_t val; VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_GID(zfsvfs), &val, sizeof(val)) == 0); *((gid_t *)attrbufptr) = val; attrbufptr = ((gid_t *)attrbufptr) + 1; } if (ATTR_CMN_ACCESSMASK & commonattr) { uint64_t val; VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_MODE(zfsvfs), &val, sizeof(val)) == 0); *((u_int32_t *)attrbufptr) = val; attrbufptr = ((u_int32_t *)attrbufptr) + 1; } if (ATTR_CMN_FLAGS & commonattr) { u_int32_t flags = zfs_getbsdflags(zp); /* Shadow Finder Info's invisible bit to UF_HIDDEN */ if ((ATTR_CMN_FNDRINFO & commonattr) && (OSSwapBigToHostInt16(finderinfo.fi_flags) & kIsInvisible)) flags |= UF_HIDDEN; *((u_int32_t *)attrbufptr) = flags; attrbufptr = ((u_int32_t *)attrbufptr) + 1; } if (ATTR_CMN_USERACCESS & commonattr) { u_int32_t user_access = 0; uint64_t val; VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_FLAGS(zfsvfs), &val, sizeof(val)) == 0); user_access = getuseraccess(zp, aip->ai_context); /* Also consider READ-ONLY file system. */ if (vfs_flags(mp) & MNT_RDONLY) { user_access &= ~W_OK; } /* Locked objects are not writable either */ if ((val & ZFS_IMMUTABLE) && (vfs_context_suser(aip->ai_context) != 0)) { user_access &= ~W_OK; } *((u_int32_t *)attrbufptr) = user_access; attrbufptr = ((u_int32_t *)attrbufptr) + 1; } if (ATTR_CMN_FILEID & commonattr) { /* * On Mac OS X we always export the root directory id as 2 */ if (objnum == zfsvfs->z_root) objnum = 2; *((u_int64_t *)attrbufptr) = objnum; attrbufptr = ((u_int64_t *)attrbufptr) + 1; } if (ATTR_CMN_PARENTID & commonattr) { uint64_t parentid; VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_PARENT(zfsvfs), &parentid, sizeof (parentid)) == 0); /* * On Mac OS X we always export the root * directory id as 2 and its parent as 1 */ if (zp && zp->z_id == zfsvfs->z_root) parentid = 1; else if (parentid == zfsvfs->z_root) parentid = 2; ASSERT(parentid != 0); *((u_int64_t *)attrbufptr) = parentid; attrbufptr = ((u_int64_t *)attrbufptr) + 1; } *aip->ai_attrbufpp = attrbufptr; *aip->ai_varbufpp = varbufptr; }
static int vfs_mount_9p(mount_t mp, vnode_t devvp, user_addr_t data, vfs_context_t ctx) { #pragma unused(devvp) struct sockaddr *addr, *authaddr; struct vfsstatfs *sp; char authkey[DESKEYLEN+1]; kauth_cred_t cred; user_args_9p args; mount_9p *nmp; size_t size; fid_9p fid; qid_9p qid; char *vers; int e; TRACE(); nmp = NULL; addr = NULL; authaddr = NULL; fid = NOFID; if (vfs_isupdate(mp)) return ENOTSUP; if (vfs_context_is64bit(ctx)) { if ((e=copyin(data, &args, sizeof(args)))) goto error; } else { args_9p args32; if ((e=copyin(data, &args32, sizeof(args32)))) goto error; args.spec = CAST_USER_ADDR_T(args32.spec); args.addr = CAST_USER_ADDR_T(args32.addr); args.addrlen = args32.addrlen; args.authaddr = CAST_USER_ADDR_T(args32.authaddr); args.authaddrlen = args32.authaddrlen; args.volume = CAST_USER_ADDR_T(args32.volume); args.uname = CAST_USER_ADDR_T(args32.uname); args.aname = CAST_USER_ADDR_T(args32.aname); args.authkey = CAST_USER_ADDR_T(args32.authkey); args.flags = args32.flags; } e = ENOMEM; nmp = malloc_9p(sizeof(*nmp)); if (nmp == NULL) return e; nmp->mp = mp; TAILQ_INIT(&nmp->req); nmp->lck = lck_mtx_alloc_init(lck_grp_9p, LCK_ATTR_NULL); nmp->reqlck = lck_mtx_alloc_init(lck_grp_9p, LCK_ATTR_NULL); nmp->nodelck = lck_mtx_alloc_init(lck_grp_9p, LCK_ATTR_NULL); nmp->node = hashinit(desiredvnodes, M_TEMP, &nmp->nodelen); if (nmp->lck==NULL || nmp->reqlck==NULL || nmp->nodelck==NULL || nmp->node==NULL) goto error; if ((e=nameget_9p(args.volume, &nmp->volume))) goto error; if ((e=nameget_9p(args.uname, &nmp->uname))) goto error; if ((e=nameget_9p(args.aname, &nmp->aname))) goto error; cred = vfs_context_ucred(ctx); if (IS_VALID_CRED(cred)) { nmp->uid = kauth_cred_getuid(cred); nmp->gid = kauth_cred_getgid(cred); } else { nmp->uid = KAUTH_UID_NONE; nmp->gid = KAUTH_GID_NONE; } vfs_getnewfsid(mp); vfs_setfsprivate(mp, nmp); nmp->flags = args.flags; if ((e=addrget_9p(args.addr, args.addrlen, &addr))) goto error; if ((e=connect_9p(nmp, addr))) goto error; vers = VERSION9P; if (ISSET(nmp->flags, FLAG_DOTU)) vers = VERSION9PDOTU; if ((e=version_9p(nmp, vers, &nmp->version))) goto error; if (ISSET(nmp->flags, FLAG_DOTU) && strcmp(VERSION9PDOTU, nmp->version)==0) SET(nmp->flags, F_DOTU); nmp->afid = NOFID; if (args.authaddr && args.authaddrlen && args.authkey) { if ((e=copyin(args.authkey, authkey, DESKEYLEN))) goto error; if ((e=addrget_9p(args.authaddr, args.authaddrlen, &authaddr))) goto error; if ((e=auth_9p(nmp, nmp->uname, nmp->aname, nmp->uid, &nmp->afid, &qid))) goto error; if (nmp->afid!=NOFID && (e=authp9any_9p(nmp, nmp->afid, authaddr, nmp->uname, authkey))) goto error; bzero(authkey, DESKEYLEN); } if ((e=attach_9p(nmp, nmp->uname, nmp->aname, nmp->afid, nmp->uid, &fid, &qid))) goto error; if ((e=nget_9p(nmp, fid, qid, NULL, &nmp->root, NULL, ctx))) goto error; nunlock_9p(NTO9P(nmp->root)); e = vnode_ref(nmp->root); vnode_put(nmp->root); if (e) goto error; vfs_setauthopaque(mp); vfs_clearauthopaqueaccess(mp); vfs_setlocklocal(mp); // init stats sp = vfs_statfs(nmp->mp); copyinstr(args.spec, sp->f_mntfromname, MNAMELEN-1, &size); bzero(sp->f_mntfromname+size, MNAMELEN-size); sp->f_bsize = PAGE_SIZE; sp->f_iosize = nmp->msize-IOHDRSZ; sp->f_blocks = sp->f_bfree = sp->f_bavail = sp->f_bused = 0; sp->f_files = 65535; sp->f_ffree = sp->f_files-2; sp->f_flags = vfs_flags(mp); free_9p(addr); free_9p(authaddr); return 0; error: bzero(authkey, DESKEYLEN); free_9p(addr); free_9p(authaddr); if (nmp->so) { clunk_9p(nmp, fid); disconnect_9p(nmp); } freemount_9p(nmp); vfs_setfsprivate(mp, NULL); return e; }
/* * Search a pathname. * This is a very central and rather complicated routine. * * The pathname is pointed to by ni_ptr and is of length ni_pathlen. * The starting directory is taken from ni_startdir. The pathname is * descended until done, or a symbolic link is encountered. The variable * ni_more is clear if the path is completed; it is set to one if a * symbolic link needing interpretation is encountered. * * The flag argument is LOOKUP, CREATE, RENAME, or DELETE depending on * whether the name is to be looked up, created, renamed, or deleted. * When CREATE, RENAME, or DELETE is specified, information usable in * creating, renaming, or deleting a directory entry may be calculated. * If flag has LOCKPARENT or'ed into it, the parent directory is returned * locked. If flag has WANTPARENT or'ed into it, the parent directory is * returned unlocked. Otherwise the parent directory is not returned. If * the target of the pathname exists and LOCKLEAF is or'ed into the flag * the target is returned locked, otherwise it is returned unlocked. * When creating or renaming and LOCKPARENT is specified, the target may not * be ".". When deleting and LOCKPARENT is specified, the target may be ".". * * Overall outline of lookup: * * dirloop: * identify next component of name at ndp->ni_ptr * handle degenerate case where name is null string * if .. and crossing mount points and on mounted filesys, find parent * call VNOP_LOOKUP routine for next component name * directory vnode returned in ni_dvp, unlocked unless LOCKPARENT set * component vnode returned in ni_vp (if it exists), locked. * if result vnode is mounted on and crossing mount points, * find mounted on vnode * if more components of name, do next level at dirloop * return the answer in ni_vp, locked if LOCKLEAF set * if LOCKPARENT set, return locked parent in ni_dvp * if WANTPARENT set, return unlocked parent in ni_dvp * * Returns: 0 Success * ENOENT No such file or directory * EBADF Bad file descriptor * ENOTDIR Not a directory * EROFS Read-only file system [CREATE] * EISDIR Is a directory [CREATE] * cache_lookup_path:ERECYCLE (vnode was recycled from underneath us, redrive lookup again) * vnode_authorize:EROFS * vnode_authorize:EACCES * vnode_authorize:EPERM * vnode_authorize:??? * VNOP_LOOKUP:ENOENT No such file or directory * VNOP_LOOKUP:EJUSTRETURN Restart system call (INTERNAL) * VNOP_LOOKUP:??? * VFS_ROOT:ENOTSUP * VFS_ROOT:ENOENT * VFS_ROOT:??? */ int lookup(struct nameidata *ndp) { char *cp; /* pointer into pathname argument */ vnode_t tdp; /* saved dp */ vnode_t dp; /* the directory we are searching */ mount_t mp; /* mount table entry */ int docache = 1; /* == 0 do not cache last component */ int wantparent; /* 1 => wantparent or lockparent flag */ int rdonly; /* lookup read-only flag bit */ int trailing_slash = 0; int dp_authorized = 0; int error = 0; struct componentname *cnp = &ndp->ni_cnd; vfs_context_t ctx = cnp->cn_context; int mounted_on_depth = 0; int dont_cache_mp = 0; vnode_t mounted_on_dp = NULLVP; int current_mount_generation = 0; int vbusyflags = 0; int nc_generation = 0; vnode_t last_dp = NULLVP; /* * Setup: break out flag bits into variables. */ if (cnp->cn_flags & (NOCACHE | DOWHITEOUT)) { if ((cnp->cn_flags & NOCACHE) || (cnp->cn_nameiop == DELETE)) docache = 0; } wantparent = cnp->cn_flags & (LOCKPARENT | WANTPARENT); rdonly = cnp->cn_flags & RDONLY; cnp->cn_flags &= ~ISSYMLINK; cnp->cn_consume = 0; dp = ndp->ni_startdir; ndp->ni_startdir = NULLVP; if ((cnp->cn_flags & CN_NBMOUNTLOOK) != 0) vbusyflags = LK_NOWAIT; cp = cnp->cn_nameptr; if (*cp == '\0') { if ( (vnode_getwithref(dp)) ) { dp = NULLVP; error = ENOENT; goto bad; } goto emptyname; } dirloop: ndp->ni_vp = NULLVP; if ( (error = cache_lookup_path(ndp, cnp, dp, ctx, &trailing_slash, &dp_authorized, last_dp)) ) { dp = NULLVP; goto bad; } if ((cnp->cn_flags & ISLASTCN)) { if (docache) cnp->cn_flags |= MAKEENTRY; } else cnp->cn_flags |= MAKEENTRY; dp = ndp->ni_dvp; if (ndp->ni_vp != NULLVP) { /* * cache_lookup_path returned a non-NULL ni_vp then, * we're guaranteed that the dp is a VDIR, it's * been authorized, and vp is not ".." * * make sure we don't try to enter the name back into * the cache if this vp is purged before we get to that * check since we won't have serialized behind whatever * activity is occurring in the FS that caused the purge */ if (dp != NULLVP) nc_generation = dp->v_nc_generation - 1; goto returned_from_lookup_path; } /* * Handle "..": two special cases. * 1. If at root directory (e.g. after chroot) * or at absolute root directory * then ignore it so can't get out. * 2. If this vnode is the root of a mounted * filesystem, then replace it with the * vnode which was mounted on so we take the * .. in the other file system. */ if ( (cnp->cn_flags & ISDOTDOT) ) { for (;;) { if (dp == ndp->ni_rootdir || dp == rootvnode) { ndp->ni_dvp = dp; ndp->ni_vp = dp; /* * we're pinned at the root * we've already got one reference on 'dp' * courtesy of cache_lookup_path... take * another one for the ".." * if we fail to get the new reference, we'll * drop our original down in 'bad' */ if ( (vnode_get(dp)) ) { error = ENOENT; goto bad; } goto nextname; } if ((dp->v_flag & VROOT) == 0 || (cnp->cn_flags & NOCROSSMOUNT)) break; if (dp->v_mount == NULL) { /* forced umount */ error = EBADF; goto bad; } tdp = dp; dp = tdp->v_mount->mnt_vnodecovered; vnode_put(tdp); if ( (vnode_getwithref(dp)) ) { dp = NULLVP; error = ENOENT; goto bad; } ndp->ni_dvp = dp; dp_authorized = 0; } } /* * We now have a segment name to search for, and a directory to search. */ unionlookup: ndp->ni_vp = NULLVP; if (dp->v_type != VDIR) { error = ENOTDIR; goto lookup_error; } if ( (cnp->cn_flags & DONOTAUTH) != DONOTAUTH ) { if (!dp_authorized) { error = vnode_authorize(dp, NULL, KAUTH_VNODE_SEARCH, ctx); if (error) goto lookup_error; } #if CONFIG_MACF error = mac_vnode_check_lookup(ctx, dp, cnp); if (error) goto lookup_error; #endif /* CONFIG_MACF */ } nc_generation = dp->v_nc_generation; if ( (error = VNOP_LOOKUP(dp, &ndp->ni_vp, cnp, ctx)) ) { lookup_error: if ((error == ENOENT) && (dp->v_flag & VROOT) && (dp->v_mount != NULL) && (dp->v_mount->mnt_flag & MNT_UNION)) { if ((cnp->cn_flags & FSNODELOCKHELD)) { cnp->cn_flags &= ~FSNODELOCKHELD; unlock_fsnode(dp, NULL); } tdp = dp; dp = tdp->v_mount->mnt_vnodecovered; vnode_put(tdp); if ( (vnode_getwithref(dp)) ) { dp = NULLVP; error = ENOENT; goto bad; } ndp->ni_dvp = dp; dp_authorized = 0; goto unionlookup; } if (error != EJUSTRETURN) goto bad; if (ndp->ni_vp != NULLVP) panic("leaf should be empty"); /* * If creating and at end of pathname, then can consider * allowing file to be created. */ if (rdonly) { error = EROFS; goto bad; } if ((cnp->cn_flags & ISLASTCN) && trailing_slash && !(cnp->cn_flags & WILLBEDIR)) { error = ENOENT; goto bad; } /* * We return with ni_vp NULL to indicate that the entry * doesn't currently exist, leaving a pointer to the * referenced directory vnode in ndp->ni_dvp. */ if (cnp->cn_flags & SAVESTART) { if ( (vnode_get(ndp->ni_dvp)) ) { error = ENOENT; goto bad; } ndp->ni_startdir = ndp->ni_dvp; } if (!wantparent) vnode_put(ndp->ni_dvp); if (kdebug_enable) kdebug_lookup(ndp->ni_dvp, cnp); return (0); } returned_from_lookup_path: dp = ndp->ni_vp; /* * Take into account any additional components consumed by * the underlying filesystem. */ if (cnp->cn_consume > 0) { cnp->cn_nameptr += cnp->cn_consume; ndp->ni_next += cnp->cn_consume; ndp->ni_pathlen -= cnp->cn_consume; cnp->cn_consume = 0; } else { if (dp->v_name == NULL || dp->v_parent == NULLVP) { int isdot_or_dotdot; int update_flags = 0; isdot_or_dotdot = (cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.') || (cnp->cn_flags & ISDOTDOT); if (isdot_or_dotdot == 0) { if (dp->v_name == NULL) update_flags |= VNODE_UPDATE_NAME; if (ndp->ni_dvp != NULLVP && dp->v_parent == NULLVP) update_flags |= VNODE_UPDATE_PARENT; if (update_flags) vnode_update_identity(dp, ndp->ni_dvp, cnp->cn_nameptr, cnp->cn_namelen, cnp->cn_hash, update_flags); } } if ( (cnp->cn_flags & MAKEENTRY) && (dp->v_flag & VNCACHEABLE) && LIST_FIRST(&dp->v_nclinks) == NULL) { /* * missing from name cache, but should * be in it... this can happen if volfs * causes the vnode to be created or the * name cache entry got recycled but the * vnode didn't... * check to make sure that ni_dvp is valid * cache_lookup_path may return a NULL * do a quick check to see if the generation of the * directory matches our snapshot... this will get * rechecked behind the name cache lock, but if it * already fails to match, no need to go any further */ if (ndp->ni_dvp != NULLVP && (nc_generation == ndp->ni_dvp->v_nc_generation)) cache_enter_with_gen(ndp->ni_dvp, dp, cnp, nc_generation); } } mounted_on_dp = dp; mounted_on_depth = 0; dont_cache_mp = 0; current_mount_generation = mount_generation; /* * Check to see if the vnode has been mounted on... * if so find the root of the mounted file system. */ check_mounted_on: if ((dp->v_type == VDIR) && dp->v_mountedhere && ((cnp->cn_flags & NOCROSSMOUNT) == 0)) { vnode_lock(dp); if ((dp->v_type == VDIR) && (mp = dp->v_mountedhere)) { struct uthread *uth = (struct uthread *)get_bsdthread_info(current_thread()); mp->mnt_crossref++; vnode_unlock(dp); if (vfs_busy(mp, vbusyflags)) { mount_dropcrossref(mp, dp, 0); if (vbusyflags == LK_NOWAIT) { error = ENOENT; goto bad2; } goto check_mounted_on; } /* * XXX - if this is the last component of the * pathname, and it's either not a lookup operation * or the NOTRIGGER flag is set for the operation, * set a uthread flag to let VFS_ROOT() for autofs * know it shouldn't trigger a mount. */ if ((cnp->cn_flags & ISLASTCN) && (cnp->cn_nameiop != LOOKUP || (cnp->cn_flags & NOTRIGGER))) { uth->uu_notrigger = 1; dont_cache_mp = 1; } error = VFS_ROOT(mp, &tdp, ctx); /* XXX - clear the uthread flag */ uth->uu_notrigger = 0; /* * mount_dropcrossref does a vnode_put * on dp if the 3rd arg is non-zero */ mount_dropcrossref(mp, dp, 1); dp = NULL; vfs_unbusy(mp); if (error) { goto bad2; } ndp->ni_vp = dp = tdp; mounted_on_depth++; goto check_mounted_on; } vnode_unlock(dp); } #if CONFIG_MACF if (vfs_flags(vnode_mount(dp)) & MNT_MULTILABEL) { error = vnode_label(vnode_mount(dp), NULL, dp, NULL, VNODE_LABEL_NEEDREF, ctx); if (error) goto bad2; } #endif if (mounted_on_depth && !dont_cache_mp) { mp = mounted_on_dp->v_mountedhere; if (mp) { mount_lock(mp); mp->mnt_realrootvp_vid = dp->v_id; mp->mnt_realrootvp = dp; mp->mnt_generation = current_mount_generation; mount_unlock(mp); } } /* * Check for symbolic link */ if ((dp->v_type == VLNK) && ((cnp->cn_flags & FOLLOW) || trailing_slash || *ndp->ni_next == '/')) { cnp->cn_flags |= ISSYMLINK; return (0); } /* * Check for bogus trailing slashes. */ if (trailing_slash) { if (dp->v_type != VDIR) { error = ENOTDIR; goto bad2; } trailing_slash = 0; } nextname: /* * Not a symbolic link. If more pathname, * continue at next component, else return. */ if (*ndp->ni_next == '/') { cnp->cn_nameptr = ndp->ni_next + 1; ndp->ni_pathlen--; while (*cnp->cn_nameptr == '/') { cnp->cn_nameptr++; ndp->ni_pathlen--; } vnode_put(ndp->ni_dvp); cp = cnp->cn_nameptr; if (*cp == '\0') goto emptyname; /* * cache_lookup_path is now responsible for dropping io ref on dp * when it is called again in the dirloop. This ensures we hold * a ref on dp until we complete the next round of lookup. */ last_dp = dp; goto dirloop; } /* * Disallow directory write attempts on read-only file systems. */ if (rdonly && (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)) { error = EROFS; goto bad2; } if (cnp->cn_flags & SAVESTART) { /* * note that we already hold a reference * on both dp and ni_dvp, but for some reason * can't get another one... in this case we * need to do vnode_put on dp in 'bad2' */ if ( (vnode_get(ndp->ni_dvp)) ) { error = ENOENT; goto bad2; } ndp->ni_startdir = ndp->ni_dvp; } if (!wantparent && ndp->ni_dvp) { vnode_put(ndp->ni_dvp); ndp->ni_dvp = NULLVP; } if (cnp->cn_flags & AUDITVNPATH1) AUDIT_ARG(vnpath, dp, ARG_VNODE1); else if (cnp->cn_flags & AUDITVNPATH2) AUDIT_ARG(vnpath, dp, ARG_VNODE2); #if NAMEDRSRCFORK /* * Caller wants the resource fork. */ if ((cnp->cn_flags & CN_WANTSRSRCFORK) && (dp != NULLVP)) { vnode_t svp = NULLVP; enum nsoperation nsop; if (dp->v_type != VREG) { error = ENOENT; goto bad2; } switch (cnp->cn_nameiop) { case DELETE: nsop = NS_DELETE; break; case CREATE: nsop = NS_CREATE; break; case LOOKUP: /* Make sure our lookup of "/..namedfork/rsrc" is allowed. */ if (cnp->cn_flags & CN_ALLOWRSRCFORK) { nsop = NS_OPEN; } else { error = EPERM; goto bad2; } break; default: error = EPERM; goto bad2; } /* Ask the file system for the resource fork. */ error = vnode_getnamedstream(dp, &svp, XATTR_RESOURCEFORK_NAME, nsop, 0, ctx); /* During a create, it OK for stream vnode to be missing. */ if (error == ENOATTR || error == ENOENT) { error = (nsop == NS_CREATE) ? 0 : ENOENT; } if (error) { goto bad2; } /* The "parent" of the stream is the file. */ if (wantparent) { if (ndp->ni_dvp) { if (ndp->ni_cnd.cn_flags & FSNODELOCKHELD) { ndp->ni_cnd.cn_flags &= ~FSNODELOCKHELD; unlock_fsnode(ndp->ni_dvp, NULL); } vnode_put(ndp->ni_dvp); } ndp->ni_dvp = dp; } else { vnode_put(dp); } ndp->ni_vp = dp = svp; /* on create this may be null */ /* Restore the truncated pathname buffer (for audits). */ if (ndp->ni_pathlen == 1 && ndp->ni_next[0] == '\0') { ndp->ni_next[0] = '/'; } cnp->cn_flags &= ~MAKEENTRY; } #endif if (kdebug_enable) kdebug_lookup(dp, cnp); return (0); emptyname: cnp->cn_namelen = 0; /* * A degenerate name (e.g. / or "") which is a way of * talking about a directory, e.g. like "/." or ".". */ if (dp->v_type != VDIR) { error = ENOTDIR; goto bad; } if (cnp->cn_nameiop != LOOKUP) { error = EISDIR; goto bad; } if (wantparent) { /* * note that we already hold a reference * on dp, but for some reason can't * get another one... in this case we * need to do vnode_put on dp in 'bad' */ if ( (vnode_get(dp)) ) { error = ENOENT; goto bad; } ndp->ni_dvp = dp; } cnp->cn_flags &= ~ISDOTDOT; cnp->cn_flags |= ISLASTCN; ndp->ni_next = cp; ndp->ni_vp = dp; if (cnp->cn_flags & AUDITVNPATH1) AUDIT_ARG(vnpath, dp, ARG_VNODE1); else if (cnp->cn_flags & AUDITVNPATH2) AUDIT_ARG(vnpath, dp, ARG_VNODE2); if (cnp->cn_flags & SAVESTART) panic("lookup: SAVESTART"); return (0); bad2: if ((cnp->cn_flags & FSNODELOCKHELD)) { cnp->cn_flags &= ~FSNODELOCKHELD; unlock_fsnode(ndp->ni_dvp, NULL); } if (ndp->ni_dvp) vnode_put(ndp->ni_dvp); if (dp) vnode_put(dp); ndp->ni_vp = NULLVP; if (kdebug_enable) kdebug_lookup(dp, cnp); return (error); bad: if ((cnp->cn_flags & FSNODELOCKHELD)) { cnp->cn_flags &= ~FSNODELOCKHELD; unlock_fsnode(ndp->ni_dvp, NULL); } if (dp) vnode_put(dp); ndp->ni_vp = NULLVP; if (kdebug_enable) kdebug_lookup(dp, cnp); return (error); }