#include <sys/param.h>
#include <sys/systm.h>
#include <sys/uio.h>
#include <sys/fcntl.h>
#include <sys/file.h>
#include <sys/file2.h>
#include <sys/stat.h>
#include <sys/proc.h>
#include <sys/caps.h>
#include <sys/mount.h>
#include <sys/nlookup.h>
#include <sys/vnode.h>
#include <sys/buf.h>
#include <sys/filio.h>
#include <sys/ttycom.h>
#include <sys/conf.h>
#include <sys/sysctl.h>
#include <sys/syslog.h>
#include <sys/spinlock.h>
#include <sys/spinlock2.h>
#include <sys/unistd.h>
#include <sys/mplock2.h>
#include <vm/vm_object.h>
#include <machine/limits.h>
static int vn_closefile (struct file *fp);
static int vn_ioctl (struct file *fp, u_long com, caddr_t data,
struct ucred *cred, struct sysmsg *msg);
static int vn_read (struct file *fp, struct uio *uio,
struct ucred *cred, int flags);
static int vn_kqfilter (struct file *fp, struct knote *kn);
static int vn_statfile (struct file *fp, struct stat *sb, struct ucred *cred);
static int vn_write (struct file *fp, struct uio *uio,
struct ucred *cred, int flags);
static int vn_seek (struct file *fp, off_t offset, int whence, off_t *res);
struct fileops vnode_fileops = {
.fo_read = vn_read,
.fo_write = vn_write,
.fo_ioctl = vn_ioctl,
.fo_kqfilter = vn_kqfilter,
.fo_stat = vn_statfile,
.fo_close = vn_closefile,
.fo_shutdown = nofo_shutdown,
.fo_seek = vn_seek
};
int
vn_open(struct nlookupdata *nd, struct file **fpp, int fmode, int cmode)
{
struct file *fp = fpp ? *fpp : NULL;
struct vnode *vp;
struct ucred *cred = nd->nl_cred;
struct vattr vat;
struct vattr *vap = &vat;
int error;
int vpexcl;
u_int flags;
uint64_t osize;
struct mount *mp;
if ((fmode & (FWRITE | O_TRUNC)) == O_TRUNC)
return(EACCES);
nd->nl_flags |= NLC_OPEN;
if (fmode & O_APPEND)
nd->nl_flags |= NLC_APPEND;
if (fmode & O_TRUNC)
nd->nl_flags |= NLC_TRUNCATE;
if (fmode & FREAD)
nd->nl_flags |= NLC_READ;
if (fmode & FWRITE)
nd->nl_flags |= NLC_WRITE;
if ((fmode & O_EXCL) == 0 && (fmode & O_NOFOLLOW) == 0)
nd->nl_flags |= NLC_FOLLOW;
if (fmode & O_CREAT) {
nd->nl_flags |= NLC_CREATE;
nd->nl_flags |= NLC_REFDVP;
bwillinode(1);
error = nlookup(nd);
if (error == EACCES && nd->nl_nch.ncp->nc_vp != NULL &&
(fmode & O_EXCL) && !nd->nl_dir_error)
{
error = EEXIST;
}
if (error == 0 && nd->nl_dvp == NULL)
error = EINVAL;
} else {
error = nlookup(nd);
}
if (error)
return (error);
again:
if ((fmode & (FWRITE | O_TRUNC)) ||
((fmode & O_CREAT) && nd->nl_nch.ncp->nc_vp == NULL)) {
error = ncp_writechk(&nd->nl_nch);
if (error)
return error;
}
vpexcl = 1;
if (fmode & O_CREAT) {
if (nd->nl_nch.ncp->nc_vp == NULL) {
VATTR_NULL(vap);
vap->va_type = VREG;
vap->va_mode = cmode;
vap->va_fuseflags = fmode;
if (fmode & O_EXCL)
vap->va_vaflags |= VA_EXCLUSIVE;
error = VOP_NCREATE(&nd->nl_nch, nd->nl_dvp, &vp,
nd->nl_cred, vap);
if (error)
return (error);
fmode &= ~O_TRUNC;
} else {
if (fmode & O_EXCL) {
error = EEXIST;
} else {
error = cache_vget(&nd->nl_nch, cred,
LK_EXCLUSIVE, &vp);
}
if (error)
return (error);
fmode &= ~O_CREAT;
}
} else {
if ((nd->nl_flags & NLC_SHAREDLOCK) &&
((nd->nl_flags & NLC_EXCLLOCK_IFEXEC) == 0 ||
nd->nl_nch.ncp->nc_flag & NCF_NOTX)) {
error = cache_vget(&nd->nl_nch, cred, LK_SHARED, &vp);
vpexcl = 0;
} else {
error = cache_vget(&nd->nl_nch, cred,
LK_EXCLUSIVE, &vp);
}
if (error)
return (error);
}
if (vp->v_type == VLNK) {
error = EMLINK;
goto bad;
}
if (vp->v_type == VSOCK) {
error = EOPNOTSUPP;
goto bad;
}
if (vp->v_type != VDIR && (fmode & O_DIRECTORY)) {
error = ENOTDIR;
goto bad;
}
if ((fmode & O_CREAT) == 0) {
if (fmode & (FWRITE | O_TRUNC)) {
if (vp->v_type == VDIR) {
error = EISDIR;
goto bad;
}
error = vn_writechk(vp);
if (error) {
if (error == ESTALE) {
u_int dummy_gen = 0;
vput(vp);
vp = NULL;
if (vpexcl == 0) {
cache_unlock(&nd->nl_nch);
cache_lock(&nd->nl_nch);
}
cache_setunresolved(&nd->nl_nch);
error = cache_resolve(&nd->nl_nch,
&dummy_gen,
cred);
if (error == 0)
goto again;
}
goto bad;
}
}
}
if (fmode & O_TRUNC) {
vn_unlock(vp);
vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
osize = vp->v_filesize;
VATTR_NULL(vap);
vap->va_size = 0;
error = VOP_SETATTR_FP(vp, vap, cred, fp);
if (error)
goto bad;
error = VOP_GETATTR(vp, vap);
if (error)
goto bad;
mp = vq_vptomp(vp);
VFS_ACCOUNT(mp, vap->va_uid, vap->va_gid, -osize);
}
flags = nd->nl_nch.ncp->nc_flag;
if ((flags & (NCF_UF_CACHE | NCF_UF_PCACHE)) &&
(flags & (NCF_SF_NOCACHE | NCF_SF_PNOCACHE)) == 0) {
vsetflags(vp, VSWAPCACHE);
} else {
vclrflags(vp, VSWAPCACHE);
}
if (fp) {
if (nd->nl_flags & NLC_APPENDONLY)
fmode |= FAPPENDONLY;
fp->f_nchandle = nd->nl_nch;
cache_zero(&nd->nl_nch);
cache_unlock(&fp->f_nchandle);
}
if (nd->nl_nch.ncp)
cache_put(&nd->nl_nch);
error = VOP_OPEN(vp, fmode, cred, fpp);
fp = fpp ? *fpp : NULL;
if (error) {
if (fp) {
fp->f_data = NULL;
fp->f_ops = &badfileops;
}
goto bad;
}
#if 0
KASSERT(vp->v_type != VREG || vp->v_object != NULL,
("vn_open: regular file was not VMIO enabled!"));
#endif
if (fp == NULL) {
nd->nl_open_vp = vp;
nd->nl_vp_fmode = fmode;
if ((nd->nl_flags & NLC_LOCKVP) == 0)
vn_unlock(vp);
} else {
vput(vp);
}
return (0);
bad:
if (vp)
vput(vp);
return (error);
}
int
vn_opendisk(const char *devname, int fmode, struct vnode **vpp)
{
struct vnode *vp;
int error;
if (strncmp(devname, "/dev/", 5) == 0)
devname += 5;
if ((vp = getsynthvnode(devname)) == NULL) {
error = ENODEV;
} else {
error = VOP_OPEN(vp, fmode, proc0.p_ucred, NULL);
vn_unlock(vp);
if (error) {
vrele(vp);
vp = NULL;
}
}
*vpp = vp;
return (error);
}
int
vn_writechk(struct vnode *vp)
{
if (vp->v_flag & VTEXT)
return (ETXTBSY);
if (vp->v_mount && (vp->v_mount->mnt_flag & MNT_RDONLY))
return (EROFS);
return 0;
}
int
ncp_writechk(struct nchandle *nch)
{
struct mount *mp;
if ((mp = nch->mount) != NULL) {
if (mp->mnt_flag & MNT_RDONLY)
return (EROFS);
if (mp->mnt_op->vfs_modifying != vfs_stdmodifying)
VFS_MODIFYING(mp);
}
return(0);
}
int
vn_close(struct vnode *vp, int flags, struct file *fp)
{
int error;
error = vn_lock(vp, LK_SHARED | LK_RETRY | LK_FAILRECLAIM);
if (error == 0) {
error = VOP_CLOSE(vp, flags, fp);
vn_unlock(vp);
}
vrele(vp);
return (error);
}
static __inline
int
sequential_heuristic(struct uio *uio, struct file *fp)
{
if ((uio->uio_offset == 0 && fp->f_seqcount > 0) ||
uio->uio_offset == fp->f_nextoff) {
int tmpseq = fp->f_seqcount;
tmpseq += howmany(uio->uio_resid, MAXBSIZE);
if (tmpseq > IO_SEQMAX)
tmpseq = IO_SEQMAX;
fp->f_seqcount = tmpseq;
return(fp->f_seqcount << IO_SEQSHIFT);
}
if (fp->f_seqcount > 1)
fp->f_seqcount = 1;
else
fp->f_seqcount = 0;
return(0);
}
static __inline off_t
vn_get_fpf_offset(struct file *fp)
{
u_int flags;
u_int nflags;
flags = fp->f_flag & ~FOFFSETLOCK;
if (atomic_cmpset_int(&fp->f_flag, flags, flags | FOFFSETLOCK))
return(fp->f_offset);
for (;;) {
flags = fp->f_flag;
if (flags & FOFFSETLOCK) {
nflags = flags | FOFFSETWAKE;
tsleep_interlock(&fp->f_flag, 0);
if (atomic_cmpset_int(&fp->f_flag, flags, nflags))
tsleep(&fp->f_flag, PINTERLOCKED, "fpoff", 0);
} else {
nflags = flags | FOFFSETLOCK;
if (atomic_cmpset_int(&fp->f_flag, flags, nflags))
break;
}
}
return(fp->f_offset);
}
static __inline void
vn_set_fpf_offset(struct file *fp, off_t offset)
{
u_int flags;
u_int nflags;
fp->f_offset = offset;
for (;;) {
flags = fp->f_flag;
nflags = flags & ~(FOFFSETLOCK | FOFFSETWAKE);
if (atomic_cmpset_int(&fp->f_flag, flags, nflags)) {
if (flags & FOFFSETWAKE)
wakeup(&fp->f_flag);
break;
}
}
}
static __inline off_t
vn_poll_fpf_offset(struct file *fp)
{
#if defined(__x86_64__)
return(fp->f_offset);
#else
off_t off = vn_get_fpf_offset(fp);
vn_set_fpf_offset(fp, off);
return(off);
#endif
}
int
vn_rdwr(enum uio_rw rw, struct vnode *vp, caddr_t base, int len,
off_t offset, enum uio_seg segflg, int ioflg,
struct ucred *cred, int *aresid)
{
struct uio auio;
struct iovec aiov;
int error;
if ((ioflg & IO_NODELOCKED) == 0)
vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
auio.uio_iov = &aiov;
auio.uio_iovcnt = 1;
aiov.iov_base = base;
aiov.iov_len = len;
auio.uio_resid = len;
auio.uio_offset = offset;
auio.uio_segflg = segflg;
auio.uio_rw = rw;
auio.uio_td = curthread;
if (rw == UIO_READ) {
error = VOP_READ(vp, &auio, ioflg, cred);
} else {
error = VOP_WRITE(vp, &auio, ioflg, cred);
}
if (aresid)
*aresid = auio.uio_resid;
else
if (auio.uio_resid && error == 0)
error = EIO;
if ((ioflg & IO_NODELOCKED) == 0)
vn_unlock(vp);
return (error);
}
int
vn_rdwr_inchunks(enum uio_rw rw, struct vnode *vp, caddr_t base, int len,
off_t offset, enum uio_seg segflg, int ioflg,
struct ucred *cred, int *aresid)
{
int error = 0;
do {
int chunk;
chunk = MAXBSIZE - (uoff_t)offset % MAXBSIZE;
if (chunk > len)
chunk = len;
if (vp->v_type == VREG && (ioflg & IO_RECURSE) == 0) {
switch(rw) {
case UIO_READ:
bwillread(chunk);
break;
case UIO_WRITE:
bwillwrite(chunk);
break;
}
}
error = vn_rdwr(rw, vp, base, chunk, offset, segflg,
ioflg, cred, aresid);
len -= chunk;
if (error)
break;
offset += chunk;
base += chunk;
lwkt_user_yield();
} while (len);
if (aresid)
*aresid += len;
return (error);
}
static int
vn_read(struct file *fp, struct uio *uio, struct ucred *cred, int flags)
{
struct vnode *vp;
int error, ioflag;
KASSERT(uio->uio_td == curthread,
("uio_td %p is not td %p", uio->uio_td, curthread));
vp = (struct vnode *)fp->f_data;
ioflag = 0;
if (flags & O_FBLOCKING) {
} else if (flags & O_FNONBLOCKING) {
ioflag |= IO_NDELAY;
} else if (fp->f_flag & FNONBLOCK) {
ioflag |= IO_NDELAY;
}
if (fp->f_flag & O_DIRECT) {
ioflag |= IO_DIRECT;
}
if ((flags & O_FOFFSET) == 0 && (vp->v_flag & VNOTSEEKABLE) == 0)
uio->uio_offset = vn_get_fpf_offset(fp);
vn_lock(vp, LK_SHARED | LK_RETRY);
ioflag |= sequential_heuristic(uio, fp);
error = VOP_READ_FP(vp, uio, ioflag, cred, fp);
fp->f_nextoff = uio->uio_offset;
vn_unlock(vp);
if ((flags & O_FOFFSET) == 0 && (vp->v_flag & VNOTSEEKABLE) == 0)
vn_set_fpf_offset(fp, uio->uio_offset);
return (error);
}
static int
vn_write(struct file *fp, struct uio *uio, struct ucred *cred, int flags)
{
struct vnode *vp;
int error, ioflag;
KASSERT(uio->uio_td == curthread,
("uio_td %p is not p %p", uio->uio_td, curthread));
vp = (struct vnode *)fp->f_data;
ioflag = IO_UNIT;
if (vp->v_type == VREG &&
((fp->f_flag & O_APPEND) || (flags & O_FAPPEND))) {
ioflag |= IO_APPEND;
}
if (flags & O_FBLOCKING) {
} else if (flags & O_FNONBLOCKING) {
ioflag |= IO_NDELAY;
} else if (fp->f_flag & FNONBLOCK) {
ioflag |= IO_NDELAY;
}
if (fp->f_flag & O_DIRECT) {
ioflag |= IO_DIRECT;
}
if (flags & O_FASYNCWRITE) {
} else if (flags & O_FSYNCWRITE) {
ioflag |= IO_SYNC;
} else if (fp->f_flag & O_FSYNC) {
ioflag |= IO_SYNC;
}
if (vp->v_mount && (vp->v_mount->mnt_flag & MNT_SYNCHRONOUS))
ioflag |= IO_SYNC;
if ((flags & O_FOFFSET) == 0)
uio->uio_offset = vn_get_fpf_offset(fp);
if (vp->v_mount)
VFS_MODIFYING(vp->v_mount);
vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
ioflag |= sequential_heuristic(uio, fp);
error = VOP_WRITE_FP(vp, uio, ioflag, cred, fp);
fp->f_nextoff = uio->uio_offset;
vn_unlock(vp);
if ((flags & O_FOFFSET) == 0)
vn_set_fpf_offset(fp, uio->uio_offset);
return (error);
}
static int
vn_statfile(struct file *fp, struct stat *sb, struct ucred *cred)
{
struct vnode *vp;
int error;
vp = (struct vnode *)fp->f_data;
error = vn_stat(vp, sb, cred);
return (error);
}
int
vn_stat(struct vnode *vp, struct stat *sb, struct ucred *cred)
{
struct vattr vattr;
struct vattr *vap;
int error;
u_short mode;
cdev_t dev;
vap = &vattr;
error = VOP_GETATTR(vp, vap);
if (error)
return (error);
sb->st_lspare = 0;
sb->st_qspare2 = 0;
if (vap->va_fsid != VNOVAL)
sb->st_dev = vap->va_fsid;
else
sb->st_dev = vp->v_mount->mnt_stat.f_fsid.val[0];
sb->st_ino = vap->va_fileid;
mode = vap->va_mode;
switch (vap->va_type) {
case VREG:
mode |= S_IFREG;
break;
case VDATABASE:
mode |= S_IFDB;
break;
case VDIR:
mode |= S_IFDIR;
break;
case VBLK:
mode |= S_IFBLK;
break;
case VCHR:
mode |= S_IFCHR;
break;
case VLNK:
mode |= S_IFLNK;
if (vp->v_mount->mnt_flag & MNT_NOSYMFOLLOW)
sb->st_mode &= ~ACCESSPERMS;
else
sb->st_mode |= ACCESSPERMS;
break;
case VSOCK:
mode |= S_IFSOCK;
break;
case VFIFO:
mode |= S_IFIFO;
break;
default:
return (EBADF);
}
sb->st_mode = mode;
if (vap->va_nlink > (nlink_t)-1)
sb->st_nlink = (nlink_t)-1;
else
sb->st_nlink = vap->va_nlink;
sb->st_uid = vap->va_uid;
sb->st_gid = vap->va_gid;
sb->st_rdev = devid_from_dev(vp->v_rdev);
sb->st_size = vap->va_size;
sb->st_atimespec = vap->va_atime;
sb->st_mtimespec = vap->va_mtime;
sb->st_ctimespec = vap->va_ctime;
if (vp->v_type == VCHR || vp->v_type == VBLK) {
dev = vp->v_rdev;
if (dev != NULL) {
if (dev->si_lastread) {
sb->st_atimespec.tv_sec = time_second +
(dev->si_lastread -
time_uptime);
sb->st_atimespec.tv_nsec = 0;
}
if (dev->si_lastwrite) {
sb->st_mtimespec.tv_sec = time_second +
(dev->si_lastwrite -
time_uptime);
sb->st_mtimespec.tv_nsec = 0;
}
}
}
if (vap->va_type == VREG) {
sb->st_blksize = vap->va_blocksize;
} else if (vn_isdisk(vp, NULL)) {
dev = vp->v_rdev;
sb->st_blksize = dev->si_bsize_best;
if (sb->st_blksize < dev->si_bsize_phys)
sb->st_blksize = dev->si_bsize_phys;
if (sb->st_blksize < BLKDEV_IOSIZE)
sb->st_blksize = BLKDEV_IOSIZE;
} else {
sb->st_blksize = PAGE_SIZE;
}
sb->st_flags = vap->va_flags;
error = caps_priv_check(cred, SYSCAP_NOVFS_GENERATION);
if (error)
sb->st_gen = 0;
else
sb->st_gen = (u_int32_t)vap->va_gen;
sb->st_blocks = vap->va_bytes / S_BLKSIZE;
sb->__old_st_blksize = sb->st_blksize;
return (0);
}
static int
vn_ioctl(struct file *fp, u_long com, caddr_t data, struct ucred *ucred,
struct sysmsg *msg)
{
struct vnode *vp = ((struct vnode *)fp->f_data);
struct vnode *ovp;
struct vattr vattr;
int error;
off_t size;
switch (vp->v_type) {
case VREG:
case VDIR:
if (com == FIONREAD) {
error = VOP_GETATTR(vp, &vattr);
if (error)
break;
size = vattr.va_size;
if ((vp->v_flag & VNOTSEEKABLE) == 0)
size -= vn_poll_fpf_offset(fp);
if (size > 0x7FFFFFFF)
size = 0x7FFFFFFF;
*(int *)data = size;
error = 0;
break;
}
if (com == FIOASYNC) {
error = 0;
break;
}
default:
#if 0
return (ENOTTY);
#endif
case VFIFO:
case VCHR:
case VBLK:
if (com == FIODTYPE) {
if (vp->v_type != VCHR && vp->v_type != VBLK) {
error = ENOTTY;
break;
}
*(int *)data = dev_dflags(vp->v_rdev) & D_TYPEMASK;
error = 0;
break;
}
error = VOP_IOCTL(vp, com, data, fp->f_flag, ucred, msg);
if (error == 0 && com == TIOCSCTTY) {
struct proc *p = curthread->td_proc;
struct session *sess;
if (p == NULL) {
error = ENOTTY;
break;
}
get_mplock();
sess = p->p_session;
if (sess->s_ttyvp == vp) {
error = 0;
rel_mplock();
break;
}
ovp = sess->s_ttyvp;
vref(vp);
sess->s_ttyvp = vp;
if (ovp)
vrele(ovp);
rel_mplock();
}
break;
}
return (error);
}
int
vn_lock(struct vnode *vp, int flags)
{
int error;
do {
error = lockmgr(&vp->v_lock, flags);
if (error == 0)
break;
} while (flags & LK_RETRY);
if (error == 0 && (vp->v_flag & VRECLAIMED)) {
if (flags & LK_FAILRECLAIM) {
lockmgr(&vp->v_lock, LK_RELEASE);
error = ENOENT;
}
}
return (error);
}
int
vn_relock(struct vnode *vp, int flags)
{
int error;
do {
error = lockmgr(&vp->v_lock, flags);
if (error == 0)
break;
} while (flags & LK_RETRY);
return error;
}
#ifdef DEBUG_VN_UNLOCK
void
debug_vn_unlock(struct vnode *vp, const char *filename, int line)
{
kprintf("vn_unlock from %s:%d\n", filename, line);
lockmgr(&vp->v_lock, LK_RELEASE);
}
#else
void
vn_unlock(struct vnode *vp)
{
lockmgr(&vp->v_lock, LK_RELEASE);
}
#endif
int
vn_islocked(struct vnode *vp)
{
return (lockstatus(&vp->v_lock, curthread));
}
int
vn_islocked_unlock(struct vnode *vp)
{
int vpls;
vpls = lockstatus(&vp->v_lock, curthread);
if (vpls == LK_EXCLUSIVE)
lockmgr(&vp->v_lock, LK_RELEASE);
return(vpls);
}
void
vn_islocked_relock(struct vnode *vp, int vpls)
{
int error;
if (vpls == LK_EXCLUSIVE)
error = lockmgr(&vp->v_lock, vpls);
}
static int
vn_closefile(struct file *fp)
{
int error;
fp->f_ops = &badfileops;
error = vn_close(((struct vnode *)fp->f_data), fp->f_flag, fp);
return (error);
}
static int
vn_kqfilter(struct file *fp, struct knote *kn)
{
int error;
error = VOP_KQFILTER(((struct vnode *)fp->f_data), kn);
return (error);
}
int
vn_bmap_seekhole_locked(struct vnode *vp, u_long cmd, off_t *off,
struct ucred *cred)
{
struct vattr vattr;
off_t size;
uint64_t bsize;
off_t noff, doff;
int error;
KASSERT(cmd == FIOSEEKHOLE || cmd == FIOSEEKDATA,
("%s: Wrong command %lu", __func__, cmd));
KKASSERT(vn_islocked(vp) == LK_EXCLUSIVE);
if (vp->v_type != VREG) {
error = ENOTTY;
goto out;
}
error = VOP_GETATTR(vp, &vattr);
if (vattr.va_size <= OFF_MAX)
size = vattr.va_size;
else
error = EFBIG;
if (error != 0)
goto out;
noff = *off;
if (noff < 0 || noff >= size) {
error = ENXIO;
goto out;
}
vm_object_page_clean(vp->v_object, 0, 0, OBJPC_SYNC);
bsize = vp->v_mount->mnt_stat.f_iosize;
for (; noff < size; noff += bsize - noff % bsize) {
error = VOP_BMAP(vp, noff, &doff, NULL, NULL, BUF_CMD_SEEK);
if (error == EOPNOTSUPP) {
error = ENOTTY;
goto out;
}
if ((doff == NOOFFSET && cmd == FIOSEEKHOLE) ||
(doff != NOOFFSET && cmd == FIOSEEKDATA)) {
if (noff < *off)
noff = *off;
goto out;
}
}
if (noff > size)
noff = size;
if (cmd == FIOSEEKDATA)
error = ENXIO;
out:
if (error == 0)
*off = noff;
return (error);
}
int
vn_bmap_seekhole(struct vnode *vp, u_long cmd, off_t *off, struct ucred *cred)
{
int error;
KASSERT(cmd == FIOSEEKHOLE || cmd == FIOSEEKDATA,
("%s: Wrong command %lu", __func__, cmd));
if (vn_lock(vp, LK_EXCLUSIVE) != 0)
return (EBADF);
error = vn_bmap_seekhole_locked(vp, cmd, off, cred);
vn_unlock(vp);
return (error);
}
int
vn_seek(struct file *fp, off_t offset, int whence, off_t *res)
{
struct thread *td = curthread;
struct vnode *vp;
struct vattr_lite lva;
off_t new_offset;
int error;
vp = (struct vnode *)fp->f_data;
switch (whence) {
case L_INCR:
spin_lock(&fp->f_spin);
new_offset = fp->f_offset + offset;
error = 0;
break;
case L_XTND:
error = VOP_GETATTR_LITE(vp, &lva);
spin_lock(&fp->f_spin);
new_offset = offset + lva.va_size;
break;
case L_SET:
new_offset = offset;
error = 0;
spin_lock(&fp->f_spin);
break;
case SEEK_DATA:
error = fo_ioctl(fp, FIOSEEKDATA, (caddr_t)&offset,
td->td_ucred, NULL);
if (error == ENOTTY)
error = EINVAL;
new_offset = offset;
spin_lock(&fp->f_spin);
break;
case SEEK_HOLE:
error = fo_ioctl(fp, FIOSEEKHOLE, (caddr_t)&offset,
td->td_ucred, NULL);
if (error == ENOTTY)
error = EINVAL;
new_offset = offset;
spin_lock(&fp->f_spin);
break;
default:
new_offset = 0;
error = EINVAL;
spin_lock(&fp->f_spin);
break;
}
if (error == 0) {
if (new_offset < 0 &&
(vp->v_type == VREG || vp->v_type == VDIR)) {
error = EINVAL;
} else {
fp->f_offset = new_offset;
}
}
*res = fp->f_offset;
spin_unlock(&fp->f_spin);
return (error);
}