+ if (args.fhsize > 0) {
+ if (inkernel)
+ bcopy(CAST_DOWN(void *, args.fh), (caddr_t)nfh, args.fhsize);
+ else
+ error = copyin(args.fh, (caddr_t)nfh, args.fhsize);
+ nfsmout_if(error);
+ }
+
+ if (inkernel)
+ error = copystr(CAST_DOWN(void *, args.hostname), mntfrom, MAXPATHLEN-1, &len);
+ else
+ error = copyinstr(args.hostname, mntfrom, MAXPATHLEN-1, &len);
+ nfsmout_if(error);
+ bzero(&mntfrom[len], MAXPATHLEN - len);
+
+ /* find the server-side path being mounted */
+ frompath = mntfrom;
+ if (*frompath == '[') { /* skip IPv6 literal address */
+ while (*frompath && (*frompath != ']'))
+ frompath++;
+ if (*frompath == ']')
+ frompath++;
+ }
+ while (*frompath && (*frompath != ':'))
+ frompath++;
+ endserverp = frompath;
+ while (*frompath && (*frompath == ':'))
+ frompath++;
+ /* count fs location path components */
+ p = frompath;
+ while (*p && (*p == '/'))
+ p++;
+ numcomps = 0;
+ while (*p) {
+ numcomps++;
+ while (*p && (*p != '/'))
+ p++;
+ while (*p && (*p == '/'))
+ p++;
+ }
+
+ /* copy socket address */
+ if (inkernel)
+ bcopy(CAST_DOWN(void *, args.addr), &ss, args.addrlen);
+ else {
+ if ((size_t)args.addrlen > sizeof (struct sockaddr_storage))
+ error = EINVAL;
+ else
+ error = copyin(args.addr, &ss, args.addrlen);
+ }
+ nfsmout_if(error);
+ ss.ss_len = args.addrlen;
+
+ /* convert address to universal address string */
+ if (ss.ss_family == AF_INET)
+ sinaddr = &((struct sockaddr_in*)&ss)->sin_addr;
+ else if (ss.ss_family == AF_INET6)
+ sinaddr = &((struct sockaddr_in6*)&ss)->sin6_addr;
+ else
+ sinaddr = NULL;
+ if (!sinaddr || (inet_ntop(ss.ss_family, sinaddr, uaddr, sizeof(uaddr)) != uaddr)) {
+ error = EINVAL;
+ goto nfsmout;
+ }
+
+ /* prepare mount flags */
+ NFS_BITMAP_ZERO(mflags_mask, NFS_MFLAG_BITMAP_LEN);
+ NFS_BITMAP_ZERO(mflags, NFS_MFLAG_BITMAP_LEN);
+ NFS_BITMAP_SET(mflags_mask, NFS_MFLAG_SOFT);
+ NFS_BITMAP_SET(mflags_mask, NFS_MFLAG_INTR);
+ NFS_BITMAP_SET(mflags_mask, NFS_MFLAG_RESVPORT);
+ NFS_BITMAP_SET(mflags_mask, NFS_MFLAG_NOCONNECT);
+ NFS_BITMAP_SET(mflags_mask, NFS_MFLAG_DUMBTIMER);
+ NFS_BITMAP_SET(mflags_mask, NFS_MFLAG_CALLUMNT);
+ NFS_BITMAP_SET(mflags_mask, NFS_MFLAG_RDIRPLUS);
+ NFS_BITMAP_SET(mflags_mask, NFS_MFLAG_NONEGNAMECACHE);
+ NFS_BITMAP_SET(mflags_mask, NFS_MFLAG_MUTEJUKEBOX);
+ NFS_BITMAP_SET(mflags_mask, NFS_MFLAG_NOQUOTA);
+ if (args.flags & NFSMNT_SOFT)
+ NFS_BITMAP_SET(mflags, NFS_MFLAG_SOFT);
+ if (args.flags & NFSMNT_INT)
+ NFS_BITMAP_SET(mflags, NFS_MFLAG_INTR);
+ if (args.flags & NFSMNT_RESVPORT)
+ NFS_BITMAP_SET(mflags, NFS_MFLAG_RESVPORT);
+ if (args.flags & NFSMNT_NOCONN)
+ NFS_BITMAP_SET(mflags, NFS_MFLAG_NOCONNECT);
+ if (args.flags & NFSMNT_DUMBTIMR)
+ NFS_BITMAP_SET(mflags, NFS_MFLAG_DUMBTIMER);
+ if (args.flags & NFSMNT_CALLUMNT)
+ NFS_BITMAP_SET(mflags, NFS_MFLAG_CALLUMNT);
+ if (args.flags & NFSMNT_RDIRPLUS)
+ NFS_BITMAP_SET(mflags, NFS_MFLAG_RDIRPLUS);
+ if (args.flags & NFSMNT_NONEGNAMECACHE)
+ NFS_BITMAP_SET(mflags, NFS_MFLAG_NONEGNAMECACHE);
+ if (args.flags & NFSMNT_MUTEJUKEBOX)
+ NFS_BITMAP_SET(mflags, NFS_MFLAG_MUTEJUKEBOX);
+ if (args.flags & NFSMNT_NOQUOTA)
+ NFS_BITMAP_SET(mflags, NFS_MFLAG_NOQUOTA);
+
+ /* prepare mount attributes */
+ NFS_BITMAP_ZERO(mattrs, NFS_MATTR_BITMAP_LEN);
+ NFS_BITMAP_SET(mattrs, NFS_MATTR_FLAGS);
+ NFS_BITMAP_SET(mattrs, NFS_MATTR_NFS_VERSION);
+ NFS_BITMAP_SET(mattrs, NFS_MATTR_SOCKET_TYPE);
+ NFS_BITMAP_SET(mattrs, NFS_MATTR_NFS_PORT);
+ NFS_BITMAP_SET(mattrs, NFS_MATTR_FH);
+ NFS_BITMAP_SET(mattrs, NFS_MATTR_FS_LOCATIONS);
+ NFS_BITMAP_SET(mattrs, NFS_MATTR_MNTFLAGS);
+ NFS_BITMAP_SET(mattrs, NFS_MATTR_MNTFROM);
+ if (args.flags & NFSMNT_NFSV4)
+ nfsvers = 4;
+ else if (args.flags & NFSMNT_NFSV3)
+ nfsvers = 3;
+ else
+ nfsvers = 2;
+ if ((args.flags & NFSMNT_RSIZE) && (args.rsize > 0))
+ NFS_BITMAP_SET(mattrs, NFS_MATTR_READ_SIZE);
+ if ((args.flags & NFSMNT_WSIZE) && (args.wsize > 0))
+ NFS_BITMAP_SET(mattrs, NFS_MATTR_WRITE_SIZE);
+ if ((args.flags & NFSMNT_TIMEO) && (args.timeo > 0))
+ NFS_BITMAP_SET(mattrs, NFS_MATTR_REQUEST_TIMEOUT);
+ if ((args.flags & NFSMNT_RETRANS) && (args.retrans > 0))
+ NFS_BITMAP_SET(mattrs, NFS_MATTR_SOFT_RETRY_COUNT);
+ if ((args.flags & NFSMNT_MAXGRPS) && (args.maxgrouplist > 0))
+ NFS_BITMAP_SET(mattrs, NFS_MATTR_MAX_GROUP_LIST);
+ if ((args.flags & NFSMNT_READAHEAD) && (args.readahead > 0))
+ NFS_BITMAP_SET(mattrs, NFS_MATTR_READAHEAD);
+ if ((args.flags & NFSMNT_READDIRSIZE) && (args.readdirsize > 0))
+ NFS_BITMAP_SET(mattrs, NFS_MATTR_READDIR_SIZE);
+ if ((args.flags & NFSMNT_NOLOCKS) ||
+ (args.flags & NFSMNT_LOCALLOCKS)) {
+ NFS_BITMAP_SET(mattrs, NFS_MATTR_LOCK_MODE);
+ if (args.flags & NFSMNT_NOLOCKS)
+ nfslockmode = NFS_LOCK_MODE_DISABLED;
+ else if (args.flags & NFSMNT_LOCALLOCKS)
+ nfslockmode = NFS_LOCK_MODE_LOCAL;
+ else
+ nfslockmode = NFS_LOCK_MODE_ENABLED;
+ }
+ if (args.version >= 4) {
+ if ((args.flags & NFSMNT_ACREGMIN) && (args.acregmin > 0))
+ NFS_BITMAP_SET(mattrs, NFS_MATTR_ATTRCACHE_REG_MIN);
+ if ((args.flags & NFSMNT_ACREGMAX) && (args.acregmax > 0))
+ NFS_BITMAP_SET(mattrs, NFS_MATTR_ATTRCACHE_REG_MAX);
+ if ((args.flags & NFSMNT_ACDIRMIN) && (args.acdirmin > 0))
+ NFS_BITMAP_SET(mattrs, NFS_MATTR_ATTRCACHE_DIR_MIN);
+ if ((args.flags & NFSMNT_ACDIRMAX) && (args.acdirmax > 0))
+ NFS_BITMAP_SET(mattrs, NFS_MATTR_ATTRCACHE_DIR_MAX);
+ }
+ if (args.version >= 5) {
+ if ((args.flags & NFSMNT_SECFLAVOR) || (args.flags & NFSMNT_SECSYSOK))
+ NFS_BITMAP_SET(mattrs, NFS_MATTR_SECURITY);
+ }
+ if (args.version >= 6) {
+ if ((args.flags & NFSMNT_DEADTIMEOUT) && (args.deadtimeout > 0))
+ NFS_BITMAP_SET(mattrs, NFS_MATTR_DEAD_TIMEOUT);
+ }
+
+ /* build xdr buffer */
+ xb_init_buffer(&xb, NULL, 0);
+ xb_add_32(error, &xb, args.version);
+ argslength_offset = xb_offset(&xb);
+ xb_add_32(error, &xb, 0); // args length
+ xb_add_32(error, &xb, NFS_XDRARGS_VERSION_0);
+ xb_add_bitmap(error, &xb, mattrs, NFS_MATTR_BITMAP_LEN);
+ attrslength_offset = xb_offset(&xb);
+ xb_add_32(error, &xb, 0); // attrs length
+ xb_add_bitmap(error, &xb, mflags_mask, NFS_MFLAG_BITMAP_LEN); /* mask */
+ xb_add_bitmap(error, &xb, mflags, NFS_MFLAG_BITMAP_LEN); /* value */
+ xb_add_32(error, &xb, nfsvers);
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_READ_SIZE))
+ xb_add_32(error, &xb, args.rsize);
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_WRITE_SIZE))
+ xb_add_32(error, &xb, args.wsize);
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_READDIR_SIZE))
+ xb_add_32(error, &xb, args.readdirsize);
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_READAHEAD))
+ xb_add_32(error, &xb, args.readahead);
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_ATTRCACHE_REG_MIN)) {
+ xb_add_32(error, &xb, args.acregmin);
+ xb_add_32(error, &xb, 0);
+ }
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_ATTRCACHE_REG_MAX)) {
+ xb_add_32(error, &xb, args.acregmax);
+ xb_add_32(error, &xb, 0);
+ }
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_ATTRCACHE_DIR_MIN)) {
+ xb_add_32(error, &xb, args.acdirmin);
+ xb_add_32(error, &xb, 0);
+ }
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_ATTRCACHE_DIR_MAX)) {
+ xb_add_32(error, &xb, args.acdirmax);
+ xb_add_32(error, &xb, 0);
+ }
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_LOCK_MODE))
+ xb_add_32(error, &xb, nfslockmode);
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_SECURITY)) {
+ uint32_t flavors[2], i=0;
+ if (args.flags & NFSMNT_SECFLAVOR)
+ flavors[i++] = args.auth;
+ if ((args.flags & NFSMNT_SECSYSOK) && ((i == 0) || (flavors[0] != RPCAUTH_SYS)))
+ flavors[i++] = RPCAUTH_SYS;
+ xb_add_word_array(error, &xb, flavors, i);
+ }
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_MAX_GROUP_LIST))
+ xb_add_32(error, &xb, args.maxgrouplist);
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_SOCKET_TYPE))
+ xb_add_string(error, &xb, ((args.sotype == SOCK_DGRAM) ? "udp" : "tcp"), 3);
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_NFS_PORT))
+ xb_add_32(error, &xb, ((ss.ss_family == AF_INET) ?
+ ntohs(((struct sockaddr_in*)&ss)->sin_port) :
+ ntohs(((struct sockaddr_in6*)&ss)->sin6_port)));
+ /* NFS_MATTR_MOUNT_PORT (not available in old args) */
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_REQUEST_TIMEOUT)) {
+ /* convert from .1s increments to time */
+ xb_add_32(error, &xb, args.timeo/10);
+ xb_add_32(error, &xb, (args.timeo%10)*100000000);
+ }
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_SOFT_RETRY_COUNT))
+ xb_add_32(error, &xb, args.retrans);
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_DEAD_TIMEOUT)) {
+ xb_add_32(error, &xb, args.deadtimeout);
+ xb_add_32(error, &xb, 0);
+ }
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_FH))
+ xb_add_fh(error, &xb, &nfh[0], args.fhsize);
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_FS_LOCATIONS)) {
+ xb_add_32(error, &xb, 1); /* fs location count */
+ xb_add_32(error, &xb, 1); /* server count */
+ xb_add_string(error, &xb, mntfrom, (endserverp - mntfrom)); /* server name */
+ xb_add_32(error, &xb, 1); /* address count */
+ xb_add_string(error, &xb, uaddr, strlen(uaddr)); /* address */
+ xb_add_32(error, &xb, 0); /* empty server info */
+ xb_add_32(error, &xb, numcomps); /* pathname component count */
+ nfsmout_if(error);
+ p = frompath;
+ while (*p && (*p == '/'))
+ p++;
+ while (*p) {
+ cp = p;
+ while (*p && (*p != '/'))
+ p++;
+ xb_add_string(error, &xb, cp, (p - cp)); /* component */
+ nfsmout_if(error);
+ while (*p && (*p == '/'))
+ p++;
+ }
+ xb_add_32(error, &xb, 0); /* empty fsl info */
+ }
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_MNTFLAGS))
+ xb_add_32(error, &xb, (vfs_flags(mp) & MNT_VISFLAGMASK)); /* VFS MNT_* flags */
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_MNTFROM))
+ xb_add_string(error, &xb, mntfrom, strlen(mntfrom)); /* fixed f_mntfromname */
+ xb_build_done(error, &xb);
+
+ /* update opaque counts */
+ end_offset = xb_offset(&xb);
+ error = xb_seek(&xb, argslength_offset);
+ xb_add_32(error, &xb, end_offset - argslength_offset + XDRWORD/*version*/);
+ nfsmout_if(error);
+ error = xb_seek(&xb, attrslength_offset);
+ xb_add_32(error, &xb, end_offset - attrslength_offset - XDRWORD/*don't include length field*/);
+
+ if (!error) {
+ /* grab the assembled buffer */
+ *xdrbufp = xb_buffer_base(&xb);
+ xb.xb_flags &= ~XB_CLEANUP;
+ }
+nfsmout:
+ xb_cleanup(&xb);
+ FREE_ZONE(mntfrom, MAXPATHLEN, M_NAMEI);
+ return (error);
+}
+
+/*
+ * 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 };
+
+ /* make sure mbuf constants are set up */
+ if (!nfs_mbuf_mhlen)
+ nfs_mbuf_init();
+
+ if (vfs_flags(mp) & MNT_UPDATE) {
+ nmp = VFSTONFS(mp);
+ /* update paths, file handles, etc, here XXX */
+ xb_free(xdrbuf);
+ return (0);
+ } else {
+ /* allocate an NFS mount structure for this mount */
+ MALLOC_ZONE(nmp, struct nfsmount *,
+ sizeof (struct nfsmount), M_NFSMNT, M_WAITOK);
+ if (!nmp) {
+ xb_free(xdrbuf);
+ return (ENOMEM);
+ }
+ bzero((caddr_t)nmp, sizeof (struct nfsmount));
+ lck_mtx_init(&nmp->nm_lock, nfs_mount_grp, LCK_ATTR_NULL);
+ TAILQ_INIT(&nmp->nm_resendq);
+ TAILQ_INIT(&nmp->nm_iodq);
+ TAILQ_INIT(&nmp->nm_gsscl);
+ LIST_INIT(&nmp->nm_monlist);
+ vfs_setfsprivate(mp, nmp);
+ vfs_getnewfsid(mp);
+ nmp->nm_mountp = mp;
+ vfs_setauthopaque(mp);
+
+ nfs_nhinit_finish();
+
+ nmp->nm_args = xdrbuf;
+
+ /* set up defaults */
+ nmp->nm_vers = 0;
+ nmp->nm_timeo = NFS_TIMEO;
+ nmp->nm_retry = NFS_RETRANS;
+ nmp->nm_sotype = 0;
+ nmp->nm_sofamily = 0;
+ nmp->nm_nfsport = 0;
+ nmp->nm_wsize = NFS_WSIZE;
+ nmp->nm_rsize = NFS_RSIZE;
+ nmp->nm_readdirsize = NFS_READDIRSIZE;
+ nmp->nm_numgrps = NFS_MAXGRPS;
+ nmp->nm_readahead = NFS_DEFRAHEAD;
+ nmp->nm_tprintf_delay = nfs_tprintf_delay;
+ if (nmp->nm_tprintf_delay < 0)
+ nmp->nm_tprintf_delay = 0;
+ nmp->nm_tprintf_initial_delay = nfs_tprintf_initial_delay;
+ if (nmp->nm_tprintf_initial_delay < 0)
+ nmp->nm_tprintf_initial_delay = 0;
+ nmp->nm_acregmin = NFS_MINATTRTIMO;
+ nmp->nm_acregmax = NFS_MAXATTRTIMO;
+ nmp->nm_acdirmin = NFS_MINDIRATTRTIMO;
+ nmp->nm_acdirmax = NFS_MAXDIRATTRTIMO;
+ nmp->nm_auth = RPCAUTH_SYS;
+ nmp->nm_deadtimeout = 0;
+ nmp->nm_curdeadtimeout = 0;
+ NFS_BITMAP_SET(nmp->nm_flags, NFS_MFLAG_NOACL);
+ nmp->nm_realm = NULL;
+ nmp->nm_principal = NULL;
+ nmp->nm_sprinc = NULL;
+ }
+
+ mattrs = nmp->nm_mattrs;
+ mflags = nmp->nm_mflags;
+ mflags_mask = nmp->nm_mflags_mask;
+
+ /* set up NFS mount with args */
+ xb_init_buffer(&xb, xdrbuf, 2*XDRWORD);
+ xb_get_32(error, &xb, val); /* version */
+ xb_get_32(error, &xb, argslength); /* args length */
+ nfsmerr_if(error);
+ xb_init_buffer(&xb, xdrbuf, argslength); /* restart parsing with actual buffer length */
+ xb_get_32(error, &xb, val); /* version */
+ xb_get_32(error, &xb, argslength); /* args length */
+ xb_get_32(error, &xb, val); /* XDR args version */
+ if (val != NFS_XDRARGS_VERSION_0)
+ error = EINVAL;
+ len = NFS_MATTR_BITMAP_LEN;
+ xb_get_bitmap(error, &xb, mattrs, len); /* mount attribute bitmap */
+ attrslength = 0;
+ xb_get_32(error, &xb, attrslength); /* attrs length */
+ if (!error && (attrslength > (argslength - ((4+NFS_MATTR_BITMAP_LEN+1)*XDRWORD))))
+ error = EINVAL;
+ nfsmerr_if(error);
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_FLAGS)) {
+ len = NFS_MFLAG_BITMAP_LEN;
+ xb_get_bitmap(error, &xb, mflags_mask, len); /* mount flag mask */
+ len = NFS_MFLAG_BITMAP_LEN;
+ xb_get_bitmap(error, &xb, mflags, len); /* mount flag values */
+ if (!error) {
+ /* clear all mask bits and OR in all the ones that are set */
+ nmp->nm_flags[0] &= ~mflags_mask[0];
+ nmp->nm_flags[0] |= (mflags_mask[0] & mflags[0]);
+ }
+ }
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_NFS_VERSION)) {
+ xb_get_32(error, &xb, vers);
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_NFS_MINOR_VERSION))
+ xb_get_32(error, &xb, minorvers);
+ else
+ minorvers = 0;
+ nfsmerr_if(error);
+ switch (vers) {
+ case 2:
+ nmp->nm_vers = NFS_VER2;
+ break;
+ case 3:
+ nmp->nm_vers = NFS_VER3;
+ break;
+ case 4:
+ switch (minorvers) {
+ case 0:
+ nmp->nm_vers = NFS_VER4;
+ break;
+ default:
+ error = EINVAL;
+ }
+ break;
+ default:
+ error = EINVAL;
+ }
+ }
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_NFS_MINOR_VERSION)) {
+ /* should have also gotten NFS version (and already gotten minorvers) */
+ if (!NFS_BITMAP_ISSET(mattrs, NFS_MATTR_NFS_VERSION))
+ error = EINVAL;
+ }
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_READ_SIZE))
+ xb_get_32(error, &xb, nmp->nm_rsize);
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_WRITE_SIZE))
+ xb_get_32(error, &xb, nmp->nm_wsize);
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_READDIR_SIZE))
+ xb_get_32(error, &xb, nmp->nm_readdirsize);
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_READAHEAD))
+ xb_get_32(error, &xb, nmp->nm_readahead);
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_ATTRCACHE_REG_MIN)) {
+ xb_get_32(error, &xb, nmp->nm_acregmin);
+ xb_skip(error, &xb, XDRWORD);
+ }
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_ATTRCACHE_REG_MAX)) {
+ xb_get_32(error, &xb, nmp->nm_acregmax);
+ xb_skip(error, &xb, XDRWORD);
+ }
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_ATTRCACHE_DIR_MIN)) {
+ xb_get_32(error, &xb, nmp->nm_acdirmin);
+ xb_skip(error, &xb, XDRWORD);
+ }
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_ATTRCACHE_DIR_MAX)) {
+ xb_get_32(error, &xb, nmp->nm_acdirmax);
+ xb_skip(error, &xb, XDRWORD);
+ }
+ nfsmerr_if(error);
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_LOCK_MODE)) {
+ xb_get_32(error, &xb, val);
+ switch (val) {
+ case NFS_LOCK_MODE_DISABLED:
+ case NFS_LOCK_MODE_LOCAL:
+ if (nmp->nm_vers >= NFS_VER4) {
+ /* disabled/local lock mode only allowed on v2/v3 */
+ error = EINVAL;
+ break;
+ }
+ /* FALLTHROUGH */
+ case NFS_LOCK_MODE_ENABLED:
+ nmp->nm_lockmode = val;
+ break;
+ default:
+ error = EINVAL;
+ }
+ }
+ nfsmerr_if(error);
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_SECURITY)) {
+ uint32_t seccnt;
+ xb_get_32(error, &xb, seccnt);
+ if (!error && ((seccnt < 1) || (seccnt > NX_MAX_SEC_FLAVORS)))
+ error = EINVAL;
+ nfsmerr_if(error);
+ nmp->nm_sec.count = seccnt;
+ for (i=0; i < seccnt; i++) {
+ xb_get_32(error, &xb, nmp->nm_sec.flavors[i]);
+ /* Check for valid security flavor */
+ switch (nmp->nm_sec.flavors[i]) {
+ case RPCAUTH_NONE:
+ case RPCAUTH_SYS:
+ case RPCAUTH_KRB5:
+ case RPCAUTH_KRB5I:
+ case RPCAUTH_KRB5P:
+ break;
+ default:
+ error = EINVAL;
+ }
+ }
+ /* start with the first flavor */
+ nmp->nm_auth = nmp->nm_sec.flavors[0];
+ }
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_MAX_GROUP_LIST))
+ xb_get_32(error, &xb, nmp->nm_numgrps);
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_SOCKET_TYPE)) {
+ char sotype[6];
+
+ xb_get_32(error, &xb, val);
+ if (!error && ((val < 3) || (val > 5)))
+ error = EINVAL;
+ nfsmerr_if(error);
+ error = xb_get_bytes(&xb, sotype, val, 0);
+ nfsmerr_if(error);
+ sotype[val] = '\0';
+ if (!strcmp(sotype, "tcp")) {
+ nmp->nm_sotype = SOCK_STREAM;
+ } else if (!strcmp(sotype, "udp")) {
+ nmp->nm_sotype = SOCK_DGRAM;
+ } else if (!strcmp(sotype, "tcp4")) {
+ nmp->nm_sotype = SOCK_STREAM;
+ nmp->nm_sofamily = AF_INET;
+ } else if (!strcmp(sotype, "udp4")) {
+ nmp->nm_sotype = SOCK_DGRAM;
+ nmp->nm_sofamily = AF_INET;
+ } else if (!strcmp(sotype, "tcp6")) {
+ nmp->nm_sotype = SOCK_STREAM;
+ nmp->nm_sofamily = AF_INET6;
+ } else if (!strcmp(sotype, "udp6")) {
+ nmp->nm_sotype = SOCK_DGRAM;
+ nmp->nm_sofamily = AF_INET6;
+ } else if (!strcmp(sotype, "inet4")) {
+ nmp->nm_sofamily = AF_INET;
+ } else if (!strcmp(sotype, "inet6")) {
+ nmp->nm_sofamily = AF_INET6;
+ } else if (!strcmp(sotype, "inet")) {
+ nmp->nm_sofamily = 0; /* ok */
+ } else {
+ error = EINVAL;
+ }
+ if (!error && (nmp->nm_vers >= NFS_VER4) && nmp->nm_sotype &&
+ (nmp->nm_sotype != SOCK_STREAM))
+ error = EINVAL; /* NFSv4 is only allowed over TCP. */
+ nfsmerr_if(error);
+ }
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_NFS_PORT))
+ xb_get_32(error, &xb, nmp->nm_nfsport);
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_MOUNT_PORT))
+ xb_get_32(error, &xb, nmp->nm_mountport);
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_REQUEST_TIMEOUT)) {
+ /* convert from time to 0.1s units */
+ xb_get_32(error, &xb, nmp->nm_timeo);
+ xb_get_32(error, &xb, val);
+ nfsmerr_if(error);
+ if (val >= 1000000000)
+ error = EINVAL;
+ nfsmerr_if(error);
+ nmp->nm_timeo *= 10;
+ nmp->nm_timeo += (val+100000000-1)/100000000;
+ /* now convert to ticks */
+ nmp->nm_timeo = (nmp->nm_timeo * NFS_HZ + 5) / 10;
+ }
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_SOFT_RETRY_COUNT)) {
+ xb_get_32(error, &xb, val);
+ if (!error && (val > 1))
+ nmp->nm_retry = val;
+ }
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_DEAD_TIMEOUT)) {
+ xb_get_32(error, &xb, nmp->nm_deadtimeout);
+ xb_skip(error, &xb, XDRWORD);
+ }
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_FH)) {
+ nfsmerr_if(error);
+ MALLOC(nmp->nm_fh, fhandle_t *, sizeof(fhandle_t), M_TEMP, M_WAITOK|M_ZERO);
+ if (!nmp->nm_fh)
+ error = ENOMEM;
+ xb_get_32(error, &xb, nmp->nm_fh->fh_len);
+ nfsmerr_if(error);
+ error = xb_get_bytes(&xb, (char*)&nmp->nm_fh->fh_data[0], nmp->nm_fh->fh_len, 0);
+ }
+ nfsmerr_if(error);
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_FS_LOCATIONS)) {
+ uint32_t loc, serv, addr, comp;
+ struct nfs_fs_location *fsl;
+ struct nfs_fs_server *fss;
+ struct nfs_fs_path *fsp;
+
+ xb_get_32(error, &xb, nmp->nm_locations.nl_numlocs); /* fs location count */
+ /* sanity check location count */
+ if (!error && ((nmp->nm_locations.nl_numlocs < 1) || (nmp->nm_locations.nl_numlocs > 256)))
+ error = EINVAL;
+ nfsmerr_if(error);
+ MALLOC(nmp->nm_locations.nl_locations, struct nfs_fs_location **, nmp->nm_locations.nl_numlocs * sizeof(struct nfs_fs_location*), M_TEMP, M_WAITOK|M_ZERO);
+ if (!nmp->nm_locations.nl_locations)
+ error = ENOMEM;
+ for (loc = 0; loc < nmp->nm_locations.nl_numlocs; loc++) {
+ nfsmerr_if(error);
+ MALLOC(fsl, struct nfs_fs_location *, sizeof(struct nfs_fs_location), M_TEMP, M_WAITOK|M_ZERO);
+ if (!fsl)
+ error = ENOMEM;
+ nmp->nm_locations.nl_locations[loc] = fsl;
+ xb_get_32(error, &xb, fsl->nl_servcount); /* server count */
+ /* sanity check server count */
+ if (!error && ((fsl->nl_servcount < 1) || (fsl->nl_servcount > 256)))
+ error = EINVAL;
+ nfsmerr_if(error);
+ MALLOC(fsl->nl_servers, struct nfs_fs_server **, fsl->nl_servcount * sizeof(struct nfs_fs_server*), M_TEMP, M_WAITOK|M_ZERO);
+ if (!fsl->nl_servers)
+ error = ENOMEM;
+ for (serv = 0; serv < fsl->nl_servcount; serv++) {
+ nfsmerr_if(error);
+ MALLOC(fss, struct nfs_fs_server *, sizeof(struct nfs_fs_server), M_TEMP, M_WAITOK|M_ZERO);
+ if (!fss)
+ error = ENOMEM;
+ fsl->nl_servers[serv] = fss;
+ xb_get_32(error, &xb, val); /* server name length */
+ /* sanity check server name length */
+ if (!error && ((val < 1) || (val > MAXPATHLEN)))
+ error = EINVAL;
+ nfsmerr_if(error);
+ MALLOC(fss->ns_name, char *, val+1, M_TEMP, M_WAITOK|M_ZERO);
+ if (!fss->ns_name)
+ error = ENOMEM;
+ nfsmerr_if(error);
+ error = xb_get_bytes(&xb, fss->ns_name, val, 0); /* server name */
+ xb_get_32(error, &xb, fss->ns_addrcount); /* address count */
+ /* sanity check address count (OK to be zero) */
+ if (!error && (fss->ns_addrcount > 256))
+ error = EINVAL;
+ nfsmerr_if(error);
+ if (fss->ns_addrcount > 0) {
+ MALLOC(fss->ns_addresses, char **, fss->ns_addrcount * sizeof(char *), M_TEMP, M_WAITOK|M_ZERO);
+ if (!fss->ns_addresses)
+ error = ENOMEM;
+ for (addr = 0; addr < fss->ns_addrcount; addr++) {
+ xb_get_32(error, &xb, val); /* address length */
+ /* sanity check address length */
+ if (!error && ((val < 1) || (val > 128)))
+ error = EINVAL;
+ nfsmerr_if(error);
+ MALLOC(fss->ns_addresses[addr], char *, val+1, M_TEMP, M_WAITOK|M_ZERO);
+ if (!fss->ns_addresses[addr])
+ error = ENOMEM;
+ nfsmerr_if(error);
+ error = xb_get_bytes(&xb, fss->ns_addresses[addr], val, 0); /* address */
+ }
+ }
+ xb_get_32(error, &xb, val); /* server info length */
+ xb_skip(error, &xb, val); /* skip server info */
+ }
+ /* get pathname */
+ fsp = &fsl->nl_path;
+ xb_get_32(error, &xb, fsp->np_compcount); /* component count */
+ /* sanity check component count */
+ if (!error && (fsp->np_compcount > MAXPATHLEN))
+ error = EINVAL;
+ nfsmerr_if(error);
+ if (fsp->np_compcount) {
+ MALLOC(fsp->np_components, char **, fsp->np_compcount * sizeof(char*), M_TEMP, M_WAITOK|M_ZERO);
+ if (!fsp->np_components)
+ error = ENOMEM;
+ }
+ for (comp = 0; comp < fsp->np_compcount; comp++) {
+ xb_get_32(error, &xb, val); /* component length */
+ /* sanity check component length */
+ if (!error && (val == 0)) {
+ /*
+ * Apparently some people think a path with zero components should
+ * be encoded with one zero-length component. So, just ignore any
+ * zero length components.
+ */
+ comp--;
+ fsp->np_compcount--;
+ if (fsp->np_compcount == 0) {
+ FREE(fsp->np_components, M_TEMP);
+ fsp->np_components = NULL;
+ }
+ continue;
+ }
+ if (!error && ((val < 1) || (val > MAXPATHLEN)))
+ error = EINVAL;
+ nfsmerr_if(error);
+ MALLOC(fsp->np_components[comp], char *, val+1, M_TEMP, M_WAITOK|M_ZERO);
+ if (!fsp->np_components[comp])
+ error = ENOMEM;
+ nfsmerr_if(error);
+ error = xb_get_bytes(&xb, fsp->np_components[comp], val, 0); /* component */
+ }
+ xb_get_32(error, &xb, val); /* fs location info length */
+ xb_skip(error, &xb, val); /* skip fs location info */
+ }
+ }
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_MNTFLAGS))
+ xb_skip(error, &xb, XDRWORD);
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_MNTFROM)) {
+ xb_get_32(error, &xb, len);
+ nfsmerr_if(error);
+ val = len;
+ if (val >= sizeof(vfs_statfs(mp)->f_mntfromname))
+ val = sizeof(vfs_statfs(mp)->f_mntfromname) - 1;
+ error = xb_get_bytes(&xb, vfs_statfs(mp)->f_mntfromname, val, 0);
+ if ((len - val) > 0)
+ xb_skip(error, &xb, len - val);
+ nfsmerr_if(error);
+ vfs_statfs(mp)->f_mntfromname[val] = '\0';
+ }
+ nfsmerr_if(error);
+
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_REALM)) {
+ xb_get_32(error, &xb, len);
+ if (!error && ((len < 1) || (len > MAXPATHLEN)))
+ error=EINVAL;
+ nfsmerr_if(error);
+ /* allocate an extra byte for a leading '@' if its not already prepended to the realm */
+ MALLOC(nmp->nm_realm, char *, len+2, M_TEMP, M_WAITOK|M_ZERO);
+ if (!nmp->nm_realm)
+ error = ENOMEM;
+ nfsmerr_if(error);
+ error = xb_get_bytes(&xb, nmp->nm_realm, len, 0);
+ if (error == 0 && *nmp->nm_realm != '@') {
+ bcopy(nmp->nm_realm, &nmp->nm_realm[1], len);
+ nmp->nm_realm[0] = '@';
+ }
+ }
+ nfsmerr_if(error);
+
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_PRINCIPAL)) {
+ xb_get_32(error, &xb, len);
+ if (!error && ((len < 1) || (len > MAXPATHLEN)))
+ error=EINVAL;
+ nfsmerr_if(error);
+ MALLOC(nmp->nm_principal, char *, len+1, M_TEMP, M_WAITOK|M_ZERO);
+ if (!nmp->nm_principal)
+ error = ENOMEM;
+ nfsmerr_if(error);
+ error = xb_get_bytes(&xb, nmp->nm_principal, len, 0);
+ }
+ nfsmerr_if(error);
+
+ if (NFS_BITMAP_ISSET(mattrs, NFS_MATTR_SVCPRINCIPAL)) {
+ xb_get_32(error, &xb, len);
+ if (!error && ((len < 1) || (len > MAXPATHLEN)))
+ error=EINVAL;
+ nfsmerr_if(error);
+ MALLOC(nmp->nm_sprinc, char *, len+1, M_TEMP, M_WAITOK|M_ZERO);
+ if (!nmp->nm_sprinc)
+ error = ENOMEM;
+ nfsmerr_if(error);
+ error = xb_get_bytes(&xb, nmp->nm_sprinc, len, 0);
+ }
+ nfsmerr_if(error);
+
+ /*
+ * Sanity check/finalize settings.
+ */
+
+ if (nmp->nm_timeo < NFS_MINTIMEO)
+ nmp->nm_timeo = NFS_MINTIMEO;
+ else if (nmp->nm_timeo > NFS_MAXTIMEO)
+ nmp->nm_timeo = NFS_MAXTIMEO;
+ if (nmp->nm_retry > NFS_MAXREXMIT)
+ nmp->nm_retry = NFS_MAXREXMIT;
+
+ if (nmp->nm_numgrps > NFS_MAXGRPS)
+ nmp->nm_numgrps = NFS_MAXGRPS;
+ if (nmp->nm_readahead > NFS_MAXRAHEAD)
+ nmp->nm_readahead = NFS_MAXRAHEAD;
+ if (nmp->nm_acregmin > nmp->nm_acregmax)
+ nmp->nm_acregmin = nmp->nm_acregmax;
+ if (nmp->nm_acdirmin > nmp->nm_acdirmax)
+ nmp->nm_acdirmin = nmp->nm_acdirmax;
+
+ /* need at least one fs location */
+ if (nmp->nm_locations.nl_numlocs < 1)
+ error = EINVAL;
+ nfsmerr_if(error);
+
+ /* init mount's mntfromname to first location */
+ if (!NM_OMATTR_GIVEN(nmp, MNTFROM))
+ nfs_location_mntfromname(&nmp->nm_locations, firstloc,
+ vfs_statfs(mp)->f_mntfromname, sizeof(vfs_statfs(mp)->f_mntfromname), 0);
+
+ /* Need to save the mounting credential for v4. */
+ nmp->nm_mcred = vfs_context_ucred(ctx);
+ if (IS_VALID_CRED(nmp->nm_mcred))
+ kauth_cred_ref(nmp->nm_mcred);
+
+ /*
+ * If a reserved port is required, check for that privilege.
+ * (Note that mirror mounts are exempt because the privilege was
+ * already checked for the original mount.)
+ */
+ if (NMFLAG(nmp, RESVPORT) && !vfs_iskernelmount(mp))
+ error = priv_check_cred(nmp->nm_mcred, PRIV_NETINET_RESERVEDPORT, 0);
+ nfsmerr_if(error);
+
+ /* do mount's initial socket connection */
+ error = nfs_mount_connect(nmp);
+ nfsmerr_if(error);
+
+ /* set up the version-specific function tables */
+ if (nmp->nm_vers < NFS_VER4)
+ nmp->nm_funcs = &nfs3_funcs;
+ else
+ nmp->nm_funcs = &nfs4_funcs;
+
+ /* sanity check settings now that version/connection is set */
+ if (nmp->nm_vers == NFS_VER2) /* ignore RDIRPLUS on NFSv2 */
+ NFS_BITMAP_CLR(nmp->nm_flags, NFS_MFLAG_RDIRPLUS);
+ if (nmp->nm_vers >= NFS_VER4) {
+ if (NFS_BITMAP_ISSET(nmp->nm_flags, NFS_MFLAG_ACLONLY)) /* aclonly trumps noacl */
+ NFS_BITMAP_CLR(nmp->nm_flags, NFS_MFLAG_NOACL);
+ NFS_BITMAP_CLR(nmp->nm_flags, NFS_MFLAG_CALLUMNT);
+ if (nmp->nm_lockmode != NFS_LOCK_MODE_ENABLED)
+ error = EINVAL; /* disabled/local lock mode only allowed on v2/v3 */
+ } else {
+ /* ignore these if not v4 */
+ NFS_BITMAP_CLR(nmp->nm_flags, NFS_MFLAG_NOCALLBACK);
+ NFS_BITMAP_CLR(nmp->nm_flags, NFS_MFLAG_NONAMEDATTR);
+ NFS_BITMAP_CLR(nmp->nm_flags, NFS_MFLAG_NOACL);
+ NFS_BITMAP_CLR(nmp->nm_flags, NFS_MFLAG_ACLONLY);
+ }
+ nfsmerr_if(error);
+
+ if (nmp->nm_sotype == SOCK_DGRAM) {
+ /* I/O size defaults for UDP are different */
+ if (!NFS_BITMAP_ISSET(mattrs, NFS_MATTR_READ_SIZE))
+ nmp->nm_rsize = NFS_DGRAM_RSIZE;
+ if (!NFS_BITMAP_ISSET(mattrs, NFS_MATTR_WRITE_SIZE))
+ nmp->nm_wsize = NFS_DGRAM_WSIZE;
+ }
+
+ /* round down I/O sizes to multiple of NFS_FABLKSIZE */
+ nmp->nm_rsize &= ~(NFS_FABLKSIZE - 1);
+ if (nmp->nm_rsize <= 0)
+ nmp->nm_rsize = NFS_FABLKSIZE;
+ nmp->nm_wsize &= ~(NFS_FABLKSIZE - 1);
+ if (nmp->nm_wsize <= 0)
+ nmp->nm_wsize = NFS_FABLKSIZE;
+
+ /* and limit I/O sizes to maximum allowed */
+ maxio = (nmp->nm_vers == NFS_VER2) ? NFS_V2MAXDATA :
+ (nmp->nm_sotype == SOCK_DGRAM) ? NFS_MAXDGRAMDATA : NFS_MAXDATA;
+ if (maxio > NFS_MAXBSIZE)
+ maxio = NFS_MAXBSIZE;
+ if (nmp->nm_rsize > maxio)
+ nmp->nm_rsize = maxio;
+ if (nmp->nm_wsize > maxio)
+ nmp->nm_wsize = maxio;
+
+ if (nmp->nm_readdirsize > maxio)
+ nmp->nm_readdirsize = maxio;
+ if (nmp->nm_readdirsize > nmp->nm_rsize)
+ nmp->nm_readdirsize = nmp->nm_rsize;
+
+ /* Set up the sockets and related info */
+ if (nmp->nm_sotype == SOCK_DGRAM)
+ TAILQ_INIT(&nmp->nm_cwndq);
+
+ /*
+ * Get the root node/attributes from the NFS server and
+ * do any basic, version-specific setup.
+ */
+ error = nmp->nm_funcs->nf_mount(nmp, ctx, &np);
+ nfsmerr_if(error);
+
+ /*
+ * A reference count is needed on the node representing the
+ * remote root. If this object is not persistent, then backward
+ * traversals of the mount point (i.e. "..") will not work if
+ * the node gets flushed out of the cache.
+ */
+ nmp->nm_dnp = np;
+ *vpp = NFSTOV(np);
+ /* get usecount and drop iocount */
+ error = vnode_ref(*vpp);
+ vnode_put(*vpp);
+ if (error) {
+ vnode_recycle(*vpp);
+ goto nfsmerr;
+ }
+
+ /*
+ * Do statfs to ensure static info gets set to reasonable values.
+ */
+ if ((error = nmp->nm_funcs->nf_update_statfs(nmp, ctx))) {
+ int error2 = vnode_getwithref(*vpp);
+ vnode_rele(*vpp);
+ if (!error2)
+ vnode_put(*vpp);
+ vnode_recycle(*vpp);
+ goto nfsmerr;
+ }
+ sbp = vfs_statfs(mp);
+ sbp->f_bsize = nmp->nm_fsattr.nfsa_bsize;
+ sbp->f_blocks = nmp->nm_fsattr.nfsa_space_total / sbp->f_bsize;
+ sbp->f_bfree = nmp->nm_fsattr.nfsa_space_free / sbp->f_bsize;
+ sbp->f_bavail = nmp->nm_fsattr.nfsa_space_avail / sbp->f_bsize;
+ sbp->f_bused = (nmp->nm_fsattr.nfsa_space_total / sbp->f_bsize) -
+ (nmp->nm_fsattr.nfsa_space_free / sbp->f_bsize);
+ sbp->f_files = nmp->nm_fsattr.nfsa_files_total;
+ sbp->f_ffree = nmp->nm_fsattr.nfsa_files_free;
+ sbp->f_iosize = nfs_iosize;
+
+ /*
+ * Calculate the size used for I/O buffers. Use the larger
+ * of the two sizes to minimise NFS requests but make sure
+ * that it is at least one VM page to avoid wasting buffer
+ * space and to allow easy mmapping of I/O buffers.
+ * The read/write RPC calls handle the splitting up of
+ * buffers into multiple requests if the buffer size is
+ * larger than the I/O size.
+ */
+ iosize = max(nmp->nm_rsize, nmp->nm_wsize);
+ if (iosize < PAGE_SIZE)
+ iosize = PAGE_SIZE;
+ nmp->nm_biosize = trunc_page_32(iosize);
+
+ /* For NFSv3 and greater, there is a (relatively) reliable ACCESS call. */
+ if (nmp->nm_vers > NFS_VER2)
+ vfs_setauthopaqueaccess(mp);
+
+ switch (nmp->nm_lockmode) {
+ case NFS_LOCK_MODE_DISABLED:
+ break;
+ case NFS_LOCK_MODE_LOCAL:
+ vfs_setlocklocal(nmp->nm_mountp);
+ break;
+ case NFS_LOCK_MODE_ENABLED:
+ default:
+ if (nmp->nm_vers <= NFS_VER3)
+ nfs_lockd_mount_register(nmp);
+ break;
+ }
+
+ /* success! */
+ lck_mtx_lock(&nmp->nm_lock);
+ nmp->nm_state |= NFSSTA_MOUNTED;
+ lck_mtx_unlock(&nmp->nm_lock);
+ return (0);
+nfsmerr:
+ nfs_mount_cleanup(nmp);
+ return (error);
+}
+
+#if CONFIG_TRIGGERS
+
+/*
+ * We've detected a file system boundary on the server and
+ * need to mount a new file system so that our file systems
+ * MIRROR the file systems on the server.
+ *
+ * Build the mount arguments for the new mount and call kernel_mount().
+ */
+int
+nfs_mirror_mount_domount(vnode_t dvp, vnode_t vp, vfs_context_t ctx)
+{
+ nfsnode_t np = VTONFS(vp);
+ nfsnode_t dnp = VTONFS(dvp);
+ struct nfsmount *nmp = NFSTONMP(np);
+ char fstype[MFSTYPENAMELEN], *mntfromname = NULL, *path = NULL, *relpath, *p, *cp;
+ int error = 0, pathbuflen = MAXPATHLEN, i, mntflags = 0, referral, skipcopy = 0;
+ size_t nlen;
+ struct xdrbuf xb, xbnew;
+ uint32_t mattrs[NFS_MATTR_BITMAP_LEN];
+ uint32_t newmattrs[NFS_MATTR_BITMAP_LEN];
+ uint32_t newmflags[NFS_MFLAG_BITMAP_LEN];
+ uint32_t newmflags_mask[NFS_MFLAG_BITMAP_LEN];
+ uint32_t argslength = 0, val, count, mlen, mlen2, rlen, relpathcomps;
+ uint32_t argslength_offset, attrslength_offset, end_offset;
+ uint32_t numlocs, loc, numserv, serv, numaddr, addr, numcomp, comp;
+ char buf[XDRWORD];
+ struct nfs_fs_locations nfsls;
+
+ referral = (np->n_vattr.nva_flags & NFS_FFLAG_TRIGGER_REFERRAL);
+ if (referral)
+ bzero(&nfsls, sizeof(nfsls));
+
+ xb_init(&xbnew, 0);
+
+ if (!nmp || (nmp->nm_state & NFSSTA_FORCE))
+ return (ENXIO);