Exemple #1
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);
}
Exemple #2
0
/*
 * 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);
}
Exemple #3
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);
}
Exemple #4
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);
}
/*
 * 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);
}
Exemple #6
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);
}
Exemple #7
0
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);
}
Exemple #8
0
/*
 * 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);
}
Exemple #9
0
/*
 * 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);
}
/*
 * 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);
}
Exemple #12
0
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);
}