1156 lines
27 KiB
C
Executable File
1156 lines
27 KiB
C
Executable File
#pragma ident "@(#)tmp_dir.c 1.29 95/08/29 SMI"
|
|
|
|
/* tmp_dir.c 1.9 90/03/30 SMI */
|
|
|
|
/*
|
|
* Copyright (c) 1989, 1990, 1991 by Sun Microsystems, Inc.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
#include <sys/sysmacros.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/time.h>
|
|
#include <sys/vfs.h>
|
|
#include <sys/vnode.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/cmn_err.h>
|
|
#include <sys/cred.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/debug.h>
|
|
#include <sys/fs/tmpnode.h>
|
|
#include <sys/fs/tmp.h>
|
|
|
|
static int tdircheckpath(struct tmpnode *, struct tmpnode *, struct cred *);
|
|
static int tdirrename(struct tmount *, struct tmpnode *, struct tmpnode *,
|
|
struct tmpnode *, char *, struct tmpnode *, struct tdirent *,
|
|
struct cred *);
|
|
static int tdirfixdotdot(struct tmpnode *, struct tmpnode *, struct tmpnode *);
|
|
static int tdirempty(struct tmpnode *);
|
|
static int tdircheckforname(struct tmpnode *, char *, struct tmpnode **,
|
|
struct tdirent **, struct cred *);
|
|
static int tdirmaketnode(struct tmpnode *, struct tmpnode **,
|
|
struct tmount *, struct vattr *, enum de_op, struct cred *);
|
|
static int tdiraddentry(struct tmpnode *, struct tmpnode *, char *,
|
|
enum de_op, struct tmpnode *, struct tmount *, struct cred *);
|
|
|
|
|
|
#define T_HASH_SIZE 1024 /* must be power of 2 */
|
|
#define T_MUTEX_SIZE 8
|
|
|
|
static struct tdirent *t_hashtable[T_HASH_SIZE];
|
|
static kmutex_t t_hashmutex[T_MUTEX_SIZE];
|
|
|
|
#define T_HASH_INDEX(a) ((a) & (T_HASH_SIZE-1))
|
|
#define T_MUTEX_INDEX(a) ((a) & (T_MUTEX_SIZE-1))
|
|
|
|
void
|
|
tmpfs_hash_init()
|
|
{
|
|
int ix;
|
|
char buf[128];
|
|
|
|
for (ix = 0; ix < T_MUTEX_SIZE; ix++) {
|
|
(void) sprintf(buf, "tmpfs_hash_%d", ix);
|
|
mutex_init(&t_hashmutex[ix], buf, MUTEX_DEFAULT, NULL);
|
|
}
|
|
}
|
|
|
|
static u_int
|
|
tmpfs_hash_key(char *name)
|
|
{
|
|
int hash;
|
|
|
|
hash = *name++;
|
|
while (*name) {
|
|
hash <<= 3;
|
|
hash += *name++;
|
|
}
|
|
return (hash);
|
|
}
|
|
|
|
/*
|
|
* This routine is where the rubber meets the road for identities.
|
|
* If a matching tdirent is found, then refuse to link in the new one.
|
|
*
|
|
* Returns 1 if the new entry was added, otherwise 0.
|
|
*/
|
|
static int
|
|
tmpfs_hash_in(struct tdirent *t)
|
|
{
|
|
u_int index;
|
|
struct tdirent *l;
|
|
struct tdirent **ht;
|
|
kmutex_t *t_hmtx;
|
|
|
|
index = T_HASH_INDEX(t->td_hash);
|
|
ht = &t_hashtable[index];
|
|
t_hmtx = &t_hashmutex[T_MUTEX_INDEX(index)];
|
|
mutex_enter(t_hmtx);
|
|
l = *ht;
|
|
while (l) {
|
|
if ((l->td_hash == t->td_hash) &&
|
|
(l->td_parent == t->td_parent) &&
|
|
(strcmp(l->td_name, t->td_name) == 0)) {
|
|
mutex_exit(t_hmtx);
|
|
return (0);
|
|
} else {
|
|
l = l->td_link;
|
|
}
|
|
}
|
|
|
|
t->td_link = *ht;
|
|
*ht = t;
|
|
mutex_exit(t_hmtx);
|
|
return (1);
|
|
}
|
|
|
|
|
|
/*
|
|
* Remove tdirent *t from the hash list. If the entry is not found,
|
|
* return 0, if it is found (and removed) return 1.
|
|
*/
|
|
static int
|
|
tmpfs_hash_out(struct tdirent *t)
|
|
{
|
|
u_int index;
|
|
struct tdirent **l;
|
|
u_int found;
|
|
kmutex_t *t_hmtx;
|
|
|
|
found = 0;
|
|
index = T_HASH_INDEX(t->td_hash);
|
|
l = &t_hashtable[index];
|
|
t_hmtx = &t_hashmutex[T_MUTEX_INDEX(index)];
|
|
mutex_enter(t_hmtx);
|
|
while (*l) {
|
|
if (*l == t) {
|
|
*l = t->td_link;
|
|
found = 1;
|
|
break;
|
|
}
|
|
l = &(*l)->td_link;
|
|
}
|
|
mutex_exit(t_hmtx);
|
|
return (found);
|
|
}
|
|
|
|
static struct tdirent *
|
|
tmpfs_hash_lookup(char *name, struct tmpnode *parent, u_int hold)
|
|
{
|
|
u_int index;
|
|
struct tdirent *l;
|
|
u_int hash;
|
|
kmutex_t *t_hmtx;
|
|
|
|
hash = tmpfs_hash_key(name);
|
|
index = T_HASH_INDEX(hash);
|
|
|
|
t_hmtx = &t_hashmutex[T_MUTEX_INDEX(index)];
|
|
mutex_enter(t_hmtx);
|
|
l = t_hashtable[index];
|
|
while (l) {
|
|
if ((l->td_hash == hash) &&
|
|
(l->td_parent == parent) &&
|
|
(strcmp(l->td_name, name) == 0)) {
|
|
if (hold) {
|
|
ASSERT(l->td_tmpnode);
|
|
tmpnode_hold(l->td_tmpnode);
|
|
}
|
|
mutex_exit(t_hmtx);
|
|
return (l);
|
|
} else {
|
|
l = l->td_link;
|
|
}
|
|
}
|
|
mutex_exit(t_hmtx);
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Search directory 'parent' for entry 'name'.
|
|
*
|
|
* The calling thread can't hold the write version
|
|
* of the rwlock for the directory being searched
|
|
*
|
|
* 0 is returned on success and *foundtp points
|
|
* to the found tmpnode with its vnode held.
|
|
*/
|
|
int
|
|
tdirlookup(
|
|
struct tmpnode *parent,
|
|
char *name,
|
|
struct tmpnode **foundtp,
|
|
int required_perms,
|
|
struct cred *cred)
|
|
{
|
|
int error;
|
|
struct tdirent *tdp;
|
|
|
|
TMP_PRINT(T_DEBUG, "tdirlookup: parent %x name %s\n",
|
|
parent, name, 0, 0, 0);
|
|
|
|
*foundtp = NULL;
|
|
if (parent->tn_type != VDIR)
|
|
return (ENOTDIR);
|
|
|
|
if ((required_perms != 0) &&
|
|
(error = tmp_taccess(parent, required_perms, cred)))
|
|
return (error);
|
|
|
|
if (*name == '\0') {
|
|
tmpnode_hold(parent);
|
|
*foundtp = parent;
|
|
return (0);
|
|
}
|
|
|
|
tmp_accessed(parent);
|
|
|
|
/*
|
|
* Search the directory for the matching name
|
|
* We need the lock protecting the tn_dir list
|
|
* so that it doesn't change out from underneath us.
|
|
* tmpfs_hash_lookup() will pass back the tmpnode
|
|
* with a hold on it.
|
|
*/
|
|
|
|
rw_enter(&parent->tn_rwlock, RW_READER);
|
|
tdp = tmpfs_hash_lookup(name, parent, 1);
|
|
if (tdp) {
|
|
ASSERT(tdp->td_tmpnode);
|
|
*foundtp = tdp->td_tmpnode;
|
|
TMP_PRINT(T_DIR, "tdirlookup: foundtp %x\n",
|
|
*foundtp, 0, 0, 0, 0);
|
|
rw_exit(&parent->tn_rwlock);
|
|
return (0);
|
|
}
|
|
|
|
TMP_PRINT(T_DIR, "tdirlookup: return ENOENT\n", 0, 0, 0, 0, 0);
|
|
rw_exit(&parent->tn_rwlock);
|
|
return (ENOENT);
|
|
}
|
|
|
|
/*
|
|
* This is the "fake" size of a tdirent structure that is used in
|
|
* assigning offsets to directory entries
|
|
*/
|
|
#define FAKEDIRSIZ 1
|
|
|
|
/*
|
|
* Enter a directory entry for 'name' and 'tp' into directory 'dir'
|
|
*
|
|
* Returns 0 on success.
|
|
*/
|
|
int
|
|
tdirenter(
|
|
struct tmount *tm,
|
|
struct tmpnode *dir, /* target directory to make entry in */
|
|
char *name, /* name of entry */
|
|
enum de_op op, /* entry operation */
|
|
struct tmpnode *fromparent, /* source directory if rename */
|
|
struct tmpnode *tp, /* source tmpnode, if link/rename */
|
|
struct vattr *va,
|
|
struct tmpnode **tpp, /* return tmpnode, if create/mkdir */
|
|
struct cred *cred)
|
|
{
|
|
struct tdirent *tdp, *tpdp;
|
|
struct tmpnode *found = NULL;
|
|
int namelen;
|
|
int error = 0;
|
|
char *s;
|
|
|
|
/*
|
|
* tn_rwlock is held to serialize direnter and dirdeletes
|
|
*/
|
|
ASSERT(RW_WRITE_HELD(&dir->tn_rwlock));
|
|
ASSERT(dir->tn_type == VDIR);
|
|
|
|
TMP_PRINT(T_DEBUG, "tdirenter: tm %x dir %x name %s tp %x\n",
|
|
tm, dir, name, tp, 0);
|
|
|
|
/*
|
|
* Don't allow '/' characters in pathname component
|
|
* (thus in ufs_direnter()).
|
|
*/
|
|
for (s = name, namelen = 0; *s; s++, namelen++)
|
|
if (*s == '/')
|
|
return (EACCES);
|
|
if (namelen == 0)
|
|
cmn_err(CE_PANIC, "bad namelen");
|
|
|
|
/*
|
|
* If name is "." or ".." then if this is a create look it up
|
|
* and return EEXIST. Rename or link TO "." or ".." is forbidden.
|
|
*/
|
|
if (name[0] == '.' &&
|
|
(namelen == 1 || (namelen == 2 && name[1] == '.'))) {
|
|
if (op == DE_RENAME) {
|
|
return (EINVAL); /* *SIGH* should be ENOTEMPTY */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 (op == DE_LINK || op == DE_RENAME) {
|
|
mutex_enter(&tp->tn_tlock);
|
|
if (tp->tn_nlink == 0) {
|
|
mutex_exit(&tp->tn_tlock);
|
|
return (ENOENT);
|
|
}
|
|
|
|
if (tp->tn_nlink == MAXLINK) {
|
|
mutex_exit(&tp->tn_tlock);
|
|
return (EMLINK);
|
|
}
|
|
tp->tn_flags |= TCHG;
|
|
tp->tn_nlink++;
|
|
mutex_exit(&tp->tn_tlock);
|
|
}
|
|
|
|
/*
|
|
* This might be a "dangling detached directory".
|
|
* it could have been removed, but a reference
|
|
* to it kept in u_cwd. don't bother searching
|
|
* it, and with any luck the user will get tired
|
|
* of dealing with us and cd to some absolute
|
|
* pathway. *sigh*, thus in ufs, too.
|
|
*/
|
|
mutex_enter(&dir->tn_tlock);
|
|
if (dir->tn_nlink == 0) {
|
|
mutex_exit(&dir->tn_tlock);
|
|
error = ENOENT;
|
|
goto out;
|
|
}
|
|
mutex_exit(&dir->tn_tlock);
|
|
|
|
/*
|
|
* Execute access is required to search the directory.
|
|
*/
|
|
if (error = tmp_taccess(dir, VEXEC, cred)) {
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* If this is a rename of a directory and the parent is
|
|
* different (".." must be changed), then the source
|
|
* directory must not be in the directory hierarchy
|
|
* above the target, as this would orphan everything
|
|
* below the source directory.
|
|
*/
|
|
if (op == DE_RENAME) {
|
|
if (tp == dir) {
|
|
error = EINVAL;
|
|
goto out;
|
|
}
|
|
if (tp->tn_type == VDIR) {
|
|
if ((fromparent != dir) &&
|
|
(error = tdircheckpath(tp, dir, cred))) {
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Search for the entry. Return "found" if it exists.
|
|
*/
|
|
tdp = NULL;
|
|
error = tdircheckforname(dir, name, &found, &tdp, cred);
|
|
if (error != 0 && error != ENOENT) {
|
|
goto out;
|
|
}
|
|
|
|
if (found) {
|
|
switch (op) {
|
|
case DE_CREATE:
|
|
case DE_MKDIR:
|
|
if (tpp) {
|
|
*tpp = found;
|
|
error = EEXIST;
|
|
} else {
|
|
tmpnode_rele(found);
|
|
}
|
|
break;
|
|
|
|
case DE_RENAME:
|
|
error = tdirrename(tm, fromparent, tp,
|
|
dir, name, found, tdp, cred);
|
|
tmpnode_rele(found);
|
|
break;
|
|
|
|
case DE_LINK:
|
|
/*
|
|
* Can't link to an existing file.
|
|
*/
|
|
error = EEXIST;
|
|
tmpnode_rele(found);
|
|
break;
|
|
}
|
|
} else {
|
|
|
|
/*
|
|
* The entry does not exist. Check write permission in
|
|
* directory to see if entry can be created.
|
|
*/
|
|
if (error = tmp_taccess(dir, VWRITE, cred))
|
|
goto out;
|
|
if (op == DE_CREATE || op == DE_MKDIR) {
|
|
/*
|
|
* Make new tmpnode and directory entry as required.
|
|
*/
|
|
if (error = tdirmaketnode(dir, &tp, tm, va, op, cred))
|
|
goto out;
|
|
}
|
|
if (error =
|
|
tdiraddentry(dir, tp, name, op, fromparent, tm, cred)) {
|
|
if (op == DE_CREATE || op == DE_MKDIR) {
|
|
/*
|
|
* Unmake the inode we just made.
|
|
*/
|
|
rw_enter(&tp->tn_rwlock, RW_WRITER);
|
|
if ((tp->tn_type) == VDIR) {
|
|
if (tdp)
|
|
tdp->td_tmpnode->tn_nlink--;
|
|
/*
|
|
* cleanup allocs made by tdirinit()
|
|
*/
|
|
tdirtrunc(tm, tp, cred);
|
|
}
|
|
tp->tn_nlink = 0;
|
|
mutex_enter(&tp->tn_tlock);
|
|
tp->tn_flags |= TCHG;
|
|
mutex_exit(&tp->tn_tlock);
|
|
rw_exit(&tp->tn_rwlock);
|
|
tmpnode_rele(tp);
|
|
tp = NULL;
|
|
}
|
|
} else if (tpp) {
|
|
*tpp = tp;
|
|
} else if (op == DE_CREATE || op == DE_MKDIR) {
|
|
tmpnode_rele(tp);
|
|
}
|
|
}
|
|
out:
|
|
if (error && (op == DE_LINK || op == DE_RENAME)) {
|
|
/*
|
|
* Undo bumped link count.
|
|
*/
|
|
DECR_COUNT(&tp->tn_nlink, &tp->tn_tlock);
|
|
mutex_enter(&tp->tn_tlock);
|
|
tp->tn_flags |= TCHG;
|
|
mutex_exit(&tp->tn_tlock);
|
|
}
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Delete entry tp of name "nm" from dir.
|
|
* Free dir entry space and decrement link count on tmpnode(s).
|
|
*
|
|
* Return 0 on success.
|
|
*/
|
|
int
|
|
tdirdelete(
|
|
register struct tmount *tm,
|
|
register struct tmpnode *dir,
|
|
register struct tmpnode *tp,
|
|
register char *nm,
|
|
enum dr_op op,
|
|
struct cred *cred)
|
|
{
|
|
register struct tdirent *tpdp, **tpdpp;
|
|
register int error, namelen;
|
|
|
|
ASSERT(RW_WRITE_HELD(&dir->tn_rwlock));
|
|
ASSERT(RW_WRITE_HELD(&tp->tn_rwlock));
|
|
ASSERT(dir->tn_type == VDIR);
|
|
|
|
TMP_PRINT(T_DEBUG, "tdirdelete: tm %x dir %x tp %x nm %s\n",
|
|
tm, dir, tp, nm, 0);
|
|
|
|
namelen = strlen(nm);
|
|
if (namelen == 0)
|
|
cmn_err(CE_PANIC,
|
|
"tdirdelete: NULL strlen for 0x%x", (int)tp);
|
|
|
|
/*
|
|
* return error when removing . and ..
|
|
*/
|
|
if (nm[0] == '.') {
|
|
if (namelen == 1)
|
|
return (EINVAL);
|
|
else if (namelen == 2 && nm[1] == '.')
|
|
return (EEXIST); /* thus in ufs */
|
|
}
|
|
|
|
if (error = tmp_taccess(dir, VEXEC|VWRITE, cred))
|
|
return (error);
|
|
|
|
/*
|
|
* If the parent directory is "sticky", then the user must
|
|
* own the parent directory or the file in it, or else must
|
|
* have permission to write the file. Otherwise it may not
|
|
* be deleted (except by the super-user). Same as ufs_dirremove.
|
|
*/
|
|
if ((dir->tn_mode & S_ISVTX) && cred->cr_uid != 0 &&
|
|
cred->cr_uid != dir->tn_uid && tp->tn_uid != cred->cr_uid &&
|
|
(error = tmp_taccess(tp, VWRITE, cred) != 0))
|
|
return (error);
|
|
|
|
if ((tpdpp = &dir->tn_dir) == NULL)
|
|
return (ENOENT);
|
|
|
|
|
|
tpdp = tmpfs_hash_lookup(nm, dir, 0);
|
|
if (tpdp == NULL) {
|
|
/*
|
|
* If it is gone, some other thread got here first!
|
|
* Return error ENOENT.
|
|
*/
|
|
return (ENOENT);
|
|
}
|
|
|
|
/*
|
|
* If the tmpnode in the tdirent changed, we were probably
|
|
* the victim of a concurrent rename operation. The original
|
|
* is gone, so return that status (same as UFS).
|
|
*/
|
|
if (tp != tpdp->td_tmpnode)
|
|
return (ENOENT);
|
|
|
|
if (tmpfs_hash_out(tpdp) == 0) {
|
|
/*
|
|
* If hash lookup found it then it better be here.
|
|
*/
|
|
|
|
cmn_err(CE_PANIC, "tdirdelete: no entry found\n", tpdp);
|
|
}
|
|
|
|
/*
|
|
* Take tpdp out of the directory list.
|
|
*/
|
|
ASSERT(tpdp->td_next != tpdp);
|
|
ASSERT(tpdp->td_prev != tpdp);
|
|
if (tpdp->td_prev) {
|
|
tpdp->td_prev->td_next = tpdp->td_next;
|
|
}
|
|
if (tpdp->td_next) {
|
|
tpdp->td_next->td_prev = tpdp->td_prev;
|
|
}
|
|
ASSERT(tpdp->td_next != tpdp);
|
|
ASSERT(tpdp->td_prev != tpdp);
|
|
|
|
/*
|
|
* tpdp points to the correct directory entry
|
|
*/
|
|
namelen = strlen(tpdp->td_name) + 1;
|
|
TMP_PRINT((T_DIR | T_ALLOC), "tdirdelete: removing entry for %s\n",
|
|
tpdp->td_name, 0, 0, 0, 0);
|
|
|
|
tmp_memfree(tm, (char *)tpdp, sizeof (struct tdirent) + namelen);
|
|
dir->tn_size -= (sizeof (struct tdirent) + namelen);
|
|
tmp_modified(dir);
|
|
|
|
ASSERT(tp->tn_nlink > 0);
|
|
DECR_COUNT(&tp->tn_nlink, &tp->tn_tlock);
|
|
if (op == DR_RMDIR && tp->tn_type == VDIR) {
|
|
tdirtrunc(tm, tp, cred);
|
|
ASSERT(tp->tn_nlink == 0);
|
|
}
|
|
DECR_COUNT(&tm->tm_direntries, &tm->tm_contents);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* tdirinit is used internally to initialize a directory (dir)
|
|
* with '.' and '..' entries without checking permissions and locking
|
|
*
|
|
* Return 0 on success.
|
|
*/
|
|
/* ARGSUSED3 */
|
|
int
|
|
tdirinit(
|
|
struct tmount *tm,
|
|
struct tmpnode *parent, /* parent of directory to initialize */
|
|
struct tmpnode *dir, /* the new directory */
|
|
struct cred *cred)
|
|
{
|
|
struct tdirent *dot, *dotdot;
|
|
|
|
ASSERT(RW_WRITE_HELD(&dir->tn_rwlock));
|
|
ASSERT(RW_WRITE_HELD(&parent->tn_rwlock));
|
|
ASSERT(dir->tn_type == VDIR);
|
|
|
|
TMP_PRINT(T_DEBUG, "tdirinit: tm %x parent %x\n", tm, parent, 0, 0, 0);
|
|
|
|
/*
|
|
* allocate "." entry
|
|
*/
|
|
TMP_PRINT(T_ALLOC, "tdirinit: allocating tdirent for .\n",
|
|
0, 0, 0, 0, 0);
|
|
dot = (struct tdirent *)tmp_memalloc(tm,
|
|
sizeof (struct tdirent) + sizeof ("."));
|
|
if (dot == NULL)
|
|
return (ENOSPC);
|
|
/*
|
|
* allocate ".." entry
|
|
*/
|
|
TMP_PRINT(T_ALLOC, "tdirinit: allocating tdirent for ..\n",
|
|
0, 0, 0, 0, 0);
|
|
dotdot = (struct tdirent *)tmp_memalloc(tm,
|
|
sizeof (struct tdirent) + sizeof (".."));
|
|
if (dotdot == NULL) {
|
|
TMP_PRINT(T_ALLOC, "tdirinit: freeing entry for .\n",
|
|
0, 0, 0, 0, 0);
|
|
tmp_memfree(tm, (char *)dot,
|
|
(sizeof (struct tdirent) + sizeof (".")));
|
|
return (ENOSPC);
|
|
}
|
|
|
|
/*
|
|
* Initialize the entries
|
|
*/
|
|
dot->td_tmpnode = dir;
|
|
dot->td_offset = 0;
|
|
dot->td_name = (char *)dot + sizeof (struct tdirent);
|
|
dot->td_parent = dir;
|
|
dot->td_hash = tmpfs_hash_key(".");
|
|
strcpy(dot->td_name, ".");
|
|
tmpfs_hash_in(dot);
|
|
|
|
dotdot->td_tmpnode = parent;
|
|
dotdot->td_offset = 1;
|
|
dotdot->td_name = (char *)dotdot + sizeof (struct tdirent);
|
|
dotdot->td_parent = dir;
|
|
dotdot->td_hash = tmpfs_hash_key("..");
|
|
strcpy(dotdot->td_name, "..");
|
|
tmpfs_hash_in(dotdot);
|
|
|
|
/*
|
|
* Account for "." in dir
|
|
*/
|
|
INCR_COUNT(&dir->tn_nlink, &dir->tn_tlock);
|
|
|
|
/*
|
|
* and ".." in parent.
|
|
*/
|
|
INCR_COUNT(&parent->tn_nlink, &parent->tn_tlock);
|
|
|
|
/*
|
|
* Initialize directory entry list.
|
|
*/
|
|
dot->td_next = dotdot;
|
|
dot->td_prev = NULL;
|
|
dotdot->td_next = NULL;
|
|
dotdot->td_prev = dot;
|
|
|
|
dir->tn_dir = dot;
|
|
|
|
mutex_enter(&tm->tm_contents);
|
|
tm->tm_direntries += 2;
|
|
mutex_exit(&tm->tm_contents);
|
|
|
|
dir->tn_size = (2 * sizeof (struct tdirent)) + 5; /* dot + dotdot */
|
|
tmp_modified(dir);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* tdirtrunc is called to remove all directory entries under this directory.
|
|
* The files themselves are removed elsewhere.
|
|
*/
|
|
/* ARGSUSED2 */
|
|
void
|
|
tdirtrunc(struct tmount *tm, struct tmpnode *dir, struct cred *cred)
|
|
{
|
|
register struct tdirent *tdp;
|
|
int namelen;
|
|
|
|
ASSERT(RW_WRITE_HELD(&dir->tn_rwlock));
|
|
ASSERT(dir->tn_type == VDIR);
|
|
|
|
TMP_PRINT(T_DEBUG, "tdirtrunc: tm %x dir %x\n", tm, dir, 0, 0, 0);
|
|
|
|
for (tdp = dir->tn_dir; tdp; tdp = dir->tn_dir) {
|
|
ASSERT(tdp->td_next != tdp);
|
|
ASSERT(tdp->td_prev != tdp);
|
|
ASSERT(tdp->td_tmpnode);
|
|
ASSERT(tdp->td_tmpnode->tn_nlink > 0);
|
|
TMP_PRINT((T_DIR | T_ALLOC),
|
|
"tdirtrunc: deleting entry for %s\n",
|
|
tdp->td_name, 0, 0, 0, 0);
|
|
|
|
dir->tn_dir = tdp->td_next;
|
|
namelen = strlen(tdp->td_name) + 1;
|
|
|
|
DECR_COUNT(&tdp->td_tmpnode->tn_nlink,
|
|
&tdp->td_tmpnode->tn_tlock);
|
|
|
|
if (tmpfs_hash_out(tdp) == 0) {
|
|
cmn_err(CE_PANIC, "tdirtrunc: missing node", tdp);
|
|
}
|
|
|
|
tmp_memfree(tm, (char *)tdp,
|
|
(sizeof (struct tdirent) + namelen));
|
|
dir->tn_size -= (sizeof (struct tdirent) + namelen);
|
|
DECR_COUNT(&tm->tm_direntries, &tm->tm_contents);
|
|
}
|
|
tmp_modified(dir);
|
|
ASSERT(dir->tn_dir == NULL);
|
|
ASSERT(dir->tn_size == 0);
|
|
}
|
|
|
|
/*
|
|
* Check if the source directory is in the path of the target directory.
|
|
* The target directory is locked by the caller.
|
|
*
|
|
* XXX - The source and target's should be different upon entry.
|
|
*/
|
|
static int
|
|
tdircheckpath(
|
|
struct tmpnode *fromtp,
|
|
struct tmpnode *toparent,
|
|
struct cred *cred)
|
|
{
|
|
int error = 0;
|
|
struct tmpnode *dir, *dotdot;
|
|
struct tdirent *tdp;
|
|
|
|
ASSERT(RW_WRITE_HELD(&toparent->tn_rwlock));
|
|
|
|
error = tdircheckforname(toparent, "..", &dotdot, &tdp, cred);
|
|
if (error)
|
|
return (error);
|
|
|
|
if (dotdot == toparent)
|
|
/* root of fs. search trivially satisfied. */
|
|
return (error);
|
|
for (dir = dotdot; dotdot != fromtp; dir = dotdot) {
|
|
error = tdirlookup(dir, "..", &dotdot, VEXEC, cred);
|
|
if (error) {
|
|
tmpnode_rele(dir);
|
|
break;
|
|
}
|
|
|
|
if (dotdot == fromtp) {
|
|
tmpnode_rele(dir);
|
|
tmpnode_rele(dotdot);
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* We're okay if we traverse the directory tree up to
|
|
* the root directory and don't run into the
|
|
* parent directory.
|
|
*/
|
|
if (dir == dotdot) {
|
|
tmpnode_rele(dir);
|
|
tmpnode_rele(dotdot);
|
|
break;
|
|
}
|
|
tmpnode_rele(dir);
|
|
}
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
tdirrename(
|
|
struct tmount *tm,
|
|
struct tmpnode *fromparent, /* parent directory of source */
|
|
struct tmpnode *fromtp, /* source tmpnode */
|
|
struct tmpnode *toparent, /* parent directory of target */
|
|
char *nm, /* entry we are trying to change */
|
|
struct tmpnode *to, /* target tmpnode */
|
|
struct tdirent *where, /* target tmpnode directory entry */
|
|
struct cred *cred) /* credentials */
|
|
{
|
|
int error = 0;
|
|
int doingdirectory;
|
|
|
|
ASSERT(MUTEX_HELD(&tm->tm_renamelck));
|
|
ASSERT(RW_WRITE_HELD(&toparent->tn_rwlock));
|
|
|
|
rw_enter(&fromtp->tn_rwlock, RW_READER);
|
|
rw_enter(&to->tn_rwlock, RW_READER);
|
|
|
|
/*
|
|
* Check that everything is on the same filesystem.
|
|
*/
|
|
if (to->tn_vnode.v_vfsp != toparent->tn_vnode.v_vfsp ||
|
|
to->tn_vnode.v_vfsp != fromtp->tn_vnode.v_vfsp) {
|
|
error = EXDEV;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Short circuit rename of something to itself.
|
|
*/
|
|
if (fromtp == to) {
|
|
error = ESAME; /* special KLUDGE error code */
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Must have write permission to rewrite target entry.
|
|
*/
|
|
if (error = tmp_taccess(fromparent, VWRITE, cred))
|
|
goto out;
|
|
|
|
/*
|
|
* If the parent directory is "sticky", then the user must own
|
|
* either the parent directory or the destination of the rename,
|
|
* or else must have permission to write the destination.
|
|
* Otherwise the destination may not be changed (except by the
|
|
* super-user). This implements append-only directories.
|
|
*/
|
|
if ((toparent->tn_mode & S_ISVTX) && cred->cr_uid != 0 &&
|
|
cred->cr_uid != toparent->tn_uid && cred->cr_uid != to->tn_uid &&
|
|
(error = tmp_taccess(to, VWRITE, cred)))
|
|
goto out;
|
|
|
|
/*
|
|
* 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; in addition it must not
|
|
* be a mount point, and both the source and target must be
|
|
* writable.
|
|
*/
|
|
doingdirectory = (fromtp->tn_type == VDIR);
|
|
if (to->tn_type == VDIR) {
|
|
if (!doingdirectory) {
|
|
error = EISDIR;
|
|
goto out;
|
|
}
|
|
/*
|
|
* vn_vfslock will prevent mounts from using the directory
|
|
* until we are done.
|
|
*/
|
|
if (vn_vfslock(TNTOV(to))) {
|
|
error = EBUSY;
|
|
goto out;
|
|
}
|
|
if (TNTOV(to)->v_vfsmountedhere) {
|
|
vn_vfsunlock(TNTOV(to));
|
|
error = EBUSY;
|
|
goto out;
|
|
}
|
|
|
|
mutex_enter(&to->tn_tlock);
|
|
if (!tdirempty(to) || to->tn_nlink > 2) {
|
|
mutex_exit(&to->tn_tlock);
|
|
vn_vfsunlock(TNTOV(to));
|
|
error = EEXIST; /* SIGH should be ENOTEMPTY */
|
|
goto out;
|
|
}
|
|
mutex_exit(&to->tn_tlock);
|
|
} else if (doingdirectory) {
|
|
error = ENOTDIR;
|
|
goto out;
|
|
}
|
|
|
|
where->td_tmpnode = fromtp;
|
|
mutex_enter(&toparent->tn_tlock);
|
|
toparent->tn_flags |= TUPD|TCHG;
|
|
mutex_exit(&toparent->tn_tlock);
|
|
|
|
/*
|
|
* Upgrade to write lock on "to" (i.e., the target tmpnode).
|
|
*/
|
|
rw_exit(&to->tn_rwlock);
|
|
rw_enter(&to->tn_rwlock, RW_WRITER);
|
|
|
|
/*
|
|
* Decrement the link count of the target tmpnode.
|
|
*/
|
|
mutex_enter(&to->tn_tlock);
|
|
to->tn_nlink--;
|
|
to->tn_flags |= TCHG;
|
|
mutex_exit(&to->tn_tlock);
|
|
|
|
if (doingdirectory) {
|
|
/*
|
|
* The entry for "to" no longer exists so release the vfslock.
|
|
*/
|
|
vn_vfsunlock(TNTOV(to));
|
|
|
|
/*
|
|
* Decrement the target link count and delete all entires.
|
|
*/
|
|
tdirtrunc(tm, to, cred);
|
|
ASSERT(to->tn_nlink == 0);
|
|
|
|
/*
|
|
* Renaming a directory with the parent different
|
|
* requires that ".." 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 (fromparent != toparent)
|
|
error = tdirfixdotdot(fromtp, fromparent, toparent);
|
|
}
|
|
out:
|
|
rw_exit(&to->tn_rwlock);
|
|
rw_exit(&fromtp->tn_rwlock);
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
tdirfixdotdot(
|
|
struct tmpnode *fromtp, /* child directory */
|
|
struct tmpnode *fromparent, /* old parent directory */
|
|
struct tmpnode *toparent) /* new parent directory */
|
|
{
|
|
struct tdirent *dotdot;
|
|
|
|
ASSERT(RW_LOCK_HELD(&toparent->tn_rwlock));
|
|
|
|
/*
|
|
* Increment the link count in the new parent tmpnode
|
|
*/
|
|
mutex_enter(&toparent->tn_tlock);
|
|
toparent->tn_nlink++;
|
|
toparent->tn_flags |= TCHG;
|
|
mutex_exit(&toparent->tn_tlock);
|
|
|
|
dotdot = tmpfs_hash_lookup("..", fromtp, 0);
|
|
|
|
ASSERT(dotdot->td_tmpnode == fromparent);
|
|
dotdot->td_tmpnode = toparent;
|
|
|
|
/*
|
|
* Decrement the link count of the old parent tmpnode.
|
|
* If fromparent is NULL, then this is a new directory link;
|
|
* it has no parent, so we need not do anything.
|
|
*/
|
|
if (fromparent != NULL) {
|
|
mutex_enter(&fromparent->tn_tlock);
|
|
if (fromparent->tn_nlink != 0) {
|
|
fromparent->tn_nlink--;
|
|
fromparent->tn_flags |= TCHG;
|
|
}
|
|
mutex_exit(&fromparent->tn_tlock);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
tdiraddentry(
|
|
struct tmpnode *dir, /* target directory to make entry in */
|
|
struct tmpnode *tp, /* new tmpnode */
|
|
char *name,
|
|
enum de_op op,
|
|
struct tmpnode *fromtp,
|
|
struct tmount *tm,
|
|
struct cred *cred)
|
|
{
|
|
struct tdirent *tdp, *tpdp;
|
|
int error = 0;
|
|
int namelen, alloc_size;
|
|
|
|
/*
|
|
* Check that everything is on the same filesystem.
|
|
*/
|
|
if (tp->tn_vnode.v_vfsp != dir->tn_vnode.v_vfsp) {
|
|
return (EXDEV);
|
|
}
|
|
if ((op == DE_RENAME) && (tp->tn_type == VDIR)) {
|
|
error = tdirfixdotdot(tp, fromtp, dir);
|
|
if (error)
|
|
goto bad;
|
|
}
|
|
|
|
/*
|
|
* Allocate and initialize directory entry
|
|
*/
|
|
namelen = strlen(name) + 1;
|
|
TMP_PRINT(T_ALLOC, "tdiraddentry: allocating tdirent for %s\n",
|
|
name, 0, 0, 0, 0);
|
|
alloc_size = namelen + sizeof (struct tdirent);
|
|
tdp = (struct tdirent *)tmp_memalloc(tm, alloc_size);
|
|
if (tdp == NULL)
|
|
return (ENOSPC);
|
|
|
|
dir->tn_size += alloc_size;
|
|
tdp->td_tmpnode = tp;
|
|
tdp->td_parent = dir;
|
|
|
|
/*
|
|
* The directory entry and its name were allocated sequentially.
|
|
*/
|
|
tdp->td_name = (char *)tdp + sizeof (struct tdirent);
|
|
tdp->td_hash = tmpfs_hash_key(name);
|
|
strcpy(tdp->td_name, name);
|
|
|
|
if (tmpfs_hash_in(tdp) == 0) {
|
|
cmn_err(CE_PANIC, "tdiraddentry: already here", tdp);
|
|
}
|
|
|
|
/*
|
|
* Some utilities expect the size of a directory to remain
|
|
* somewhat static. For example, a routine which unlinks
|
|
* files between calls to readdir(); the size of the
|
|
* directory changes from underneath it and so the real
|
|
* directory offset in bytes is invalid. To circumvent
|
|
* this problem, we initialize a directory entry with an
|
|
* phony offset, and use this offset to determine end of
|
|
* file in tmp_readdir.
|
|
*/
|
|
tpdp = dir->tn_dir;
|
|
/*
|
|
* Install at first empty "slot" in directory list.
|
|
*/
|
|
while (tpdp->td_next != NULL && (tpdp->td_next->td_offset -
|
|
tpdp->td_offset) <= FAKEDIRSIZ) {
|
|
ASSERT(tpdp->td_next != tpdp);
|
|
ASSERT(tpdp->td_prev != tpdp);
|
|
ASSERT(tpdp->td_next->td_offset > tpdp->td_offset);
|
|
tpdp = tpdp->td_next;
|
|
}
|
|
tdp->td_offset = tpdp->td_offset + FAKEDIRSIZ;
|
|
|
|
ASSERT(tpdp->td_next != tpdp);
|
|
ASSERT(tpdp->td_prev != tpdp);
|
|
|
|
tdp->td_next = tpdp->td_next;
|
|
if (tdp->td_next) {
|
|
tdp->td_next->td_prev = tdp;
|
|
}
|
|
tdp->td_prev = tpdp;
|
|
tpdp->td_next = tdp;
|
|
|
|
ASSERT(tdp->td_next != tdp);
|
|
ASSERT(tdp->td_prev != tdp);
|
|
ASSERT(tpdp->td_next != tpdp);
|
|
ASSERT(tpdp->td_prev != tpdp);
|
|
|
|
tmp_modified(dir);
|
|
|
|
INCR_COUNT(&tm->tm_direntries, &tm->tm_contents);
|
|
return (0);
|
|
bad:
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
tdirmaketnode(
|
|
struct tmpnode *dir,
|
|
struct tmpnode **tpp,
|
|
struct tmount *tm,
|
|
struct vattr *va,
|
|
enum de_op op,
|
|
struct cred *cred)
|
|
{
|
|
struct tmpnode *tp;
|
|
enum vtype type;
|
|
int error = 0;
|
|
|
|
ASSERT(va != NULL);
|
|
ASSERT(op == DE_CREATE || op == DE_MKDIR);
|
|
type = va->va_type;
|
|
/*
|
|
ASSERT((va->va_mask & (AT_TYPE|AT_MODE)) == (AT_TYPE|AT_MODE));
|
|
*/
|
|
tp = tmpnode_alloc(tm, va, cred);
|
|
if (tp == NULL)
|
|
return (ENOSPC);
|
|
mutex_enter(&tp->tn_tlock);
|
|
tp->tn_flags |= TACC|TUPD|TCHG;
|
|
mutex_exit(&tp->tn_tlock);
|
|
if (type == VBLK || type == VCHR) {
|
|
tp->tn_vnode.v_rdev = tp->tn_rdev = va->va_rdev;
|
|
}
|
|
tp->tn_vnode.v_type = type;
|
|
tp->tn_uid = cred->cr_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 ((va->va_mask & AT_GID) &&
|
|
((cred->cr_uid == 0) || (va->va_gid == dir->tn_gid) ||
|
|
groupmember(va->va_gid, cred))) {
|
|
/*
|
|
* XXX - is this only the case when a 4.0 NFS client, or a
|
|
* client derived from that code, makes a call over the wire?
|
|
*/
|
|
tp->tn_gid = va->va_gid;
|
|
} else {
|
|
if (dir->tn_mode & VSGID)
|
|
tp->tn_gid = dir->tn_gid;
|
|
else
|
|
tp->tn_gid = cred->cr_gid;
|
|
}
|
|
/*
|
|
* If we're creating a directory, and the parent directory has the
|
|
* set-GID bit set, set it on the new directory.
|
|
* Otherwise, if the user is neither super-user nor a member of the
|
|
* file's new group, clear the file's set-GID bit.
|
|
*/
|
|
if (dir->tn_mode & VSGID && type == VDIR)
|
|
tp->tn_mode |= VSGID;
|
|
else {
|
|
if ((tp->tn_mode & VSGID) &&
|
|
!groupmember((uid_t)tp->tn_gid, cred) && cred->cr_uid != 0)
|
|
tp->tn_mode &= ~VSGID;
|
|
}
|
|
if (op == DE_MKDIR) {
|
|
rw_enter(&tp->tn_rwlock, RW_WRITER);
|
|
error = tdirinit(tm, dir, tp, cred);
|
|
rw_exit(&tp->tn_rwlock);
|
|
}
|
|
if (error) {
|
|
tmpnode_free(tm, tp, cred);
|
|
} else {
|
|
*tpp = tp;
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
tdirempty(struct tmpnode *dir)
|
|
{
|
|
struct tdirent *dp;
|
|
int i;
|
|
|
|
for (dp = dir->tn_dir, i = 0; dp && i <= 2; dp = dp->td_next, i++)
|
|
continue;
|
|
return (i <= 2);
|
|
}
|
|
|
|
static int
|
|
tdircheckforname(
|
|
struct tmpnode *parent,
|
|
char *name,
|
|
struct tmpnode **foundit,
|
|
struct tdirent **whereitwas,
|
|
struct cred *cred)
|
|
{
|
|
int error;
|
|
struct tdirent *tdp;
|
|
|
|
error = tmp_taccess(parent, VEXEC, cred);
|
|
if (error)
|
|
return (error);
|
|
|
|
tdp = tmpfs_hash_lookup(name, parent, 1);
|
|
if (tdp) {
|
|
ASSERT(tdp->td_tmpnode);
|
|
*foundit = tdp->td_tmpnode;
|
|
*whereitwas = tdp;
|
|
return (0);
|
|
|
|
}
|
|
*foundit = NULL;
|
|
return (ENOENT);
|
|
}
|