Files
seta75D 2e8a93c394 Init
2021-10-11 18:20:23 -03:00

1580 lines
40 KiB
C

#ident "@(#)ufs_dir.c 1.1 92/07/30 SMI"
/*
* Copyright (c) 1987 by Sun Microsystems, Inc.
*/
/*
* Directory manipulation routines.
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/user.h>
#include <sys/vfs.h>
#include <sys/vfs_stat.h>
#include <sys/vnode.h>
#include <sys/buf.h>
#include <sys/uio.h>
#include <sys/dnlc.h>
#include <ufs/inode.h>
#include <ufs/fs.h>
#include <ufs/mount.h>
#include <ufs/fsdir.h>
#ifdef QUOTA
#include <ufs/quota.h>
#endif
#include <vm/seg.h>
/*
* 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))
int dirchk = 0;
/*
* 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;
{
struct fbuf *fbp = NULL; /* 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 addr's buffer */
int numdirpasses; /* strategy for directory search */
off_t endsearch; /* offset to end directory search */
int namlen = strlen(namep); /* length of name */
off_t offset;
int err;
register int i;
/*
* Check accessiblity of directory.
*/
if ((dp->i_mode & IFMT) != IFDIR) {
return (ENOTDIR);
}
if (err = iaccess(dp, IEXEC))
return (err);
/*
* Check the directory name lookup cache.
*/
vp = dnlc_lookup(ITOV(dp), namep, NOCRED);
if (vp) {
VN_HOLD(vp);
*ipp = VTOI(vp);
ilock(*ipp);
#if defined(DEBUG_INODES)
printinode("dirlook: cache hit", *ipp);
#endif
return (0);
}
VFS_RECORD(ITOV(dp)->v_vfsp, VS_LOOKUP, VS_MISS);
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) {
err = blkatoff(dp, offset, (char **)0, &fbp);
if (err) {
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 (fbp != NULL)
fbrelse(fbp, S_OTHER);
err = blkatoff(dp, offset, (char **)0, &fbp);
if (err) {
goto bad;
}
entryoffsetinblock = 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 *)(fbp->fb_addr + entryoffsetinblock);
if (ep->d_reclen == 0 ||
dirchk && dirmangled(dp, ep, entryoffsetinblock, offset)) {
i = DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1));
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 &&
*namep == *ep->d_name && /* fast chk 1st chr */
bcmp(namep, ep->d_name, (int)ep->d_namlen) == 0) {
u_long ep_ino;
/*
* We have to release the fbp early ehere to avoid
* a possible deadlock situation where we have the
* fbp and want the directory inode and someone doing
* a direnter has the directory inode and wants the
* fbp. XXX - is this still needed?
*/
ep_ino = ep->d_ino;
fbrelse(fbp, S_OTHER);
fbp = NULL;
dp->i_diroff = offset;
if (namlen == 2 && namep[0] == '.' && namep[1] == '.') {
iunlock(dp); /* race to get inode */
err = iget(dp->i_dev, dp->i_fs, ep_ino, ipp);
if (err) {
goto bad2;
}
} else if (dp->i_number == ep_ino) {
VN_HOLD(ITOV(dp)); /* want ourself, "." */
*ipp = dp;
} else {
err = iget(dp->i_dev, dp->i_fs, ep_ino, ipp);
iunlock(dp);
if (err) {
goto bad2;
}
}
ip = *ipp;
dnlc_enter(ITOV(dp), namep, ITOV(ip), NOCRED);
#if defined(DEBUG_INODES)
printinode("dirlook: search hit", *ipp);
#endif
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;
}
err = ENOENT;
bad:
iunlock(dp);
bad2:
if (fbp)
fbrelse(fbp, S_OTHER);
return (err);
}
/*
* If "dircheckforname" fails to find an entry with the given name, this
* structure holds state for "direnter" as to where there is space to put
* an entry with that name.
* If "dircheckforname" finds an entry with the given name, this structure
* holds state for "dirrename" and "dirremove" as to where the entry is.
* "status" indicates what "dircheckforname" found:
* NONE name not found, large enough free slot not found,
* can't make large enough free slot by compacting entries
* COMPACT name not found, large enough free slot not found,
* can make large enough free slot by compacting entries
* FOUND name not found, large enough free slot found
* EXIST name found
* If "dircheckforname" fails due to an error, this structure is not filled in.
*
* After "dircheckforname" succeeds the values are:
* status offset size fbp, 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
*
* "endoff" is set to 0 if the an entry with the given name is found, or if no
* free slot could be found or made; this means that the directory should not
* be truncated. If the entry was found, the search terminates so
* "dircheckforname" didn't find out where the last valid entry in the
* directory was, so it doesn't know where to cut the directory off; if no free
* slot could be found or made, the directory has to be extended to make room
* for the new entry, so there's nothing to cut off.
* Otherwise, "endoff" is set to the larger of the offset of the last
* non-empty entry in the directory, or the offset at which the new entry will
* be placed, whichever is larger. This is used by "diraddentry"; if a new
* entry is to be added to the directory, any complete directory blocks at the
* end of the directory that contain no non-empty entries are lopped off the
* end, thus shrinking the directory dynamically.
*
* On success, "dirprepareentry" makes "fbp" and "ep" valid.
*/
struct slot {
enum {NONE, COMPACT, FOUND, EXIST} status;
off_t offset; /* offset of area with free space */
int size; /* size of area at slotoffset */
struct fbuf *fbp; /* dir buf where slot is */
struct direct *ep; /* pointer to slot */
off_t endoff; /* last useful location found in search */
};
/*
* 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 err; /* error number */
register char *s;
/*
* Don't allow '/' characters in pathname components
*/
for (s = namep, namlen = 0; *s; s++, namlen++)
if (*s == '/')
return (EACCES);
if (namlen == 0)
panic("direnter");
/*
* If name is "." or ".." then if this is a create look it up
* and return EEXIST. Rename or link TO "." or ".." is forbidden.
*/
if (namep[0] == '.' &&
(namlen == 1 || (namlen == 2 && namep[1] == '.'))) {
if (op == DE_RENAME) {
return (ENOTEMPTY);
}
if (ipp) {
if (err = dirlook(tdp, namep, ipp))
return (err);
}
return (EEXIST);
}
slot.status = NONE;
slot.fbp = NULL;
/*
* Lock the directory in which we are trying to make the new entry.
*/
ilock(tdp);
/*
* For link and rename lock the source 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);
iunlock(tdp);
return (ENOENT);
}
if (sip->i_nlink == MAXLINK) {
iunlock(sip);
iunlock(tdp);
return (EMLINK);
}
sip->i_nlink++;
sip->i_flag |= ICHG;
iupdat(sip, 1);
iunlock(sip);
}
/*
* Check accessiblity of directory.
*/
if ((tdp->i_mode & IFMT) != IFDIR) {
err = ENOTDIR;
goto out;
}
/*
* If target directory has not been removed, then we can consider
* allowing file to be created.
*/
if (tdp->i_nlink == 0) {
err = ENOENT;
goto out;
}
/*
* Execute access is required to search the directory.
*/
if (err = 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 (err = iaccess(sip, IWRITE))
goto out;
if (err = dircheckpath(sip, tdp))
goto out;
}
/*
* Search for the entry.
*/
err = dircheckforname(tdp, namep, namlen, &slot, &tip);
if (err) {
goto out;
}
if (tip) {
switch (op) {
case DE_CREATE:
if (ipp) {
*ipp = tip;
err = EEXIST;
} else {
iput(tip);
}
break;
case DE_RENAME:
err = dirrename(sdp, sip, tdp, namep,
namlen, tip, &slot);
iput(tip);
break;
case DE_LINK:
/*
* Can't link to an existing file
*/
iput(tip);
err = EEXIST;
break;
}
} else {
/*
* The entry does not exist. Check write permission in
* directory to see if entry can be created.
*/
if (err = iaccess(tdp, IWRITE)) {
goto out;
}
if (op == DE_CREATE) {
/*
* make a new inode and directory as required
*/
err = dirmakeinode(tdp, &sip, vap);
if (err) {
goto out;
}
}
err = diraddentry(tdp, namep, namlen, &slot, sip, sdp);
if (err) {
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.fbp)
fbrelse(slot.fbp, S_OTHER);
if (err && (op != DE_CREATE)) {
/*
* Undo bumped link count
*/
sip->i_nlink--;
sip->i_flag |= ICHG;
}
iunlock(tdp);
return (err);
}
/*
* 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.
*/
static int
dircheckforname(tdp, namep, namlen, slotp, ipp)
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 **ipp; /* return inode if we find one */
{
int dirsize; /* size of the directory */
struct fbuf *fbp; /* pointer to directory block */
register int entryoffsetinblk; /* offset of ep in fbp's buffer */
int slotfreespace; /* free space in block */
register struct direct *ep; /* directory entry */
register off_t offset; /* offset in the directory */
register off_t last_offset; /* last offset */
off_t enduseful; /* pointer past last used dir slot */
int i; /* length of mangled entry */
int needed;
int err;
fbp = NULL;
entryoffsetinblk = 0;
needed = LDIRSIZ(namlen);
/*
* No point in using i_diroff since we must search whole directory
*/
dirsize = roundup(tdp->i_size, DIRBLKSIZ);
enduseful = 0;
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 (fbp != NULL)
fbrelse(fbp, S_OTHER);
err = blkatoff(tdp, offset, (char **)0, &fbp);
if (err) {
return (err);
}
entryoffsetinblk = 0;
}
/*
* If still looking for a slot, and at a DIRBLKSIZ
* boundary, have to start looking for free space
* again.
*/
if (slotp->status == NONE &&
(entryoffsetinblk&(DIRBLKSIZ-1)) == 0) {
slotp->offset = -1;
slotfreespace = 0;
}
/*
* Get pointer to next entry.
* Since we are going to do some entry manipulation
* we call dirmangled to do more thorough checks.
*/
ep = (struct direct *)(fbp->fb_addr + entryoffsetinblk);
if (ep->d_reclen == 0 ||
dirmangled(tdp, ep, entryoffsetinblk, offset)) {
i = DIRBLKSIZ - (entryoffsetinblk & (DIRBLKSIZ - 1));
offset += i;
entryoffsetinblk += 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 (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 &&
*namep == *ep->d_name && /* fast chk 1st char */
bcmp(namep, ep->d_name, namlen) == 0) {
tdp->i_diroff = offset;
if (tdp->i_number == ep->d_ino) {
*ipp = tdp; /* we want ourself, ie "." */
VN_HOLD(ITOV(tdp));
} else {
err = iget(tdp->i_dev, tdp->i_fs,
ep->d_ino, ipp);
if (err) {
fbrelse(fbp, S_OTHER);
return (err);
}
}
slotp->status = EXIST;
slotp->offset = offset;
slotp->size = offset - last_offset;
slotp->fbp = fbp;
slotp->ep = ep;
slotp->endoff = 0;
return (0);
}
last_offset = offset;
offset += ep->d_reclen;
entryoffsetinblk += ep->d_reclen;
if (ep->d_ino)
enduseful = offset;
}
if (fbp) {
fbrelse(fbp, S_OTHER);
}
if (slotp->status == NONE) {
/*
* We didn't find a slot; the new directory entry should be put
* at the end of the directory. Return an indication of where
* this is, and set "endoff" to zero; since we're going to have
* to extend the directory, we're certainly not going to
* trucate it.
*/
slotp->offset = dirsize;
slotp->size = DIRBLKSIZ;
slotp->endoff = 0;
} else {
/*
* We found a slot, and will return an indication of where that
* slot is, as any new directory entry will be put there.
* Since that slot will become a useful entry, if the last
* useful entry we found was before this one, update the offset
* of the last useful entry.
*/
if (enduseful < slotp->offset + slotp->size)
enduseful = slotp->offset + slotp->size;
slotp->endoff = roundup(enduseful, DIRBLKSIZ);
}
*ipp = (struct inode *)NULL;
return (0);
}
/*
* Rename the entry in the directory tdp so that
* it points to sip instead of tip.
*/
/*ARGSUSED*/
static
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 err;
int doingdirectory;
/*
* 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 */
/*
* Short circuit rename (foo, foo).
*/
if (sip->i_number == tip->i_number)
return (ESAME); /* special error code */
/*
* Must have write permission to rewrite target entry.
*/
if (err = iaccess(tdp, IWRITE)) {
return (err);
}
/*
* 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 ((tdp->i_mode & ISVTX) && u.u_uid != 0 &&
u.u_uid != tdp->i_uid && tip->i_uid != u.u_uid)
return (EPERM);
/*
* 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.
*/
doingdirectory = ((sip->i_mode & IFMT) == IFDIR);
if ((tip->i_mode & IFMT) == IFDIR) {
if (!doingdirectory)
return (ENOTDIR);
if (!dirempty(tip, tdp->i_number) || (tip->i_nlink > 2))
return (ENOTEMPTY);
} else if (doingdirectory) {
return (ENOTDIR);
}
/*
* 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;
dnlc_enter(ITOV(tdp), namep, ITOV(sip), NOCRED);
err = fbwrite(slotp->fbp, tdp);
slotp->fbp = NULL;
if (err)
return (err);
tdp->i_flag |= 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--;
tip->i_flag |= ICHG;
if (doingdirectory) {
/*
* Decrement target link count once more if it was a directory.
*/
if (--tip->i_nlink != 0) {
panic("direnter: target directory link count");
}
(void) 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.
* Unconditionally decrement the link count in the new parent;
* If the parent is the same, we decrement the link count,
* since the original directory is going away. If the
* new parent is different, dirfixdotdot() will bump the
* link count back.
*/
tdp->i_nlink--;
tdp->i_flag |= ICHG;
if (sdp != tdp) {
err = dirfixdotdot(sip, sdp, tdp);
if (err) {
return (err);
}
}
}
return (0);
}
/*
* 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.
*/
static
dirfixdotdot(dp, opdp, npdp)
register struct inode *dp; /* child directory */
register struct inode *opdp; /* old parent directory */
register struct inode *npdp; /* new parent directory */
{
struct fbuf *fbp;
struct dirtemplate *dirp;
int err;
ilock(dp);
/*
* check whether this is an ex-directory
*/
if ((dp->i_nlink == 0) || (dp->i_size < sizeof (struct dirtemplate))) {
iunlock(dp);
return (0);
}
err = blkatoff(dp, (off_t)0, (char **)&dirp, &fbp);
if (err) {
goto bad;
}
if (dirp->dotdot_ino == npdp->i_number) { /* just a no-op */
goto bad;
}
if (dirp->dotdot_namlen != 2 ||
dirp->dotdot_name[0] != '.' ||
dirp->dotdot_name[1] != '.') {
dirbad(dp, "mangled .. entry", (off_t)0);
err = EINVAL;
goto bad;
}
/*
* Increment the link count in the new parent inode and force it out.
*/
npdp->i_nlink++;
npdp->i_flag |= 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);
err = fbwrite(fbp, dp);
fbp = NULL;
if (err)
goto bad;
iunlock(dp);
/*
* Decrement the link count of the old parent inode and force it out.
* If opdp is NULL, then this is a new directory link; it has no
* parent, so we need not do anything.
*/
if (opdp != NULL) {
ilock(opdp);
if (opdp->i_nlink != 0) {
opdp->i_nlink--;
opdp->i_flag |= ICHG;
iupdat(opdp, 1);
}
iunlock(opdp);
}
return (0);
bad:
if (fbp)
fbrelse(fbp, S_OTHER);
iunlock(dp);
return (err);
}
/*
* Enter the file sip in the directory tdp with name namep.
*/
static
diraddentry(tdp, namep, namlen, slotp, sip, sdp)
struct inode *tdp;
char *namep;
int namlen;
struct slot *slotp;
struct inode *sip;
struct inode *sdp;
{
int err;
char *strncpy();
/*
* Prepare a new entry. If the caller has not supplied an
* existing inode, make a new one.
*/
err = dirprepareentry(tdp, slotp);
if (err) {
return (err);
}
/*
* 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) {
err = EXDEV;
goto bad;
}
if ((sip->i_mode & IFMT) == IFDIR) {
err = dirfixdotdot(sip, sdp, tdp);
if (err) {
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;
dnlc_enter(ITOV(tdp), namep, ITOV(sip), NOCRED);
/*
* Write out the directory entry.
*/
err = fbwrite(slotp->fbp, tdp);
slotp->fbp = NULL;
if (err)
return (err); /* XXX - already fixed dotdot? */
/*
* Mark the directory inode to reflect the changes.
* Truncate the directory to chop off blocks of empty entries.
*/
tdp->i_flag |= IUPD|ICHG;
tdp->i_diroff = 0;
if (slotp->endoff && slotp->endoff < tdp->i_size)
(void) itrunc(tdp, (u_long)slotp->endoff);
return (0);
bad:
/*
* Clear out entry prepared by dirprepareent.
*/
slotp->ep->d_ino = 0;
(void) fbwrite(slotp->fbp, tdp); /* XXX - is this right? */
slotp->fbp = NULL;
return (err);
}
/*
* Prepare a directory slot to receive an entry.
*/
static
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;
register int loc;
register struct direct *ep, *nep;
char *dirbuf;
off_t entryend;
int err;
/*
* 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.
*/
entryend = slotp->offset + slotp->size;
if (slotp->status == NONE) {
if (slotp->offset & (DIRBLKSIZ - 1))
panic("dirprepareentry: new block");
if (DIRBLKSIZ > dp->i_fs->fs_fsize)
panic("DIRBLKSIZ > fsize");
/*
* Allocate the new block.
*/
err = BMAPALLOC(dp, (daddr_t)lblkno(dp->i_fs, slotp->offset),
(int)(blkoff(dp->i_fs, slotp->offset) + DIRBLKSIZ));
if (err)
return (err);
dp->i_size = entryend;
dp->i_flag |= IUPD|ICHG;
} else if (entryend > dp->i_size) {
/*
* Adjust directory size, if needed. This should never
* push the size past a new multiple of DIRBLKSIZ.
* This is an artifact of the old (4.2BSD) way of initializing
* directory sizes to be less than DIRBLKSIZ.
*/
dp->i_size = roundup(entryend, DIRBLKSIZ);
dp->i_flag |= IUPD|ICHG;
}
/*
* Get the block containing the space for the new directory entry.
*/
err = blkatoff(dp, slotp->offset, (char **)&slotp->ep, &slotp->fbp);
if (err) {
return (err);
}
ep = slotp->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.
*/
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.
*/
dirbuf = (char *)ep;
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, (unsigned)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.
*/
static
dirmakeinode(tdp, ipp, vap)
struct inode *tdp;
struct inode **ipp;
register struct vattr *vap;
{
register enum vtype type;
struct inode *ip;
int imode; /* mode and format as in inode */
ino_t ipref;
int err;
if (vap == (struct vattr *)0) {
panic("dirmakeinode: no attributes");
}
/*
* Allocate a new inode.
*/
type = vap->va_type;
if (type == VDIR) {
ipref = dirpref(tdp->i_fs);
} else {
ipref = tdp->i_number;
}
imode = MAKEIMODE(type, vap->va_mode);
err = ialloc(tdp, ipref, imode, &ip);
if (err)
return (err);
#ifdef QUOTA
if (ip->i_dquot != NULL)
panic("direnter: dquot");
#endif
ip->i_flag |= IACC|IUPD|ICHG;
ip->i_mode = imode;
if (type == VBLK || type == VCHR) {
ip->i_vnode.v_rdev = ip->i_rdev = vap->va_rdev;
}
ip->i_vnode.v_type = type;
if (type == VDIR) {
ip->i_nlink = 2; /* anticipating a call to dirmakedirect */
} else {
ip->i_nlink = 1;
}
ip->i_uid = u.u_uid;
/*
* To determine the group-id of the created file:
* 1) If the gid is set in the attribute list (non-Sun & pre-4.0
* clients are not likely to set the gid), then use it if
* the process is super-user, belongs to the target group,
* or the group is the same as the parent directory.
* 2) If the filesystem was not mounted with the Old-BSD-compatible
* GRPID option, and the directory's set-gid bit is clear,
* then use the process's gid.
* 3) Otherwise, set the group-id to the gid of the parent directory.
*/
if ((vap->va_gid != (gid_t)-1) &&
((u.u_uid == 0) || (vap->va_gid == tdp->i_gid) ||
groupmember((int)vap->va_gid))) {
ip->i_gid = vap->va_gid;
} else if (!(tdp->i_vnode.v_vfsp->vfs_flag & VFS_GRPID) &&
!(tdp->i_mode & ISGID)) {
ip->i_gid = u.u_gid;
} else
ip->i_gid = tdp->i_gid;
/*
* If the user is neither super-user nor a member of the file's
* new group, clear the file's set-gid bit.
*/
if ((u.u_uid != 0) &&
(ip->i_mode & ISGID) && !groupmember((int)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.
*/
if (type == VDIR) {
err = dirmakedirect(ip, tdp);
}
iupdat(ip, 1);
if (err) {
ip->i_nlink = 0;
ip->i_flag |= ICHG;
iput(ip);
} else {
iunlock(ip);
*ipp = ip;
}
return (err);
}
/*
* Make an empty directory for inode ip in dp.
*/
static
dirmakedirect(ip, dp)
register struct inode *ip; /* new directory */
register struct inode *dp; /* parent directory */
{
register struct dirtemplate *dirp;
struct fbuf *fbp;
int err;
/*
* Allocate space for the directory we're creating.
*/
err = BMAPALLOC(ip, (daddr_t)0, DIRBLKSIZ);
if (err)
return (err);
if (DIRBLKSIZ > ip->i_fs->fs_fsize)
panic("DIRBLKSIZ > fsize");
ip->i_size = DIRBLKSIZ;
ip->i_flag |= IUPD|ICHG;
/*
* Update the tdp link count and write out the change.
* This reflects the ".." entry we'll soon write.
*/
dp->i_nlink++;
dp->i_flag |= ICHG;
iupdat(dp, 1);
/*
* Initialize directory with "."
* and ".." from static template.
*/
err = fbread(ITOV(ip), 0, (u_int)ip->i_fs->fs_fsize, S_OTHER, &fbp);
if (err)
return (err);
dirp = (struct dirtemplate *)fbp->fb_addr;
/*
* Now initialize the directory we're creating
* with the "." and ".." entries.
*/
*dirp = mastertemplate; /* structure assignment */
dirp->dot_ino = ip->i_number;
dirp->dotdot_ino = dp->i_number;
err = fbwrite(fbp, ip);
return (err);
}
/*
* 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;
struct slot slot;
int err = 0;
namlen = strlen(namep);
if (namlen == 0)
panic("dirremove");
/*
* return error when removing . and ..
*/
if (namep[0] == '.') {
if (namlen == 1)
return (EINVAL);
else if (namlen == 2 && namep[1] == '.')
return (ENOTEMPTY);
}
ip = NULL;
slot.fbp = NULL;
ilock(dp);
/*
* Check accessiblity of directory.
*/
if ((dp->i_mode & IFMT) != IFDIR) {
err = 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 (err = iaccess(dp, IEXEC|IWRITE)) {
goto out;
}
slot.status = FOUND; /* don't need to look for empty slot */
err = dircheckforname(dp, namep, namlen, &slot, &ip);
if (err) {
goto out;
}
if (ip == (struct inode *)0) {
err = ENOENT;
goto out;
}
if (oip && oip != ip) {
err = ENOENT;
goto out;
}
/*
* There used to be a check here to make sure you are not removing a
* a mounted on dir. This was no longer correct because iget() does
* not cross mount points anymore so the the i_dev fields in the inodes
* pointed to by ip and dp will never be different. There does need
* to be a check here though, to eliminate the race between mount and
* rmdir (It can also be a race between mount and unlink, if your
* kernel allows you to unlink a directory.)
*/
if (ITOV(ip)->v_vfsmountedhere != (struct vfs *)0) {
err = EBUSY;
goto out;
}
/*
* If the parent directory is "sticky", then the user must
* own the parent directory, or the file in it, otherwise
* he may not delete it (unless he's root). This implements
* append-only directories.
*/
if ((dp->i_mode & ISVTX) && u.u_uid != 0 &&
u.u_uid != dp->i_uid && ip->i_uid != u.u_uid) {
err = EPERM;
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, dp->i_number)) {
err = ENOTEMPTY;
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;
}
err = fbwrite(slot.fbp, dp);
slot.fbp = NULL;
dp->i_flag |= IUPD|ICHG;
ip->i_flag |= ICHG;
if (err)
goto out;
/*
* 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), "..");
(void) itrunc(ip, (u_long)0);
} else {
ip->i_nlink--;
}
}
out:
if (ip)
iput(ip);
if (slot.fbp)
fbrelse(slot.fbp, S_OTHER);
iunlock(dp);
return (err);
}
/*
* 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.
*/
static
blkatoff(ip, offset, res, fbpp)
struct inode *ip;
off_t offset;
char **res;
struct fbuf **fbpp;
{
register struct fs *fs;
struct fbuf *fbp;
daddr_t lbn;
u_int bsize;
int err;
fs = ip->i_fs;
lbn = lblkno(fs, offset);
bsize = blksize(fs, ip, lbn);
err = fbread(ITOV(ip), (u_int)(offset & fs->fs_bmask), bsize, S_OTHER,
&fbp);
if (err) {
*fbpp = (struct fbuf *)NULL;
return (err);
}
if (res)
*res = fbp->fb_addr + blkoff(fs, offset);
*fbpp = fbp;
return (0);
}
/*
* Do consistency checking:
* 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
* if dirchk is on:
* name must be as long as advertised, and null terminated
* NOTE: record length must not be zero (should be checked previously).
*/
static
dirmangled(dp, ep, entryoffsetinblock, offset)
register struct inode *dp;
register struct direct *ep;
int entryoffsetinblock;
off_t offset;
{
register int i;
i = DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1));
if ((ep->d_reclen & 0x3) != 0 || ep->d_reclen > i ||
ep->d_reclen < DIRSIZ(ep) || ep->d_namlen > MAXNAMLEN ||
dirchk && dirbadname(ep->d_name, (int)ep->d_namlen)) {
dirbad(dp, "mangled entry", offset);
return (1);
}
return (0);
}
static
dirbad(ip, how, offset)
struct inode *ip;
char *how;
off_t offset;
{
printf("%s: bad dir ino %d at offset %d: %s\n",
ip->i_fs->fs_fsmnt, ip->i_number, offset, how);
}
static
dirbadname(sp, l)
register char *sp;
register int l;
{
while (l--) { /* check for nulls */
if (*sp++ == '\0') {
return (1);
}
}
return (*sp); /* check for terminating null */
}
/*
* Check if a directory is empty or not.
*
* Using a struct dirtemplate here is not precisely
* what we want, but better than using a struct direct.
*
* N.B.: does not handle corrupted directories.
*/
static
dirempty(ip, parentino)
register struct inode *ip;
ino_t parentino;
{
register off_t off;
struct dirtemplate dbuf;
register struct direct *dp = (struct direct *)&dbuf;
int err, count;
#define MINDIRSIZ (sizeof (struct dirtemplate) / 2)
for (off = 0; off < ip->i_size; off += dp->d_reclen) {
err = 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 (err || count != 0 || 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);
}
#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.
*/
static
dircheckpath(source, target)
struct inode *source, *target;
{
struct fbuf *fbp;
struct dirtemplate *dirp;
register struct inode *ip;
struct inode *tip;
static char serialize_flag = 0;
ino_t dotdotino;
int err = 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;
(void) sleep((caddr_t)&serialize_flag, PINOD);
}
serialize_flag = RENAME_IN_PROGRESS;
ip = target;
if (ip->i_number == source->i_number) {
err = EINVAL;
goto out;
}
if (ip->i_number == ROOTINO) {
goto out;
}
fbp = NULL;
for (;;) {
if (((ip->i_mode & IFMT) != IFDIR) || (ip->i_nlink == 0) ||
(ip->i_size < sizeof (struct dirtemplate))) {
dirbad(ip, "bad size, unlinked or not dir", (off_t)0);
err = ENOTDIR;
break;
}
err = blkatoff(ip, (off_t)0, (char **)&dirp, &fbp);
if (err) {
break;
}
if (dirp->dotdot_namlen != 2 ||
dirp->dotdot_name[0] != '.' ||
dirp->dotdot_name[1] != '.') {
dirbad(ip, "mangled .. entry", (off_t)0);
err = ENOTDIR;
break;
}
dotdotino = dirp->dotdot_ino;
if (dotdotino == source->i_number) {
err = EINVAL;
break;
}
if (dotdotino == ROOTINO) {
break;
}
if (fbp) {
fbrelse(fbp, S_OTHER);
fbp = NULL;
}
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.
*/
err = iget(ip->i_dev, ip->i_fs, dotdotino, &tip);
if (err) {
ip = NULL;
break;
}
ip = tip;
}
if (fbp) {
fbrelse(fbp, S_OTHER);
}
out:
/*
* unserialize before relocking target to avoid a race
*/
if (serialize_flag & RENAME_WAITING) {
wakeup((caddr_t)&serialize_flag);
}
serialize_flag = 0;
if (ip) {
if (ip != target) {
iput(ip);
/*
* Relock target and make sure it has not gone away
* while it was unlocked.
*/
ilock(target);
if ((err == 0) && (target->i_nlink == 0)) {
err = ENOENT;
}
}
}
return (err);
}