Make it possible to reroot into NFS. This means one can have

eg an NFSv4 root over WiFi: boot from md_root (small rootfs image
preloaded by loader(8)), setup WiFi, and then reroot into the actual
root, over NFS.

Note that it's currently limited to NFSv4, and due to problems with
nfsuserd(8) it requres a workaround on the server side: one needs
to set the vfs.nfsd.enable_stringtouid=1 sysctl and not run nfsuserd(8)
on either the server or the client side.

Reviewed by:	rmacklem@
MFC after:	1 month
Relnotes:	yes
Sponsored by:	The FreeBSD Foundation
Differential Revision:	https://reviews.freebsd.org/D6347
This commit is contained in:
Edward Tomasz Napierala 2016-05-15 08:34:59 +00:00
parent e542607041
commit 0d1654c39b

View File

@ -740,6 +740,101 @@ static const char *nfs_opts[] = { "from", "nfs_args",
"pnfs", "wcommitsize",
NULL };
/*
* Parse the "from" mountarg, passed by the generic mount(8) program
* or the mountroot code. This is used when rerooting into NFS.
*
* Note that the "hostname" is actually a "hostname:/share/path" string.
*/
static int
nfs_mount_parse_from(struct vfsoptlist *opts, char **hostnamep,
struct sockaddr_in **sinp, char *dirpath, size_t dirpathsize, int *dirlenp)
{
char nam[MNAMELEN + 1];
char *delimp, *hostp, *spec;
int error, have_bracket = 0, offset, rv, speclen;
struct sockaddr_in *sin;
size_t len;
error = vfs_getopt(opts, "from", (void **)&spec, &speclen);
if (error != 0)
return (error);
/*
* This part comes from sbin/mount_nfs/mount_nfs.c:getnfsargs().
*/
if (*spec == '[' && (delimp = strchr(spec + 1, ']')) != NULL &&
*(delimp + 1) == ':') {
hostp = spec + 1;
spec = delimp + 2;
have_bracket = 1;
} else if ((delimp = strrchr(spec, ':')) != NULL) {
hostp = spec;
spec = delimp + 1;
} else if ((delimp = strrchr(spec, '@')) != NULL) {
printf("%s: path@server syntax is deprecated, "
"use server:path\n", __func__);
hostp = delimp + 1;
} else {
printf("%s: no <host>:<dirpath> nfs-name\n", __func__);
return (EINVAL);
}
*delimp = '\0';
/*
* If there has been a trailing slash at mounttime it seems
* that some mountd implementations fail to remove the mount
* entries from their mountlist while unmounting.
*/
for (speclen = strlen(spec);
speclen > 1 && spec[speclen - 1] == '/';
speclen--)
spec[speclen - 1] = '\0';
if (strlen(hostp) + strlen(spec) + 1 > MNAMELEN) {
printf("%s: %s:%s: name too long", __func__, hostp, spec);
return (EINVAL);
}
/* Make both '@' and ':' notations equal */
if (*hostp != '\0') {
len = strlen(hostp);
offset = 0;
if (have_bracket)
nam[offset++] = '[';
memmove(nam + offset, hostp, len);
if (have_bracket)
nam[len + offset++] = ']';
nam[len + offset++] = ':';
memmove(nam + len + offset, spec, speclen);
nam[len + speclen + offset] = '\0';
}
/*
* XXX: IPv6
*/
sin = malloc(sizeof(*sin), M_SONAME, M_WAITOK);
rv = inet_pton(AF_INET, hostp, &sin->sin_addr);
if (rv != 1) {
printf("%s: cannot parse '%s', inet_pton() returned %d\n",
__func__, hostp, rv);
free(sin, M_SONAME);
return (EINVAL);
}
sin->sin_len = sizeof(*sin);
sin->sin_family = AF_INET;
/*
* XXX: hardcoded port number.
*/
sin->sin_port = htons(2049);
*hostnamep = strdup(nam, M_NEWNFSMNT);
*sinp = sin;
strlcpy(dirpath, spec, dirpathsize);
*dirlenp = strlen(dirpath);
return (0);
}
/*
* VFS Operations.
*
@ -785,17 +880,20 @@ nfs_mount(struct mount *mp)
int nametimeo = NFS_DEFAULT_NAMETIMEO;
int negnametimeo = NFS_DEFAULT_NEGNAMETIMEO;
int minvers = 0;
int dirlen, has_nfs_args_opt, krbnamelen, srvkrbnamelen;
int dirlen, has_nfs_args_opt, has_nfs_from_opt,
krbnamelen, srvkrbnamelen;
size_t hstlen;
has_nfs_args_opt = 0;
has_nfs_from_opt = 0;
if (vfs_filteropt(mp->mnt_optnew, nfs_opts)) {
error = EINVAL;
goto out;
}
td = curthread;
if ((mp->mnt_flag & (MNT_ROOTFS | MNT_UPDATE)) == MNT_ROOTFS) {
if ((mp->mnt_flag & (MNT_ROOTFS | MNT_UPDATE)) == MNT_ROOTFS &&
nfs_diskless_valid != 0) {
error = nfs_mountroot(mp);
goto out;
}
@ -1135,6 +1233,19 @@ nfs_mount(struct mount *mp)
args.addrlen);
if (error != 0)
goto out;
} else if (nfs_mount_parse_from(mp->mnt_optnew,
&args.hostname, (struct sockaddr_in **)&nam, dirpath,
sizeof(dirpath), &dirlen) == 0) {
has_nfs_from_opt = 1;
bcopy(args.hostname, hst, MNAMELEN);
hst[MNAMELEN - 1] = '\0';
/*
* This only works with NFSv4 for now.
*/
args.fhsize = 0;
args.flags |= NFSMNT_NFSV4;
args.sotype = SOCK_STREAM;
} else {
if (vfs_getopt(mp->mnt_optnew, "fh", (void **)&args.fh,
&args.fhsize) == 0) {
@ -1174,13 +1285,16 @@ nfs_mount(struct mount *mp)
krbname[0] = '\0';
krbnamelen = strlen(krbname);
if (vfs_getopt(mp->mnt_optnew, "dirpath", (void **)&name, NULL) == 0)
strlcpy(dirpath, name, sizeof (dirpath));
else
dirpath[0] = '\0';
dirlen = strlen(dirpath);
if (has_nfs_from_opt == 0) {
if (vfs_getopt(mp->mnt_optnew,
"dirpath", (void **)&name, NULL) == 0)
strlcpy(dirpath, name, sizeof (dirpath));
else
dirpath[0] = '\0';
dirlen = strlen(dirpath);
}
if (has_nfs_args_opt == 0) {
if (has_nfs_args_opt == 0 && has_nfs_from_opt == 0) {
if (vfs_getopt(mp->mnt_optnew, "addr",
(void **)&args.addr, &args.addrlen) == 0) {
if (args.addrlen > SOCK_MAXADDRLEN) {