static int zfsctl_unmount_snap(zfs_snapentry_t *sep, int fflags, cred_t *cr) { vnode_t *svp = sep->se_root; int error; ASSERT(vn_ismntpt(svp)); /* this will be dropped by dounmount() */ if ((error = vn_vfswlock(svp)) != 0) return (error); VN_HOLD(svp); error = dounmount(vn_mountedvfs(svp), fflags, cr); if (error) { VN_RELE(svp); return (error); } /* * We can't use VN_RELE(), as that will try to invoke * zfsctl_snapdir_inactive(), which would cause us to destroy * the sd_lock mutex held by our caller. */ ASSERT(svp->v_count == 1); gfs_vop_inactive(svp, cr, NULL); kmem_free(sep->se_name, strlen(sep->se_name) + 1); kmem_free(sep, sizeof (zfs_snapentry_t)); return (0); }
static void zfsctl_rename_snap(zfsctl_snapdir_t *sdp, zfs_snapentry_t *sep, const char *nm) { avl_index_t where; vfs_t *vfsp; refstr_t *pathref; char newpath[MAXNAMELEN]; char *tail; ASSERT(MUTEX_HELD(&sdp->sd_lock)); ASSERT(sep != NULL); vfsp = vn_mountedvfs(sep->se_root); ASSERT(vfsp != NULL); vfs_lock_wait(vfsp); /* * Change the name in the AVL tree. */ avl_remove(&sdp->sd_snaps, sep); kmem_free(sep->se_name, strlen(sep->se_name) + 1); sep->se_name = kmem_alloc(strlen(nm) + 1, KM_SLEEP); (void) strcpy(sep->se_name, nm); VERIFY(avl_find(&sdp->sd_snaps, sep, &where) == NULL); avl_insert(&sdp->sd_snaps, sep, where); /* * Change the current mountpoint info: * - update the tail of the mntpoint path * - update the tail of the resource path */ pathref = vfs_getmntpoint(vfsp); (void) strncpy(newpath, refstr_value(pathref), sizeof (newpath)); VERIFY((tail = strrchr(newpath, '/')) != NULL); *(tail+1) = '\0'; ASSERT3U(strlen(newpath) + strlen(nm), <, sizeof (newpath)); (void) strcat(newpath, nm); refstr_rele(pathref); vfs_setmntpoint(vfsp, newpath); pathref = vfs_getresource(vfsp); (void) strncpy(newpath, refstr_value(pathref), sizeof (newpath)); VERIFY((tail = strrchr(newpath, '@')) != NULL); *(tail+1) = '\0'; ASSERT3U(strlen(newpath) + strlen(nm), <, sizeof (newpath)); (void) strcat(newpath, nm); refstr_rele(pathref); vfs_setresource(vfsp, newpath); vfs_unlock(vfsp); }
int traverse(vnode_t **cvpp, int lktype) { vnode_t *cvp; vnode_t *tvp; vfs_t *vfsp; int error; cvp = *cvpp; tvp = NULL; /* * If this vnode is mounted on, then we transparently indirect * to the vnode which is the root of the mounted file system. * Before we do this we must check that an unmount is not in * progress on this vnode. */ for (;;) { /* * Reached the end of the mount chain? */ vfsp = vn_mountedvfs(cvp); if (vfsp == NULL) break; error = vfs_busy(vfsp, 0); /* * tvp is NULL for *cvpp vnode, which we can't unlock. */ if (tvp != NULL) vput(cvp); else vrele(cvp); if (error) return (error); /* * The read lock must be held across the call to VFS_ROOT() to * prevent a concurrent unmount from destroying the vfs. */ error = VFS_ROOT(vfsp, lktype, &tvp); vfs_unbusy(vfsp); if (error != 0) return (error); cvp = tvp; } *cvpp = cvp; return (0); }
/* * smb_vop_traverse_check() * * This function checks to see if the passed-in vnode has a file system * mounted on it. If it does, the mount point is "traversed" and the * vnode for the root of the file system is returned. */ int smb_vop_traverse_check(vnode_t **vpp) { int error; if (vn_mountedvfs(*vpp) == 0) return (0); /* * traverse() may return a different held vnode, even in the error case. * If it returns a different vnode, it will have released the original. */ error = traverse(vpp); return (error); }
static int xmem_rmdir(struct vnode *dvp, char *nm, struct vnode *cdir, struct cred *cred) { struct xmemnode *parent = (struct xmemnode *)VTOXN(dvp); struct xmemnode *self = NULL; struct vnode *vp; int error = 0; /* * Return error when removing . and .. */ if (strcmp(nm, ".") == 0) return (EINVAL); if (strcmp(nm, "..") == 0) return (EEXIST); /* Should be ENOTEMPTY */ error = xdirlookup(parent, nm, &self, cred); if (error) return (error); rw_enter(&parent->xn_rwlock, RW_WRITER); rw_enter(&self->xn_rwlock, RW_WRITER); vp = XNTOV(self); if (vp == dvp || vp == cdir) { error = EINVAL; goto done1; } if (self->xn_type != VDIR) { error = ENOTDIR; goto done1; } mutex_enter(&self->xn_tlock); if (self->xn_nlink > 2) { mutex_exit(&self->xn_tlock); error = EEXIST; goto done1; } mutex_exit(&self->xn_tlock); if (vn_vfswlock(vp)) { error = EBUSY; goto done1; } if (vn_mountedvfs(vp) != NULL) { error = EBUSY; goto done; } /* * Check for an empty directory * i.e. only includes entries for "." and ".." */ if (self->xn_dirents > 2) { error = EEXIST; /* SIGH should be ENOTEMPTY */ /* * Update atime because checking xn_dirents is logically * equivalent to reading the directory */ gethrestime(&self->xn_atime); goto done; } error = xdirdelete(parent, self, nm, DR_RMDIR, cred); done: vn_vfsunlock(vp); done1: rw_exit(&self->xn_rwlock); rw_exit(&parent->xn_rwlock); xmemnode_rele(self); return (error); }
/*ARGSUSED*/ static int lo_mount(struct vfs *vfsp, struct vnode *vp, struct mounta *uap, struct cred *cr) { int error; struct vnode *srootvp = NULL; /* the server's root */ struct vnode *realrootvp; struct loinfo *li; int nodev; nodev = vfs_optionisset(vfsp, MNTOPT_NODEVICES, NULL); if ((error = secpolicy_fs_mount(cr, vp, vfsp)) != 0) return (EPERM); /* * Loopback devices which get "nodevices" added can be done without * "nodevices" set because we cannot import devices into a zone * with loopback. Note that we have all zone privileges when * this happens; if not, we'd have gotten "nosuid". */ if (!nodev && vfs_optionisset(vfsp, MNTOPT_NODEVICES, NULL)) vfs_setmntopt(vfsp, MNTOPT_DEVICES, NULL, VFS_NODISPLAY); mutex_enter(&vp->v_lock); if (!(uap->flags & MS_OVERLAY) && (vp->v_count != 1 || (vp->v_flag & VROOT))) { mutex_exit(&vp->v_lock); return (EBUSY); } mutex_exit(&vp->v_lock); /* * Find real root, and make vfs point to real vfs */ if (error = lookupname(uap->spec, (uap->flags & MS_SYSSPACE) ? UIO_SYSSPACE : UIO_USERSPACE, FOLLOW, NULLVPP, &realrootvp)) return (error); /* * Enforce MAC policy if needed. * * Loopback mounts must not allow writing up. The dominance test * is intended to prevent a global zone caller from accidentally * creating write-up conditions between two labeled zones. * Local zones can't violate MAC on their own without help from * the global zone because they can't name a pathname that * they don't already have. * * The special case check for the NET_MAC_AWARE process flag is * to support the case of the automounter in the global zone. We * permit automounting of local zone directories such as home * directories, into the global zone as required by setlabel, * zonecopy, and saving of desktop sessions. Such mounts are * trusted not to expose the contents of one zone's directories * to another by leaking them through the global zone. */ if (is_system_labeled() && crgetzoneid(cr) == GLOBAL_ZONEID) { char specname[MAXPATHLEN]; zone_t *from_zptr; zone_t *to_zptr; if (vnodetopath(NULL, realrootvp, specname, sizeof (specname), CRED()) != 0) { VN_RELE(realrootvp); return (EACCES); } from_zptr = zone_find_by_path(specname); to_zptr = zone_find_by_path(refstr_value(vfsp->vfs_mntpt)); /* * Special case for zone devfs: the zone for /dev will * incorrectly appear as the global zone since it's not * under the zone rootpath. So for zone devfs check allow * read-write mounts. * * Second special case for scratch zones used for Live Upgrade: * this is used to mount the zone's root from /root to /a in * the scratch zone. As with the other special case, this * appears to be outside of the zone because it's not under * the zone rootpath, which is $ZONEPATH/lu in the scratch * zone case. */ if (from_zptr != to_zptr && !(to_zptr->zone_flags & ZF_IS_SCRATCH)) { /* * We know at this point that the labels aren't equal * because the zone pointers aren't equal, and zones * can't share a label. * * If the source is the global zone then making * it available to a local zone must be done in * read-only mode as the label will become admin_low. * * If it is a mount between local zones then if * the current process is in the global zone and has * the NET_MAC_AWARE flag, then regular read-write * access is allowed. If it's in some other zone, but * the label on the mount point dominates the original * source, then allow the mount as read-only * ("read-down"). */ if (from_zptr->zone_id == GLOBAL_ZONEID) { /* make the mount read-only */ vfs_setmntopt(vfsp, MNTOPT_RO, NULL, 0); } else { /* cross-zone mount */ if (to_zptr->zone_id == GLOBAL_ZONEID && /* LINTED: no consequent */ getpflags(NET_MAC_AWARE, cr) != 0) { /* Allow the mount as read-write */ } else if (bldominates( label2bslabel(to_zptr->zone_slabel), label2bslabel(from_zptr->zone_slabel))) { /* make the mount read-only */ vfs_setmntopt(vfsp, MNTOPT_RO, NULL, 0); } else { VN_RELE(realrootvp); zone_rele(to_zptr); zone_rele(from_zptr); return (EACCES); } } } zone_rele(to_zptr); zone_rele(from_zptr); } /* * realrootvp may be an AUTOFS node, in which case we * perform a VOP_ACCESS() to trigger the mount of the * intended filesystem, so we loopback mount the intended * filesystem instead of the AUTOFS filesystem. */ (void) VOP_ACCESS(realrootvp, 0, 0, cr, NULL); /* * We're interested in the top most filesystem. * This is specially important when uap->spec is a trigger * AUTOFS node, since we're really interested in mounting the * filesystem AUTOFS mounted as result of the VOP_ACCESS() * call not the AUTOFS node itself. */ if (vn_mountedvfs(realrootvp) != NULL) { if (error = traverse(&realrootvp)) { VN_RELE(realrootvp); return (error); } } /* * Allocate a vfs info struct and attach it */ li = kmem_zalloc(sizeof (struct loinfo), KM_SLEEP); li->li_realvfs = realrootvp->v_vfsp; li->li_mountvfs = vfsp; /* * Set mount flags to be inherited by loopback vfs's */ if (vfs_optionisset(vfsp, MNTOPT_RO, NULL)) { li->li_mflag |= VFS_RDONLY; } if (vfs_optionisset(vfsp, MNTOPT_NOSUID, NULL)) { li->li_mflag |= (VFS_NOSETUID|VFS_NODEVICES); } if (vfs_optionisset(vfsp, MNTOPT_NODEVICES, NULL)) { li->li_mflag |= VFS_NODEVICES; } if (vfs_optionisset(vfsp, MNTOPT_NOSETUID, NULL)) { li->li_mflag |= VFS_NOSETUID; } /* * Permissive flags are added to the "deny" bitmap. */ if (vfs_optionisset(vfsp, MNTOPT_NOXATTR, NULL)) { li->li_dflag |= VFS_XATTR; } if (vfs_optionisset(vfsp, MNTOPT_NONBMAND, NULL)) { li->li_dflag |= VFS_NBMAND; } /* * Propagate inheritable mount flags from the real vfs. */ if ((li->li_realvfs->vfs_flag & VFS_RDONLY) && !vfs_optionisset(vfsp, MNTOPT_RO, NULL)) vfs_setmntopt(vfsp, MNTOPT_RO, NULL, VFS_NODISPLAY); if ((li->li_realvfs->vfs_flag & VFS_NOSETUID) && !vfs_optionisset(vfsp, MNTOPT_NOSETUID, NULL)) vfs_setmntopt(vfsp, MNTOPT_NOSETUID, NULL, VFS_NODISPLAY); if ((li->li_realvfs->vfs_flag & VFS_NODEVICES) && !vfs_optionisset(vfsp, MNTOPT_NODEVICES, NULL)) vfs_setmntopt(vfsp, MNTOPT_NODEVICES, NULL, VFS_NODISPLAY); /* * Permissive flags such as VFS_XATTR, as opposed to restrictive flags * such as VFS_RDONLY, are handled differently. An explicit * MNTOPT_NOXATTR should override the underlying filesystem's VFS_XATTR. */ if ((li->li_realvfs->vfs_flag & VFS_XATTR) && !vfs_optionisset(vfsp, MNTOPT_NOXATTR, NULL) && !vfs_optionisset(vfsp, MNTOPT_XATTR, NULL)) vfs_setmntopt(vfsp, MNTOPT_XATTR, NULL, VFS_NODISPLAY); if ((li->li_realvfs->vfs_flag & VFS_NBMAND) && !vfs_optionisset(vfsp, MNTOPT_NBMAND, NULL) && !vfs_optionisset(vfsp, MNTOPT_NONBMAND, NULL)) vfs_setmntopt(vfsp, MNTOPT_NBMAND, NULL, VFS_NODISPLAY); li->li_refct = 0; vfsp->vfs_data = (caddr_t)li; vfsp->vfs_bcount = 0; vfsp->vfs_fstype = lofsfstype; vfsp->vfs_bsize = li->li_realvfs->vfs_bsize; vfsp->vfs_dev = li->li_realvfs->vfs_dev; vfsp->vfs_fsid.val[0] = li->li_realvfs->vfs_fsid.val[0]; vfsp->vfs_fsid.val[1] = li->li_realvfs->vfs_fsid.val[1]; if (vfs_optionisset(vfsp, MNTOPT_LOFS_NOSUB, NULL)) { li->li_flag |= LO_NOSUB; } /* * Propagate any VFS features */ vfs_propagate_features(li->li_realvfs, vfsp); /* * Setup the hashtable. If the root of this mount isn't a directory, * there's no point in allocating a large hashtable. A table with one * bucket is sufficient. */ if (realrootvp->v_type != VDIR) lsetup(li, 1); else lsetup(li, 0); /* * Make the root vnode */ srootvp = makelonode(realrootvp, li, 0); srootvp->v_flag |= VROOT; li->li_rootvp = srootvp; #ifdef LODEBUG lo_dprint(4, "lo_mount: vfs %p realvfs %p root %p realroot %p li %p\n", vfsp, li->li_realvfs, srootvp, realrootvp, li); #endif return (0); }
static int auto_lookup( vnode_t *dvp, char *nm, vnode_t **vpp, pathname_t *pnp, int flags, vnode_t *rdir, cred_t *cred, caller_context_t *ct, int *direntflags, pathname_t *realpnp) { int error = 0; vnode_t *newvp = NULL; vfs_t *vfsp; fninfo_t *dfnip; fnnode_t *dfnp = NULL; fnnode_t *fnp = NULL; char *searchnm; int operation; /* either AUTOFS_LOOKUP or AUTOFS_MOUNT */ dfnip = vfstofni(dvp->v_vfsp); AUTOFS_DPRINT((3, "auto_lookup: dvp=%p (%s) name=%s\n", (void *)dvp, dfnip->fi_map, nm)); if (nm[0] == 0) { VN_HOLD(dvp); *vpp = dvp; return (0); } if (error = VOP_ACCESS(dvp, VEXEC, 0, cred, ct)) return (error); if (nm[0] == '.' && nm[1] == 0) { VN_HOLD(dvp); *vpp = dvp; return (0); } if (nm[0] == '.' && nm[1] == '.' && nm[2] == 0) { fnnode_t *pdfnp; pdfnp = (vntofn(dvp))->fn_parent; ASSERT(pdfnp != NULL); /* * Since it is legitimate to have the VROOT flag set for the * subdirectories of the indirect map in autofs filesystem, * rootfnnodep is checked against fnnode of dvp instead of * just checking whether VROOT flag is set in dvp */ if (pdfnp == pdfnp->fn_globals->fng_rootfnnodep) { vnode_t *vp; vfs_rlock_wait(dvp->v_vfsp); if (dvp->v_vfsp->vfs_flag & VFS_UNMOUNTED) { vfs_unlock(dvp->v_vfsp); return (EIO); } vp = dvp->v_vfsp->vfs_vnodecovered; VN_HOLD(vp); vfs_unlock(dvp->v_vfsp); error = VOP_LOOKUP(vp, nm, vpp, pnp, flags, rdir, cred, ct, direntflags, realpnp); VN_RELE(vp); return (error); } else { *vpp = fntovn(pdfnp); VN_HOLD(*vpp); return (0); } } top: dfnp = vntofn(dvp); searchnm = nm; operation = 0; ASSERT(vn_matchops(dvp, auto_vnodeops)); AUTOFS_DPRINT((3, "auto_lookup: dvp=%p dfnp=%p\n", (void *)dvp, (void *)dfnp)); /* * If a lookup or mount of this node is in progress, wait for it * to finish, and return whatever result it got. */ mutex_enter(&dfnp->fn_lock); if (dfnp->fn_flags & (MF_LOOKUP | MF_INPROG)) { mutex_exit(&dfnp->fn_lock); error = auto_wait4mount(dfnp); if (error == AUTOFS_SHUTDOWN) error = ENOENT; if (error == EAGAIN) goto top; if (error) return (error); } else mutex_exit(&dfnp->fn_lock); error = vn_vfsrlock_wait(dvp); if (error) return (error); vfsp = vn_mountedvfs(dvp); if (vfsp != NULL) { error = VFS_ROOT(vfsp, &newvp); vn_vfsunlock(dvp); if (!error) { error = VOP_LOOKUP(newvp, nm, vpp, pnp, flags, rdir, cred, ct, direntflags, realpnp); VN_RELE(newvp); } return (error); } vn_vfsunlock(dvp); rw_enter(&dfnp->fn_rwlock, RW_READER); error = auto_search(dfnp, nm, &fnp, cred); if (error) { if (dfnip->fi_flags & MF_DIRECT) { /* * direct map. */ if (dfnp->fn_dirents) { /* * Mount previously triggered. * 'nm' not found */ error = ENOENT; } else { /* * I need to contact the daemon to trigger * the mount. 'dfnp' will be the mountpoint. */ operation = AUTOFS_MOUNT; VN_HOLD(fntovn(dfnp)); fnp = dfnp; error = 0; } } else if (dvp == dfnip->fi_rootvp) { /* * 'dfnp' is the root of the indirect AUTOFS. */ if (rw_tryupgrade(&dfnp->fn_rwlock) == 0) { /* * Could not acquire writer lock, release * reader, and wait until available. We * need to search for 'nm' again, since we * had to release the lock before reacquiring * it. */ rw_exit(&dfnp->fn_rwlock); rw_enter(&dfnp->fn_rwlock, RW_WRITER); error = auto_search(dfnp, nm, &fnp, cred); } ASSERT(RW_WRITE_HELD(&dfnp->fn_rwlock)); if (error) { /* * create node being looked-up and request * mount on it. */ error = auto_enter(dfnp, nm, &fnp, kcred); if (!error) operation = AUTOFS_LOOKUP; } } else if ((dfnp->fn_dirents == NULL) && ((dvp->v_flag & VROOT) == 0) && ((fntovn(dfnp->fn_parent))->v_flag & VROOT)) { /* * dfnp is the actual 'mountpoint' of indirect map, * it is the equivalent of a direct mount, * ie, /home/'user1' */ operation = AUTOFS_MOUNT; VN_HOLD(fntovn(dfnp)); fnp = dfnp; error = 0; searchnm = dfnp->fn_name; } } if (error == EAGAIN) { rw_exit(&dfnp->fn_rwlock); goto top; } if (error) { rw_exit(&dfnp->fn_rwlock); return (error); } /* * We now have the actual fnnode we're interested in. * The 'MF_LOOKUP' indicates another thread is currently * performing a daemon lookup of this node, therefore we * wait for its completion. * The 'MF_INPROG' indicates another thread is currently * performing a daemon mount of this node, we wait for it * to be done if we are performing a MOUNT. We don't * wait for it if we are performing a LOOKUP. * We can release the reader/writer lock as soon as we acquire * the mutex, since the state of the lock can only change by * first acquiring the mutex. */ mutex_enter(&fnp->fn_lock); rw_exit(&dfnp->fn_rwlock); if ((fnp->fn_flags & MF_LOOKUP) || ((operation == AUTOFS_MOUNT) && (fnp->fn_flags & MF_INPROG))) { mutex_exit(&fnp->fn_lock); error = auto_wait4mount(fnp); VN_RELE(fntovn(fnp)); if (error == AUTOFS_SHUTDOWN) error = ENOENT; if (error && error != EAGAIN) return (error); goto top; } if (operation == 0) { /* * got the fnnode, check for any errors * on the previous operation on that node. */ error = fnp->fn_error; if ((error == EINTR) || (error == EAGAIN)) { /* * previous operation on this node was * not completed, do a lookup now. */ operation = AUTOFS_LOOKUP; } else { /* * previous operation completed. Return * a pointer to the node only if there was * no error. */ mutex_exit(&fnp->fn_lock); if (!error) *vpp = fntovn(fnp); else VN_RELE(fntovn(fnp)); return (error); } } /* * Since I got to this point, it means I'm the one * responsible for triggering the mount/look-up of this node. */ switch (operation) { case AUTOFS_LOOKUP: AUTOFS_BLOCK_OTHERS(fnp, MF_LOOKUP); fnp->fn_error = 0; mutex_exit(&fnp->fn_lock); error = auto_lookup_aux(fnp, searchnm, cred); if (!error) { /* * Return this vnode */ *vpp = fntovn(fnp); } else { /* * release our reference to this vnode * and return error */ VN_RELE(fntovn(fnp)); } break; case AUTOFS_MOUNT: AUTOFS_BLOCK_OTHERS(fnp, MF_INPROG); fnp->fn_error = 0; mutex_exit(&fnp->fn_lock); /* * auto_new_mount_thread fires up a new thread which * calls automountd finishing up the work */ auto_new_mount_thread(fnp, searchnm, cred); /* * At this point, we are simply another thread * waiting for the mount to complete */ error = auto_wait4mount(fnp); if (error == AUTOFS_SHUTDOWN) error = ENOENT; /* * now release our reference to this vnode */ VN_RELE(fntovn(fnp)); if (!error) goto top; break; default: auto_log(dfnp->fn_globals->fng_verbose, dfnp->fn_globals->fng_zoneid, CE_WARN, "auto_lookup: unknown operation %d", operation); } AUTOFS_DPRINT((5, "auto_lookup: name=%s *vpp=%p return=%d\n", nm, (void *)*vpp, error)); return (error); }
static int auto_getattr( vnode_t *vp, vattr_t *vap, int flags, cred_t *cred, caller_context_t *ct) { fnnode_t *fnp = vntofn(vp); vnode_t *newvp; vfs_t *vfsp; int error; AUTOFS_DPRINT((4, "auto_getattr vp %p\n", (void *)vp)); if (flags & ATTR_TRIGGER) { /* * Pre-trigger the mount */ error = auto_trigger_mount(vp, cred, &newvp); if (error) return (error); if (newvp == NULL) goto defattr; if (error = vn_vfsrlock_wait(vp)) { VN_RELE(newvp); return (error); } vfsp = newvp->v_vfsp; VN_RELE(newvp); } else { /* * Recursive auto_getattr/mount; go to the vfsp == NULL * case. */ if (vn_vfswlock_held(vp)) goto defattr; if (error = vn_vfsrlock_wait(vp)) return (error); vfsp = vn_mountedvfs(vp); } if (vfsp != NULL) { /* * Node is mounted on. */ error = VFS_ROOT(vfsp, &newvp); vn_vfsunlock(vp); if (error) return (error); mutex_enter(&fnp->fn_lock); if (fnp->fn_seen == newvp && fnp->fn_thread == curthread) { /* * Recursive auto_getattr(); just release newvp and drop * into the vfsp == NULL case. */ mutex_exit(&fnp->fn_lock); VN_RELE(newvp); } else { while (fnp->fn_thread && fnp->fn_thread != curthread) { fnp->fn_flags |= MF_ATTR_WAIT; cv_wait(&fnp->fn_cv_mount, &fnp->fn_lock); } fnp->fn_thread = curthread; fnp->fn_seen = newvp; mutex_exit(&fnp->fn_lock); error = VOP_GETATTR(newvp, vap, flags, cred, ct); VN_RELE(newvp); mutex_enter(&fnp->fn_lock); fnp->fn_seen = 0; fnp->fn_thread = 0; if (fnp->fn_flags & MF_ATTR_WAIT) { fnp->fn_flags &= ~MF_ATTR_WAIT; cv_broadcast(&fnp->fn_cv_mount); } mutex_exit(&fnp->fn_lock); return (error); } } else { vn_vfsunlock(vp); } defattr: ASSERT(vp->v_type == VDIR || vp->v_type == VLNK); vap->va_uid = 0; vap->va_gid = 0; vap->va_nlink = fnp->fn_linkcnt; vap->va_nodeid = (u_longlong_t)fnp->fn_nodeid; vap->va_size = fnp->fn_size; vap->va_atime = fnp->fn_atime; vap->va_mtime = fnp->fn_mtime; vap->va_ctime = fnp->fn_ctime; vap->va_type = vp->v_type; vap->va_mode = fnp->fn_mode; vap->va_fsid = vp->v_vfsp->vfs_dev; vap->va_rdev = 0; vap->va_blksize = MAXBSIZE; vap->va_nblocks = (fsblkcnt64_t)btod(vap->va_size); vap->va_seq = 0; return (0); }
/* * Triggers the mount if needed. If the mount has been triggered by * another thread, it will wait for its return status, and return it. * Whether the mount is triggered by this thread, another thread, or * if the vnode was already covered, '*newvp' is a * VN_HELD vnode pointing to the root of the filesystem covering 'vp'. * If the node is not mounted on, and should not be mounted on, '*newvp' * will be NULL. * The calling routine may use '*newvp' to do the filesystem jump. */ static int auto_trigger_mount(vnode_t *vp, cred_t *cred, vnode_t **newvp) { fnnode_t *fnp = vntofn(vp); fninfo_t *fnip = vfstofni(vp->v_vfsp); vnode_t *dvp; vfs_t *vfsp; int delayed_ind; char name[AUTOFS_MAXPATHLEN]; int error; AUTOFS_DPRINT((4, "auto_trigger_mount: vp=%p\n", (void *)vp)); *newvp = NULL; /* * Cross-zone mount triggering is disallowed. */ if (fnip->fi_zoneid != getzoneid()) return (EPERM); /* Not owner of mount */ retry: error = 0; delayed_ind = 0; mutex_enter(&fnp->fn_lock); while (fnp->fn_flags & (MF_LOOKUP | MF_INPROG)) { /* * Mount or lookup in progress, * wait for it before proceeding. */ mutex_exit(&fnp->fn_lock); error = auto_wait4mount(fnp); if (error == AUTOFS_SHUTDOWN) { error = 0; goto done; } if (error && error != EAGAIN) goto done; error = 0; mutex_enter(&fnp->fn_lock); } /* * If the vfslock can't be acquired for the first time. * drop the fn_lock and retry next time in blocking mode. */ if (vn_vfswlock(vp)) { /* * Lock held by another thread. * Perform blocking by dropping the * fn_lock. */ mutex_exit(&fnp->fn_lock); error = vn_vfswlock_wait(vp); if (error) goto done; /* * Because fn_lock wasn't held, the state * of the trigger node might have changed. * Need to run through the checks on trigger * node again. */ vn_vfsunlock(vp); goto retry; } vfsp = vn_mountedvfs(vp); if (vfsp != NULL) { mutex_exit(&fnp->fn_lock); error = VFS_ROOT(vfsp, newvp); vn_vfsunlock(vp); goto done; } else { vn_vfsunlock(vp); if ((fnp->fn_flags & MF_MOUNTPOINT) && fnp->fn_trigger != NULL) { ASSERT(fnp->fn_dirents == NULL); mutex_exit(&fnp->fn_lock); /* * The filesystem that used to sit here has been * forcibly unmounted. Do our best to recover. * Try to unmount autofs subtree below this node * and retry the action. */ if (unmount_subtree(fnp, B_TRUE) != 0) { error = EIO; goto done; } goto retry; } } ASSERT(vp->v_type == VDIR); dvp = fntovn(fnp->fn_parent); if ((fnp->fn_dirents == NULL) && ((fnip->fi_flags & MF_DIRECT) == 0) && ((vp->v_flag & VROOT) == 0) && (dvp->v_flag & VROOT)) { /* * If the parent of this node is the root of an indirect * AUTOFS filesystem, this node is remountable. */ delayed_ind = 1; } if (delayed_ind || ((fnip->fi_flags & MF_DIRECT) && (fnp->fn_dirents == NULL))) { /* * Trigger mount since: * direct mountpoint with no subdirs or * delayed indirect. */ AUTOFS_BLOCK_OTHERS(fnp, MF_INPROG); fnp->fn_error = 0; mutex_exit(&fnp->fn_lock); if (delayed_ind) (void) strcpy(name, fnp->fn_name); else (void) strcpy(name, "."); fnp->fn_ref_time = gethrestime_sec(); auto_new_mount_thread(fnp, name, cred); /* * At this point we're simply another thread waiting * for the mount to finish. */ error = auto_wait4mount(fnp); if (error == EAGAIN) goto retry; if (error == AUTOFS_SHUTDOWN) { error = 0; goto done; } if (error == 0) { if (error = vn_vfsrlock_wait(vp)) goto done; /* Reacquire after dropping locks */ vfsp = vn_mountedvfs(vp); if (vfsp != NULL) { error = VFS_ROOT(vfsp, newvp); vn_vfsunlock(vp); } else { vn_vfsunlock(vp); goto retry; } } } else mutex_exit(&fnp->fn_lock); done: AUTOFS_DPRINT((5, "auto_trigger_mount: error=%d\n", error)); return (error); }
static int chdirec(vnode_t *vp, int ischroot, int do_traverse) { int error; vnode_t *oldvp; proc_t *pp = curproc; vnode_t **vpp; refstr_t *cwd; int newcwd = 1; if (vp->v_type != VDIR) { error = ENOTDIR; goto bad; } if (error = VOP_ACCESS(vp, VEXEC, 0, CRED(), NULL)) goto bad; /* * The VOP_ACCESS() may have covered 'vp' with a new filesystem, * if 'vp' is an autoFS vnode. Traverse the mountpoint so * that we don't end up with a covered current directory. */ if (vn_mountedvfs(vp) != NULL && do_traverse) { if (error = traverse(&vp)) goto bad; } /* * Special chroot semantics: chroot is allowed if privileged * or if the target is really a loopback mount of the root (or * root of the zone) as determined by comparing dev and inode * numbers */ if (ischroot) { struct vattr tattr; struct vattr rattr; vnode_t *zonevp = curproc->p_zone->zone_rootvp; tattr.va_mask = AT_FSID|AT_NODEID; if (error = VOP_GETATTR(vp, &tattr, 0, CRED(), NULL)) goto bad; rattr.va_mask = AT_FSID|AT_NODEID; if (error = VOP_GETATTR(zonevp, &rattr, 0, CRED(), NULL)) goto bad; if ((tattr.va_fsid != rattr.va_fsid || tattr.va_nodeid != rattr.va_nodeid) && (error = secpolicy_chroot(CRED())) != 0) goto bad; vpp = &PTOU(pp)->u_rdir; } else { vpp = &PTOU(pp)->u_cdir; } if (audit_active) /* update abs cwd/root path see c2audit.c */ audit_chdirec(vp, vpp); mutex_enter(&pp->p_lock); /* * This bit of logic prevents us from overwriting u_cwd if we are * changing to the same directory. We set the cwd to NULL so that we * don't try to do the lookup on the next call to getcwd(). */ if (!ischroot && *vpp != NULL && vp != NULL && VN_CMP(*vpp, vp)) newcwd = 0; oldvp = *vpp; *vpp = vp; if ((cwd = PTOU(pp)->u_cwd) != NULL && newcwd) PTOU(pp)->u_cwd = NULL; mutex_exit(&pp->p_lock); if (cwd && newcwd) refstr_rele(cwd); if (oldvp) VN_RELE(oldvp); return (0); bad: VN_RELE(vp); return (error); }
/* * Traverse a mount point. Routine accepts a vnode pointer as a reference * parameter and performs the indirection, releasing the original vnode. */ int traverse(vnode_t **cvpp) { int error = 0; vnode_t *cvp; vnode_t *tvp; vfs_t *vfsp; cvp = *cvpp; /* * If this vnode is mounted on, then we transparently indirect * to the vnode which is the root of the mounted file system. * Before we do this we must check that an unmount is not in * progress on this vnode. */ for (;;) { /* * Try to read lock the vnode. If this fails because * the vnode is already write locked, then check to * see whether it is the current thread which locked * the vnode. If it is not, then read lock the vnode * by waiting to acquire the lock. * * The code path in domount() is an example of support * which needs to look up two pathnames and locks one * of them in between the two lookups. */ error = vn_vfsrlock(cvp); if (error) { if (!vn_vfswlock_held(cvp)) error = vn_vfsrlock_wait(cvp); if (error != 0) { /* * lookuppn() expects a held vnode to be * returned because it promptly calls * VN_RELE after the error return */ *cvpp = cvp; return (error); } } /* * Reached the end of the mount chain? */ vfsp = vn_mountedvfs(cvp); if (vfsp == NULL) { vn_vfsunlock(cvp); break; } /* * The read lock must be held across the call to VFS_ROOT() to * prevent a concurrent unmount from destroying the vfs. */ error = VFS_ROOT(vfsp, &tvp); vn_vfsunlock(cvp); if (error) break; VN_RELE(cvp); cvp = tvp; } *cvpp = cvp; return (error); }
/* * Starting at current directory, translate pathname pnp to end. * Leave pathname of final component in pnp, return the vnode * for the final component in *compvpp, and return the vnode * for the parent of the final component in dirvpp. * * This is the central routine in pathname translation and handles * multiple components in pathnames, separating them at /'s. It also * implements mounted file systems and processes symbolic links. * * vp is the vnode where the directory search should start. * * Reference counts: vp must be held prior to calling this function. rootvp * should only be held if rootvp != rootdir. */ int lookuppnvp( struct pathname *pnp, /* pathname to lookup */ struct pathname *rpnp, /* if non-NULL, return resolved path */ int flags, /* follow symlinks */ vnode_t **dirvpp, /* ptr for parent vnode */ vnode_t **compvpp, /* ptr for entry vnode */ vnode_t *rootvp, /* rootvp */ vnode_t *vp, /* directory to start search at */ cred_t *cr) /* user's credential */ { vnode_t *cvp; /* current component vp */ char component[MAXNAMELEN]; /* buffer for component (incl null) */ int error; int nlink; int lookup_flags; struct pathname presrvd; /* case preserved name */ struct pathname *pp = NULL; vnode_t *startvp; vnode_t *zonevp = curproc->p_zone->zone_rootvp; /* zone root */ int must_be_directory = 0; boolean_t retry_with_kcred; uint32_t auditing = AU_AUDITING(); CPU_STATS_ADDQ(CPU, sys, namei, 1); nlink = 0; cvp = NULL; if (rpnp) rpnp->pn_pathlen = 0; lookup_flags = dirvpp ? LOOKUP_DIR : 0; if (flags & FIGNORECASE) { lookup_flags |= FIGNORECASE; pn_alloc(&presrvd); pp = &presrvd; } if (auditing) audit_anchorpath(pnp, vp == rootvp); /* * Eliminate any trailing slashes in the pathname. * If there are any, we must follow all symlinks. * Also, we must guarantee that the last component is a directory. */ if (pn_fixslash(pnp)) { flags |= FOLLOW; must_be_directory = 1; } startvp = vp; next: retry_with_kcred = B_FALSE; /* * Make sure we have a directory. */ if (vp->v_type != VDIR) { error = ENOTDIR; goto bad; } if (rpnp && VN_CMP(vp, rootvp)) (void) pn_set(rpnp, "/"); /* * Process the next component of the pathname. */ if (error = pn_getcomponent(pnp, component)) { goto bad; } /* * Handle "..": two special cases. * 1. If we're at the root directory (e.g. after chroot or * zone_enter) then change ".." to "." so we can't get * out of this subtree. * 2. If this vnode is the root of a mounted file system, * then replace it with the vnode that was mounted on * so that we take the ".." in the other file system. */ if (component[0] == '.' && component[1] == '.' && component[2] == 0) { checkforroot: if (VN_CMP(vp, rootvp) || VN_CMP(vp, zonevp)) { component[1] = '\0'; } else if (vp->v_flag & VROOT) { vfs_t *vfsp; cvp = vp; /* * While we deal with the vfs pointer from the vnode * the filesystem could have been forcefully unmounted * and the vnode's v_vfsp could have been invalidated * by VFS_UNMOUNT. Hence, we cache v_vfsp and use it * with vfs_rlock_wait/vfs_unlock. * It is safe to use the v_vfsp even it is freed by * VFS_UNMOUNT because vfs_rlock_wait/vfs_unlock * do not dereference v_vfsp. It is just used as a * magic cookie. * One more corner case here is the memory getting * reused for another vfs structure. In this case * lookuppnvp's vfs_rlock_wait will succeed, domount's * vfs_lock will fail and domount will bail out with an * error (EBUSY). */ vfsp = cvp->v_vfsp; /* * This lock is used to synchronize * mounts/unmounts and lookups. * Threads doing mounts/unmounts hold the * writers version vfs_lock_wait(). */ vfs_rlock_wait(vfsp); /* * If this vnode is on a file system that * has been forcibly unmounted, * we can't proceed. Cancel this operation * and return EIO. * * vfs_vnodecovered is NULL if unmounted. * Currently, nfs uses VFS_UNMOUNTED to * check if it's a forced-umount. Keep the * same checking here as well even though it * may not be needed. */ if (((vp = cvp->v_vfsp->vfs_vnodecovered) == NULL) || (cvp->v_vfsp->vfs_flag & VFS_UNMOUNTED)) { vfs_unlock(vfsp); VN_RELE(cvp); if (pp) pn_free(pp); return (EIO); } VN_HOLD(vp); vfs_unlock(vfsp); VN_RELE(cvp); cvp = NULL; /* * Crossing mount points. For eg: We are doing * a lookup of ".." for file systems root vnode * mounted here, and VOP_LOOKUP() (with covered vnode) * will be on underlying file systems mount point * vnode. Set retry_with_kcred flag as we might end * up doing VOP_LOOKUP() with kcred if required. */ retry_with_kcred = B_TRUE; goto checkforroot; } } /* * LOOKUP_CHECKREAD is a private flag used by vnodetopath() to indicate * that we need to have read permission on every directory in the entire * path. This is used to ensure that a forward-lookup of a cached value * has the same effect as a reverse-lookup when the cached value cannot * be found. */ if ((flags & LOOKUP_CHECKREAD) && (error = VOP_ACCESS(vp, VREAD, 0, cr, NULL)) != 0) goto bad; /* * Perform a lookup in the current directory. */ error = VOP_LOOKUP(vp, component, &cvp, pnp, lookup_flags, rootvp, cr, NULL, NULL, pp); /* * Retry with kcred - If crossing mount points & error is EACCES. * * If we are crossing mount points here and doing ".." lookup, * VOP_LOOKUP() might fail if the underlying file systems * mount point has no execute permission. In cases like these, * we retry VOP_LOOKUP() by giving as much privilage as possible * by passing kcred credentials. * * In case of hierarchical file systems, passing kcred still may * or may not work. * For eg: UFS FS --> Mount NFS FS --> Again mount UFS on some * directory inside NFS FS. */ if ((error == EACCES) && retry_with_kcred) error = VOP_LOOKUP(vp, component, &cvp, pnp, lookup_flags, rootvp, zone_kcred(), NULL, NULL, pp); if (error) { cvp = NULL; /* * On error, return hard error if * (a) we're not at the end of the pathname yet, or * (b) the caller didn't want the parent directory, or * (c) we failed for some reason other than a missing entry. */ if (pn_pathleft(pnp) || dirvpp == NULL || error != ENOENT) goto bad; if (auditing) { /* directory access */ if (error = audit_savepath(pnp, vp, vp, error, cr)) goto bad_noaudit; } pn_setlast(pnp); /* * We inform the caller that the desired entry must be * a directory by adding a '/' to the component name. */ if (must_be_directory && (error = pn_addslash(pnp)) != 0) goto bad; *dirvpp = vp; if (compvpp != NULL) *compvpp = NULL; if (rootvp != rootdir) VN_RELE(rootvp); if (pp) pn_free(pp); return (0); } /* * Traverse mount points. * XXX why don't we need to hold a read lock here (call vn_vfsrlock)? * What prevents a concurrent update to v_vfsmountedhere? * Possible answer: if mounting, we might not see the mount * if it is concurrently coming into existence, but that's * really not much different from the thread running a bit slower. * If unmounting, we may get into traverse() when we shouldn't, * but traverse() will catch this case for us. * (For this to work, fetching v_vfsmountedhere had better * be atomic!) */ if (vn_mountedvfs(cvp) != NULL) { if ((error = traverse(&cvp)) != 0) goto bad; } /* * If we hit a symbolic link and there is more path to be * translated or this operation does not wish to apply * to a link, then place the contents of the link at the * front of the remaining pathname. */ if (cvp->v_type == VLNK && ((flags & FOLLOW) || pn_pathleft(pnp))) { struct pathname linkpath; if (++nlink > MAXSYMLINKS) { error = ELOOP; goto bad; } pn_alloc(&linkpath); if (error = pn_getsymlink(cvp, &linkpath, cr)) { pn_free(&linkpath); goto bad; } if (auditing) audit_symlink(pnp, &linkpath); if (pn_pathleft(&linkpath) == 0) (void) pn_set(&linkpath, "."); error = pn_insert(pnp, &linkpath, strlen(component)); pn_free(&linkpath); if (error) goto bad; VN_RELE(cvp); cvp = NULL; if (pnp->pn_pathlen == 0) { error = ENOENT; goto bad; } if (pnp->pn_path[0] == '/') { do { pnp->pn_path++; pnp->pn_pathlen--; } while (pnp->pn_path[0] == '/'); VN_RELE(vp); vp = rootvp; VN_HOLD(vp); } if (auditing) audit_anchorpath(pnp, vp == rootvp); if (pn_fixslash(pnp)) { flags |= FOLLOW; must_be_directory = 1; } goto next; } /* * If rpnp is non-NULL, remember the resolved path name therein. * Do not include "." components. Collapse occurrences of * "previous/..", so long as "previous" is not itself "..". * Exhausting rpnp results in error ENAMETOOLONG. */ if (rpnp && strcmp(component, ".") != 0) { size_t len; if (strcmp(component, "..") == 0 && rpnp->pn_pathlen != 0 && !((rpnp->pn_pathlen > 2 && strncmp(rpnp->pn_path+rpnp->pn_pathlen-3, "/..", 3) == 0) || (rpnp->pn_pathlen == 2 && strncmp(rpnp->pn_path, "..", 2) == 0))) { while (rpnp->pn_pathlen && rpnp->pn_path[rpnp->pn_pathlen-1] != '/') rpnp->pn_pathlen--; if (rpnp->pn_pathlen > 1) rpnp->pn_pathlen--; rpnp->pn_path[rpnp->pn_pathlen] = '\0'; } else { if (rpnp->pn_pathlen != 0 && rpnp->pn_path[rpnp->pn_pathlen-1] != '/') rpnp->pn_path[rpnp->pn_pathlen++] = '/'; if (flags & FIGNORECASE) { /* * Return the case-preserved name * within the resolved path. */ error = copystr(pp->pn_buf, rpnp->pn_path + rpnp->pn_pathlen, rpnp->pn_bufsize - rpnp->pn_pathlen, &len); } else { error = copystr(component, rpnp->pn_path + rpnp->pn_pathlen, rpnp->pn_bufsize - rpnp->pn_pathlen, &len); } if (error) /* copystr() returns ENAMETOOLONG */ goto bad; rpnp->pn_pathlen += (len - 1); ASSERT(rpnp->pn_bufsize > rpnp->pn_pathlen); } } /* * If no more components, return last directory (if wanted) and * last component (if wanted). */ if (pn_pathleft(pnp) == 0) { /* * If there was a trailing slash in the pathname, * make sure the last component is a directory. */ if (must_be_directory && cvp->v_type != VDIR) { error = ENOTDIR; goto bad; } if (dirvpp != NULL) { /* * Check that we have the real parent and not * an alias of the last component. */ if (vn_compare(vp, cvp)) { if (auditing) (void) audit_savepath(pnp, cvp, vp, EINVAL, cr); pn_setlast(pnp); VN_RELE(vp); VN_RELE(cvp); if (rootvp != rootdir) VN_RELE(rootvp); if (pp) pn_free(pp); return (EINVAL); } *dirvpp = vp; } else VN_RELE(vp); if (auditing) (void) audit_savepath(pnp, cvp, vp, 0, cr); if (pnp->pn_path == pnp->pn_buf) (void) pn_set(pnp, "."); else pn_setlast(pnp); if (rpnp) { if (VN_CMP(cvp, rootvp)) (void) pn_set(rpnp, "/"); else if (rpnp->pn_pathlen == 0) (void) pn_set(rpnp, "."); } if (compvpp != NULL) *compvpp = cvp; else VN_RELE(cvp); if (rootvp != rootdir) VN_RELE(rootvp); if (pp) pn_free(pp); return (0); } /* * Skip over slashes from end of last component. */ while (pnp->pn_path[0] == '/') { pnp->pn_path++; pnp->pn_pathlen--; } /* * Searched through another level of directory: * release previous directory handle and save new (result * of lookup) as current directory. */ VN_RELE(vp); vp = cvp; cvp = NULL; goto next; bad: if (auditing) /* reached end of path */ (void) audit_savepath(pnp, cvp, vp, error, cr); bad_noaudit: /* * Error. Release vnodes and return. */ if (cvp) VN_RELE(cvp); /* * If the error was ESTALE and the current directory to look in * was the root for this lookup, the root for a mounted file * system, or the starting directory for lookups, then * return ENOENT instead of ESTALE. In this case, no recovery * is possible by the higher level. If ESTALE was returned for * some intermediate directory along the path, then recovery * is potentially possible and retrying from the higher level * will either correct the situation by purging stale cache * entries or eventually get back to the point where no recovery * is possible. */ if (error == ESTALE && (VN_CMP(vp, rootvp) || (vp->v_flag & VROOT) || vp == startvp)) error = ENOENT; VN_RELE(vp); if (rootvp != rootdir) VN_RELE(rootvp); if (pp) pn_free(pp); return (error); }
/* * Locking i_contents in this * function seems to be really weird */ int ud_dirremove( struct ud_inode *dp, char *namep, struct ud_inode *oip, struct vnode *cdir, enum dr_op op, struct cred *cr, caller_context_t *ctp) { struct udf_vfs *udf_vfsp; int32_t namelen, err = 0; struct slot slot; struct ud_inode *ip; mode_t mode; struct file_id *fid; uint8_t *buf = NULL; uint32_t tbno; ud_printf("ud_dirremove\n"); ASSERT(RW_WRITE_HELD(&dp->i_rwlock)); udf_vfsp = dp->i_udf; namelen = (int)strlen(namep); if (namelen == 0) { cmn_err(CE_WARN, "name length == 0 in ud_dirremove"); return (EINVAL); } /* * return err when removing . and .. */ if (namep[0] == '.') { if (namelen == 1) { return (EINVAL); } else if (namelen == 2 && namep[1] == '.') { return (EEXIST); /* SIGH should be ENOTEMPTY */ } } ASSERT(RW_WRITE_HELD(&dp->i_rwlock)); /* * Check accessibility of directory. */ if (dp->i_type != VDIR) { return (ENOTDIR); } ip = NULL; slot.status = FOUND; /* don't need to look for empty slot */ slot.offset = 0; slot.size = 0; slot.fbp = NULL; slot.ep = NULL; slot.endoff = 0; /* * Execute access is required to search the directory. * Access for write is interpreted as allowing * deletion of files in the directory. */ if (err = ud_iaccess(dp, IEXEC|IWRITE, cr)) { return (err); } buf = (uint8_t *)kmem_zalloc(udf_vfsp->udf_lbsize, KM_SLEEP); rw_enter(&dp->i_contents, RW_WRITER); if (err = ud_dircheckforname(dp, namep, namelen, &slot, &ip, buf, cr)) { goto out_novfs; } if (ip == NULL) { err = ENOENT; goto out_novfs; } if (oip && oip != ip) { err = ENOENT; goto out_novfs; } if ((mode = ip->i_type) == VDIR) { /* * vn_vfswlock() prevents races between mount and rmdir. */ if (vn_vfswlock(ITOV(ip))) { err = EBUSY; goto out_novfs; } if (vn_mountedvfs(ITOV(ip)) != NULL && op != DR_RENAME) { err = EBUSY; goto out; } /* * If we are removing a directory, get a lock on it. * If the directory is empty, it will stay empty until * we can remove it. */ rw_enter(&ip->i_rwlock, RW_READER); } /* We must be holding i_contents */ rw_enter(&ip->i_contents, RW_READER); if (err = ud_sticky_remove_access(dp, ip, cr)) { rw_exit(&ip->i_contents); if (mode == VDIR) { rw_exit(&ip->i_rwlock); } goto out; } if (op == DR_RMDIR) { /* * For rmdir(2), some special checks are required. * (a) Don't remove any alias of the parent (e.g. "."). * (b) Don't remove the current directory. * (c) Make sure the entry is (still) a directory. * (d) Make sure the directory is empty. */ if (dp == ip || ITOV(ip) == cdir) { err = EINVAL; } else if (ip->i_type != VDIR) { err = ENOTDIR; } else if ((ip->i_nlink != 1) || (!ud_dirempty(ip, dp->i_uniqid, cr))) { /* * Directories do not have an * entry for "." so only one link * will be there */ err = EEXIST; /* SIGH should be ENOTEMPTY */ } if (err) { rw_exit(&ip->i_contents); if (mode == VDIR) { rw_exit(&ip->i_rwlock); } goto out; } } else if (op == DR_REMOVE) { /* * unlink(2) requires a different check: allow only * privileged processes to unlink a directory. */ struct vnode *vp = ITOV(ip); if (vp->v_type == VDIR && secpolicy_fs_linkdir(cr, vp->v_vfsp)) { err = EPERM; rw_exit(&ip->i_contents); rw_exit(&ip->i_rwlock); goto out; } } rw_exit(&ip->i_contents); /* * Remove the cache'd entry, if any. */ dnlc_remove(ITOV(dp), namep); /* * We can collapse all the directory * entries that are deleted into one big entry * but the better way is to * defer it till next directory entry * creation. where we can do this * in a more efficient way */ fid = slot.ep; /* * If this is the last entry * just truncate the file instead * of marking it deleted */ if ((slot.offset + FID_LEN(fid)) == dp->i_size) { fbrelse(slot.fbp, S_OTHER); if ((err = ud_itrunc(dp, slot.offset, 0, cr)) != 0) { goto out; } } else { fid->fid_flags |= FID_DELETED; if ((err = ud_ip_off2bno(dp, slot.offset, &tbno)) != 0) { goto out; } ud_make_tag(dp->i_udf, &fid->fid_tag, UD_FILE_ID_DESC, tbno, FID_LEN(fid)); err = ud_write_fid(dp, &slot, buf); } slot.fbp = NULL; /* * If we were removing a directory, it is 'gone' now so we can * unlock it. */ if (mode == VDIR) { rw_exit(&ip->i_rwlock); } mutex_enter(&dp->i_tlock); dp->i_flag |= IUPD|ICHG; mutex_exit(&dp->i_tlock); mutex_enter(&ip->i_tlock); ip->i_flag |= ICHG; mutex_exit(&ip->i_tlock); if (err != 0) { goto out; } rw_enter(&ip->i_contents, RW_WRITER); /* * Now dispose of the inode. */ if (ip->i_nlink > 0) { if ((op == DR_RMDIR) && (ip->i_type == VDIR)) { /* * Decrement by 1 because there is no "." * Clear the inode, but there may be other hard * links so don't free the inode. * Decrement the dp linkcount because we're * trashing the ".." entry. */ ip->i_nlink --; dp->i_nlink--; dnlc_remove(ITOV(ip), "."); dnlc_remove(ITOV(ip), ".."); /* * (void) ud_itrunc(ip, 0, 0, cr); */ } else { ip->i_nlink--; } } ITIMES_NOLOCK(dp); ITIMES_NOLOCK(ip); rw_exit(&ip->i_contents); out: if (mode == VDIR) { vn_vfsunlock(ITOV(ip)); } out_novfs: ASSERT(RW_WRITE_HELD(&dp->i_contents)); if (slot.fbp != NULL) { fbrelse(slot.fbp, S_OTHER); } rw_exit(&dp->i_contents); if (ip) { /* * If no errors, send any events after locks are dropped, * but before the VN_RELE(). */ if (err == 0) { if (op == DR_REMOVE) { vnevent_remove(ITOV(ip), ITOV(dp), namep, ctp); } else if (op == DR_RMDIR) { vnevent_rmdir(ITOV(ip), ITOV(dp), namep, ctp); } } VN_RELE(ITOV(ip)); } kmem_free(buf, udf_vfsp->udf_lbsize); return (err); }
int ud_dirrename(struct ud_inode *sdp, struct ud_inode *sip, struct ud_inode *tdp, struct ud_inode *tip, char *namep, uint8_t *buf, struct slot *slotp, struct cred *cr) { int32_t error = 0, doingdirectory; struct file_id *fid; ud_printf("ud_dirrename\n"); ASSERT(sdp->i_udf != NULL); ASSERT(MUTEX_HELD(&sdp->i_udf->udf_rename_lck)); ASSERT(RW_WRITE_HELD(&tdp->i_rwlock)); ASSERT(buf); ASSERT(slotp->ep); fid = slotp->ep; /* * Short circuit rename of something to itself. */ if (sip->i_icb_lbano == tip->i_icb_lbano) { return (ESAME); /* special KLUDGE error code */ } /* * Everything is protected under the vfs_rename_lock so the ordering * of i_contents locks doesn't matter here. */ rw_enter(&sip->i_contents, RW_READER); rw_enter(&tip->i_contents, RW_READER); /* * Check that everything is on the same filesystem. */ if ((ITOV(tip)->v_vfsp != ITOV(tdp)->v_vfsp) || (ITOV(tip)->v_vfsp != ITOV(sip)->v_vfsp)) { error = EXDEV; /* XXX archaic */ goto out; } /* * Must have write permission to rewrite target entry. */ if ((error = ud_iaccess(tdp, IWRITE, cr)) != 0 || (error = ud_sticky_remove_access(tdp, tip, cr)) != 0) goto out; /* * Ensure source and target are compatible (both directories * or both not directories). If target is a directory it must * be empty and have no links to it; in addition it must not * be a mount point, and both the source and target must be * writable. */ doingdirectory = (sip->i_type == VDIR); if (tip->i_type == VDIR) { if (!doingdirectory) { error = EISDIR; goto out; } /* * vn_vfswlock will prevent mounts from using the directory * until we are done. */ if (vn_vfswlock(ITOV(tip))) { error = EBUSY; goto out; } if (vn_mountedvfs(ITOV(tip)) != NULL) { vn_vfsunlock(ITOV(tip)); error = EBUSY; goto out; } if (!ud_dirempty(tip, tdp->i_uniqid, cr) || tip->i_nlink > 2) { vn_vfsunlock(ITOV(tip)); error = EEXIST; /* SIGH should be ENOTEMPTY */ goto out; } } else if (doingdirectory) { error = ENOTDIR; goto out; } /* * Rewrite the inode pointer for target name entry * from the target inode (ip) to the source inode (sip). * This prevents the target entry from disappearing * during a crash. Mark the directory inode to reflect the changes. */ dnlc_remove(ITOV(tdp), namep); fid->fid_icb.lad_ext_prn = SWAP_16(sip->i_icb_prn); fid->fid_icb.lad_ext_loc = SWAP_32(sip->i_icb_block); dnlc_enter(ITOV(tdp), namep, ITOV(sip)); ud_make_tag(tdp->i_udf, &fid->fid_tag, UD_FILE_ID_DESC, SWAP_32(fid->fid_tag.tag_loc), FID_LEN(fid)); error = ud_write_fid(tdp, slotp, buf); if (error) { if (doingdirectory) { vn_vfsunlock(ITOV(tip)); } goto out; } /* * Upgrade to write lock on tip */ rw_exit(&tip->i_contents); rw_enter(&tip->i_contents, RW_WRITER); mutex_enter(&tdp->i_tlock); tdp->i_flag |= IUPD|ICHG; mutex_exit(&tdp->i_tlock); /* * Decrement the link count of the target inode. * Fix the ".." entry in sip to point to dp. * This is done after the new entry is on the disk. */ tip->i_nlink--; mutex_enter(&tip->i_tlock); tip->i_flag |= ICHG; mutex_exit(&tip->i_tlock); if (doingdirectory) { /* * The entry for tip no longer exists so I can unlock the * vfslock. */ vn_vfsunlock(ITOV(tip)); /* * Decrement target link count once more if it was a directory. */ if (tip->i_nlink != 0) { cmn_err(CE_WARN, "ud_direnter: target directory link count != 0"); rw_exit(&tip->i_contents); rw_exit(&sip->i_contents); return (EINVAL); } /* * Renaming a directory with the parent different * requires that ".." be rewritten. The window is * still there for ".." to be inconsistent, but this * is unavoidable, and a lot shorter than when it was * done in a user process. We decrement the link * count in the new parent as appropriate to reflect * the just-removed target. If the parent is the * same, this is appropriate since the original * directory is going away. If the new parent is * different, dirfixdotdot() will bump the link count * back. */ tdp->i_nlink--; mutex_enter(&tdp->i_tlock); tdp->i_flag |= ICHG; mutex_exit(&tdp->i_tlock); ITIMES_NOLOCK(tdp); if (sdp != tdp) { rw_exit(&tip->i_contents); rw_exit(&sip->i_contents); error = ud_dirfixdotdot(sip, sdp, tdp); return (error); } } out: rw_exit(&tip->i_contents); rw_exit(&sip->i_contents); return (error); }
static int xdirrename( struct xmemnode *fromparent, /* parent directory of source */ struct xmemnode *fromxp, /* source xmemnode */ struct xmemnode *toparent, /* parent directory of target */ char *nm, /* entry we are trying to change */ struct xmemnode *to, /* target xmemnode */ struct xdirent *where, /* target xmemnode directory entry */ struct cred *cred) /* credentials */ { int error = 0; int doingdirectory; timestruc_t now; #if defined(lint) nm = nm; #endif ASSERT(RW_WRITE_HELD(&toparent->xn_rwlock)); rw_enter(&fromxp->xn_rwlock, RW_READER); rw_enter(&to->xn_rwlock, RW_READER); /* * Check that everything is on the same filesystem. */ if (to->xn_vnode->v_vfsp != toparent->xn_vnode->v_vfsp || to->xn_vnode->v_vfsp != fromxp->xn_vnode->v_vfsp) { error = EXDEV; goto out; } /* * Short circuit rename of something to itself. */ if (fromxp == to) { error = ESAME; /* special KLUDGE error code */ goto out; } /* * Must have write permission to rewrite target entry. */ if (error = xmem_xaccess(fromparent, VWRITE, cred)) goto out; /* * If the parent directory is "sticky", then the user must own * either the parent directory or the destination of the rename, * or else must have permission to write the destination. * Otherwise the destination may not be changed (except by the * privileged users). This implements append-only directories. */ if (error = xmem_sticky_remove_access(toparent, to, cred)) goto out; /* * Ensure source and target are compatible (both directories * or both not directories). If target is a directory it must * be empty and have no links to it; in addition it must not * be a mount point, and both the source and target must be * writable. */ doingdirectory = (fromxp->xn_type == VDIR); if (to->xn_type == VDIR) { if (!doingdirectory) { error = EISDIR; goto out; } /* * vn_vfswlock will prevent mounts from using the directory * until we are done. */ if (vn_vfswlock(XNTOV(to))) { error = EBUSY; goto out; } if (vn_mountedvfs(XNTOV(to)) != NULL) { vn_vfsunlock(XNTOV(to)); error = EBUSY; goto out; } mutex_enter(&to->xn_tlock); if (to->xn_dirents > 2 || to->xn_nlink > 2) { mutex_exit(&to->xn_tlock); vn_vfsunlock(XNTOV(to)); error = EEXIST; /* SIGH should be ENOTEMPTY */ /* * Update atime because checking xn_dirents is * logically equivalent to reading the directory */ gethrestime(&to->xn_atime); goto out; } mutex_exit(&to->xn_tlock); } else if (doingdirectory) { error = ENOTDIR; goto out; } where->xd_xmemnode = fromxp; gethrestime(&now); toparent->xn_mtime = now; toparent->xn_ctime = now; /* * Upgrade to write lock on "to" (i.e., the target xmemnode). */ rw_exit(&to->xn_rwlock); rw_enter(&to->xn_rwlock, RW_WRITER); /* * Decrement the link count of the target xmemnode. */ DECR_COUNT(&to->xn_nlink, &to->xn_tlock); to->xn_ctime = now; if (doingdirectory) { /* * The entry for "to" no longer exists so release the vfslock. */ vn_vfsunlock(XNTOV(to)); /* * Decrement the target link count and delete all entires. */ xdirtrunc(to); ASSERT(to->xn_nlink == 0); /* * Renaming a directory with the parent different * requires that ".." be rewritten. The window is * still there for ".." to be inconsistent, but this * is unavoidable, and a lot shorter than when it was * done in a user process. */ if (fromparent != toparent) xdirfixdotdot(fromxp, fromparent, toparent); } out: rw_exit(&to->xn_rwlock); rw_exit(&fromxp->xn_rwlock); return (error); }