PSEUDOCODE
int
vop_rename(struct vnode *fdvp, struct vnode *fvp, struct componentname *fcnp,
struct vnode *tdvp, struct vnode *tvp, struct componentname *tcnp)
{
int doingdirectory = 0;
int error = 0;
/*
* Check for cross-device rename.
*/
if (fvp->v_mount != tdvp->v_mount) {
error = EXDEV;
abortit:
if (tdvp == tvp)
vrele(tdvp);
else
vput(tdvp);
if (tvp)
vput(tvp);
vrele(fdvp);
vrele(fvp);
return error;
}
if (tvp exists and is immutable) {
error = EPERM;
goto abortit;
}
/*
* Check if just deleting a link name.
*/
if (fvp == tvp) {
if (fvp->v_type == VDIR) {
error = EINVAL;
goto abortit;
}
/*
* Release destination.
*/
vput(tdvp);
vput(tvp);
/*
* Delete source. Pretty bizarre stuff.
*/
vrele(fdvp);
vrele(fvp);
fcnp->cn_flags &= ~MODMASK;
fcnp->cn_flags |= LOCKPARENT | LOCKLEAF;
fcnp->cn_nameiop = DELETE;
VREF(fdvp);
error = relookup(fdvp, &fvp, fcnp);
if (error == 0)
vrele(fdvp);
return VOP_REMOVE(fdvp, fvp, fcnp);
}
if (fvp is immutable) {
error = EPERM;
goto abortit;
}
error = VOP_LOCK(fvp);
if (error)
goto abortit;
if (vp is a directory) {
/*
* Avoid ".", "..", and aliases of "." for obvious reasons.
*/
if ((fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == .)
|| fdvp == fvp
|| ((fcnp->cn_flags | tcnp->cn_flags) & ISDOTDOT)) {
VOP_UNLOCK(fvp);
error = EINVAL;
goto abortit;
}
doingdirectory = 1;
}
vrele(fdvp);
/*
* Bump link count on fvp while we are moving stuff around. If we
* crash before completing the work, the link count may be wrong
* but correctable.
*/
...;
/*
* If ".." must be changed (ie the directory gets a new
* parent) then the source directory must not be in the
* directory hierarchy above the target, as this would
* orphan everything below the source directory. Also
* the user must have write permission in the source so
* as to be able to change "..".
*/
error = VOP_ACCESS(fvp, VWRITE, tcnp->cn_cred, tcnp->cn_thread);
VOP_UNLOCK(fvp);
if (doingdirectory && fdvp != tdvp) {
/*
* Check for pathname conflict.
*/
...;
}
/*
* If the target doesnt exist, link the target to the source and
* unlink the source. Otherwise, rewrite the target directory to
* reference the source and remove the original entry.
*/
if (tvp == NULL) {
/*
* Account for ".." in new directory.
*/
if (doingdirectory && fdvp != tdvp) {
/*
* Increase link count of tdvp.
*/
...;
}
/*
* Add name in new directory.
*/
...;
if (error) {
if (doingdirectory && fdvp != tdvp) {
/*
* Decrease link count if tdvp.
*/
...;
}
goto bad;
}
vput(tdvp);
} else {
/*
* Target must be empty if a directory and have no links
* to it. Also, ensure source and target are compatible
* (both directories, or both not directories).
*/
if (tvp is a directory) {
if (tvp is not empty) {
error = ENOTEMPTY;
goto bad;
}
if (!doingdirectory) {
error = ENOTDIR;
goto bad;
}
/*
* Update name cache since directory is going away.
*/
cache_purge(tdvp);
} else if (doingdirectory) {
error = ENOTDIR;
goto bad;
}
/*
* Change name tcnp in tdvp to point at fvp.
*/
...;
/*
* If the target directory is in same directory as the source
* directory, decrement the link count on the parent of the
* target directory. This accounts for the fact that a
* directory links back to its parent with "..".
*/
if (doingdirectory && fdvp == tdvp) {
/*
* Decrement link count of tdvp.
*/
...;
}
vput(tdvp);
/*
* Decrement the link count of tvp since the directory no
* longer points at it.
*/
...;
if (doingdirectory) {
/*
* Clean up the old directory tvp.
*/
...;
}
vput(tvp);
}
/*
* Unlink the source. If a directory was moved to a new parent,
* update its ".." entry. Gobs of ugly UFS code omitted here.
*/
...;
bad:
if (tvp)
vput(tvp);
vput(tdvp);
out:
if (VOP_LOCK(fvp) == 0) {
/*
* Decrement link count of fvp.
*/
...;
vput(fvp);
} else
vrele(fvp);
return error;
}
ERRORS