Index: head/sys/fs/nfsclient/nfs_clvfsops.c =================================================================== --- head/sys/fs/nfsclient/nfs_clvfsops.c +++ head/sys/fs/nfsclient/nfs_clvfsops.c @@ -741,6 +741,101 @@ 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 : 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. * * mount system call @@ -785,17 +880,20 @@ 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 @@ 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 @@ 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) {