+/*
+ * VFS Operations.
+ *
+ * mount system call
+ */
+int
+nfs_vfs_mount(mount_t mp, vnode_t vp, user_addr_t data, vfs_context_t ctx)
+{
+ int error = 0, inkernel = vfs_iskernelmount(mp);
+ uint32_t argsversion, argslength;
+ char *xdrbuf = NULL;
+
+ /* read in version */
+ if (inkernel)
+ bcopy(CAST_DOWN(void *, data), &argsversion, sizeof(argsversion));
+ else if ((error = copyin(data, &argsversion, sizeof(argsversion))))
+ return (error);
+
+ /* If we have XDR args, then all values in the buffer are in network order */
+ if (argsversion == htonl(NFS_ARGSVERSION_XDR))
+ argsversion = NFS_ARGSVERSION_XDR;
+
+ switch (argsversion) {
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ /* convert old-style args to xdr */
+ error = nfs_convert_old_nfs_args(mp, data, ctx, argsversion, inkernel, &xdrbuf);
+ break;
+ case NFS_ARGSVERSION_XDR:
+ /* copy in xdr buffer */
+ if (inkernel)
+ bcopy(CAST_DOWN(void *, (data + XDRWORD)), &argslength, XDRWORD);
+ else
+ error = copyin((data + XDRWORD), &argslength, XDRWORD);
+ if (error)
+ break;
+ argslength = ntohl(argslength);
+ /* put a reasonable limit on the size of the XDR args */
+ if (argslength > 16*1024) {
+ error = E2BIG;
+ break;
+ }
+ /* allocate xdr buffer */
+ xdrbuf = xb_malloc(xdr_rndup(argslength));
+ if (!xdrbuf) {
+ error = ENOMEM;
+ break;
+ }
+ if (inkernel)
+ bcopy(CAST_DOWN(void *, data), xdrbuf, argslength);
+ else
+ error = copyin(data, xdrbuf, argslength);
+ break;
+ default:
+ error = EPROGMISMATCH;
+ }
+
+ if (error) {
+ if (xdrbuf)
+ xb_free(xdrbuf);
+ return (error);
+ }
+ error = mountnfs(xdrbuf, mp, ctx, &vp);
+ return (error);
+}
+
+/*
+ * Common code for mount and mountroot
+ */
+
+/* Set up an NFSv2/v3 mount */
+int
+nfs3_mount(
+ struct nfsmount *nmp,
+ vfs_context_t ctx,
+ nfsnode_t *npp)
+{
+ int error = 0;
+ struct nfs_vattr nvattr;
+ u_int64_t xid;
+
+ *npp = NULL;
+
+ if (!nmp->nm_fh)
+ return (EINVAL);
+
+ /*
+ * Get file attributes for the mountpoint. These are needed
+ * in order to properly create the root vnode.
+ */
+ error = nfs3_getattr_rpc(NULL, nmp->nm_mountp, nmp->nm_fh->fh_data, nmp->nm_fh->fh_len, 0,
+ ctx, &nvattr, &xid);
+ if (error)
+ goto out;
+
+ error = nfs_nget(nmp->nm_mountp, NULL, NULL, nmp->nm_fh->fh_data, nmp->nm_fh->fh_len,
+ &nvattr, &xid, RPCAUTH_UNKNOWN, NG_MARKROOT, npp);
+ if (*npp)
+ nfs_node_unlock(*npp);
+ if (error)
+ goto out;
+
+ /*
+ * Try to make sure we have all the general info from the server.
+ */
+ if (nmp->nm_vers == NFS_VER2) {
+ NFS_BITMAP_SET(nmp->nm_fsattr.nfsa_bitmap, NFS_FATTR_MAXNAME);
+ nmp->nm_fsattr.nfsa_maxname = NFS_MAXNAMLEN;
+ } else if (nmp->nm_vers == NFS_VER3) {
+ /* get the NFSv3 FSINFO */
+ error = nfs3_fsinfo(nmp, *npp, ctx);
+ if (error)
+ goto out;
+ /* If the server indicates all pathconf info is */
+ /* the same, grab a copy of that info now */
+ if (NFS_BITMAP_ISSET(nmp->nm_fsattr.nfsa_bitmap, NFS_FATTR_HOMOGENEOUS) &&
+ (nmp->nm_fsattr.nfsa_flags & NFS_FSFLAG_HOMOGENEOUS)) {
+ struct nfs_fsattr nfsa;
+ if (!nfs3_pathconf_rpc(*npp, &nfsa, ctx)) {
+ /* cache a copy of the results */
+ lck_mtx_lock(&nmp->nm_lock);
+ nfs3_pathconf_cache(nmp, &nfsa);
+ lck_mtx_unlock(&nmp->nm_lock);
+ }
+ }
+ }
+out:
+ if (*npp && error) {
+ vnode_put(NFSTOV(*npp));
+ vnode_recycle(NFSTOV(*npp));
+ *npp = NULL;
+ }
+ return (error);
+}
+
+/*
+ * Update an NFSv4 mount path with the contents of the symlink.
+ *
+ * Read the link for the given file handle.
+ * Insert the link's components into the path.
+ */
+int
+nfs4_mount_update_path_with_symlink(struct nfsmount *nmp, struct nfs_fs_path *nfsp, uint32_t curcomp, fhandle_t *dirfhp, int *depthp, fhandle_t *fhp, vfs_context_t ctx)
+{
+ int error = 0, status, numops;
+ uint32_t len = 0, comp, newcomp, linkcompcount;
+ u_int64_t xid;
+ struct nfsm_chain nmreq, nmrep;
+ struct nfsreq rq, *req = &rq;
+ struct nfsreq_secinfo_args si;
+ char *link = NULL, *p, *q, ch;
+ struct nfs_fs_path nfsp2;
+
+ bzero(&nfsp2, sizeof(nfsp2));
+ if (dirfhp->fh_len)
+ NFSREQ_SECINFO_SET(&si, NULL, dirfhp->fh_data, dirfhp->fh_len, nfsp->np_components[curcomp], 0);
+ else
+ NFSREQ_SECINFO_SET(&si, NULL, NULL, 0, nfsp->np_components[curcomp], 0);
+ nfsm_chain_null(&nmreq);
+ nfsm_chain_null(&nmrep);
+
+ MALLOC_ZONE(link, char *, MAXPATHLEN, M_NAMEI, M_WAITOK);
+ if (!link)
+ error = ENOMEM;
+
+ // PUTFH, READLINK
+ numops = 2;
+ nfsm_chain_build_alloc_init(error, &nmreq, 12 * NFSX_UNSIGNED);
+ nfsm_chain_add_compound_header(error, &nmreq, "readlink", numops);
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+ nfsm_chain_add_fh(error, &nmreq, NFS_VER4, fhp->fh_data, fhp->fh_len);
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_READLINK);
+ nfsm_chain_build_done(error, &nmreq);
+ nfsm_assert(error, (numops == 0), EPROTO);
+ nfsmout_if(error);
+
+ error = nfs_request_async(NULL, nmp->nm_mountp, &nmreq, NFSPROC4_COMPOUND,
+ vfs_context_thread(ctx), vfs_context_ucred(ctx), &si, 0, NULL, &req);
+ if (!error)
+ error = nfs_request_async_finish(req, &nmrep, &xid, &status);
+
+ nfsm_chain_skip_tag(error, &nmrep);
+ nfsm_chain_get_32(error, &nmrep, numops);
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_PUTFH);
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_READLINK);
+ nfsm_chain_get_32(error, &nmrep, len);
+ nfsmout_if(error);
+ if (len == 0)
+ error = ENOENT;
+ else if (len >= MAXPATHLEN)
+ len = MAXPATHLEN - 1;
+ nfsm_chain_get_opaque(error, &nmrep, len, link);
+ nfsmout_if(error);
+ /* make sure link string is terminated properly */
+ link[len] = '\0';
+
+ /* count the number of components in link */
+ p = link;
+ while (*p && (*p == '/'))
+ p++;
+ linkcompcount = 0;
+ while (*p) {
+ linkcompcount++;
+ while (*p && (*p != '/'))
+ p++;
+ while (*p && (*p == '/'))
+ p++;
+ }
+
+ /* free up used components */
+ for (comp=0; comp <= curcomp; comp++) {
+ if (nfsp->np_components[comp]) {
+ FREE(nfsp->np_components[comp], M_TEMP);
+ nfsp->np_components[comp] = NULL;
+ }
+ }
+
+ /* set up new path */
+ nfsp2.np_compcount = nfsp->np_compcount - curcomp - 1 + linkcompcount;
+ MALLOC(nfsp2.np_components, char **, nfsp2.np_compcount*sizeof(char*), M_TEMP, M_WAITOK|M_ZERO);
+ if (!nfsp2.np_components) {
+ error = ENOMEM;
+ goto nfsmout;
+ }
+
+ /* add link components */
+ p = link;
+ while (*p && (*p == '/'))
+ p++;
+ for (newcomp=0; newcomp < linkcompcount; newcomp++) {
+ /* find end of component */
+ q = p;
+ while (*q && (*q != '/'))
+ q++;
+ MALLOC(nfsp2.np_components[newcomp], char *, q-p+1, M_TEMP, M_WAITOK|M_ZERO);
+ if (!nfsp2.np_components[newcomp]) {
+ error = ENOMEM;
+ break;
+ }
+ ch = *q;
+ *q = '\0';
+ strlcpy(nfsp2.np_components[newcomp], p, q-p+1);
+ *q = ch;
+ p = q;
+ while (*p && (*p == '/'))
+ p++;
+ }
+ nfsmout_if(error);
+
+ /* add remaining components */
+ for(comp = curcomp + 1; comp < nfsp->np_compcount; comp++,newcomp++) {
+ nfsp2.np_components[newcomp] = nfsp->np_components[comp];
+ nfsp->np_components[comp] = NULL;
+ }
+
+ /* move new path into place */
+ FREE(nfsp->np_components, M_TEMP);
+ nfsp->np_components = nfsp2.np_components;
+ nfsp->np_compcount = nfsp2.np_compcount;
+ nfsp2.np_components = NULL;
+
+ /* for absolute link, let the caller now that the next dirfh is root */
+ if (link[0] == '/') {
+ dirfhp->fh_len = 0;
+ *depthp = 0;
+ }
+nfsmout:
+ if (link)
+ FREE_ZONE(link, MAXPATHLEN, M_NAMEI);
+ if (nfsp2.np_components) {
+ for (comp=0; comp < nfsp2.np_compcount; comp++)
+ if (nfsp2.np_components[comp])
+ FREE(nfsp2.np_components[comp], M_TEMP);
+ FREE(nfsp2.np_components, M_TEMP);
+ }
+ nfsm_chain_cleanup(&nmreq);
+ nfsm_chain_cleanup(&nmrep);
+ return (error);
+}
+
+/* Set up an NFSv4 mount */
+int
+nfs4_mount(
+ struct nfsmount *nmp,
+ vfs_context_t ctx,
+ nfsnode_t *npp)
+{
+ struct nfsm_chain nmreq, nmrep;
+ int error = 0, numops, status, interval, isdotdot, loopcnt = 0, depth = 0;
+ struct nfs_fs_path fspath, *nfsp, fspath2;
+ uint32_t bitmap[NFS_ATTR_BITMAP_LEN], comp, comp2;
+ fhandle_t fh, dirfh;
+ struct nfs_vattr nvattr;
+ u_int64_t xid;
+ struct nfsreq rq, *req = &rq;
+ struct nfsreq_secinfo_args si;
+ struct nfs_sec sec;
+ struct nfs_fs_locations nfsls;
+
+ *npp = NULL;
+ fh.fh_len = dirfh.fh_len = 0;
+ TAILQ_INIT(&nmp->nm_open_owners);
+ TAILQ_INIT(&nmp->nm_delegations);
+ TAILQ_INIT(&nmp->nm_dreturnq);
+ nmp->nm_stategenid = 1;
+ NVATTR_INIT(&nvattr);
+ bzero(&nfsls, sizeof(nfsls));
+ nfsm_chain_null(&nmreq);
+ nfsm_chain_null(&nmrep);
+
+ /*
+ * If no security flavors were specified we'll want to default to the server's
+ * preferred flavor. For NFSv4.0 we need a file handle and name to get that via
+ * SECINFO, so we'll do that on the last component of the server path we are
+ * mounting. If we are mounting the server's root, we'll need to defer the
+ * SECINFO call to the first successful LOOKUP request.
+ */
+ if (!nmp->nm_sec.count)
+ nmp->nm_state |= NFSSTA_NEEDSECINFO;
+
+ /* make a copy of the current location's path */
+ nfsp = &nmp->nm_locations.nl_locations[nmp->nm_locations.nl_current.nli_loc]->nl_path;
+ bzero(&fspath, sizeof(fspath));
+ fspath.np_compcount = nfsp->np_compcount;
+ if (fspath.np_compcount > 0) {
+ MALLOC(fspath.np_components, char **, fspath.np_compcount*sizeof(char*), M_TEMP, M_WAITOK|M_ZERO);
+ if (!fspath.np_components) {
+ error = ENOMEM;
+ goto nfsmout;
+ }
+ for (comp=0; comp < nfsp->np_compcount; comp++) {
+ int slen = strlen(nfsp->np_components[comp]);
+ MALLOC(fspath.np_components[comp], char *, slen+1, M_TEMP, M_WAITOK|M_ZERO);
+ if (!fspath.np_components[comp]) {
+ error = ENOMEM;
+ break;
+ }
+ strlcpy(fspath.np_components[comp], nfsp->np_components[comp], slen+1);
+ }
+ if (error)
+ goto nfsmout;
+ }
+
+ /* for mirror mounts, we can just use the file handle passed in */
+ if (nmp->nm_fh) {
+ dirfh.fh_len = nmp->nm_fh->fh_len;
+ bcopy(nmp->nm_fh->fh_data, dirfh.fh_data, dirfh.fh_len);
+ NFSREQ_SECINFO_SET(&si, NULL, dirfh.fh_data, dirfh.fh_len, NULL, 0);
+ goto gotfh;
+ }
+
+ /* otherwise, we need to get the fh for the directory we are mounting */
+
+ /* if no components, just get root */
+ if (fspath.np_compcount == 0) {
+nocomponents:
+ // PUTROOTFH + GETATTR(FH)
+ NFSREQ_SECINFO_SET(&si, NULL, NULL, 0, NULL, 0);
+ numops = 2;
+ nfsm_chain_build_alloc_init(error, &nmreq, 9 * NFSX_UNSIGNED);
+ nfsm_chain_add_compound_header(error, &nmreq, "mount", numops);
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTROOTFH);
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+ NFS_CLEAR_ATTRIBUTES(bitmap);
+ NFS4_DEFAULT_ATTRIBUTES(bitmap);
+ NFS_BITMAP_SET(bitmap, NFS_FATTR_FILEHANDLE);
+ nfsm_chain_add_bitmap(error, &nmreq, bitmap, NFS_ATTR_BITMAP_LEN);
+ nfsm_chain_build_done(error, &nmreq);
+ nfsm_assert(error, (numops == 0), EPROTO);
+ nfsmout_if(error);
+ error = nfs_request_async(NULL, nmp->nm_mountp, &nmreq, NFSPROC4_COMPOUND,
+ vfs_context_thread(ctx), vfs_context_ucred(ctx), &si, 0, NULL, &req);
+ if (!error)
+ error = nfs_request_async_finish(req, &nmrep, &xid, &status);
+ nfsm_chain_skip_tag(error, &nmrep);
+ nfsm_chain_get_32(error, &nmrep, numops);
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_PUTROOTFH);
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+ nfsmout_if(error);
+ NFS_CLEAR_ATTRIBUTES(nmp->nm_fsattr.nfsa_bitmap);
+ error = nfs4_parsefattr(&nmrep, &nmp->nm_fsattr, &nvattr, &dirfh, NULL, NULL);
+ if (!error && !NFS_BITMAP_ISSET(&nvattr.nva_bitmap, NFS_FATTR_FILEHANDLE)) {
+ printf("nfs: mount didn't return filehandle?\n");
+ error = EBADRPC;
+ }
+ nfsmout_if(error);
+ nfsm_chain_cleanup(&nmrep);
+ nfsm_chain_null(&nmreq);
+ NVATTR_CLEANUP(&nvattr);
+ goto gotfh;
+ }
+
+ /* look up each path component */
+ for (comp=0; comp < fspath.np_compcount; ) {
+ isdotdot = 0;
+ if (fspath.np_components[comp][0] == '.') {
+ if (fspath.np_components[comp][1] == '\0') {
+ /* skip "." */
+ comp++;
+ continue;
+ }
+ /* treat ".." specially */
+ if ((fspath.np_components[comp][1] == '.') &&
+ (fspath.np_components[comp][2] == '\0'))
+ isdotdot = 1;
+ if (isdotdot && (dirfh.fh_len == 0)) {
+ /* ".." in root directory is same as "." */
+ comp++;
+ continue;
+ }
+ }
+ // PUT(ROOT)FH + LOOKUP(P) + GETFH + GETATTR
+ if (dirfh.fh_len == 0)
+ NFSREQ_SECINFO_SET(&si, NULL, NULL, 0, isdotdot ? NULL : fspath.np_components[comp], 0);
+ else
+ NFSREQ_SECINFO_SET(&si, NULL, dirfh.fh_data, dirfh.fh_len, isdotdot ? NULL : fspath.np_components[comp], 0);
+ numops = 4;
+ nfsm_chain_build_alloc_init(error, &nmreq, 18 * NFSX_UNSIGNED);
+ nfsm_chain_add_compound_header(error, &nmreq, "mount", numops);
+ numops--;
+ if (dirfh.fh_len) {
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+ nfsm_chain_add_fh(error, &nmreq, NFS_VER4, dirfh.fh_data, dirfh.fh_len);
+ } else {
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTROOTFH);
+ }
+ numops--;
+ if (isdotdot) {
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_LOOKUPP);
+ } else {
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_LOOKUP);
+ nfsm_chain_add_name(error, &nmreq,
+ fspath.np_components[comp], strlen(fspath.np_components[comp]), nmp);
+ }
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_GETFH);
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+ NFS_CLEAR_ATTRIBUTES(bitmap);
+ NFS4_DEFAULT_ATTRIBUTES(bitmap);
+ /* if no namedattr support or component is ".zfs", clear NFS_FATTR_NAMED_ATTR */
+ if (NMFLAG(nmp, NONAMEDATTR) || !strcmp(fspath.np_components[comp], ".zfs"))
+ NFS_BITMAP_CLR(bitmap, NFS_FATTR_NAMED_ATTR);
+ nfsm_chain_add_bitmap(error, &nmreq, bitmap, NFS_ATTR_BITMAP_LEN);
+ nfsm_chain_build_done(error, &nmreq);
+ nfsm_assert(error, (numops == 0), EPROTO);
+ nfsmout_if(error);
+ error = nfs_request_async(NULL, nmp->nm_mountp, &nmreq, NFSPROC4_COMPOUND,
+ vfs_context_thread(ctx), vfs_context_ucred(ctx), &si, 0, NULL, &req);
+ if (!error)
+ error = nfs_request_async_finish(req, &nmrep, &xid, &status);
+ nfsm_chain_skip_tag(error, &nmrep);
+ nfsm_chain_get_32(error, &nmrep, numops);
+ nfsm_chain_op_check(error, &nmrep, dirfh.fh_len ? NFS_OP_PUTFH : NFS_OP_PUTROOTFH);
+ nfsm_chain_op_check(error, &nmrep, isdotdot ? NFS_OP_LOOKUPP : NFS_OP_LOOKUP);
+ nfsmout_if(error);
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_GETFH);
+ nfsm_chain_get_32(error, &nmrep, fh.fh_len);
+ nfsm_chain_get_opaque(error, &nmrep, fh.fh_len, fh.fh_data);
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+ if (!error) {
+ NFS_CLEAR_ATTRIBUTES(nmp->nm_fsattr.nfsa_bitmap);
+ error = nfs4_parsefattr(&nmrep, &nmp->nm_fsattr, &nvattr, NULL, NULL, &nfsls);
+ }
+ nfsm_chain_cleanup(&nmrep);
+ nfsm_chain_null(&nmreq);
+ if (error) {
+ /* LOOKUP succeeded but GETATTR failed? This could be a referral. */
+ /* Try the lookup again with a getattr for fs_locations. */
+ nfs_fs_locations_cleanup(&nfsls);
+ error = nfs4_get_fs_locations(nmp, NULL, dirfh.fh_data, dirfh.fh_len, fspath.np_components[comp], ctx, &nfsls);
+ if (!error && (nfsls.nl_numlocs < 1))
+ error = ENOENT;
+ nfsmout_if(error);
+ if (++loopcnt > MAXSYMLINKS) {
+ /* too many symlink/referral redirections */
+ error = ELOOP;
+ goto nfsmout;
+ }
+ /* tear down the current connection */
+ nfs_disconnect(nmp);
+ /* replace fs locations */
+ nfs_fs_locations_cleanup(&nmp->nm_locations);
+ nmp->nm_locations = nfsls;
+ bzero(&nfsls, sizeof(nfsls));
+ /* initiate a connection using the new fs locations */
+ error = nfs_mount_connect(nmp);
+ if (!error && !(nmp->nm_locations.nl_current.nli_flags & NLI_VALID))
+ error = EIO;
+ nfsmout_if(error);
+ /* add new server's remote path to beginning of our path and continue */
+ nfsp = &nmp->nm_locations.nl_locations[nmp->nm_locations.nl_current.nli_loc]->nl_path;
+ bzero(&fspath2, sizeof(fspath2));
+ fspath2.np_compcount = (fspath.np_compcount - comp - 1) + nfsp->np_compcount;
+ if (fspath2.np_compcount > 0) {
+ MALLOC(fspath2.np_components, char **, fspath2.np_compcount*sizeof(char*), M_TEMP, M_WAITOK|M_ZERO);
+ if (!fspath2.np_components) {
+ error = ENOMEM;
+ goto nfsmout;
+ }
+ for (comp2=0; comp2 < nfsp->np_compcount; comp2++) {
+ int slen = strlen(nfsp->np_components[comp2]);
+ MALLOC(fspath2.np_components[comp2], char *, slen+1, M_TEMP, M_WAITOK|M_ZERO);
+ if (!fspath2.np_components[comp2]) {
+ /* clean up fspath2, then error out */
+ while (comp2 > 0) {
+ comp2--;
+ FREE(fspath2.np_components[comp2], M_TEMP);
+ }
+ FREE(fspath2.np_components, M_TEMP);
+ error = ENOMEM;
+ goto nfsmout;
+ }
+ strlcpy(fspath2.np_components[comp2], nfsp->np_components[comp2], slen+1);
+ }
+ if ((fspath.np_compcount - comp - 1) > 0)
+ bcopy(&fspath.np_components[comp+1], &fspath2.np_components[nfsp->np_compcount], (fspath.np_compcount - comp - 1)*sizeof(char*));
+ /* free up unused parts of old path (prior components and component array) */
+ do {
+ FREE(fspath.np_components[comp], M_TEMP);
+ } while (comp-- > 0);
+ FREE(fspath.np_components, M_TEMP);
+ /* put new path in place */
+ fspath = fspath2;
+ }
+ /* reset dirfh and component index */
+ dirfh.fh_len = 0;
+ comp = 0;
+ NVATTR_CLEANUP(&nvattr);
+ if (fspath.np_compcount == 0)
+ goto nocomponents;
+ continue;
+ }
+ nfsmout_if(error);
+ /* if file handle is for a symlink, then update the path with the symlink contents */
+ if (NFS_BITMAP_ISSET(&nvattr.nva_bitmap, NFS_FATTR_TYPE) && (nvattr.nva_type == VLNK)) {
+ if (++loopcnt > MAXSYMLINKS)
+ error = ELOOP;
+ else
+ error = nfs4_mount_update_path_with_symlink(nmp, &fspath, comp, &dirfh, &depth, &fh, ctx);
+ nfsmout_if(error);
+ /* directory file handle is either left the same or reset to root (if link was absolute) */
+ /* path traversal starts at beginning of the path again */
+ comp = 0;
+ NVATTR_CLEANUP(&nvattr);
+ nfs_fs_locations_cleanup(&nfsls);
+ continue;
+ }
+ NVATTR_CLEANUP(&nvattr);
+ nfs_fs_locations_cleanup(&nfsls);
+ /* not a symlink... */
+ if ((nmp->nm_state & NFSSTA_NEEDSECINFO) && (comp == (fspath.np_compcount-1)) && !isdotdot) {
+ /* need to get SECINFO for the directory being mounted */
+ if (dirfh.fh_len == 0)
+ NFSREQ_SECINFO_SET(&si, NULL, NULL, 0, isdotdot ? NULL : fspath.np_components[comp], 0);
+ else
+ NFSREQ_SECINFO_SET(&si, NULL, dirfh.fh_data, dirfh.fh_len, isdotdot ? NULL : fspath.np_components[comp], 0);
+ sec.count = NX_MAX_SEC_FLAVORS;
+ error = nfs4_secinfo_rpc(nmp, &si, vfs_context_ucred(ctx), sec.flavors, &sec.count);
+ /* [sigh] some implementations return "illegal" error for unsupported ops */
+ if (error == NFSERR_OP_ILLEGAL)
+ error = 0;
+ nfsmout_if(error);
+ /* set our default security flavor to the first in the list */
+ if (sec.count)
+ nmp->nm_auth = sec.flavors[0];
+ nmp->nm_state &= ~NFSSTA_NEEDSECINFO;
+ }
+ /* advance directory file handle, component index, & update depth */
+ dirfh = fh;
+ comp++;
+ if (!isdotdot) /* going down the hierarchy */
+ depth++;
+ else if (--depth <= 0) /* going up the hierarchy */
+ dirfh.fh_len = 0; /* clear dirfh when we hit root */
+ }
+
+gotfh:
+ /* get attrs for mount point root */
+ numops = NMFLAG(nmp, NONAMEDATTR) ? 2 : 3; // PUTFH + GETATTR + OPENATTR
+ nfsm_chain_build_alloc_init(error, &nmreq, 25 * NFSX_UNSIGNED);
+ nfsm_chain_add_compound_header(error, &nmreq, "mount", numops);
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+ nfsm_chain_add_fh(error, &nmreq, NFS_VER4, dirfh.fh_data, dirfh.fh_len);
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+ NFS_CLEAR_ATTRIBUTES(bitmap);
+ NFS4_DEFAULT_ATTRIBUTES(bitmap);
+ /* if no namedattr support or last component is ".zfs", clear NFS_FATTR_NAMED_ATTR */
+ if (NMFLAG(nmp, NONAMEDATTR) || ((fspath.np_compcount > 0) && !strcmp(fspath.np_components[fspath.np_compcount-1], ".zfs")))
+ NFS_BITMAP_CLR(bitmap, NFS_FATTR_NAMED_ATTR);
+ nfsm_chain_add_bitmap(error, &nmreq, bitmap, NFS_ATTR_BITMAP_LEN);
+ if (!NMFLAG(nmp, NONAMEDATTR)) {
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_OPENATTR);
+ nfsm_chain_add_32(error, &nmreq, 0);
+ }
+ nfsm_chain_build_done(error, &nmreq);
+ nfsm_assert(error, (numops == 0), EPROTO);
+ nfsmout_if(error);
+ error = nfs_request_async(NULL, nmp->nm_mountp, &nmreq, NFSPROC4_COMPOUND,
+ vfs_context_thread(ctx), vfs_context_ucred(ctx), &si, 0, NULL, &req);
+ if (!error)
+ error = nfs_request_async_finish(req, &nmrep, &xid, &status);
+ nfsm_chain_skip_tag(error, &nmrep);
+ nfsm_chain_get_32(error, &nmrep, numops);
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_PUTFH);
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+ nfsmout_if(error);
+ NFS_CLEAR_ATTRIBUTES(nmp->nm_fsattr.nfsa_bitmap);
+ error = nfs4_parsefattr(&nmrep, &nmp->nm_fsattr, &nvattr, NULL, NULL, NULL);
+ nfsmout_if(error);
+ if (!NMFLAG(nmp, NONAMEDATTR)) {
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_OPENATTR);
+ if (error == ENOENT)
+ error = 0;
+ /* [sigh] some implementations return "illegal" error for unsupported ops */
+ if (error || !NFS_BITMAP_ISSET(nmp->nm_fsattr.nfsa_supp_attr, NFS_FATTR_NAMED_ATTR)) {
+ nmp->nm_fsattr.nfsa_flags &= ~NFS_FSFLAG_NAMED_ATTR;
+ } else {
+ nmp->nm_fsattr.nfsa_flags |= NFS_FSFLAG_NAMED_ATTR;
+ }
+ } else {
+ nmp->nm_fsattr.nfsa_flags &= ~NFS_FSFLAG_NAMED_ATTR;
+ }
+ if (NMFLAG(nmp, NOACL)) /* make sure ACL support is turned off */
+ nmp->nm_fsattr.nfsa_flags &= ~NFS_FSFLAG_ACL;
+ if (NMFLAG(nmp, ACLONLY) && !(nmp->nm_fsattr.nfsa_flags & NFS_FSFLAG_ACL))
+ NFS_BITMAP_CLR(nmp->nm_flags, NFS_MFLAG_ACLONLY);
+ if (NFS_BITMAP_ISSET(nmp->nm_fsattr.nfsa_supp_attr, NFS_FATTR_FH_EXPIRE_TYPE)) {
+ uint32_t fhtype = ((nmp->nm_fsattr.nfsa_flags & NFS_FSFLAG_FHTYPE_MASK) >> NFS_FSFLAG_FHTYPE_SHIFT);
+ if (fhtype != NFS_FH_PERSISTENT)
+ printf("nfs: warning: non-persistent file handles! for %s\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname);
+ }
+
+ /* make sure it's a directory */
+ if (!NFS_BITMAP_ISSET(&nvattr.nva_bitmap, NFS_FATTR_TYPE) || (nvattr.nva_type != VDIR)) {
+ error = ENOTDIR;
+ goto nfsmout;
+ }
+
+ /* save the NFS fsid */
+ nmp->nm_fsid = nvattr.nva_fsid;
+
+ /* create the root node */
+ error = nfs_nget(nmp->nm_mountp, NULL, NULL, dirfh.fh_data, dirfh.fh_len, &nvattr, &xid, rq.r_auth, NG_MARKROOT, npp);
+ nfsmout_if(error);
+
+ if (nmp->nm_fsattr.nfsa_flags & NFS_FSFLAG_ACL)
+ vfs_setextendedsecurity(nmp->nm_mountp);
+
+ /* adjust I/O sizes to server limits */
+ if (NFS_BITMAP_ISSET(nmp->nm_fsattr.nfsa_bitmap, NFS_FATTR_MAXREAD) && (nmp->nm_fsattr.nfsa_maxread > 0)) {
+ if (nmp->nm_fsattr.nfsa_maxread < (uint64_t)nmp->nm_rsize) {
+ nmp->nm_rsize = nmp->nm_fsattr.nfsa_maxread & ~(NFS_FABLKSIZE - 1);
+ if (nmp->nm_rsize == 0)
+ nmp->nm_rsize = nmp->nm_fsattr.nfsa_maxread;
+ }
+ }
+ if (NFS_BITMAP_ISSET(nmp->nm_fsattr.nfsa_bitmap, NFS_FATTR_MAXWRITE) && (nmp->nm_fsattr.nfsa_maxwrite > 0)) {
+ if (nmp->nm_fsattr.nfsa_maxwrite < (uint64_t)nmp->nm_wsize) {
+ nmp->nm_wsize = nmp->nm_fsattr.nfsa_maxwrite & ~(NFS_FABLKSIZE - 1);
+ if (nmp->nm_wsize == 0)
+ nmp->nm_wsize = nmp->nm_fsattr.nfsa_maxwrite;
+ }
+ }
+
+ /* set up lease renew timer */
+ nmp->nm_renew_timer = thread_call_allocate(nfs4_renew_timer, nmp);
+ interval = nmp->nm_fsattr.nfsa_lease / 2;
+ if (interval < 1)
+ interval = 1;
+ nfs_interval_timer_start(nmp->nm_renew_timer, interval * 1000);
+
+nfsmout:
+ if (fspath.np_components) {
+ for (comp=0; comp < fspath.np_compcount; comp++)
+ if (fspath.np_components[comp])
+ FREE(fspath.np_components[comp], M_TEMP);
+ FREE(fspath.np_components, M_TEMP);
+ }
+ NVATTR_CLEANUP(&nvattr);
+ nfs_fs_locations_cleanup(&nfsls);
+ if (*npp)
+ nfs_node_unlock(*npp);
+ nfsm_chain_cleanup(&nmreq);
+ nfsm_chain_cleanup(&nmrep);
+ return (error);
+}
+
+/*
+ * Thread to handle initial NFS mount connection.
+ */
+void
+nfs_mount_connect_thread(void *arg, __unused wait_result_t wr)
+{
+ struct nfsmount *nmp = arg;
+ int error = 0, savederror = 0, slpflag = (NMFLAG(nmp, INTR) ? PCATCH : 0);
+ int done = 0, timeo, tries, maxtries;
+
+ if (NM_OMFLAG(nmp, MNTQUICK)) {
+ timeo = 8;
+ maxtries = 1;
+ } else {
+ timeo = 30;
+ maxtries = 2;
+ }
+
+ for (tries = 0; tries < maxtries; tries++) {
+ error = nfs_connect(nmp, 1, timeo);
+ switch (error) {
+ case ETIMEDOUT:
+ case EAGAIN:
+ case EPIPE:
+ case EADDRNOTAVAIL:
+ case ENETDOWN:
+ case ENETUNREACH:
+ case ENETRESET:
+ case ECONNABORTED:
+ case ECONNRESET:
+ case EISCONN:
+ case ENOTCONN:
+ case ESHUTDOWN:
+ case ECONNREFUSED:
+ case EHOSTDOWN:
+ case EHOSTUNREACH:
+ /* just keep retrying on any of these errors */
+ break;
+ case 0:
+ default:
+ /* looks like we got an answer... */
+ done = 1;
+ break;
+ }
+
+ /* save the best error */
+ if (nfs_connect_error_class(error) >= nfs_connect_error_class(savederror))
+ savederror = error;
+ if (done) {
+ error = savederror;
+ break;
+ }
+
+ /* pause before next attempt */
+ if ((error = nfs_sigintr(nmp, NULL, current_thread(), 0)))
+ break;
+ error = tsleep(nmp, PSOCK|slpflag, "nfs_mount_connect_retry", 2*hz);
+ if (error && (error != EWOULDBLOCK))
+ break;
+ error = savederror;
+ }
+
+ /* update status of mount connect */
+ lck_mtx_lock(&nmp->nm_lock);
+ if (!nmp->nm_mounterror)
+ nmp->nm_mounterror = error;
+ nmp->nm_state &= ~NFSSTA_MOUNT_THREAD;
+ lck_mtx_unlock(&nmp->nm_lock);
+ wakeup(&nmp->nm_nss);
+}
+
+int
+nfs_mount_connect(struct nfsmount *nmp)
+{
+ int error = 0, slpflag;
+ thread_t thd;
+ struct timespec ts = { 2, 0 };
+
+ /*
+ * Set up the socket. Perform initial search for a location/server/address to
+ * connect to and negotiate any unspecified mount parameters. This work is
+ * done on a kernel thread to satisfy reserved port usage needs.
+ */
+ slpflag = NMFLAG(nmp, INTR) ? PCATCH : 0;
+ lck_mtx_lock(&nmp->nm_lock);
+ /* set flag that the thread is running */
+ nmp->nm_state |= NFSSTA_MOUNT_THREAD;
+ if (kernel_thread_start(nfs_mount_connect_thread, nmp, &thd) != KERN_SUCCESS) {
+ nmp->nm_state &= ~NFSSTA_MOUNT_THREAD;
+ nmp->nm_mounterror = EIO;
+ printf("nfs mount %s start socket connect thread failed\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname);
+ } else {
+ thread_deallocate(thd);
+ }
+
+ /* wait until mount connect thread is finished/gone */
+ while (nmp->nm_state & NFSSTA_MOUNT_THREAD) {
+ error = msleep(&nmp->nm_nss, &nmp->nm_lock, slpflag|PSOCK, "nfsconnectthread", &ts);
+ if ((error && (error != EWOULDBLOCK)) || ((error = nfs_sigintr(nmp, NULL, current_thread(), 1)))) {
+ /* record error */
+ if (!nmp->nm_mounterror)
+ nmp->nm_mounterror = error;
+ /* signal the thread that we are aborting */
+ nmp->nm_sockflags |= NMSOCK_UNMOUNT;
+ if (nmp->nm_nss)
+ wakeup(nmp->nm_nss);
+ /* and continue waiting on it to finish */
+ slpflag = 0;
+ }
+ }
+ lck_mtx_unlock(&nmp->nm_lock);
+
+ /* grab mount connect status */
+ error = nmp->nm_mounterror;
+
+ return (error);
+}
+
+/*
+ * Common code to mount an NFS file system.
+ */
+int
+mountnfs(
+ char *xdrbuf,
+ mount_t mp,
+ vfs_context_t ctx,
+ vnode_t *vpp)
+{
+ struct nfsmount *nmp;
+ nfsnode_t np;
+ int error = 0;
+ struct vfsstatfs *sbp;
+ struct xdrbuf xb;
+ uint32_t i, val, vers = 0, minorvers, maxio, iosize, len;
+ uint32_t *mattrs;
+ uint32_t *mflags_mask;
+ uint32_t *mflags;
+ uint32_t argslength, attrslength;
+ struct nfs_location_index firstloc = { NLI_VALID, 0, 0, 0 };