Changeset View
Changeset View
Standalone View
Standalone View
sys/fs/tmpfs/tmpfs_vnops.c
| Show First 20 Lines • Show All 957 Lines • ▼ Show 20 Lines | releout: | ||||
| if (*tvpp != NULL) | if (*tvpp != NULL) | ||||
| vrele(*tvpp); | vrele(*tvpp); | ||||
| tmpfs_rename_restarts += restarts; | tmpfs_rename_restarts += restarts; | ||||
| return (error); | return (error); | ||||
| } | } | ||||
| static int | static int | ||||
| tmpfs_rename_check_parent(struct tmpfs_mount *tmp, struct tmpfs_node *fdnode, | |||||
| struct vnode *fvp, struct tmpfs_node *fnode, struct tmpfs_dirent *de, | |||||
| struct tmpfs_node *tdnode, struct ucred *tcred) | |||||
| { | |||||
| struct tmpfs_node *n; | |||||
| int error; | |||||
| TMPFS_NODE_LOCK(fnode); | |||||
| error = tmpfs_access_locked(fvp, fnode, VWRITE, tcred); | |||||
| TMPFS_NODE_UNLOCK(fnode); | |||||
| if (error != 0) | |||||
| return (error); | |||||
| /* | |||||
| * Ensure the target directory is not a child of the | |||||
| * directory being moved. Otherwise, we'd end up | |||||
| * with stale nodes. | |||||
| * | |||||
| * TMPFS_LOCK guaranties that no nodes are freed while | |||||
markj: guarantees | |||||
| * traversing the list. Nodes can only be marked as | |||||
| * removed: tn_parent == NULL. | |||||
| */ | |||||
| n = tdnode; | |||||
| TMPFS_LOCK(tmp); | |||||
| TMPFS_NODE_LOCK(n); | |||||
| while (n != n->tn_dir.tn_parent) { | |||||
| struct tmpfs_node *parent; | |||||
| if (n == fnode) { | |||||
| TMPFS_NODE_UNLOCK(n); | |||||
| TMPFS_UNLOCK(tmp); | |||||
| return (EINVAL); | |||||
| } | |||||
| parent = n->tn_dir.tn_parent; | |||||
| TMPFS_NODE_UNLOCK(n); | |||||
| if (parent == NULL) { | |||||
| n = NULL; | |||||
| break; | |||||
| } | |||||
| TMPFS_NODE_LOCK(parent); | |||||
| if (parent->tn_dir.tn_parent == NULL) { | |||||
| TMPFS_NODE_UNLOCK(parent); | |||||
| n = NULL; | |||||
| break; | |||||
| } | |||||
| n = parent; | |||||
| } | |||||
| TMPFS_UNLOCK(tmp); | |||||
| if (n == NULL) | |||||
| return (EINVAL); | |||||
| TMPFS_NODE_UNLOCK(n); | |||||
| return (0); | |||||
| } | |||||
| static void | |||||
| tmpfs_rename_set_parent(struct tmpfs_node *fdnode, struct tmpfs_node *fnode, | |||||
| struct tmpfs_dirent *de, struct tmpfs_node *tdnode) | |||||
| { | |||||
| /* Adjust the parent pointer. */ | |||||
| TMPFS_VALIDATE_DIR(fnode); | |||||
| TMPFS_NODE_LOCK(de->td_node); | |||||
| de->td_node->tn_dir.tn_parent = tdnode; | |||||
| TMPFS_NODE_UNLOCK(de->td_node); | |||||
| /* | |||||
| * As a result of changing the target of the '..' | |||||
| * entry, the link count of the source and target | |||||
| * directories has to be adjusted. | |||||
| */ | |||||
| TMPFS_NODE_LOCK(tdnode); | |||||
| TMPFS_ASSERT_LOCKED(tdnode); | |||||
| tdnode->tn_links++; | |||||
| TMPFS_NODE_UNLOCK(tdnode); | |||||
| TMPFS_NODE_LOCK(fdnode); | |||||
| TMPFS_ASSERT_LOCKED(fdnode); | |||||
| fdnode->tn_links--; | |||||
| TMPFS_NODE_UNLOCK(fdnode); | |||||
| } | |||||
| static int | |||||
| tmpfs_rename(struct vop_rename_args *v) | tmpfs_rename(struct vop_rename_args *v) | ||||
| { | { | ||||
| struct vnode *fdvp = v->a_fdvp; | struct vnode *fdvp = v->a_fdvp; | ||||
| struct vnode *fvp = v->a_fvp; | struct vnode *fvp = v->a_fvp; | ||||
| struct componentname *fcnp = v->a_fcnp; | struct componentname *fcnp = v->a_fcnp; | ||||
| struct vnode *tdvp = v->a_tdvp; | struct vnode *tdvp = v->a_tdvp; | ||||
| struct vnode *tvp = v->a_tvp; | struct vnode *tvp = v->a_tvp; | ||||
| struct componentname *tcnp = v->a_tcnp; | struct componentname *tcnp = v->a_tcnp; | ||||
| char *newname; | char *newname; | ||||
| struct tmpfs_dirent *de; | struct tmpfs_dirent *de, *tde; | ||||
| struct tmpfs_mount *tmp; | struct tmpfs_mount *tmp; | ||||
| struct tmpfs_node *fdnode; | struct tmpfs_node *fdnode; | ||||
| struct tmpfs_node *fnode; | struct tmpfs_node *fnode; | ||||
| struct tmpfs_node *tnode; | struct tmpfs_node *tnode; | ||||
| struct tmpfs_node *tdnode; | struct tmpfs_node *tdnode; | ||||
| int error; | int error; | ||||
| bool want_seqc_end; | bool want_seqc_end; | ||||
| bool exchange; | |||||
| want_seqc_end = false; | want_seqc_end = false; | ||||
| newname = NULL; | |||||
| error = 0; | |||||
| /* | /* | ||||
| * Disallow cross-device renames. | * Disallow cross-device renames. | ||||
| * XXX Why isn't this done by the caller? | * XXX Why isn't this done by the caller? | ||||
| */ | */ | ||||
| if (fvp->v_mount != tdvp->v_mount || | if (fvp->v_mount != tdvp->v_mount || | ||||
| (tvp != NULL && fvp->v_mount != tvp->v_mount)) { | (tvp != NULL && fvp->v_mount != tvp->v_mount)) { | ||||
| error = EXDEV; | error = EXDEV; | ||||
| goto out; | goto out; | ||||
| } | } | ||||
| if ((v->a_flags & ~(AT_RENAME_NOREPLACE)) != 0) { | if ((v->a_flags & ~(AT_RENAME_NOREPLACE | AT_RENAME_EXCHANGE)) != 0) { | ||||
| error = EOPNOTSUPP; | error = EOPNOTSUPP; | ||||
| goto out; | goto out; | ||||
| } | } | ||||
| /* If source and target are the same file, there is nothing to do. */ | /* If source and target are the same file, there is nothing to do. */ | ||||
| if (fvp == tvp) { | if (fvp == tvp) | ||||
| error = 0; | |||||
| goto out; | goto out; | ||||
| } | exchange = (v->a_flags & AT_RENAME_EXCHANGE) != 0; | ||||
| /* | /* | ||||
| * If we need to move the directory between entries, lock the | * If we need to move the directory between entries, lock the | ||||
| * source so that we can safely operate on it. | * source so that we can safely operate on it. | ||||
| */ | */ | ||||
| if (fdvp != tdvp && fdvp != tvp) { | if (fdvp != tdvp && fdvp != tvp) { | ||||
| if (vn_lock(fdvp, LK_EXCLUSIVE | LK_NOWAIT) != 0) { | if (vn_lock(fdvp, LK_EXCLUSIVE | LK_NOWAIT) != 0) { | ||||
| error = tmpfs_rename_relock(fdvp, &fvp, tdvp, &tvp, | error = tmpfs_rename_relock(fdvp, &fvp, tdvp, &tvp, | ||||
| Show All 36 Lines | tmpfs_rename(struct vop_rename_args *v) | ||||
| want_seqc_end = true; | want_seqc_end = true; | ||||
| tmp = VFS_TO_TMPFS(tdvp->v_mount); | tmp = VFS_TO_TMPFS(tdvp->v_mount); | ||||
| tdnode = VP_TO_TMPFS_DIR(tdvp); | tdnode = VP_TO_TMPFS_DIR(tdvp); | ||||
| tnode = (tvp == NULL) ? NULL : VP_TO_TMPFS_NODE(tvp); | tnode = (tvp == NULL) ? NULL : VP_TO_TMPFS_NODE(tvp); | ||||
| fdnode = VP_TO_TMPFS_DIR(fdvp); | fdnode = VP_TO_TMPFS_DIR(fdvp); | ||||
| fnode = VP_TO_TMPFS_NODE(fvp); | fnode = VP_TO_TMPFS_NODE(fvp); | ||||
| de = tmpfs_dir_lookup(fdnode, fnode, fcnp); | de = tmpfs_dir_lookup(fdnode, fnode, fcnp); | ||||
| if (exchange) | |||||
| tde = tmpfs_dir_lookup(tdnode, tnode, tcnp); | |||||
| /* | /* | ||||
| * Entry can disappear before we lock fdvp. | * Entry can disappear before we lock fdvp. | ||||
| */ | */ | ||||
| if (de == NULL) { | if (de == NULL) { | ||||
| if ((fcnp->cn_flags & ISDOTDOT) != 0 || | if ((fcnp->cn_flags & ISDOTDOT) != 0 || | ||||
| (fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.')) | (fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.')) | ||||
| error = EINVAL; | error = EINVAL; | ||||
| else | else | ||||
| error = ENOENT; | error = ENOENT; | ||||
| goto out_locked; | goto out_locked; | ||||
| } | } | ||||
| MPASS(de->td_node == fnode); | MPASS(de->td_node == fnode); | ||||
| if (exchange) { | |||||
| if (tde == NULL) { | |||||
| error = ENOENT; | |||||
| goto out_locked; | |||||
| } | |||||
| MPASS(tde->td_node == tnode); | |||||
| } | |||||
| /* | /* | ||||
| * If re-naming a directory to another preexisting directory | * If re-naming a directory to another preexisting directory | ||||
| * ensure that the target directory is empty so that its | * ensure that the target directory is empty so that its | ||||
| * removal causes no side effects. | * removal causes no side effects. | ||||
| * Kern_rename guarantees the destination to be a directory | * Kern_rename guarantees the destination to be a directory | ||||
| * if the source is one. | * if the source is one. | ||||
| */ | */ | ||||
| if (tvp != NULL) { | if (tvp != NULL) { | ||||
| MPASS(tnode != NULL); | MPASS(tnode != NULL); | ||||
| if ((tnode->tn_flags & (NOUNLINK | IMMUTABLE | APPEND)) || | if ((tnode->tn_flags & (NOUNLINK | IMMUTABLE | APPEND)) || | ||||
| (tdnode->tn_flags & (APPEND | IMMUTABLE))) { | (tdnode->tn_flags & (APPEND | IMMUTABLE))) { | ||||
| error = EPERM; | error = EPERM; | ||||
| goto out_locked; | goto out_locked; | ||||
| } | } | ||||
| if (!exchange) { | |||||
| if (fnode->tn_type == VDIR && tnode->tn_type == VDIR) { | if (fnode->tn_type == VDIR && tnode->tn_type == VDIR) { | ||||
| if (tnode->tn_size != 0 && | if (tnode->tn_size != 0 && | ||||
| ((tcnp->cn_flags & IGNOREWHITEOUT) == 0 || | ((tcnp->cn_flags & IGNOREWHITEOUT) == 0 || | ||||
| tnode->tn_size > tnode->tn_dir.tn_wht_size)) { | tnode->tn_size > | ||||
| tnode->tn_dir.tn_wht_size)) { | |||||
| error = ENOTEMPTY; | error = ENOTEMPTY; | ||||
| goto out_locked; | goto out_locked; | ||||
| } | } | ||||
| } else if (fnode->tn_type == VDIR && tnode->tn_type != VDIR) { | } else if (fnode->tn_type == VDIR && | ||||
| tnode->tn_type != VDIR) { | |||||
| error = ENOTDIR; | error = ENOTDIR; | ||||
| goto out_locked; | goto out_locked; | ||||
| } else if (fnode->tn_type != VDIR && tnode->tn_type == VDIR) { | } else if (fnode->tn_type != VDIR && | ||||
| tnode->tn_type == VDIR) { | |||||
| error = EISDIR; | error = EISDIR; | ||||
| goto out_locked; | goto out_locked; | ||||
| } else { | } else { | ||||
| MPASS(fnode->tn_type != VDIR && | MPASS(fnode->tn_type != VDIR && | ||||
| tnode->tn_type != VDIR); | tnode->tn_type != VDIR); | ||||
| } | } | ||||
| } | } | ||||
| } | |||||
| if ((fnode->tn_flags & (NOUNLINK | IMMUTABLE | APPEND)) | if ((fnode->tn_flags & (NOUNLINK | IMMUTABLE | APPEND)) != 0 || | ||||
| || (fdnode->tn_flags & (APPEND | IMMUTABLE))) { | (fdnode->tn_flags & (APPEND | IMMUTABLE)) != 0) { | ||||
| error = EPERM; | error = EPERM; | ||||
| goto out_locked; | goto out_locked; | ||||
| } | } | ||||
| /* | /* | ||||
| * Ensure that we have enough memory to hold the new name, if it | * Ensure that we have enough memory to hold the new name, if it | ||||
| * has to be changed. | * has to be changed. | ||||
| */ | */ | ||||
| if (fcnp->cn_namelen != tcnp->cn_namelen || | if (!exchange && (fcnp->cn_namelen != tcnp->cn_namelen || | ||||
| bcmp(fcnp->cn_nameptr, tcnp->cn_nameptr, fcnp->cn_namelen) != 0) { | bcmp(fcnp->cn_nameptr, tcnp->cn_nameptr, fcnp->cn_namelen) != 0)) | ||||
| newname = malloc(tcnp->cn_namelen, M_TMPFSNAME, M_WAITOK); | newname = malloc(tcnp->cn_namelen, M_TMPFSNAME, M_WAITOK); | ||||
| } else | |||||
| newname = NULL; | |||||
| /* | /* | ||||
| * If the node is being moved to another directory, we have to do | * If the node is being moved to another directory, we have to do | ||||
| * the move. | * the move. | ||||
| */ | */ | ||||
| if (fdnode != tdnode) { | if (fdnode != tdnode) { | ||||
| /* | /* | ||||
| * In case we are moving a directory, we have to adjust its | * In case we are moving a directory, we have to adjust its | ||||
| * parent to point to the new parent. | * parent to point to the new parent. | ||||
| */ | */ | ||||
| if (de->td_node->tn_type == VDIR) { | if (fnode->tn_type == VDIR) { | ||||
| struct tmpfs_node *n; | error = tmpfs_rename_check_parent(tmp, fdnode, fvp, | ||||
| fnode, de, tdnode, tcnp->cn_cred); | |||||
| TMPFS_NODE_LOCK(fnode); | if (error != 0) { | ||||
| error = tmpfs_access_locked(fvp, fnode, VWRITE, | |||||
| tcnp->cn_cred); | |||||
| TMPFS_NODE_UNLOCK(fnode); | |||||
| if (error) { | |||||
| if (newname != NULL) | |||||
| free(newname, M_TMPFSNAME); | free(newname, M_TMPFSNAME); | ||||
| goto out_locked; | goto out_locked; | ||||
| } | } | ||||
| /* | |||||
| * Ensure the target directory is not a child of the | |||||
| * directory being moved. Otherwise, we'd end up | |||||
| * with stale nodes. | |||||
| */ | |||||
| n = tdnode; | |||||
| /* | |||||
| * TMPFS_LOCK guaranties that no nodes are freed while | |||||
| * traversing the list. Nodes can only be marked as | |||||
| * removed: tn_parent == NULL. | |||||
| */ | |||||
| TMPFS_LOCK(tmp); | |||||
| TMPFS_NODE_LOCK(n); | |||||
| while (n != n->tn_dir.tn_parent) { | |||||
| struct tmpfs_node *parent; | |||||
| if (n == fnode) { | |||||
| TMPFS_NODE_UNLOCK(n); | |||||
| TMPFS_UNLOCK(tmp); | |||||
| error = EINVAL; | |||||
| if (newname != NULL) | |||||
| free(newname, M_TMPFSNAME); | |||||
| goto out_locked; | |||||
| } | } | ||||
| parent = n->tn_dir.tn_parent; | if (exchange && tnode->tn_type == VDIR) { | ||||
| TMPFS_NODE_UNLOCK(n); | MPASS(newname == NULL); | ||||
| if (parent == NULL) { | error = tmpfs_rename_check_parent(tmp, tdnode, tvp, | ||||
| n = NULL; | tnode, tde, fdnode, fcnp->cn_cred); | ||||
| break; | if (error != 0) | ||||
| } | |||||
| TMPFS_NODE_LOCK(parent); | |||||
| if (parent->tn_dir.tn_parent == NULL) { | |||||
| TMPFS_NODE_UNLOCK(parent); | |||||
| n = NULL; | |||||
| break; | |||||
| } | |||||
| n = parent; | |||||
| } | |||||
| TMPFS_UNLOCK(tmp); | |||||
| if (n == NULL) { | |||||
| error = EINVAL; | |||||
| if (newname != NULL) | |||||
| free(newname, M_TMPFSNAME); | |||||
| goto out_locked; | goto out_locked; | ||||
| } | } | ||||
| TMPFS_NODE_UNLOCK(n); | if (fnode->tn_type == VDIR) | ||||
| tmpfs_rename_set_parent(fdnode, fnode, de, tdnode); | |||||
| /* Adjust the parent pointer. */ | if (exchange && tnode->tn_type == VDIR) | ||||
| TMPFS_VALIDATE_DIR(fnode); | tmpfs_rename_set_parent(tdnode, tnode, tde, fdnode); | ||||
| TMPFS_NODE_LOCK(de->td_node); | |||||
| de->td_node->tn_dir.tn_parent = tdnode; | |||||
| TMPFS_NODE_UNLOCK(de->td_node); | |||||
| /* | |||||
| * As a result of changing the target of the '..' | |||||
| * entry, the link count of the source and target | |||||
| * directories has to be adjusted. | |||||
| */ | |||||
| TMPFS_NODE_LOCK(tdnode); | |||||
| TMPFS_ASSERT_LOCKED(tdnode); | |||||
| tdnode->tn_links++; | |||||
| TMPFS_NODE_UNLOCK(tdnode); | |||||
| TMPFS_NODE_LOCK(fdnode); | |||||
| TMPFS_ASSERT_LOCKED(fdnode); | |||||
| fdnode->tn_links--; | |||||
| TMPFS_NODE_UNLOCK(fdnode); | |||||
| } | } | ||||
| if (exchange) { | |||||
| de->td_node = tnode; | |||||
| tde->td_node = fnode; | |||||
| goto out_locked; | |||||
| } | } | ||||
| /* | /* | ||||
| * Do the move: just remove the entry from the source directory | * Do the move: just remove the entry from the source directory | ||||
| * and insert it into the target one. | * and insert it into the target one. | ||||
| */ | */ | ||||
| tmpfs_dir_detach(fdvp, de); | tmpfs_dir_detach(fdvp, de); | ||||
| ▲ Show 20 Lines • Show All 1,020 Lines • Show Last 20 Lines | |||||
guarantees