1909 lines
43 KiB
C
1909 lines
43 KiB
C
#ident "@(#)nfs_server.c 1.1 92/07/30 SMI"
|
|
|
|
/*
|
|
* Copyright (c) 1987 by Sun Microsystems, Inc.
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/user.h>
|
|
#include <sys/buf.h>
|
|
#include <sys/vfs.h>
|
|
#include <sys/vnode.h>
|
|
#include <sys/pathname.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/file.h>
|
|
#include <sys/socketvar.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/mbuf.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/syslog.h>
|
|
#include <netinet/in.h>
|
|
#include <rpc/types.h>
|
|
#include <rpc/auth.h>
|
|
#include <rpc/auth_unix.h>
|
|
#include <rpc/auth_des.h>
|
|
#include <rpc/svc.h>
|
|
#include <rpc/xdr.h>
|
|
#include <nfs/nfs.h>
|
|
#include <nfs/export.h>
|
|
#include <sys/dirent.h>
|
|
#include <vm/hat.h>
|
|
#include <vm/as.h>
|
|
#include <vm/seg.h>
|
|
#include <vm/seg_map.h>
|
|
#include <machine/seg_kmem.h>
|
|
#include <machine/mmu.h>
|
|
|
|
extern struct vnodeops nfs_vnodeops;
|
|
/*
|
|
* states of requests for duplicate request caching
|
|
*/
|
|
#define DUP_INPROGRESS 0x01 /* request already going */
|
|
#define DUP_DONE 0x02 /* request done */
|
|
#define DUP_DROP 0x03 /* drop the request */
|
|
|
|
/*
|
|
* rpc service program version range supported
|
|
*/
|
|
#define VERSIONMIN 2
|
|
#define VERSIONMAX 2
|
|
|
|
/*
|
|
* Returns true iff exported filesystem is read-only
|
|
*/
|
|
#define rdonly(exi, req) \
|
|
(((exi)->exi_export.ex_flags & EX_RDONLY) || \
|
|
(((exi)->exi_export.ex_flags & EX_RDMOSTLY) && \
|
|
!hostinlist((struct sockaddr *)svc_getcaller((req)->rq_xprt), \
|
|
&(exi)->exi_export.ex_writeaddrs)))
|
|
|
|
struct vnode *fhtovp();
|
|
struct file *getsock();
|
|
void svcerr_progvers();
|
|
void rfs_dispatch();
|
|
|
|
#ifdef NFSDEBUG
|
|
extern int nfsdebug;
|
|
#endif
|
|
|
|
struct {
|
|
u_int ncalls; /* number of calls received */
|
|
u_int nbadcalls; /* calls that failed */
|
|
u_int reqs[32]; /* count for each request */
|
|
} svstat;
|
|
|
|
int nfs_chars = 50000;
|
|
static int nfsd_count = 0;
|
|
|
|
/*
|
|
* NFS Server system call.
|
|
* Does all of the work of running a NFS server.
|
|
* sock is the fd of an open UDP socket.
|
|
*/
|
|
nfs_svc(uap)
|
|
struct a {
|
|
int sock;
|
|
} *uap;
|
|
{
|
|
struct socket *so;
|
|
struct file *fp;
|
|
SVCXPRT *xprt;
|
|
u_long vers;
|
|
int error;
|
|
char exitsig;
|
|
|
|
fp = getsock(uap->sock);
|
|
if (fp == NULL) {
|
|
u.u_error = EBADF;
|
|
return;
|
|
}
|
|
so = (struct socket *)fp->f_data;
|
|
|
|
/*
|
|
* Allocate extra space for this socket, to minimize
|
|
* lost requests for NFS. We don't want everyone to do
|
|
* this, so do it here, rather than in udp_usrreq().
|
|
*/
|
|
error = soreserve(so, nfs_chars,
|
|
nfs_chars + 2 *(sizeof (struct sockaddr)));
|
|
if (error) {
|
|
u.u_error = error;
|
|
return;
|
|
}
|
|
|
|
/* Now, release client memory; we never return back to user */
|
|
relvm(u.u_procp);
|
|
xprt = svckudp_create(so, NFS_PORT);
|
|
for (vers = VERSIONMIN; vers <= VERSIONMAX; vers++) {
|
|
(void) svc_register(xprt, NFS_PROGRAM, vers, rfs_dispatch,
|
|
FALSE);
|
|
}
|
|
if (setjmp(&u.u_qsave)) {
|
|
if (--nfsd_count == 0)
|
|
for (vers = VERSIONMIN; vers <= VERSIONMAX; vers++) {
|
|
svc_unregister(NFS_PROGRAM, vers);
|
|
}
|
|
SVC_DESTROY(xprt);
|
|
exitsig = u.u_procp->p_cursig;
|
|
if (exitsig != SIGTERM && exitsig != SIGKILL) {
|
|
log(LOG_WARNING,
|
|
"nfssvc (pid %d) exiting on signal %d\n",
|
|
u.u_procp->p_pid, exitsig);
|
|
}
|
|
u.u_error = EINTR; /* not needed; may help debugging */
|
|
exit(0);
|
|
} else {
|
|
nfsd_count++;
|
|
svc_run(xprt); /* never returns */
|
|
}
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
|
|
/*
|
|
* These are the interface routines for the server side of the
|
|
* Networked File System. See the NFS protocol specification
|
|
* for a description of this interface.
|
|
*/
|
|
|
|
|
|
/*
|
|
* Get file attributes.
|
|
* Returns the current attributes of the file with the given fhandle.
|
|
*/
|
|
int
|
|
rfs_getattr(fhp, ns, exi)
|
|
fhandle_t *fhp;
|
|
register struct nfsattrstat *ns;
|
|
struct exportinfo *exi;
|
|
{
|
|
int error;
|
|
register struct vnode *vp;
|
|
struct vattr va;
|
|
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 4, "rfs_getattr fh %x %x %d\n",
|
|
fhp->fh_fsid.val[0], fhp->fh_fsid.val[1], fhp->fh_len);
|
|
#endif
|
|
vp = fhtovp(fhp, exi);
|
|
if (vp == NULL) {
|
|
ns->ns_status = NFSERR_STALE;
|
|
return;
|
|
}
|
|
error = VOP_GETATTR(vp, &va, u.u_cred);
|
|
if (!error) {
|
|
vattr_to_nattr(&va, &ns->ns_attr);
|
|
}
|
|
ns->ns_status = puterrno(error);
|
|
VN_RELE(vp);
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 5, "rfs_getattr: returning %d\n", error);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Set file attributes.
|
|
* Sets the attributes of the file with the given fhandle. Returns
|
|
* the new attributes.
|
|
*/
|
|
int
|
|
rfs_setattr(args, ns, exi, req)
|
|
struct nfssaargs *args;
|
|
register struct nfsattrstat *ns;
|
|
struct exportinfo *exi;
|
|
struct svc_req *req;
|
|
{
|
|
int error = 0; /* Fix bug 1045536, bp */
|
|
/* necessary, so that we won't return junk */
|
|
register struct vnode *vp;
|
|
struct vattr va;
|
|
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 4, "rfs_setattr fh %x %x %d\n",
|
|
args->saa_fh.fh_fsid.val[0], args->saa_fh.fh_fsid.val[1],
|
|
args->saa_fh.fh_len);
|
|
#endif
|
|
vp = fhtovp(&args->saa_fh, exi);
|
|
if (vp == NULL) {
|
|
ns->ns_status = NFSERR_STALE;
|
|
return;
|
|
}
|
|
if (rdonly(exi, req) || isrofile(vp)) {
|
|
error = EROFS;
|
|
} else {
|
|
sattr_to_vattr(&args->saa_sa, &va);
|
|
|
|
/*
|
|
* Allow SysV-compatible option to set access and
|
|
* modified times if root, owner, or write access.
|
|
*
|
|
* XXX - Until an NFS Protocol Revision, this may be
|
|
* simulated by setting the client time in the
|
|
* tv_sec field of the access and modified times
|
|
* and setting the tv_usec field of the modified
|
|
* time to an invalid value (1000000). This
|
|
* may be detected by servers modified to do the
|
|
* right thing, but will not be disastrous on
|
|
* unmodified servers.
|
|
*
|
|
* XXX - For now, va_mtime.tv_usec == -1 flags this in
|
|
* VOP_SETATTR().
|
|
*/
|
|
if ((va.va_mtime.tv_sec != -1) &&
|
|
(va.va_mtime.tv_usec == 1000000)) {
|
|
va.va_mtime.tv_sec = 0;
|
|
va.va_mtime.tv_usec = -1;
|
|
va.va_atime.tv_sec = -1;
|
|
va.va_atime.tv_usec = -1;
|
|
}
|
|
|
|
/*
|
|
* Fix bug id 1045536.
|
|
* If truncating check permission.
|
|
* This check was done below VOP_SETATTR layer
|
|
* prioe to SunOS4.1.
|
|
*/
|
|
if (va.va_size != ((u_long) -1))
|
|
error = VOP_ACCESS(vp,VWRITE, u.u_cred);
|
|
if (!error)
|
|
error = VOP_SETATTR(vp, &va, u.u_cred);
|
|
if (!error) {
|
|
error = VOP_GETATTR(vp, &va, u.u_cred);
|
|
if (!error) {
|
|
vattr_to_nattr(&va, &ns->ns_attr);
|
|
}
|
|
}
|
|
}
|
|
ns->ns_status = puterrno(error);
|
|
VN_RELE(vp);
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 5, "rfs_setattr: returning %d\n", error);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Directory lookup.
|
|
* Returns an fhandle and file attributes for file name in a directory.
|
|
*/
|
|
int
|
|
rfs_lookup(da, dr, exi)
|
|
struct nfsdiropargs *da;
|
|
register struct nfsdiropres *dr;
|
|
struct exportinfo *exi;
|
|
{
|
|
int error;
|
|
register struct vnode *dvp;
|
|
struct vnode *vp;
|
|
struct vattr va;
|
|
fhandle_t *fp = &da->da_fhandle; /* fix bug 1064433 */
|
|
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 4, "rfs_lookup %s fh %x %x %x %d\n",
|
|
da->da_name, da->da_fhandle.fh_fsid.val[0],
|
|
da->da_fhandle.fh_fsid.val[1], da->da_fhandle.fh_len);
|
|
#endif
|
|
/*
|
|
* Disallow NULL paths
|
|
*/
|
|
if ((da->da_name == (char *) NULL) || (*da->da_name == '\0')) {
|
|
dr->dr_status = NFSERR_ACCES;
|
|
return;
|
|
}
|
|
|
|
/* Fix bug 1064433 - huey
|
|
* Not allow lookup beyond root.
|
|
* The file handler refers to the mount point itself
|
|
* if the fh_data and fh_xdata are equal.
|
|
*/
|
|
if ( !strcmp(da->da_name, "..")
|
|
&& (fp->fh_len == fp->fh_xlen)
|
|
&& (!bcmp(fp->fh_data, fp->fh_xdata, (int) fp->fh_len))) {
|
|
dr->dr_status = NFSERR_NOENT;
|
|
return;
|
|
}
|
|
|
|
dvp = fhtovp(&da->da_fhandle, exi);
|
|
if (dvp == NULL) {
|
|
dr->dr_status = NFSERR_STALE;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* do lookup.
|
|
*/
|
|
error = VOP_LOOKUP(dvp, da->da_name, &vp, u.u_cred,
|
|
(struct pathname *) NULL, 0);
|
|
if (error) {
|
|
vp = (struct vnode *)NULL;
|
|
} else {
|
|
error = VOP_GETATTR(vp, &va, u.u_cred);
|
|
if (!error) {
|
|
vattr_to_nattr(&va, &dr->dr_attr);
|
|
error = makefh(&dr->dr_fhandle, vp, exi);
|
|
}
|
|
}
|
|
dr->dr_status = puterrno(error);
|
|
if (vp) {
|
|
VN_RELE(vp);
|
|
}
|
|
VN_RELE(dvp);
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 5, "rfs_lookup: returning %d\n", error);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Read symbolic link.
|
|
* Returns the string in the symbolic link at the given fhandle.
|
|
*/
|
|
int
|
|
rfs_readlink(fhp, rl, exi)
|
|
fhandle_t *fhp;
|
|
register struct nfsrdlnres *rl;
|
|
struct exportinfo *exi;
|
|
{
|
|
int error;
|
|
struct iovec iov;
|
|
struct uio uio;
|
|
struct vnode *vp;
|
|
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 4, "rfs_readlink fh %x %x %d\n",
|
|
fhp->fh_fsid.val[0], fhp->fh_fsid.val[1], fhp->fh_len);
|
|
#endif
|
|
vp = fhtovp(fhp, exi);
|
|
if (vp == NULL) {
|
|
rl->rl_status = NFSERR_STALE;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Allocate data for pathname. This will be freed by rfs_rlfree.
|
|
*/
|
|
rl->rl_data = (char *)new_kmem_alloc((u_int)MAXPATHLEN, KMEM_SLEEP);
|
|
|
|
/*
|
|
* Set up io vector to read sym link data
|
|
*/
|
|
iov.iov_base = rl->rl_data;
|
|
iov.iov_len = NFS_MAXPATHLEN;
|
|
uio.uio_iov = &iov;
|
|
uio.uio_iovcnt = 1;
|
|
uio.uio_segflg = UIO_SYSSPACE;
|
|
uio.uio_offset = 0;
|
|
uio.uio_resid = NFS_MAXPATHLEN;
|
|
|
|
/*
|
|
* read link
|
|
*/
|
|
error = VOP_READLINK(vp, &uio, u.u_cred);
|
|
|
|
/*
|
|
* Clean up
|
|
*/
|
|
if (error) {
|
|
kmem_free((caddr_t)rl->rl_data, (u_int)NFS_MAXPATHLEN);
|
|
rl->rl_count = 0;
|
|
rl->rl_data = NULL;
|
|
} else {
|
|
rl->rl_count = NFS_MAXPATHLEN - uio.uio_resid;
|
|
}
|
|
rl->rl_status = puterrno(error);
|
|
VN_RELE(vp);
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 5, "rfs_readlink: returning '%s' %d\n",
|
|
rl->rl_data, error);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Free data allocated by rfs_readlink
|
|
*/
|
|
rfs_rlfree(rl)
|
|
struct nfsrdlnres *rl;
|
|
{
|
|
|
|
if (rl->rl_data) {
|
|
kmem_free((caddr_t)rl->rl_data, (u_int)NFS_MAXPATHLEN);
|
|
}
|
|
}
|
|
|
|
int nfsreadmap = 1;
|
|
|
|
/*
|
|
* Read data.
|
|
* Returns some data read from the file at the given fhandle.
|
|
*/
|
|
int
|
|
rfs_read(ra, rr, exi)
|
|
struct nfsreadargs *ra;
|
|
register struct nfsrdresult *rr;
|
|
struct exportinfo *exi;
|
|
{
|
|
int error;
|
|
struct vnode *vp;
|
|
struct vattr va;
|
|
struct iovec iov;
|
|
struct uio uio;
|
|
int offset;
|
|
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 4, "rfs_read %d from fh %x %x %d\n",
|
|
ra->ra_count, ra->ra_fhandle.fh_fsid.val[0],
|
|
ra->ra_fhandle.fh_fsid.val[1], ra->ra_fhandle.fh_len);
|
|
#endif
|
|
rr->rr_data = NULL;
|
|
rr->rr_count = 0;
|
|
vp = fhtovp(&ra->ra_fhandle, exi);
|
|
if (vp == NULL) {
|
|
rr->rr_status = NFSERR_STALE;
|
|
return;
|
|
}
|
|
if (vp->v_type != VREG) {
|
|
printf("rfs_read: attempt to read from non-file\n");
|
|
error = EISDIR;
|
|
} else {
|
|
error = VOP_GETATTR(vp, &va, u.u_cred);
|
|
}
|
|
if (error) {
|
|
goto bad;
|
|
}
|
|
|
|
/*
|
|
* This is a kludge to allow reading of files created
|
|
* with no read permission. The owner of the file
|
|
* is always allowed to read it.
|
|
*/
|
|
if (u.u_uid != va.va_uid) {
|
|
error = VOP_ACCESS(vp, VREAD, u.u_cred);
|
|
if (error) {
|
|
/*
|
|
* Exec is the same as read over the net because
|
|
* of demand loading.
|
|
*/
|
|
error = VOP_ACCESS(vp, VEXEC, u.u_cred);
|
|
}
|
|
if (error) {
|
|
goto bad;
|
|
}
|
|
}
|
|
|
|
if (ra->ra_offset >= va.va_size) {
|
|
rr->rr_count = 0;
|
|
vattr_to_nattr(&va, &rr->rr_attr);
|
|
goto done; /* hit EOF */
|
|
}
|
|
|
|
/*
|
|
* Check whether we can do this with segmap,
|
|
* which would save the copy through the uio.
|
|
*/
|
|
offset = ra->ra_offset & MAXBOFFSET;
|
|
if (nfsreadmap && (offset + ra->ra_count <= MAXBSIZE) &&
|
|
(vp->v_flag & (VNOCACHE | VNOMAP)) == 0) {
|
|
faultcode_t fault_error;
|
|
|
|
rr->rr_map = segmap_getmap(segkmap, vp,
|
|
(u_int)(ra->ra_offset & MAXBMASK));
|
|
rr->rr_data = rr->rr_map + offset;
|
|
rr->rr_count = MIN(va.va_size - ra->ra_offset, ra->ra_count);
|
|
/*
|
|
* Fault in and lock down the pages.
|
|
*/
|
|
fault_error = as_fault(&kas, rr->rr_data, (u_int)rr->rr_count,
|
|
F_SOFTLOCK, S_READ);
|
|
if (fault_error == 0) {
|
|
VN_HOLD(vp);
|
|
rr->rr_vp = vp;
|
|
vattr_to_nattr(&va, &rr->rr_attr);
|
|
goto done;
|
|
} else {
|
|
if (FC_CODE(fault_error) == FC_OBJERR)
|
|
error = FC_ERRNO(fault_error);
|
|
else
|
|
error = EIO;
|
|
printf("rfs_read: map failed, error = %d\n", error);
|
|
(void) segmap_release(segkmap, rr->rr_map, 0);
|
|
/* Fall through and try just doing a read */
|
|
}
|
|
}
|
|
rr->rr_map = NULL;
|
|
|
|
/*
|
|
* Allocate space for data. This will be freed by xdr_rdresult
|
|
* when it is called with x_op = XDR_FREE.
|
|
*/
|
|
rr->rr_data = new_kmem_alloc((u_int)ra->ra_count, KMEM_SLEEP);
|
|
|
|
/*
|
|
* Set up io vector
|
|
*/
|
|
iov.iov_base = rr->rr_data;
|
|
iov.iov_len = ra->ra_count;
|
|
uio.uio_iov = &iov;
|
|
uio.uio_iovcnt = 1;
|
|
uio.uio_segflg = UIO_SYSSPACE;
|
|
uio.uio_offset = ra->ra_offset;
|
|
uio.uio_resid = ra->ra_count;
|
|
/*
|
|
* For now we assume no append mode and
|
|
* ignore totcount (read ahead)
|
|
*/
|
|
error = VOP_RDWR(vp, &uio, UIO_READ, IO_SYNC, u.u_cred);
|
|
if (error) {
|
|
goto bad;
|
|
} else {
|
|
/*
|
|
* Get attributes again so we send the latest access
|
|
* time to the client side for his cache.
|
|
*/
|
|
error = VOP_GETATTR(vp, &va, u.u_cred);
|
|
if (error)
|
|
goto bad;
|
|
}
|
|
vattr_to_nattr(&va, &rr->rr_attr);
|
|
rr->rr_count = ra->ra_count - uio.uio_resid;
|
|
/*
|
|
* Resize the buffer if we didn't fill it.
|
|
*/
|
|
if (uio.uio_resid) {
|
|
rr->rr_data = new_kmem_resize(rr->rr_data, (u_int)0,
|
|
(u_int)rr->rr_count, (u_int)ra->ra_count,
|
|
KMEM_SLEEP);
|
|
}
|
|
bad:
|
|
if (error && rr->rr_data != NULL) {
|
|
kmem_free(rr->rr_data, (u_int)ra->ra_count);
|
|
rr->rr_data = NULL;
|
|
rr->rr_count = 0;
|
|
}
|
|
done:
|
|
rr->rr_status = puterrno(error);
|
|
VN_RELE(vp);
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 5, "rfs_read returning %d, count = %d\n",
|
|
error, rr->rr_count);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Free data allocated by rfs_read.
|
|
*/
|
|
rfs_rdfree(rr)
|
|
struct nfsrdresult *rr;
|
|
{
|
|
|
|
if (rr->rr_map == NULL && rr->rr_data != NULL) {
|
|
kmem_free(rr->rr_data, (u_int)rr->rr_count);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Write data to file.
|
|
* Returns attributes of a file after writing some data to it.
|
|
*/
|
|
int
|
|
rfs_write(wa, ns, exi, req)
|
|
struct nfswriteargs *wa;
|
|
struct nfsattrstat *ns;
|
|
struct exportinfo *exi;
|
|
struct svc_req *req;
|
|
{
|
|
register int error;
|
|
register struct vnode *vp;
|
|
struct vattr va;
|
|
struct iovec iov;
|
|
struct uio uio;
|
|
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 4, "rfs_write: %d bytes fh %x %x %d\n",
|
|
wa->wa_count, wa->wa_fhandle.fh_fsid.val[0],
|
|
wa->wa_fhandle.fh_fsid.val[1], wa->wa_fhandle.fh_len);
|
|
#endif
|
|
vp = fhtovp(&wa->wa_fhandle, exi);
|
|
if (vp == NULL) {
|
|
ns->ns_status = NFSERR_STALE;
|
|
return;
|
|
}
|
|
if (rdonly(exi, req)) {
|
|
error = EROFS;
|
|
} else if (vp->v_type != VREG) {
|
|
printf("rfs_write: attempt to write to non-file\n");
|
|
error = EISDIR;
|
|
} else {
|
|
error = VOP_GETATTR(vp, &va, u.u_cred);
|
|
}
|
|
if (!error) {
|
|
if (u.u_uid != va.va_uid) {
|
|
/*
|
|
* This is a kludge to allow writes of files created
|
|
* with read only permission. The owner of the file
|
|
* is always allowed to write it.
|
|
*/
|
|
error = VOP_ACCESS(vp, VWRITE, u.u_cred);
|
|
}
|
|
if (!error) {
|
|
if (wa->wa_data) {
|
|
iov.iov_base = wa->wa_data;
|
|
iov.iov_len = wa->wa_count;
|
|
uio.uio_iov = &iov;
|
|
uio.uio_iovcnt = 1;
|
|
uio.uio_segflg = UIO_SYSSPACE;
|
|
uio.uio_offset = wa->wa_offset;
|
|
uio.uio_resid = wa->wa_count;
|
|
/*
|
|
* for now we assume no append mode
|
|
*/
|
|
error = VOP_RDWR(vp, &uio, UIO_WRITE, IO_SYNC,
|
|
u.u_cred);
|
|
} else { /* mbuf hack */
|
|
register struct mbuf *m;
|
|
register struct iovec *iovp;
|
|
register int iovcnt;
|
|
static caddr_t *base10, *base40;
|
|
|
|
iovcnt = 0;
|
|
for (m = (struct mbuf *)wa->wa_mbuf; m;
|
|
m = m->m_next) {
|
|
iovcnt++;
|
|
}
|
|
if (iovcnt < 10) {
|
|
iovp = (struct iovec *)
|
|
new_kmem_fast_alloc(
|
|
(caddr_t *)&base10,
|
|
sizeof (struct iovec) * 10, 10,
|
|
KMEM_SLEEP);
|
|
} else if (iovcnt < 40) {
|
|
iovp = (struct iovec *)
|
|
new_kmem_fast_alloc(
|
|
(caddr_t *)&base40,
|
|
sizeof (struct iovec) * 40, 10,
|
|
KMEM_SLEEP);
|
|
} else {
|
|
iovp = (struct iovec *)
|
|
new_kmem_alloc(
|
|
(u_int)(sizeof (struct iovec) *
|
|
iovcnt), KMEM_SLEEP);
|
|
}
|
|
mbuf_to_iov((struct mbuf *)wa->wa_mbuf, iovp);
|
|
uio.uio_iov = iovp;
|
|
uio.uio_iovcnt = iovcnt;
|
|
uio.uio_segflg = UIO_SYSSPACE;
|
|
uio.uio_offset = wa->wa_offset;
|
|
uio.uio_resid = wa->wa_count;
|
|
/*
|
|
* for now we assume no append mode
|
|
*/
|
|
error = VOP_RDWR(vp, &uio, UIO_WRITE, IO_SYNC,
|
|
u.u_cred);
|
|
if (iovcnt < 10) {
|
|
kmem_fast_free((caddr_t *)&base10,
|
|
(caddr_t)iovp);
|
|
} else if (iovcnt < 40) {
|
|
kmem_fast_free((caddr_t *)&base40,
|
|
(caddr_t)iovp);
|
|
} else {
|
|
kmem_free((caddr_t)iovp,
|
|
(u_int)(sizeof (struct iovec) *
|
|
iovcnt));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!error) {
|
|
/*
|
|
* Get attributes again so we send the latest mod
|
|
* time to the client side for his cache.
|
|
*/
|
|
error = VOP_GETATTR(vp, &va, u.u_cred);
|
|
}
|
|
ns->ns_status = puterrno(error);
|
|
if (!error) {
|
|
vattr_to_nattr(&va, &ns->ns_attr);
|
|
}
|
|
VN_RELE(vp);
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 5, "rfs_write: returning %d\n", error);
|
|
#endif
|
|
}
|
|
|
|
static
|
|
mbuf_to_iov(m, iov)
|
|
register struct mbuf *m;
|
|
register struct iovec *iov;
|
|
{
|
|
|
|
while (m) {
|
|
iov->iov_base = mtod(m, caddr_t);
|
|
iov->iov_len = m->m_len;
|
|
iov++;
|
|
m = m->m_next;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Create a file.
|
|
* Creates a file with given attributes and returns those attributes
|
|
* and an fhandle for the new file.
|
|
*/
|
|
int
|
|
rfs_create(args, dr, exi, req)
|
|
struct nfscreatargs *args;
|
|
struct nfsdiropres *dr;
|
|
struct exportinfo *exi;
|
|
struct svc_req *req;
|
|
{
|
|
register int error;
|
|
struct vattr va;
|
|
struct vnode *vp;
|
|
register struct vnode *dvp;
|
|
register char *name = args->ca_da.da_name;
|
|
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 4, "rfs_create: %s dfh %x %x %d\n",
|
|
name, args->ca_da.da_fhandle.fh_fsid.val[0],
|
|
args->ca_da.da_fhandle.fh_fsid.val[1],
|
|
args->ca_da.da_fhandle.fh_len);
|
|
#endif
|
|
/*
|
|
* Disallow NULL paths
|
|
*/
|
|
if ((name == (char *) NULL) || (*name == '\0')) {
|
|
dr->dr_status = NFSERR_ACCES;
|
|
return;
|
|
}
|
|
|
|
sattr_to_vattr(&args->ca_sa, &va);
|
|
/*
|
|
* This is a completely gross hack to make mknod
|
|
* work over the wire until we can wack the protocol
|
|
*/
|
|
#define IFMT 0170000 /* type of file */
|
|
#define IFCHR 0020000 /* character special */
|
|
#define IFBLK 0060000 /* block special */
|
|
#define IFSOCK 0140000 /* socket */
|
|
if ((va.va_mode & IFMT) == IFCHR) {
|
|
va.va_type = VCHR;
|
|
if (va.va_size == (u_long)NFS_FIFO_DEV)
|
|
va.va_type = VFIFO; /* xtra kludge for named pipe */
|
|
else
|
|
va.va_rdev = (dev_t)va.va_size;
|
|
va.va_size = 0;
|
|
} else if ((va.va_mode & IFMT) == IFBLK) {
|
|
va.va_type = VBLK;
|
|
va.va_rdev = (dev_t)va.va_size;
|
|
va.va_size = 0;
|
|
} else if ((va.va_mode & IFMT) == IFSOCK) {
|
|
va.va_type = VSOCK;
|
|
} else {
|
|
va.va_type = VREG;
|
|
}
|
|
va.va_mode &= ~IFMT;
|
|
|
|
/*
|
|
* XXX - Should get exclusive flag and use it.
|
|
*/
|
|
dvp = fhtovp(&args->ca_da.da_fhandle, exi);
|
|
if (dvp == NULL) {
|
|
dr->dr_status = NFSERR_STALE;
|
|
return;
|
|
}
|
|
if (rdonly(exi, req)) {
|
|
error = EROFS;
|
|
} else {
|
|
/*
|
|
* Very strange. If we are going to trunc the file
|
|
* (va_size == 0) we check the cache first to be sure
|
|
* this is not a delayed retransmission, otherwise we
|
|
* do the create and if it fails check the cache to see
|
|
* if it was a retransmission.
|
|
* XXX this really points out a protocol bug:
|
|
* The server should always do exclusive create.
|
|
*/
|
|
error = VOP_CREATE(dvp, name, &va, NONEXCL, VWRITE, &vp,
|
|
u.u_cred);
|
|
}
|
|
if (!error) {
|
|
error = VOP_GETATTR(vp, &va, u.u_cred);
|
|
if (!error) {
|
|
vattr_to_nattr(&va, &dr->dr_attr);
|
|
error = makefh(&dr->dr_fhandle, vp, exi);
|
|
}
|
|
VN_RELE(vp);
|
|
}
|
|
dr->dr_status = puterrno(error);
|
|
VN_RELE(dvp);
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 5, "rfs_create: returning %d\n", error);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Remove a file.
|
|
* Remove named file from parent directory.
|
|
*/
|
|
int
|
|
rfs_remove(da, status, exi, req)
|
|
struct nfsdiropargs *da;
|
|
enum nfsstat *status;
|
|
struct exportinfo *exi;
|
|
struct svc_req *req;
|
|
{
|
|
int error;
|
|
register struct vnode *vp;
|
|
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 4, "rfs_remove %s dfh %x %x %d\n",
|
|
da->da_name, da->da_fhandle.fh_fsid.val[0],
|
|
da->da_fhandle.fh_fsid.val[1], da->da_fhandle.fh_len);
|
|
#endif
|
|
/*
|
|
* Disallow NULL paths
|
|
*/
|
|
if ((da->da_name == (char *) NULL) || (*da->da_name == '\0')) {
|
|
*status = NFSERR_ACCES;
|
|
return;
|
|
}
|
|
|
|
vp = fhtovp(&da->da_fhandle, exi);
|
|
if (vp == NULL) {
|
|
*status = NFSERR_STALE;
|
|
return;
|
|
}
|
|
if (rdonly(exi, req)) {
|
|
error = EROFS;
|
|
} else {
|
|
error = VOP_REMOVE(vp, da->da_name, u.u_cred);
|
|
}
|
|
*status = puterrno(error);
|
|
VN_RELE(vp);
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 5, "rfs_remove: %s returning %d\n",
|
|
da->da_name, error);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* rename a file
|
|
* Give a file (from) a new name (to).
|
|
*/
|
|
int
|
|
rfs_rename(args, status, exi, req)
|
|
struct nfsrnmargs *args;
|
|
enum nfsstat *status;
|
|
struct exportinfo *exi;
|
|
struct svc_req *req;
|
|
{
|
|
int error;
|
|
register struct vnode *fromvp;
|
|
register struct vnode *tovp;
|
|
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 4, "rfs_rename %s ffh %x %x %d -> %s tfh %x %x %d\n",
|
|
args->rna_from.da_name,
|
|
args->rna_from.da_fhandle.fh_fsid.val[0],
|
|
args->rna_from.da_fhandle.fh_fsid.val[1],
|
|
args->rna_from.da_fhandle.fh_len,
|
|
args->rna_to.da_name,
|
|
args->rna_to.da_fhandle.fh_fsid.val[0],
|
|
args->rna_to.da_fhandle.fh_fsid.val[1],
|
|
args->rna_to.da_fhandle.fh_len);
|
|
#endif
|
|
/*
|
|
* Disallow NULL paths
|
|
*/
|
|
if ((args->rna_from.da_name == (char *) NULL) ||
|
|
(*args->rna_from.da_name == '\0') ||
|
|
(args->rna_to.da_name == (char *) NULL) ||
|
|
(*args->rna_to.da_name == '\0')) {
|
|
*status = NFSERR_ACCES;
|
|
return;
|
|
}
|
|
|
|
fromvp = fhtovp(&args->rna_from.da_fhandle, exi);
|
|
if (fromvp == NULL) {
|
|
*status = NFSERR_STALE;
|
|
return;
|
|
}
|
|
if (rdonly(exi, req)) {
|
|
error = EROFS;
|
|
goto fromerr;
|
|
}
|
|
tovp = fhtovp(&args->rna_to.da_fhandle, exi);
|
|
if (tovp == NULL) {
|
|
*status = NFSERR_STALE;
|
|
VN_RELE(fromvp);
|
|
return;
|
|
}
|
|
error = VOP_RENAME(fromvp, args->rna_from.da_name,
|
|
tovp, args->rna_to.da_name, u.u_cred);
|
|
VN_RELE(tovp);
|
|
fromerr:
|
|
VN_RELE(fromvp);
|
|
*status = puterrno(error);
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 5, "rfs_rename: returning %d\n", error);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Link to a file.
|
|
* Create a file (to) which is a hard link to the given file (from).
|
|
*/
|
|
int
|
|
rfs_link(args, status, exi, req)
|
|
struct nfslinkargs *args;
|
|
enum nfsstat *status;
|
|
struct exportinfo *exi;
|
|
struct svc_req *req;
|
|
{
|
|
int error;
|
|
register struct vnode *fromvp;
|
|
register struct vnode *tovp;
|
|
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 4, "rfs_link ffh %x %x %d -> %s tfh %x %x %d\n",
|
|
args->la_from.fh_fsid.val[0], args->la_from.fh_fsid.val[1],
|
|
args->la_from.fh_len, args->la_to.da_name,
|
|
args->la_to.da_fhandle.fh_fsid.val[0],
|
|
args->la_to.da_fhandle.fh_fsid.val[1],
|
|
args->la_to.da_fhandle.fh_len);
|
|
#endif
|
|
/*
|
|
* Disallow NULL paths
|
|
*/
|
|
if ((args->la_to.da_name == (char *) NULL) ||
|
|
(*args->la_to.da_name == '\0')) {
|
|
*status = NFSERR_ACCES;
|
|
return;
|
|
}
|
|
|
|
fromvp = fhtovp(&args->la_from, exi);
|
|
if (fromvp == NULL) {
|
|
*status = NFSERR_STALE;
|
|
return;
|
|
}
|
|
tovp = fhtovp(&args->la_to.da_fhandle, exi);
|
|
if (tovp == NULL) {
|
|
*status = NFSERR_STALE;
|
|
VN_RELE(fromvp);
|
|
return;
|
|
}
|
|
if (rdonly(exi, req)) {
|
|
error = EROFS;
|
|
} else {
|
|
error = VOP_LINK(fromvp, tovp, args->la_to.da_name, u.u_cred);
|
|
}
|
|
*status = puterrno(error);
|
|
VN_RELE(fromvp);
|
|
VN_RELE(tovp);
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 5, "rfs_link: returning %d\n", error);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Symbolicly link to a file.
|
|
* Create a file (to) with the given attributes which is a symbolic link
|
|
* to the given path name (to).
|
|
*/
|
|
int
|
|
rfs_symlink(args, status, exi, req)
|
|
struct nfsslargs *args;
|
|
enum nfsstat *status;
|
|
struct exportinfo *exi;
|
|
struct svc_req *req;
|
|
{
|
|
int error;
|
|
struct vattr va;
|
|
register struct vnode *vp;
|
|
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 4, "rfs_symlink %s ffh %x %x %d -> %s\n",
|
|
args->sla_from.da_name,
|
|
args->sla_from.da_fhandle.fh_fsid.val[0],
|
|
args->sla_from.da_fhandle.fh_fsid.val[1],
|
|
args->sla_from.da_fhandle.fh_len,
|
|
args->sla_tnm);
|
|
#endif
|
|
/*
|
|
* Disallow NULL paths
|
|
*/
|
|
if ((args->sla_from.da_name == (char *) NULL) ||
|
|
(*args->sla_from.da_name == '\0')) {
|
|
*status = NFSERR_ACCES;
|
|
return;
|
|
}
|
|
|
|
sattr_to_vattr(&args->sla_sa, &va);
|
|
va.va_type = VLNK;
|
|
vp = fhtovp(&args->sla_from.da_fhandle, exi);
|
|
if (vp == NULL) {
|
|
*status = NFSERR_STALE;
|
|
return;
|
|
}
|
|
if (rdonly(exi, req)) {
|
|
error = EROFS;
|
|
} else {
|
|
error = VOP_SYMLINK(vp, args->sla_from.da_name,
|
|
&va, args->sla_tnm, u.u_cred);
|
|
}
|
|
*status = puterrno(error);
|
|
VN_RELE(vp);
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 5, "rfs_symlink: returning %d\n", error);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Make a directory.
|
|
* Create a directory with the given name, parent directory, and attributes.
|
|
* Returns a file handle and attributes for the new directory.
|
|
*/
|
|
int
|
|
rfs_mkdir(args, dr, exi, req)
|
|
struct nfscreatargs *args;
|
|
struct nfsdiropres *dr;
|
|
struct exportinfo *exi;
|
|
struct svc_req *req;
|
|
{
|
|
int error;
|
|
struct vattr va;
|
|
struct vnode *dvp;
|
|
register struct vnode *vp;
|
|
register char *name = args->ca_da.da_name;
|
|
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 4, "rfs_mkdir %s fh %x %x %d\n",
|
|
name, args->ca_da.da_fhandle.fh_fsid.val[0],
|
|
args->ca_da.da_fhandle.fh_fsid.val[1],
|
|
args->ca_da.da_fhandle.fh_len);
|
|
#endif
|
|
/*
|
|
* Disallow NULL paths
|
|
*/
|
|
if ((name == (char *) NULL) || (*name == '\0')) {
|
|
dr->dr_status = NFSERR_ACCES;
|
|
return;
|
|
}
|
|
|
|
sattr_to_vattr(&args->ca_sa, &va);
|
|
va.va_type = VDIR;
|
|
/*
|
|
* Should get exclusive flag and pass it on here
|
|
*/
|
|
vp = fhtovp(&args->ca_da.da_fhandle, exi);
|
|
if (vp == NULL) {
|
|
dr->dr_status = NFSERR_STALE;
|
|
return;
|
|
}
|
|
if (rdonly(exi, req)) {
|
|
error = EROFS;
|
|
} else {
|
|
error = VOP_MKDIR(vp, name, &va, &dvp, u.u_cred);
|
|
if (!error) {
|
|
/*
|
|
* Attribtutes of the newly created directory should
|
|
* be returned to the client.
|
|
*/
|
|
error = VOP_GETATTR(dvp, &va, u.u_cred);
|
|
if (!error) {
|
|
vattr_to_nattr(&va, &dr->dr_attr);
|
|
error = makefh(&dr->dr_fhandle, dvp, exi);
|
|
}
|
|
VN_RELE(dvp);
|
|
}
|
|
}
|
|
dr->dr_status = puterrno(error);
|
|
VN_RELE(vp);
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 5, "rfs_mkdir: returning %d\n", error);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Remove a directory.
|
|
* Remove the given directory name from the given parent directory.
|
|
*/
|
|
int
|
|
rfs_rmdir(da, status, exi, req)
|
|
struct nfsdiropargs *da;
|
|
enum nfsstat *status;
|
|
struct exportinfo *exi;
|
|
struct svc_req *req;
|
|
{
|
|
int error;
|
|
register struct vnode *vp;
|
|
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 4, "rfs_rmdir %s fh %x %x %d\n",
|
|
da->da_name, da->da_fhandle.fh_fsid.val[0],
|
|
da->da_fhandle.fh_fsid.val[1], da->da_fhandle.fh_len);
|
|
#endif
|
|
/*
|
|
* Disallow NULL paths
|
|
*/
|
|
if ((da->da_name == (char *) NULL) || (*da->da_name == '\0')) {
|
|
*status = NFSERR_ACCES;
|
|
return;
|
|
}
|
|
|
|
vp = fhtovp(&da->da_fhandle, exi);
|
|
if (vp == NULL) {
|
|
*status = NFSERR_STALE;
|
|
return;
|
|
}
|
|
if (rdonly(exi, req)) {
|
|
error = EROFS;
|
|
} else {
|
|
error = VOP_RMDIR(vp, da->da_name, u.u_cred);
|
|
}
|
|
*status = puterrno(error);
|
|
VN_RELE(vp);
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 5, "nfs_rmdir returning %d\n", error);
|
|
#endif
|
|
}
|
|
|
|
int
|
|
rfs_readdir(rda, rd, exi)
|
|
struct nfsrddirargs *rda;
|
|
register struct nfsrddirres *rd;
|
|
struct exportinfo *exi;
|
|
{
|
|
int error;
|
|
struct iovec iov;
|
|
struct uio uio;
|
|
register struct vnode *vp;
|
|
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 4, "rfs_readdir fh %x %x %d count %d\n",
|
|
rda->rda_fh.fh_fsid.val[0], rda->rda_fh.fh_fsid.val[1],
|
|
rda->rda_fh.fh_len, rda->rda_count);
|
|
#endif
|
|
vp = fhtovp(&rda->rda_fh, exi);
|
|
if (vp == NULL) {
|
|
rd->rd_status = NFSERR_STALE;
|
|
return;
|
|
}
|
|
if (vp->v_type != VDIR) {
|
|
printf("rfs_readdir: attempt to read non-directory\n");
|
|
error = ENOTDIR;
|
|
goto bad;
|
|
}
|
|
/*
|
|
* check read access of dir. we have to do this here because
|
|
* the opendir doesn't go over the wire.
|
|
*/
|
|
error = VOP_ACCESS(vp, VREAD, u.u_cred);
|
|
if (error) {
|
|
goto bad;
|
|
}
|
|
|
|
if (rda->rda_count == 0) {
|
|
rd->rd_size = 0;
|
|
rd->rd_eof = FALSE;
|
|
rd->rd_entries = NULL;
|
|
rd->rd_bufsize = 0;
|
|
goto bad;
|
|
}
|
|
|
|
rda->rda_count = MIN(rda->rda_count, NFS_MAXDATA);
|
|
|
|
/*
|
|
* Allocate data for entries. This will be freed by rfs_rdfree.
|
|
*/
|
|
rd->rd_entries = (struct dirent *)
|
|
new_kmem_alloc((u_int)rda->rda_count, KMEM_SLEEP);
|
|
rd->rd_bufsize = rda->rda_count;
|
|
rd->rd_origreqsize = rda->rda_count;
|
|
|
|
/*
|
|
* Set up io vector to read directory data
|
|
*/
|
|
iov.iov_base = (caddr_t)rd->rd_entries;
|
|
iov.iov_len = rda->rda_count;
|
|
uio.uio_iov = &iov;
|
|
uio.uio_iovcnt = 1;
|
|
uio.uio_segflg = UIO_SYSSPACE;
|
|
uio.uio_offset = rda->rda_offset;
|
|
uio.uio_resid = rda->rda_count;
|
|
|
|
/*
|
|
* read directory
|
|
*/
|
|
error = VOP_READDIR(vp, &uio, u.u_cred);
|
|
|
|
/*
|
|
* Clean up
|
|
*/
|
|
if (error) {
|
|
rd->rd_size = 0;
|
|
goto bad;
|
|
}
|
|
|
|
/*
|
|
* set size and eof
|
|
*/
|
|
if (rda->rda_count && uio.uio_resid == rda->rda_count) {
|
|
rd->rd_size = 0;
|
|
rd->rd_eof = TRUE;
|
|
} else {
|
|
rd->rd_size = rda->rda_count - uio.uio_resid;
|
|
rd->rd_eof = FALSE;
|
|
}
|
|
|
|
bad:
|
|
rd->rd_status = puterrno(error);
|
|
VN_RELE(vp);
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 5, "rfs_readdir: returning %d\n", error);
|
|
#endif
|
|
}
|
|
|
|
rfs_rddirfree(rd)
|
|
struct nfsrddirres *rd;
|
|
{
|
|
if (rd->rd_entries != NULL)
|
|
kmem_free((caddr_t)rd->rd_entries, (u_int)rd->rd_bufsize);
|
|
}
|
|
|
|
rfs_statfs(fh, fs, exi)
|
|
fhandle_t *fh;
|
|
register struct nfsstatfs *fs;
|
|
struct exportinfo *exi;
|
|
{
|
|
int error;
|
|
struct statfs sb;
|
|
register struct vnode *vp;
|
|
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 4, "rfs_statfs fh %x %x %d\n",
|
|
fh->fh_fsid.val[0], fh->fh_fsid.val[1], fh->fh_len);
|
|
#endif
|
|
vp = fhtovp(fh, exi);
|
|
if (vp == NULL) {
|
|
fs->fs_status = NFSERR_STALE;
|
|
return;
|
|
}
|
|
error = VFS_STATFS(vp->v_vfsp, &sb);
|
|
fs->fs_status = puterrno(error);
|
|
if (!error) {
|
|
fs->fs_tsize = nfstsize();
|
|
fs->fs_bsize = sb.f_bsize;
|
|
fs->fs_blocks = sb.f_blocks;
|
|
fs->fs_bfree = sb.f_bfree;
|
|
fs->fs_bavail = sb.f_bavail;
|
|
}
|
|
VN_RELE(vp);
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 5, "rfs_statfs returning %d\n", error);
|
|
#endif
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
rfs_null(argp, resp)
|
|
caddr_t *argp;
|
|
caddr_t *resp;
|
|
{
|
|
/* do nothing */
|
|
return (0);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
rfs_error(argp, resp)
|
|
caddr_t *argp;
|
|
caddr_t *resp;
|
|
{
|
|
|
|
return (EOPNOTSUPP);
|
|
}
|
|
|
|
int
|
|
nullfree()
|
|
{
|
|
}
|
|
|
|
/*
|
|
* rfs dispatch table
|
|
* Indexed by version, proc
|
|
*/
|
|
|
|
struct rfsdisp {
|
|
int (*dis_proc)(); /* proc to call */
|
|
xdrproc_t dis_xdrargs; /* xdr routine to get args */
|
|
int dis_argsz; /* sizeof args */
|
|
xdrproc_t dis_xdrres; /* xdr routine to put results */
|
|
int dis_ressz; /* size of results */
|
|
int (*dis_resfree)(); /* frees space allocated by proc */
|
|
} rfsdisptab[][RFS_NPROC] = {
|
|
{
|
|
/*
|
|
* VERSION 2
|
|
* Changed rddirres to have eof at end instead of beginning
|
|
*/
|
|
/* RFS_NULL = 0 */
|
|
{rfs_null, xdr_void, 0,
|
|
xdr_void, 0, nullfree},
|
|
/* RFS_GETATTR = 1 */
|
|
{rfs_getattr, xdr_fhandle, sizeof (fhandle_t),
|
|
xdr_attrstat, sizeof (struct nfsattrstat), nullfree},
|
|
/* RFS_SETATTR = 2 */
|
|
{rfs_setattr, xdr_saargs, sizeof (struct nfssaargs),
|
|
xdr_attrstat, sizeof (struct nfsattrstat), nullfree},
|
|
/* RFS_ROOT = 3 *** NO LONGER SUPPORTED *** */
|
|
{rfs_error, xdr_void, 0,
|
|
xdr_void, 0, nullfree},
|
|
/* RFS_LOOKUP = 4 */
|
|
{rfs_lookup, xdr_diropargs, sizeof (struct nfsdiropargs),
|
|
xdr_diropres, sizeof (struct nfsdiropres), nullfree},
|
|
/* RFS_READLINK = 5 */
|
|
{rfs_readlink, xdr_fhandle, sizeof (fhandle_t),
|
|
xdr_rdlnres, sizeof (struct nfsrdlnres), rfs_rlfree},
|
|
/* RFS_READ = 6 */
|
|
{rfs_read, xdr_readargs, sizeof (struct nfsreadargs),
|
|
xdr_rdresult, sizeof (struct nfsrdresult), rfs_rdfree},
|
|
/* RFS_WRITECACHE = 7 *** NO LONGER SUPPORTED *** */
|
|
{rfs_error, xdr_void, 0,
|
|
xdr_void, 0, nullfree},
|
|
/* RFS_WRITE = 8 */
|
|
{rfs_write, xdr_writeargs, sizeof (struct nfswriteargs),
|
|
xdr_attrstat, sizeof (struct nfsattrstat), nullfree},
|
|
/* RFS_CREATE = 9 */
|
|
{rfs_create, xdr_creatargs, sizeof (struct nfscreatargs),
|
|
xdr_diropres, sizeof (struct nfsdiropres), nullfree},
|
|
/* RFS_REMOVE = 10 */
|
|
{rfs_remove, xdr_diropargs, sizeof (struct nfsdiropargs),
|
|
xdr_enum, sizeof (enum nfsstat), nullfree},
|
|
/* RFS_RENAME = 11 */
|
|
{rfs_rename, xdr_rnmargs, sizeof (struct nfsrnmargs),
|
|
xdr_enum, sizeof (enum nfsstat), nullfree},
|
|
/* RFS_LINK = 12 */
|
|
{rfs_link, xdr_linkargs, sizeof (struct nfslinkargs),
|
|
xdr_enum, sizeof (enum nfsstat), nullfree},
|
|
/* RFS_SYMLINK = 13 */
|
|
{rfs_symlink, xdr_slargs, sizeof (struct nfsslargs),
|
|
xdr_enum, sizeof (enum nfsstat), nullfree},
|
|
/* RFS_MKDIR = 14 */
|
|
{rfs_mkdir, xdr_creatargs, sizeof (struct nfscreatargs),
|
|
xdr_diropres, sizeof (struct nfsdiropres), nullfree},
|
|
/* RFS_RMDIR = 15 */
|
|
{rfs_rmdir, xdr_diropargs, sizeof (struct nfsdiropargs),
|
|
xdr_enum, sizeof (enum nfsstat), nullfree},
|
|
/* RFS_READDIR = 16 */
|
|
{rfs_readdir, xdr_rddirargs, sizeof (struct nfsrddirargs),
|
|
xdr_putrddirres, sizeof (struct nfsrddirres), rfs_rddirfree},
|
|
/* RFS_STATFS = 17 */
|
|
{rfs_statfs, xdr_fhandle, sizeof (fhandle_t),
|
|
xdr_statfs, sizeof (struct nfsstatfs), nullfree},
|
|
}
|
|
};
|
|
|
|
struct rfsspace {
|
|
struct rfsspace *rs_next;
|
|
caddr_t rs_dummy;
|
|
};
|
|
|
|
struct rfsspace *rfsfreesp = NULL;
|
|
|
|
int rfssize = 0;
|
|
|
|
caddr_t
|
|
rfsget()
|
|
{
|
|
int i;
|
|
struct rfsdisp *dis;
|
|
caddr_t ret;
|
|
|
|
if (rfssize == 0) {
|
|
for (i = 0; i < 1 + VERSIONMAX - VERSIONMIN; i++) {
|
|
for (dis = &rfsdisptab[i][0];
|
|
dis < &rfsdisptab[i][RFS_NPROC];
|
|
dis++) {
|
|
rfssize = MAX(rfssize, dis->dis_argsz);
|
|
rfssize = MAX(rfssize, dis->dis_ressz);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rfsfreesp) {
|
|
ret = (caddr_t)rfsfreesp;
|
|
rfsfreesp = rfsfreesp->rs_next;
|
|
} else {
|
|
ret = new_kmem_alloc((u_int)rfssize, KMEM_SLEEP);
|
|
}
|
|
return (ret);
|
|
}
|
|
|
|
rfsput(rs)
|
|
struct rfsspace *rs;
|
|
{
|
|
|
|
rs->rs_next = rfsfreesp;
|
|
rfsfreesp = rs;
|
|
}
|
|
|
|
|
|
/*
|
|
* If nfs_portmon is set, then clients are required to use
|
|
* privileged ports (ports < IPPORT_RESERVED) in order to get NFS services.
|
|
*/
|
|
int nfs_portmon = 0;
|
|
|
|
void
|
|
rfs_dispatch(req, xprt)
|
|
struct svc_req *req;
|
|
register SVCXPRT *xprt;
|
|
{
|
|
int which;
|
|
int vers;
|
|
caddr_t *args = NULL;
|
|
caddr_t *res = NULL;
|
|
register struct rfsdisp *disp = NULL;
|
|
struct ucred *tmpcr;
|
|
struct ucred *newcr = NULL;
|
|
int error;
|
|
struct exportinfo *exi = NULL;
|
|
register int dupstat;
|
|
register int ressize;
|
|
int anon_ok = 0; /* Fix for bug 1038302 - corbin */
|
|
label_t qsave;
|
|
|
|
svstat.ncalls++;
|
|
error = 0;
|
|
which = req->rq_proc;
|
|
if (which < 0 || which >= RFS_NPROC) {
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 2,
|
|
"rfs_dispatch: bad proc %d\n", which);
|
|
#endif
|
|
svcerr_noproc(req->rq_xprt);
|
|
error++;
|
|
printf("nfs_server: bad proc number\n");
|
|
goto done;
|
|
}
|
|
vers = req->rq_vers;
|
|
if (vers < VERSIONMIN || vers > VERSIONMAX) {
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 2,
|
|
"rfs_dispatch: bad vers %d low %d high %d\n",
|
|
vers, VERSIONMIN, VERSIONMAX);
|
|
#endif
|
|
svcerr_progvers(req->rq_xprt, (u_long)VERSIONMIN,
|
|
(u_long)VERSIONMAX);
|
|
error++;
|
|
printf("nfs_server: bad version number\n");
|
|
goto done;
|
|
}
|
|
vers -= VERSIONMIN;
|
|
disp = &rfsdisptab[vers][which];
|
|
|
|
/*
|
|
* Clean up as if a system call just started
|
|
*/
|
|
u.u_error = 0;
|
|
|
|
/*
|
|
* Allocate args struct and deserialize into it.
|
|
*/
|
|
args = (caddr_t *)rfsget();
|
|
bzero((caddr_t)args, (u_int)rfssize);
|
|
if (! SVC_GETARGS(xprt, disp->dis_xdrargs, args)) {
|
|
svcerr_decode(xprt);
|
|
error++;
|
|
printf("nfs_server: bad getargs\n");
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* Find export information and check authentication,
|
|
* setting the credential if everything is ok.
|
|
*/
|
|
if (which != RFS_NULL) {
|
|
/*
|
|
* XXX: this isn't really quite correct. Instead of doing
|
|
* this blind cast, we should extract out the fhandle for
|
|
* each NFS call. What's more, some procedures (like rename)
|
|
* have more than one fhandle passed in, and we should check
|
|
* that the two fhandles point to the same exported path.
|
|
*/
|
|
fhandle_t *fh = (fhandle_t *) args;
|
|
|
|
/*
|
|
* Fix for bug 1038302 - corbin
|
|
* There is a problem here if anonymous access is
|
|
* disallowed. If the current request is part of the
|
|
* client's mount process for the requested filesystem,
|
|
* then it will carry root (uid 0) credentials on it, and
|
|
* will be denied by checkauth if that client does not
|
|
* have explicit root=0 permission. This will cause the
|
|
* client's mount operation to fail. As a work-around,
|
|
* we check here to see if the request is a getattr or
|
|
* statfs operation on the exported vnode itself, and
|
|
* pass a flag to checkauth with the result of this test.
|
|
*
|
|
* The filehandle refers to the mountpoint itself if
|
|
* the fh_data and fh_xdata portions of the filehandle
|
|
* are equal.
|
|
*/
|
|
|
|
if ( (which == RFS_GETATTR || which == RFS_STATFS)
|
|
&& (fh->fh_len == fh->fh_xlen)
|
|
&& (bcmp(fh->fh_data, fh->fh_xdata, (int)fh->fh_len) == 0)) {
|
|
anon_ok = 1;
|
|
}
|
|
|
|
|
|
newcr = crget();
|
|
tmpcr = u.u_cred;
|
|
u.u_cred = newcr;
|
|
exi = findexport(&fh->fh_fsid, (struct fid *) &fh->fh_xlen);
|
|
if (exi != NULL && !checkauth(exi, req, newcr, anon_ok)) {
|
|
extern char *inet_ntoa();
|
|
|
|
svcerr_weakauth(xprt);
|
|
error++;
|
|
printf(
|
|
"nfs_server: weak authentication, source IP address=%s\n",
|
|
inet_ntoa(req->rq_xprt->xp_raddr.sin_addr));
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Allocate results struct.
|
|
*/
|
|
res = (caddr_t *)rfsget();
|
|
bzero((caddr_t)res, (u_int)rfssize);
|
|
|
|
svstat.reqs[which]++;
|
|
|
|
/*
|
|
* Request caching: throw all duplicate requests away while in
|
|
* progress. The final response for non-idempotent requests is
|
|
* stored for re-sending the reply, so that non-idempotent requests
|
|
* are not re-done (until the cache rolls over).
|
|
* A 'ressz' of 0 distinguishes idempotent req's from non-idempotent
|
|
* req's that we save results for (ugly).
|
|
*/
|
|
switch (which) {
|
|
|
|
/*
|
|
* non-idempotent requests
|
|
*/
|
|
case RFS_SETATTR:
|
|
case RFS_WRITE:
|
|
case RFS_REMOVE:
|
|
case RFS_RENAME:
|
|
case RFS_LINK:
|
|
case RFS_SYMLINK:
|
|
case RFS_RMDIR:
|
|
case RFS_CREATE:
|
|
case RFS_MKDIR:
|
|
ressize = disp->dis_ressz;
|
|
break;
|
|
|
|
/*
|
|
* idempotent requests
|
|
*/
|
|
default:
|
|
ressize = 0;
|
|
break;
|
|
}
|
|
|
|
if (exi != NULL) {
|
|
exi->exi_refcnt++;
|
|
}
|
|
|
|
/*
|
|
* Need to catch exiting nfsd here to decr exi_refcnt
|
|
*/
|
|
qsave = u.u_qsave;
|
|
if (setjmp(&u.u_qsave)) {
|
|
if (exi != NULL) {
|
|
if (--exi->exi_refcnt == 0 &&
|
|
exi->exi_flags & EXI_WANTED)
|
|
wakeup((caddr_t)exi);
|
|
}
|
|
u.u_qsave = qsave;
|
|
longjmp(&u.u_qsave);
|
|
}
|
|
|
|
if ((dupstat = svckudp_dup(req, (caddr_t)res, ressize))
|
|
== DUP_INPROGRESS) {
|
|
/*
|
|
* drop all in-progress requests; no reply
|
|
*/
|
|
error++;
|
|
} else if (dupstat != DUP_DONE) {
|
|
/*
|
|
* Call service routine with arg struct and results struct
|
|
*/
|
|
svckudp_dupsave(req, sizeof (struct nfsdiropres));
|
|
u.u_XXX[0] = ENOTBLK;
|
|
(*disp->dis_proc)(args, res, exi, req);
|
|
/*
|
|
* if file system was locked, then drop the request and
|
|
* the client will timeout. This is better than hanging
|
|
* the network with all nfsd's sleeping on the locked
|
|
* file system; especially since the client would
|
|
* probably timeout before the lock was unlocked.
|
|
*/
|
|
if (u.u_XXX[0] == EAGAIN) {
|
|
error++;
|
|
svckudp_dupdrop(req, (caddr_t)res, ressize);
|
|
} else
|
|
svckudp_dupdone(req, (caddr_t)res, ressize);
|
|
} else if ((dupstat == DUP_DONE) && (ressize == 0)) {
|
|
/*
|
|
* re-do idempotent requests
|
|
*/
|
|
(*disp->dis_proc)(args, res, exi, req);
|
|
} else if (dupstat != DUP_DONE) {
|
|
error++; /* bad status */
|
|
}
|
|
|
|
if (exi != NULL) {
|
|
if (--exi->exi_refcnt == 0 &&
|
|
exi->exi_flags & EXI_WANTED)
|
|
wakeup((caddr_t)exi);
|
|
exi = NULL;
|
|
}
|
|
u.u_qsave = qsave;
|
|
|
|
/*
|
|
* implicitly re-send old reply for finished non-idempotent req's
|
|
*/
|
|
|
|
done:
|
|
/*
|
|
* Free arguments struct
|
|
* corbin 6/25/90 - Fix bug 1037476
|
|
*/
|
|
if (!SVC_FREEARGS(xprt, disp != NULL ? disp->dis_xdrargs : NULL,
|
|
disp != NULL ? args : NULL)) {
|
|
printf("nfs_server: bad freeargs\n");
|
|
error++;
|
|
}
|
|
if (args != NULL) {
|
|
rfsput((struct rfsspace *)args);
|
|
}
|
|
|
|
/*
|
|
* Serialize and send results struct
|
|
*/
|
|
if (!error) {
|
|
if (!svc_sendreply(xprt, disp->dis_xdrres, (caddr_t)res)) {
|
|
printf("nfs_server: bad sendreply\n");
|
|
error++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Free results struct
|
|
*/
|
|
if (res != NULL) {
|
|
if (disp->dis_resfree != nullfree) {
|
|
(*disp->dis_resfree)(res);
|
|
}
|
|
rfsput((struct rfsspace *)res);
|
|
}
|
|
/*
|
|
* restore original credentials
|
|
*/
|
|
if (newcr) {
|
|
u.u_cred = tmpcr;
|
|
crfree(newcr);
|
|
}
|
|
svstat.nbadcalls += error;
|
|
}
|
|
|
|
sattr_to_vattr(sa, vap)
|
|
register struct nfssattr *sa;
|
|
register struct vattr *vap;
|
|
{
|
|
|
|
vattr_null(vap);
|
|
vap->va_mode = sa->sa_mode;
|
|
vap->va_uid = sa->sa_uid;
|
|
vap->va_gid = sa->sa_gid;
|
|
vap->va_size = sa->sa_size;
|
|
vap->va_atime.tv_sec = sa->sa_atime.tv_sec;
|
|
vap->va_atime.tv_usec = sa->sa_atime.tv_usec;
|
|
vap->va_mtime.tv_sec = sa->sa_mtime.tv_sec;
|
|
vap->va_mtime.tv_usec = sa->sa_mtime.tv_usec;
|
|
}
|
|
|
|
/*
|
|
* Convert an fhandle into a vnode.
|
|
* Uses the file id (fh_len + fh_data) in the fhandle to get the vnode.
|
|
* WARNING: users of this routine must do a VN_RELE on the vnode when they
|
|
* are done with it.
|
|
*/
|
|
struct vnode *
|
|
fhtovp(fh, exi)
|
|
fhandle_t *fh;
|
|
struct exportinfo *exi;
|
|
{
|
|
register struct vfs *vfsp;
|
|
struct vnode *vp;
|
|
int error;
|
|
|
|
if (exi == NULL) {
|
|
return (NULL); /* not exported */
|
|
}
|
|
vfsp = getvfs(&fh->fh_fsid);
|
|
if (vfsp == NULL) {
|
|
return (NULL);
|
|
}
|
|
error = VFS_VGET(vfsp, &vp, (struct fid *)&(fh->fh_len));
|
|
if (error || vp == NULL) {
|
|
#ifdef NFSDEBUG
|
|
dprint(nfsdebug, 1, "fhtovp(%x) couldn't vget\n", fh);
|
|
#endif
|
|
return (NULL);
|
|
}
|
|
return (vp);
|
|
}
|
|
|
|
/*
|
|
* Determine if two addresses are equal
|
|
* Only AF_INET supported for now
|
|
*/
|
|
eqaddr(addr1, addr2)
|
|
struct sockaddr *addr1;
|
|
struct sockaddr *addr2;
|
|
{
|
|
|
|
if (addr1->sa_family != addr2->sa_family) {
|
|
return (0);
|
|
}
|
|
switch (addr1->sa_family) {
|
|
case AF_INET:
|
|
return (((struct sockaddr_in *) addr1)->sin_addr.s_addr ==
|
|
((struct sockaddr_in *) addr2)->sin_addr.s_addr);
|
|
case AF_NS:
|
|
/* coming soon? */
|
|
break;
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
hostinlist(sa, addrs)
|
|
struct sockaddr *sa;
|
|
struct exaddrlist *addrs;
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < addrs->naddrs; i++) {
|
|
if (eqaddr(sa, &addrs->addrvec[i])) {
|
|
return (1);
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Check to see if the given name corresponds to a
|
|
* root user of the exported filesystem.
|
|
*/
|
|
rootname(ex, netname)
|
|
struct export *ex;
|
|
char *netname;
|
|
{
|
|
int i;
|
|
int namelen;
|
|
|
|
namelen = strlen(netname) + 1;
|
|
for (i = 0; i < ex->ex_des.nnames; i++) {
|
|
if (bcmp(netname, ex->ex_des.rootnames[i], namelen) == 0) {
|
|
return (1);
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Fix for bug 1038302 - corbin
|
|
* Added anon_ok argument.
|
|
*/
|
|
checkauth(exi, req, cred, anon_ok)
|
|
struct exportinfo *exi;
|
|
struct svc_req *req;
|
|
struct ucred *cred;
|
|
int anon_ok;
|
|
{
|
|
struct authunix_parms *aup;
|
|
struct authdes_cred *adc;
|
|
int flavor;
|
|
int *gp;
|
|
short grouplen;
|
|
|
|
/*
|
|
* Check for privileged port number
|
|
*/
|
|
if (nfs_portmon &&
|
|
ntohs(req->rq_xprt->xp_raddr.sin_port) >= IPPORT_RESERVED) {
|
|
printf("NFS request from unprivileged port.\n");
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Set uid, gid, and gids to auth params
|
|
*/
|
|
flavor = req->rq_cred.oa_flavor;
|
|
if (flavor != exi->exi_export.ex_auth) {
|
|
flavor = AUTH_NULL;
|
|
/*
|
|
* Fix for bug 1038302 - corbin
|
|
* only allow anon override if credentials are of the
|
|
* correct flavor. XXX is this really necessary?
|
|
*/
|
|
anon_ok = 0;
|
|
}
|
|
switch (flavor) {
|
|
case AUTH_NULL:
|
|
cred->cr_uid = exi->exi_export.ex_anon;
|
|
cred->cr_gid = exi->exi_export.ex_anon;
|
|
gp = cred->cr_groups;
|
|
break;
|
|
|
|
case AUTH_UNIX:
|
|
aup = (struct authunix_parms *)req->rq_clntcred;
|
|
if (aup->aup_uid == 0 &&
|
|
!hostinlist((struct sockaddr *)svc_getcaller(req->rq_xprt),
|
|
&exi->exi_export.ex_unix.rootaddrs)) {
|
|
cred->cr_uid = exi->exi_export.ex_anon;
|
|
cred->cr_gid = exi->exi_export.ex_anon;
|
|
gp = cred->cr_groups;
|
|
} else {
|
|
cred->cr_uid = aup->aup_uid;
|
|
cred->cr_gid = aup->aup_gid;
|
|
bcopy((caddr_t)aup->aup_gids, (caddr_t)cred->cr_groups,
|
|
aup->aup_len * sizeof (cred->cr_groups[0]));
|
|
gp = &cred->cr_groups[aup->aup_len];
|
|
}
|
|
break;
|
|
|
|
|
|
case AUTH_DES:
|
|
adc = (struct authdes_cred *)req->rq_clntcred;
|
|
if (adc->adc_fullname.window > exi->exi_export.ex_des.window) {
|
|
return (0);
|
|
}
|
|
if (!authdes_getucred(adc, &cred->cr_uid, &cred->cr_gid,
|
|
&grouplen, cred->cr_groups)) {
|
|
if (rootname(&exi->exi_export,
|
|
adc->adc_fullname.name)) {
|
|
cred->cr_uid = 0;
|
|
} else {
|
|
cred->cr_uid = exi->exi_export.ex_anon;
|
|
}
|
|
cred->cr_gid = exi->exi_export.ex_anon;
|
|
grouplen = 0;
|
|
}
|
|
gp = &cred->cr_groups[grouplen];
|
|
break;
|
|
|
|
default:
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Set real UID/GID to effective UID/GID
|
|
* corbin 6/19/90 - Fix bug 1029628
|
|
*/
|
|
cred->cr_ruid = cred->cr_uid;
|
|
cred->cr_rgid = cred->cr_gid;
|
|
|
|
while (gp < &cred->cr_groups[NGROUPS]) {
|
|
*gp++ = NOGROUP;
|
|
}
|
|
while (gp < &cred->cr_groups[NGROUPS]) {
|
|
*gp++ = NOGROUP;
|
|
}
|
|
return (anon_ok || cred->cr_uid != (uid_t) -1);
|
|
}
|