xref: /freebsd/sbin/mount_nfs/mount_nfs.c (revision 1e413cf93298b5b97441a21d9a50fdcd0ee9945e)
1 /*
2  * copyright (c) 2003
3  * the regents of the university of michigan
4  * all rights reserved
5  *
6  * permission is granted to use, copy, create derivative works and redistribute
7  * this software and such derivative works for any purpose, so long as the name
8  * of the university of michigan is not used in any advertising or publicity
9  * pertaining to the use or distribution of this software without specific,
10  * written prior authorization.  if the above copyright notice or any other
11  * identification of the university of michigan is included in any copy of any
12  * portion of this software, then the disclaimer below must also be included.
13  *
14  * this software is provided as is, without representation from the university
15  * of michigan as to its fitness for any purpose, and without warranty by the
16  * university of michigan of any kind, either express or implied, including
17  * without limitation the implied warranties of merchantability and fitness for
18  * a particular purpose. the regents of the university of michigan shall not be
19  * liable for any damages, including special, indirect, incidental, or
20  * consequential damages, with respect to any claim arising out of or in
21  * connection with the use of the software, even if it has been or is hereafter
22  * advised of the possibility of such damages.
23  */
24 
25 /*
26  * Copyright (c) 1992, 1993, 1994
27  *	The Regents of the University of California.  All rights reserved.
28  *
29  * This code is derived from software contributed to Berkeley by
30  * Rick Macklem at The University of Guelph.
31  *
32  * Redistribution and use in source and binary forms, with or without
33  * modification, are permitted provided that the following conditions
34  * are met:
35  * 1. Redistributions of source code must retain the above copyright
36  *    notice, this list of conditions and the following disclaimer.
37  * 2. Redistributions in binary form must reproduce the above copyright
38  *    notice, this list of conditions and the following disclaimer in the
39  *    documentation and/or other materials provided with the distribution.
40  * 4. Neither the name of the University nor the names of its contributors
41  *    may be used to endorse or promote products derived from this software
42  *    without specific prior written permission.
43  *
44  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
45  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
46  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
47  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
48  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
49  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
50  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
51  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
52  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
53  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
54  * SUCH DAMAGE.
55  */
56 
57 #if 0
58 #ifndef lint
59 static const char copyright[] =
60 "@(#) Copyright (c) 1992, 1993, 1994\n\
61 	The Regents of the University of California.  All rights reserved.\n";
62 #endif /* not lint */
63 
64 #ifndef lint
65 static char sccsid[] = "@(#)mount_nfs.c	8.11 (Berkeley) 5/4/95";
66 #endif /* not lint */
67 #endif
68 #include <sys/cdefs.h>
69 __FBSDID("$FreeBSD$");
70 
71 #include <sys/param.h>
72 #include <sys/mount.h>
73 #include <sys/socket.h>
74 #include <sys/stat.h>
75 #include <sys/syslog.h>
76 #include <sys/uio.h>
77 
78 #include <rpc/rpc.h>
79 #include <rpc/pmap_clnt.h>
80 #include <rpc/pmap_prot.h>
81 
82 #include <nfs/rpcv2.h>
83 #include <nfs/nfsproto.h>
84 #include <nfsclient/nfs.h>
85 #include <nfsclient/nfsargs.h>
86 
87 #include <arpa/inet.h>
88 
89 #include <ctype.h>
90 #include <err.h>
91 #include <errno.h>
92 #include <fcntl.h>
93 #include <netdb.h>
94 #include <stdio.h>
95 #include <stdlib.h>
96 #include <string.h>
97 #include <strings.h>
98 #include <sysexits.h>
99 #include <unistd.h>
100 
101 #include "mntopts.h"
102 #include "mounttab.h"
103 
104 #define	ALTF_BG		0x1
105 #define ALTF_NOCONN	0x2
106 #define ALTF_DUMBTIMR	0x4
107 #define ALTF_INTR	0x8
108 #define ALTF_NFSV3	0x20
109 #define ALTF_RDIRPLUS	0x40
110 #define ALTF_MNTUDP	0x80
111 #define ALTF_RESVPORT	0x100
112 #define ALTF_SEQPACKET	0x200
113 #define ALTF_SOFT	0x800
114 #define ALTF_TCP	0x1000
115 #define ALTF_PORT	0x2000
116 #define ALTF_NFSV2	0x4000
117 #define ALTF_ACREGMIN	0x8000
118 #define ALTF_ACREGMAX	0x10000
119 #define ALTF_ACDIRMIN	0x20000
120 #define ALTF_ACDIRMAX	0x40000
121 #define ALTF_NOLOCKD	0x80000
122 #define ALTF_NOINET4	0x100000
123 #define ALTF_NOINET6	0x200000
124 
125 struct mntopt mopts[] = {
126 	MOPT_STDOPTS,
127 	MOPT_FORCE,
128 	MOPT_UPDATE,
129 	MOPT_ASYNC,
130 	{ "bg", 0, ALTF_BG, 1 },
131 	{ "fg", 1, ALTF_BG, 1 },
132 	{ "conn", 1, ALTF_NOCONN, 1 },
133 	{ "dumbtimer", 0, ALTF_DUMBTIMR, 1 },
134 	{ "intr", 0, ALTF_INTR, 1 },
135 	{ "nfsv3", 0, ALTF_NFSV3, 1 },
136 	{ "rdirplus", 0, ALTF_RDIRPLUS, 1 },
137 	{ "mntudp", 0, ALTF_MNTUDP, 1 },
138 	{ "resvport", 0, ALTF_RESVPORT, 1 },
139 	{ "soft", 0, ALTF_SOFT, 1 },
140 	{ "hard", 1, ALTF_SOFT, 1 },
141 	{ "tcp", 0, ALTF_TCP, 1 },
142 	{ "port=", 0, ALTF_PORT, 1 },
143 	{ "nfsv2", 0, ALTF_NFSV2, 1 },
144 	{ "acregmin=", 0, ALTF_ACREGMIN, 1 },
145 	{ "acregmax=", 0, ALTF_ACREGMAX, 1 },
146 	{ "acdirmin=", 0, ALTF_ACDIRMIN, 1 },
147 	{ "acdirmax=", 0, ALTF_ACDIRMAX, 1 },
148 	{ "lockd", 1, ALTF_NOLOCKD, 1 },
149 	{ "inet4", 1, ALTF_NOINET4, 1 },
150 	{ "inet6", 1, ALTF_NOINET6, 1 },
151 	MOPT_END
152 };
153 
154 struct nfs_args nfsdefargs = {
155 	NFS_ARGSVERSION,
156 	NULL,
157 	sizeof (struct sockaddr_in),
158 	SOCK_DGRAM,
159 	0,
160 	NULL,
161 	0,
162 	NFSMNT_RESVPORT,
163 	NFS_WSIZE,
164 	NFS_RSIZE,
165 	NFS_READDIRSIZE,
166 	10,
167 	NFS_RETRANS,
168 	NFS_MAXGRPS,
169 	NFS_DEFRAHEAD,
170 	0,			/* was: NQ_DEFLEASE */
171 	NFS_MAXDEADTHRESH,	/* was: NQ_DEADTHRESH */
172 	NULL,
173 	/* args version 4 */
174 	NFS_MINATTRTIMO,
175 	NFS_MAXATTRTIMO,
176 	NFS_MINDIRATTRTIMO,
177 	NFS_MAXDIRATTRTIMO,
178 };
179 
180 /* Table for af,sotype -> netid conversions. */
181 struct nc_protos {
182 	char *netid;
183 	int af;
184 	int sotype;
185 } nc_protos[] = {
186 	{"udp",		AF_INET,	SOCK_DGRAM},
187 	{"tcp",		AF_INET,	SOCK_STREAM},
188 	{"udp6",	AF_INET6,	SOCK_DGRAM},
189 	{"tcp6",	AF_INET6,	SOCK_STREAM},
190 	{NULL,		0,		0}
191 };
192 
193 struct nfhret {
194 	u_long		stat;
195 	long		vers;
196 	long		auth;
197 	long		fhsize;
198 	u_char		nfh[NFSX_V3FHMAX];
199 };
200 #define	BGRND	1
201 #define	ISBGRND	2
202 #define	OF_NOINET4	4
203 #define	OF_NOINET6	8
204 int retrycnt = -1;
205 int opflags = 0;
206 int nfsproto = IPPROTO_UDP;
207 int mnttcp_ok = 1;
208 char *portspec = NULL;	/* Server nfs port; NULL means look up via rpcbind. */
209 enum mountmode {
210 	ANY,
211 	V2,
212 	V3,
213 	V4
214 } mountmode = ANY;
215 
216 /* Return codes for nfs_tryproto. */
217 enum tryret {
218 	TRYRET_SUCCESS,
219 	TRYRET_TIMEOUT,		/* No response received. */
220 	TRYRET_REMOTEERR,	/* Error received from remote server. */
221 	TRYRET_LOCALERR		/* Local failure. */
222 };
223 
224 int	getnfsargs(char *, struct nfs_args *);
225 int	getnfs4args(char *, struct nfs_args *);
226 /* void	set_rpc_maxgrouplist(int); */
227 struct netconfig *getnetconf_cached(const char *netid);
228 char	*netidbytype(int af, int sotype);
229 void	usage(void) __dead2;
230 int	xdr_dir(XDR *, char *);
231 int	xdr_fh(XDR *, struct nfhret *);
232 enum tryret nfs_tryproto(struct nfs_args *nfsargsp, struct addrinfo *ai,
233     char *hostp, char *spec, char **errstr);
234 enum tryret nfs4_tryproto(struct nfs_args *nfsargsp, struct addrinfo *ai,
235     char *hostp, char *spec, char **errstr);
236 enum tryret returncode(enum clnt_stat stat, struct rpc_err *rpcerr);
237 
238 /*
239  * Used to set mount flags with getmntopts.  Call with dir=TRUE to
240  * initialize altflags from the current mount flags.  Call with
241  * dir=FALSE to update mount flags with the new value of altflags after
242  * the call to getmntopts.
243  */
244 static void
245 set_flags(int* altflags, int* nfsflags, int dir)
246 {
247 #define F2(af, nf)					\
248 	if (dir) {					\
249 		if (*nfsflags & NFSMNT_##nf)		\
250 			*altflags |= ALTF_##af;		\
251 		else					\
252 			*altflags &= ~ALTF_##af;	\
253 	} else {					\
254 		if (*altflags & ALTF_##af)		\
255 			*nfsflags |= NFSMNT_##nf;	\
256 		else					\
257 			*nfsflags &= ~NFSMNT_##nf;	\
258 	}
259 #define F(f)	F2(f,f)
260 
261 	F(NOCONN);
262 	F(DUMBTIMR);
263 	F2(INTR, INT);
264 	F(RDIRPLUS);
265 	F(RESVPORT);
266 	F(SOFT);
267 	F(ACREGMIN);
268 	F(ACREGMAX);
269 	F(ACDIRMIN);
270 	F(ACDIRMAX);
271 	F(NOLOCKD);
272 
273 #undef F
274 #undef F2
275 }
276 
277 int
278 main(int argc, char *argv[])
279 {
280 	int c;
281 	struct nfs_args *nfsargsp;
282 	struct nfs_args nfsargs;
283 	struct iovec *iov;
284 	int mntflags, altflags, num;
285 	int iovlen;
286 	char *name, *p, *spec, *fstype;
287 	char mntpath[MAXPATHLEN], errmsg[255];
288 
289 	mntflags = 0;
290 	altflags = 0;
291 	nfsargs = nfsdefargs;
292 	nfsargsp = &nfsargs;
293 	iov = NULL;
294 	iovlen = 0;
295 	memset(errmsg, 0, sizeof(errmsg));
296 
297 	fstype = strrchr(argv[0], '_');
298 	if (fstype == NULL)
299 		errx(EX_USAGE, "argv[0] must end in _fstype");
300 
301 	++fstype;
302 
303 	if (strcmp(fstype, "nfs4") == 0) {
304 		nfsproto = IPPROTO_TCP;
305 		portspec = "2049";
306 		nfsdefargs.sotype = SOCK_STREAM;
307 		mountmode = V4;
308 	}
309 
310 	while ((c = getopt(argc, argv,
311 	    "234a:bcdD:g:I:iLlNo:PR:r:sTt:w:x:U")) != -1)
312 		switch (c) {
313 		case '2':
314 			mountmode = V2;
315 			break;
316 		case '3':
317 			mountmode = V3;
318 			break;
319 		case '4':
320 			mountmode = V4;
321 			fstype = "nfs4";
322 			break;
323 		case 'a':
324 			num = strtol(optarg, &p, 10);
325 			if (*p || num < 0)
326 				errx(1, "illegal -a value -- %s", optarg);
327 			nfsargsp->readahead = num;
328 			nfsargsp->flags |= NFSMNT_READAHEAD;
329 			break;
330 		case 'b':
331 			opflags |= BGRND;
332 			break;
333 		case 'c':
334 			nfsargsp->flags |= NFSMNT_NOCONN;
335 			break;
336 		case 'D':
337 			num = strtol(optarg, &p, 10);
338 			if (*p || num <= 0)
339 				errx(1, "illegal -D value -- %s", optarg);
340 			nfsargsp->deadthresh = num;
341 			nfsargsp->flags |= NFSMNT_DEADTHRESH;
342 			break;
343 		case 'd':
344 			nfsargsp->flags |= NFSMNT_DUMBTIMR;
345 			break;
346 #if 0 /* XXXX */
347 		case 'g':
348 			num = strtol(optarg, &p, 10);
349 			if (*p || num <= 0)
350 				errx(1, "illegal -g value -- %s", optarg);
351 			set_rpc_maxgrouplist(num);
352 			nfsargsp->maxgrouplist = num;
353 			nfsargsp->flags |= NFSMNT_MAXGRPS;
354 			break;
355 #endif
356 		case 'I':
357 			num = strtol(optarg, &p, 10);
358 			if (*p || num <= 0)
359 				errx(1, "illegal -I value -- %s", optarg);
360 			nfsargsp->readdirsize = num;
361 			nfsargsp->flags |= NFSMNT_READDIRSIZE;
362 			break;
363 		case 'i':
364 			nfsargsp->flags |= NFSMNT_INT;
365 			break;
366 		case 'L':
367 			nfsargsp->flags |= NFSMNT_NOLOCKD;
368 			break;
369 		case 'l':
370 			nfsargsp->flags |= NFSMNT_RDIRPLUS;
371 			break;
372 		case 'N':
373 			nfsargsp->flags &= ~NFSMNT_RESVPORT;
374 			break;
375 		case 'o':
376 			altflags = 0;
377 			set_flags(&altflags, &nfsargsp->flags, TRUE);
378 			if (mountmode == V2)
379 				altflags |= ALTF_NFSV2;
380 			else if (mountmode == V3)
381 				altflags |= ALTF_NFSV3;
382 			getmntopts(optarg, mopts, &mntflags, &altflags);
383 			set_flags(&altflags, &nfsargsp->flags, FALSE);
384 			/*
385 			 * Handle altflags which don't map directly to
386 			 * mount flags.
387 			 */
388 			if (altflags & ALTF_BG)
389 				opflags |= BGRND;
390 			if (altflags & ALTF_NOINET4)
391 				opflags |= OF_NOINET4;
392 			if (altflags & ALTF_NOINET6)
393 				opflags |= OF_NOINET6;
394 			if (altflags & ALTF_MNTUDP)
395 				mnttcp_ok = 0;
396 			if (altflags & ALTF_TCP) {
397 				nfsargsp->sotype = SOCK_STREAM;
398 				nfsproto = IPPROTO_TCP;
399 			}
400 			if (altflags & ALTF_PORT) {
401 				/*
402 				 * XXX Converting from a string to an int
403 				 * and back again is silly, and we should
404 				 * allow /etc/services names.
405 				 */
406 				p = strstr(optarg, "port=");
407 				if (p) {
408 					asprintf(&portspec, "%d",
409 					    atoi(p + 5));
410 					if (portspec == NULL)
411 						err(1, "asprintf");
412 				}
413 			}
414 			mountmode = ANY;
415 			if (altflags & ALTF_NFSV2)
416 				mountmode = V2;
417 			if (altflags & ALTF_NFSV3)
418 				mountmode = V3;
419 			if (altflags & ALTF_ACREGMIN) {
420 				p = strstr(optarg, "acregmin=");
421 				if (p)
422 					nfsargsp->acregmin = atoi(p + 9);
423 			}
424 			if (altflags & ALTF_ACREGMAX) {
425 				p = strstr(optarg, "acregmax=");
426 				if (p)
427 					nfsargsp->acregmax = atoi(p + 9);
428 			}
429 			if (altflags & ALTF_ACDIRMIN) {
430 				p = strstr(optarg, "acdirmin=");
431 				if (p)
432 					nfsargsp->acdirmin = atoi(p + 9);
433 			}
434 			if (altflags & ALTF_ACDIRMAX) {
435 				p = strstr(optarg, "acdirmax=");
436 				if (p)
437 					nfsargsp->acdirmax = atoi(p + 9);
438 			}
439 			break;
440 		case 'P':
441 			/* obsolete for NFSMNT_RESVPORT, now default */
442 			break;
443 		case 'R':
444 			num = strtol(optarg, &p, 10);
445 			if (*p || num < 0)
446 				errx(1, "illegal -R value -- %s", optarg);
447 			retrycnt = num;
448 			break;
449 		case 'r':
450 			num = strtol(optarg, &p, 10);
451 			if (*p || num <= 0)
452 				errx(1, "illegal -r value -- %s", optarg);
453 			nfsargsp->rsize = num;
454 			nfsargsp->flags |= NFSMNT_RSIZE;
455 			break;
456 		case 's':
457 			nfsargsp->flags |= NFSMNT_SOFT;
458 			break;
459 		case 'T':
460 			nfsargsp->sotype = SOCK_STREAM;
461 			nfsproto = IPPROTO_TCP;
462 			break;
463 		case 't':
464 			num = strtol(optarg, &p, 10);
465 			if (*p || num <= 0)
466 				errx(1, "illegal -t value -- %s", optarg);
467 			nfsargsp->timeo = num;
468 			nfsargsp->flags |= NFSMNT_TIMEO;
469 			break;
470 		case 'w':
471 			num = strtol(optarg, &p, 10);
472 			if (*p || num <= 0)
473 				errx(1, "illegal -w value -- %s", optarg);
474 			nfsargsp->wsize = num;
475 			nfsargsp->flags |= NFSMNT_WSIZE;
476 			break;
477 		case 'x':
478 			num = strtol(optarg, &p, 10);
479 			if (*p || num <= 0)
480 				errx(1, "illegal -x value -- %s", optarg);
481 			nfsargsp->retrans = num;
482 			nfsargsp->flags |= NFSMNT_RETRANS;
483 			break;
484 		case 'U':
485 			mnttcp_ok = 0;
486 			nfsargsp->sotype = SOCK_DGRAM;
487 			nfsproto = IPPROTO_UDP;
488 			break;
489 		default:
490 			usage();
491 			break;
492 		}
493 	argc -= optind;
494 	argv += optind;
495 
496 	if (argc != 2) {
497 		usage();
498 		/* NOTREACHED */
499 	}
500 
501 	spec = *argv++;
502 	name = *argv;
503 
504 	if (retrycnt == -1)
505 		/* The default is to keep retrying forever. */
506 		retrycnt = 0;
507 
508 	if (mountmode == V4) {
509 		if (!getnfs4args(spec, nfsargsp))
510 			exit(1);
511 	} else {
512 		if (!getnfsargs(spec, nfsargsp))
513 			exit(1);
514 	}
515 
516 	/* resolve the mountpoint with realpath(3) */
517 	(void)checkpath(name, mntpath);
518 
519 	build_iovec(&iov, &iovlen, "nfs_args", nfsargsp, sizeof(*nfsargsp));
520 	build_iovec(&iov, &iovlen, "fstype", fstype, (size_t)-1);
521 	build_iovec(&iov, &iovlen, "fspath", mntpath, (size_t)-1);
522 	build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg));
523 
524 	if (nmount(iov, iovlen, mntflags))
525 		err(1, "%s, %s", mntpath, errmsg);
526 
527 	exit(0);
528 }
529 
530 int
531 getnfsargs(char *spec, struct nfs_args *nfsargsp)
532 {
533 	struct addrinfo hints, *ai_nfs, *ai;
534 	enum tryret ret;
535 	int ecode, speclen, remoteerr;
536 	char *hostp, *delimp, *errstr;
537 	size_t len;
538 	static char nam[MNAMELEN + 1];
539 
540 	if ((delimp = strrchr(spec, ':')) != NULL) {
541 		hostp = spec;
542 		spec = delimp + 1;
543 	} else if ((delimp = strrchr(spec, '@')) != NULL) {
544 		warnx("path@server syntax is deprecated, use server:path");
545 		hostp = delimp + 1;
546 	} else {
547 		warnx("no <host>:<dirpath> nfs-name");
548 		return (0);
549 	}
550 	*delimp = '\0';
551 
552 	/*
553 	 * If there has been a trailing slash at mounttime it seems
554 	 * that some mountd implementations fail to remove the mount
555 	 * entries from their mountlist while unmounting.
556 	 */
557 	for (speclen = strlen(spec);
558 		speclen > 1 && spec[speclen - 1] == '/';
559 		speclen--)
560 		spec[speclen - 1] = '\0';
561 	if (strlen(hostp) + strlen(spec) + 1 > MNAMELEN) {
562 		warnx("%s:%s: %s", hostp, spec, strerror(ENAMETOOLONG));
563 		return (0);
564 	}
565 	/* Make both '@' and ':' notations equal */
566 	if (*hostp != '\0') {
567 		len = strlen(hostp);
568 		memmove(nam, hostp, len);
569 		nam[len] = ':';
570 		memmove(nam + len + 1, spec, speclen);
571 		nam[len + speclen + 1] = '\0';
572 	}
573 
574 	/*
575 	 * Handle an internet host address.
576 	 */
577 	memset(&hints, 0, sizeof hints);
578 	hints.ai_flags = AI_NUMERICHOST;
579 	hints.ai_socktype = nfsargsp->sotype;
580 	if (getaddrinfo(hostp, portspec, &hints, &ai_nfs) != 0) {
581 		hints.ai_flags = 0;
582 		if ((ecode = getaddrinfo(hostp, portspec, &hints, &ai_nfs))
583 		    != 0) {
584 			if (portspec == NULL)
585 				errx(1, "%s: %s", hostp, gai_strerror(ecode));
586 			else
587 				errx(1, "%s:%s: %s", hostp, portspec,
588 				    gai_strerror(ecode));
589 			return (0);
590 		}
591 	}
592 
593 	ret = TRYRET_LOCALERR;
594 	for (;;) {
595 		/*
596 		 * Try each entry returned by getaddrinfo(). Note the
597 		 * occurence of remote errors by setting `remoteerr'.
598 		 */
599 		remoteerr = 0;
600 		for (ai = ai_nfs; ai != NULL; ai = ai->ai_next) {
601 			if ((ai->ai_family == AF_INET6) &&
602 			    (opflags & OF_NOINET6))
603 				continue;
604 			if ((ai->ai_family == AF_INET) &&
605 			    (opflags & OF_NOINET4))
606 				continue;
607 			ret = nfs_tryproto(nfsargsp, ai, hostp, spec, &errstr);
608 			if (ret == TRYRET_SUCCESS)
609 				break;
610 			if (ret != TRYRET_LOCALERR)
611 				remoteerr = 1;
612 			if ((opflags & ISBGRND) == 0)
613 				fprintf(stderr, "%s\n", errstr);
614 		}
615 		if (ret == TRYRET_SUCCESS)
616 			break;
617 
618 		/* Exit if all errors were local. */
619 		if (!remoteerr)
620 			exit(1);
621 
622 		/*
623 		 * If retrycnt == 0, we are to keep retrying forever.
624 		 * Otherwise decrement it, and exit if it hits zero.
625 		 */
626 		if (retrycnt != 0 && --retrycnt == 0)
627 			exit(1);
628 
629 		if ((opflags & (BGRND | ISBGRND)) == BGRND) {
630 			warnx("Cannot immediately mount %s:%s, backgrounding",
631 			    hostp, spec);
632 			opflags |= ISBGRND;
633 			if (daemon(0, 0) != 0)
634 				err(1, "daemon");
635 		}
636 		sleep(60);
637 	}
638 	freeaddrinfo(ai_nfs);
639 	nfsargsp->hostname = nam;
640 	/* Add mounted file system to PATH_MOUNTTAB */
641 	if (!add_mtab(hostp, spec))
642 		warnx("can't update %s for %s:%s", PATH_MOUNTTAB, hostp, spec);
643 	return (1);
644 }
645 
646 
647 int
648 getnfs4args(char *spec, struct nfs_args *nfsargsp)
649 {
650 	struct addrinfo hints, *ai_nfs, *ai;
651 	enum tryret ret;
652 	int ecode, speclen, remoteerr;
653 	char *hostp, *delimp, *errstr;
654 	size_t len;
655 	static char nam[MNAMELEN + 1];
656 
657 	if ((delimp = strrchr(spec, ':')) != NULL) {
658 		hostp = spec;
659 		spec = delimp + 1;
660 	} else if ((delimp = strrchr(spec, '@')) != NULL) {
661 		warnx("path@server syntax is deprecated, use server:path");
662 		hostp = delimp + 1;
663 	} else {
664 		warnx("no <host>:<dirpath> nfs-name");
665 		return (0);
666 	}
667 	*delimp = '\0';
668 
669 	/*
670 	 * If there has been a trailing slash at mounttime it seems
671 	 * that some mountd implementations fail to remove the mount
672 	 * entries from their mountlist while unmounting.
673 	 */
674 	for (speclen = strlen(spec);
675 		speclen > 1 && spec[speclen - 1] == '/';
676 		speclen--)
677 		spec[speclen - 1] = '\0';
678 	if (strlen(hostp) + strlen(spec) + 1 > MNAMELEN) {
679 		warnx("%s:%s: %s", hostp, spec, strerror(ENAMETOOLONG));
680 		return (0);
681 	}
682 	/* Make both '@' and ':' notations equal */
683 	if (*hostp != '\0') {
684 		len = strlen(hostp);
685 		memmove(nam, hostp, len);
686 		nam[len] = ':';
687 		memmove(nam + len + 1, spec, speclen);
688 		nam[len + speclen + 1] = '\0';
689 	}
690 
691 	/*
692 	 * Handle an internet host address.
693 	 */
694 	memset(&hints, 0, sizeof hints);
695 	hints.ai_flags = AI_NUMERICHOST;
696 	hints.ai_socktype = nfsargsp->sotype;
697 	if (getaddrinfo(hostp, portspec, &hints, &ai_nfs) != 0) {
698 		hints.ai_flags = 0;
699 		if ((ecode = getaddrinfo(hostp, portspec, &hints, &ai_nfs))
700 		    != 0) {
701 			if (portspec == NULL)
702 				errx(1, "%s: %s", hostp, gai_strerror(ecode));
703 			else
704 				errx(1, "%s:%s: %s", hostp, portspec,
705 				    gai_strerror(ecode));
706 			return (0);
707 		}
708 	}
709 
710 	ret = TRYRET_LOCALERR;
711 	for (;;) {
712 		/*
713 		 * Try each entry returned by getaddrinfo(). Note the
714 		 * occurence of remote errors by setting `remoteerr'.
715 		 */
716 		remoteerr = 0;
717 		for (ai = ai_nfs; ai != NULL; ai = ai->ai_next) {
718 			if ((ai->ai_family == AF_INET6) &&
719 			    (opflags & OF_NOINET6))
720 				continue;
721 			if ((ai->ai_family == AF_INET) &&
722 			    (opflags & OF_NOINET4))
723 				continue;
724 			ret = nfs4_tryproto(nfsargsp, ai, hostp, spec, &errstr);
725 			if (ret == TRYRET_SUCCESS)
726 				break;
727 			if (ret != TRYRET_LOCALERR)
728 				remoteerr = 1;
729 			if ((opflags & ISBGRND) == 0)
730 				fprintf(stderr, "%s\n", errstr);
731 		}
732 		if (ret == TRYRET_SUCCESS)
733 			break;
734 
735 		/* Exit if all errors were local. */
736 		if (!remoteerr)
737 			exit(1);
738 
739 		/*
740 		 * If retrycnt == 0, we are to keep retrying forever.
741 		 * Otherwise decrement it, and exit if it hits zero.
742 		 */
743 		if (retrycnt != 0 && --retrycnt == 0)
744 			exit(1);
745 
746 		if ((opflags & (BGRND | ISBGRND)) == BGRND) {
747 			warnx("Cannot immediately mount %s:%s, backgrounding",
748 			    hostp, spec);
749 			opflags |= ISBGRND;
750 			if (daemon(0, 0) != 0)
751 				err(1, "daemon");
752 		}
753 		sleep(60);
754 	}
755 	freeaddrinfo(ai_nfs);
756 	nfsargsp->hostname = nam;
757 	/* Add mounted file system to PATH_MOUNTTAB */
758 	if (!add_mtab(hostp, spec))
759 		warnx("can't update %s for %s:%s", PATH_MOUNTTAB, hostp, spec);
760 	return (1);
761 }
762 
763 /*
764  * Try to set up the NFS arguments according to the address
765  * family, protocol (and possibly port) specified in `ai'.
766  *
767  * Returns TRYRET_SUCCESS if successful, or:
768  *   TRYRET_TIMEOUT		The server did not respond.
769  *   TRYRET_REMOTEERR		The server reported an error.
770  *   TRYRET_LOCALERR		Local failure.
771  *
772  * In all error cases, *errstr will be set to a statically-allocated string
773  * describing the error.
774  */
775 enum tryret
776 nfs_tryproto(struct nfs_args *nfsargsp, struct addrinfo *ai, char *hostp,
777     char *spec, char **errstr)
778 {
779 	static char errbuf[256];
780 	struct sockaddr_storage nfs_ss;
781 	struct netbuf nfs_nb;
782 	struct nfhret nfhret;
783 	struct timeval try;
784 	struct rpc_err rpcerr;
785 	CLIENT *clp;
786 	struct netconfig *nconf, *nconf_mnt;
787 	char *netid, *netid_mnt;
788 	int doconnect, nfsvers, mntvers;
789 	enum clnt_stat stat;
790 	enum mountmode trymntmode;
791 
792 	trymntmode = mountmode;
793 	errbuf[0] = '\0';
794 	*errstr = errbuf;
795 
796 	if ((netid = netidbytype(ai->ai_family, nfsargsp->sotype)) == NULL) {
797 		snprintf(errbuf, sizeof errbuf,
798 		    "af %d sotype %d not supported", ai->ai_family,
799 		    nfsargsp->sotype);
800 		return (TRYRET_LOCALERR);
801 	}
802 	if ((nconf = getnetconf_cached(netid)) == NULL) {
803 		snprintf(errbuf, sizeof errbuf, "%s: %s", netid, nc_sperror());
804 		return (TRYRET_LOCALERR);
805 	}
806 	/* The RPCPROG_MNT netid may be different. */
807 	if (mnttcp_ok) {
808 		netid_mnt = netid;
809 		nconf_mnt = nconf;
810 	} else {
811 		if ((netid_mnt = netidbytype(ai->ai_family, SOCK_DGRAM))
812 		     == NULL) {
813 			snprintf(errbuf, sizeof errbuf,
814 			    "af %d sotype SOCK_DGRAM not supported",
815 			     ai->ai_family);
816 			return (TRYRET_LOCALERR);
817 		}
818 		if ((nconf_mnt = getnetconf_cached(netid_mnt)) == NULL) {
819 			snprintf(errbuf, sizeof errbuf, "%s: %s", netid_mnt,
820 			    nc_sperror());
821 			return (TRYRET_LOCALERR);
822 		}
823 	}
824 
825 tryagain:
826 	if (trymntmode == V2) {
827 		nfsvers = 2;
828 		mntvers = 1;
829 	} else {
830 		nfsvers = 3;
831 		mntvers = 3;
832 	}
833 
834 	if (portspec != NULL) {
835 		/* `ai' contains the complete nfsd sockaddr. */
836 		nfs_nb.buf = ai->ai_addr;
837 		nfs_nb.len = nfs_nb.maxlen = ai->ai_addrlen;
838 	} else {
839 		/* Ask the remote rpcbind. */
840 		nfs_nb.buf = &nfs_ss;
841 		nfs_nb.len = nfs_nb.maxlen = sizeof nfs_ss;
842 
843 		if (!rpcb_getaddr(RPCPROG_NFS, nfsvers, nconf, &nfs_nb,
844 		    hostp)) {
845 			if (rpc_createerr.cf_stat == RPC_PROGVERSMISMATCH &&
846 			    trymntmode == ANY) {
847 				trymntmode = V2;
848 				goto tryagain;
849 			}
850 			snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s",
851 			    netid, hostp, spec,
852 			    clnt_spcreateerror("RPCPROG_NFS"));
853 			return (returncode(rpc_createerr.cf_stat,
854 			    &rpc_createerr.cf_error));
855 		}
856 	}
857 
858 	/* Check that the server (nfsd) responds on the port we have chosen. */
859 	clp = clnt_tli_create(RPC_ANYFD, nconf, &nfs_nb, RPCPROG_NFS, nfsvers,
860 	    0, 0);
861 	if (clp == NULL) {
862 		snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid,
863 		    hostp, spec, clnt_spcreateerror("nfsd: RPCPROG_NFS"));
864 		return (returncode(rpc_createerr.cf_stat,
865 		    &rpc_createerr.cf_error));
866 	}
867 	if (nfsargsp->sotype == SOCK_DGRAM &&
868 	    !(nfsargsp->flags & NFSMNT_NOCONN)) {
869 		/*
870 		 * Use connect(), to match what the kernel does. This
871 		 * catches cases where the server responds from the
872 		 * wrong source address.
873 		 */
874 		doconnect = 1;
875 		if (!clnt_control(clp, CLSET_CONNECT, (char *)&doconnect)) {
876 			clnt_destroy(clp);
877 			snprintf(errbuf, sizeof errbuf,
878 			    "[%s] %s:%s: CLSET_CONNECT failed", netid, hostp,
879 			    spec);
880 			return (TRYRET_LOCALERR);
881 		}
882 	}
883 
884 	try.tv_sec = 10;
885 	try.tv_usec = 0;
886 	stat = clnt_call(clp, NFSPROC_NULL, (xdrproc_t)xdr_void, NULL,
887 			 (xdrproc_t)xdr_void, NULL,
888 	    try);
889 	if (stat != RPC_SUCCESS) {
890 		if (stat == RPC_PROGVERSMISMATCH && trymntmode == ANY) {
891 			clnt_destroy(clp);
892 			trymntmode = V2;
893 			goto tryagain;
894 		}
895 		clnt_geterr(clp, &rpcerr);
896 		snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid,
897 		    hostp, spec, clnt_sperror(clp, "NFSPROC_NULL"));
898 		clnt_destroy(clp);
899 		return (returncode(stat, &rpcerr));
900 	}
901 	clnt_destroy(clp);
902 
903 	/* Send the RPCMNT_MOUNT RPC to get the root filehandle. */
904 	try.tv_sec = 10;
905 	try.tv_usec = 0;
906 	clp = clnt_tp_create(hostp, RPCPROG_MNT, mntvers, nconf_mnt);
907 	if (clp == NULL) {
908 		snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid_mnt,
909 		    hostp, spec, clnt_spcreateerror("RPCMNT: clnt_create"));
910 		return (returncode(rpc_createerr.cf_stat,
911 		    &rpc_createerr.cf_error));
912 	}
913 	clp->cl_auth = authsys_create_default();
914 	nfhret.auth = RPCAUTH_UNIX;
915 	nfhret.vers = mntvers;
916 	stat = clnt_call(clp, RPCMNT_MOUNT, (xdrproc_t)xdr_dir, spec,
917 			 (xdrproc_t)xdr_fh, &nfhret,
918 	    try);
919 	auth_destroy(clp->cl_auth);
920 	if (stat != RPC_SUCCESS) {
921 		if (stat == RPC_PROGVERSMISMATCH && trymntmode == ANY) {
922 			clnt_destroy(clp);
923 			trymntmode = V2;
924 			goto tryagain;
925 		}
926 		clnt_geterr(clp, &rpcerr);
927 		snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid_mnt,
928 		    hostp, spec, clnt_sperror(clp, "RPCPROG_MNT"));
929 		clnt_destroy(clp);
930 		return (returncode(stat, &rpcerr));
931 	}
932 	clnt_destroy(clp);
933 
934 	if (nfhret.stat != 0) {
935 		snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid_mnt,
936 		    hostp, spec, strerror(nfhret.stat));
937 		return (TRYRET_REMOTEERR);
938 	}
939 
940 	/*
941 	 * Store the filehandle and server address in nfsargsp, making
942 	 * sure to copy any locally allocated structures.
943 	 */
944 	nfsargsp->addrlen = nfs_nb.len;
945 	nfsargsp->addr = malloc(nfsargsp->addrlen);
946 	nfsargsp->fhsize = nfhret.fhsize;
947 	nfsargsp->fh = malloc(nfsargsp->fhsize);
948 	if (nfsargsp->addr == NULL || nfsargsp->fh == NULL)
949 		err(1, "malloc");
950 	bcopy(nfs_nb.buf, nfsargsp->addr, nfsargsp->addrlen);
951 	bcopy(nfhret.nfh, nfsargsp->fh, nfsargsp->fhsize);
952 
953 	if (nfsvers == 3)
954 		nfsargsp->flags |= NFSMNT_NFSV3;
955 	else
956 		nfsargsp->flags &= ~NFSMNT_NFSV3;
957 
958 	return (TRYRET_SUCCESS);
959 }
960 
961 
962 /*
963  * Try to set up the NFS arguments according to the address
964  * family, protocol (and possibly port) specified in `ai'.
965  *
966  * Returns TRYRET_SUCCESS if successful, or:
967  *   TRYRET_TIMEOUT		The server did not respond.
968  *   TRYRET_REMOTEERR		The server reported an error.
969  *   TRYRET_LOCALERR		Local failure.
970  *
971  * In all error cases, *errstr will be set to a statically-allocated string
972  * describing the error.
973  */
974 enum tryret
975 nfs4_tryproto(struct nfs_args *nfsargsp, struct addrinfo *ai, char *hostp,
976     char *spec, char **errstr)
977 {
978 	static char errbuf[256];
979 	struct sockaddr_storage nfs_ss;
980 	struct netbuf nfs_nb;
981 	struct netconfig *nconf;
982 	char *netid;
983 	int nfsvers;
984 
985 	errbuf[0] = '\0';
986 	*errstr = errbuf;
987 
988 	if ((netid = netidbytype(ai->ai_family, nfsargsp->sotype)) == NULL) {
989 		snprintf(errbuf, sizeof errbuf,
990 		    "af %d sotype %d not supported", ai->ai_family,
991 		    nfsargsp->sotype);
992 		return (TRYRET_LOCALERR);
993 	}
994 	if ((nconf = getnetconf_cached(netid)) == NULL) {
995 		snprintf(errbuf, sizeof errbuf, "%s: %s", netid, nc_sperror());
996 		return (TRYRET_LOCALERR);
997 	}
998 
999 	nfsvers = 4;
1000 
1001 	if (portspec != NULL && atoi(portspec) != 0) {
1002 		/* `ai' contains the complete nfsd sockaddr. */
1003 		nfs_nb.buf = ai->ai_addr;
1004 		nfs_nb.len = nfs_nb.maxlen = ai->ai_addrlen;
1005 	} else {
1006 		/* Ask the remote rpcbind. */
1007 		nfs_nb.buf = &nfs_ss;
1008 		nfs_nb.len = nfs_nb.maxlen = sizeof nfs_ss;
1009 
1010 		if (!rpcb_getaddr(RPCPROG_NFS, nfsvers, nconf, &nfs_nb,
1011 		    hostp)) {
1012 			snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s",
1013 			    netid, hostp, spec,
1014 			    clnt_spcreateerror("RPCPROG_NFS"));
1015 			return (returncode(rpc_createerr.cf_stat,
1016 			    &rpc_createerr.cf_error));
1017 		}
1018 	}
1019 
1020 	/*
1021 	 * Store the filehandle and server address in nfsargsp, making
1022 	 * sure to copy any locally allocated structures.
1023 	 */
1024 	nfsargsp->addrlen = nfs_nb.len;
1025 	nfsargsp->addr = malloc(nfsargsp->addrlen);
1026 
1027 	if (nfsargsp->addr == NULL)
1028 		err(1, "malloc");
1029 	bcopy(nfs_nb.buf, nfsargsp->addr, nfsargsp->addrlen);
1030 
1031 	/* XXX hack */
1032 	nfsargsp->flags |= (NFSMNT_NFSV3 | NFSMNT_NFSV4);
1033 
1034 	return (TRYRET_SUCCESS);
1035 }
1036 
1037 /*
1038  * Catagorise a RPC return status and error into an `enum tryret'
1039  * return code.
1040  */
1041 enum tryret
1042 returncode(enum clnt_stat stat, struct rpc_err *rpcerr)
1043 {
1044 	switch (stat) {
1045 	case RPC_TIMEDOUT:
1046 		return (TRYRET_TIMEOUT);
1047 	case RPC_PMAPFAILURE:
1048 	case RPC_PROGNOTREGISTERED:
1049 	case RPC_PROGVERSMISMATCH:
1050 	/* XXX, these can be local or remote. */
1051 	case RPC_CANTSEND:
1052 	case RPC_CANTRECV:
1053 		return (TRYRET_REMOTEERR);
1054 	case RPC_SYSTEMERROR:
1055 		switch (rpcerr->re_errno) {
1056 		case ETIMEDOUT:
1057 			return (TRYRET_TIMEOUT);
1058 		case ENOMEM:
1059 			break;
1060 		default:
1061 			return (TRYRET_REMOTEERR);
1062 		}
1063 		/* FALLTHROUGH */
1064 	default:
1065 		break;
1066 	}
1067 	return (TRYRET_LOCALERR);
1068 }
1069 
1070 /*
1071  * Look up a netid based on an address family and socket type.
1072  * `af' is the address family, and `sotype' is SOCK_DGRAM or SOCK_STREAM.
1073  *
1074  * XXX there should be a library function for this.
1075  */
1076 char *
1077 netidbytype(int af, int sotype)
1078 {
1079 	struct nc_protos *p;
1080 
1081 	for (p = nc_protos; p->netid != NULL; p++) {
1082 		if (af != p->af || sotype != p->sotype)
1083 			continue;
1084 		return (p->netid);
1085 	}
1086 	return (NULL);
1087 }
1088 
1089 /*
1090  * Look up a netconfig entry based on a netid, and cache the result so
1091  * that we don't need to remember to call freenetconfigent().
1092  *
1093  * Otherwise it behaves just like getnetconfigent(), so nc_*error()
1094  * work on failure.
1095  */
1096 struct netconfig *
1097 getnetconf_cached(const char *netid)
1098 {
1099 	static struct nc_entry {
1100 		struct netconfig *nconf;
1101 		struct nc_entry *next;
1102 	} *head;
1103 	struct nc_entry *p;
1104 	struct netconfig *nconf;
1105 
1106 	for (p = head; p != NULL; p = p->next)
1107 		if (strcmp(netid, p->nconf->nc_netid) == 0)
1108 			return (p->nconf);
1109 
1110 	if ((nconf = getnetconfigent(netid)) == NULL)
1111 		return (NULL);
1112 	if ((p = malloc(sizeof(*p))) == NULL)
1113 		err(1, "malloc");
1114 	p->nconf = nconf;
1115 	p->next = head;
1116 	head = p;
1117 
1118 	return (p->nconf);
1119 }
1120 
1121 /*
1122  * xdr routines for mount rpc's
1123  */
1124 int
1125 xdr_dir(XDR *xdrsp, char *dirp)
1126 {
1127 	return (xdr_string(xdrsp, &dirp, RPCMNT_PATHLEN));
1128 }
1129 
1130 int
1131 xdr_fh(XDR *xdrsp, struct nfhret *np)
1132 {
1133 	int i;
1134 	long auth, authcnt, authfnd = 0;
1135 
1136 	if (!xdr_u_long(xdrsp, &np->stat))
1137 		return (0);
1138 	if (np->stat)
1139 		return (1);
1140 	switch (np->vers) {
1141 	case 1:
1142 		np->fhsize = NFSX_V2FH;
1143 		return (xdr_opaque(xdrsp, (caddr_t)np->nfh, NFSX_V2FH));
1144 	case 3:
1145 		if (!xdr_long(xdrsp, &np->fhsize))
1146 			return (0);
1147 		if (np->fhsize <= 0 || np->fhsize > NFSX_V3FHMAX)
1148 			return (0);
1149 		if (!xdr_opaque(xdrsp, (caddr_t)np->nfh, np->fhsize))
1150 			return (0);
1151 		if (!xdr_long(xdrsp, &authcnt))
1152 			return (0);
1153 		for (i = 0; i < authcnt; i++) {
1154 			if (!xdr_long(xdrsp, &auth))
1155 				return (0);
1156 			if (auth == np->auth)
1157 				authfnd++;
1158 		}
1159 		/*
1160 		 * Some servers, such as DEC's OSF/1 return a nil authenticator
1161 		 * list to indicate RPCAUTH_UNIX.
1162 		 */
1163 		if (!authfnd && (authcnt > 0 || np->auth != RPCAUTH_UNIX))
1164 			np->stat = EAUTH;
1165 		return (1);
1166 	};
1167 	return (0);
1168 }
1169 
1170 void
1171 usage()
1172 {
1173 	(void)fprintf(stderr, "%s\n%s\n%s\n%s\n",
1174 "usage: mount_nfs [-234bcdiLlNPsTU] [-a maxreadahead] [-D deadthresh]",
1175 "                 [-g maxgroups] [-I readdirsize] [-o options] [-R retrycnt]",
1176 "                 [-r readsize] [-t timeout] [-w writesize] [-x retrans]",
1177 "                 rhost:path node");
1178 	exit(1);
1179 }
1180