Subject: Re: bin/10986: mount_nfs doesn't fallback to IPv4
To: Andrew Brown <atatat@atatdot.net>
From: Benedikt Meurer <bmeurer@fwdn.de>
List: tech-userlevel
Date: 10/02/2002 19:04:12
On Tue, 01 Oct 2002, Andrew Brown wrote:

> >> > I prefer the "inet" names.
> >> 
> >> The correct solution is to use a "proto=" option, like Solaris has.
> >> Last time I looked into that, there was something that getmntopt(3)
> >> didn't like about that, but I don't quite remember.
> >> 
> >> Please don't implement anything else.
> >
> >Ok, I don't have access to any solaris system for now, but somebody
> >sent me the mount_nfs(1M) man page :-). We would then use proto=
> >{tcp,tcp6,udp,udp6} options and use getnetconfigent("...") to get
> >the struct netconfig, right? That would also obsolete ALTF_TCP, since
> >we could specify proto={tcp,tcp6}, or do I missed something about that?

Ok. I changed mount_nfs to support proto=<netid> option as mentioned in
the mount_nfs(1M) Solaris man page. In addition, I added mntproto=<netid>
to explicitly set the mount protocol to use (if differs from nfs
proto), that will obsolete -U and mntudp, since we now have a more generic
way to specify the mount protocol. I also added a vers=<nfs version number>
option as mentioned in the solaris mount_nfs manual to allow a smooth
integration of NFSv4 (NFSv5, ...), so we do not need to add -<NFS version> and
nfsv<NFS version> options, whenever new NFS protocol versions come up. This
will obsolete -2/-3 and nfsv2/nfsv3 (it would be a pain to handle int force2,
force3, force4, .. and so on). So e.g. if you used something like
	mount_nfs -3 -T -U host:/dirpath /localdir
before, you would now say
	mount_nfs -o vers=3,proto=tcp,mntproto=udp host:/dirpath /localdir
(or even udp6/tcp6). So it'll be easy to add support for new NFS version
and even support for new protocols, I think. We could also get rid of the
sluggish ISO code with this approach.
Maybe we should also replace things like -w with wsize= and -t with timeo=,
or are there any objections to the use of Solaris-like options?

It would also be nice, if people could test my patch. I've tested it with
IPv4 and IPv6 nfsd/mountd, and it seems to work fine.

HTH, Benedikt

Index: mount_nfs.c
===================================================================
RCS file: /cvsroot/basesrc/sbin/mount_nfs/mount_nfs.c,v
retrieving revision 1.35
diff -u -r1.35 mount_nfs.c
--- mount_nfs.c	2002/10/01 03:08:56	1.35
+++ mount_nfs.c	2002/10/02 16:40:25
@@ -98,15 +98,18 @@
 #define ALTF_DUMBTIMR	0x4
 #define ALTF_INTR	0x8
 #define ALTF_KERB	0x10
-#define ALTF_NFSV3	0x20
+#define ALTF_NFSV3	0x20		/* obsoleted by vers=3 */
 #define ALTF_RDIRPLUS	0x40
-#define	ALTF_MNTUDP	0x80
+#define ALTF_MNTUDP	0x80		/* obsoleted by mntproto=udp */
 #define ALTF_NORESPORT	0x100
 #define ALTF_SEQPACKET	0x200
 #define ALTF_NQNFS	0x400
 #define ALTF_SOFT	0x800
-#define ALTF_TCP	0x1000
-#define ALTF_NFSV2	0x2000
+#define ALTF_TCP	0x1000		/* obsoleted by proto=tcp */
+#define ALTF_NFSV2	0x2000		/* obsoleted by vers=2 */
+#define ALTF_PROTO	0x4000
+#define ALTF_VERS	0x8000
+#define ALTF_MNTPROTO	0x10000
 
 static const struct mntopt mopts[] = {
 	MOPT_STDOPTS,
@@ -131,6 +134,9 @@
 	{ "soft", 0, ALTF_SOFT, 1 },
 	{ "tcp", 0, ALTF_TCP, 1 },
 	{ "nfsv2", 0, ALTF_NFSV2, 1 },
+	{ "proto", 0, ALTF_PROTO, 1 },
+	{ "vers", 0, ALTF_VERS, 1 },
+	{ "mntproto", 0, ALTF_MNTPROTO, 1 },
 	{ NULL }
 };
 
