#include <sys/kernel.h>
#include <sys/param.h>
#include <sys/caps.h>
#include <sys/proc.h>
#include <sys/stat.h>
#include <sys/systm.h>
#include <sys/vnode.h>
#include <sys/vmmeter.h>
#include <sys/malloc.h>
#include <vm/vm.h>
#include <vm/vm_object.h>
#include <vm/vm_page.h>
#include <vm/vm_pager.h>
#include <vm/vm_extern.h>
#include <vm/vm_pageout.h>
#include <vm/vm_page2.h>
#include <vfs/tmpfs/tmpfs.h>
#include <vfs/tmpfs/tmpfs_vnops.h>
static ino_t tmpfs_fetch_ino(struct tmpfs_mount *);
static int tmpfs_dirtree_compare(struct tmpfs_dirent *a,
struct tmpfs_dirent *b);
RB_GENERATE(tmpfs_dirtree, tmpfs_dirent, rb_node, tmpfs_dirtree_compare);
static int tmpfs_dirtree_compare_cookie(struct tmpfs_dirent *a,
struct tmpfs_dirent *b);
RB_GENERATE(tmpfs_dirtree_cookie, tmpfs_dirent,
rb_cookienode, tmpfs_dirtree_compare_cookie);
int
tmpfs_alloc_node(struct tmpfs_mount *tmp, enum vtype type,
uid_t uid, gid_t gid, mode_t mode,
char *target, int rmajor, int rminor,
struct tmpfs_node **node)
{
struct tmpfs_node *nnode;
struct timespec ts;
dev_t rdev;
KKASSERT(IFF(type == VLNK, target != NULL));
KKASSERT(IFF(type == VBLK || type == VCHR, rmajor != VNOVAL));
if (tmp->tm_nodes_inuse >= tmp->tm_nodes_max)
return (ENOSPC);
nnode = kmalloc_obj(sizeof(struct tmpfs_node), tmp->tm_node_zone,
M_WAITOK | M_ZERO | M_NULLOK);
if (nnode == NULL)
return (ENOSPC);
tmpfs_node_init(nnode);
nnode->tn_type = type;
vfs_timestamp(&ts);
nnode->tn_ctime = nnode->tn_mtime = nnode->tn_atime
= ts.tv_sec;
nnode->tn_ctimensec = nnode->tn_mtimensec = nnode->tn_atimensec
= ts.tv_nsec;
nnode->tn_uid = uid;
nnode->tn_gid = gid;
nnode->tn_mode = mode;
nnode->tn_id = tmpfs_fetch_ino(tmp);
nnode->tn_advlock.init_done = 0;
KKASSERT(nnode->tn_links == 0);
switch (nnode->tn_type) {
case VBLK:
case VCHR:
rdev = makeudev(rmajor, rminor);
if (rdev == NOUDEV) {
tmpfs_node_uninit(nnode);
kfree_obj(nnode, tmp->tm_node_zone);
return(EINVAL);
}
nnode->tn_rdev = rdev;
break;
case VDIR:
RB_INIT(&nnode->tn_dir.tn_dirtree);
RB_INIT(&nnode->tn_dir.tn_cookietree);
nnode->tn_dir.tn_parent = NULL;
nnode->tn_size = 0;
break;
case VFIFO:
case VSOCK:
break;
case VLNK:
nnode->tn_size = strlen(target);
nnode->tn_link = kmalloc(nnode->tn_size + 1, tmp->tm_name_zone,
M_WAITOK | M_NULLOK);
if (nnode->tn_link == NULL) {
tmpfs_node_uninit(nnode);
kfree_obj(nnode, tmp->tm_node_zone);
return (ENOSPC);
}
bcopy(target, nnode->tn_link, nnode->tn_size);
nnode->tn_link[nnode->tn_size] = '\0';
break;
case VREG:
nnode->tn_reg.tn_aobj = swap_pager_alloc(NULL, 0,
VM_PROT_DEFAULT, 0);
nnode->tn_reg.tn_aobj_pages = 0;
nnode->tn_size = 0;
vm_object_set_flag(nnode->tn_reg.tn_aobj, OBJ_NOPAGEIN);
break;
default:
panic("tmpfs_alloc_node: type %p %d", nnode, (int)nnode->tn_type);
}
TMPFS_NODE_LOCK(nnode);
TMPFS_LOCK(tmp);
LIST_INSERT_HEAD(&tmp->tm_nodes_used, nnode, tn_entries);
tmp->tm_nodes_inuse++;
TMPFS_UNLOCK(tmp);
TMPFS_NODE_UNLOCK(nnode);
*node = nnode;
return 0;
}
void
tmpfs_free_node(struct tmpfs_mount *tmp, struct tmpfs_node *node)
{
vm_pindex_t pages = 0;
#ifdef INVARIANTS
TMPFS_ASSERT_ELOCKED(node);
KKASSERT(node->tn_vnode == NULL);
#endif
TMPFS_LOCK(tmp);
LIST_REMOVE(node, tn_entries);
tmp->tm_nodes_inuse--;
TMPFS_UNLOCK(tmp);
TMPFS_NODE_UNLOCK(node);
switch (node->tn_type) {
case VNON:
case VBLK:
case VCHR:
break;
case VDIR:
node->tn_size = 0;
KKASSERT(node->tn_dir.tn_parent == NULL);
if (node == tmp->tm_root)
tmp->tm_root = NULL;
break;
case VFIFO:
case VSOCK:
break;
case VLNK:
kfree(node->tn_link, tmp->tm_name_zone);
node->tn_link = NULL;
node->tn_size = 0;
break;
case VREG:
if (node->tn_reg.tn_aobj != NULL)
vm_object_deallocate(node->tn_reg.tn_aobj);
node->tn_reg.tn_aobj = NULL;
pages = node->tn_reg.tn_aobj_pages;
break;
default:
panic("tmpfs_free_node: type %p %d", node, (int)node->tn_type);
}
tmpfs_node_uninit(node);
kfree_obj(node, tmp->tm_node_zone);
if (pages)
atomic_add_long(&tmp->tm_pages_used, -(long)pages);
}
int
tmpfs_alloc_dirent(struct tmpfs_mount *tmp, struct tmpfs_node *node,
const char *name, uint16_t len, struct tmpfs_dirent **de)
{
struct tmpfs_dirent *nde;
nde = kmalloc_obj(sizeof(struct tmpfs_dirent),
tmp->tm_dirent_zone, M_WAITOK);
nde->td_name = kmalloc(len + 1, tmp->tm_name_zone, M_WAITOK | M_NULLOK);
if (nde->td_name == NULL) {
kfree_obj(nde, tmp->tm_dirent_zone);
*de = NULL;
return (ENOSPC);
}
nde->td_namelen = len;
bcopy(name, nde->td_name, len);
nde->td_name[len] = '\0';
nde->td_node = node;
atomic_add_int(&node->tn_links, 1);
*de = nde;
return 0;
}
void
tmpfs_free_dirent(struct tmpfs_mount *tmp, struct tmpfs_dirent *de)
{
struct tmpfs_node *node;
node = de->td_node;
KKASSERT(node->tn_links > 0);
atomic_add_int(&node->tn_links, -1);
kfree(de->td_name, tmp->tm_name_zone);
de->td_namelen = 0;
de->td_name = NULL;
de->td_node = NULL;
kfree_obj(de, tmp->tm_dirent_zone);
}
int
tmpfs_alloc_vp(struct mount *mp,
struct tmpfs_node *dnode, struct tmpfs_node *node, int lkflag,
struct vnode **vpp)
{
int error = 0;
struct vnode *vp;
loop:
vp = NULL;
if (node->tn_vnode == NULL) {
error = getnewvnode(VT_TMPFS, mp, &vp,
VLKTIMEOUT, LK_CANRECURSE);
if (error)
goto out;
}
TMPFS_NODE_LOCK(node);
if (node->tn_vnode) {
if (vp) {
vp->v_type = VBAD;
vx_put(vp);
}
vp = node->tn_vnode;
KKASSERT((node->tn_vpstate & TMPFS_VNODE_DOOMED) == 0);
vhold(vp);
TMPFS_NODE_UNLOCK(node);
if (dnode) {
if (vget(vp, (lkflag & ~LK_RETRY) |
LK_NOWAIT |
LK_EXCLUSIVE) != 0)
{
TMPFS_NODE_UNLOCK(dnode);
if (vget(vp, (lkflag & ~LK_RETRY) |
LK_SLEEPFAIL |
LK_EXCLUSIVE) == 0) {
vput(vp);
}
vdrop(vp);
TMPFS_NODE_LOCK_SH(dnode);
return EAGAIN;
}
} else {
if (vget(vp, lkflag | LK_EXCLUSIVE) != 0) {
vdrop(vp);
goto loop;
}
}
if (node->tn_vnode != vp) {
vput(vp);
vdrop(vp);
goto loop;
}
vdrop(vp);
goto out;
}
if (vp == NULL) {
TMPFS_NODE_UNLOCK(node);
goto loop;
}
if (node->tn_vpstate & TMPFS_VNODE_DOOMED) {
TMPFS_NODE_UNLOCK(node);
vp->v_type = VBAD;
vx_put(vp);
error = ENOENT;
goto out;
}
KKASSERT(node->tn_vnode == NULL);
KKASSERT(vp != NULL);
vp->v_data = node;
vp->v_type = node->tn_type;
switch (node->tn_type) {
case VBLK:
case VCHR:
case VSOCK:
break;
case VREG:
vsetflags(vp, VKVABIO);
vinitvmio(vp, node->tn_size, node->tn_blksize, -1);
break;
case VLNK:
break;
case VFIFO:
vp->v_ops = &mp->mnt_vn_fifo_ops;
break;
case VDIR:
break;
default:
panic("tmpfs_alloc_vp: type %p %d", node, (int)node->tn_type);
}
node->tn_vnode = vp;
TMPFS_NODE_UNLOCK(node);
vx_downgrade(vp);
out:
*vpp = vp;
KKASSERT(IFF(error == 0, *vpp != NULL && vn_islocked(*vpp)));
return error;
}
int
tmpfs_alloc_file(struct vnode *dvp, struct vnode **vpp, struct vattr *vap,
struct namecache *ncp, struct ucred *cred, char *target)
{
int error;
struct tmpfs_dirent *de;
struct tmpfs_mount *tmp;
struct tmpfs_node *dnode;
struct tmpfs_node *node;
tmp = VFS_TO_TMPFS(dvp->v_mount);
dnode = VP_TO_TMPFS_DIR(dvp);
*vpp = NULL;
TMPFS_NODE_LOCK(dnode);
KKASSERT(dnode->tn_type == VDIR);
if (dnode != tmp->tm_root && dnode->tn_dir.tn_parent == NULL) {
TMPFS_NODE_UNLOCK(dnode);
return ENOENT;
}
if (vap->va_type == VDIR && dnode->tn_links >= LINK_MAX) {
TMPFS_NODE_UNLOCK(dnode);
return EMLINK;
}
error = tmpfs_alloc_node(tmp, vap->va_type, cred->cr_uid,
dnode->tn_gid, vap->va_mode, target,
vap->va_rmajor, vap->va_rminor, &node);
if (error != 0) {
TMPFS_NODE_UNLOCK(dnode);
return error;
}
TMPFS_NODE_LOCK(node);
error = tmpfs_alloc_dirent(tmp, node, ncp->nc_name, ncp->nc_nlen, &de);
if (error != 0) {
TMPFS_NODE_UNLOCK(dnode);
tmpfs_free_node(tmp, node);
return error;
}
error = tmpfs_alloc_vp(dvp->v_mount, NULL, node, LK_EXCLUSIVE, vpp);
if (error != 0) {
TMPFS_NODE_UNLOCK(dnode);
tmpfs_free_dirent(tmp, de);
tmpfs_free_node(tmp, node);
return error;
}
tmpfs_dir_attach_locked(dnode, de);
TMPFS_NODE_UNLOCK(dnode);
TMPFS_NODE_UNLOCK(node);
return error;
}
void
tmpfs_dir_attach_locked(struct tmpfs_node *dnode, struct tmpfs_dirent *de)
{
struct tmpfs_node *node = de->td_node;
struct tmpfs_dirent *de2;
if (node && node->tn_type == VDIR) {
TMPFS_NODE_LOCK(node);
atomic_add_int(&node->tn_links, 1);
node->tn_status |= TMPFS_NODE_CHANGED;
node->tn_dir.tn_parent = dnode;
atomic_add_int(&dnode->tn_links, 1);
TMPFS_NODE_UNLOCK(node);
}
de2 = RB_INSERT(tmpfs_dirtree, &dnode->tn_dir.tn_dirtree, de);
KASSERT(de2 == NULL,
("tmpfs_dir_attach_lockedA: duplicate insertion of %p, has %p\n",
de, de2));
de2 = RB_INSERT(tmpfs_dirtree_cookie, &dnode->tn_dir.tn_cookietree, de);
KASSERT(de2 == NULL,
("tmpfs_dir_attach_lockedB: duplicate insertion of %p, has %p\n",
de, de2));
dnode->tn_size += sizeof(struct tmpfs_dirent);
dnode->tn_status |= TMPFS_NODE_ACCESSED | TMPFS_NODE_CHANGED |
TMPFS_NODE_MODIFIED;
}
void
tmpfs_dir_detach_locked(struct tmpfs_node *dnode, struct tmpfs_dirent *de)
{
struct tmpfs_node *node = de->td_node;
RB_REMOVE(tmpfs_dirtree, &dnode->tn_dir.tn_dirtree, de);
RB_REMOVE(tmpfs_dirtree_cookie, &dnode->tn_dir.tn_cookietree, de);
dnode->tn_size -= sizeof(struct tmpfs_dirent);
dnode->tn_status |= TMPFS_NODE_ACCESSED | TMPFS_NODE_CHANGED |
TMPFS_NODE_MODIFIED;
if (node && node->tn_type == VDIR && node->tn_dir.tn_parent) {
TMPFS_NODE_LOCK(node);
KKASSERT(node->tn_dir.tn_parent == dnode);
atomic_add_int(&dnode->tn_links, -1);
atomic_add_int(&node->tn_links, -1);
node->tn_dir.tn_parent = NULL;
TMPFS_NODE_UNLOCK(node);
}
}
struct tmpfs_dirent *
tmpfs_dir_lookup(struct tmpfs_node *node, struct tmpfs_node *f,
struct namecache *ncp)
{
struct tmpfs_dirent *de;
int len = ncp->nc_nlen;
struct tmpfs_dirent wanted;
wanted.td_namelen = len;
wanted.td_name = ncp->nc_name;
TMPFS_VALIDATE_DIR(node);
de = RB_FIND(tmpfs_dirtree, &node->tn_dir.tn_dirtree, &wanted);
KASSERT((f == NULL || de == NULL || f == de->td_node),
("tmpfs_dir_lookup: Incorrect node %p %p %p",
f, de, (de ? de->td_node : NULL)));
return de;
}
int
tmpfs_dir_getdotdent(struct tmpfs_node *node, struct uio *uio)
{
int error;
TMPFS_VALIDATE_DIR(node);
KKASSERT(uio->uio_offset == TMPFS_DIRCOOKIE_DOT);
if (vop_write_dirent(&error, uio, node->tn_id, DT_DIR, 1, "."))
return -1;
if (error == 0)
uio->uio_offset = TMPFS_DIRCOOKIE_DOTDOT;
return error;
}
int
tmpfs_dir_getdotdotdent(struct tmpfs_mount *tmp, struct tmpfs_node *node,
struct uio *uio)
{
int error;
ino_t d_ino;
TMPFS_VALIDATE_DIR(node);
KKASSERT(uio->uio_offset == TMPFS_DIRCOOKIE_DOTDOT);
if (node->tn_dir.tn_parent) {
TMPFS_NODE_LOCK(node);
if (node->tn_dir.tn_parent)
d_ino = node->tn_dir.tn_parent->tn_id;
else
d_ino = tmp->tm_root->tn_id;
TMPFS_NODE_UNLOCK(node);
} else {
d_ino = tmp->tm_root->tn_id;
}
if (vop_write_dirent(&error, uio, d_ino, DT_DIR, 2, ".."))
return -1;
if (error == 0) {
struct tmpfs_dirent *de;
de = RB_MIN(tmpfs_dirtree_cookie, &node->tn_dir.tn_cookietree);
if (de == NULL)
uio->uio_offset = TMPFS_DIRCOOKIE_EOF;
else
uio->uio_offset = tmpfs_dircookie(de);
}
return error;
}
#if 0
struct lubycookie_info {
off_t cookie;
struct tmpfs_dirent *de;
};
static int
lubycookie_cmp(struct tmpfs_dirent *de, void *arg)
{
struct lubycookie_info *info = arg;
off_t cookie = tmpfs_dircookie(de);
if (cookie < info->cookie)
return(-1);
if (cookie > info->cookie)
return(1);
return(0);
}
static int
lubycookie_callback(struct tmpfs_dirent *de, void *arg)
{
struct lubycookie_info *info = arg;
if (tmpfs_dircookie(de) == info->cookie) {
info->de = de;
return(-1);
}
return(0);
}
#endif
struct tmpfs_dirent *
tmpfs_dir_lookupbycookie(struct tmpfs_node *node, off_t cookie, int exact)
{
#if 0
struct lubycookie_info info;
info.cookie = cookie;
info.de = NULL;
RB_SCAN(tmpfs_dirtree_cookie, &node->tn_dir.tn_cookietree,
lubycookie_cmp, lubycookie_callback, &info);
return (info.de);
#endif
struct tmpfs_dirent *cdent = tmpfs_cookiedir(cookie);
struct tmpfs_dirent *last;
struct tmpfs_dirent *tmp;
last = NULL;
tmp = RB_ROOT(&node->tn_dir.tn_cookietree);
while (tmp) {
if (cdent == tmp)
return cdent;
if (cdent > tmp) {
last = tmp;
tmp = RB_RIGHT(tmp, rb_cookienode);
} else {
tmp = RB_LEFT(tmp, rb_cookienode);
}
}
return (exact ? NULL : last);
}
int
tmpfs_dir_getdents(struct tmpfs_node *node, struct uio *uio, off_t *cntp)
{
int error;
off_t startcookie;
struct tmpfs_dirent *de;
TMPFS_VALIDATE_DIR(node);
startcookie = uio->uio_offset;
KKASSERT(startcookie != TMPFS_DIRCOOKIE_DOT);
KKASSERT(startcookie != TMPFS_DIRCOOKIE_DOTDOT);
if (startcookie == TMPFS_DIRCOOKIE_EOF)
return 0;
de = tmpfs_dir_lookupbycookie(node, startcookie, 0);
if (de == NULL) {
uio->uio_offset = TMPFS_DIRCOOKIE_EOF;
return 0;
}
do {
ino_t d_ino;
uint8_t d_type;
d_ino = de->td_node->tn_id;
switch (de->td_node->tn_type) {
case VBLK:
d_type = DT_BLK;
break;
case VCHR:
d_type = DT_CHR;
break;
case VDIR:
d_type = DT_DIR;
break;
case VFIFO:
d_type = DT_FIFO;
break;
case VLNK:
d_type = DT_LNK;
break;
case VREG:
d_type = DT_REG;
break;
case VSOCK:
d_type = DT_SOCK;
break;
default:
panic("tmpfs_dir_getdents: type %p %d",
de->td_node, (int)de->td_node->tn_type);
}
KKASSERT(de->td_namelen < 256);
if (vop_write_dirent(&error, uio, d_ino, d_type,
de->td_namelen, de->td_name)) {
error = -1;
break;
}
(*cntp)++;
de = RB_NEXT(tmpfs_dirtree_cookie,
node->tn_dir.tn_cookietree, de);
} while (error == 0 && uio->uio_resid > 0 && de != NULL);
if (de == NULL) {
uio->uio_offset = TMPFS_DIRCOOKIE_EOF;
} else {
uio->uio_offset = tmpfs_dircookie(de);
}
return error;
}
int
tmpfs_reg_resize(struct vnode *vp, off_t newsize, int trivial)
{
int error;
vm_pindex_t newpages, oldpages;
struct tmpfs_mount *tmp;
struct tmpfs_node *node;
off_t oldsize;
int nvextflags;
#ifdef INVARIANTS
KKASSERT(vp->v_type == VREG);
KKASSERT(newsize >= 0);
#endif
node = VP_TO_TMPFS_NODE(vp);
tmp = VFS_TO_TMPFS(vp->v_mount);
oldsize = node->tn_size;
oldpages = round_page64(oldsize) / PAGE_SIZE;
KKASSERT(oldpages == node->tn_reg.tn_aobj_pages);
newpages = round_page64(newsize) / PAGE_SIZE;
if (newpages > oldpages &&
tmp->tm_pages_used + newpages - oldpages > tmp->tm_pages_max) {
error = ENOSPC;
goto out;
}
node->tn_reg.tn_aobj_pages = newpages;
node->tn_size = newsize;
if (newpages != oldpages)
atomic_add_long(&tmp->tm_pages_used, (newpages - oldpages));
if (vm_pages_needed || vm_paging_start(0) ||
tmpfs_bufcache_mode >= 2) {
nvextflags = 0;
} else {
nvextflags = NVEXTF_BUWRITE;
}
if (newsize < oldsize) {
vm_pindex_t osize;
vm_pindex_t nsize;
vm_object_t aobj;
error = nvtruncbuf(vp, newsize, node->tn_blksize,
-1, nvextflags);
aobj = node->tn_reg.tn_aobj;
if (aobj) {
osize = aobj->size;
nsize = vp->v_object->size;
if (nsize < osize) {
aobj->size = osize;
swap_pager_freespace(aobj, nsize,
osize - nsize);
vm_object_page_remove(aobj, nsize, osize,
FALSE);
}
}
} else {
vm_object_t aobj;
int nblksize;
nblksize = node->tn_blksize;
while (nblksize < TMPFS_BLKSIZE &&
nblksize < newsize) {
nblksize += PAGE_SIZE;
}
if (trivial)
nvextflags |= NVEXTF_TRIVIAL;
error = nvextendbuf(vp, oldsize, newsize,
node->tn_blksize, nblksize,
-1, -1, nvextflags);
node->tn_blksize = nblksize;
aobj = node->tn_reg.tn_aobj;
if (aobj)
aobj->size = vp->v_object->size;
}
out:
return error;
}
int
tmpfs_chflags(struct vnode *vp, u_long vaflags, struct ucred *cred)
{
int error;
struct tmpfs_node *node;
int flags;
KKASSERT(vn_islocked(vp));
node = VP_TO_TMPFS_NODE(vp);
flags = node->tn_flags;
if (vp->v_mount->mnt_flag & MNT_RDONLY)
return EROFS;
error = vop_helper_setattr_flags(&flags, vaflags, node->tn_uid, cred);
if (error == 0) {
TMPFS_NODE_LOCK(node);
node->tn_flags = flags;
node->tn_status |= TMPFS_NODE_CHANGED;
TMPFS_NODE_UNLOCK(node);
}
KKASSERT(vn_islocked(vp));
return error;
}
int
tmpfs_chmod(struct vnode *vp, mode_t vamode, struct ucred *cred)
{
struct tmpfs_node *node;
mode_t cur_mode;
int error;
KKASSERT(vn_islocked(vp));
node = VP_TO_TMPFS_NODE(vp);
if (vp->v_mount->mnt_flag & MNT_RDONLY)
return EROFS;
if (node->tn_flags & (IMMUTABLE | APPEND))
return EPERM;
cur_mode = node->tn_mode;
error = vop_helper_chmod(vp, vamode, cred, node->tn_uid, node->tn_gid,
&cur_mode);
if (error == 0 &&
(node->tn_mode & ALLPERMS) != (cur_mode & ALLPERMS)) {
TMPFS_NODE_LOCK(node);
node->tn_mode &= ~ALLPERMS;
node->tn_mode |= cur_mode & ALLPERMS;
node->tn_status |= TMPFS_NODE_CHANGED;
TMPFS_NODE_UNLOCK(node);
}
KKASSERT(vn_islocked(vp));
return 0;
}
int
tmpfs_chown(struct vnode *vp, uid_t uid, gid_t gid, struct ucred *cred)
{
mode_t cur_mode;
uid_t cur_uid;
gid_t cur_gid;
struct tmpfs_node *node;
int error;
KKASSERT(vn_islocked(vp));
node = VP_TO_TMPFS_NODE(vp);
if (vp->v_mount->mnt_flag & MNT_RDONLY)
return EROFS;
if (node->tn_flags & (IMMUTABLE | APPEND))
return EPERM;
cur_uid = node->tn_uid;
cur_gid = node->tn_gid;
cur_mode = node->tn_mode;
error = vop_helper_chown(vp, uid, gid, cred,
&cur_uid, &cur_gid, &cur_mode);
if (error == 0) {
TMPFS_NODE_LOCK(node);
if (cur_uid != node->tn_uid ||
cur_gid != node->tn_gid ||
cur_mode != node->tn_mode) {
node->tn_uid = cur_uid;
node->tn_gid = cur_gid;
node->tn_mode = cur_mode;
node->tn_status |= TMPFS_NODE_CHANGED;
}
TMPFS_NODE_UNLOCK(node);
}
return error;
}
int
tmpfs_chsize(struct vnode *vp, u_quad_t size, struct ucred *cred)
{
int error;
struct tmpfs_node *node;
KKASSERT(vn_islocked(vp));
node = VP_TO_TMPFS_NODE(vp);
error = 0;
switch (vp->v_type) {
case VDIR:
return EISDIR;
case VREG:
if (vp->v_mount->mnt_flag & MNT_RDONLY)
return EROFS;
break;
case VBLK:
case VCHR:
case VFIFO:
return 0;
default:
return EOPNOTSUPP;
}
if (node->tn_flags & (IMMUTABLE | APPEND))
return EPERM;
error = tmpfs_truncate(vp, size);
KKASSERT(vn_islocked(vp));
return error;
}
int
tmpfs_chtimes(struct vnode *vp, struct timespec *atime, struct timespec *mtime,
int vaflags, struct ucred *cred)
{
struct tmpfs_node *node;
KKASSERT(vn_islocked(vp));
node = VP_TO_TMPFS_NODE(vp);
if (vp->v_mount->mnt_flag & MNT_RDONLY)
return EROFS;
if (node->tn_flags & (IMMUTABLE | APPEND))
return EPERM;
TMPFS_NODE_LOCK(node);
if (atime->tv_sec != VNOVAL && atime->tv_nsec != VNOVAL)
node->tn_status |= TMPFS_NODE_ACCESSED;
if (mtime->tv_sec != VNOVAL && mtime->tv_nsec != VNOVAL) {
node->tn_status |= TMPFS_NODE_MODIFIED;
vclrflags(vp, VLASTWRITETS);
}
TMPFS_NODE_UNLOCK(node);
tmpfs_itimes(vp, atime, mtime);
KKASSERT(vn_islocked(vp));
return 0;
}
void
tmpfs_itimes(struct vnode *vp, const struct timespec *acc,
const struct timespec *mod)
{
struct tmpfs_node *node;
struct timespec now;
node = VP_TO_TMPFS_NODE(vp);
if ((node->tn_status & (TMPFS_NODE_ACCESSED | TMPFS_NODE_MODIFIED |
TMPFS_NODE_CHANGED)) == 0) {
return;
}
vfs_timestamp(&now);
TMPFS_NODE_LOCK(node);
if (node->tn_status & TMPFS_NODE_ACCESSED) {
if (acc == NULL)
acc = &now;
node->tn_atime = acc->tv_sec;
node->tn_atimensec = acc->tv_nsec;
}
if (node->tn_status & TMPFS_NODE_MODIFIED) {
if (mod == NULL)
mod = &now;
node->tn_mtime = mod->tv_sec;
node->tn_mtimensec = mod->tv_nsec;
}
if (node->tn_status & TMPFS_NODE_CHANGED) {
node->tn_ctime = now.tv_sec;
node->tn_ctimensec = now.tv_nsec;
}
node->tn_status &= ~(TMPFS_NODE_ACCESSED |
TMPFS_NODE_MODIFIED |
TMPFS_NODE_CHANGED);
TMPFS_NODE_UNLOCK(node);
}
void
tmpfs_update(struct vnode *vp)
{
tmpfs_itimes(vp, NULL, NULL);
}
int
tmpfs_truncate(struct vnode *vp, off_t length)
{
int error;
struct tmpfs_node *node;
node = VP_TO_TMPFS_NODE(vp);
if (length < 0) {
error = EINVAL;
goto out;
}
if (node->tn_size == length) {
error = 0;
goto out;
}
if (length > VFS_TO_TMPFS(vp->v_mount)->tm_maxfilesize)
return (EFBIG);
error = tmpfs_reg_resize(vp, length, 1);
if (error == 0)
node->tn_status |= TMPFS_NODE_CHANGED | TMPFS_NODE_MODIFIED;
out:
tmpfs_update(vp);
return error;
}
static ino_t
tmpfs_fetch_ino(struct tmpfs_mount *tmp)
{
ino_t ret;
ret = atomic_fetchadd_64(&tmp->tm_ino, 1);
return (ret);
}
static int
tmpfs_dirtree_compare(struct tmpfs_dirent *a, struct tmpfs_dirent *b)
{
if (a->td_namelen > b->td_namelen)
return 1;
else if (a->td_namelen < b->td_namelen)
return -1;
else
return strncmp(a->td_name, b->td_name, a->td_namelen);
}
static int
tmpfs_dirtree_compare_cookie(struct tmpfs_dirent *a, struct tmpfs_dirent *b)
{
if (a < b)
return(-1);
if (a > b)
return(1);
return 0;
}
void
tmpfs_lock4(struct tmpfs_node *node1, struct tmpfs_node *node2,
struct tmpfs_node *node3, struct tmpfs_node *node4)
{
if (node1->tn_dir.tn_parent != node2 &&
(node1 < node2 || node2->tn_dir.tn_parent == node1)) {
TMPFS_NODE_LOCK(node1);
TMPFS_NODE_LOCK(node3);
TMPFS_NODE_LOCK(node2);
if (node4)
TMPFS_NODE_LOCK(node4);
} else {
TMPFS_NODE_LOCK(node2);
if (node4)
TMPFS_NODE_LOCK(node4);
TMPFS_NODE_LOCK(node1);
TMPFS_NODE_LOCK(node3);
}
}
void
tmpfs_unlock4(struct tmpfs_node *node1, struct tmpfs_node *node2,
struct tmpfs_node *node3, struct tmpfs_node *node4)
{
if (node4)
TMPFS_NODE_UNLOCK(node4);
TMPFS_NODE_UNLOCK(node2);
TMPFS_NODE_UNLOCK(node3);
TMPFS_NODE_UNLOCK(node1);
}