+ struct timeval tv;
+ int error;
+
+#ifdef DIAGNOSTIC
+ if ((cnp->cn_flags & HASBUF) == 0)
+ panic("ufs_link: no name");
+#endif
+ if (vp->v_mount != tdvp->v_mount) {
+ VOP_ABORTOP(vp, cnp);
+ error = EXDEV;
+ goto out2;
+ }
+ if (vp != tdvp && (error = VOP_LOCK(tdvp))) {
+ VOP_ABORTOP(vp, cnp);
+ goto out2;
+ }
+ ip = VTOI(tdvp);
+ if ((nlink_t)ip->i_nlink >= LINK_MAX) {
+ VOP_ABORTOP(vp, cnp);
+ error = EMLINK;
+ goto out1;
+ }
+ if (ip->i_flags & (IMMUTABLE | APPEND)) {
+ VOP_ABORTOP(vp, cnp);
+ error = EPERM;
+ goto out1;
+ }
+ ip->i_nlink++;
+ ip->i_flag |= ICHG;
+ tv = time;
+ error = VOP_UPDATE(tdvp, &tv, &tv, 1);
+ if (!error)
+ error = ufs_direnter(ip, vp, cnp);
+ if (error) {
+ ip->i_nlink--;
+ ip->i_flag |= ICHG;
+ }
+ FREE(cnp->cn_pnbuf, M_NAMEI);
+out1:
+ if (vp != tdvp)
+ VOP_UNLOCK(tdvp);
+out2:
+ vput(vp);
+ return (error);
+}
+
+
+
+/*
+ * relookup - lookup a path name component
+ * Used by lookup to re-aquire things.
+ */
+int
+relookup(dvp, vpp, cnp)
+ struct vnode *dvp, **vpp;
+ struct componentname *cnp;
+{
+ register struct vnode *dp = 0; /* the directory we are searching */
+ struct vnode *tdp; /* saved dp */
+ struct mount *mp; /* mount table entry */
+ int docache; /* == 0 do not cache last component */
+ int wantparent; /* 1 => wantparent or lockparent flag */
+ int rdonly; /* lookup read-only flag bit */
+ char *cp; /* DEBUG: check name ptr/len */
+ int newhash; /* DEBUG: check name hash */
+ int error = 0;
+
+ /*
+ * Setup: break out flag bits into variables.
+ */
+ wantparent = cnp->cn_flags & (LOCKPARENT|WANTPARENT);
+ docache = (cnp->cn_flags & NOCACHE) ^ NOCACHE;
+ if (cnp->cn_nameiop == DELETE ||
+ (wantparent && cnp->cn_nameiop != CREATE))
+ docache = 0;
+ rdonly = cnp->cn_flags & RDONLY;
+ cnp->cn_flags &= ~ISSYMLINK;
+ dp = dvp;
+ VOP_LOCK(dp);
+
+/* dirloop: */
+ /*
+ * Search a new directory.
+ *
+ * The cn_hash value is for use by vfs_cache.
+ * The last component of the filename is left accessible via
+ * cnp->cn_nameptr for callers that need the name. Callers needing
+ * the name set the SAVENAME flag. When done, they assume
+ * responsibility for freeing the pathname buffer.
+ */
+#ifdef NAMEI_DIAGNOSTIC
+ for (newhash = 0, cp = cnp->cn_nameptr; *cp != 0 && *cp != '/'; cp++)
+ newhash += (unsigned char)*cp;
+ if (newhash != cnp->cn_hash)
+ panic("relookup: bad hash");
+ if (cnp->cn_namelen != cp - cnp->cn_nameptr)
+ panic ("relookup: bad len");
+ if (*cp != 0)
+ panic("relookup: not last component");
+ printf("{%s}: ", cnp->cn_nameptr);
+#endif
+
+ /*
+ * Check for degenerate name (e.g. / or "")
+ * which is a way of talking about a directory,
+ * e.g. like "/." or ".".
+ */
+ if (cnp->cn_nameptr[0] == '\0') {
+ if (cnp->cn_nameiop != LOOKUP || wantparent) {
+ error = EISDIR;
+ goto bad;
+ }
+ if (dp->v_type != VDIR) {
+ error = ENOTDIR;
+ goto bad;
+ }
+ if (!(cnp->cn_flags & LOCKLEAF))
+ VOP_UNLOCK(dp);
+ *vpp = dp;
+ if (cnp->cn_flags & SAVESTART)
+ panic("lookup: SAVESTART");
+ return (0);
+ }
+
+ if (cnp->cn_flags & ISDOTDOT)
+ panic ("relookup: lookup on dot-dot");
+
+ /*
+ * We now have a segment name to search for, and a directory to search.
+ */
+ if (error = VOP_LOOKUP(dp, vpp, cnp)) {
+#ifdef DIAGNOSTIC
+ if (*vpp != NULL)
+ panic("leaf should be empty");
+#endif
+ if (error != EJUSTRETURN)
+ goto bad;
+ /*
+ * If creating and at end of pathname, then can consider
+ * allowing file to be created.
+ */
+ if (rdonly || (dvp->v_mount->mnt_flag & MNT_RDONLY)) {
+ error = EROFS;
+ goto bad;
+ }
+ /* ASSERT(dvp == ndp->ni_startdir) */
+ if (cnp->cn_flags & SAVESTART)
+ VREF(dvp);
+ /*
+ * We return with ni_vp NULL to indicate that the entry
+ * doesn't currently exist, leaving a pointer to the
+ * (possibly locked) directory inode in ndp->ni_dvp.
+ */
+ return (0);
+ }
+ dp = *vpp;
+
+#ifdef DIAGNOSTIC
+ /*
+ * Check for symbolic link
+ */
+ if (dp->v_type == VLNK && (cnp->cn_flags & FOLLOW))
+ panic ("relookup: symlink found.\n");
+#endif
+
+nextname:
+ /*
+ * Check for read-only file systems.
+ */
+ if (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME) {
+ /*
+ * Disallow directory write attempts on read-only
+ * file systems.
+ */
+ if (rdonly || (dp->v_mount->mnt_flag & MNT_RDONLY) ||
+ (wantparent &&
+ (dvp->v_mount->mnt_flag & MNT_RDONLY))) {
+ error = EROFS;
+ goto bad2;
+ }
+ }
+ /* ASSERT(dvp == ndp->ni_startdir) */
+ if (cnp->cn_flags & SAVESTART)
+ VREF(dvp);
+
+ if (!wantparent)
+ vrele(dvp);
+ if ((cnp->cn_flags & LOCKLEAF) == 0)
+ VOP_UNLOCK(dp);
+ return (0);
+
+bad2:
+ if ((cnp->cn_flags & LOCKPARENT) && (cnp->cn_flags & ISLASTCN))
+ VOP_UNLOCK(dvp);
+ vrele(dvp);
+bad:
+ vput(dp);
+ *vpp = NULL;
+ return (error);
+}
+
+
+/*
+ * Rename system call.
+ * rename("foo", "bar");
+ * is essentially
+ * unlink("bar");
+ * link("foo", "bar");
+ * unlink("foo");
+ * but ``atomically''. Can't do full commit without saving state in the
+ * inode on disk which isn't feasible at this time. Best we can do is
+ * always guarantee the target exists.
+ *
+ * Basic algorithm is:
+ *
+ * 1) Bump link count on source while we're linking it to the
+ * target. This also ensure the inode won't be deleted out
+ * from underneath us while we work (it may be truncated by
+ * a concurrent `trunc' or `open' for creation).
+ * 2) Link source to destination. If destination already exists,
+ * delete it first.
+ * 3) Unlink source reference to inode if still around. If a
+ * directory was moved and the parent of the destination
+ * is different from the source, patch the ".." entry in the
+ * directory.
+ */
+int
+ufs_rename(ap)
+ struct vop_rename_args /* {
+ struct vnode *a_fdvp;
+ struct vnode *a_fvp;
+ struct componentname *a_fcnp;
+ struct vnode *a_tdvp;
+ struct vnode *a_tvp;
+ struct componentname *a_tcnp;
+ } */ *ap;
+{
+ struct vnode *tvp = ap->a_tvp;
+ register struct vnode *tdvp = ap->a_tdvp;
+ struct vnode *fvp = ap->a_fvp;
+ register struct vnode *fdvp = ap->a_fdvp;
+ register struct componentname *tcnp = ap->a_tcnp;
+ register struct componentname *fcnp = ap->a_fcnp;
+ register struct inode *ip, *xp, *dp;
+ struct dirtemplate dirbuf;
+ struct timeval tv;
+ int doingdirectory = 0, oldparent = 0, newparent = 0;
+ int error = 0;
+ int fdvpneedsrele = 1, tdvpneedsrele = 1;
+ u_char namlen;
+
+#ifdef DIAGNOSTIC
+ if ((tcnp->cn_flags & HASBUF) == 0 ||
+ (fcnp->cn_flags & HASBUF) == 0)
+ panic("ufs_rename: no name");
+#endif
+ /*
+ * Check for cross-device rename.
+ */
+ if ((fvp->v_mount != tdvp->v_mount) ||
+ (tvp && (fvp->v_mount != tvp->v_mount))) {
+ error = EXDEV;
+abortit:
+ VOP_ABORTOP(tdvp, tcnp); /* XXX, why not in NFS? */
+ if (tdvp == tvp)
+ vrele(tdvp);
+ else
+ vput(tdvp);
+ if (tvp)
+ vput(tvp);
+ VOP_ABORTOP(fdvp, fcnp); /* XXX, why not in NFS? */
+ vrele(fdvp);
+ vrele(fvp);
+ return (error);
+ }
+
+ /*
+ * Check if just deleting a link name.
+ */
+ if (tvp && ((VTOI(tvp)->i_flags & (IMMUTABLE | APPEND)) ||
+ (VTOI(tdvp)->i_flags & APPEND))) {
+ error = EPERM;
+ goto abortit;
+ }
+ if (fvp == tvp) {
+ if (fvp->v_type == VDIR) {
+ error = EINVAL;
+ goto abortit;
+ }
+ VOP_ABORTOP(fdvp, fcnp);
+ vrele(fdvp);
+ vrele(fvp);
+ vput(tdvp);
+ vput(tvp);
+ tcnp->cn_flags &= ~MODMASK;
+ tcnp->cn_flags |= LOCKPARENT | LOCKLEAF;
+ if ((tcnp->cn_flags & SAVESTART) == 0)
+ panic("ufs_rename: lost from startdir");
+ tcnp->cn_nameiop = DELETE;
+ (void) relookup(tdvp, &tvp, tcnp);
+ return (VOP_REMOVE(tdvp, tvp, tcnp));
+ }
+ if (error = VOP_LOCK(fvp))
+ goto abortit;
+ dp = VTOI(fdvp);
+ ip = VTOI(fvp);
+ if ((ip->i_flags & (IMMUTABLE | APPEND)) || (dp->i_flags & APPEND)) {
+ VOP_UNLOCK(fvp);
+ error = EPERM;
+ goto abortit;
+ }
+ if ((ip->i_mode & IFMT) == IFDIR) {
+ /*
+ * Avoid ".", "..", and aliases of "." for obvious reasons.
+ */
+ if ((fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.') ||
+ dp == ip || (fcnp->cn_flags&ISDOTDOT) ||
+ (ip->i_flag & IRENAME)) {
+ VOP_UNLOCK(fvp);
+ error = EINVAL;
+ goto abortit;
+ }
+ ip->i_flag |= IRENAME;
+ oldparent = dp->i_number;
+ doingdirectory++;
+ }
+ vrele(fdvp);
+
+ /*
+ * When the target exists, both the directory
+ * and target vnodes are returned locked.
+ */
+ dp = VTOI(tdvp);
+ xp = NULL;
+ if (tvp)
+ xp = VTOI(tvp);
+
+ /*
+ * 1) Bump link count while we're moving stuff
+ * around. If we crash somewhere before
+ * completing our work, the link count
+ * may be wrong, but correctable.
+ */
+ ip->i_nlink++;
+ ip->i_flag |= ICHG;
+ tv = time;
+ if (error = VOP_UPDATE(fvp, &tv, &tv, 1)) {
+ VOP_UNLOCK(fvp);
+ goto bad;
+ }
+
+ /*
+ * If ".." must be changed (ie the directory gets a new
+ * parent) then the source directory must not be in the
+ * directory heirarchy 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 "..". We must repeat the call
+ * to namei, as the parent directory is unlocked by the
+ * call to checkpath().
+ */
+ error = VOP_ACCESS(fvp, VWRITE, tcnp->cn_cred, tcnp->cn_proc);
+ VOP_UNLOCK(fvp);
+ if (oldparent != dp->i_number)
+ newparent = dp->i_number;
+ if (doingdirectory && newparent) {
+ if (error) /* write access check above */
+ goto bad;
+ if (xp != NULL)
+ vput(tvp);
+ if (error = ufs_checkpath(ip, dp, tcnp->cn_cred))
+ goto out;
+ if ((tcnp->cn_flags & SAVESTART) == 0)
+ panic("ufs_rename: lost to startdir");
+ p->p_spare[1]--;
+ if (error = relookup(tdvp, &tvp, tcnp))
+ goto out;
+ dp = VTOI(tdvp);
+ xp = NULL;
+ if (tvp)
+ xp = VTOI(tvp);
+ }
+ /*
+ * 2) If target doesn't exist, link the target
+ * to the source and unlink the source.
+ * Otherwise, rewrite the target directory
+ * entry to reference the source inode and
+ * expunge the original entry's existence.
+ */
+ if (xp == NULL) {
+ if (dp->i_dev != ip->i_dev)
+ panic("rename: EXDEV");
+ /*
+ * Account for ".." in new directory.
+ * When source and destination have the same
+ * parent we don't fool with the link count.
+ */
+ if (doingdirectory && newparent) {
+ if ((nlink_t)dp->i_nlink >= LINK_MAX) {
+ error = EMLINK;
+ goto bad;
+ }
+ dp->i_nlink++;
+ dp->i_flag |= ICHG;
+ if (error = VOP_UPDATE(tdvp, &tv, &tv, 1))
+ goto bad;
+ }
+ if (error = ufs_direnter(ip, tdvp, tcnp)) {
+ if (doingdirectory && newparent) {
+ dp->i_nlink--;
+ dp->i_flag |= ICHG;
+ (void)VOP_UPDATE(tdvp, &tv, &tv, 1);
+ }
+ goto bad;
+ }
+ vput(tdvp);
+ } else {
+ if (xp->i_dev != dp->i_dev || xp->i_dev != ip->i_dev)
+ panic("rename: EXDEV");
+ /*
+ * Short circuit rename(foo, foo).
+ */
+ if (xp->i_number == ip->i_number)
+ panic("rename: same file");
+ /*
+ * If the parent directory is "sticky", then the user must
+ * own the parent directory, or the destination of the rename,
+ * otherwise the destination may not be changed (except by
+ * root). This implements append-only directories.
+ */
+ if ((dp->i_mode & ISVTX) && tcnp->cn_cred->cr_uid != 0 &&
+ tcnp->cn_cred->cr_uid != dp->i_uid &&
+ xp->i_uid != tcnp->cn_cred->cr_uid) {
+ error = EPERM;
+ goto bad;
+ }
+ /*
+ * 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 ((xp->i_mode&IFMT) == IFDIR) {
+ if (!ufs_dirempty(xp, dp->i_number, tcnp->cn_cred) ||
+ xp->i_nlink > 2) {
+ error = ENOTEMPTY;
+ goto bad;
+ }
+ if (!doingdirectory) {
+ error = ENOTDIR;
+ goto bad;
+ }
+ cache_purge(tdvp);
+ } else if (doingdirectory) {
+ error = EISDIR;
+ goto bad;
+ }
+ if (error = ufs_dirrewrite(dp, ip, tcnp))
+ goto bad;
+ /*
+ * If the target directory is in the same
+ * directory as the source directory,
+ * decrement the link count on the parent
+ * of the target directory.
+ */
+ if (doingdirectory && !newparent) {
+ dp->i_nlink--;
+ dp->i_flag |= ICHG;
+ }
+ vput(tdvp);
+ /*
+ * Adjust the link count of the target to
+ * reflect the dirrewrite above. If this is
+ * a directory it is empty and there are
+ * no links to it, so we can squash the inode and
+ * any space associated with it. We disallowed
+ * renaming over top of a directory with links to
+ * it above, as the remaining link would point to
+ * a directory without "." or ".." entries.
+ */
+ xp->i_nlink--;
+ if (doingdirectory) {
+ if (--xp->i_nlink != 0)
+ panic("rename: linked directory");
+ error = VOP_TRUNCATE(tvp, (off_t)0, IO_SYNC,
+ tcnp->cn_cred, tcnp->cn_proc);
+ }
+ xp->i_flag |= ICHG;
+ vput(tvp);
+ xp = NULL;
+ }
+
+ /*
+ * 3) Unlink the source.
+ */
+ fcnp->cn_flags &= ~MODMASK;
+ fcnp->cn_flags |= LOCKPARENT | LOCKLEAF;
+ if ((fcnp->cn_flags & SAVESTART) == 0)
+ panic("ufs_rename: lost from startdir");
+ p->p_spare[1]--;
+ (void) relookup(fdvp, &fvp, fcnp);
+ if (fvp != NULL) {
+ xp = VTOI(fvp);
+ dp = VTOI(fdvp);