#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/fcntl.h>
#include <sys/kernel.h>
#include <sys/file.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/lockf.h>
#include <sys/mbuf.h>
#include <sys/mount.h>
#include <sys/namei.h>
#include <sys/proc.h>
#include <sys/resourcevar.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <sys/unistd.h>
#include <sys/user.h>
#include <sys/vnode.h>
#include <kern/thread_act.h>
#include <machine/limits.h>
#include <net/if.h>
#include <nfs/rpcv2.h>
#include <nfs/nfsproto.h>
#include <nfs/nfs.h>
#include <nfs/nfsmount.h>
#include <nfs/nfsnode.h>
#include <nfs/nfs_lock.h>
#include <nfs/nlminfo.h>
#define OFF_MAX QUAD_MAX
uint64_t nfsadvlocks = 0;
struct timeval nfsadvlock_longest = {0, 0};
struct timeval nfsadvlocks_time = {0, 0};
pid_t nfslockdpid = 0;
struct file *nfslockdfp = 0;
int nfslockdwaiting = 0;
int nfslockdfifowritten = 0;
int nfslockdfifolock = 0;
#define NFSLOCKDFIFOLOCK_LOCKED 1
#define NFSLOCKDFIFOLOCK_WANT 2
int
nfs_dolock(struct vop_advlock_args *ap)
{
LOCKD_MSG msg;
struct nameidata nd;
struct vnode *vp, *wvp;
struct nfsnode *np;
int error, error1;
struct flock *fl;
int fmode, ioflg;
struct proc *p;
struct uthread *ut;
struct timeval elapsed;
struct nfsmount *nmp;
struct vattr vattr;
off_t start, end;
ut = get_bsdthread_info(current_act());
p = current_proc();
vp = ap->a_vp;
fl = ap->a_fl;
np = VTONFS(vp);
nmp = VFSTONFS(vp->v_mount);
if (!nmp)
return (ENXIO);
if (nmp->nm_flag & NFSMNT_NOLOCKS)
return (EOPNOTSUPP);
if (fl->l_whence != SEEK_END) {
if ((fl->l_whence != SEEK_CUR && fl->l_whence != SEEK_SET) ||
fl->l_start < 0 ||
(fl->l_len > 0 && fl->l_len - 1 > OFF_MAX - fl->l_start) ||
(fl->l_len < 0 && fl->l_start + fl->l_len < 0))
return (EINVAL);
}
if (!nfslockdfp || !(wvp = (struct vnode *)nfslockdfp->f_data)) {
if (!nfslockdwaiting)
return (EOPNOTSUPP);
if (!nfslockdfp && (fl->l_type == F_UNLCK))
return (EINVAL);
(void)wakeup((void *)&nfslockdwaiting);
tsleep((void *)&nfslockdfp, PCATCH | PUSER, "lockd", 60*hz);
if (!nfslockdfp || !(wvp = (struct vnode *)nfslockdfp->f_data))
return (EOPNOTSUPP);
}
VREF(wvp);
if (ut->uu_nlminfo == NULL) {
if (ap->a_op == F_UNLCK) {
vrele(wvp);
return (0);
}
MALLOC(ut->uu_nlminfo, struct nlminfo *,
sizeof(struct nlminfo), M_LOCKF, M_WAITOK | M_ZERO);
ut->uu_nlminfo->pid_start = p->p_stats->p_start;
}
msg.lm_version = LOCKD_MSG_VERSION;
msg.lm_msg_ident.pid = p->p_pid;
msg.lm_msg_ident.ut = ut;
msg.lm_msg_ident.pid_start = ut->uu_nlminfo->pid_start;
msg.lm_msg_ident.msg_seq = ++(ut->uu_nlminfo->msg_seq);
switch (fl->l_whence) {
case SEEK_SET:
case SEEK_CUR:
start = fl->l_start;
break;
case SEEK_END:
if (np->n_flag & NMODIFIED) {
np->n_xid = 0;
error = nfs_vinvalbuf(vp, V_SAVE, p->p_ucred, p, 1);
if (error) {
vrele(wvp);
return (error);
}
}
np->n_xid = 0;
error = VOP_GETATTR(vp, &vattr, p->p_ucred, p);
if (error) {
vrele(wvp);
return (error);
}
start = np->n_size + fl->l_start;
break;
default:
vrele(wvp);
return (EINVAL);
}
if (fl->l_len == 0)
end = -1;
else if (fl->l_len > 0)
end = start + fl->l_len - 1;
else {
end = start - 1;
start += fl->l_len;
}
if (start < 0) {
vrele(wvp);
return (EINVAL);
}
msg.lm_fl = *fl;
msg.lm_fl.l_start = start;
if (end != -1)
msg.lm_fl.l_len = end - start + 1;
msg.lm_wait = ap->a_flags & F_WAIT;
msg.lm_getlk = ap->a_op == F_GETLK;
nmp = VFSTONFS(vp->v_mount);
if (!nmp) {
vrele(wvp);
return (ENXIO);
}
bcopy(mtod(nmp->nm_nam, struct sockaddr *), &msg.lm_addr,
min(sizeof msg.lm_addr,
mtod(nmp->nm_nam, struct sockaddr *)->sa_len));
msg.lm_fh_len = NFS_ISV3(vp) ? VTONFS(vp)->n_fhsize : NFSX_V2FH;
bcopy(VTONFS(vp)->n_fhp, msg.lm_fh, msg.lm_fh_len);
msg.lm_nfsv3 = NFS_ISV3(vp);
cru2x(p->p_ucred, &msg.lm_cred);
microuptime(&ut->uu_nlminfo->nlm_lockstart);
fmode = FFLAGS(O_WRONLY);
if ((error = VOP_OPEN(wvp, fmode, kernproc->p_ucred, p))) {
vrele(wvp);
return (error);
}
++wvp->v_writecount;
#define IO_NOMACCHECK 0;
ioflg = IO_UNIT | IO_NOMACCHECK;
for (;;) {
VOP_LEASE(wvp, p, kernproc->p_ucred, LEASE_WRITE);
while (nfslockdfifolock & NFSLOCKDFIFOLOCK_LOCKED) {
nfslockdfifolock |= NFSLOCKDFIFOLOCK_WANT;
if (tsleep((void *)&nfslockdfifolock, PCATCH | PUSER, "lockdfifo", 20*hz))
break;
}
nfslockdfifolock |= NFSLOCKDFIFOLOCK_LOCKED;
error = vn_rdwr(UIO_WRITE, wvp, (caddr_t)&msg, sizeof(msg), 0,
UIO_SYSSPACE, ioflg, kernproc->p_ucred, NULL, p);
nfslockdfifowritten = 1;
nfslockdfifolock &= ~NFSLOCKDFIFOLOCK_LOCKED;
if (nfslockdfifolock & NFSLOCKDFIFOLOCK_WANT) {
nfslockdfifolock &= ~NFSLOCKDFIFOLOCK_WANT;
wakeup((void *)&nfslockdfifolock);
}
if (nfslockdwaiting)
(void)wakeup((void *)&nfslockdwaiting);
if (error && (((ioflg & IO_NDELAY) == 0) || error != EAGAIN)) {
break;
}
if (fl->l_type == F_UNLCK)
break;
if ((error = tsleep((void *)ut->uu_nlminfo,
PCATCH | PUSER, "lockd", 20*hz)) != 0) {
if (error == EWOULDBLOCK) {
ioflg |= IO_NDELAY;
continue;
}
break;
}
if (msg.lm_getlk && ut->uu_nlminfo->retcode == 0) {
if (ut->uu_nlminfo->set_getlk) {
fl->l_pid = ut->uu_nlminfo->getlk_pid;
fl->l_start = ut->uu_nlminfo->getlk_start;
fl->l_len = ut->uu_nlminfo->getlk_len;
fl->l_whence = SEEK_SET;
} else {
fl->l_type = F_UNLCK;
}
}
error = ut->uu_nlminfo->retcode;
break;
}
nfsadvlocks++;
microuptime(&elapsed);
timevalsub(&elapsed, &ut->uu_nlminfo->nlm_lockstart);
if (timevalcmp(&elapsed, &nfsadvlock_longest, >))
nfsadvlock_longest = elapsed;
timevaladd(&nfsadvlocks_time, &elapsed);
timerclear(&ut->uu_nlminfo->nlm_lockstart);
error1 = vn_close(wvp, FWRITE, kernproc->p_ucred, p);
return (error != 0 ? error : error1);
}
int
nfslockdans(struct proc *p, struct lockd_ans *ansp)
{
struct proc *targetp;
struct uthread *targetut, *uth;
int error;
if ((error = suser(p->p_ucred, &p->p_acflag)) != 0 &&
p->p_cred->p_svuid != 0)
return (error);
if (ansp->la_vers != LOCKD_ANS_VERSION)
return (EINVAL);
if ((targetp = pfind(ansp->la_msg_ident.pid)) == NULL)
return (ESRCH);
targetut = ansp->la_msg_ident.ut;
TAILQ_FOREACH(uth, &targetp->p_uthlist, uu_list) {
if (uth == targetut)
break;
}
if (uth == NULL || uth != targetut || targetut->uu_nlminfo == NULL)
return (EPIPE);
if (ansp->la_msg_ident.msg_seq != -1) {
if (timevalcmp(&targetut->uu_nlminfo->pid_start,
&ansp->la_msg_ident.pid_start, !=))
return (EPIPE);
if (targetut->uu_nlminfo->msg_seq != ansp->la_msg_ident.msg_seq)
return (0);
}
targetut->uu_nlminfo->retcode = ansp->la_errno;
targetut->uu_nlminfo->set_getlk = ansp->la_getlk_set;
targetut->uu_nlminfo->getlk_pid = ansp->la_getlk_pid;
targetut->uu_nlminfo->getlk_start = ansp->la_getlk_start;
targetut->uu_nlminfo->getlk_len = ansp->la_getlk_len;
(void)wakeup((void *)targetut->uu_nlminfo);
return (0);
}
int
nfslockdfd(struct proc *p, int fd)
{
int error;
struct file *fp, *ofp;
error = suser(p->p_ucred, &p->p_acflag);
if (error)
return (error);
if (fd < 0) {
fp = 0;
} else {
error = getvnode(p, fd, &fp);
if (error)
return (error);
(void)fref(fp);
}
ofp = nfslockdfp;
nfslockdfp = fp;
if (ofp)
(void)frele(ofp);
nfslockdpid = nfslockdfp ? p->p_pid : 0;
(void)wakeup((void *)&nfslockdfp);
return (0);
}
int
nfslockdwait(struct proc *p)
{
int error;
struct file *fp, *ofp;
if (p->p_pid != nfslockdpid) {
error = suser(p->p_ucred, &p->p_acflag);
if (error)
return (error);
}
if (nfslockdwaiting)
return (EBUSY);
if (nfslockdfifowritten) {
nfslockdfifowritten = 0;
return (0);
}
nfslockdwaiting = 1;
tsleep((void *)&nfslockdwaiting, PCATCH | PUSER, "lockd", 0);
nfslockdwaiting = 0;
return (0);
}