1332 lines
28 KiB
C
1332 lines
28 KiB
C
#ifndef lint
|
|
/* @(#)tmp_vnodeops.c 1.1 92/07/30 SMI */
|
|
#endif
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/user.h>
|
|
#include <sys/time.h>
|
|
#include <sys/vfs.h>
|
|
#include <sys/vnode.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/vfs_stat.h>
|
|
#include <sys/ucred.h>
|
|
#include <sys/dirent.h>
|
|
#include <sys/pathname.h>
|
|
#include <tmpfs/tmp.h>
|
|
#include <tmpfs/tmpnode.h>
|
|
#include <tmpfs/tmpdir.h>
|
|
#include <sys/mman.h>
|
|
#include <vm/hat.h>
|
|
#include <vm/seg_vn.h>
|
|
#include <vm/seg_map.h>
|
|
#include <vm/seg.h>
|
|
#include <vm/anon.h>
|
|
#include <vm/as.h>
|
|
#include <vm/page.h>
|
|
#include <sys/syslog.h>
|
|
#include <sys/debug.h>
|
|
|
|
#define ISVDEV(t) ((t == VCHR) || (t == VBLK) || (t == VFIFO))
|
|
|
|
static int tmp_open();
|
|
static int tmp_close();
|
|
static int tmp_rdwr();
|
|
static int tmp_ioctl();
|
|
static int tmp_select();
|
|
static int tmp_getattr();
|
|
static int tmp_setattr();
|
|
static int tmp_access();
|
|
static int tmp_lookup();
|
|
static int tmp_create();
|
|
static int tmp_remove();
|
|
static int tmp_link();
|
|
static int tmp_rename();
|
|
static int tmp_mkdir();
|
|
static int tmp_rmdir();
|
|
static int tmp_readdir();
|
|
static int tmp_symlink();
|
|
static int tmp_readlink();
|
|
static int tmp_fsync();
|
|
static int tmp_inactive();
|
|
static int tmp_lockctl();
|
|
static int tmp_fid();
|
|
static int tmp_getpage();
|
|
static int tmp_putpage();
|
|
static int tmp_map();
|
|
static int tmp_dump();
|
|
static int tmp_cmp();
|
|
static int tmp_realvp();
|
|
static int tmp_cntl();
|
|
|
|
struct vnodeops tmp_vnodeops = {
|
|
tmp_open,
|
|
tmp_close,
|
|
tmp_rdwr,
|
|
tmp_ioctl,
|
|
tmp_select,
|
|
tmp_getattr,
|
|
tmp_setattr,
|
|
tmp_access,
|
|
tmp_lookup,
|
|
tmp_create,
|
|
tmp_remove,
|
|
tmp_link,
|
|
tmp_rename,
|
|
tmp_mkdir,
|
|
tmp_rmdir,
|
|
tmp_readdir,
|
|
tmp_symlink,
|
|
tmp_readlink,
|
|
tmp_fsync,
|
|
tmp_inactive,
|
|
tmp_lockctl,
|
|
tmp_fid,
|
|
tmp_getpage,
|
|
tmp_putpage,
|
|
tmp_map,
|
|
tmp_dump,
|
|
tmp_cmp,
|
|
tmp_realvp,
|
|
tmp_cntl,
|
|
};
|
|
|
|
#ifdef TMPFSDEBUG
|
|
int tmpfsdebug = 0;
|
|
int tmpdebugerrs = 0;
|
|
int tmplockdebug = 0;
|
|
int tmpdirdebug = 0;
|
|
int tmprwdebug = 0;
|
|
int tmpdebugalloc = 0;
|
|
#endif TMPFSDEBUG
|
|
|
|
extern struct timeval time;
|
|
|
|
/*
|
|
* Table to convert vnode types to tmpnode formats
|
|
*/
|
|
int vttotf_tab[] = {
|
|
0, TFREG, TFDIR, TFBLK, TFCHR, TFLNK, TFSOCK, TFMT, TFIFO
|
|
};
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
tmp_open(vpp, flag, cred)
|
|
struct vnode **vpp;
|
|
int flag;
|
|
struct ucred *cred;
|
|
{
|
|
VFS_RECORD((*vpp)->v_vfsp, VS_OPEN, VS_CALL);
|
|
#ifdef TMPFSDEBUG
|
|
if (tmpfsdebug)
|
|
printf("tmp_open: vpp %x\n", *vpp);
|
|
#endif TMPFSDEBUG
|
|
/*
|
|
* swapon to tmpfs files are not supported
|
|
* so access is denied on open
|
|
*/
|
|
if ((*vpp)->v_flag & VISSWAP)
|
|
return (EINVAL);
|
|
return (0);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
tmp_close(vp, flag, count, cred)
|
|
struct vnode *vp;
|
|
int flag, count;
|
|
struct ucred *cred;
|
|
{
|
|
VFS_RECORD(vp->v_vfsp, VS_CLOSE, VS_CALL);
|
|
#ifdef TMPFSDEBUG
|
|
if (tmpfsdebug)
|
|
printf("tmp_close: vp %x\n", vp);
|
|
#endif TMPFSDEBUG
|
|
return (0);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
tmp_rdwr(vp, uiop, rw, ioflag, cred)
|
|
struct vnode *vp;
|
|
struct uio *uiop;
|
|
enum uio_rw rw;
|
|
int ioflag;
|
|
struct ucred *cred;
|
|
{
|
|
struct tmpnode *tp = VP_TO_TN(vp);
|
|
struct tmount *tm = VP_TO_TM(vp);
|
|
int error;
|
|
|
|
if (tp->tn_attr.va_type != VREG) {
|
|
return (EISDIR);
|
|
}
|
|
if ((ioflag & IO_APPEND) && (rw == UIO_WRITE)) {
|
|
/*
|
|
* In append mode start at end of file.
|
|
*/
|
|
uiop->uio_offset = tp->tn_attr.va_size;
|
|
}
|
|
error = rwtmp(tm, tp, uiop, rw);
|
|
if (rw == UIO_WRITE)
|
|
modified(tp);
|
|
else
|
|
accessed(tp);
|
|
return (error);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
rwtmp(tm, tp, uiop, rw)
|
|
struct tmount *tm;
|
|
struct tmpnode *tp;
|
|
struct uio *uiop;
|
|
enum uio_rw rw;
|
|
{
|
|
extern void swap_xlate();
|
|
register char *base = NULL; /* base of segmap */
|
|
register u_int offset; /* offset in tmpfs file (uio_offset) */
|
|
register u_int pageoffset; /* byte offset from page boundary */
|
|
register u_int segmap_offset; /* pagesize byte offset into segmap */
|
|
register u_int newpageno;
|
|
register u_int oldpageno = -1;
|
|
register int bytes; /* bytes to uiomove */
|
|
register int nmoved; /* bytes actually moved */
|
|
int pagecreate = 0; /* == 1 if we allocated a page */
|
|
int error = 0;
|
|
enum vtype type = tp->tn_attr.va_type;
|
|
struct vnode *swapvp;
|
|
u_int swapoff;
|
|
int adjust_resid = 0;
|
|
|
|
#ifdef TMPFSDEBUG
|
|
if (tmpfsdebug || tmprwdebug)
|
|
printf("rwtmp: tp %x offset %d resid %d rw %d\n", tp,
|
|
uiop->uio_offset, uiop->uio_resid, rw);
|
|
#endif TMPFSDEBUG
|
|
|
|
if (rw != UIO_READ && rw != UIO_WRITE)
|
|
panic("rwtmp");
|
|
if (type != VREG && type != VDIR && type != VLNK)
|
|
panic("rwtmp type");
|
|
|
|
if (uiop->uio_offset < 0 || (uiop->uio_offset + uiop->uio_resid) < 0)
|
|
return (EINVAL);
|
|
|
|
if (rw == UIO_WRITE) {
|
|
if (type == VREG && uiop->uio_offset + uiop->uio_resid >
|
|
u.u_rlimit[RLIMIT_FSIZE].rlim_cur) {
|
|
if (uiop->uio_offset >=
|
|
u.u_rlimit[RLIMIT_FSIZE].rlim_cur) {
|
|
psignal(u.u_procp, SIGXFSZ);
|
|
error = EFBIG;
|
|
goto out;
|
|
} else {
|
|
adjust_resid = uiop->uio_resid;
|
|
uiop->uio_resid =
|
|
u.u_rlimit[RLIMIT_FSIZE].rlim_cur -
|
|
uiop->uio_offset;
|
|
adjust_resid -= uiop->uio_resid;
|
|
}
|
|
}
|
|
}
|
|
|
|
tmpnode_lock(tp);
|
|
while (uiop->uio_resid > 0 && !error) {
|
|
offset = uiop->uio_offset;
|
|
pageoffset = offset & PAGEOFFSET;
|
|
bytes = MIN(PAGESIZE - pageoffset, uiop->uio_resid);
|
|
newpageno = btop(offset);
|
|
|
|
/*
|
|
* if we have a segmap and on new page in file,
|
|
* release segmap
|
|
*/
|
|
if (base != NULL && newpageno != oldpageno) {
|
|
(void) segmap_release(segkmap, (addr_t)base, 0);
|
|
base = NULL;
|
|
}
|
|
if (rw == UIO_READ) {
|
|
int diff = tp->tn_attr.va_size - offset;
|
|
|
|
VFS_RECORD(tm->tm_vfsp, VS_READ, VS_CALL);
|
|
if (diff <= 0) {
|
|
error = 0;
|
|
break;
|
|
}
|
|
if (diff < bytes)
|
|
bytes = diff;
|
|
} else {
|
|
VFS_RECORD(tm->tm_vfsp, VS_WRITE, VS_CALL);
|
|
modified(tp);
|
|
}
|
|
|
|
/*
|
|
* For tmpfs, the underlying filesystem blocks are actually
|
|
* anonymous pages whose vp & offset correspond to the swap
|
|
* device rather than the tmpfs file. Addresses and offsets
|
|
* regarding the uiomove are set relative to swapvp rather
|
|
* than what is in the uio structure.
|
|
*/
|
|
if (base == NULL) {
|
|
/*
|
|
* if we need a segmap page, first allocate the
|
|
* anon slot for the tmpnode
|
|
*/
|
|
if (rw == UIO_WRITE ||
|
|
tp->tn_amapp->anon[newpageno] == NULL) {
|
|
pagecreate = tmpnode_findpage(tm, tp, offset);
|
|
if (pagecreate < 0) {
|
|
log(LOG_ERR,
|
|
"%s: file system full, anon allocation exceeded\n",
|
|
tm->tm_mntpath);
|
|
uprintf(
|
|
"\n%s: write failed, file system is full\n",
|
|
tm->tm_mntpath);
|
|
error = ENOSPC;
|
|
break;
|
|
}
|
|
}
|
|
swap_xlate(tp->tn_amapp->anon[newpageno],
|
|
&swapvp, &swapoff);
|
|
/*
|
|
* The base for the uiomove is set relative to
|
|
* offset on the swap device.
|
|
*/
|
|
base = segmap_getmap(segkmap, swapvp,
|
|
(swapoff & MAXBMASK));
|
|
segmap_offset = swapoff & MAXBOFFSET;
|
|
}
|
|
|
|
if (rw == UIO_WRITE) {
|
|
int delta = offset + bytes - tp->tn_attr.va_size;
|
|
/*
|
|
* Extend the file length. Just update the size
|
|
* in tmpnode since anon slot is already allocated
|
|
*/
|
|
if (delta > 0) {
|
|
if (tmp_resv(tm, tp, (u_int)delta,
|
|
pagecreate)) {
|
|
log(LOG_ERR,
|
|
"%s: file system full, anon reservation exceeded\n",
|
|
tm->tm_mntpath);
|
|
uprintf(
|
|
"\n%s: write failed, file system is full\n",
|
|
tm->tm_mntpath);
|
|
error = ENOSPC;
|
|
break;
|
|
}
|
|
#ifdef TMPFSDEBUG
|
|
if (tmprwdebug)
|
|
printf("rwtmp: extend file %d bytes\n",
|
|
delta);
|
|
|
|
#endif TMPFSDEBUG
|
|
tp->tn_attr.va_size += delta;
|
|
}
|
|
}
|
|
|
|
#ifdef TMPFSDEBUG
|
|
if (tmprwdebug)
|
|
printf("rwtmp: %s %x %d\n",
|
|
(rw == UIO_READ ? "reading" : "writing"),
|
|
base + pageoffset + segmap_offset, bytes);
|
|
#endif TMPFSDEBUG
|
|
if (pagecreate > 0) {
|
|
segmap_pagecreate(segkmap, base + segmap_offset,
|
|
(u_int)bytes, 0);
|
|
/*
|
|
* if the file has a hole, we need to zero the
|
|
* page now rather than let a page of stuff get
|
|
* to the user.
|
|
*/
|
|
if (rw == UIO_READ)
|
|
bzero(base + segmap_offset, (u_int) PAGESIZE);
|
|
}
|
|
error = uiomove(base + segmap_offset + pageoffset, bytes,
|
|
rw, uiop);
|
|
nmoved = uiop->uio_offset - offset;
|
|
ASSERT((nmoved + pageoffset) <= PAGESIZE);
|
|
#ifdef TMPFSDEBUG
|
|
if (tmprwdebug) {
|
|
printf("rwtmp: uio_offset %x actually moved %d\n",
|
|
uiop->uio_offset, nmoved);
|
|
}
|
|
#endif TMPFSDEBUG
|
|
|
|
/*
|
|
* if we wrote a partial page, its remainder must be zeroed
|
|
*/
|
|
if (rw == UIO_WRITE && pagecreate > 0 && !error) {
|
|
int zoffset; /* offset into page to zero from */
|
|
|
|
/*
|
|
* Clear from the beginning of the page to the starting
|
|
* offset of the data.
|
|
*/
|
|
if (pageoffset > 0)
|
|
kzero(base + segmap_offset, pageoffset);
|
|
|
|
/*
|
|
* Zero from the end of data in the page to the
|
|
* end of the page.
|
|
*/
|
|
if ((zoffset = pageoffset + nmoved) < PAGESIZE)
|
|
kzero(base + segmap_offset + zoffset,
|
|
(u_int) (PAGESIZE - zoffset));
|
|
}
|
|
if (error == 0 && rw == UIO_WRITE) {
|
|
if (u.u_ruid != 0 && (tp->tn_attr.va_mode & (TEXEC |
|
|
(TEXEC >> 3) | (TEXEC >> 6))) != 0)
|
|
tp->tn_attr.va_mode &= ~(S_ISUID|S_ISGID);
|
|
}
|
|
oldpageno = newpageno;
|
|
}
|
|
/*
|
|
* XXX if there was an error in the uiomove, we'll need to
|
|
* delete appropriate anon slots allocated for the tmpnode
|
|
*/
|
|
if (base != NULL)
|
|
(void) segmap_release(segkmap, (addr_t)base, 0);
|
|
tmpnode_unlock(tp);
|
|
out:
|
|
if (!error && adjust_resid) {
|
|
uiop->uio_resid = adjust_resid;
|
|
psignal(u.u_procp, SIGXFSZ);
|
|
}
|
|
return (error);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
tmp_ioctl(vp, com, data, flag, cred)
|
|
struct vnode *vp;
|
|
int com;
|
|
caddr_t data;
|
|
int flag;
|
|
struct ucred *cred;
|
|
{
|
|
VFS_RECORD(vp->v_vfsp, VS_IOCTL, VS_CALL);
|
|
return (EINVAL);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
tmp_select(vp, which, cred)
|
|
struct vnode *vp;
|
|
int which;
|
|
struct ucred *cred;
|
|
{
|
|
VFS_RECORD(vp->v_vfsp, VS_SELECT, VS_CALL);
|
|
return (EINVAL);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
tmp_getattr(vp, vap, cred)
|
|
struct vnode *vp;
|
|
struct vattr *vap;
|
|
struct ucred *cred;
|
|
{
|
|
struct tmpnode *tp = VP_TO_TN(vp);
|
|
|
|
VFS_RECORD(vp->v_vfsp, VS_GETATTR, VS_CALL);
|
|
|
|
*vap = tp->tn_attr;
|
|
vap->va_blocks = btodb(ptob(btopr(vap->va_size)));
|
|
#ifdef TMPFSDEBUG
|
|
if (tmpfsdebug)
|
|
printf("tmp_getattr: tp %x vap:%x\n", tp, vap);
|
|
#endif TMPFSDEBUG
|
|
return (0);
|
|
}
|
|
|
|
#define OWNER(cred, va) \
|
|
((((cred)->cr_uid == 0) || ((va)->va_uid == (cred)->cr_uid))? 0: EPERM)
|
|
|
|
static int
|
|
tmp_setattr(vp, vap, cred)
|
|
struct vnode *vp;
|
|
struct vattr *vap;
|
|
struct ucred *cred;
|
|
{
|
|
struct tmount *tm = VP_TO_TM(vp);
|
|
struct tmpnode *tp = VP_TO_TN(vp);
|
|
int error = 0;
|
|
struct vattr *get = &tp->tn_attr;
|
|
|
|
VFS_RECORD(vp->v_vfsp, VS_SETATTR, VS_CALL);
|
|
#ifdef TMPFSDEBUG
|
|
if (tmpfsdebug)
|
|
printf("tmp_setattr: tp %x vap %x\n", tp, vap);
|
|
#endif TMPFSDEBUG
|
|
|
|
/*
|
|
* Cannot set these attributes
|
|
*/
|
|
if ((vap->va_nlink != -1) || (vap->va_blocksize != -1) ||
|
|
(vap->va_rdev != -1) || (vap->va_blocks != -1) ||
|
|
(vap->va_fsid != -1) || (vap->va_nodeid != -1) ||
|
|
((int)vap->va_type != -1)) {
|
|
return (EINVAL);
|
|
}
|
|
tmpnode_lock(tp);
|
|
if (vap->va_mode != (u_short)-1) {
|
|
if (error = OWNER(cred, get)) {
|
|
goto out;
|
|
}
|
|
get->va_mode &= S_IFMT;
|
|
get->va_mode |= vap->va_mode & ~S_IFMT;
|
|
if (cred->cr_uid != 0) {
|
|
if ((get->va_mode & S_IFMT) != S_IFDIR)
|
|
get->va_mode &= ~S_ISVTX;
|
|
if (!groupmember((int)get->va_gid))
|
|
get->va_mode &= ~S_ISGID;
|
|
}
|
|
}
|
|
if (vap->va_uid != (uid_t)-1) {
|
|
if (cred->cr_uid == 0 || (cred->cr_uid == get->va_uid &&
|
|
get->va_uid == vap->va_uid)) {
|
|
get->va_uid = vap->va_uid;
|
|
if (cred->cr_uid != 0)
|
|
get->va_mode &= ~(S_ISUID|S_ISGID);
|
|
} else {
|
|
error = EPERM;
|
|
goto out;
|
|
}
|
|
}
|
|
if (vap->va_gid != (gid_t)-1) {
|
|
if (cred->cr_uid == 0 || ingroup(vap->va_gid, cred)) {
|
|
get->va_gid = vap->va_gid;
|
|
if (cred->cr_uid != 0)
|
|
get->va_mode &= ~(S_ISUID|S_ISGID);
|
|
} else {
|
|
error = EPERM;
|
|
goto out;
|
|
}
|
|
}
|
|
if (vap->va_atime.tv_sec != -1) {
|
|
if (error = OWNER(cred, get)) {
|
|
goto out;
|
|
}
|
|
get->va_atime = vap->va_atime;
|
|
GET_TIME(&get->va_ctime);
|
|
}
|
|
if (vap->va_mtime.tv_sec != -1) {
|
|
if (error = OWNER(cred, get)) {
|
|
goto out;
|
|
}
|
|
/*
|
|
* Allow SystemV compatible option to set access and
|
|
* modified times to the current time.
|
|
*
|
|
* XXX - va_mtime.tv_usec == -1 flags this.
|
|
*/
|
|
if (vap->va_mtime.tv_usec == -1) {
|
|
GET_TIME(&get->va_atime);
|
|
GET_TIME(&get->va_mtime);
|
|
} else {
|
|
get->va_mtime = vap->va_mtime;
|
|
}
|
|
GET_TIME(&get->va_ctime);
|
|
}
|
|
if (vap->va_size != (u_long)-1) {
|
|
if (tp->tn_attr.va_type == VDIR) {
|
|
error = EISDIR;
|
|
goto out;
|
|
}
|
|
/* write access was checked above the vnode layer */
|
|
if (error = tmpnode_trunc(tm, tp, vap->va_size)) {
|
|
goto out;
|
|
}
|
|
}
|
|
out:
|
|
tmpnode_unlock(tp);
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
tmp_access(vp, mode, cred)
|
|
struct vnode *vp;
|
|
int mode;
|
|
struct ucred *cred;
|
|
{
|
|
struct tmpnode *tp = VP_TO_TN(vp);
|
|
|
|
VFS_RECORD(vp->v_vfsp, VS_ACCESS, VS_CALL);
|
|
|
|
return (taccess(tp, cred, mode));
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
tmp_lookup(dvp, nm, vpp, cred, pnp, flags)
|
|
struct vnode *dvp;
|
|
char *nm;
|
|
struct vnode **vpp;
|
|
struct ucred *cred;
|
|
struct pathname *pnp;
|
|
int flags;
|
|
{
|
|
struct tmpnode *tp = VP_TO_TN(dvp);
|
|
struct tmpnode *ntp;
|
|
int error;
|
|
|
|
#ifdef TMPFSDEBUG
|
|
if (tmpfsdebug || tmpdirdebug)
|
|
printf("tmp_lookup: tp %x nm %s\n", tp, nm);
|
|
#endif TMPFSDEBUG
|
|
|
|
VFS_RECORD(dvp->v_vfsp, VS_LOOKUP, VS_CALL);
|
|
|
|
error = tdirlookup(tp, nm, &ntp, cred);
|
|
if (error == 0) {
|
|
*vpp = TP_TO_VP(ntp);
|
|
accessed(tp);
|
|
tmpnode_unlock(ntp);
|
|
/*
|
|
* If vnode is a device return special vnode instead
|
|
*/
|
|
if (ISVDEV((*vpp)->v_type)) {
|
|
struct vnode *newvp;
|
|
|
|
newvp = specvp(*vpp, (*vpp)->v_rdev, (*vpp)->v_type);
|
|
VN_RELE(*vpp);
|
|
*vpp = newvp;
|
|
}
|
|
}
|
|
return (error);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
tmp_create(dvp, nm, vap, exclusive, mode, vpp, cred)
|
|
struct vnode *dvp;
|
|
char *nm;
|
|
struct vattr *vap;
|
|
enum vcexcl exclusive;
|
|
int mode;
|
|
struct vnode **vpp;
|
|
struct ucred *cred;
|
|
{
|
|
struct tmpnode *parent = VP_TO_TN(dvp);
|
|
struct tmount *tm = VP_TO_TM(dvp);
|
|
struct tmpnode *self;
|
|
int error;
|
|
struct tmpnode *oldtp;
|
|
|
|
#ifdef TMPFSDEBUG
|
|
if (tmpfsdebug || tmpdirdebug)
|
|
printf("tmp_create: parent %x nm %s\n", parent, nm);
|
|
#endif TMPFSDEBUG
|
|
VFS_RECORD(dvp->v_vfsp, VS_CREATE, VS_CALL);
|
|
|
|
switch ((int) vap->va_type) {
|
|
/* only super-user can create non-FIFO special devices */
|
|
case (int) VBLK:
|
|
case (int) VCHR:
|
|
if (cred->cr_uid != 0)
|
|
return (EPERM);
|
|
else
|
|
break;
|
|
|
|
/* tmp_mkdir is used to create directories */
|
|
case (int) VDIR:
|
|
return (EISDIR);
|
|
}
|
|
|
|
error = tdirlookup(parent, nm, &oldtp, cred);
|
|
if (error == 0) { /* name found */
|
|
if (exclusive == EXCL) {
|
|
tmpnode_put(oldtp);
|
|
return (EEXIST);
|
|
}
|
|
if (oldtp->tn_attr.va_type == VDIR) {
|
|
tmpnode_put(oldtp);
|
|
return (EISDIR);
|
|
}
|
|
if (error = taccess(oldtp, cred, mode)) {
|
|
tmpnode_put(oldtp);
|
|
return (error);
|
|
}
|
|
if (vap->va_size == 0) {
|
|
(void) tmpnode_trunc(tm, oldtp, (u_long)0);
|
|
}
|
|
*vpp = TP_TO_VP(oldtp);
|
|
if (vap != NULL)
|
|
*vap = oldtp->tn_attr;
|
|
tmpnode_unlock(oldtp);
|
|
/*
|
|
* If vnode is a device return special vnode instead
|
|
*/
|
|
if (ISVDEV((*vpp)->v_type)) {
|
|
struct vnode *newvp;
|
|
|
|
newvp = specvp(*vpp, (*vpp)->v_rdev, (*vpp)->v_type);
|
|
VN_RELE(*vpp);
|
|
*vpp = newvp;
|
|
}
|
|
return (0);
|
|
}
|
|
if (error != ENOENT) {
|
|
return (error);
|
|
}
|
|
self = tmpnode_alloc(tm, vap->va_type);
|
|
if (self == NULL) {
|
|
log(LOG_ERR, "%s: file system full, kmem_alloc failure\n",
|
|
tm->tm_mntpath);
|
|
uprintf("\n%s: create failed, out of kernel memory\n",
|
|
tm->tm_mntpath);
|
|
return (ENOSPC);
|
|
}
|
|
#ifdef TMPFSDEBUG
|
|
if (tmpfsdebug || tmpdirdebug)
|
|
printf("tmp_create: newnode type %x mode %x\n", vap->va_type,
|
|
vttotf_tab[(int)vap->va_type] | (vap->va_mode & 0777));
|
|
#endif TMPFSDEBUG
|
|
newnode(self, vttotf_tab[(int)vap->va_type] | (vap->va_mode & 0777),
|
|
cred->cr_uid, cred->cr_gid);
|
|
error = tdirenter(tm, parent, nm, self, cred);
|
|
if (error) {
|
|
tmpnode_free(tm, self);
|
|
return (error);
|
|
}
|
|
tmpnode_unlock(self);
|
|
modified(parent);
|
|
*vpp = TP_TO_VP(self);
|
|
if (ISVDEV((*vpp)->v_type)) {
|
|
struct vnode *newvp;
|
|
|
|
if ((*vpp)->v_type == VBLK || (*vpp)->v_type == VCHR) {
|
|
self->tn_attr.va_rdev = (*vpp)->v_rdev =
|
|
vap->va_rdev;
|
|
}
|
|
newvp = specvp(*vpp, (*vpp)->v_rdev, (*vpp)->v_type);
|
|
VN_RELE(*vpp);
|
|
*vpp = newvp;
|
|
}
|
|
|
|
if (vap != NULL)
|
|
*vap = self->tn_attr;
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
tmp_remove(dvp, nm, cred)
|
|
struct vnode *dvp;
|
|
char *nm;
|
|
struct ucred *cred;
|
|
{
|
|
struct tmpnode *parent = VP_TO_TN(dvp);
|
|
struct tmount *tm = VP_TO_TM(dvp);
|
|
int error;
|
|
struct tmpnode *tp;
|
|
|
|
#ifdef TMPFSDEBUG
|
|
if (tmpfsdebug || tmpdirdebug)
|
|
printf("tmp_remove: parent %x nm %s\n", parent, nm);
|
|
#endif TMPFSDEBUG
|
|
|
|
VFS_RECORD(dvp->v_vfsp, VS_REMOVE, VS_CALL);
|
|
|
|
error = tdirlookup(parent, nm, &tp, cred);
|
|
if (error) {
|
|
return (error);
|
|
}
|
|
if (tp->tn_attr.va_type == VDIR) {
|
|
tmpnode_put(tp);
|
|
return (EISDIR);
|
|
}
|
|
error = tdirdelete(tm, parent, tp, nm, cred);
|
|
if (error) {
|
|
tmpnode_put(tp);
|
|
return (error);
|
|
}
|
|
tmpnode_put(tp);
|
|
modified(parent);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
tmp_link(vp, tdvp, tnm, cred)
|
|
struct vnode *vp;
|
|
struct vnode *tdvp;
|
|
char *tnm;
|
|
struct ucred *cred;
|
|
{
|
|
struct tmpnode *from = VP_TO_TN(vp);
|
|
struct tmpnode *parent = VP_TO_TN(tdvp);
|
|
struct tmount *tm = VP_TO_TM(vp);
|
|
int error;
|
|
struct tmpnode *found;
|
|
|
|
#ifdef TMPFSDEBUG
|
|
if (tmpfsdebug || tmpdirdebug)
|
|
printf("tmp_link: from %x parent %x tnm %s\n", from,
|
|
parent, tnm);
|
|
#endif TMPFSDEBUG
|
|
VFS_RECORD(vp->v_vfsp, VS_LINK, VS_CALL);
|
|
if (from->tn_attr.va_type == VDIR && cred->cr_uid != 0)
|
|
return (EPERM);
|
|
error = tdirlookup(parent, tnm, &found, cred);
|
|
if (error == 0) {
|
|
tmpnode_put(found);
|
|
return (EEXIST);
|
|
}
|
|
if (error != ENOENT)
|
|
return (error);
|
|
error = tdirenter(tm, parent, tnm, from, cred);
|
|
if (error) {
|
|
return (error);
|
|
}
|
|
modified(parent);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
tmp_rename(odvp, onm, ndvp, nnm, cred)
|
|
struct vnode *odvp;
|
|
char *onm;
|
|
struct vnode *ndvp;
|
|
char *nnm;
|
|
struct ucred *cred;
|
|
{
|
|
struct tmpnode *fromparent = VP_TO_TN(odvp);
|
|
struct tmpnode *toparent = VP_TO_TN(ndvp);
|
|
struct tmpnode *fromtp = NULL;
|
|
struct tmpnode *totp = NULL;
|
|
struct tmpnode *tmptp = NULL;
|
|
struct tmount *tm = VP_TO_TM(odvp);
|
|
int error, doingdirectory;
|
|
|
|
#ifdef TMPFSDEBUG
|
|
if (tmpfsdebug || tmpdirdebug)
|
|
printf("tmp_rename: fromparent %x onm %s toparent %x nnm %s\n",
|
|
fromparent, onm, toparent, nnm);
|
|
#endif TMPFSDEBUG
|
|
VFS_RECORD(odvp->v_vfsp, VS_RENAME, VS_CALL);
|
|
|
|
/*
|
|
* Make sure we can delete the old (source) entry.
|
|
*/
|
|
error = taccess(fromparent, cred, VWRITE);
|
|
if (error) {
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Check for renaming '.' or '..'
|
|
*/
|
|
if ((strcmp(onm, ".") == 0) || (strcmp(onm, "..") == 0))
|
|
return (EINVAL);
|
|
|
|
/*
|
|
* Lookup source
|
|
*/
|
|
error = tdirlookup(fromparent, onm, &fromtp, cred);
|
|
if (error) {
|
|
return (error);
|
|
}
|
|
|
|
if ((fromparent->tn_attr.va_mode & TSVTX) && cred->cr_uid != 0 &&
|
|
cred->cr_uid != fromparent->tn_attr.va_uid &&
|
|
fromtp->tn_attr.va_uid != cred->cr_uid) {
|
|
tmpnode_put(fromtp);
|
|
return (EPERM);
|
|
}
|
|
|
|
/*
|
|
* Check that we don't move a parent directory down to one of its
|
|
* children, or we'd end up removing this subtree from the directory
|
|
* hierarchy.
|
|
*/
|
|
if (isparent(fromtp, toparent)) {
|
|
tmpnode_put(fromtp);
|
|
return (EINVAL);
|
|
}
|
|
|
|
doingdirectory = (fromtp->tn_attr.va_type == VDIR);
|
|
|
|
/*
|
|
* Check out the target
|
|
*/
|
|
error = tdirlookup(toparent, nnm, &totp, cred);
|
|
if (error == 0) { /* entry of name already exists */
|
|
if (totp->tn_attr.va_type == VDIR) {
|
|
if (!doingdirectory)
|
|
error = ENOTDIR;
|
|
if (totp->tn_attr.va_size > TEMPTYDIRSIZE ||
|
|
totp->tn_attr.va_nlink > 2)
|
|
error = ENOTEMPTY;
|
|
goto done;
|
|
} else if (doingdirectory) {
|
|
error = ENOTDIR;
|
|
goto done;
|
|
}
|
|
|
|
if (totp == fromtp) { /* same file */
|
|
goto done;
|
|
}
|
|
/*
|
|
* Unlink old target
|
|
* XXX truncate and remove if nlink < 2 ??
|
|
*/
|
|
error = tdirdelete(tm, toparent, totp, nnm, cred);
|
|
if (error) {
|
|
goto done;
|
|
}
|
|
} else if (error != ENOENT) {
|
|
tmpnode_put(fromtp);
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Link source to new target
|
|
*/
|
|
error = tdirenter(tm, toparent, nnm, fromtp, cred);
|
|
if (error) {
|
|
/*
|
|
* Re-enter old target into directory if necessary
|
|
*/
|
|
if (totp)
|
|
error = tdirenter(tm, toparent, nnm, totp, cred);
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* if renaming a directory moves it into a different parent directory,
|
|
* rename the .. entry to point at new parent
|
|
* always account for link to parent if renaming directories
|
|
*/
|
|
if (doingdirectory) {
|
|
if (fromparent != toparent) {
|
|
if (tdirlookup(fromtp, "..", &tmptp, cred) == 0) {
|
|
(void) tdirdelete(tm, fromtp, tmptp,
|
|
"..", cred);
|
|
tmpnode_put(tmptp);
|
|
/*
|
|
* tdirdelete decrements the count on the
|
|
* tmpnode we remove (i.e. parent of fromtp) and
|
|
* the directory we remove it from (i.e. fromtp)
|
|
* We need to bump up the link count on these
|
|
* because they'll be decremented again below
|
|
* when the source is unlinked.
|
|
*/
|
|
fromtp->tn_attr.va_nlink++;
|
|
tmptp->tn_attr.va_nlink++;
|
|
}
|
|
if (tdirenter(tm, fromtp, "..", toparent, cred) != 0)
|
|
panic("tmp_rename");
|
|
} else
|
|
toparent->tn_attr.va_nlink++;
|
|
}
|
|
|
|
/*
|
|
* Unlink source
|
|
*/
|
|
error = tdirdelete(tm, fromparent, fromtp, onm, cred);
|
|
if (error) {
|
|
goto done;
|
|
}
|
|
|
|
modified(fromparent);
|
|
modified(toparent);
|
|
done:
|
|
tmpnode_put(fromtp);
|
|
if (totp)
|
|
tmpnode_put(totp);
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
tmp_mkdir(dvp, nm, va, vpp, cred)
|
|
struct vnode *dvp;
|
|
char *nm;
|
|
struct vattr *va;
|
|
struct vnode **vpp;
|
|
struct ucred *cred;
|
|
{
|
|
struct tmpnode *parent = VP_TO_TN(dvp);
|
|
struct tmpnode *self;
|
|
struct tmount *tm = VP_TO_TM(dvp);
|
|
int error;
|
|
|
|
#ifdef TMPFSDEBUG
|
|
if (tmpfsdebug || tmpdirdebug)
|
|
printf("tmp_mkdir: parent %x nm %s\n", parent, nm);
|
|
#endif TMPFSDEBUG
|
|
|
|
VFS_RECORD(dvp->v_vfsp, VS_MKDIR, VS_CALL);
|
|
/*
|
|
* Make sure we have write permission on the parent directory
|
|
* before we allocate anything
|
|
*/
|
|
if (error = taccess(parent, cred, VWRITE)) {
|
|
tdirclose(parent);
|
|
return (error);
|
|
}
|
|
error = tdirlookup(parent, nm, &self, cred);
|
|
if (error == 0) {
|
|
tmpnode_put(self);
|
|
return (EEXIST);
|
|
}
|
|
if (error != ENOENT) {
|
|
return (error);
|
|
}
|
|
self = tmpnode_alloc(tm, VDIR);
|
|
if (self == NULL) {
|
|
return (ENOSPC);
|
|
}
|
|
newnode(self, S_IFDIR | (va->va_mode & 0777), cred->cr_uid,
|
|
cred->cr_gid);
|
|
*va = self->tn_attr;
|
|
if ((error = tdirenter(tm, self, ".", self, cred)) ||
|
|
(error = tdirenter(tm, self, "..", parent, cred)) ||
|
|
(error = tdirenter(tm, parent, nm, self, cred))) {
|
|
tmpnode_free(tm, self);
|
|
return (error);
|
|
}
|
|
tmpnode_unlock(self);
|
|
modified(parent);
|
|
*vpp = TP_TO_VP(self);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
tmp_rmdir(dvp, nm, cred)
|
|
struct vnode *dvp;
|
|
char *nm;
|
|
struct ucred *cred;
|
|
{
|
|
struct tmpnode *parent = VP_TO_TN(dvp);
|
|
struct tmpnode *self;
|
|
struct tmount *tm = VP_TO_TM(dvp);
|
|
int error = 0;
|
|
|
|
#ifdef TMPFSDEBUG
|
|
if (tmpfsdebug || tmpdirdebug)
|
|
printf("tmp_rmdir: parent %x nm %s\n", parent, nm);
|
|
#endif TMPFSDEBUG
|
|
|
|
VFS_RECORD(dvp->v_vfsp, VS_RMDIR, VS_CALL);
|
|
|
|
/*
|
|
* return error when removing . and ..
|
|
*/
|
|
if (strcmp(nm, ".") == 0)
|
|
return (EINVAL);
|
|
if (strcmp(nm, "..") == 0)
|
|
return (ENOTEMPTY);
|
|
error = tdirlookup(parent, nm, &self, cred);
|
|
if (error) {
|
|
return (error);
|
|
}
|
|
if (self->tn_attr.va_type != VDIR) {
|
|
tmpnode_put(self);
|
|
return (ENOTDIR);
|
|
}
|
|
if ((self->tn_attr.va_nlink > 2) ||
|
|
(self->tn_attr.va_size > TEMPTYDIRSIZE)) {
|
|
tmpnode_put(self);
|
|
return (ENOTEMPTY);
|
|
}
|
|
error = tdirdelete(tm, parent, self, nm, cred);
|
|
if (error) {
|
|
tmpnode_put(self);
|
|
} else {
|
|
self->tn_attr.va_nlink--;
|
|
(void) tmpnode_trunc(tm, self, (u_long)0);
|
|
tmpnode_put(self);
|
|
modified(parent);
|
|
}
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
tmp_readdir(vp, uiop, cred)
|
|
struct vnode *vp;
|
|
struct uio *uiop;
|
|
struct ucred *cred;
|
|
{
|
|
struct tmpnode *tp = VP_TO_TN(vp);
|
|
struct dirent d;
|
|
struct tdirent *tdp;
|
|
char *strcpy();
|
|
int error, offset;
|
|
|
|
VFS_RECORD(vp->v_vfsp, VS_READDIR, VS_CALL);
|
|
|
|
#ifdef TMPFSDEBUG
|
|
if (tmpfsdebug || tmpdirdebug)
|
|
printf("tmp_readdir: tp %x offset %d count %d\n", tp,
|
|
uiop->uio_offset, uiop->uio_iov->iov_len);
|
|
#endif TMPFSDEBUG
|
|
if (uiop->uio_iovcnt != 1) {
|
|
return (EINVAL);
|
|
}
|
|
error = tdiropen(tp, cred);
|
|
if (error) {
|
|
return (error);
|
|
}
|
|
if (uiop->uio_offset >= tp->tn_attr.va_size)
|
|
return (0);
|
|
if ((tdp = tp->tn_dir) == NULL) {
|
|
panic("empty directory");
|
|
}
|
|
|
|
for (offset = 0; tdp; tdp = tdp->td_next) {
|
|
if (offset >= uiop->uio_offset) {
|
|
d.d_namlen = tdp->td_namelen;
|
|
d.d_reclen = DIRSIZ(&d);
|
|
if (d.d_reclen > uiop->uio_iov->iov_len) {
|
|
break;
|
|
}
|
|
d.d_off = offset+tdp->td_reclen;
|
|
d.d_fileno = (u_long)tdp->td_tmpnode->tn_attr.va_nodeid;
|
|
(void) strcpy(d.d_name, tdp->td_name);
|
|
if (error = uiomove((char *)&d, (int)d.d_reclen,
|
|
UIO_READ, uiop))
|
|
break;
|
|
}
|
|
offset += tdp->td_reclen;
|
|
}
|
|
accessed(tp);
|
|
tdirclose(tp);
|
|
return (error);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
tmp_symlink(dvp, lnm, tva, tnm, cred)
|
|
struct vnode *dvp;
|
|
char *lnm;
|
|
struct vattr *tva;
|
|
char *tnm;
|
|
struct ucred *cred;
|
|
{
|
|
struct tmpnode *parent = VP_TO_TN(dvp);
|
|
struct tmpnode *self;
|
|
struct tmount *tm = VP_TO_TM(dvp);
|
|
char *cp, *strncpy();
|
|
int error, len;
|
|
|
|
#ifdef TMPFSDEBUG
|
|
if (tmpfsdebug || tmpdirdebug)
|
|
printf("tmp_symlink: parent %x lnm %s tnm %s\n", parent,
|
|
lnm, tnm);
|
|
#endif TMPFSDEBUG
|
|
|
|
error = tdirlookup(parent, lnm, &self, cred);
|
|
if (error == 0) {
|
|
tmpnode_put(self);
|
|
return (EEXIST);
|
|
}
|
|
if (error != ENOENT) {
|
|
return (error);
|
|
}
|
|
self = tmpnode_alloc(tm, VLNK);
|
|
if (self == NULL) {
|
|
return (ENOSPC);
|
|
}
|
|
newnode(self, S_IFLNK | 0777, cred->cr_uid, cred->cr_gid);
|
|
len = strlen(tnm);
|
|
if ((cp = tmp_memalloc(tm, (u_int)len)) == NULL) {
|
|
tmpnode_free(tm, self);
|
|
return (ENOSPC);
|
|
}
|
|
(void) strncpy(cp, tnm, len);
|
|
self->tn_symlink = cp;
|
|
self->tn_attr.va_size = len;
|
|
error = tdirenter(tm, parent, lnm, self, cred);
|
|
if (error) {
|
|
tmpnode_free(tm, self);
|
|
tmp_memfree(tm, cp, (u_int)len);
|
|
return (error);
|
|
}
|
|
tmpnode_put(self);
|
|
modified(parent);
|
|
return (0);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
tmp_readlink(vp, uiop, cred)
|
|
struct vnode *vp;
|
|
struct uio *uiop;
|
|
struct ucred *cred;
|
|
{
|
|
struct tmpnode *tp = VP_TO_TN(vp);
|
|
int error;
|
|
|
|
VFS_RECORD(vp->v_vfsp, VS_READLINK, VS_CALL);
|
|
|
|
if (tp->tn_attr.va_type != VLNK) {
|
|
return (EINVAL);
|
|
}
|
|
error = uiomove(tp->tn_symlink, (int)tp->tn_attr.va_size,
|
|
UIO_READ, uiop);
|
|
accessed(tp);
|
|
if (error) {
|
|
return (error);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
tmp_fsync(vp, cred)
|
|
struct vnode *vp;
|
|
struct ucred *cred;
|
|
{
|
|
VFS_RECORD(vp->v_vfsp, VS_FSYNC, VS_CALL);
|
|
return (0);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
tmp_inactive(vp, cred)
|
|
struct vnode *vp;
|
|
struct ucred *cred;
|
|
{
|
|
struct tmpnode *tp = VP_TO_TN(vp);
|
|
struct tmount *tm = VFSP_TO_TM(vp->v_vfsp);
|
|
|
|
VFS_RECORD(vp->v_vfsp, VS_INACTIVE, VS_CALL);
|
|
|
|
tmpnode_inactive(tm, tp);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
tmp_lockctl()
|
|
{
|
|
return (EINVAL);
|
|
}
|
|
|
|
/*
|
|
* nfs mounts of tmpfs file systems are not supported
|
|
*/
|
|
/*ARGSUSED*/
|
|
static int
|
|
tmp_fid(vp, fidpp)
|
|
struct vnode *vp;
|
|
struct fid **fidpp;
|
|
{
|
|
return (EINVAL);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
tmp_getpage(vp, off, len, protp, pl, plsz, seg, addr, rw, cred)
|
|
struct vnode *vp;
|
|
u_int off;
|
|
u_int *protp;
|
|
struct page *pl[];
|
|
u_int plsz;
|
|
struct seg *seg;
|
|
addr_t addr;
|
|
enum seg_rw rw;
|
|
struct ucred *cred;
|
|
{
|
|
return (0);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
tmp_putpage(vp, off, len, flags, cred)
|
|
struct vnode *vp;
|
|
u_int off, len;
|
|
int flags;
|
|
struct ucred *cred;
|
|
{
|
|
return (0);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
tmp_map(vp, off, as, addrp, len, prot, maxprot, flags, cred)
|
|
struct vnode *vp;
|
|
u_int off;
|
|
struct as *as;
|
|
addr_t *addrp;
|
|
u_int len;
|
|
u_int prot;
|
|
u_int maxprot;
|
|
u_int flags;
|
|
struct ucred *cred;
|
|
{
|
|
struct segvn_crargs vn_a;
|
|
struct tmpnode *tp = VP_TO_TN(vp);
|
|
|
|
VFS_RECORD(vp->v_vfsp, VS_MAP, VS_CALL);
|
|
|
|
#ifdef TMPFSDEBUG
|
|
if (tmpfsdebug)
|
|
printf("tmp_map: tp %x off %x len %x flags %x as %x\n",
|
|
tp, off, len, flags, as);
|
|
#endif TMPFSDEBUG
|
|
if ((int)off < 0 || (int)(off + len) < 0)
|
|
return (EINVAL);
|
|
|
|
if (vp->v_type != VREG)
|
|
return (ENODEV);
|
|
|
|
if (off + len > roundup(tp->tn_attr.va_size, PAGESIZE))
|
|
return (EINVAL);
|
|
|
|
if ((flags & MAP_FIXED) == 0) {
|
|
map_addr(addrp, len, (off_t)off, 1);
|
|
if (*addrp == NULL)
|
|
return (ENOMEM);
|
|
} else {
|
|
/*
|
|
* User specified address - blow away any previous mappings
|
|
*/
|
|
(void) as_unmap(as, *addrp, len);
|
|
}
|
|
|
|
vn_a.vp = NULL;
|
|
vn_a.offset = off;
|
|
vn_a.type = flags & MAP_TYPE;
|
|
vn_a.prot = prot;
|
|
vn_a.maxprot = maxprot;
|
|
vn_a.cred = cred;
|
|
vn_a.amp = tp->tn_amapp;
|
|
return (as_map(as, *addrp, len, segvn_create, (caddr_t)&vn_a));
|
|
}
|
|
|
|
static int
|
|
tmp_dump()
|
|
{
|
|
return (EINVAL);
|
|
}
|
|
|
|
static int
|
|
tmp_cmp(vp1, vp2)
|
|
struct vnode *vp1;
|
|
struct vnode *vp2;
|
|
{
|
|
return (vp1 == vp2);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
tmp_realvp(vp, vpp)
|
|
struct vnode *vp;
|
|
struct vnode **vpp;
|
|
{
|
|
return (EINVAL);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
tmp_cntl(vp, cmd, idata, odata, iflg, oflg)
|
|
struct vnode *vp;
|
|
caddr_t idata, odata;
|
|
{
|
|
return (ENOSYS);
|
|
}
|