+ int error = 0;
+ size_t pathlen;
+ struct nfs_exportfs *nxfs, *nxfs2, *nxfs3;
+ struct nfs_export *nx, *nx2, *nx3;
+ struct nfs_filehandle nfh;
+ struct nameidata mnd, xnd;
+ vnode_t mvp = NULL, xvp = NULL;
+ mount_t mp = NULL;
+ char path[MAXPATHLEN];
+ int expisroot;
+
+ if (unxa->nxa_flags == NXA_CHECK) {
+ /* just check if the path is an NFS-exportable file system */
+ error = copyinstr(unxa->nxa_fspath, path, MAXPATHLEN, &pathlen);
+ if (error)
+ return (error);
+ NDINIT(&mnd, LOOKUP, OP_LOOKUP, FOLLOW | LOCKLEAF | AUDITVNPATH1,
+ UIO_SYSSPACE, CAST_USER_ADDR_T(path), ctx);
+ error = namei(&mnd);
+ if (error)
+ return (error);
+ mvp = mnd.ni_vp;
+ mp = vnode_mount(mvp);
+ /* make sure it's the root of a file system */
+ if (!vnode_isvroot(mvp))
+ error = EINVAL;
+ /* make sure the file system is NFS-exportable */
+ if (!error) {
+ nfh.nfh_len = NFSV3_MAX_FID_SIZE;
+ error = VFS_VPTOFH(mvp, (int*)&nfh.nfh_len, &nfh.nfh_fid[0], NULL);
+ }
+ if (!error && (nfh.nfh_len > (int)NFSV3_MAX_FID_SIZE))
+ error = EIO;
+ if (!error && !(mp->mnt_vtable->vfc_vfsflags & VFC_VFSREADDIR_EXTENDED))
+ error = EISDIR;
+ vnode_put(mvp);
+ nameidone(&mnd);
+ return (error);
+ }
+
+ /* all other operations: must be super user */
+ if ((error = vfs_context_suser(ctx)))
+ return (error);
+
+ if (unxa->nxa_flags & NXA_DELETE_ALL) {
+ /* delete all exports on all file systems */
+ lck_rw_lock_exclusive(&nfsrv_export_rwlock);
+ while ((nxfs = LIST_FIRST(&nfsrv_exports))) {
+ mp = vfs_getvfs_by_mntonname(nxfs->nxfs_path);
+ if (mp) {
+ vfs_clearflags(mp, MNT_EXPORTED);
+ mount_iterdrop(mp);
+ mp = NULL;
+ }
+ /* delete all exports on this file system */
+ while ((nx = LIST_FIRST(&nxfs->nxfs_exports))) {
+ LIST_REMOVE(nx, nx_next);
+ LIST_REMOVE(nx, nx_hash);
+ /* delete all netopts for this export */
+ nfsrv_free_addrlist(nx, NULL);
+ nx->nx_flags &= ~NX_DEFAULTEXPORT;
+ if (IS_VALID_CRED(nx->nx_defopt.nxo_cred)) {
+ kauth_cred_unref(&nx->nx_defopt.nxo_cred);
+ }
+ /* free active user list for this export */
+ nfsrv_free_user_list(&nx->nx_user_list);
+ FREE(nx->nx_path, M_TEMP);
+ FREE(nx, M_TEMP);
+ }
+ LIST_REMOVE(nxfs, nxfs_next);
+ FREE(nxfs->nxfs_path, M_TEMP);
+ FREE(nxfs, M_TEMP);
+ }
+ if (nfsrv_export_hashtbl) {
+ /* all exports deleted, clean up export hash table */
+ FREE(nfsrv_export_hashtbl, M_TEMP);
+ nfsrv_export_hashtbl = NULL;
+ }
+ lck_rw_done(&nfsrv_export_rwlock);
+ return (0);
+ }
+
+ error = copyinstr(unxa->nxa_fspath, path, MAXPATHLEN, &pathlen);
+ if (error)
+ return (error);
+
+ lck_rw_lock_exclusive(&nfsrv_export_rwlock);
+
+ /* init export hash table if not already */
+ if (!nfsrv_export_hashtbl) {
+ if (nfsrv_export_hash_size <= 0)
+ nfsrv_export_hash_size = NFSRVEXPHASHSZ;
+ nfsrv_export_hashtbl = hashinit(nfsrv_export_hash_size, M_TEMP, &nfsrv_export_hash);
+ }
+
+ // first check if we've already got an exportfs with the given ID
+ LIST_FOREACH(nxfs, &nfsrv_exports, nxfs_next) {
+ if (nxfs->nxfs_id == unxa->nxa_fsid)
+ break;
+ }
+ if (nxfs) {
+ /* verify exported FS path matches given path */
+ if (strncmp(path, nxfs->nxfs_path, MAXPATHLEN)) {
+ error = EEXIST;
+ goto unlock_out;
+ }
+ if ((unxa->nxa_flags & (NXA_ADD|NXA_OFFLINE)) == NXA_ADD) {
+ /* if adding, verify that the mount is still what we expect */
+ mp = vfs_getvfs_by_mntonname(nxfs->nxfs_path);
+ if (mp) {
+ mount_ref(mp, 0);
+ mount_iterdrop(mp);
+ }
+ /* find exported FS root vnode */
+ NDINIT(&mnd, LOOKUP, OP_LOOKUP, FOLLOW | LOCKLEAF | AUDITVNPATH1,
+ UIO_SYSSPACE, CAST_USER_ADDR_T(nxfs->nxfs_path), ctx);
+ error = namei(&mnd);
+ if (error)
+ goto unlock_out;
+ mvp = mnd.ni_vp;
+ /* make sure it's (still) the root of a file system */
+ if (!vnode_isvroot(mvp)) {
+ error = EINVAL;
+ goto out;
+ }
+ /* sanity check: this should be same mount */
+ if (mp != vnode_mount(mvp)) {
+ error = EINVAL;
+ goto out;
+ }
+ }
+ } else {
+ /* no current exported file system with that ID */
+ if (!(unxa->nxa_flags & NXA_ADD)) {
+ error = ENOENT;
+ goto unlock_out;
+ }
+
+ /* find exported FS root vnode */
+ NDINIT(&mnd, LOOKUP, OP_LOOKUP, FOLLOW | LOCKLEAF | AUDITVNPATH1,
+ UIO_SYSSPACE, CAST_USER_ADDR_T(path), ctx);
+ error = namei(&mnd);
+ if (error) {
+ if (!(unxa->nxa_flags & NXA_OFFLINE))
+ goto unlock_out;
+ } else {
+ mvp = mnd.ni_vp;
+ /* make sure it's the root of a file system */
+ if (!vnode_isvroot(mvp)) {
+ /* bail if not marked offline */
+ if (!(unxa->nxa_flags & NXA_OFFLINE)) {
+ error = EINVAL;
+ goto out;
+ }
+ vnode_put(mvp);
+ nameidone(&mnd);
+ mvp = NULL;
+ } else {
+ mp = vnode_mount(mvp);
+ mount_ref(mp, 0);
+
+ /* make sure the file system is NFS-exportable */
+ nfh.nfh_len = NFSV3_MAX_FID_SIZE;
+ error = VFS_VPTOFH(mvp, (int*)&nfh.nfh_len, &nfh.nfh_fid[0], NULL);
+ if (!error && (nfh.nfh_len > (int)NFSV3_MAX_FID_SIZE))
+ error = EIO;
+ if (!error && !(mp->mnt_vtable->vfc_vfsflags & VFC_VFSREADDIR_EXTENDED))
+ error = EISDIR;
+ if (error)
+ goto out;
+ }
+ }
+
+ /* add an exportfs for it */
+ MALLOC(nxfs, struct nfs_exportfs *, sizeof(struct nfs_exportfs), M_TEMP, M_WAITOK);
+ if (!nxfs) {
+ error = ENOMEM;
+ goto out;
+ }
+ bzero(nxfs, sizeof(struct nfs_exportfs));
+ nxfs->nxfs_id = unxa->nxa_fsid;
+ MALLOC(nxfs->nxfs_path, char*, pathlen, M_TEMP, M_WAITOK);
+ if (!nxfs->nxfs_path) {
+ FREE(nxfs, M_TEMP);
+ error = ENOMEM;
+ goto out;
+ }
+ bcopy(path, nxfs->nxfs_path, pathlen);
+ /* insert into list in reverse-sorted order */
+ nxfs3 = NULL;
+ LIST_FOREACH(nxfs2, &nfsrv_exports, nxfs_next) {
+ if (strncmp(nxfs->nxfs_path, nxfs2->nxfs_path, MAXPATHLEN) > 0)
+ break;
+ nxfs3 = nxfs2;
+ }
+ if (nxfs2)
+ LIST_INSERT_BEFORE(nxfs2, nxfs, nxfs_next);
+ else if (nxfs3)
+ LIST_INSERT_AFTER(nxfs3, nxfs, nxfs_next);
+ else
+ LIST_INSERT_HEAD(&nfsrv_exports, nxfs, nxfs_next);
+
+ /* make sure any quotas are enabled before we export the file system */
+ if (mp)
+ enablequotas(mp, ctx);
+ }
+
+ if (unxa->nxa_exppath) {
+ error = copyinstr(unxa->nxa_exppath, path, MAXPATHLEN, &pathlen);
+ if (error)
+ goto out;
+ LIST_FOREACH(nx, &nxfs->nxfs_exports, nx_next) {
+ if (nx->nx_id == unxa->nxa_expid)
+ break;
+ }
+ if (nx) {
+ /* verify exported FS path matches given path */
+ if (strncmp(path, nx->nx_path, MAXPATHLEN)) {
+ error = EEXIST;
+ goto out;
+ }
+ } else {
+ /* no current export with that ID */
+ if (!(unxa->nxa_flags & NXA_ADD)) {
+ error = ENOENT;
+ goto out;
+ }
+ /* add an export for it */
+ MALLOC(nx, struct nfs_export *, sizeof(struct nfs_export), M_TEMP, M_WAITOK);
+ if (!nx) {
+ error = ENOMEM;
+ goto out1;
+ }
+ bzero(nx, sizeof(struct nfs_export));
+ nx->nx_id = unxa->nxa_expid;
+ nx->nx_fs = nxfs;
+ microtime(&nx->nx_exptime);
+ MALLOC(nx->nx_path, char*, pathlen, M_TEMP, M_WAITOK);
+ if (!nx->nx_path) {
+ error = ENOMEM;
+ FREE(nx, M_TEMP);
+ nx = NULL;
+ goto out1;
+ }
+ bcopy(path, nx->nx_path, pathlen);
+ /* initialize the active user list */
+ nfsrv_init_user_list(&nx->nx_user_list);
+ /* insert into list in reverse-sorted order */
+ nx3 = NULL;
+ LIST_FOREACH(nx2, &nxfs->nxfs_exports, nx_next) {
+ if (strncmp(nx->nx_path, nx2->nx_path, MAXPATHLEN) > 0)
+ break;
+ nx3 = nx2;
+ }
+ if (nx2)
+ LIST_INSERT_BEFORE(nx2, nx, nx_next);
+ else if (nx3)
+ LIST_INSERT_AFTER(nx3, nx, nx_next);
+ else
+ LIST_INSERT_HEAD(&nxfs->nxfs_exports, nx, nx_next);
+ /* insert into hash */
+ LIST_INSERT_HEAD(NFSRVEXPHASH(nxfs->nxfs_id, nx->nx_id), nx, nx_hash);
+
+ /*
+ * We don't allow/support nested exports. Check if the new entry
+ * nests with the entries before and after or if there's an
+ * entry for the file system root and subdirs.
+ */
+ error = 0;
+ if ((nx3 && !strncmp(nx3->nx_path, nx->nx_path, pathlen - 1) &&
+ (nx3->nx_path[pathlen-1] == '/')) ||
+ (nx2 && !strncmp(nx2->nx_path, nx->nx_path, strlen(nx2->nx_path)) &&
+ (nx->nx_path[strlen(nx2->nx_path)] == '/')))
+ error = EINVAL;
+ if (!error) {
+ /* check export conflict with fs root export and vice versa */
+ expisroot = !nx->nx_path[0] ||
+ ((nx->nx_path[0] == '.') && !nx->nx_path[1]);
+ LIST_FOREACH(nx2, &nxfs->nxfs_exports, nx_next) {
+ if (expisroot) {
+ if (nx2 != nx)
+ break;
+ } else if (!nx2->nx_path[0])
+ break;
+ else if ((nx2->nx_path[0] == '.') && !nx2->nx_path[1])
+ break;
+ }
+ if (nx2)
+ error = EINVAL;
+ }
+ if (error) {
+ /*
+ * Don't actually return an error because mountd is
+ * probably about to delete the conflicting export.
+ * This can happen when a new export momentarily conflicts
+ * with an old export while the transition is being made.
+ * Theoretically, mountd could be written to avoid this
+ * transient situation - but it would greatly increase the
+ * complexity of mountd for very little overall benefit.
+ */
+ printf("nfsrv_export: warning: nested exports: %s/%s\n",
+ nxfs->nxfs_path, nx->nx_path);
+ error = 0;
+ }
+ nx->nx_fh.nfh_xh.nxh_flags = NXHF_INVALIDFH;
+ }
+ /* make sure file handle is set up */
+ if ((nx->nx_fh.nfh_xh.nxh_version != htonl(NFS_FH_VERSION)) ||
+ (nx->nx_fh.nfh_xh.nxh_flags & NXHF_INVALIDFH)) {
+ /* try to set up export root file handle */
+ nx->nx_fh.nfh_xh.nxh_version = htonl(NFS_FH_VERSION);
+ nx->nx_fh.nfh_xh.nxh_fsid = htonl(nx->nx_fs->nxfs_id);
+ nx->nx_fh.nfh_xh.nxh_expid = htonl(nx->nx_id);
+ nx->nx_fh.nfh_xh.nxh_flags = 0;
+ nx->nx_fh.nfh_xh.nxh_reserved = 0;
+ nx->nx_fh.nfh_fhp = (u_char*)&nx->nx_fh.nfh_xh;
+ bzero(&nx->nx_fh.nfh_fid[0], NFSV2_MAX_FID_SIZE);
+ if (mvp) {
+ /* find export root vnode */
+ if (!nx->nx_path[0] || ((nx->nx_path[0] == '.') && !nx->nx_path[1])) {
+ /* exporting file system's root directory */
+ xvp = mvp;
+ vnode_get(xvp);
+ } else {
+ xnd.ni_cnd.cn_nameiop = LOOKUP;
+#if CONFIG_TRIGGERS
+ xnd.ni_op = OP_LOOKUP;
+#endif
+ xnd.ni_cnd.cn_flags = LOCKLEAF;
+ xnd.ni_pathlen = pathlen - 1;
+ xnd.ni_cnd.cn_nameptr = xnd.ni_cnd.cn_pnbuf = path;
+ xnd.ni_startdir = mvp;
+ xnd.ni_usedvp = mvp;
+ xnd.ni_cnd.cn_context = ctx;
+ while ((error = lookup(&xnd)) == ERECYCLE) {
+ xnd.ni_cnd.cn_flags = LOCKLEAF;
+ xnd.ni_cnd.cn_nameptr = xnd.ni_cnd.cn_pnbuf;
+ xnd.ni_usedvp = xnd.ni_dvp = xnd.ni_startdir = mvp;
+ }
+ if (error)
+ goto out1;
+ xvp = xnd.ni_vp;
+ }
+
+ if (vnode_vtype(xvp) != VDIR) {
+ error = EINVAL;
+ vnode_put(xvp);
+ goto out1;
+ }
+
+ /* grab file handle */
+ nx->nx_fh.nfh_len = NFSV3_MAX_FID_SIZE;
+ error = VFS_VPTOFH(xvp, (int*)&nx->nx_fh.nfh_len, &nx->nx_fh.nfh_fid[0], NULL);
+ if (!error && (nx->nx_fh.nfh_len > (int)NFSV3_MAX_FID_SIZE)) {
+ error = EIO;
+ } else {
+ nx->nx_fh.nfh_xh.nxh_fidlen = nx->nx_fh.nfh_len;
+ nx->nx_fh.nfh_len += sizeof(nx->nx_fh.nfh_xh);
+ }
+
+ vnode_put(xvp);
+ if (error)
+ goto out1;
+ } else {
+ nx->nx_fh.nfh_xh.nxh_flags = NXHF_INVALIDFH;
+ nx->nx_fh.nfh_xh.nxh_fidlen = 0;
+ nx->nx_fh.nfh_len = sizeof(nx->nx_fh.nfh_xh);
+ }
+ }
+ } else {
+ nx = NULL;
+ }
+
+ /* perform the export changes */
+ if (unxa->nxa_flags & NXA_DELETE) {
+ if (!nx) {
+ /* delete all exports on this file system */
+ while ((nx = LIST_FIRST(&nxfs->nxfs_exports))) {
+ LIST_REMOVE(nx, nx_next);
+ LIST_REMOVE(nx, nx_hash);
+ /* delete all netopts for this export */
+ nfsrv_free_addrlist(nx, NULL);
+ nx->nx_flags &= ~NX_DEFAULTEXPORT;
+ if (IS_VALID_CRED(nx->nx_defopt.nxo_cred)) {
+ kauth_cred_unref(&nx->nx_defopt.nxo_cred);
+ }
+ /* delete active user list for this export */
+ nfsrv_free_user_list(&nx->nx_user_list);
+ FREE(nx->nx_path, M_TEMP);
+ FREE(nx, M_TEMP);
+ }
+ goto out1;
+ } else if (!unxa->nxa_netcount) {
+ /* delete all netopts for this export */
+ nfsrv_free_addrlist(nx, NULL);
+ nx->nx_flags &= ~NX_DEFAULTEXPORT;
+ if (IS_VALID_CRED(nx->nx_defopt.nxo_cred)) {
+ kauth_cred_unref(&nx->nx_defopt.nxo_cred);
+ }
+ } else {
+ /* delete only the netopts for the given addresses */
+ error = nfsrv_free_addrlist(nx, unxa);
+ if (error)
+ goto out1;
+ }
+ }
+ if (unxa->nxa_flags & NXA_ADD) {
+ /*
+ * If going offline set the export time so that when
+ * coming back on line we will present a new write verifier
+ * to the client.
+ */
+ if (unxa->nxa_flags & NXA_OFFLINE)
+ microtime(&nx->nx_exptime);
+
+ error = nfsrv_hang_addrlist(nx, unxa);
+ if (!error && mp)
+ vfs_setflags(mp, MNT_EXPORTED);
+ }
+
+out1:
+ if (nx && !nx->nx_expcnt) {
+ /* export has no export options */
+ LIST_REMOVE(nx, nx_next);
+ LIST_REMOVE(nx, nx_hash);
+ /* delete active user list for this export */
+ nfsrv_free_user_list(&nx->nx_user_list);
+ FREE(nx->nx_path, M_TEMP);
+ FREE(nx, M_TEMP);
+ }
+ if (LIST_EMPTY(&nxfs->nxfs_exports)) {
+ /* exported file system has no more exports */
+ LIST_REMOVE(nxfs, nxfs_next);
+ FREE(nxfs->nxfs_path, M_TEMP);
+ FREE(nxfs, M_TEMP);
+ if (mp)
+ vfs_clearflags(mp, MNT_EXPORTED);
+ }
+
+out:
+ if (mvp) {
+ vnode_put(mvp);
+ nameidone(&mnd);
+ }
+unlock_out:
+ if (mp)
+ mount_drop(mp, 0);
+ lck_rw_done(&nfsrv_export_rwlock);
+ return (error);
+}
+
+struct nfs_export_options *
+nfsrv_export_lookup(struct nfs_export *nx, mbuf_t nam)
+{
+ struct nfs_export_options *nxo = NULL;
+ struct nfs_netopt *no = NULL;
+ struct radix_node_head *rnh;
+ struct sockaddr *saddr;
+
+ /* Lookup in the export list first. */
+ if (nam != NULL) {
+ saddr = mbuf_data(nam);
+ rnh = nx->nx_rtable[saddr->sa_family];
+ if (rnh != NULL) {
+ no = (struct nfs_netopt *)
+ (*rnh->rnh_matchaddr)((caddr_t)saddr, rnh);
+ if (no && no->no_rnodes->rn_flags & RNF_ROOT)
+ no = NULL;
+ if (no)
+ nxo = &no->no_opt;
+ }
+ }
+ /* If no address match, use the default if it exists. */
+ if ((nxo == NULL) && (nx->nx_flags & NX_DEFAULTEXPORT))
+ nxo = &nx->nx_defopt;
+ return (nxo);
+}
+
+/* find an export for the given handle */
+struct nfs_export *
+nfsrv_fhtoexport(struct nfs_filehandle *nfhp)
+{
+ struct nfs_exphandle *nxh = (struct nfs_exphandle*)nfhp->nfh_fhp;
+ struct nfs_export *nx;
+ uint32_t fsid, expid;
+
+ if (!nfsrv_export_hashtbl)
+ return (NULL);
+ fsid = ntohl(nxh->nxh_fsid);
+ expid = ntohl(nxh->nxh_expid);
+ nx = NFSRVEXPHASH(fsid, expid)->lh_first;
+ for (; nx; nx = LIST_NEXT(nx, nx_hash)) {
+ if (nx->nx_fs->nxfs_id != fsid)
+ continue;
+ if (nx->nx_id != expid)
+ continue;
+ break;
+ }
+ return (nx);
+}
+
+/*
+ * nfsrv_fhtovp() - convert FH to vnode and export info
+ */
+int
+nfsrv_fhtovp(
+ struct nfs_filehandle *nfhp,
+ struct nfsrv_descript *nd,
+ vnode_t *vpp,
+ struct nfs_export **nxp,
+ struct nfs_export_options **nxop)
+{
+ struct nfs_exphandle *nxh = (struct nfs_exphandle*)nfhp->nfh_fhp;
+ struct nfs_export_options *nxo;
+ u_char *fidp;
+ int error;
+ struct mount *mp;
+ mbuf_t nam = NULL;
+ uint32_t v;
+ int i, valid;
+
+ *vpp = NULL;
+ *nxp = NULL;
+ *nxop = NULL;
+
+ if (nd != NULL)
+ nam = nd->nd_nam;
+
+ v = ntohl(nxh->nxh_version);
+ if (v != NFS_FH_VERSION) {
+ /* file handle format not supported */
+ return (ESTALE);
+ }
+ if (nfhp->nfh_len > NFSV3_MAX_FH_SIZE)
+ return (EBADRPC);
+ if (nfhp->nfh_len < (int)sizeof(struct nfs_exphandle))
+ return (ESTALE);
+ v = ntohs(nxh->nxh_flags);
+ if (v & NXHF_INVALIDFH)
+ return (ESTALE);
+
+ *nxp = nfsrv_fhtoexport(nfhp);
+ if (!*nxp)
+ return (ESTALE);
+
+ /* Get the export option structure for this <export, client> tuple. */
+ *nxop = nxo = nfsrv_export_lookup(*nxp, nam);
+ if (nam && (*nxop == NULL))
+ return (EACCES);
+
+ if (nd != NULL) {
+ /* Validate the security flavor of the request */
+ for (i = 0, valid = 0; i < nxo->nxo_sec.count; i++) {
+ if (nd->nd_sec == nxo->nxo_sec.flavors[i]) {
+ valid = 1;
+ break;
+ }
+ }
+ if (!valid) {
+ /*
+ * RFC 2623 section 2.3.2 recommends no authentication
+ * requirement for certain NFS procedures used for mounting.
+ * This allows an unauthenticated superuser on the client
+ * to do mounts for the benefit of authenticated users.
+ */
+ if (nd->nd_vers == NFS_VER2)
+ if (nd->nd_procnum == NFSV2PROC_GETATTR ||
+ nd->nd_procnum == NFSV2PROC_STATFS)
+ valid = 1;
+ if (nd->nd_vers == NFS_VER3)
+ if (nd->nd_procnum == NFSPROC_FSINFO)
+ valid = 1;
+
+ if (!valid)
+ return (NFSERR_AUTHERR | AUTH_REJECTCRED);
+ }
+ }
+
+ if (nxo && (nxo->nxo_flags & NX_OFFLINE))
+ return ((nd == NULL || nd->nd_vers == NFS_VER2) ? ESTALE : NFSERR_TRYLATER);
+
+ /* find mount structure */
+ mp = vfs_getvfs_by_mntonname((*nxp)->nx_fs->nxfs_path);
+ if (mp) {
+ error = vfs_busy(mp, LK_NOWAIT);
+ mount_iterdrop(mp);
+ if (error)
+ mp = NULL;
+ }
+ if (!mp) {
+ /*
+ * We have an export, but no mount?
+ * Perhaps the export just hasn't been marked offline yet.
+ */
+ return ((nd == NULL || nd->nd_vers == NFS_VER2) ? ESTALE : NFSERR_TRYLATER);
+ }
+
+ fidp = nfhp->nfh_fhp + sizeof(*nxh);
+ error = VFS_FHTOVP(mp, nxh->nxh_fidlen, fidp, vpp, NULL);
+ vfs_unbusy(mp);
+ if (error)
+ return (error);
+ /* vnode pointer should be good at this point or ... */
+ if (*vpp == NULL)
+ return (ESTALE);
+ return (0);
+}
+
+/*
+ * nfsrv_credcheck() - check/map credentials according
+ * to given export options.
+ */
+int
+nfsrv_credcheck(
+ struct nfsrv_descript *nd,
+ vfs_context_t ctx,
+ __unused struct nfs_export *nx,
+ struct nfs_export_options *nxo)
+{
+ if (nxo && nxo->nxo_cred) {
+ if ((nxo->nxo_flags & NX_MAPALL) ||
+ ((nxo->nxo_flags & NX_MAPROOT) && !suser(nd->nd_cr, NULL))) {
+ kauth_cred_ref(nxo->nxo_cred);
+ kauth_cred_unref(&nd->nd_cr);
+ nd->nd_cr = nxo->nxo_cred;
+ }
+ }
+ ctx->vc_ucred = nd->nd_cr;
+ return (0);
+}
+
+/*
+ * nfsrv_vptofh() - convert vnode to file handle for given export
+ *
+ * If the caller is passing in a vnode for a ".." directory entry,
+ * they can pass a directory NFS file handle (dnfhp) which will be
+ * checked against the root export file handle. If it matches, we
+ * refuse to provide the file handle for the out-of-export directory.
+ */
+int
+nfsrv_vptofh(
+ struct nfs_export *nx,
+ int nfsvers,
+ struct nfs_filehandle *dnfhp,
+ vnode_t vp,
+ vfs_context_t ctx,
+ struct nfs_filehandle *nfhp)
+{
+ int error;
+ uint32_t maxfidsize;
+
+ nfhp->nfh_fhp = (u_char*)&nfhp->nfh_xh;
+ nfhp->nfh_xh.nxh_version = htonl(NFS_FH_VERSION);
+ nfhp->nfh_xh.nxh_fsid = htonl(nx->nx_fs->nxfs_id);
+ nfhp->nfh_xh.nxh_expid = htonl(nx->nx_id);
+ nfhp->nfh_xh.nxh_flags = 0;
+ nfhp->nfh_xh.nxh_reserved = 0;
+
+ if (nfsvers == NFS_VER2)
+ bzero(&nfhp->nfh_fid[0], NFSV2_MAX_FID_SIZE);
+
+ /* if directory FH matches export root, return invalid FH */
+ if (dnfhp && nfsrv_fhmatch(dnfhp, &nx->nx_fh)) {
+ if (nfsvers == NFS_VER2)
+ nfhp->nfh_len = NFSX_V2FH;
+ else
+ nfhp->nfh_len = sizeof(nfhp->nfh_xh);
+ nfhp->nfh_xh.nxh_fidlen = 0;
+ nfhp->nfh_xh.nxh_flags = htons(NXHF_INVALIDFH);
+ return (0);
+ }
+
+ if (nfsvers == NFS_VER2)
+ maxfidsize = NFSV2_MAX_FID_SIZE;
+ else
+ maxfidsize = NFSV3_MAX_FID_SIZE;
+ nfhp->nfh_len = maxfidsize;
+
+ error = VFS_VPTOFH(vp, (int*)&nfhp->nfh_len, &nfhp->nfh_fid[0], ctx);
+ if (error)
+ return (error);
+ if (nfhp->nfh_len > maxfidsize)
+ return (EOVERFLOW);
+ nfhp->nfh_xh.nxh_fidlen = nfhp->nfh_len;
+ nfhp->nfh_len += sizeof(nfhp->nfh_xh);
+ if ((nfsvers == NFS_VER2) && (nfhp->nfh_len < NFSX_V2FH))
+ nfhp->nfh_len = NFSX_V2FH;
+
+ return (0);
+}
+
+/*
+ * Compare two file handles to see it they're the same.
+ * Note that we don't use nfh_len because that may include
+ * padding in an NFSv2 file handle.
+ */
+int
+nfsrv_fhmatch(struct nfs_filehandle *fh1, struct nfs_filehandle *fh2)
+{
+ struct nfs_exphandle *nxh1, *nxh2;
+ int len1, len2;
+
+ nxh1 = (struct nfs_exphandle *)fh1->nfh_fhp;
+ nxh2 = (struct nfs_exphandle *)fh2->nfh_fhp;
+ len1 = sizeof(fh1->nfh_xh) + nxh1->nxh_fidlen;
+ len2 = sizeof(fh2->nfh_xh) + nxh2->nxh_fidlen;
+ if (len1 != len2)
+ return (0);
+ if (bcmp(nxh1, nxh2, len1))
+ return (0);
+ return (1);
+}
+
+/*
+ * Functions for dealing with active user lists
+ */
+
+/*
+ * Search the hash table for a user node with a matching IP address and uid field.
+ * If found, the node's tm_last timestamp is updated and the node is returned.
+ *
+ * If not found, a new node is allocated (or reclaimed via LRU), initialized, and returned.
+ * Returns NULL if a new node could not be allcoated.
+ *
+ * The list's user_mutex lock MUST be held.
+ */
+struct nfs_user_stat_node *
+nfsrv_get_user_stat_node(struct nfs_active_user_list *list, struct sockaddr *saddr, uid_t uid)
+{
+ struct nfs_user_stat_node *unode;
+ struct timeval now;
+ struct nfs_user_stat_hashtbl_head *head;
+
+ /* seach the hash table */
+ head = NFS_USER_STAT_HASH(list->user_hashtbl, uid);
+ LIST_FOREACH(unode, head, hash_link) {
+ if ((uid == unode->uid) && (nfs_sockaddr_cmp(saddr, (struct sockaddr*)&unode->sock) == 0)) {
+ /* found matching node */
+ break;
+ }
+ }
+
+ if (unode) {
+ /* found node in the hash table, now update lru position */
+ TAILQ_REMOVE(&list->user_lru, unode, lru_link);
+ TAILQ_INSERT_TAIL(&list->user_lru, unode, lru_link);
+
+ /* update time stamp */
+ microtime(&now);
+ unode->tm_last = (uint32_t)now.tv_sec;
+ return unode;
+ }
+
+ if (list->node_count < nfsrv_user_stat_max_nodes) {
+ /* Allocate a new node */
+ MALLOC(unode, struct nfs_user_stat_node *, sizeof(struct nfs_user_stat_node),
+ M_TEMP, M_WAITOK | M_ZERO);
+
+ if (!unode)
+ return NULL;
+
+ /* increment node count */
+ OSAddAtomic(1, &nfsrv_user_stat_node_count);
+ list->node_count++;
+ } else {
+ /* reuse the oldest node in the lru list */
+ unode = TAILQ_FIRST(&list->user_lru);
+
+ if (!unode)
+ return NULL;
+
+ /* Remove the node */
+ TAILQ_REMOVE(&list->user_lru, unode, lru_link);
+ LIST_REMOVE(unode, hash_link);
+ }
+
+ /* Initialize the node */
+ unode->uid = uid;
+ bcopy(saddr, &unode->sock, saddr->sa_len);
+ microtime(&now);
+ unode->ops = 0;
+ unode->bytes_read = 0;
+ unode->bytes_written = 0;
+ unode->tm_start = (uint32_t)now.tv_sec;
+ unode->tm_last = (uint32_t)now.tv_sec;
+
+ /* insert the node */
+ TAILQ_INSERT_TAIL(&list->user_lru, unode, lru_link);
+ LIST_INSERT_HEAD(head, unode, hash_link);
+
+ return unode;
+}
+
+void
+nfsrv_update_user_stat(struct nfs_export *nx, struct nfsrv_descript *nd, uid_t uid, u_int ops, u_int rd_bytes, u_int wr_bytes)
+{
+ struct nfs_user_stat_node *unode;
+ struct nfs_active_user_list *ulist;
+ struct sockaddr *saddr;
+
+ if ((!nfsrv_user_stat_enabled) || (!nx) || (!nd) || (!nd->nd_nam))
+ return;
+
+ saddr = (struct sockaddr *)mbuf_data(nd->nd_nam);
+
+ /* check address family before going any further */
+ if ((saddr->sa_family != AF_INET) && (saddr->sa_family != AF_INET6))
+ return;
+
+ ulist = &nx->nx_user_list;
+
+ /* lock the active user list */
+ lck_mtx_lock(&ulist->user_mutex);
+
+ /* get the user node */
+ unode = nfsrv_get_user_stat_node(ulist, saddr, uid);
+
+ if (!unode) {
+ lck_mtx_unlock(&ulist->user_mutex);
+ return;
+ }
+
+ /* update counters */
+ unode->ops += ops;
+ unode->bytes_read += rd_bytes;
+ unode->bytes_written += wr_bytes;
+
+ /* done */
+ lck_mtx_unlock(&ulist->user_mutex);
+}
+
+/* initialize an active user list */
+void
+nfsrv_init_user_list(struct nfs_active_user_list *ulist)
+{
+ uint i;
+
+ /* initialize the lru */
+ TAILQ_INIT(&ulist->user_lru);
+
+ /* initialize the hash table */
+ for(i = 0; i < NFS_USER_STAT_HASH_SIZE; i++)
+ LIST_INIT(&ulist->user_hashtbl[i]);
+ ulist->node_count = 0;
+
+ lck_mtx_init(&ulist->user_mutex, nfsrv_active_user_mutex_group, LCK_ATTR_NULL);
+}
+
+/* Free all nodes in an active user list */
+void
+nfsrv_free_user_list(struct nfs_active_user_list *ulist)
+{
+ struct nfs_user_stat_node *unode;
+
+ if (!ulist)
+ return;
+
+ while ((unode = TAILQ_FIRST(&ulist->user_lru))) {
+ /* Remove node and free */
+ TAILQ_REMOVE(&ulist->user_lru, unode, lru_link);
+ LIST_REMOVE(unode, hash_link);
+ FREE(unode, M_TEMP);
+
+ /* decrement node count */
+ OSAddAtomic(-1, &nfsrv_user_stat_node_count);
+ }
+ ulist->node_count = 0;
+
+ lck_mtx_destroy(&ulist->user_mutex, nfsrv_active_user_mutex_group);
+}
+
+/* Reclaim old expired user nodes from active user lists. */
+void
+nfsrv_active_user_list_reclaim(void)
+{
+ struct nfs_exportfs *nxfs;
+ struct nfs_export *nx;
+ struct nfs_active_user_list *ulist;
+ struct nfs_user_stat_hashtbl_head oldlist;
+ struct nfs_user_stat_node *unode, *unode_next;
+ struct timeval now;
+ uint32_t tstale;
+
+ LIST_INIT(&oldlist);
+
+ lck_rw_lock_shared(&nfsrv_export_rwlock);
+ microtime(&now);
+ tstale = now.tv_sec - nfsrv_user_stat_max_idle_sec;
+ LIST_FOREACH(nxfs, &nfsrv_exports, nxfs_next) {
+ LIST_FOREACH(nx, &nxfs->nxfs_exports, nx_next) {
+ /* Scan through all user nodes of this export */
+ ulist = &nx->nx_user_list;
+ lck_mtx_lock(&ulist->user_mutex);
+ for (unode = TAILQ_FIRST(&ulist->user_lru); unode; unode = unode_next) {
+ unode_next = TAILQ_NEXT(unode, lru_link);
+
+ /* check if this node has expired */
+ if (unode->tm_last >= tstale)
+ break;
+
+ /* Remove node from the active user list */
+ TAILQ_REMOVE(&ulist->user_lru, unode, lru_link);
+ LIST_REMOVE(unode, hash_link);
+
+ /* Add node to temp list */
+ LIST_INSERT_HEAD(&oldlist, unode, hash_link);
+
+ /* decrement node count */
+ OSAddAtomic(-1, &nfsrv_user_stat_node_count);
+ ulist->node_count--;
+ }
+ /* can unlock this export's list now */
+ lck_mtx_unlock(&ulist->user_mutex);
+ }
+ }
+ lck_rw_done(&nfsrv_export_rwlock);
+
+ /* Free expired nodes */
+ while ((unode = LIST_FIRST(&oldlist))) {
+ LIST_REMOVE(unode, hash_link);
+ FREE(unode, M_TEMP);
+ }
+}
+
+/*
+ * Maps errno values to nfs error numbers.
+ * Use NFSERR_IO as the catch all for ones not specifically defined in
+ * RFC 1094.
+ */
+static u_char nfsrv_v2errmap[] = {
+ NFSERR_PERM, NFSERR_NOENT, NFSERR_IO, NFSERR_IO, NFSERR_IO,
+ NFSERR_NXIO, NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO,
+ NFSERR_IO, NFSERR_IO, NFSERR_ACCES, NFSERR_IO, NFSERR_IO,
+ NFSERR_IO, NFSERR_EXIST, NFSERR_IO, NFSERR_NODEV, NFSERR_NOTDIR,
+ NFSERR_ISDIR, NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO,
+ NFSERR_IO, NFSERR_FBIG, NFSERR_NOSPC, NFSERR_IO, NFSERR_ROFS,
+ NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO,
+ NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO,
+ NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO,
+ NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO,
+ NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO,
+ NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO,
+ NFSERR_IO, NFSERR_IO, NFSERR_NAMETOL, NFSERR_IO, NFSERR_IO,
+ NFSERR_NOTEMPTY, NFSERR_IO, NFSERR_IO, NFSERR_DQUOT, NFSERR_STALE,
+};
+
+/*
+ * Maps errno values to nfs error numbers.
+ * Although it is not obvious whether or not NFS clients really care if
+ * a returned error value is in the specified list for the procedure, the
+ * safest thing to do is filter them appropriately. For Version 2, the
+ * X/Open XNFS document is the only specification that defines error values
+ * for each RPC (The RFC simply lists all possible error values for all RPCs),
+ * so I have decided to not do this for Version 2.
+ * The first entry is the default error return and the rest are the valid
+ * errors for that RPC in increasing numeric order.
+ */
+static short nfsv3err_null[] = {
+ 0,
+ 0,
+};
+
+static short nfsv3err_getattr[] = {
+ NFSERR_IO,
+ NFSERR_IO,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_SERVERFAULT,
+ NFSERR_TRYLATER,
+ 0,
+};
+
+static short nfsv3err_setattr[] = {
+ NFSERR_IO,
+ NFSERR_PERM,
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_INVAL,
+ NFSERR_NOSPC,
+ NFSERR_ROFS,
+ NFSERR_DQUOT,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_NOT_SYNC,
+ NFSERR_SERVERFAULT,
+ NFSERR_TRYLATER,
+ 0,
+};
+
+static short nfsv3err_lookup[] = {
+ NFSERR_IO,
+ NFSERR_NOENT,
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_NOTDIR,
+ NFSERR_NAMETOL,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_SERVERFAULT,
+ NFSERR_TRYLATER,
+ 0,
+};
+
+static short nfsv3err_access[] = {
+ NFSERR_IO,
+ NFSERR_IO,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_SERVERFAULT,
+ NFSERR_TRYLATER,
+ 0,
+};
+
+static short nfsv3err_readlink[] = {
+ NFSERR_IO,
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_INVAL,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_NOTSUPP,
+ NFSERR_SERVERFAULT,
+ NFSERR_TRYLATER,
+ 0,
+};
+
+static short nfsv3err_read[] = {
+ NFSERR_IO,
+ NFSERR_IO,
+ NFSERR_NXIO,
+ NFSERR_ACCES,
+ NFSERR_INVAL,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_SERVERFAULT,
+ NFSERR_TRYLATER,
+ 0,
+};
+
+static short nfsv3err_write[] = {
+ NFSERR_IO,
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_INVAL,
+ NFSERR_FBIG,
+ NFSERR_NOSPC,
+ NFSERR_ROFS,
+ NFSERR_DQUOT,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_SERVERFAULT,
+ NFSERR_TRYLATER,
+ 0,
+};
+
+static short nfsv3err_create[] = {
+ NFSERR_IO,
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_EXIST,
+ NFSERR_NOTDIR,
+ NFSERR_NOSPC,
+ NFSERR_ROFS,
+ NFSERR_NAMETOL,
+ NFSERR_DQUOT,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_NOTSUPP,
+ NFSERR_SERVERFAULT,
+ NFSERR_TRYLATER,
+ 0,
+};
+
+static short nfsv3err_mkdir[] = {
+ NFSERR_IO,
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_EXIST,
+ NFSERR_NOTDIR,
+ NFSERR_NOSPC,
+ NFSERR_ROFS,
+ NFSERR_NAMETOL,
+ NFSERR_DQUOT,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_NOTSUPP,
+ NFSERR_SERVERFAULT,
+ NFSERR_TRYLATER,
+ 0,
+};
+
+static short nfsv3err_symlink[] = {
+ NFSERR_IO,
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_EXIST,
+ NFSERR_NOTDIR,
+ NFSERR_NOSPC,
+ NFSERR_ROFS,
+ NFSERR_NAMETOL,
+ NFSERR_DQUOT,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_NOTSUPP,
+ NFSERR_SERVERFAULT,
+ NFSERR_TRYLATER,
+ 0,
+};
+
+static short nfsv3err_mknod[] = {
+ NFSERR_IO,
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_EXIST,
+ NFSERR_NOTDIR,
+ NFSERR_NOSPC,
+ NFSERR_ROFS,
+ NFSERR_NAMETOL,
+ NFSERR_DQUOT,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_NOTSUPP,
+ NFSERR_SERVERFAULT,
+ NFSERR_BADTYPE,
+ NFSERR_TRYLATER,
+ 0,
+};
+
+static short nfsv3err_remove[] = {
+ NFSERR_IO,
+ NFSERR_NOENT,
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_NOTDIR,
+ NFSERR_ROFS,
+ NFSERR_NAMETOL,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_SERVERFAULT,
+ NFSERR_TRYLATER,
+ 0,
+};
+
+static short nfsv3err_rmdir[] = {
+ NFSERR_IO,
+ NFSERR_NOENT,
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_EXIST,
+ NFSERR_NOTDIR,
+ NFSERR_INVAL,
+ NFSERR_ROFS,
+ NFSERR_NAMETOL,
+ NFSERR_NOTEMPTY,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_NOTSUPP,
+ NFSERR_SERVERFAULT,
+ NFSERR_TRYLATER,
+ 0,
+};
+
+static short nfsv3err_rename[] = {
+ NFSERR_IO,
+ NFSERR_NOENT,
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_EXIST,
+ NFSERR_XDEV,
+ NFSERR_NOTDIR,
+ NFSERR_ISDIR,
+ NFSERR_INVAL,
+ NFSERR_NOSPC,
+ NFSERR_ROFS,
+ NFSERR_MLINK,
+ NFSERR_NAMETOL,
+ NFSERR_NOTEMPTY,
+ NFSERR_DQUOT,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_NOTSUPP,
+ NFSERR_SERVERFAULT,
+ NFSERR_TRYLATER,
+ 0,
+};
+
+static short nfsv3err_link[] = {
+ NFSERR_IO,
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_EXIST,
+ NFSERR_XDEV,
+ NFSERR_NOTDIR,
+ NFSERR_INVAL,
+ NFSERR_NOSPC,
+ NFSERR_ROFS,
+ NFSERR_MLINK,
+ NFSERR_NAMETOL,
+ NFSERR_DQUOT,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_NOTSUPP,
+ NFSERR_SERVERFAULT,
+ NFSERR_TRYLATER,
+ 0,
+};
+
+static short nfsv3err_readdir[] = {
+ NFSERR_IO,
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_NOTDIR,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_BAD_COOKIE,
+ NFSERR_TOOSMALL,
+ NFSERR_SERVERFAULT,
+ NFSERR_TRYLATER,
+ 0,
+};
+
+static short nfsv3err_readdirplus[] = {
+ NFSERR_IO,
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_NOTDIR,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_BAD_COOKIE,
+ NFSERR_NOTSUPP,
+ NFSERR_TOOSMALL,
+ NFSERR_SERVERFAULT,
+ NFSERR_TRYLATER,
+ 0,
+};
+
+static short nfsv3err_fsstat[] = {
+ NFSERR_IO,
+ NFSERR_IO,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_SERVERFAULT,
+ NFSERR_TRYLATER,
+ 0,
+};
+
+static short nfsv3err_fsinfo[] = {
+ NFSERR_STALE,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_SERVERFAULT,
+ NFSERR_TRYLATER,
+ 0,
+};
+
+static short nfsv3err_pathconf[] = {
+ NFSERR_STALE,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_SERVERFAULT,
+ NFSERR_TRYLATER,
+ 0,
+};
+
+static short nfsv3err_commit[] = {
+ NFSERR_IO,
+ NFSERR_IO,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_SERVERFAULT,
+ NFSERR_TRYLATER,
+ 0,
+};
+
+static short *nfsrv_v3errmap[] = {
+ nfsv3err_null,
+ nfsv3err_getattr,
+ nfsv3err_setattr,
+ nfsv3err_lookup,
+ nfsv3err_access,
+ nfsv3err_readlink,
+ nfsv3err_read,
+ nfsv3err_write,
+ nfsv3err_create,
+ nfsv3err_mkdir,
+ nfsv3err_symlink,
+ nfsv3err_mknod,
+ nfsv3err_remove,
+ nfsv3err_rmdir,
+ nfsv3err_rename,
+ nfsv3err_link,
+ nfsv3err_readdir,
+ nfsv3err_readdirplus,
+ nfsv3err_fsstat,
+ nfsv3err_fsinfo,
+ nfsv3err_pathconf,
+ nfsv3err_commit,
+};
+
+/*
+ * Map errnos to NFS error numbers. For Version 3 also filter out error
+ * numbers not specified for the associated procedure.
+ */
+int
+nfsrv_errmap(struct nfsrv_descript *nd, int err)
+{
+ short *defaulterrp, *errp;
+
+ if (nd->nd_vers == NFS_VER2) {
+ if (err <= (int)sizeof(nfsrv_v2errmap))
+ return ((int)nfsrv_v2errmap[err - 1]);
+ return (NFSERR_IO);
+ }
+ /* NFSv3 */
+ if (nd->nd_procnum > NFSPROC_COMMIT)
+ return (err & 0xffff);
+ errp = defaulterrp = nfsrv_v3errmap[nd->nd_procnum];
+ while (*++errp) {
+ if (*errp == err)
+ return (err);
+ else if (*errp > err)
+ break;