+ 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;