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