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); }
/* * Unlink zp from dl, and mark zp for reaping if this was the last link. * Can fail if zp is a mount point (EBUSY) or a non-empty directory (EEXIST). * If 'reaped_ptr' is NULL, we put reaped znodes on the delete queue. * If it's non-NULL, we use it to indicate whether the znode needs reaping, * and it's the caller's job to do it. */ int zfs_link_destroy(zfs_dirlock_t *dl, znode_t *zp, dmu_tx_t *tx, int flag, int *reaped_ptr) { znode_t *dzp = dl->dl_dzp; vnode_t *vp = ZTOV(zp); int zp_is_dir = (vp->v_type == VDIR); int reaped = 0; int error; dnlc_remove(ZTOV(dzp), dl->dl_name); if (!(flag & ZRENAMING)) { dmu_buf_will_dirty(zp->z_dbuf, tx); if (vn_vfswlock(vp)) /* prevent new mounts on zp */ return (EBUSY); if (vn_ismntpt(vp)) { /* don't remove mount point */ vn_vfsunlock(vp); return (EBUSY); } mutex_enter(&zp->z_lock); if (zp_is_dir && !zfs_dirempty(zp)) { /* dir not empty */ mutex_exit(&zp->z_lock); vn_vfsunlock(vp); return (EEXIST); } ASSERT(zp->z_phys->zp_links > zp_is_dir); if (--zp->z_phys->zp_links == zp_is_dir) { zp->z_reap = 1; zp->z_phys->zp_links = 0; reaped = 1; } else { zfs_time_stamper_locked(zp, STATE_CHANGED, tx); } mutex_exit(&zp->z_lock); vn_vfsunlock(vp); } dmu_buf_will_dirty(dzp->z_dbuf, tx); mutex_enter(&dzp->z_lock); dzp->z_phys->zp_size--; /* one dirent removed */ dzp->z_phys->zp_links -= zp_is_dir; /* ".." link from zp */ zfs_time_stamper_locked(dzp, CONTENT_MODIFIED, tx); mutex_exit(&dzp->z_lock); error = zap_remove(zp->z_zfsvfs->z_os, dzp->z_id, dl->dl_name, tx); ASSERT(error == 0); if (reaped_ptr != NULL) *reaped_ptr = reaped; else if (reaped) zfs_dq_add(zp, tx); return (0); }
/* * Reopen zfsvfs_t::z_os and release VOPs. */ int zfs_resume_fs(zfsvfs_t *zfsvfs, const char *osname, int mode) { int err; ASSERT(RRW_WRITE_HELD(&zfsvfs->z_teardown_lock)); ASSERT(RW_WRITE_HELD(&zfsvfs->z_teardown_inactive_lock)); err = dmu_objset_open(osname, DMU_OST_ZFS, mode, &zfsvfs->z_os); if (err) { zfsvfs->z_os = NULL; } else { znode_t *zp; VERIFY(zfsvfs_setup(zfsvfs, B_FALSE) == 0); /* * Attempt to re-establish all the active znodes with * their dbufs. If a zfs_rezget() fails, then we'll let * any potential callers discover that via ZFS_ENTER_VERIFY_VP * when they try to use their znode. */ mutex_enter(&zfsvfs->z_znodes_lock); for (zp = list_head(&zfsvfs->z_all_znodes); zp; zp = list_next(&zfsvfs->z_all_znodes, zp)) { (void) zfs_rezget(zp); } mutex_exit(&zfsvfs->z_znodes_lock); } /* release the VOPs */ rw_exit(&zfsvfs->z_teardown_inactive_lock); rrw_exit(&zfsvfs->z_teardown_lock, FTAG); if (err) { /* * Since we couldn't reopen zfsvfs::z_os, force * unmount this file system. */ if (vn_vfswlock(zfsvfs->z_vfs->vfs_vnodecovered) == 0) (void) dounmount(zfsvfs->z_vfs, MS_FORCE, curthread); } return (err); }
/* * dsl_crypto_key_unload * * Remove the key from the in memory keystore. * * First we have to remove the minor node for a ZVOL or unmount * the filesystem. This is so that we flush all pending IO for it to disk * so we won't need to encrypt anything with this key. Anything in flight * should already have a lock on the keys it needs. * We can't assume that userland has already successfully unmounted the * dataset though in many cases it will have. * * If the key can't be removed return the failure back to our caller. */ int dsl_crypto_key_unload(const char *dsname) { dsl_dataset_t *ds; objset_t *os; int error; spa_t *spa; dsl_pool_t *dp; #ifdef _KERNEL dmu_objset_type_t os_type; //vfs_t *vfsp; struct vfsmount *vfsp; #endif /* _KERNEL */ error = dsl_pool_hold(dsname, FTAG, &dp); if (error != 0) return (error); /* XXX - should we use own_exclusive() here? */ if ((error = dsl_dataset_hold(dp, dsname, FTAG, &ds)) != 0) { dsl_pool_rele(dp, FTAG); return (error); } if ((error = dmu_objset_from_ds(ds, &os)) != 0) { dsl_dataset_rele(ds, FTAG); dsl_pool_rele(dp, FTAG); return (error); } #ifdef _KERNEL /* * Make sure that the device node has gone for ZVOLs * and that filesystems are umounted. */ #if 0 // FIXME os_type = dmu_objset_type(os); if (os_type == DMU_OST_ZVOL) { error = zvol_remove_minor(dsname); if (error == ENXIO) error = 0; } else if (os_type == DMU_OST_ZFS) { vfsp = zfs_get_vfs(dsname); if (vfsp != NULL) { error = vn_vfswlock(vfsp->vfs_vnodecovered); VFS_RELE(vfsp); if (error == 0) error = dounmount(vfsp, 0, CRED()); } } if (error != 0) { dsl_dataset_rele(ds, FTAG); return (error); } #endif #endif /* _KERNEL */ /* * Make sure all dbufs are synced. * * It is essential for encrypted datasets to ensure that * there is no further pending IO before removing the key. */ if (dmu_objset_is_dirty(os, 0)) // FIXME, 0? txg_wait_synced(dmu_objset_pool(os), 0); dmu_objset_evict_dbufs(os); spa = dsl_dataset_get_spa(ds); error = zcrypt_keystore_remove(spa, ds->ds_object); dsl_dataset_rele(ds, FTAG); dsl_pool_rele(dp, FTAG); return (error); }
/* * Unlink zp from dl, and mark zp for deletion if this was the last link. * Can fail if zp is a mount point (EBUSY) or a non-empty directory (EEXIST). * If 'unlinkedp' is NULL, we put unlinked znodes on the unlinked list. * If it's non-NULL, we use it to indicate whether the znode needs deletion, * and it's the caller's job to do it. */ int zfs_link_destroy(zfs_dirlock_t *dl, znode_t *zp, dmu_tx_t *tx, int flag, boolean_t *unlinkedp) { znode_t *dzp = dl->dl_dzp; zfsvfs_t *zfsvfs = dzp->z_zfsvfs; vnode_t *vp = ZTOV(zp); int zp_is_dir = (vp->v_type == VDIR); boolean_t unlinked = B_FALSE; sa_bulk_attr_t bulk[5]; uint64_t mtime[2], ctime[2]; int count = 0; int error; dnlc_remove(ZTOV(dzp), dl->dl_name); if (!(flag & ZRENAMING)) { #ifdef HAVE_ZPL if (vn_vfswlock(vp)) /* prevent new mounts on zp */ return (EBUSY); if (vn_ismntpt(vp)) { /* don't remove mount point */ vn_vfsunlock(vp); return (EBUSY); } #endif mutex_enter(&zp->z_lock); if (zp_is_dir && !zfs_dirempty(zp)) { mutex_exit(&zp->z_lock); #ifdef HAVE_ZPL vn_vfsunlock(vp); #endif return (EEXIST); } /* * If we get here, we are going to try to remove the object. * First try removing the name from the directory; if that * fails, return the error. */ error = zfs_dropname(dl, zp, dzp, tx, flag); if (error != 0) { mutex_exit(&zp->z_lock); #ifdef HAVE_ZPL vn_vfsunlock(vp); #endif return (error); } if (zp->z_links <= zp_is_dir) { #ifdef HAVE_ZPL zfs_panic_recover("zfs: link count on %s is %u, " "should be at least %u", zp->z_vnode->v_path ? zp->z_vnode->v_path : "<unknown>", (int)zp->z_links, zp_is_dir + 1); #endif zp->z_links = zp_is_dir + 1; } if (--zp->z_links == zp_is_dir) { zp->z_unlinked = B_TRUE; zp->z_links = 0; unlinked = B_TRUE; } else { SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), NULL, &ctime, sizeof (ctime)); SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), NULL, &zp->z_pflags, sizeof (zp->z_pflags)); zfs_tstamp_update_setup(zp, STATE_CHANGED, mtime, ctime, B_TRUE); } SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_LINKS(zfsvfs), NULL, &zp->z_links, sizeof (zp->z_links)); error = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx); count = 0; ASSERT(error == 0); mutex_exit(&zp->z_lock); #ifdef HAVE_ZPL vn_vfsunlock(vp); #endif } else { error = zfs_dropname(dl, zp, dzp, tx, flag); if (error != 0) return (error); } mutex_enter(&dzp->z_lock); dzp->z_size--; /* one dirent removed */ dzp->z_links -= zp_is_dir; /* ".." link from zp */ SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_LINKS(zfsvfs), NULL, &dzp->z_links, sizeof (dzp->z_links)); SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_SIZE(zfsvfs), NULL, &dzp->z_size, sizeof (dzp->z_size)); SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), NULL, ctime, sizeof (ctime)); SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MTIME(zfsvfs), NULL, mtime, sizeof (mtime)); SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), NULL, &dzp->z_pflags, sizeof (dzp->z_pflags)); zfs_tstamp_update_setup(dzp, CONTENT_MODIFIED, mtime, ctime, B_TRUE); error = sa_bulk_update(dzp->z_sa_hdl, bulk, count, tx); ASSERT(error == 0); mutex_exit(&dzp->z_lock); if (unlinkedp != NULL) *unlinkedp = unlinked; else if (unlinked) zfs_unlinked_add(zp, tx); return (0); }
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); }
/* * Unlink zp from dl, and mark zp for deletion if this was the last link. * Can fail if zp is a mount point (EBUSY) or a non-empty directory (EEXIST). * If 'unlinkedp' is NULL, we put unlinked znodes on the unlinked list. * If it's non-NULL, we use it to indicate whether the znode needs deletion, * and it's the caller's job to do it. */ int zfs_link_destroy(zfs_dirlock_t *dl, znode_t *zp, dmu_tx_t *tx, int flag, boolean_t *unlinkedp) { znode_t *dzp = dl->dl_dzp; vnode_t *vp = ZTOV(zp); int zp_is_dir = (vp->v_type == VDIR); boolean_t unlinked = B_FALSE; int error; dnlc_remove(ZTOV(dzp), dl->dl_name); if (!(flag & ZRENAMING)) { dmu_buf_will_dirty(zp->z_dbuf, tx); if (vn_vfswlock(vp)) /* prevent new mounts on zp */ return (EBUSY); if (vn_ismntpt(vp)) { /* don't remove mount point */ vn_vfsunlock(vp); return (EBUSY); } mutex_enter(&zp->z_lock); if (zp_is_dir && !zfs_dirempty(zp)) { /* dir not empty */ mutex_exit(&zp->z_lock); vn_vfsunlock(vp); return (ENOTEMPTY); } if (zp->z_phys->zp_links <= zp_is_dir) { zfs_panic_recover("zfs: link count on vnode %p is %u, " "should be at least %u", zp->z_vnode, (int)zp->z_phys->zp_links, zp_is_dir + 1); zp->z_phys->zp_links = zp_is_dir + 1; } if (--zp->z_phys->zp_links == zp_is_dir) { zp->z_unlinked = B_TRUE; zp->z_phys->zp_links = 0; unlinked = B_TRUE; } else { zfs_time_stamper_locked(zp, STATE_CHANGED, tx); } mutex_exit(&zp->z_lock); vn_vfsunlock(vp); } dmu_buf_will_dirty(dzp->z_dbuf, tx); mutex_enter(&dzp->z_lock); dzp->z_phys->zp_size--; /* one dirent removed */ dzp->z_phys->zp_links -= zp_is_dir; /* ".." link from zp */ zfs_time_stamper_locked(dzp, CONTENT_MODIFIED, tx); mutex_exit(&dzp->z_lock); if (zp->z_zfsvfs->z_norm) { if (((zp->z_zfsvfs->z_case == ZFS_CASE_INSENSITIVE) && (flag & ZCIEXACT)) || ((zp->z_zfsvfs->z_case == ZFS_CASE_MIXED) && !(flag & ZCILOOK))) error = zap_remove_norm(zp->z_zfsvfs->z_os, dzp->z_id, dl->dl_name, MT_EXACT, tx); else error = zap_remove_norm(zp->z_zfsvfs->z_os, dzp->z_id, dl->dl_name, MT_FIRST, tx); } else { error = zap_remove(zp->z_zfsvfs->z_os, dzp->z_id, dl->dl_name, tx); } ASSERT(error == 0); if (unlinkedp != NULL) *unlinkedp = unlinked; else if (unlinked) zfs_unlinked_add(zp, tx); 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); }
/* * Unlink zp from dl, and mark zp for deletion if this was the last link. * Can fail if zp is a mount point (EBUSY) or a non-empty directory (EEXIST). * If 'unlinkedp' is NULL, we put unlinked znodes on the unlinked list. * If it's non-NULL, we use it to indicate whether the znode needs deletion, * and it's the caller's job to do it. */ int zfs_link_destroy(zfs_dirlock_t *dl, znode_t *zp, dmu_tx_t *tx, int flag, boolean_t *unlinkedp) { znode_t *dzp = dl->dl_dzp; vnode_t *vp = ZTOV(zp); #ifdef __APPLE__ int zp_is_dir = S_ISDIR(zp->z_phys->zp_mode); #else int zp_is_dir = (vp->v_type == VDIR); #endif boolean_t unlinked = B_FALSE; int error; #ifndef __APPLE__ dnlc_remove(ZTOV(dzp), dl->dl_name); #endif if (!(flag & ZRENAMING)) { dmu_buf_will_dirty(zp->z_dbuf, tx); #ifdef __APPLE__ if (vp) { #endif /* __APPLE__ */ if (vn_vfswlock(vp)) /* prevent new mounts on zp */ return (EBUSY); if (vn_ismntpt(vp)) { /* don't remove mount point */ vn_vfsunlock(vp); return (EBUSY); } #ifdef __APPLE__ } /* if (vp) */ #endif /* __APPLE__ */ mutex_enter(&zp->z_lock); if (zp_is_dir && !zfs_dirempty(zp)) { /* dir not empty */ mutex_exit(&zp->z_lock); #ifdef __APPLE__ return (ENOTEMPTY); #else vn_vfsunlock(vp); return (EEXIST); #endif } if (zp->z_phys->zp_links <= zp_is_dir) { #ifndef __APPLE__ zfs_panic_recover("zfs: link count on %s is %u, " "should be at least %u", zp->z_vnode->v_path ? zp->z_vnode->v_path : "<unknown>", (int)zp->z_phys->zp_links, zp_is_dir + 1); #endif zp->z_phys->zp_links = zp_is_dir + 1; } if (--zp->z_phys->zp_links == zp_is_dir) { zp->z_unlinked = B_TRUE; zp->z_phys->zp_links = 0; unlinked = B_TRUE; } else { zfs_time_stamper_locked(zp, STATE_CHANGED, tx); } mutex_exit(&zp->z_lock); #ifndef __APPLE__ vn_vfsunlock(vp); #endif } dmu_buf_will_dirty(dzp->z_dbuf, tx); mutex_enter(&dzp->z_lock); dzp->z_phys->zp_size--; /* one dirent removed */ dzp->z_phys->zp_links -= zp_is_dir; /* ".." link from zp */ zfs_time_stamper_locked(dzp, CONTENT_MODIFIED, tx); mutex_exit(&dzp->z_lock); error = zap_remove(zp->z_zfsvfs->z_os, dzp->z_id, dl->dl_name, tx); ASSERT(error == 0); if (unlinkedp != NULL) *unlinkedp = unlinked; else if (unlinked) zfs_unlinked_add(zp, tx); return (0); }
/* * Force the unmouting of a file descriptor from ALL of the nodes * that it was mounted to. * At the present time, the only usage for this routine is in the * event one end of a pipe was mounted. At the time the unmounted * end gets closed down, the mounted end is forced to be unmounted. * * This routine searches the namenode hash list for all namenodes * that have a nm_filevp field equal to vp. Each time one is found, * the dounmount() routine is called. This causes the nm_unmount() * routine to be called and thus, the file descriptor is unmounted * from the node. * * At the start of this routine, the reference count for vp is * incremented to protect the vnode from being released in the * event the mount was the only thing keeping the vnode active. * If that is the case, the VOP_CLOSE operation is applied to * the vnode, prior to it being released. */ static int nm_umountall(vnode_t *vp, cred_t *crp) { vfs_t *vfsp; struct namenode *nodep; int error = 0; int realerr = 0; /* * For each namenode that is associated with the file: * If the v_vfsp field is not namevfs, dounmount it. Otherwise, * it was created in nm_open() and will be released in time. * The following loop replicates some code from nm_find. That * routine can't be used as is since the list isn't strictly * consumed as it is traversed. */ mutex_enter(&ntable_lock); nodep = *NM_FILEVP_HASH(vp); while (nodep) { if (nodep->nm_filevp == vp && (vfsp = NMTOV(nodep)->v_vfsp) != NULL && vfsp != &namevfs && (NMTOV(nodep)->v_flag & VROOT)) { /* * If the vn_vfswlock fails, skip the vfs since * somebody else may be unmounting it. */ if (vn_vfswlock(vfsp->vfs_vnodecovered)) { realerr = EBUSY; nodep = nodep->nm_nextp; continue; } /* * Can't hold ntable_lock across call to do_unmount * because nm_unmount tries to acquire it. This means * there is a window where another mount of vp can * happen so it is possible that after nm_unmountall * there are still some mounts. This situation existed * without MT locking because dounmount can sleep * so another mount could happen during that time. * This situation is unlikely and doesn't really cause * any problems. */ mutex_exit(&ntable_lock); if ((error = dounmount(vfsp, 0, crp)) != 0) realerr = error; mutex_enter(&ntable_lock); /* * Since we dropped the ntable_lock, we * have to start over from the beginning. * If for some reasons dounmount() fails, * start from beginning means that we will keep on * trying unless another thread unmounts it for us. */ nodep = *NM_FILEVP_HASH(vp); } else nodep = nodep->nm_nextp; } mutex_exit(&ntable_lock); return (realerr); }
/* * 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); }