nfs_diskless: Add support for an NFSv4 root fs

Without this patch, diskless root NFS file systems
could only be mounted via NFSv3 (or NFSv2).
This patch adds the basic support needed to mount
a root fs via NFSv4.

At this time, the NFSv4 mount will only work if
the following is done on the NFS server configuration:
- The root directory specified in the "V4:" line in
  /etc/exports must be "/".  This is needed since the
  path to mount must be the same for NFSv3 and NFSv4.
- The NFS server must be configured to do both NFSv3
  and NFSv4, since the bootstrap code still uses NFSv3.
- The NFSv4 server must be configured with:
  vfs.nfs.enable_uidtostring=1
  vfs.nfsd.enable_stringtouid=1
  since the NFSv4 root fs cannot be running nfsuserd(8)
  when it is booting.  (This limitation may be removed
  in a future commit by hard-wiring enough id<-->name
  mapping entries to handle things until the nfsuserd(8)
  is running.)

To enable the root fs to be mounted via NFSv4, it needs:
- in the root file system's /boot/loader.conf
  boot.nfsroot.options="nfsv4"
  (Additional options like rsize=65536,wsize=65536 can
   also be specified.)
- in the root file system's /etc/sysctl.conf
  vfs.nfs.enable_uidtostring=1

Requested by:	Dan Shelton <dan.f.dhelton@gmail.com>
MFC after:	1 week
This commit is contained in:
Rick Macklem
2026-04-05 09:00:24 -07:00
parent cb813145f0
commit 8b9775912c
6 changed files with 79 additions and 17 deletions
+1
View File
@@ -609,6 +609,7 @@ int nfscl_relbytelock(vnode_t, u_int64_t, u_int64_t,
int nfscl_checkwritelocked(vnode_t, struct flock *, int nfscl_checkwritelocked(vnode_t, struct flock *,
struct ucred *, NFSPROC_T *, void *, int); struct ucred *, NFSPROC_T *, void *, int);
void nfscl_lockrelease(struct nfscllockowner *, int, int); void nfscl_lockrelease(struct nfscllockowner *, int, int);
void nfscl_uuidcheck(char *);
void nfscl_fillclid(u_int64_t, char *, u_int8_t *, u_int16_t); void nfscl_fillclid(u_int64_t, char *, u_int8_t *, u_int16_t);
void nfscl_filllockowner(void *, u_int8_t *, int); void nfscl_filllockowner(void *, u_int8_t *, int);
void nfscl_freeopen(struct nfsclopen *, int, bool); void nfscl_freeopen(struct nfsclopen *, int, bool);
+31
View File
@@ -659,6 +659,37 @@ ncl_pager_setsize(struct vnode *vp, u_quad_t *nsizep)
return (setnsize); return (setnsize);
} }
/*
* If the uuid passed in is the DEFAULT_UUID, try and find an
* alternate to replace it with.
* If no alternate is available, set uuid to "" so that nfscl_fillclid()
* will use random bytes.
*/
void
nfscl_uuidcheck(char *uuid)
{
int ucplen, uuidlen;
char *ucp;
/*
* If the uuid is the DEFAULT_UUID, try and get an alternative.
*/
uuidlen = strlen(uuid);
ucp = NULL;
if (uuidlen == strlen(DEFAULT_HOSTUUID) &&
NFSBCMP(uuid, DEFAULT_HOSTUUID, uuidlen) == 0) {
*uuid = '\0';
/* Use smbios.system.uuid if it exists. */
if ((ucp = kern_getenv("smbios.system.uuid")) != NULL) {
ucplen = strlen(ucp);
if (ucplen < HOSTUUIDLEN && ucplen > 0)
strlcpy(uuid, ucp, HOSTUUIDLEN);
}
}
if (ucp != NULL)
freeenv(ucp);
}
/* /*
* Fill in the client id name. For these bytes: * Fill in the client id name. For these bytes:
* 1 - they must be unique * 1 - they must be unique
+2 -1
View File
@@ -67,6 +67,7 @@ SYSCTL_U64(_vfs_nfs, OID_AUTO, maxcopyrange, CTLFLAG_RW,
/* /*
* Global variables * Global variables
*/ */
uint32_t nfs_exchangeboot = 0;
extern struct nfsstatsv1 nfsstatsv1; extern struct nfsstatsv1 nfsstatsv1;
extern int nfs_numnfscbd; extern int nfs_numnfscbd;
extern struct timeval nfsboottime; extern struct timeval nfsboottime;
@@ -5537,7 +5538,7 @@ nfsrpc_exchangeid(struct nfsmount *nmp, struct nfsclclient *clp,
nfscl_reqstart(nd, NFSPROC_EXCHANGEID, nmp, NULL, 0, NULL, NULL, nfscl_reqstart(nd, NFSPROC_EXCHANGEID, nmp, NULL, 0, NULL, NULL,
NFS_VER4, minorvers, NULL); NFS_VER4, minorvers, NULL);
NFSM_BUILD(tl, uint32_t *, 2 * NFSX_UNSIGNED); NFSM_BUILD(tl, uint32_t *, 2 * NFSX_UNSIGNED);
*tl++ = txdr_unsigned(nfsboottime.tv_sec); /* Client owner */ *tl++ = txdr_unsigned(nfs_exchangeboot); /* Client owner */
*tl = txdr_unsigned(clp->nfsc_rev); *tl = txdr_unsigned(clp->nfsc_rev);
(void) nfsm_strtom(nd, clp->nfsc_id, clp->nfsc_idlen); (void) nfsm_strtom(nd, clp->nfsc_id, clp->nfsc_idlen);
+15 -2
View File
@@ -89,6 +89,8 @@ extern u_int32_t newnfs_false, newnfs_true;
extern int nfscl_debuglevel; extern int nfscl_debuglevel;
extern int nfscl_enablecallb; extern int nfscl_enablecallb;
extern int nfs_numnfscbd; extern int nfs_numnfscbd;
extern struct timeval nfsboottime;
extern uint32_t nfs_exchangeboot;
NFSREQSPINLOCK; NFSREQSPINLOCK;
NFSCLSTATEMUTEX; NFSCLSTATEMUTEX;
int nfscl_inited = 0; int nfscl_inited = 0;
@@ -883,9 +885,11 @@ nfscl_getcl(struct mount *mp, struct ucred *cred, NFSPROC_T *p,
if (cred != NULL) { if (cred != NULL) {
getcredhostuuid(cred, uuid, sizeof uuid); getcredhostuuid(cred, uuid, sizeof uuid);
idlen = strlen(uuid); idlen = strlen(uuid);
if (idlen > 0) if (idlen > 0) {
nfscl_uuidcheck(uuid);
idlen = strlen(uuid);
idlen += sizeof (u_int64_t); idlen += sizeof (u_int64_t);
else } else
idlen += sizeof (u_int64_t) + 16; /* 16 random bytes */ idlen += sizeof (u_int64_t) + 16; /* 16 random bytes */
newclp = malloc( newclp = malloc(
sizeof (struct nfsclclient) + idlen - 1, M_NFSCLCLIENT, sizeof (struct nfsclclient) + idlen - 1, M_NFSCLCLIENT,
@@ -996,6 +1000,15 @@ nfscl_getcl(struct mount *mp, struct ucred *cred, NFSPROC_T *p,
* such that the server throws away the clientid before * such that the server throws away the clientid before
* receiving the SetClientIDConfirm. * receiving the SetClientIDConfirm.
*/ */
/*
* Must be done here while locked and before calling
* nfsrpc_setclient().
*/
if (nfs_exchangeboot == 0) {
nfs_exchangeboot = nfsboottime.tv_sec;
if (nfs_exchangeboot == 0)
nfs_exchangeboot = arc4random();
}
if (clp->nfsc_renew > 0) if (clp->nfsc_renew > 0)
clidinusedelay = NFSCL_LEASE(clp->nfsc_renew) * 2; clidinusedelay = NFSCL_LEASE(clp->nfsc_renew) * 2;
else else
+5 -2
View File
@@ -567,7 +567,7 @@ nfs_mountdiskless(char *path,
struct vnode **vpp, struct mount *mp) struct vnode **vpp, struct mount *mp)
{ {
struct sockaddr *nam; struct sockaddr *nam;
int dirlen, error; int dirlen, error, minvers;
char *dirpath; char *dirpath;
/* /*
@@ -580,9 +580,12 @@ nfs_mountdiskless(char *path,
else else
dirlen = 0; dirlen = 0;
nam = sodupsockaddr((struct sockaddr *)sin, M_WAITOK); nam = sodupsockaddr((struct sockaddr *)sin, M_WAITOK);
minvers = 0;
if ((args->flags & NFSMNT_NFSV4) != 0)
minvers = -1;
if ((error = mountnfs(args, mp, nam, path, NULL, 0, dirpath, dirlen, if ((error = mountnfs(args, mp, nam, path, NULL, 0, dirpath, dirlen,
NULL, 0, vpp, td->td_ucred, td, NFS_DEFAULT_NAMETIMEO, NULL, 0, vpp, td->td_ucred, td, NFS_DEFAULT_NAMETIMEO,
NFS_DEFAULT_NEGNAMETIMEO, 0, 0, NULL, 0)) != 0) { NFS_DEFAULT_NEGNAMETIMEO, minvers, 0, NULL, 0)) != 0) {
printf("nfs_mountroot: mount %s on /: %d\n", path, error); printf("nfs_mountroot: mount %s on /: %d\n", path, error);
return (error); return (error);
} }
+25 -12
View File
@@ -119,6 +119,10 @@ nfs_parse_options(const char *envopts, struct nfs_args *nd)
else if (strcmp(o, "nfsv3") == 0) { else if (strcmp(o, "nfsv3") == 0) {
nd->flags &= ~NFSMNT_NFSV4; nd->flags &= ~NFSMNT_NFSV4;
nd->flags |= NFSMNT_NFSV3; nd->flags |= NFSMNT_NFSV3;
} else if (strcmp(o, "nfsv4") == 0) {
nd->flags &= ~NFSMNT_NFSV3;
nd->flags |= NFSMNT_NFSV4;
nd->sotype = SOCK_STREAM;
} else if (strcmp(o, "tcp") == 0) } else if (strcmp(o, "tcp") == 0)
nd->sotype = SOCK_STREAM; nd->sotype = SOCK_STREAM;
else if (strcmp(o, "udp") == 0) else if (strcmp(o, "udp") == 0)
@@ -271,24 +275,33 @@ nfs_setup_diskless(void)
return; return;
} }
nd3->root_saddr.sin_port = htons(NFS_PORT); nd3->root_saddr.sin_port = htons(NFS_PORT);
fhlen = decode_nfshandle("boot.nfsroot.nfshandle", if ((cp = kern_getenv("boot.nfsroot.options")) != NULL) {
&nd3->root_fh[0], NFSX_V3FHMAX); nfs_parse_options(cp, &nd3->root_args);
if (fhlen == 0) { freeenv(cp);
printf("nfs_diskless: no NFS handle\n");
return;
} }
if (fhlen != nd3->root_fhsize) { if ((nd3->root_args.flags & NFSMNT_NFSV4) == 0) {
printf("nfs_diskless: bad NFS handle len=%d\n", fhlen); fhlen = decode_nfshandle("boot.nfsroot.nfshandle",
return; &nd3->root_fh[0], NFSX_V3FHMAX);
if (fhlen == 0) {
printf("nfs_diskless: no NFS handle\n");
return;
}
if (fhlen != nd3->root_fhsize) {
printf("nfs_diskless: bad NFS handle len=%d\n",
fhlen);
return;
}
} else {
/*
* For NFSv4, the file handle is derived from the
* boot.nfsroot.path during mounting by NFSv4.
*/
nd3->root_fhsize = 0;
} }
if ((cp = kern_getenv("boot.nfsroot.path")) != NULL) { if ((cp = kern_getenv("boot.nfsroot.path")) != NULL) {
strncpy(nd3->root_hostnam, cp, MNAMELEN - 1); strncpy(nd3->root_hostnam, cp, MNAMELEN - 1);
freeenv(cp); freeenv(cp);
} }
if ((cp = kern_getenv("boot.nfsroot.options")) != NULL) {
nfs_parse_options(cp, &nd3->root_args);
freeenv(cp);
}
nfs_diskless_valid = 3; nfs_diskless_valid = 3;
} else { } else {