-/* ufs_lookup.c 3.3 %G% */
+/*
+ * Copyright (c) 1989 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * %sccs.include.redist.c%
+ *
+ * @(#)ufs_lookup.c 7.36 (Berkeley) %G%
+ */
-#include "../h/param.h"
-#include "../h/systm.h"
-#include "../h/inode.h"
-#include "../h/mount.h"
-#include "../h/dir.h"
-#include "../h/user.h"
-#include "../h/buf.h"
+#include <sys/param.h>
+#include <sys/namei.h>
+#include <sys/buf.h>
+#include <sys/file.h>
+#include <sys/mount.h>
+#include <sys/vnode.h>
+
+#include <ufs/ufs/quota.h>
+#include <ufs/ufs/inode.h>
+#include <ufs/ufs/dir.h>
+#include <ufs/ufs/ufsmount.h>
+#include <ufs/ufs/ufs_extern.h>
+
+struct nchstats nchstats;
+#ifdef DIAGNOSTIC
+int dirchk = 1;
+#else
+int dirchk = 0;
+#endif
/*
- * Convert a pathname into a pointer to
- * an inode. Note that the inode is locked.
+ * Convert a component of a pathname into a pointer to a locked inode.
+ * This is a very central and rather complicated routine.
+ * If the file system is not maintained in a strict tree hierarchy,
+ * this can result in a deadlock situation (see comments in code below).
+ *
+ * The flag argument is LOOKUP, CREATE, RENAME, or DELETE depending on
+ * whether the name is to be looked up, created, renamed, or deleted.
+ * When CREATE, RENAME, or DELETE is specified, information usable in
+ * creating, renaming, or deleting a directory entry may be calculated.
+ * If flag has LOCKPARENT or'ed into it and the target of the pathname
+ * exists, lookup returns both the target and its parent directory locked.
+ * When creating or renaming and LOCKPARENT is specified, the target may
+ * not be ".". When deleting and LOCKPARENT is specified, the target may
+ * be "."., but the caller must check to ensure it does an vrele and iput
+ * instead of two iputs.
+ *
+ * Overall outline of ufs_lookup:
*
- * func = function called to get next char of name
- * &uchar if name is in user space
- * &schar if name is in system space
- * flag = 0 if name is sought
- * 1 if name is to be created
- * 2 if name is to be deleted
+ * check accessibility of directory
+ * look for name in cache, if found, then if at end of path
+ * and deleting or creating, drop it, else return name
+ * search for name in directory, to found or notfound
+ * notfound:
+ * if creating, return locked directory, leaving info on available slots
+ * else return error
+ * found:
+ * if at end of path and deleting, return information to allow delete
+ * if at end of path and rewriting (RENAME and LOCKPARENT), lock target
+ * inode and return info to allow rewrite
+ * if not at end, add name to cache; if at end and neither creating
+ * nor deleting, add name to cache
+ *
+ * NOTE: (LOOKUP | LOCKPARENT) currently returns the parent inode unlocked.
*/
-struct inode *
-namei(func, flag)
-int (*func)();
+int
+ufs_lookup(vdp, ndp, p)
+ register struct vnode *vdp;
+ register struct nameidata *ndp;
+ struct proc *p;
{
- register struct inode *dp;
- register c;
- register char *cp;
- struct buf *bp;
- register struct direct *ep;
- int i;
- dev_t d;
- off_t eo;
+ register struct inode *dp; /* the directory we are searching */
+ struct buf *bp; /* a buffer of directory entries */
+ register struct direct *ep; /* the current directory entry */
+ int entryoffsetinblock; /* offset of ep in bp's buffer */
+ enum {NONE, COMPACT, FOUND} slotstatus;
+ int slotoffset; /* offset of area with free space */
+ int slotsize; /* size of area at slotoffset */
+ int slotfreespace; /* amount of space free in slot */
+ int slotneeded; /* size of the entry we're seeking */
+ int numdirpasses; /* strategy for directory search */
+ int endsearch; /* offset to end directory search */
+ int prevoff; /* prev entry ndp->ni_ufs.ufs_offset */
+ struct inode *pdp; /* saved dp during symlink work */
+ struct vnode *tdp; /* returned by VOP_VGET */
+ off_t enduseful; /* pointer past last used dir slot */
+ u_long bmask; /* block offset mask */
+ int flag; /* LOOKUP, CREATE, RENAME, or DELETE */
+ int lockparent; /* 1 => lockparent flag is set */
+ int wantparent; /* 1 => wantparent or lockparent flag */
+ int error;
+
+ bp = NULL;
+ slotoffset = -1;
+ ndp->ni_dvp = vdp;
+ ndp->ni_vp = NULL;
+ dp = VTOI(vdp);
+ lockparent = ndp->ni_nameiop & LOCKPARENT;
+ flag = ndp->ni_nameiop & OPMASK;
+ wantparent = ndp->ni_nameiop & (LOCKPARENT|WANTPARENT);
/*
- * If name starts with '/' start from
- * root; otherwise start from current dir.
+ * Check accessiblity of directory.
*/
+ if ((dp->i_mode&IFMT) != IFDIR)
+ return (ENOTDIR);
+ if (error = ufs_access(vdp, VEXEC, ndp->ni_cred, p))
+ return (error);
- dp = u.u_cdir;
- if((c=(*func)()) == '/')
- if ((dp = u.u_rdir) == NULL)
- dp = rootdir;
- (void) iget(dp->i_dev, dp->i_number);
- while(c == '/')
- c = (*func)();
- if(c == '\0' && flag != 0)
- u.u_error = ENOENT;
-
-cloop:
/*
- * Here dp contains pointer
- * to last component matched.
+ * We now have a segment name to search for, and a directory to search.
+ *
+ * Before tediously performing a linear scan of the directory,
+ * check the name cache to see if the directory/name pair
+ * we are looking for is known already.
*/
+ if (error = cache_lookup(ndp)) {
+ int vpid; /* capability number of vnode */
- if(u.u_error)
- goto out;
- if(c == '\0')
- return(dp);
+ if (error == ENOENT)
+ return (error);
+#ifdef PARANOID
+ if (vdp == ndp->ni_rdir && ndp->ni_isdotdot)
+ panic("ufs_lookup: .. through root");
+#endif
+ /*
+ * Get the next vnode in the path.
+ * See comment below starting `Step through' for
+ * an explaination of the locking protocol.
+ */
+ pdp = dp;
+ dp = VTOI(ndp->ni_vp);
+ vdp = ndp->ni_vp;
+ vpid = vdp->v_id;
+ if (pdp == dp) {
+ VREF(vdp);
+ error = 0;
+ } else if (ndp->ni_isdotdot) {
+ IUNLOCK(pdp);
+ error = vget(vdp);
+ if (!error && lockparent && *ndp->ni_next == '\0')
+ ILOCK(pdp);
+ } else {
+ error = vget(vdp);
+ if (!lockparent || error || *ndp->ni_next != '\0')
+ IUNLOCK(pdp);
+ }
+ /*
+ * Check that the capability number did not change
+ * while we were waiting for the lock.
+ */
+ if (!error) {
+ if (vpid == vdp->v_id)
+ return (0);
+ ufs_iput(dp);
+ if (lockparent && pdp != dp && *ndp->ni_next == '\0')
+ IUNLOCK(pdp);
+ }
+ ILOCK(pdp);
+ dp = pdp;
+ vdp = ITOV(dp);
+ ndp->ni_vp = NULL;
+ }
/*
- * If there is another component,
- * Gather up name into
- * users' dir buffer.
+ * Suppress search for slots unless creating
+ * file and at end of pathname, in which case
+ * we watch for a place to put the new file in
+ * case it doesn't already exist.
*/
-
- cp = &u.u_dbuf[0];
- while (c != '/' && c != '\0' && u.u_error == 0 ) {
- if (mpxip!=NULL && c=='!')
- break;
- if (cp < &u.u_dbuf[DIRSIZ])
- *cp++ = c;
- c = (*func)();
+ slotstatus = FOUND;
+ if ((flag == CREATE || flag == RENAME) && *ndp->ni_next == 0) {
+ slotstatus = NONE;
+ slotfreespace = 0;
+ slotneeded = ((sizeof (struct direct) - (MAXNAMLEN + 1)) +
+ ((ndp->ni_namelen + 1 + 3) &~ 3));
}
- while(cp < &u.u_dbuf[DIRSIZ])
- *cp++ = '\0';
- while(c == '/')
- c = (*func)();
- if (c == '!' && mpxip != NULL) {
- iput(dp);
- plock(mpxip);
- mpxip->i_count++;
- return(mpxip);
+
+ /*
+ * If there is cached information on a previous search of
+ * this directory, pick up where we last left off.
+ * We cache only lookups as these are the most common
+ * and have the greatest payoff. Caching CREATE has little
+ * benefit as it usually must search the entire directory
+ * to determine that the entry does not exist. Caching the
+ * location of the last DELETE or RENAME has not reduced
+ * profiling time and hence has been removed in the interest
+ * of simplicity.
+ */
+ bmask = VFSTOUFS(vdp->v_mount)->um_mountp->mnt_stat.f_iosize - 1;
+ if (flag != LOOKUP || dp->i_diroff == 0 || dp->i_diroff > dp->i_size) {
+ ndp->ni_ufs.ufs_offset = 0;
+ numdirpasses = 1;
+ } else {
+ ndp->ni_ufs.ufs_offset = dp->i_diroff;
+ if ((entryoffsetinblock = ndp->ni_ufs.ufs_offset & bmask) &&
+ (error = VOP_BLKATOFF(vdp, ndp->ni_ufs.ufs_offset, NULL,
+ &bp)))
+ return (error);
+ numdirpasses = 2;
+ nchstats.ncs_2passes++;
}
+ endsearch = roundup(dp->i_size, DIRBLKSIZ);
+ enduseful = 0;
-seloop:
+searchloop:
+ while (ndp->ni_ufs.ufs_offset < endsearch) {
+ /*
+ * If offset is on a block boundary, read the next directory
+ * block. Release previous if it exists.
+ */
+ if ((ndp->ni_ufs.ufs_offset & bmask) == 0) {
+ if (bp != NULL)
+ brelse(bp);
+ if (error = VOP_BLKATOFF(vdp, ndp->ni_ufs.ufs_offset,
+ NULL, &bp))
+ return (error);
+ entryoffsetinblock = 0;
+ }
+ /*
+ * If still looking for a slot, and at a DIRBLKSIZE
+ * boundary, have to start looking for free space again.
+ */
+ if (slotstatus == NONE &&
+ (entryoffsetinblock & (DIRBLKSIZ - 1)) == 0) {
+ slotoffset = -1;
+ slotfreespace = 0;
+ }
+ /*
+ * Get pointer to next entry.
+ * Full validation checks are slow, so we only check
+ * enough to insure forward progress through the
+ * directory. Complete checks can be run by patching
+ * "dirchk" to be true.
+ */
+ ep = (struct direct *)(bp->b_un.b_addr + entryoffsetinblock);
+ if (ep->d_reclen == 0 ||
+ dirchk && ufs_dirbadentry(ep, entryoffsetinblock)) {
+ int i;
+
+ ufs_dirbad(dp, ndp->ni_ufs.ufs_offset, "mangled entry");
+ i = DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1));
+ ndp->ni_ufs.ufs_offset += i;
+ entryoffsetinblock += i;
+ continue;
+ }
+
+ /*
+ * If an appropriate sized slot has not yet been found,
+ * check to see if one is available. Also accumulate space
+ * in the current block so that we can determine if
+ * compaction is viable.
+ */
+ if (slotstatus != FOUND) {
+ int size = ep->d_reclen;
+
+ if (ep->d_ino != 0)
+ size -= DIRSIZ(ep);
+ if (size > 0) {
+ if (size >= slotneeded) {
+ slotstatus = FOUND;
+ slotoffset = ndp->ni_ufs.ufs_offset;
+ slotsize = ep->d_reclen;
+ } else if (slotstatus == NONE) {
+ slotfreespace += size;
+ if (slotoffset == -1)
+ slotoffset =
+ ndp->ni_ufs.ufs_offset;
+ if (slotfreespace >= slotneeded) {
+ slotstatus = COMPACT;
+ slotsize =
+ ndp->ni_ufs.ufs_offset +
+ ep->d_reclen - slotoffset;
+ }
+ }
+ }
+ }
+
+ /*
+ * Check for a name match.
+ */
+ if (ep->d_ino) {
+ if (ep->d_namlen == ndp->ni_namelen &&
+ !bcmp(ndp->ni_ptr, ep->d_name,
+ (unsigned)ep->d_namlen)) {
+ /*
+ * Save directory entry's inode number and
+ * reclen in ndp->ni_ufs area, and release
+ * directory buffer.
+ */
+ ndp->ni_ufs.ufs_ino = ep->d_ino;
+ ndp->ni_ufs.ufs_reclen = ep->d_reclen;
+ brelse(bp);
+ goto found;
+ }
+ }
+ prevoff = ndp->ni_ufs.ufs_offset;
+ ndp->ni_ufs.ufs_offset += ep->d_reclen;
+ entryoffsetinblock += ep->d_reclen;
+ if (ep->d_ino)
+ enduseful = ndp->ni_ufs.ufs_offset;
+ }
+/* notfound: */
+ /*
+ * If we started in the middle of the directory and failed
+ * to find our target, we must check the beginning as well.
+ */
+ if (numdirpasses == 2) {
+ numdirpasses--;
+ ndp->ni_ufs.ufs_offset = 0;
+ endsearch = dp->i_diroff;
+ goto searchloop;
+ }
+ if (bp != NULL)
+ brelse(bp);
/*
- * dp must be a directory and
- * must have X permission.
+ * If creating, and at end of pathname and current
+ * directory has not been removed, then can consider
+ * allowing file to be created.
*/
+ if ((flag == CREATE || flag == RENAME) &&
+ *ndp->ni_next == 0 && dp->i_nlink != 0) {
+ /*
+ * Access for write is interpreted as allowing
+ * creation of files in the directory.
+ */
+ if (error = ufs_access(vdp, VWRITE, ndp->ni_cred, p))
+ return (error);
+ /*
+ * Return an indication of where the new directory
+ * entry should be put. If we didn't find a slot,
+ * then set ndp->ni_ufs.ufs_count to 0 indicating
+ * that the new slot belongs at the end of the
+ * directory. If we found a slot, then the new entry
+ * can be put in the range from ndp->ni_ufs.ufs_offset
+ * to ndp->ni_ufs.ufs_offset + ndp->ni_ufs.ufs_count.
+ */
+ if (slotstatus == NONE) {
+ ndp->ni_ufs.ufs_offset = roundup(dp->i_size, DIRBLKSIZ);
+ ndp->ni_ufs.ufs_count = 0;
+ enduseful = ndp->ni_ufs.ufs_offset;
+ } else {
+ ndp->ni_ufs.ufs_offset = slotoffset;
+ ndp->ni_ufs.ufs_count = slotsize;
+ if (enduseful < slotoffset + slotsize)
+ enduseful = slotoffset + slotsize;
+ }
+ ndp->ni_ufs.ufs_endoff = roundup(enduseful, DIRBLKSIZ);
+ dp->i_flag |= IUPD|ICHG;
+ /*
+ * We return with the directory locked, so that
+ * the parameters we set up above will still be
+ * valid if we actually decide to do a direnter().
+ * We return ni_vp == NULL to indicate that the entry
+ * does not currently exist; we leave a pointer to
+ * the (locked) directory inode in ndp->ni_dvp.
+ * The pathname buffer is saved so that the name
+ * can be obtained later.
+ *
+ * NB - if the directory is unlocked, then this
+ * information cannot be used.
+ */
+ ndp->ni_nameiop |= SAVENAME;
+ if (!lockparent)
+ IUNLOCK(dp);
+ }
+ /*
+ * Insert name into cache (as non-existent) if appropriate.
+ */
+ if (ndp->ni_makeentry && flag != CREATE)
+ cache_enter(ndp);
+ return (ENOENT);
- if((dp->i_mode&IFMT) != IFDIR)
- u.u_error = ENOTDIR;
- (void) access(dp, IEXEC);
- if(u.u_error)
- goto out;
+found:
+ if (numdirpasses == 2)
+ nchstats.ncs_pass2++;
+ /*
+ * Check that directory length properly reflects presence
+ * of this entry.
+ */
+ if (entryoffsetinblock + DIRSIZ(ep) > dp->i_size) {
+ ufs_dirbad(dp, ndp->ni_ufs.ufs_offset, "i_size too small");
+ dp->i_size = entryoffsetinblock + DIRSIZ(ep);
+ dp->i_flag |= IUPD|ICHG;
+ }
/*
- * set up to search a directory
+ * Found component in pathname.
+ * If the final component of path name, save information
+ * in the cache as to where the entry was found.
*/
- u.u_offset = 0;
- u.u_segflg = 1;
- eo = 0;
- bp = NULL;
+ if (*ndp->ni_next == '\0' && flag == LOOKUP)
+ dp->i_diroff = ndp->ni_ufs.ufs_offset &~ (DIRBLKSIZ - 1);
-eloop:
+ /*
+ * If deleting, and at end of pathname, return
+ * parameters which can be used to remove file.
+ * If the wantparent flag isn't set, we return only
+ * the directory (in ndp->ni_dvp), otherwise we go
+ * on and lock the inode, being careful with ".".
+ */
+ if (flag == DELETE && *ndp->ni_next == 0) {
+ /*
+ * Write access to directory required to delete files.
+ */
+ if (error = ufs_access(vdp, VWRITE, ndp->ni_cred, p))
+ return (error);
+ /*
+ * Return pointer to current entry in ndp->ni_ufs.ufs_offset,
+ * and distance past previous entry (if there
+ * is a previous entry in this block) in ndp->ni_ufs.ufs_count.
+ * Save directory inode pointer in ndp->ni_dvp for dirremove().
+ */
+ if ((ndp->ni_ufs.ufs_offset&(DIRBLKSIZ-1)) == 0)
+ ndp->ni_ufs.ufs_count = 0;
+ else
+ ndp->ni_ufs.ufs_count =
+ ndp->ni_ufs.ufs_offset - prevoff;
+ if (dp->i_number == ndp->ni_ufs.ufs_ino) {
+ VREF(vdp);
+ ndp->ni_vp = vdp;
+ return (0);
+ }
+ if (error = VOP_VGET(vdp, ndp->ni_ufs.ufs_ino, &tdp))
+ return (error);
+ /*
+ * If directory is "sticky", then user must own
+ * the directory, or the file in it, else she
+ * may not delete it (unless she's root). This
+ * implements append-only directories.
+ */
+ if ((dp->i_mode & ISVTX) &&
+ ndp->ni_cred->cr_uid != 0 &&
+ ndp->ni_cred->cr_uid != dp->i_uid &&
+ VTOI(tdp)->i_uid != ndp->ni_cred->cr_uid) {
+ vput(tdp);
+ return (EPERM);
+ }
+ ndp->ni_vp = tdp;
+ if (!lockparent)
+ IUNLOCK(dp);
+ return (0);
+ }
/*
- * If at the end of the directory,
- * the search failed. Report what
- * is appropriate as per flag.
+ * If rewriting (RENAME), return the inode and the
+ * information required to rewrite the present directory
+ * Must get inode of directory entry to verify it's a
+ * regular file, or empty directory.
*/
+ if (flag == RENAME && wantparent && *ndp->ni_next == 0) {
+ if (error = ufs_access(vdp, VWRITE, ndp->ni_cred, p))
+ return (error);
+ /*
+ * Careful about locking second inode.
+ * This can only occur if the target is ".".
+ */
+ if (dp->i_number == ndp->ni_ufs.ufs_ino)
+ return (EISDIR);
+ if (error = VOP_VGET(vdp, ndp->ni_ufs.ufs_ino, &tdp))
+ return (error);
+ ndp->ni_vp = tdp;
+ ndp->ni_nameiop |= SAVENAME;
+ if (!lockparent)
+ IUNLOCK(dp);
+ return (0);
+ }
- if(u.u_offset >= dp->i_size) {
- if(bp != NULL)
- brelse(bp);
- if(flag==1 && c=='\0') {
- if(access(dp, IWRITE))
- goto out;
- u.u_pdir = dp;
- if(eo)
- u.u_offset = eo-sizeof(struct direct);
- else
- dp->i_flag |= IUPD|ICHG;
- return(NULL);
+ /*
+ * Step through the translation in the name. We do not `iput' the
+ * directory because we may need it again if a symbolic link
+ * is relative to the current directory. Instead we save it
+ * unlocked as "pdp". We must get the target inode before unlocking
+ * the directory to insure that the inode will not be removed
+ * before we get it. We prevent deadlock by always fetching
+ * inodes from the root, moving down the directory tree. Thus
+ * when following backward pointers ".." we must unlock the
+ * parent directory before getting the requested directory.
+ * There is a potential race condition here if both the current
+ * and parent directories are removed before the `iget' for the
+ * inode associated with ".." returns. We hope that this occurs
+ * infrequently since we cannot avoid this race condition without
+ * implementing a sophisticated deadlock detection algorithm.
+ * Note also that this simple deadlock detection scheme will not
+ * work if the file system has any hard links other than ".."
+ * that point backwards in the directory structure.
+ */
+ pdp = dp;
+ if (ndp->ni_isdotdot) {
+ IUNLOCK(pdp); /* race to get the inode */
+ if (error = VOP_VGET(vdp, ndp->ni_ufs.ufs_ino, &tdp)) {
+ ILOCK(pdp);
+ return (error);
}
- u.u_error = ENOENT;
- goto out;
+ if (lockparent && *ndp->ni_next == '\0')
+ ILOCK(pdp);
+ ndp->ni_vp = tdp;
+ } else if (dp->i_number == ndp->ni_ufs.ufs_ino) {
+ VREF(vdp); /* we want ourself, ie "." */
+ ndp->ni_vp = vdp;
+ } else {
+ if (error = VOP_VGET(vdp, ndp->ni_ufs.ufs_ino, &tdp))
+ return (error);
+ if (!lockparent || *ndp->ni_next != '\0')
+ IUNLOCK(pdp);
+ ndp->ni_vp = tdp;
}
/*
- * If offset is on a block boundary,
- * read the next directory block.
- * Release previous if it exists.
+ * Insert name into cache if appropriate.
*/
+ if (ndp->ni_makeentry)
+ cache_enter(ndp);
+ return (0);
+}
+
+void
+ufs_dirbad(ip, offset, how)
+ struct inode *ip;
+ off_t offset;
+ char *how;
+{
+ struct mount *mp;
+
+ mp = ITOV(ip)->v_mount;
+ (void)printf("%s: bad dir ino %d at offset %d: %s\n",
+ mp->mnt_stat.f_mntonname, ip->i_number, offset, how);
+ if ((mp->mnt_stat.f_flags & MNT_RDONLY) == 0)
+ panic("bad dir");
+}
- if((u.u_offset&BMASK) == 0) {
- if(bp != NULL)
- brelse(bp);
- bp = bread(dp->i_dev,
- bmap(dp, (daddr_t)(u.u_offset>>BSHIFT), B_READ));
- if (bp->b_flags & B_ERROR) {
- brelse(bp);
- goto out;
+/*
+ * Do consistency checking on a directory entry:
+ * record length must be multiple of 4
+ * entry must fit in rest of its DIRBLKSIZ block
+ * record must be large enough to contain entry
+ * name is not longer than MAXNAMLEN
+ * name must be as long as advertised, and null terminated
+ */
+int
+ufs_dirbadentry(ep, entryoffsetinblock)
+ register struct direct *ep;
+ int entryoffsetinblock;
+{
+ register int i;
+
+ if ((ep->d_reclen & 0x3) != 0 ||
+ ep->d_reclen > DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1)) ||
+ ep->d_reclen < DIRSIZ(ep) || ep->d_namlen > MAXNAMLEN) {
+ /*return (1); */
+ printf("First bad\n");
+ goto bad;
+ }
+ for (i = 0; i < ep->d_namlen; i++)
+ if (ep->d_name[i] == '\0') {
+ /*return (1); */
+ printf("Second bad\n");
+ goto bad;
+ }
+ if (ep->d_name[i])
+ goto bad;
+ return (ep->d_name[i]);
+bad:
+printf("ufs_dirbadentry: jumping out: reclen: %d namlen %d ino %d name %s\n",
+ ep->d_reclen, ep->d_namlen, ep->d_ino, ep->d_name );
+ return(1);
+}
+
+/*
+ * Write a directory entry after a call to namei, using the parameters
+ * that it left in nameidata. The argument ip is the inode which the new
+ * directory entry will refer to. The nameidata field ndp->ni_dvp is a
+ * pointer to the directory to be written, which was left locked by namei.
+ * Remaining parameters (ndp->ni_ufs.ufs_offset, ndp->ni_ufs.ufs_count)
+ * indicate how the space for the new entry is to be obtained.
+ */
+int
+ufs_direnter(ip, ndp)
+ struct inode *ip;
+ register struct nameidata *ndp;
+{
+ register struct direct *ep, *nep;
+ register struct inode *dp;
+ register struct vnode *dvp;
+ struct buf *bp;
+ struct direct newdir;
+ struct iovec aiov;
+ struct uio auio;
+ u_int dsize;
+ int error, loc, newentrysize, spacefree;
+ char *dirbuf;
+
+#ifdef DIAGNOSTIC
+ if ((ndp->ni_nameiop & SAVENAME) == 0)
+ panic("direnter: missing name");
+#endif
+ dvp = ndp->ni_dvp;
+ dp = VTOI(dvp);
+ newdir.d_ino = ip->i_number;
+ newdir.d_namlen = ndp->ni_namelen;
+ bcopy(ndp->ni_ptr, newdir.d_name, (unsigned)ndp->ni_namelen + 1);
+ newentrysize = DIRSIZ(&newdir);
+ if (ndp->ni_ufs.ufs_count == 0) {
+ /*
+ * If ndp->ni_ufs.ufs_count is 0, then namei could find no
+ * space in the directory. Here, ndp->ni_ufs.ufs_offset will
+ * be on a directory block boundary and we will write the
+ * new entry into a fresh block.
+ */
+ if (ndp->ni_ufs.ufs_offset & (DIRBLKSIZ - 1))
+ panic("wdir: newblk");
+ auio.uio_offset = ndp->ni_ufs.ufs_offset;
+ newdir.d_reclen = DIRBLKSIZ;
+ auio.uio_resid = newentrysize;
+ aiov.iov_len = newentrysize;
+ aiov.iov_base = (caddr_t)&newdir;
+ auio.uio_iov = &aiov;
+ auio.uio_iovcnt = 1;
+ auio.uio_rw = UIO_WRITE;
+ auio.uio_segflg = UIO_SYSSPACE;
+ auio.uio_procp = (struct proc *)0;
+ error = VOP_WRITE(dvp, &auio, IO_SYNC, ndp->ni_cred);
+ if (DIRBLKSIZ >
+ VFSTOUFS(dvp->v_mount)->um_mountp->mnt_stat.f_bsize)
+ /* XXX should grow with balloc() */
+ panic("ufs_direnter: frag size");
+ else if (!error) {
+ dp->i_size = roundup(dp->i_size, DIRBLKSIZ);
+ dp->i_flag |= ICHG;
}
- ep = (struct direct *)bp->b_un.b_addr;
- } else
- ep++;
+ return (error);
+ }
/*
- * Note first empty directory slot
- * in eo for possible creat.
- * String compare the directory entry
- * and the current component.
- * If they do not match, go back to eloop.
+ * If ndp->ni_ufs.ufs_count is non-zero, then namei found space
+ * for the new entry in the range ndp->ni_ufs.ufs_offset to
+ * ndp->ni_ufs.ufs_offset + ndp->ni_ufs.ufs_count in the directory.
+ * To use this space, we may have to compact the entries located
+ * there, by copying them together towards the beginning of the
+ * block, leaving the free space in one usable chunk at the end.
*/
- u.u_offset += sizeof(struct direct);
- if(ep->d_ino == 0) {
- if(eo == 0)
- eo = u.u_offset;
- goto eloop;
+ /*
+ * Increase size of directory if entry eats into new space.
+ * This should never push the size past a new multiple of
+ * DIRBLKSIZE.
+ *
+ * N.B. - THIS IS AN ARTIFACT OF 4.2 AND SHOULD NEVER HAPPEN.
+ */
+ if (ndp->ni_ufs.ufs_offset + ndp->ni_ufs.ufs_count > dp->i_size)
+ dp->i_size = ndp->ni_ufs.ufs_offset + ndp->ni_ufs.ufs_count;
+ /*
+ * Get the block containing the space for the new directory entry.
+ */
+ if (error = VOP_BLKATOFF(dvp, ndp->ni_ufs.ufs_offset, &dirbuf, &bp))
+ return (error);
+ /*
+ * Find space for the new entry. In the simple case, the entry at
+ * offset base will have the space. If it does not, then namei
+ * arranged that compacting the region ndp->ni_ufs.ufs_offset to
+ * ndp->ni_ufs.ufs_offset + ndp->ni_ufs.ufs_count would yield the
+ * space.
+ */
+ ep = (struct direct *)dirbuf;
+ dsize = DIRSIZ(ep);
+ spacefree = ep->d_reclen - dsize;
+ for (loc = ep->d_reclen; loc < ndp->ni_ufs.ufs_count; ) {
+ nep = (struct direct *)(dirbuf + loc);
+ if (ep->d_ino) {
+ /* trim the existing slot */
+ ep->d_reclen = dsize;
+ ep = (struct direct *)((char *)ep + dsize);
+ } else {
+ /* overwrite; nothing there; header is ours */
+ spacefree += dsize;
+ }
+ dsize = DIRSIZ(nep);
+ spacefree += nep->d_reclen - dsize;
+ loc += nep->d_reclen;
+ bcopy((caddr_t)nep, (caddr_t)ep, dsize);
}
- for(i=0; i<DIRSIZ; i++) {
- if(u.u_dbuf[i] != ep->d_name[i])
- goto eloop;
- if(u.u_dbuf[i] == 0)
- break;
+ /*
+ * Update the pointer fields in the previous entry (if any),
+ * copy in the new entry, and write out the block.
+ */
+ if (ep->d_ino == 0) {
+ if (spacefree + dsize < newentrysize)
+ panic("wdir: compact1");
+ newdir.d_reclen = spacefree + dsize;
+ } else {
+ if (spacefree < newentrysize)
+ panic("wdir: compact2");
+ newdir.d_reclen = spacefree;
+ ep->d_reclen = dsize;
+ ep = (struct direct *)((char *)ep + dsize);
}
+ bcopy((caddr_t)&newdir, (caddr_t)ep, (u_int)newentrysize);
+ error = VOP_BWRITE(bp);
+ dp->i_flag |= IUPD|ICHG;
+ if (!error && ndp->ni_ufs.ufs_endoff &&
+ ndp->ni_ufs.ufs_endoff < dp->i_size)
+ error = VOP_TRUNCATE(dvp, (u_long)ndp->ni_ufs.ufs_endoff,
+ IO_SYNC);
+ return (error);
+}
+
+/*
+ * Remove a directory entry after a call to namei, using
+ * the parameters which it left in nameidata. The entry
+ * ni_ufs.ufs_offset contains the offset into the directory of the
+ * entry to be eliminated. The ni_ufs.ufs_count field contains the
+ * size of the previous record in the directory. If this
+ * is 0, the first entry is being deleted, so we need only
+ * zero the inode number to mark the entry as free. If the
+ * entry is not the first in the directory, we must reclaim
+ * the space of the now empty record by adding the record size
+ * to the size of the previous entry.
+ */
+int
+ufs_dirremove(ndp)
+ register struct nameidata *ndp;
+{
+ register struct inode *dp;
+ struct direct *ep;
+ struct buf *bp;
+ int error;
+ dp = VTOI(ndp->ni_dvp);
+ if (ndp->ni_ufs.ufs_count == 0) {
+ /*
+ * First entry in block: set d_ino to zero.
+ */
+ if (error = VOP_BLKATOFF(ndp->ni_dvp, ndp->ni_ufs.ufs_offset,
+ (char **)&ep, &bp))
+ return (error);
+ ep->d_ino = 0;
+ error = VOP_BWRITE(bp);
+ dp->i_flag |= IUPD|ICHG;
+ return (error);
+ }
/*
- * Here a component matched in a directory.
- * If there is more pathname, go back to
- * cloop, otherwise return.
+ * Collapse new free space into previous entry.
*/
- bcopy((caddr_t)ep, (caddr_t)&u.u_dent, sizeof(struct direct));
- if(bp != NULL)
- brelse(bp);
- if(flag==2 && c=='\0') {
- if(access(dp, IWRITE))
- goto out;
- return(dp);
- }
- d = dp->i_dev;
- if(u.u_dent.d_ino == ROOTINO)
- if(dp->i_number == ROOTINO)
- if(u.u_dent.d_name[1] == '.')
- for(i=1; i<NMOUNT; i++)
- if(mount[i].m_bufp != NULL)
- if(mount[i].m_dev == d) {
- iput(dp);
- dp = mount[i].m_inodp;
- dp->i_count++;
- plock(dp);
- goto seloop;
- }
- iput(dp);
- dp = iget(d, u.u_dent.d_ino);
- if(dp == NULL)
- return(NULL);
- goto cloop;
+ if (error = VOP_BLKATOFF(ndp->ni_dvp,
+ ndp->ni_ufs.ufs_offset - ndp->ni_ufs.ufs_count, (char **)&ep, &bp))
+ return (error);
+ ep->d_reclen += ndp->ni_ufs.ufs_reclen;
+ error = VOP_BWRITE(bp);
+ dp->i_flag |= IUPD|ICHG;
+ return (error);
+}
-out:
- iput(dp);
- return(NULL);
+/*
+ * Rewrite an existing directory entry to point at the inode
+ * supplied. The parameters describing the directory entry are
+ * set up by a call to namei.
+ */
+int
+ufs_dirrewrite(dp, ip, ndp)
+ struct inode *dp, *ip;
+ struct nameidata *ndp;
+{
+ struct buf *bp;
+ struct direct *ep;
+ int error;
+
+ if (error = VOP_BLKATOFF(ITOV(dp), ndp->ni_ufs.ufs_offset,
+ (char **)&ep, &bp))
+ return (error);
+ ep->d_ino = ip->i_number;
+ error = VOP_BWRITE(bp);
+ dp->i_flag |= IUPD|ICHG;
+ return (error);
}
/*
- * Return the next character from the
- * kernel string pointed at by dirp.
+ * Check if a directory is empty or not.
+ * Inode supplied must be locked.
+ *
+ * Using a struct dirtemplate here is not precisely
+ * what we want, but better than using a struct direct.
+ *
+ * NB: does not handle corrupted directories.
*/
-schar()
+int
+ufs_dirempty(ip, parentino, cred)
+ register struct inode *ip;
+ ino_t parentino;
+ struct ucred *cred;
{
+ register off_t off;
+ struct dirtemplate dbuf;
+ register struct direct *dp = (struct direct *)&dbuf;
+ int error, count;
+#define MINDIRSIZ (sizeof (struct dirtemplate) / 2)
- return(*u.u_dirp++ & 0377);
+ for (off = 0; off < ip->i_size; off += dp->d_reclen) {
+ error = vn_rdwr(UIO_READ, ITOV(ip), (caddr_t)dp, MINDIRSIZ, off,
+ UIO_SYSSPACE, IO_NODELOCKED, cred, &count, (struct proc *)0);
+ /*
+ * Since we read MINDIRSIZ, residual must
+ * be 0 unless we're at end of file.
+ */
+ if (error || count != 0)
+ return (0);
+ /* avoid infinite loops */
+ if (dp->d_reclen == 0)
+ return (0);
+ /* skip empty entries */
+ if (dp->d_ino == 0)
+ continue;
+ /* accept only "." and ".." */
+ if (dp->d_namlen > 2)
+ return (0);
+ if (dp->d_name[0] != '.')
+ return (0);
+ /*
+ * At this point d_namlen must be 1 or 2.
+ * 1 implies ".", 2 implies ".." if second
+ * char is also "."
+ */
+ if (dp->d_namlen == 1)
+ continue;
+ if (dp->d_name[1] == '.' && dp->d_ino == parentino)
+ continue;
+ return (0);
+ }
+ return (1);
}
/*
- * Return the next character from the
- * user string pointed at by dirp.
+ * Check if source directory is in the path of the target directory.
+ * Target is supplied locked, source is unlocked.
+ * The target is always iput before returning.
*/
-uchar()
+int
+ufs_checkpath(source, target, cred)
+ struct inode *source, *target;
+ struct ucred *cred;
{
- register c;
+ struct dirtemplate dirbuf;
+ register struct inode *ip;
+ struct vnode *vp;
+ int error, rootino;
- c = fubyte(u.u_dirp++);
- if(c == -1)
- u.u_error = EFAULT;
- return(c);
+ ip = target;
+ if (ip->i_number == source->i_number) {
+ error = EEXIST;
+ goto out;
+ }
+ rootino = ROOTINO;
+ error = 0;
+ if (ip->i_number == rootino)
+ goto out;
+
+ for (;;) {
+ if ((ip->i_mode&IFMT) != IFDIR) {
+ error = ENOTDIR;
+ break;
+ }
+ vp = ITOV(ip);
+ error = vn_rdwr(UIO_READ, vp, (caddr_t)&dirbuf,
+ sizeof (struct dirtemplate), (off_t)0, UIO_SYSSPACE,
+ IO_NODELOCKED, cred, (int *)0, (struct proc *)0);
+ if (error != 0)
+ break;
+ if (dirbuf.dotdot_namlen != 2 ||
+ dirbuf.dotdot_name[0] != '.' ||
+ dirbuf.dotdot_name[1] != '.') {
+ error = ENOTDIR;
+ break;
+ }
+ if (dirbuf.dotdot_ino == source->i_number) {
+ error = EINVAL;
+ break;
+ }
+ if (dirbuf.dotdot_ino == rootino)
+ break;
+ ufs_iput(ip);
+ if (error = VOP_VGET(vp, dirbuf.dotdot_ino, &vp))
+ break;
+ ip = VTOI(vp);
+ }
+
+out:
+ if (error == ENOTDIR)
+ printf("checkpath: .. not a directory\n");
+ if (ip != NULL)
+ ufs_iput(ip);
+ return (error);
}