@@ -167,10 +173,9 @@
 #define	ISBGRND	2
 int retrycnt;
 int opflags = 0;
-int nfsproto = IPPROTO_UDP;
-int force2 = 0;
-int force3 = 0;
-int mnttcp_ok = 1;
+int forceversion = 0;	/* this is better than having force2,force3,force4..*/
+int forcefamily = 0;
+char mntproto[7] = { 0, };
 
 #ifdef NFSKERB
 static char inst[INST_SZ];
@@ -198,6 +203,7 @@
 static void	usage __P((void));
 static int	xdr_dir __P((XDR *, char *));
 static int	xdr_fh __P((XDR *, struct nfhret *));
+static char *	getoptval __P((const char *, const char *, char *, size_t));
 
 #ifndef MOUNT_NOMAIN
 int
@@ -219,6 +225,7 @@
 	struct nfs_args nfsargs;
 	struct nfsd_cargs ncd;
 	int mntflags, altflags, i, nfssvc_flag, num;
+	char buffer[32];
 	char *name, *p, *spec, *ospec;
 #ifdef NFSKERB
 	uid_t last_ruid;
@@ -242,14 +249,16 @@
 	    "23a:bcCdD:g:I:iKL:lm:o:PpqR:r:sTt:w:x:UX")) != -1)
 		switch (c) {
 		case '3':
-			if (force2)
-				errx(1, "-2 and -3 are mutually exclusive");
-			force3 = 1;
+			warnx("-3 is obsolete, use vers=3 instead");
+			if (forceversion && forceversion != 3)
+				errx(1, "conflicting version options");
+			forceversion = 3;
 			break;
 		case '2':
-			if (force3)
-				errx(1, "-2 and -3 are mutually exclusive");
-			force2 = 1;
+			warnx("-2 is obsolete, use vers=2 instead");
+			if (forceversion && forceversion != 2)
+				errx(1, "conflicting version options");
+			forceversion = 2;
 			nfsargsp->flags &= ~NFSMNT_NFSV3;
 			break;
 		case 'a':
@@ -334,20 +343,27 @@
 				nfsargsp->flags |= NFSMNT_KERB;
 #endif
 			if (altflags & ALTF_NFSV3) {
-				if (force2)
+				warnx("nfsv3 is obsolete, use vers=3 instead");
+				if (forceversion && forceversion != 3)
 					errx(1, "conflicting version options");
-				force3 = 1;
+				forceversion = 3;
 			}
 			if (altflags & ALTF_NFSV2) {
-				if (force3)
+				warnx("nfsv2 is obsolete, use vers=2 instead");
+				if (forceversion && forceversion != 2)
 					errx(1, "conflicting version options");
-				force2 = 1;
+				forceversion = 2;
 				nfsargsp->flags &= ~NFSMNT_NFSV3;
 			}
 			if (altflags & ALTF_RDIRPLUS)
 				nfsargsp->flags |= NFSMNT_RDIRPLUS;
-			if (altflags & ALTF_MNTUDP)
-				mnttcp_ok = 0;
+			if (altflags & ALTF_MNTUDP) {
+				warnx("mntudp is obsolete, use mntproto=udp "
+					"instead");
+				if (*mntproto && strcmp(mntproto, "udp"))
+					errx(1, "conflicting mntproto options");
+				strlcpy(mntproto,"udp",sizeof(mntproto));
+			}
 			if (altflags & ALTF_NORESPORT)
 				nfsargsp->flags &= ~NFSMNT_RESVPORT;
 #ifdef ISO
@@ -355,16 +371,98 @@
 				nfsargsp->sotype = SOCK_SEQPACKET;
 #endif
 			if (altflags & ALTF_NQNFS) {
-				if (force2)
+				if (forceversion && forceversion != 3)
 					errx(1, "nqnfs only available with v3");
-				force3 = 1;
+				forceversion = 3;
 				nfsargsp->flags |= NFSMNT_NQNFS;
 			}
 			if (altflags & ALTF_SOFT)
 				nfsargsp->flags |= NFSMNT_SOFT;
 			if (altflags & ALTF_TCP) {
+				warnx("tcp is obsolete, use proto=tcp instead");
 				nfsargsp->sotype = SOCK_STREAM;
-				nfsproto = IPPROTO_TCP;
+			}
+			/* proto=<netid> should be the suggested way of
+			 * selecting a specific mount/nfs protocol, since its
+			 * a more generic way, which allows easy addition
+			 * of new protocols, without having to add any
+			 * further arguments/options to mount_nfs. ALTF_TCP
+			 * should therefore be considered obsolete (maybe
+			 * ALTF_MNTUDP too, but i'm not sure).
+			 * - Benedikt Meurer
+			 */
+			if (altflags & ALTF_PROTO) {
+				struct netconfig *nc;
+
+				if (getoptval(optarg, "proto", buffer,
+				    sizeof(buffer)) == NULL)
+					errx(1, "invalid proto option");
+
+				if ((nc = getnetconfigent (buffer)) == NULL)
+					errx(1, "%s: %s", buffer, nc_sperror());
+
+				/* XXX Is there any generic way? */ 
+				if (!strcmp(nc->nc_protofmly, NC_INET))
+					forcefamily = PF_INET;
+				else if (!strcmp(nc->nc_protofmly, NC_INET6))
+					forcefamily = PF_INET6;
+				else
+					errx(1, "%s: protocol family not "
+						"supported", nc->nc_protofmly);
+
+				/* XXX Is there any generic way? */
+				if (!strcmp(nc->nc_proto, NC_TCP))
+					nfsargsp->sotype = SOCK_STREAM;
+				else if (!strcmp(nc->nc_proto, NC_UDP))
+					nfsargsp->sotype = SOCK_DGRAM;
+				else
+					errx(1, "%s: unsupported protocol",
+						nc->nc_proto);
+
+				freenetconfigent(nc);
+			}
+			/* vers=<NFS version number> should be the suggested
+			 * way of specifing NFS protocol version to use, else
+			 * we'll probably end up with nfsv2,nfsv3,nfsv4,...
+			 * options. I think the Solaris like vers option is
+			 * easier to handle.
+			 * - Benedikt Meurer
+			 */
+			if (altflags & ALTF_VERS) {
+				if (getoptval(optarg, "vers", buffer,
+				    sizeof(buffer)) == NULL)
+					errx(1, "invalid vers option");
+
+				num = strtol(buffer, &p, 10);
+				if (*p || num <= 0)
+					errx(1, "illegal version -- %s",buffer);
+
+				warnx("vers: %d, forceversion: %d", num, forceversion);
+				if (forceversion && forceversion != num)
+					errx(1,"conflicting version options");
+
+				switch (num) {
+				case 3:
+					forceversion = 3;
+					nfsargsp->flags |= NFSMNT_NFSV3;
+					break;
+				case 2:
+					forceversion = 2;
+					nfsargsp->flags &= ~NFSMNT_NFSV3;
+					break;
+				default:
+					errx(1,"unsupported version -- %s",
+						buffer);
+				}
+			}
+			if (altflags & ALTF_MNTPROTO) {
+				if (getoptval(optarg,"mntproto",buffer,
+				    sizeof(buffer)) == NULL) {
+					errx(1,"invalid mntproto option");
+				}
+				if (*mntproto && strcmp(mntproto, buffer))
+					errx(1, "conflicting mntproto options");
+				strlcpy(mntproto, buffer, sizeof(mntproto));
 			}
 			altflags = 0;
 			break;
@@ -375,9 +473,9 @@
 			nfsargsp->flags &= ~NFSMNT_RESVPORT;
 			break;
 		case 'q':
-			if (force2)
+			if (forceversion && forceversion != 3)
 				errx(1, "nqnfs only available with v3");
-			force3 = 1;
+			forceversion = 3;
 			nfsargsp->flags |= NFSMNT_NQNFS;
 			break;
 		case 'R':
@@ -403,7 +501,6 @@
 			break;
 		case 'T':
 			nfsargsp->sotype = SOCK_STREAM;
-			nfsproto = IPPROTO_TCP;
 			break;
 		case 't':
 			num = strtol(optarg, &p, 10);
@@ -430,7 +527,9 @@
 			nfsargsp->flags |= NFSMNT_XLATECOOKIE;
 			break;
 		case 'U':
-			mnttcp_ok = 0;
+			if (*mntproto && strcmp(mntproto, "udp"))
+				errx(1, "conflicting mntproto options");
+			strlcpy(mntproto, "udp", sizeof(mntproto));
 			break;
 		default:
 			usage();
@@ -455,7 +554,7 @@
 	if ((retval = mount(MOUNT_NFS, name, mntflags, nfsargsp))) {
 		/* Did we just default to v3 on a v2-only kernel?
 		 * If so, default to v2 & try again */
-		if ((errno == EPROGMISMATCH) && !force3) {
+		if ((errno == EPROGMISMATCH) && forceversion < 3) {
 			nfsargsp->flags &= ~NFSMNT_NFSV3;
 			retval = mount(MOUNT_NFS, name, mntflags, nfsargsp);
 		}
@@ -575,7 +674,7 @@
 	char fbuf[2048];
 
 	(void)snprintb(fbuf, sizeof(fbuf), NFSMNT_BITS, nfsargsp->flags);
-	printf("version=%d, addrlen=%d, sotype=%d, proto=%d, fhsize=%d, "
+	(void)printf("version=%d, addrlen=%d, sotype=%d, proto=%d, fhsize=%d, "
 	    "flags=%s, wsize=%d, rsize=%d, readdirsize=%d, timeo=%d, "
 	    "retrans=%d, maxgrouplist=%d, readahead=%d, leaseterm=%d, "
 	    "deadthresh=%d\n",
@@ -673,6 +772,7 @@
 	memset(&hints, 0, sizeof hints);
 	hints.ai_flags = AI_NUMERICHOST;
 	hints.ai_socktype = nfsargsp->sotype;
+	hints.ai_family = forcefamily;
 	if (getaddrinfo(hostp, "nfs", &hints, &ai_nfs) == 0) {
 		if ((nfsargsp->flags & NFSMNT_KERB)) {
 			hints.ai_flags = 0;
@@ -702,12 +802,17 @@
 	}
 #endif /* NFSKERB */
 
-	if (force2) {
+	switch (forceversion) {
+	case 2:
 		nfsvers = NFS_VER2;
 		mntvers = RPCMNT_VER1;
-	} else {
+		break;
+	case 0: case 3:	/* defaults to NFSv3 */
 		nfsvers = NFS_VER3;
 		mntvers = RPCMNT_VER3;
+		break;
+	default:
+		errx(1, "unsupported nfs version \'%d\'", forceversion);
 	}
 	orgcnt = retrycnt;
 	nfhret.stat = EACCES;	/* Mark not yet successful */
@@ -729,6 +834,10 @@
 			netid = "udp";
 	}
 
+	/* mount protocol defaults to connection protocol */
+	if (*mntproto == '\0')
+		strlcpy(mntproto, netid, sizeof(mntproto));
+
 	nconf = getnetconfigent(netid);
 
 tryagain:
@@ -757,7 +866,7 @@
 			 * socket.
 			 */
 			clp = clnt_tp_create(hostp, RPCPROG_MNT, mntvers,
-			     mnttcp_ok ? nconf : getnetconfigent("udp"));
+				getnetconfigent(mntproto));
 			if (clp == NULL) {
 				if ((opflags & ISBGRND) == 0) {
 					clnt_pcreateerror(
@@ -778,7 +887,8 @@
 				    xdr_dir, spec, xdr_fh, &nfhret, try);
 				switch (clnt_stat) {
 				case RPC_PROGVERSMISMATCH:
-					if (nfsvers == NFS_VER3 && !force3) {
+					if (nfsvers == NFS_VER3 &&
+						forceversion < 3) {
 						nfsvers = NFS_VER2;
 						mntvers = RPCMNT_VER1;
 						nfsargsp->flags &=
@@ -898,6 +1008,41 @@
 		return (1);
 	};
 	return (0);
+}
+
+static char *
+getoptval(options, option, bufp, len)
+	const char *options;
+	const char *option;
+	char *bufp;
+	size_t len;
+{
+	/* XXX shouldn't we merge this code with getmntopts? Maybe using a
+	 * callback function? Or we could add something like m_wantval and
+	 * m_valuep to the mntopts struct.
+	 */
+	char *optbuf, *opt, *p;
+
+	if ((optbuf = strdup(options)) == NULL)
+		err(1, NULL);
+
+	for (opt = optbuf; (opt = strtok(opt, ",")) != NULL; opt = NULL) {
+		if ((p = strchr(opt, '=')) == NULL) {
+			free(optbuf);
+			return NULL;
+		}
+
+		*p++ = '\0';
+
+		if (!strcasecmp(opt, option)) {
+			strlcpy(bufp, p, len);
+			free(optbuf);
+			return bufp;
+		}
+	}
+
+	free(optbuf);
+	return NULL;
 }
 
 static void