-/* ufs_lookup.c 4.5 81/03/09 */
+/*
+ * Copyright (c) 1989 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * %sccs.include.redist.c%
+ *
+ * @(#)ufs_lookup.c 7.31 (Berkeley) %G%
+ */
+
+#include "param.h"
+#include "namei.h"
+#include "buf.h"
+#include "file.h"
+#include "vnode.h"
-#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 "../h/conf.h"
+#include "quota.h"
+#include "inode.h"
+#include "fs.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:
+ *
+ * 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
*
- * 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
+ * NOTE: (LOOKUP | LOCKPARENT) currently returns the parent inode unlocked.
*/
-struct inode *
-namei(func, flag)
-int (*func)();
+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 */
+ register struct fs *fs; /* file system that directory is in */
+ struct buf *bp = 0; /* 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 = -1; /* 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; /* ndp->ni_offset of previous entry */
+ struct inode *pdp; /* saved dp during symlink work */
+ struct inode *tdp; /* returned by iget */
+ off_t enduseful; /* pointer past last used dir slot */
+ int flag; /* LOOKUP, CREATE, RENAME, or DELETE */
+ int lockparent; /* 1 => lockparent flag is set */
+ int wantparent; /* 1 => wantparent or lockparent flag */
+ int error;
+
+ ndp->ni_dvp = vdp;
+ ndp->ni_vp = NULL;
+ dp = VTOI(vdp);
+ fs = dp->i_fs;
+ 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);
+ 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.
*/
+ slotstatus = FOUND;
+ if ((flag == CREATE || flag == RENAME) && *ndp->ni_next == 0) {
+ slotstatus = NONE;
+ slotfreespace = 0;
+ slotneeded = DIRSIZ(&ndp->ni_dent);
+ }
- cp = &u.u_dbuf[0];
- while (c != '/' && c != '\0' && u.u_error == 0 ) {
- if (mpxip!=NULL && c=='!')
- break;
- if (flag==1 && c == ('/'|0200)) {
- u.u_error = ENOENT;
- goto out;
+ /*
+ * 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.
+ */
+ if (flag != LOOKUP || dp->i_diroff == 0 || dp->i_diroff > dp->i_size) {
+ ndp->ni_offset = 0;
+ numdirpasses = 1;
+ } else {
+ ndp->ni_offset = dp->i_diroff;
+ entryoffsetinblock = blkoff(fs, ndp->ni_offset);
+ if (entryoffsetinblock != 0) {
+ error = blkatoff(dp, ndp->ni_offset, (char **)0, &bp);
+ if (error)
+ return (error);
}
- if (cp < &u.u_dbuf[DIRSIZ])
- *cp++ = c;
- c = (*func)();
- }
- 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);
+ numdirpasses = 2;
+ nchstats.ncs_2passes++;
}
+ endsearch = roundup(dp->i_size, DIRBLKSIZ);
+ enduseful = 0;
+
+searchloop:
+ while (ndp->ni_offset < endsearch) {
+ /*
+ * If offset is on a block boundary,
+ * read the next directory block.
+ * Release previous if it exists.
+ */
+ if (blkoff(fs, ndp->ni_offset) == 0) {
+ if (bp != NULL)
+ brelse(bp);
+ error = blkatoff(dp, ndp->ni_offset, (char **)0, &bp);
+ if (error)
+ 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 && dirbadentry(ep, entryoffsetinblock)) {
+ int i;
-seloop:
+ dirbad(dp, ndp->ni_offset, "mangled entry");
+ i = DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1));
+ ndp->ni_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_offset;
+ slotsize = ep->d_reclen;
+ } else if (slotstatus == NONE) {
+ slotfreespace += size;
+ if (slotoffset == -1)
+ slotoffset = ndp->ni_offset;
+ if (slotfreespace >= slotneeded) {
+ slotstatus = COMPACT;
+ slotsize = ndp->ni_offset +
+ ep->d_reclen - slotoffset;
+ }
+ }
+ }
+ }
+
+ /*
+ * Check for a name match.
+ */
+ if (ep->d_ino) {
+ if (ep->d_namlen == ndp->ni_dent.d_namlen &&
+ !bcmp(ndp->ni_ptr, ep->d_name,
+ (unsigned)ep->d_namlen)) {
+ /*
+ * Save directory entry's inode number and
+ * reclen in ndp->ni_dent, and release
+ * directory buffer.
+ */
+ ndp->ni_dent.d_ino = ep->d_ino;
+ ndp->ni_dent.d_reclen = ep->d_reclen;
+ brelse(bp);
+ goto found;
+ }
+ }
+ prevoff = ndp->ni_offset;
+ ndp->ni_offset += ep->d_reclen;
+ entryoffsetinblock += ep->d_reclen;
+ if (ep->d_ino)
+ enduseful = ndp->ni_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_offset = 0;
+ endsearch = dp->i_diroff;
+ goto searchloop;
+ }
+ if (bp != NULL)
+ brelse(bp);
+ /*
+ * 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_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
+ * [ndp->ni_offset .. ndp->ni_offset + ndp->ni_count)
+ */
+ if (slotstatus == NONE) {
+ ndp->ni_offset = roundup(dp->i_size, DIRBLKSIZ);
+ ndp->ni_count = 0;
+ enduseful = ndp->ni_offset;
+ } else {
+ ndp->ni_offset = slotoffset;
+ ndp->ni_count = slotsize;
+ if (enduseful < slotoffset + slotsize)
+ enduseful = slotoffset + slotsize;
+ }
+ ndp->ni_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.
+ *
+ * NB - if the directory is unlocked, then this
+ * information cannot be used.
+ */
+ if (!lockparent)
+ IUNLOCK(dp);
+ }
/*
- * dp must be a directory and
- * must have X permission.
+ * 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) {
+ dirbad(dp, ndp->ni_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 (dp == u.u_rdir && u.u_dent.d_name[0] == '.' &&
- u.u_dent.d_name[1] == '.' && u.u_dent.d_name[2] == 0)
- goto cloop;
+ if (*ndp->ni_next == '\0' && flag == LOOKUP)
+ dp->i_diroff = ndp->ni_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_offset,
+ * and distance past previous entry (if there
+ * is a previous entry in this block) in ndp->ni_count.
+ * Save directory inode pointer in ndp->ni_dvp for dirremove().
+ */
+ if ((ndp->ni_offset&(DIRBLKSIZ-1)) == 0)
+ ndp->ni_count = 0;
+ else
+ ndp->ni_count = ndp->ni_offset - prevoff;
+ if (dp->i_number == ndp->ni_dent.d_ino) {
+ VREF(vdp);
+ ndp->ni_vp = vdp;
+ return (0);
+ }
+ if (error = iget(dp, ndp->ni_dent.d_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 &&
+ tdp->i_uid != ndp->ni_cred->cr_uid) {
+ iput(tdp);
+ return (EPERM);
+ }
+ ndp->ni_vp = ITOV(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_dent.d_ino)
+ return (EISDIR);
+ if (error = iget(dp, ndp->ni_dent.d_ino, &tdp))
+ return (error);
+ ndp->ni_vp = ITOV(tdp);
+ if (!lockparent)
+ IUNLOCK(dp);
+ return (0);
+ }
- if(u.u_offset >= dp->i_size) {
- if(bp != NULL)
- brelse(bp);
- if(flag==1 && c=='\0' && dp->i_nlink) {
- 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 = iget(dp, ndp->ni_dent.d_ino, &tdp)) {
+ ILOCK(pdp);
+ return (error);
}
- u.u_error = ENOENT;
- goto out;
+ if (lockparent && *ndp->ni_next == '\0')
+ ILOCK(pdp);
+ ndp->ni_vp = ITOV(tdp);
+ } else if (dp->i_number == ndp->ni_dent.d_ino) {
+ VREF(vdp); /* we want ourself, ie "." */
+ ndp->ni_vp = vdp;
+ } else {
+ if (error = iget(dp, ndp->ni_dent.d_ino, &tdp))
+ return (error);
+ if (!lockparent || *ndp->ni_next != '\0')
+ IUNLOCK(pdp);
+ ndp->ni_vp = ITOV(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);
+}
+
+
+dirbad(ip, offset, how)
+ struct inode *ip;
+ off_t offset;
+ char *how;
+{
+
+ printf("%s: bad dir ino %d at offset %d: %s\n",
+ ip->i_fs->fs_fsmnt, ip->i_number, offset, how);
+ if (ip->i_fs->fs_ronly == 0)
+ panic("bad dir");
+}
+
+/*
+ * 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
+ */
+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);
+ for (i = 0; i < ep->d_namlen; i++)
+ if (ep->d_name[i] == '\0')
+ return (1);
+ return (ep->d_name[i]);
+}
- 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;
+/*
+ * Write a directory entry after a call to namei, using the parameters
+ * which 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_offset, ndp->ni_count) indicate
+ * how the space for the new entry is to be gotten.
+ */
+direnter(ip, ndp)
+ struct inode *ip;
+ register struct nameidata *ndp;
+{
+ register struct direct *ep, *nep;
+ register struct inode *dp = VTOI(ndp->ni_dvp);
+ struct buf *bp;
+ int loc, spacefree, error = 0;
+ u_int dsize;
+ int newentrysize;
+ char *dirbuf;
+
+ ndp->ni_dent.d_ino = ip->i_number;
+ newentrysize = DIRSIZ(&ndp->ni_dent);
+ if (ndp->ni_count == 0) {
+ /*
+ * If ndp->ni_count is 0, then namei could find no space in the
+ * directory. In this case ndp->ni_offset will be on a directory
+ * block boundary and we will write the new entry into a fresh
+ * block.
+ */
+ if (ndp->ni_offset&(DIRBLKSIZ-1))
+ panic("wdir: newblk");
+ ndp->ni_dent.d_reclen = DIRBLKSIZ;
+ ndp->ni_count = newentrysize;
+ ndp->ni_resid = newentrysize;
+ ndp->ni_base = (caddr_t)&ndp->ni_dent;
+ ndp->ni_iov = &ndp->ni_nd.nd_iovec;
+ ndp->ni_iovcnt = 1;
+ ndp->ni_rw = UIO_WRITE;
+ ndp->ni_uioseg = UIO_SYSSPACE;
+ error =
+ ufs_write(ndp->ni_dvp, &ndp->ni_uio, IO_SYNC, ndp->ni_cred);
+ if (DIRBLKSIZ > dp->i_fs->fs_fsize) {
+ panic("wdir: blksize"); /* XXX - should grow w/balloc */
+ } else {
+ 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_count is non-zero, then namei found space for the new
+ * entry in the range ndp->ni_offset to ndp->ni_offset + ndp->ni_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;
- }
- for(i=0; i<DIRSIZ; i++) {
- if(u.u_dbuf[i] != ep->d_name[i])
- goto eloop;
- if(u.u_dbuf[i] == 0)
- break;
+ /*
+ * 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_offset + ndp->ni_count > dp->i_size)
+ dp->i_size = ndp->ni_offset + ndp->ni_count;
+ /*
+ * Get the block containing the space for the new directory entry.
+ */
+ if (error = blkatoff(dp, ndp->ni_offset, (char **)&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_offset to ndp->ni_offset+ndp->ni_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_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);
}
-
/*
- * Here a component matched in a directory.
- * If there is more pathname, go back to
- * cloop, otherwise return.
+ * Update the pointer fields in the previous entry (if any),
+ * copy in the new entry, and write out the block.
*/
- 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 (ep->d_ino == 0) {
+ if (spacefree + dsize < newentrysize)
+ panic("wdir: compact1");
+ ndp->ni_dent.d_reclen = spacefree + dsize;
+ } else {
+ if (spacefree < newentrysize)
+ panic("wdir: compact2");
+ ndp->ni_dent.d_reclen = spacefree;
+ ep->d_reclen = dsize;
+ ep = (struct direct *)((char *)ep + dsize);
+ }
+ bcopy((caddr_t)&ndp->ni_dent, (caddr_t)ep, (u_int)newentrysize);
+ error = bwrite(bp);
+ dp->i_flag |= IUPD|ICHG;
+ if (!error && ndp->ni_endoff && ndp->ni_endoff < dp->i_size)
+ error = itrunc(dp, (u_long)ndp->ni_endoff, IO_SYNC);
+ return (error);
+}
-out:
- iput(dp);
- return(NULL);
+/*
+ * Remove a directory entry after a call to namei, using
+ * the parameters which it left in nameidata. The entry
+ * ni_offset contains the offset into the directory of the
+ * entry to be eliminated. The ni_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 isn't 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.
+ */
+dirremove(ndp)
+ register struct nameidata *ndp;
+{
+ register struct inode *dp = VTOI(ndp->ni_dvp);
+ struct direct *ep;
+ struct buf *bp;
+ int error;
+
+ if (ndp->ni_count == 0) {
+ /*
+ * First entry in block: set d_ino to zero.
+ */
+ ndp->ni_dent.d_ino = 0;
+ ndp->ni_count = ndp->ni_resid = DIRSIZ(&ndp->ni_dent);
+ ndp->ni_base = (caddr_t)&ndp->ni_dent;
+ ndp->ni_iov = &ndp->ni_nd.nd_iovec;
+ ndp->ni_iovcnt = 1;
+ ndp->ni_rw = UIO_WRITE;
+ ndp->ni_uioseg = UIO_SYSSPACE;
+ error =
+ ufs_write(ndp->ni_dvp, &ndp->ni_uio, IO_SYNC, ndp->ni_cred);
+ } else {
+ /*
+ * Collapse new free space into previous entry.
+ */
+ if (error = blkatoff(dp, ndp->ni_offset - ndp->ni_count,
+ (char **)&ep, &bp)) {
+ return (error);
+ }
+ ep->d_reclen += ndp->ni_dent.d_reclen;
+ error = bwrite(bp);
+ dp->i_flag |= IUPD|ICHG;
+ }
+ return (error);
}
/*
- * Return the next character from the
- * kernel string pointed at by dirp.
+ * 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.
*/
-schar()
+dirrewrite(dp, ip, ndp)
+ struct inode *dp, *ip;
+ struct nameidata *ndp;
{
- return(*u.u_dirp++ & 0377);
+ ndp->ni_dent.d_ino = ip->i_number;
+ ndp->ni_count = ndp->ni_resid = DIRSIZ(&ndp->ni_dent);
+ ndp->ni_base = (caddr_t)&ndp->ni_dent;
+ ndp->ni_iov = &ndp->ni_nd.nd_iovec;
+ ndp->ni_iovcnt = 1;
+ ndp->ni_rw = UIO_WRITE;
+ ndp->ni_uioseg = UIO_SYSSPACE;
+ return (ufs_write(ITOV(dp), &ndp->ni_uio, IO_SYNC, ndp->ni_cred));
}
/*
- * Return the next character from the
- * user string pointed at by dirp.
+ * Return buffer with contents of block "offset"
+ * from the beginning of directory "ip". If "res"
+ * is non-zero, fill it in with a pointer to the
+ * remaining space in the directory.
*/
-uchar()
+blkatoff(ip, offset, res, bpp)
+ struct inode *ip;
+ off_t offset;
+ char **res;
+ struct buf **bpp;
{
- register c;
+ register struct fs *fs = ip->i_fs;
+ daddr_t lbn = lblkno(fs, offset);
+ int bsize = blksize(fs, ip, lbn);
+ struct buf *bp;
+ daddr_t bn;
+ int error;
- c = fubyte(u.u_dirp++);
- if(c == -1)
- u.u_error = EFAULT;
- return(c);
+ *bpp = 0;
+ if (error = bread(ITOV(ip), lbn, bsize, NOCRED, &bp)) {
+ brelse(bp);
+ return (error);
+ }
+ if (res)
+ *res = bp->b_un.b_addr + blkoff(fs, offset);
+ *bpp = bp;
+ return (0);
+}
+
+/*
+ * 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.
+ */
+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)
+
+ 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);
+}
+
+/*
+ * 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.
+ */
+checkpath(source, target, cred)
+ struct inode *source, *target;
+ struct ucred *cred;
+{
+ struct dirtemplate dirbuf;
+ struct inode *ip;
+ int error = 0;
+
+ ip = target;
+ if (ip->i_number == source->i_number) {
+ error = EEXIST;
+ goto out;
+ }
+ if (ip->i_number == ROOTINO)
+ goto out;
+
+ for (;;) {
+ if ((ip->i_mode&IFMT) != IFDIR) {
+ error = ENOTDIR;
+ break;
+ }
+ error = vn_rdwr(UIO_READ, ITOV(ip), (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;
+ iput(ip);
+ if (error = iget(ip, dirbuf.dotdot_ino, &ip))
+ break;
+ }
+
+out:
+ if (error == ENOTDIR)
+ printf("checkpath: .. not a directory\n");
+ if (ip != NULL)
+ iput(ip);
+ return (error);
}