4.3BSD-UWisc/src/sys/ufs/ufs_dir.c
/* ufs_dir.c 1.1 86/02/03 */
/* @(#)ufs_dir.c 2.1 86/04/16 NFSSRC */
/*
* Copyright (c) 1984 by Sun Microsystems, Inc.
*/
/*
* Directory manipulation routines.
* From outside this file, only dirlook, direnter and dirremove
* should be called.
*/
#include "../h/param.h"
#include "../h/systm.h"
#include "../h/user.h"
#include "../h/vfs.h"
#include "../h/vnode.h"
#include "../h/buf.h"
#include "../h/uio.h"
#include "../h/dnlc.h"
#include "../h/conf.h"
#include "../ufs/inode.h"
#include "../ufs/fs.h"
#include "../ufs/mount.h"
#include "../ufs/fsdir.h"
#ifdef QUOTA
#include "../ufs/quota.h"
#endif
/*
* A virgin directory.
*/
struct dirtemplate mastertemplate = {
0, 12, 1, ".",
0, DIRBLKSIZ - 12, 2, ".."
};
#define LDIRSIZ(len) \
((sizeof (struct direct) - (MAXNAMLEN+1)) + ((len+1 + 3) &~ 3))
struct buf *blkatoff();
/*
* Look for a certain name in a directory
* On successful return, *ipp will point to the (locked) inode.
*/
dirlook(dp, namep, ipp)
register struct inode *dp;
register char *namep; /* name */
register struct inode **ipp;
{
register struct buf *bp = 0; /* a buffer of directory entries */
register struct direct *ep; /* the current directory entry */
register struct inode *ip;
struct vnode *vp, *dnlc_lookup();
int entryoffsetinblock; /* offset of ep in bp's buffer */
int numdirpasses; /* strategy for directory search */
int endsearch; /* offset to end directory search */
int namlen = strlen(namep); /* length of name */
int offset;
int error;
int i;
/*
* Check accessiblity of directory.
*/
if ((dp->i_mode&IFMT) != IFDIR) {
return (ENOTDIR);
}
if (error = iaccess(dp, IEXEC))
return (error);
/*
* Check the directory name lookup cache.
*/
vp = dnlc_lookup(ITOV(dp), namep, NOCRED);
if (vp) {
VN_HOLD(vp);
*ipp = VTOI(vp);
ilock(*ipp);
return (0);
}
ilock(dp);
if (dp->i_diroff > dp->i_size) {
dp->i_diroff = 0;
}
if (dp->i_diroff == 0) {
offset = 0;
numdirpasses = 1;
} else {
offset = dp->i_diroff;
entryoffsetinblock = blkoff(dp->i_fs, offset);
if (entryoffsetinblock != 0) {
bp = blkatoff(dp, offset, (char **)0);
if (bp == 0) {
error = u.u_error;
goto bad;
}
}
numdirpasses = 2;
}
endsearch = roundup(dp->i_size, DIRBLKSIZ);
searchloop:
while (offset < endsearch) {
/*
* If offset is on a block boundary,
* read the next directory block.
* Release previous if it exists.
*/
if (blkoff(dp->i_fs, offset) == 0) {
if (bp != NULL)
brelse(bp);
bp = blkatoff(dp, offset, (char **)0);
if (bp == 0) {
error = u.u_error; /* XXX */
goto bad;
}
entryoffsetinblock = 0;
}
ep = (struct direct *)(bp->b_un.b_addr + entryoffsetinblock);
if (i = dirmangled(dp, ep, entryoffsetinblock)) {
offset += i;
entryoffsetinblock += i;
continue;
}
/*
* Check for a name match.
* 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 can't 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.
* See comments at head of file about deadlocks.
*/
if (ep->d_ino && ep->d_namlen == namlen &&
bcmp(namep, ep->d_name, (int)ep->d_namlen) == 0) {
u_long ep_ino;
ep_ino = ep->d_ino; /* save before brelse */
brelse(bp); /* early to avoid race */
bp = 0; /* avoid double brelse */
dp->i_diroff = offset;
if (namlen == 2 && bcmp(namep, "..", 2) == 0) {
iunlock(dp); /* race to get the inode */
ip = iget(dp->i_dev, dp->i_fs, ep_ino);
if (ip == NULL) {
error = u.u_error;
goto bad2;
}
} else if (dp->i_number == ep_ino) {
VN_HOLD(ITOV(dp)); /* want ourself, "." */
ip = dp;
} else {
ip = iget(dp->i_dev, dp->i_fs, ep_ino);
iunlock(dp);
if (ip == NULL) {
error = u.u_error;
goto bad2;
}
}
*ipp = ip;
if ((ip->i_mode & IFMT) == IFDIR) {
dnlc_enter(ITOV(dp), namep, ITOV(ip), NOCRED);
}
return (0);
}
offset += ep->d_reclen;
entryoffsetinblock += ep->d_reclen;
}
/*
* 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--;
offset = 0;
endsearch = dp->i_diroff;
goto searchloop;
}
error = ENOENT;
bad:
iunlock(dp);
bad2:
if (bp)
brelse(bp);
return (error);
}
/*
* If dircheckforname fails to find a name, this structure holds
* state for direnter as to where there is space for an entry.
* If dircheckforname succeeds then this structure holds state
* for dirrename and dirremove as to where the entry is.
* After dircheckforname succeeds the values are:
* status offset size bp, ep
* ------ ------ ---- ------
* NONE end of dir needed not valid
* COMPACT start of area of area not valid
* FOUND start of entry of ent not valid
* EXIST start if entry of prev ent valid
* On success, dirprepareentry makes bp and ep valid.
*/
struct slot {
enum {NONE, COMPACT, FOUND, EXIST} status;
int offset; /* offset of area with free space */
int size; /* size of area at slotoffset */
struct buf *bp; /* dir buf where slot is */
struct direct *ep; /* pointer to slot */
};
/*
* Write a new directory entry.
* The directory must not have been removed and must be writeable.
* There are three operations in building the new entry: creating a file
* or directory (DE_CREATE), renaming (DE_RENAME) or linking (DE_LINK).
* There are five possible cases to consider:
* Name
* found op action
* ---- --- -------------------------------
* no DE_CREATE create file according to vap and enter
* no DE_LINK | DE_RENAME enter the file sip
* yes DE_CREATE error EEXIST *ipp = found file
* yes DE_LINK error EEXIST
* yes DE_RENAME remove existing file, enter new file
*/
direnter(tdp, namep, op, sdp, sip, vap, ipp)
register struct inode *tdp; /* target directory to make entry in */
register char *namep; /* name of entry */
enum de_op op; /* entry operation */
register struct inode *sdp; /* source inode parent if rename */
struct inode *sip; /* source inode if link/rename */
struct vattr *vap; /* attributes if new inode needed */
struct inode **ipp; /* return entered inode (locked) here */
{
struct inode *tip; /* inode of (existing) target file */
struct slot slot; /* slot info to pass around */
register int namlen; /* length of name */
register int error; /* error number */
namlen = strlen(namep);
/*
* If name is "." or ".." then if this is a create look it up
* and return EEXIST. Rename or link TO "." or ".." is forbidden.
*/
if (namlen == 1 && bcmp(namep, ".", 1) == 0 ||
namlen == 2 && bcmp(namep, "..", 2) == 0) {
if (op == DE_RENAME) {
return (ENOTEMPTY);
}
if (ipp) {
if (error = dirlook(tdp, namep, ipp))
return (error);
}
return (EEXIST);
}
if (dirbadname(namep, namlen)) {
return (EPERM);
}
slot.status = NONE;
slot.bp = NULL;
/*
* For link and rename lock the sorce entry and check the link count to
* see if it has been removed while it was unlocked. If not, we
* increment the link count and force the inode to disk to make sure
* that it is there before any directory entry that points to it.
*/
if (op != DE_CREATE) {
ilock(sip);
if (sip->i_nlink == 0) {
iunlock(sip);
return (ENOENT);
}
sip->i_nlink++;
imark(sip, ICHG);
iupdat(sip, 1);
iunlock(sip);
}
/*
* lock the directory in which we are trying to make the new entry.
*/
ilock(tdp);
/*
* Check accessiblity of directory.
*/
if ((tdp->i_mode&IFMT) != IFDIR) {
error = ENOTDIR;
goto out;
}
/*
* If target directory has not been removed, then we can consider
* allowing file to be created.
*/
if (tdp->i_nlink == 0) {
error = ENOENT;
goto out;
}
/*
* Execute access is required to search the directory.
*/
if (error = iaccess(tdp, IEXEC)) {
goto out;
}
/*
* If this is a rename and we are doing a directory and the parent
* is different (".." must be changed), 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 "..".
*/
if ((op == DE_RENAME) &&
((sip->i_mode&IFMT) == IFDIR) && (sdp != tdp)) {
if (error = iaccess(sip, IWRITE))
goto out;
if (error = dircheckpath(sip, tdp))
goto out;
}
/*
* search for the entry
*/
error = dircheckforname(tdp, namep, namlen, &slot, &tip);
if (error) {
goto out;
}
if (tip) {
switch (op) {
case DE_CREATE:
if (ipp) {
*ipp = tip;
error = EEXIST;
} else {
iput(tip);
}
break;
case DE_RENAME:
error =
dirrename(sdp, sip, tdp, namep, namlen,
tip, &slot);
iput(tip);
break;
case DE_LINK:
/*
* Can't link to an existing file
*/
iput(tip);
error = EEXIST;
break;
}
} else {
/*
* The entry does not exist. Check write permission in
* directory to see if entry can be created.
*/
if (error = iaccess(tdp, IWRITE)) {
goto out;
}
if (op == DE_CREATE) {
/*
* make a new inode and directory as required
*/
error = dirmakeinode(tdp, &sip, vap);
if (error) {
goto out;
}
if ((sip->i_mode & IFMT) == IFDIR) {
error = dirmakedirect(sip, tdp);
if (error) {
sip->i_nlink = 0;
irele(sip);
sip = NULL;
goto out;
}
}
}
error = diraddentry(tdp, namep, namlen, &slot, sip, sdp);
if (error) {
if (op == DE_CREATE) {
/*
* unmake the inode we just made
*/
if ((sip->i_mode & IFMT) == IFDIR) {
tdp->i_nlink--;
}
sip->i_nlink = 0;
sip->i_flag |= ICHG;
irele(sip);
sip = NULL;
}
} else if (ipp) {
ilock(sip);
*ipp = sip;
} else if (op == DE_CREATE) {
irele(sip);
}
}
out:
if (slot.bp)
brelse(slot.bp);
if (error && (op != DE_CREATE)) {
/*
* undo bumped link count
*/
sip->i_nlink--;
imark(sip, ICHG);
}
iunlock(tdp);
return (error);
}
/*
* Check for the existence of a slot to make a directory entry.
* On successful return *ipp points at the (locked) inode found.
* The target directory inode (tdp) is supplied locked.
* This may not be used on "." or "..", but aliases of "." are ok.
*/
dircheckforname(tdp, namep, namlen, slotp, tipp)
register struct inode *tdp; /* inode of directory being checked */
char *namep; /* name we're checking for */
register int namlen; /* length of name */
register struct slot *slotp; /* slot structure */
struct inode **tipp; /* return inode if we find one */
{
int dirsize; /* size of the directory */
register struct buf *bp; /* pointer to directory block */
register int entryoffsetinblock;/* offset of ep in bp's buffer */
int slotfreespace; /* free space in block */
register struct direct *ep; /* directory entry */
register int offset; /* offset in the directory */
register int last_offset; /* last offset */
int len; /* length of mangled entry */
int needed;
register struct inode *ip;
bp = NULL;
entryoffsetinblock = 0;
needed = LDIRSIZ(namlen);
/*
* No point in using i_diroff since we must search whole directory
*/
dirsize = roundup(tdp->i_size, DIRBLKSIZ);
offset = last_offset = 0;
while (offset < dirsize) {
/*
* If offset is on a block boundary,
* read the next directory block.
* Release previous if it exists.
*/
if (blkoff(tdp->i_fs, offset) == 0) {
if (bp != NULL)
brelse(bp);
bp = blkatoff(tdp, offset, (char **)0);
if (bp == 0) {
return (u.u_error);
}
entryoffsetinblock = 0;
}
/*
* If still looking for a slot, and at a DIRBLKSIZE
* boundary, have to start looking for free space
* again.
*/
if (slotp->status == NONE &&
(entryoffsetinblock&(DIRBLKSIZ-1)) == 0) {
slotp->offset = -1;
slotfreespace = 0;
}
ep = (struct direct *)(bp->b_un.b_addr + entryoffsetinblock);
if (len = dirmangled(tdp, ep, entryoffsetinblock)) {
offset += len;
entryoffsetinblock += len;
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 (slotp->status != FOUND) {
int size = ep->d_reclen;
if (ep->d_ino != 0)
size -= DIRSIZ(ep);
if (size > 0) {
if (size >= needed) {
slotp->status = FOUND;
slotp->offset = offset;
slotp->size = ep->d_reclen;
} else if (slotp->status == NONE) {
slotfreespace += size;
if (slotp->offset == -1)
slotp->offset = offset;
if (slotfreespace >= needed) {
slotp->status = COMPACT;
slotp->size =
offset+ep->d_reclen -
slotp->offset;
}
}
}
}
/*
* Check for a name match.
*/
if (ep->d_ino && ep->d_namlen == namlen &&
bcmp(namep, ep->d_name, namlen) == 0) {
tdp->i_diroff = offset;
if (tdp->i_number == ep->d_ino) {
*tipp = tdp; /* we want ourself, ie "." */
VN_HOLD(ITOV(tdp));
} else {
ip = iget(tdp->i_dev, tdp->i_fs, ep->d_ino);
if (ip == NULL) {
brelse(bp);
return (u.u_error);
}
*tipp = ip;
}
slotp->status = EXIST;
slotp->offset = offset;
slotp->size = offset - last_offset;
slotp->bp = bp;
slotp->ep = ep;
return (0);
}
last_offset = offset;
offset += ep->d_reclen;
entryoffsetinblock += ep->d_reclen;
}
if (bp) {
brelse(bp);
}
if (slotp->status == NONE) {
slotp->offset = offset;
slotp->size = needed;
}
*tipp = (struct inode *)0;
return (0);
}
/*
* Rename the entry in the directory tdp so that
* it points to sip instead of tip.
*/
dirrename(sdp, sip, tdp, namep, namlen, tip, slotp)
register struct inode *sdp; /* parent directory of source */
register struct inode *sip; /* source inode */
register struct inode *tdp; /* parent directory of target */
char *namep; /* entry we are trying to change */
int namlen; /* length of entry string */
struct inode *tip; /* locked target inode */
struct slot *slotp; /* slot for entry */
{
int error;
int doingdirectory;
int parentdifferent;
if (sip->i_number == tip->i_number) {
return(0); /* not an error, just nop */
}
/*
* Must have write permission to rewrite target entry.
*/
if (error = iaccess(tdp, IWRITE)) {
return (error);
}
if ((tdp->i_mode & ISVTX) &&
u.u_uid != 0 &&
u.u_uid != tdp->i_uid &&
tip->i_uid != u.u_uid) {
error = EPERM;
return (EPERM);
}
doingdirectory = ((sip->i_mode & IFMT) == IFDIR);
parentdifferent = (sdp != tdp);
/*
* Check that everything is on the same filesystem.
*/
if ((tip->i_vnode.v_vfsp != tdp->i_vnode.v_vfsp) ||
(tip->i_vnode.v_vfsp != sip->i_vnode.v_vfsp)) {
return (EXDEV); /* XXX archaic */
}
/*
* Ensure source and target are compatible
* (both directories or both not directories).
* If target is a directory it must be empty
* and have no links to it.
*/
if ((tip->i_mode & IFMT) == IFDIR) {
/*
* Target is a dir. Source must be a dir and
* target must be empty.
*/
if (!doingdirectory) {
error = ENOTDIR;
goto bad;
}
if (!dirempty(tip) || (tip->i_nlink > 2)) {
error = ENOTEMPTY;
goto bad;
}
} else if (doingdirectory) {
/*
* Source is a dir and target is not.
*/
error = ENOTDIR;
goto bad;
}
/*
* Rewrite the inode pointer for target name entry
* from the target inode (ip) to the source inode (sip).
* This prevents the target entry from disappearing
* during a crash. Mark the directory inode to reflect the changes.
*/
dnlc_remove(ITOV(tdp), namep);
slotp->ep->d_ino = sip->i_number;
if (doingdirectory) {
dnlc_enter(ITOV(tdp), namep, ITOV(sip), NOCRED);
}
bwrite(slotp->bp);
slotp->bp = NULL;
imark(tdp, IUPD|ICHG);
/*
* Decrement the link count of the target inode.
* Fix the ".." entry in sip to point to dp.
* This is done after the new entry is on the disk.
*/
tip->i_nlink--;
if (doingdirectory) {
/*
* Decrement target link count once more if it was a directory.
*/
if (--tip->i_nlink != 0) {
panic("direnter: target directory link count");
}
itrunc(tip, (u_long)0);
/*
* Renaming a directory with the parent different requires
* ".." to be rewritten. The window is still there for ".."
* to be inconsistent, but this is unavoidable, and a lot
* shorter than when it was done in a user process.
* If the parent was different we must decrement the target
* parent link count to reflect removal of tip.
*/
tdp->i_nlink--;
if (parentdifferent) {
error = dirfixdotdot(sip, sdp, tdp);
if (error) {
goto bad;
}
}
}
imark(tip, ICHG);
bad:
return (error);
}
/*
* Fix the ".." entry of the child directory from the old parent to the
* new parent directory.
* Assumes dp is a directory and that all the inodes are on the same
* file system.
*/
dirfixdotdot(dp, opdp, npdp)
register struct inode *dp; /* child directory */
register struct inode *opdp; /* old parent directory */
register struct inode *npdp; /* new parent directory */
{
register struct buf *bp;
struct dirtemplate *dirp;
register int error = 0;
/*
* check whether this is an ex-directory
*/
ilock(dp);
if ((dp->i_nlink == 0) || (dp->i_size < sizeof(struct dirtemplate))) {
iunlock(dp);
return (0);
}
bp = blkatoff(dp, (off_t)0, (char **) &dirp);
if (bp == NULL) {
error = u.u_error;
goto bad;
}
if (dirp->dotdot_ino == npdp->i_number) { /* just a no-op */
goto bad;
}
if (bcmp(dirp->dotdot_name, "..", 3)) {
dirbad(dp, "mangled .. entry", 0);
error = EINVAL;
goto bad;
}
/*
* Increment the link count in the new parent inode and force it out.
*/
npdp->i_nlink++;
imark(npdp, ICHG);
iupdat(npdp, 1);
/*
* Rewrite the child ".." entry and force it out.
*/
dnlc_remove(ITOV(dp), "..");
dirp->dotdot_ino = npdp->i_number;
dnlc_enter(ITOV(dp), "..", ITOV(npdp), NOCRED);
bwrite(bp);
iunlock(dp);
/*
* Decrement the link count of the old parent inode and force it out.
*/
ilock(opdp);
if (opdp->i_nlink != 0) {
opdp->i_nlink--;
imark(opdp, ICHG);
iupdat(opdp, 1);
}
iunlock(opdp);
return (0);
bad:
if (bp)
brelse(bp);
iunlock(dp);
return (error);
}
/*
* Enter the file sip in the directory tdp with name namep.
*/
diraddentry(tdp, namep, namlen, slotp, sip, sdp)
struct inode *tdp;
char *namep;
int namlen;
struct slot *slotp;
struct inode *sip;
struct inode *sdp;
{
int error;
char *strncpy();
/*
* Prepare a new entry. If the caller has not supplied an
* existing inode, make a new one.
*/
error = dirprepareentry(tdp, slotp);
if (error) {
return (error);
}
/*
* Check inode to be linked to see if it is in the
* same filesystem.
*/
if (tdp->i_vnode.v_vfsp != sip->i_vnode.v_vfsp) {
error = EXDEV;
goto bad;
}
if ((sip->i_mode & IFMT) == IFDIR) {
error = dirfixdotdot(sip, sdp, tdp);
if (error) {
goto bad;
}
}
/*
* Fill in entry data
*/
slotp->ep->d_namlen = namlen;
(void) strncpy(slotp->ep->d_name, namep, (namlen + 4) & ~3);
slotp->ep->d_ino = sip->i_number;
if ((sip->i_mode & IFMT) == IFDIR) {
dnlc_enter(ITOV(tdp), namep, ITOV(sip), NOCRED);
}
/*
* Write out the directroy entry.
*/
bwrite(slotp->bp);
slotp->bp = NULL;
/*
* Mark the directory inode to reflect the changes.
*/
imark(tdp, IUPD|ICHG);
tdp->i_diroff = 0;
return (0);
bad:
/*
* Clear out entry prepared by dirprepareent.
*/
slotp->ep->d_ino = 0;
bwrite(slotp->bp);
slotp->bp = NULL;
return (error);
}
/*
* Prepare a directory slot to receive an entry.
*/
dirprepareentry(dp, slotp)
register struct inode *dp; /* directory we are working in */
register struct slot *slotp; /* available slot info */
{
register int slotfreespace;
register int dsize;
int entryend;
register int loc;
register struct direct *ep, *nep;
char *dirbuf;
/*
* If we didn't find a slot, then indicate that the
* new slot belongs at the end of the directory.
* If we found a slot, then the new entry can be
* put at slotp->offset.
*/
if (slotp->status == NONE) {
if (slotp->offset & (DIRBLKSIZ - 1)) {
panic("dirprepareentry: new block");
}
entryend = slotp->offset + slotp->size;
/*
* Allocate the new block.
*/
if ((bmap(dp, (daddr_t)lblkno(dp->i_fs, slotp->offset), B_WRITE,
blkoff(dp->i_fs, entryend), 0) <= 0) || u.u_error) {
if (u.u_error == 0)
u.u_error = ENOSPC;
return (u.u_error);
}
} else {
entryend = slotp->offset + slotp->size;
}
/*
* adjust directory size, if needed.
*/
if (entryend > dp->i_size) {
dp->i_size = entryend;
imark(dp, IUPD|ICHG);
}
/*
* If we didn't find a slot, get the block containing the space
* for the new directory entry.
*/
slotp->bp = blkatoff(dp, slotp->offset, (char **)&slotp->ep);
if (slotp->bp == 0) {
return (u.u_error);
}
ep = slotp->ep;
dirbuf = (char *)ep;
switch (slotp->status) {
case NONE:
/*
* No space in the directory. slotp->offset will be on a
* directory block boundary and we will write the new entry
* into a fresh block.
*/
bzero((char *)ep, DIRBLKSIZ);
ep->d_reclen = DIRBLKSIZ;
break;
case FOUND:
case COMPACT:
/*
* Found space for the new entry
* in the range slotp->offset to slotp->offset + slotp->size
* in the directory. To use this space, we 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.
*/
dsize = DIRSIZ(ep);
slotfreespace = ep->d_reclen - dsize;
for (loc = ep->d_reclen; loc < slotp->size; ) {
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 */
slotfreespace += dsize;
}
dsize = DIRSIZ(nep);
slotfreespace += nep->d_reclen - dsize;
loc += nep->d_reclen;
bcopy((caddr_t)nep, (caddr_t)ep, dsize);
}
/*
* Update the pointer fields in the previous entry (if any).
* At this point, ep is the last entry in the range
* slotp->offset to slotp->offset + slotp->size.
* Slotfreespace is the now unallocated space after the
* ep entry that resulted from copying entries above.
*/
if (ep->d_ino == 0) {
ep->d_reclen = slotfreespace + dsize;
} else {
ep->d_reclen = dsize;
ep = (struct direct *)((char *)ep + dsize);
ep->d_reclen = slotfreespace;
}
break;
default:
panic("dirprepareentry: invalid slot status");
}
slotp->ep = ep;
return (0);
}
/*
* Allocate and initialize a new inode that will go
* into directory tdp.
*/
dirmakeinode(tdp, ipp, vap)
struct inode *tdp;
struct inode **ipp;
register struct vattr *vap;
{
register struct inode *ip;
int imode; /* mode and format as in inode */
ino_t ipref;
if (vap == (struct vattr *) 0) {
panic("dirmakeinode: no attributes");
}
/*
* Allocate a new inode.
*/
if (vap->va_type == VDIR) {
ipref = dirpref(tdp->i_fs);
} else {
ipref = tdp->i_number;
}
imode = MAKEIMODE(vap->va_type, vap->va_mode);
ip = ialloc(tdp, ipref, imode);
if (ip == NULL) {
return (u.u_error);
}
#ifdef QUOTA
if (ip->i_dquot != NULL)
panic("direnter: dquot");
#endif
imark(ip, IACC|IUPD|ICHG);
ip->i_mode = imode;
if (vap->va_type == VBLK || vap->va_type == VCHR) {
ip->i_rdev = vap->va_rdev;
}
ip->i_vnode.v_type = vap->va_type;
ip->i_vnode.v_rdev = vap->va_rdev;
if ((ip->i_mode & IFMT) == IFDIR) {
ip->i_nlink = 2; /* anticipating a call to dirmakedirect */
} else {
ip->i_nlink = 1;
}
ip->i_uid = u.u_uid;
ip->i_gid = tdp->i_gid;
if ((ip->i_mode & ISGID) && !groupmember(ip->i_gid)) {
ip->i_mode &= ~ISGID;
}
#ifdef QUOTA
ip->i_dquot = getinoquota(ip);
#endif
/*
* Make sure inode goes to disk before directory data and entries
* pointing to it.
* Then unlock it, since nothing points to it yet.
*/
imark(ip, ICHG);
iupdat(ip, 1);
iunlock(ip);
*ipp = ip;
return (0);
}
/*
* Make an prototype directory in the inode ip.
*/
dirmakedirect(sip, tdp)
register struct inode *sip; /* new directory */
register struct inode *tdp; /* parent directory */
{
register struct buf *bp;
register struct dirtemplate *dirp;
/*
* Allocate space for the directory we're creating.
*/
if (bmap(sip, (daddr_t)0, B_WRITE,
blkoff(tdp->i_fs, sizeof(struct dirtemplate)), 0) <= 0 ||
u.u_error) {
if (u.u_error == 0)
u.u_error = ENOSPC;
return (u.u_error);
}
if (DIRBLKSIZ > sip->i_fs->fs_fsize)
panic("dirmakedirect: blksize");
else
sip->i_size = DIRBLKSIZ;
imark(sip, IUPD|ICHG);
bp = blkatoff(sip, (off_t)0, (char **)0);
if (bp == (struct buf *)0) {
return (u.u_error);
}
/*
* Update the tdp link count and write out the
* change. This reflects the ".." entry we'll soon write.
*/
tdp->i_nlink++;
imark(tdp, ICHG);
iupdat(tdp, 1);
dirp = (struct dirtemplate *)bp->b_un.b_addr;
/*
* Now initialize the directory we're creating with the
* "." and ".." entries.
*/
*dirp = mastertemplate;
dirp->dot_ino = sip->i_number;
dirp->dotdot_ino = tdp->i_number;
bwrite(bp);
return (0);
}
/*
* Delete a directory entry
* If oip is nonzero the entry is checked to make sure it still reflects oip.
*/
dirremove(dp, namep, oip, rmdir)
register struct inode *dp;
char *namep;
struct inode *oip;
int rmdir;
{
register struct direct *ep;
struct direct *pep;
struct inode *ip;
int namlen = strlen(namep); /* length of name */
struct slot slot;
int error = 0;
/*
* return error when removing . and ..
*/
if (namlen == 1 && bcmp(namep, ".", 1) == 0) {
return (EINVAL);
}
if (namlen == 2 && bcmp(namep, "..", 2) == 0) {
return (ENOTEMPTY);
}
ip = NULL;
slot.bp = NULL;
ilock(dp);
/*
* Check accessiblity of directory.
*/
if ((dp->i_mode&IFMT) != IFDIR) {
error = ENOTDIR;
goto out;
}
/*
* Execute access is required to search the directory.
* Access for write is interpreted as allowing
* deletion of files in the directory.
*/
if (error = iaccess(dp, IEXEC|IWRITE)) {
goto out;
}
slot.status = FOUND; /* don't need to look for empty slot */
error = dircheckforname(dp, namep, namlen, &slot, &ip);
if (error) {
goto out;
}
if (ip == (struct inode *) 0) {
error = ENOENT;
goto out;
}
if (oip && oip != ip) {
error = ENOENT;
goto out;
}
/*
* Don't remove a mounted on directory. XXX
*/
if (ip->i_dev != dp->i_dev) {
error = EBUSY;
goto out;
}
/*
* If the inode being removed is a directory, we must be
* sure it only has entries "." and "..".
*/
if (rmdir && (ip->i_mode&IFMT) == IFDIR) {
if ((ip->i_nlink != 2) || !dirempty(ip)) {
error = ENOTEMPTY;
goto out;
}
}
if ((dp->i_mode & ISVTX) &&
u.u_uid != 0 &&
u.u_uid != dp->i_uid &&
ip->i_uid != u.u_uid) {
error = EPERM;
goto out;
}
/*
* Remove the cache'd entry, if any.
*/
dnlc_remove(ITOV(dp), namep);
/*
* 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.
*/
ep = slot.ep;
if ((slot.offset & (DIRBLKSIZ - 1)) == 0) {
/*
* First entry in block: set d_ino to zero.
*/
ep->d_ino = 0;
} else {
/*
* Collapse new free space into previous entry.
*/
pep = (struct direct *) ((char *) ep - slot.size);
pep->d_reclen += ep->d_reclen;
}
bwrite(slot.bp);
slot.bp = NULL;
/*
* Now dereference the inode.
*/
if (ip->i_nlink > 0) {
if (rmdir && (ip->i_mode & IFMT) == IFDIR) {
/*
* Decrement by 2 because we're trashing the "."
* entry as well as removing the entry in dp.
* Clear the inode, but there may be other hard
* links so don't free the inode.
* Decrement the dp linkcount because we're
* trashing the ".." entry.
*/
ip->i_nlink -= 2;
dp->i_nlink--;
dnlc_remove(ITOV(ip), ".");
dnlc_remove(ITOV(ip), "..");
itrunc(ip, (u_long)0);
} else {
ip->i_nlink--;
}
}
imark(dp, IUPD|ICHG);
imark(ip, ICHG);
out:
if (ip)
iput(ip);
if (slot.bp)
brelse(slot.bp);
iunlock(dp);
return (error);
}
/*
* 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.
*/
struct buf *
blkatoff(ip, offset, res)
struct inode *ip;
off_t offset;
char **res;
{
register struct fs *fs;
daddr_t lbn;
int bsize;
daddr_t bn;
register struct buf *bp;
fs = ip->i_fs;
lbn = lblkno(fs, offset);
bsize = blksize(fs, ip, lbn);
bn = fsbtodb(fs, bmap(ip, lbn, B_READ));
if (bn < 0) {
dirbad(ip, "nonexixtent directory block", offset);
u.u_error = ENOENT;
}
if (u.u_error) {
return (0);
}
bp = bread(ip->i_devvp, bn, bsize);
if (bp->b_flags & B_ERROR) {
brelse(bp);
return (0);
}
if (res)
*res = bp->b_un.b_addr + blkoff(fs, offset);
return (bp);
}
/*
* Do consistency checking:
* record length must be multiple of 4
* record length must not be zero
* entry must fit in rest of this DIRBLKSIZ block
* record must be large enough to contain name.
* When dirchk is set we also check:
* name is not longer than MAXNAMLEN
* name must be as long as advertised, and null terminated.
* Checking last two conditions is done only when dirchk is
* set, to save time.
*/
int dirchk = 0;
dirmangled(dp, ep, entryoffsetinblock)
register struct inode *dp;
register struct direct *ep;
int entryoffsetinblock;
{
register int i;
i = DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1));
if ((ep->d_reclen & 0x3) || ep->d_reclen == 0 ||
ep->d_reclen > i || DIRSIZ(ep) > ep->d_reclen ||
dirchk &&
(ep->d_namlen > MAXNAMLEN ||
dirbadname(ep->d_name, ep->d_namlen))) {
dirbad(dp, "mangled entry", entryoffsetinblock);
return (i);
}
return (0);
}
dirbad(ip, how, offset)
struct inode *ip;
char *how;
int offset;
{
printf("%s: bad dir ino %d at offset %d: %s\n",
ip->i_fs->fs_fsmnt, ip->i_number, offset, how);
}
dirbadname(sp, l)
register char *sp;
register int l;
{
register char c;
while (l--) { /* check for nulls or high bit */
c = *sp++;
if ((c == 0) || (c & 0200)) {
return (1);
}
}
return (*sp); /* check for terminating null */
}
/*
* 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)
register struct inode *ip;
{
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 = rdwri(UIO_READ, ip, (caddr_t)dp, MINDIRSIZ,
off, 1, &count);
/*
* 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 || dp->d_name[1] == '.')
continue;
return (0);
}
return (1);
}
#define RENAME_IN_PROGRESS 0x01
#define RENAME_WAITING 0x02
/*
* Check if source directory is in the path of the target directory.
* Target is supplied locked, source is unlocked.
* The target is always relocked before returning.
*/
dircheckpath(source, target)
struct inode *source, *target;
{
struct buf *bp;
struct dirtemplate *dirp;
register struct inode *ip;
static char serialize_flag = 0;
int error = 0;
/*
* If two renames of directories were in progress at once, the partially
* completed work of one dircheckpath could be invalidated by the other
* rename. To avoid this, all directory renames in the system are
* serialized.
*/
while (serialize_flag & RENAME_IN_PROGRESS) {
serialize_flag |= RENAME_WAITING;
sleep((caddr_t) &serialize_flag, PINOD);
}
serialize_flag = RENAME_IN_PROGRESS;
ip = target;
if (ip->i_number == source->i_number) {
error = EINVAL;
goto out;
}
if (ip->i_number == ROOTINO) {
goto out;
}
bp = 0;
for (;;) {
if (((ip->i_mode&IFMT) != IFDIR) ||
(ip->i_nlink == 0) ||
(ip->i_size < sizeof(struct dirtemplate))) {
printf("dircheckpath: not a directory\n");
error = ENOTDIR;
break;
}
if (bp) {
brelse(bp);
}
bp = blkatoff(ip, (off_t)0, (char **)&dirp);
if (bp == 0) {
error = u.u_error;
break;
}
if (dirp->dotdot_namlen != 2 ||
bcmp(dirp->dotdot_name, "..", 3) != 0) {
dirbad(ip, "mangled .. entry", 0);
error = ENOTDIR;
break;
}
if (dirp->dotdot_ino == source->i_number) {
error = EINVAL;
break;
}
if (dirp->dotdot_ino == ROOTINO) {
break;
}
if (ip != target) {
iput(ip);
} else {
iunlock(ip);
}
/*
* i_dev and i_fs are still valid after iput
* This is a race to get ".." just like dirlook.
*/
ip = iget(ip->i_dev, ip->i_fs, dirp->dotdot_ino);
if (ip == NULL) {
error = u.u_error;
break;
}
}
if (bp) {
brelse(bp);
}
out:
if (ip) {
if (ip != target) {
iput(ip);
/*
* Relock target and make sure it has not gone away
* while it was unlocked.
*/
ilock(target);
if ((error == 0) && (target->i_nlink == 0)) {
error = ENOENT;
}
}
}
/*
* unserialize
*/
if (serialize_flag & RENAME_WAITING) {
wakeup((caddr_t) &serialize_flag);
}
serialize_flag = 0;
return (error);
}