xref: /netbsd-src/external/bsd/am-utils/dist/amd/srvr_nfs.c (revision 274254cdae52594c1aa480a736aef78313d15c9c)
1 /*	$NetBSD: srvr_nfs.c,v 1.1.1.2 2009/03/20 20:26:50 christos Exp $	*/
2 
3 /*
4  * Copyright (c) 1997-2009 Erez Zadok
5  * Copyright (c) 1990 Jan-Simon Pendry
6  * Copyright (c) 1990 Imperial College of Science, Technology & Medicine
7  * Copyright (c) 1990 The Regents of the University of California.
8  * All rights reserved.
9  *
10  * This code is derived from software contributed to Berkeley by
11  * Jan-Simon Pendry at Imperial College, London.
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions
15  * are met:
16  * 1. Redistributions of source code must retain the above copyright
17  *    notice, this list of conditions and the following disclaimer.
18  * 2. Redistributions in binary form must reproduce the above copyright
19  *    notice, this list of conditions and the following disclaimer in the
20  *    documentation and/or other materials provided with the distribution.
21  * 3. All advertising materials mentioning features or use of this software
22  *    must display the following acknowledgment:
23  *      This product includes software developed by the University of
24  *      California, Berkeley and its contributors.
25  * 4. Neither the name of the University nor the names of its contributors
26  *    may be used to endorse or promote products derived from this software
27  *    without specific prior written permission.
28  *
29  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
30  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
32  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
33  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
34  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
35  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
37  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
38  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
39  * SUCH DAMAGE.
40  *
41  *
42  * File: am-utils/amd/srvr_nfs.c
43  *
44  */
45 
46 /*
47  * NFS server modeling
48  */
49 
50 #ifdef HAVE_CONFIG_H
51 # include <config.h>
52 #endif /* HAVE_CONFIG_H */
53 #include <am_defs.h>
54 #include <amd.h>
55 
56 /*
57  * Number of pings allowed to fail before host is declared down
58  * - three-fifths of the allowed mount time...
59  */
60 #define	MAX_ALLOWED_PINGS	(3 + /* for luck ... */ 1)
61 
62 /*
63  * How often to ping when starting a new server
64  */
65 #define	FAST_NFS_PING		3
66 
67 #if (FAST_NFS_PING * MAX_ALLOWED_PINGS) >= ALLOWED_MOUNT_TIME
68 # error: sanity check failed in srvr_nfs.c
69 /*
70  * you cannot do things this way...
71  * sufficient fast pings must be given the chance to fail
72  * within the allowed mount time
73  */
74 #endif /* (FAST_NFS_PING * MAX_ALLOWED_PINGS) >= ALLOWED_MOUNT_TIME */
75 
76 /* structures and typedefs */
77 typedef struct nfs_private {
78   u_short np_mountd;		/* Mount daemon port number */
79   char np_mountd_inval;		/* Port *may* be invalid */
80   int np_ping;			/* Number of failed ping attempts */
81   time_t np_ttl;		/* Time when server is thought dead */
82   int np_xid;			/* RPC transaction id for pings */
83   int np_error;			/* Error during portmap request */
84 } nfs_private;
85 
86 /* globals */
87 qelem nfs_srvr_list = {&nfs_srvr_list, &nfs_srvr_list};
88 
89 /* statics */
90 static int global_xid;		/* For NFS pings */
91 #define	XID_ALLOC()		(++global_xid)
92 
93 #ifdef HAVE_FS_NFS3
94 # define NUM_NFS_VERS 2
95 #else  /* not HAVE_FS_NFS3 */
96 # define NUM_NFS_VERS 1
97 #endif /* not HAVE_FS_NFS3 */
98 static int ping_len[NUM_NFS_VERS];
99 static char ping_buf[NUM_NFS_VERS][sizeof(struct rpc_msg) + 32];
100 
101 #if defined(MNTTAB_OPT_PROTO) || defined(HAVE_FS_NFS3)
102 /*
103  * Protocols we know about, in order of preference.
104  *
105  * Note that Solaris 8 and newer NetBSD systems are switching to UDP first,
106  * so this order may have to be adjusted for Amd in the future once more
107  * vendors make that change. -Erez 11/24/2000
108  *
109  * Or we might simply make this is a platform-specific order. -Ion 09/13/2003
110  */
111 static char *protocols[] = { "tcp", "udp", NULL };
112 #endif /* defined(MNTTAB_OPT_PROTO) || defined(HAVE_FS_NFS3) */
113 
114 /* forward definitions */
115 static void nfs_keepalive(voidp);
116 
117 
118 /*
119  * Flush cached data for an fserver (or for all, if fs==NULL)
120  */
121 void
122 flush_srvr_nfs_cache(fserver *fs)
123 {
124   fserver *fs2 = NULL;
125 
126   ITER(fs2, fserver, &nfs_srvr_list) {
127     if (fs == NULL || fs == fs2) {
128       nfs_private *np = (nfs_private *) fs2->fs_private;
129       if (np) {
130 	np->np_mountd_inval = TRUE;
131 	np->np_error = -1;
132       }
133     }
134   }
135 }
136 
137 
138 /*
139  * Startup the NFS ping for a particular version.
140  */
141 static void
142 create_ping_payload(u_long nfs_version)
143 {
144   XDR ping_xdr;
145   struct rpc_msg ping_msg;
146 
147   /*
148    * Non nfs mounts like /afs/glue.umd.edu have ended up here.
149    */
150   if (nfs_version == 0) {
151     nfs_version = NFS_VERSION;
152     plog(XLOG_WARNING, "create_ping_payload: nfs_version = 0, changed to 2");
153   } else
154     plog(XLOG_INFO, "create_ping_payload: nfs_version: %d", (int) nfs_version);
155 
156   rpc_msg_init(&ping_msg, NFS_PROGRAM, nfs_version, NFSPROC_NULL);
157 
158   /*
159    * Create an XDR endpoint
160    */
161   xdrmem_create(&ping_xdr, ping_buf[nfs_version - NFS_VERSION], sizeof(ping_buf[0]), XDR_ENCODE);
162 
163   /*
164    * Create the NFS ping message
165    */
166   if (!xdr_callmsg(&ping_xdr, &ping_msg)) {
167     plog(XLOG_ERROR, "Couldn't create ping RPC message");
168     going_down(3);
169   }
170   /*
171    * Find out how long it is
172    */
173   ping_len[nfs_version - NFS_VERSION] = xdr_getpos(&ping_xdr);
174 
175   /*
176    * Destroy the XDR endpoint - we don't need it anymore
177    */
178   xdr_destroy(&ping_xdr);
179 }
180 
181 
182 /*
183  * Called when a portmap reply arrives
184  */
185 static void
186 got_portmap(voidp pkt, int len, struct sockaddr_in *sa, struct sockaddr_in *ia, voidp idv, int done)
187 {
188   fserver *fs2 = (fserver *) idv;
189   fserver *fs = NULL;
190 
191   /*
192    * Find which fileserver we are talking about
193    */
194   ITER(fs, fserver, &nfs_srvr_list)
195     if (fs == fs2)
196       break;
197 
198   if (fs == fs2) {
199     u_long port = 0;	/* XXX - should be short but protocol is naff */
200     int error = done ? pickup_rpc_reply(pkt, len, (voidp) &port, (XDRPROC_T_TYPE) xdr_u_long) : -1;
201     nfs_private *np = (nfs_private *) fs->fs_private;
202 
203     if (!error && port) {
204       dlog("got port (%d) for mountd on %s", (int) port, fs->fs_host);
205       /*
206        * Grab the port number.  Portmap sends back
207        * an u_long in native ordering, so it
208        * needs converting to a u_short in
209        * network ordering.
210        */
211       np->np_mountd = htons((u_short) port);
212       np->np_mountd_inval = FALSE;
213       np->np_error = 0;
214     } else {
215       dlog("Error fetching port for mountd on %s", fs->fs_host);
216       dlog("\t error=%d, port=%d", error, (int) port);
217       /*
218        * Almost certainly no mountd running on remote host
219        */
220       np->np_error = error ? error : ETIMEDOUT;
221     }
222 
223     if (fs->fs_flags & FSF_WANT)
224       wakeup_srvr(fs);
225   } else if (done) {
226     dlog("Got portmap for old port request");
227   } else {
228     dlog("portmap request timed out");
229   }
230 }
231 
232 
233 /*
234  * Obtain portmap information
235  */
236 static int
237 call_portmap(fserver *fs, AUTH *auth, u_long prog, u_long vers, u_long prot)
238 {
239   struct rpc_msg pmap_msg;
240   int len;
241   char iobuf[UDPMSGSIZE];
242   int error;
243   struct pmap pmap;
244 
245   rpc_msg_init(&pmap_msg, PMAPPROG, PMAPVERS, PMAPPROC_NULL);
246   pmap.pm_prog = prog;
247   pmap.pm_vers = vers;
248   pmap.pm_prot = prot;
249   pmap.pm_port = 0;
250   len = make_rpc_packet(iobuf,
251 			sizeof(iobuf),
252 			PMAPPROC_GETPORT,
253 			&pmap_msg,
254 			(voidp) &pmap,
255 			(XDRPROC_T_TYPE) xdr_pmap,
256 			auth);
257   if (len > 0) {
258     struct sockaddr_in sin;
259     memset((voidp) &sin, 0, sizeof(sin));
260     sin = *fs->fs_ip;
261     sin.sin_port = htons(PMAPPORT);
262     error = fwd_packet(RPC_XID_PORTMAP, iobuf, len,
263 		       &sin, &sin, (voidp) fs, got_portmap);
264   } else {
265     error = -len;
266   }
267 
268   return error;
269 }
270 
271 
272 static void
273 recompute_portmap(fserver *fs)
274 {
275   int error;
276   u_long mnt_version;
277 
278   /*
279    * No portmap calls for pure WebNFS servers.
280    */
281   if (fs->fs_flags & FSF_WEBNFS)
282     return;
283 
284   if (nfs_auth)
285     error = 0;
286   else
287     error = make_nfs_auth();
288 
289   if (error) {
290     nfs_private *np = (nfs_private *) fs->fs_private;
291     np->np_error = error;
292     return;
293   }
294 
295   if (fs->fs_version == 0)
296     plog(XLOG_WARNING, "recompute_portmap: nfs_version = 0 fixed");
297 
298   plog(XLOG_INFO, "recompute_portmap: NFS version %d on %s",
299        (int) fs->fs_version, fs->fs_host);
300 #ifdef HAVE_FS_NFS3
301   if (fs->fs_version == NFS_VERSION3)
302     mnt_version = AM_MOUNTVERS3;
303   else
304 #endif /* HAVE_FS_NFS3 */
305     mnt_version = MOUNTVERS;
306 
307   plog(XLOG_INFO, "Using MOUNT version: %d", (int) mnt_version);
308   call_portmap(fs, nfs_auth, MOUNTPROG, mnt_version, (u_long) IPPROTO_UDP);
309 }
310 
311 
312 int
313 get_mountd_port(fserver *fs, u_short *port, wchan_t wchan)
314 {
315   int error = -1;
316   if (FSRV_ISDOWN(fs))
317     return EWOULDBLOCK;
318 
319   if (FSRV_ISUP(fs)) {
320     nfs_private *np = (nfs_private *) fs->fs_private;
321     if (np->np_error == 0) {
322       *port = np->np_mountd;
323       error = 0;
324     } else {
325       error = np->np_error;
326     }
327     /*
328      * Now go get the port mapping again in case it changed.
329      * Note that it is used even if (np_mountd_inval)
330      * is True.  The flag is used simply as an
331      * indication that the mountd may be invalid, not
332      * that it is known to be invalid.
333      */
334     if (np->np_mountd_inval)
335       recompute_portmap(fs);
336     else
337       np->np_mountd_inval = TRUE;
338   }
339   if (error < 0 && wchan && !(fs->fs_flags & FSF_WANT)) {
340     /*
341      * If a wait channel is supplied, and no
342      * error has yet occurred, then arrange
343      * that a wakeup is done on the wait channel,
344      * whenever a wakeup is done on this fs node.
345      * Wakeup's are done on the fs node whenever
346      * it changes state - thus causing control to
347      * come back here and new, better things to happen.
348      */
349     fs->fs_flags |= FSF_WANT;
350     sched_task(wakeup_task, wchan, (wchan_t) fs);
351   }
352   return error;
353 }
354 
355 
356 /*
357  * This is called when we get a reply to an RPC ping.
358  * The value of id was taken from the nfs_private
359  * structure when the ping was transmitted.
360  */
361 static void
362 nfs_keepalive_callback(voidp pkt, int len, struct sockaddr_in *sp, struct sockaddr_in *tsp, voidp idv, int done)
363 {
364   int xid = (long) idv;		/* cast needed for 64-bit archs */
365   fserver *fs;
366   int found_map = 0;
367 
368   if (!done)
369     return;
370 
371   /*
372    * For each node...
373    */
374   ITER(fs, fserver, &nfs_srvr_list) {
375     nfs_private *np = (nfs_private *) fs->fs_private;
376     if (np->np_xid == xid && (fs->fs_flags & FSF_PINGING)) {
377       /*
378        * Reset the ping counter.
379        * Update the keepalive timer.
380        * Log what happened.
381        */
382       if (fs->fs_flags & FSF_DOWN) {
383 	fs->fs_flags &= ~FSF_DOWN;
384 	if (fs->fs_flags & FSF_VALID) {
385 	  srvrlog(fs, "is up");
386 	} else {
387 	  if (np->np_ping > 1)
388 	    srvrlog(fs, "ok");
389 	  else
390 	    srvrlog(fs, "starts up");
391 	  fs->fs_flags |= FSF_VALID;
392 	}
393 
394 	map_flush_srvr(fs);
395       } else {
396 	if (fs->fs_flags & FSF_VALID) {
397 	  dlog("file server %s type nfs is still up", fs->fs_host);
398 	} else {
399 	  if (np->np_ping > 1)
400 	    srvrlog(fs, "ok");
401 	  fs->fs_flags |= FSF_VALID;
402 	}
403       }
404 
405       /*
406        * Adjust ping interval
407        */
408       untimeout(fs->fs_cid);
409       fs->fs_cid = timeout(fs->fs_pinger, nfs_keepalive, (voidp) fs);
410 
411       /*
412        * Update ttl for this server
413        */
414       np->np_ttl = clocktime(NULL) +
415 	(MAX_ALLOWED_PINGS - 1) * FAST_NFS_PING + fs->fs_pinger - 1;
416 
417       /*
418        * New RPC xid...
419        */
420       np->np_xid = XID_ALLOC();
421 
422       /*
423        * Failed pings is zero...
424        */
425       np->np_ping = 0;
426 
427       /*
428        * Recompute portmap information if not known
429        */
430       if (np->np_mountd_inval)
431 	recompute_portmap(fs);
432 
433       found_map++;
434       break;
435     }
436   }
437 
438   if (found_map == 0)
439     dlog("Spurious ping packet");
440 }
441 
442 
443 static void
444 check_fs_addr_change(fserver *fs)
445 {
446   struct hostent *hp = NULL;
447   struct in_addr ia;
448   char *old_ipaddr, *new_ipaddr;
449 
450   hp = gethostbyname(fs->fs_host);
451   if (!hp ||
452       hp->h_addrtype != AF_INET ||
453       !STREQ((char *) hp->h_name, fs->fs_host) ||
454       memcmp((voidp) &fs->fs_ip->sin_addr,
455 	     (voidp) hp->h_addr,
456 	     sizeof(fs->fs_ip->sin_addr)) == 0)
457     return;
458   /* if got here: downed server changed IP address */
459   old_ipaddr = strdup(inet_ntoa(fs->fs_ip->sin_addr));
460   memmove((voidp) &ia, (voidp) hp->h_addr, sizeof(struct in_addr));
461   new_ipaddr = inet_ntoa(ia);	/* ntoa uses static buf */
462   plog(XLOG_WARNING, "EZK: down fileserver %s changed ip: %s -> %s",
463        fs->fs_host, old_ipaddr, new_ipaddr);
464   XFREE(old_ipaddr);
465   /* copy new IP addr */
466   memmove((voidp) &fs->fs_ip->sin_addr,
467 	  (voidp) hp->h_addr,
468 	  sizeof(fs->fs_ip->sin_addr));
469   /* XXX: do we need to un/set these flags? */
470   fs->fs_flags &= ~FSF_DOWN;
471   fs->fs_flags |= FSF_VALID | FSF_WANT;
472   map_flush_srvr(fs);		/* XXX: a race with flush_srvr_nfs_cache? */
473   flush_srvr_nfs_cache(fs);
474   fs->fs_flags |= FSF_FORCE_UNMOUNT;
475 
476 #if 0
477   flush_nfs_fhandle_cache(fs);	/* done in caller: nfs_keepalive_timeout */
478   /* XXX: need to purge nfs_private so that somehow it will get re-initialized? */
479 #endif /* 0 */
480 }
481 
482 
483 /*
484  * Called when no ping-reply received
485  */
486 static void
487 nfs_keepalive_timeout(voidp v)
488 {
489   fserver *fs = v;
490   nfs_private *np = (nfs_private *) fs->fs_private;
491 
492   /*
493    * Another ping has failed
494    */
495   np->np_ping++;
496   if (np->np_ping > 1)
497     srvrlog(fs, "not responding");
498 
499   /*
500    * Not known to be up any longer
501    */
502   if (FSRV_ISUP(fs))
503     fs->fs_flags &= ~FSF_VALID;
504 
505   /*
506    * If ttl has expired then guess that it is dead
507    */
508   if (np->np_ttl < clocktime(NULL)) {
509     int oflags = fs->fs_flags;
510     dlog("ttl has expired");
511     if ((fs->fs_flags & FSF_DOWN) == 0) {
512       /*
513        * Server was up, but is now down.
514        */
515       srvrlog(fs, "is down");
516       fs->fs_flags |= FSF_DOWN | FSF_VALID;
517       /*
518        * Since the server is down, the portmap
519        * information may now be wrong, so it
520        * must be flushed from the local cache
521        */
522       flush_nfs_fhandle_cache(fs);
523       np->np_error = -1;
524       check_fs_addr_change(fs); /* check if IP addr of fserver changed */
525     } else {
526       /*
527        * Known to be down
528        */
529       if ((fs->fs_flags & FSF_VALID) == 0)
530 	srvrlog(fs, "starts down");
531       fs->fs_flags |= FSF_VALID;
532     }
533     if (oflags != fs->fs_flags && (fs->fs_flags & FSF_WANT))
534       wakeup_srvr(fs);
535     /*
536      * Reset failed ping count
537      */
538     np->np_ping = 0;
539   } else {
540     if (np->np_ping > 1)
541       dlog("%d pings to %s failed - at most %d allowed", np->np_ping, fs->fs_host, MAX_ALLOWED_PINGS);
542   }
543 
544   /*
545    * New RPC xid, so any late responses to the previous ping
546    * get ignored...
547    */
548   np->np_xid = XID_ALLOC();
549 
550   /*
551    * Run keepalive again
552    */
553   nfs_keepalive(fs);
554 }
555 
556 
557 /*
558  * Keep track of whether a server is alive
559  */
560 static void
561 nfs_keepalive(voidp v)
562 {
563   fserver *fs = v;
564   int error;
565   nfs_private *np = (nfs_private *) fs->fs_private;
566   int fstimeo = -1;
567 
568   /*
569    * Send an NFS ping to this node
570    */
571 
572   if (ping_len[fs->fs_version - NFS_VERSION] == 0)
573     create_ping_payload(fs->fs_version);
574 
575   /*
576    * Queue the packet...
577    */
578   error = fwd_packet(MK_RPC_XID(RPC_XID_NFSPING, np->np_xid),
579 		     ping_buf[fs->fs_version - NFS_VERSION],
580 		     ping_len[fs->fs_version - NFS_VERSION],
581 		     fs->fs_ip,
582 		     (struct sockaddr_in *) NULL,
583 		     (voidp) ((long) np->np_xid), /* cast needed for 64-bit archs */
584 		     nfs_keepalive_callback);
585 
586   /*
587    * See if a hard error occurred
588    */
589   switch (error) {
590   case ENETDOWN:
591   case ENETUNREACH:
592   case EHOSTDOWN:
593   case EHOSTUNREACH:
594     np->np_ping = MAX_ALLOWED_PINGS;	/* immediately down */
595     np->np_ttl = (time_t) 0;
596     /*
597      * This causes an immediate call to nfs_keepalive_timeout
598      * whenever the server was thought to be up.
599      * See +++ below.
600      */
601     fstimeo = 0;
602     break;
603 
604   case 0:
605     dlog("Sent NFS ping to %s", fs->fs_host);
606     break;
607   }
608 
609   /*
610    * Back off the ping interval if we are not getting replies and
611    * the remote system is known to be down.
612    */
613   switch (fs->fs_flags & (FSF_DOWN | FSF_VALID)) {
614   case FSF_VALID:		/* Up */
615     if (fstimeo < 0)		/* +++ see above */
616       fstimeo = FAST_NFS_PING;
617     break;
618 
619   case FSF_VALID | FSF_DOWN:	/* Down */
620     fstimeo = fs->fs_pinger;
621     break;
622 
623   default:			/* Unknown */
624     fstimeo = FAST_NFS_PING;
625     break;
626   }
627 
628   dlog("NFS timeout in %d seconds", fstimeo);
629 
630   fs->fs_cid = timeout(fstimeo, nfs_keepalive_timeout, (voidp) fs);
631 }
632 
633 
634 static void
635 start_nfs_pings(fserver *fs, int pingval)
636 {
637   if (pingval == 0)	    /* could be because ping mnt option not found */
638     pingval = AM_PINGER;
639   /* if pings haven't been initalized, then init them for first time */
640   if (fs->fs_flags & FSF_PING_UNINIT) {
641     fs->fs_flags &= ~FSF_PING_UNINIT;
642     plog(XLOG_INFO, "initializing %s's pinger to %d sec", fs->fs_host, pingval);
643     goto do_pings;
644   }
645 
646   if ((fs->fs_flags & FSF_PINGING)  &&  fs->fs_pinger == pingval) {
647     dlog("already running pings to %s", fs->fs_host);
648     return;
649   }
650 
651   /* if got here, then we need to update the ping value */
652   plog(XLOG_INFO, "changing %s's ping value from %d%s to %d%s",
653        fs->fs_host,
654        fs->fs_pinger, (fs->fs_pinger < 0 ? " (off)" : ""),
655        pingval, (pingval < 0 ? " (off)" : ""));
656  do_pings:
657   fs->fs_pinger = pingval;
658 
659   if (fs->fs_cid)
660     untimeout(fs->fs_cid);
661   if (pingval < 0) {
662     srvrlog(fs, "wired up (pings disabled)");
663     fs->fs_flags |= FSF_VALID;
664     fs->fs_flags &= ~FSF_DOWN;
665   } else {
666     fs->fs_flags |= FSF_PINGING;
667     nfs_keepalive(fs);
668   }
669 }
670 
671 
672 /*
673  * Find an nfs server for a host.
674  */
675 fserver *
676 find_nfs_srvr(mntfs *mf)
677 {
678   char *host = mf->mf_fo->opt_rhost;
679   fserver *fs;
680   int pingval;
681   mntent_t mnt;
682   nfs_private *np;
683   struct hostent *hp = NULL;
684   struct sockaddr_in *ip = NULL;
685   u_long nfs_version = 0;	/* default is no version specified */
686   u_long best_nfs_version = 0;
687   char *nfs_proto = NULL;	/* no IP protocol either */
688   int nfs_port = 0;
689   int nfs_port_opt = 0;
690   int fserver_is_down = 0;
691 
692   /*
693    * Get ping interval from mount options.
694    * Current only used to decide whether pings
695    * are required or not.  < 0 = no pings.
696    */
697   mnt.mnt_opts = mf->mf_mopts;
698   pingval = hasmntval(&mnt, "ping");
699 
700   if (mf->mf_flags & MFF_NFS_SCALEDOWN) {
701     /*
702      * the server granted us a filehandle, but we were unable to mount it.
703      * therefore, scale down to NFSv2/UDP and try again.
704      */
705     nfs_version = NFS_VERSION;
706     nfs_proto = "udp";
707     plog(XLOG_WARNING, "find_nfs_srvr: NFS mount failed, trying again with NFSv2/UDP");
708     mf->mf_flags &= ~MFF_NFS_SCALEDOWN;
709   } else {
710     /*
711      * Get the NFS version from the mount options. This is used
712      * to decide the highest NFS version to try.
713      */
714 #ifdef MNTTAB_OPT_VERS
715     nfs_version = hasmntval(&mnt, MNTTAB_OPT_VERS);
716 #endif /* MNTTAB_OPT_VERS */
717 
718 #ifdef MNTTAB_OPT_PROTO
719     {
720       char *proto_opt = hasmnteq(&mnt, MNTTAB_OPT_PROTO);
721       if (proto_opt) {
722 	char **p;
723 	for (p = protocols; *p; p++)
724 	  if (NSTREQ(proto_opt, *p, strlen(*p))) {
725 	    nfs_proto = *p;
726 	    break;
727 	  }
728 	if (*p == NULL)
729 	  plog(XLOG_WARNING, "ignoring unknown protocol option for %s:%s",
730 	       host, mf->mf_fo->opt_rfs);
731       }
732     }
733 #endif /* MNTTAB_OPT_PROTO */
734 
735 #ifdef HAVE_NFS_NFSV2_H
736     /* allow overriding if nfsv2 option is specified in mount options */
737     if (amu_hasmntopt(&mnt, "nfsv2")) {
738       nfs_version = NFS_VERSION;/* nullify any ``vers=X'' statements */
739       nfs_proto = "udp";	/* nullify any ``proto=tcp'' statements */
740       plog(XLOG_WARNING, "found compatibility option \"nfsv2\": set options vers=2,proto=udp for host %s", host);
741     }
742 #endif /* HAVE_NFS_NFSV2_H */
743 
744     /* check if we've globally overridden the NFS version/protocol */
745     if (gopt.nfs_vers) {
746       nfs_version = gopt.nfs_vers;
747       plog(XLOG_INFO, "find_nfs_srvr: force NFS version to %d",
748 	   (int) nfs_version);
749     }
750     if (gopt.nfs_proto) {
751       nfs_proto = gopt.nfs_proto;
752       plog(XLOG_INFO, "find_nfs_srvr: force NFS protocol transport to %s", nfs_proto);
753     }
754   }
755 
756   /*
757    * lookup host address and canonical name
758    */
759   hp = gethostbyname(host);
760 
761   /*
762    * New code from Bob Harris <harris@basil-rathbone.mit.edu>
763    * Use canonical name to keep track of file server
764    * information.  This way aliases do not generate
765    * multiple NFS pingers.  (Except when we're normalizing
766    * hosts.)
767    */
768   if (hp && !(gopt.flags & CFM_NORMALIZE_HOSTNAMES))
769     host = (char *) hp->h_name;
770 
771   if (hp) {
772     switch (hp->h_addrtype) {
773     case AF_INET:
774       ip = ALLOC(struct sockaddr_in);
775       memset((voidp) ip, 0, sizeof(*ip));
776       /* as per POSIX, sin_len need not be set (used internally by kernel) */
777       ip->sin_family = AF_INET;
778       memmove((voidp) &ip->sin_addr, (voidp) hp->h_addr, sizeof(ip->sin_addr));
779       break;
780 
781     default:
782       plog(XLOG_USER, "No IP address for host %s", host);
783       goto no_dns;
784     }
785   } else {
786     plog(XLOG_USER, "Unknown host: %s", host);
787     goto no_dns;
788   }
789 
790   /*
791    * This may not be the best way to do things, but it really doesn't make
792    * sense to query a file server which is marked as 'down' for any
793    * version/proto combination.
794    */
795   ITER(fs, fserver, &nfs_srvr_list) {
796     if (FSRV_ISDOWN(fs) &&
797 	STREQ(host, fs->fs_host)) {
798       plog(XLOG_WARNING, "fileserver %s is already hung - not running NFS proto/version discovery", host);
799       fs->fs_refc++;
800       if (ip)
801 	XFREE(ip);
802       return fs;
803     }
804   }
805 
806   /*
807    * Get the NFS Version, and verify server is up.
808    * If the client only supports NFSv2, hardcode it but still try to
809    * contact the remote portmapper to see if the service is running.
810    */
811 #ifndef HAVE_FS_NFS3
812   nfs_version = NFS_VERSION;
813   nfs_proto = "udp";
814   plog(XLOG_INFO, "The client supports only NFS(2,udp)");
815 #endif /* not HAVE_FS_NFS3 */
816 
817 
818   if (amu_hasmntopt(&mnt, MNTTAB_OPT_PUBLIC)) {
819     /*
820      * Use WebNFS to obtain file handles.
821      */
822     mf->mf_flags |= MFF_WEBNFS;
823     plog(XLOG_INFO, "%s option used, NOT contacting the portmapper on %s",
824 	 MNTTAB_OPT_PUBLIC, host);
825     /*
826      * Prefer NFSv3/tcp if the client supports it (cf. RFC 2054, 7).
827      */
828     if (!nfs_version) {
829 #ifdef HAVE_FS_NFS3
830       nfs_version = NFS_VERSION3;
831 #else /* not HAVE_FS_NFS3 */
832       nfs_version = NFS_VERSION;
833 #endif /* not HAVE_FS_NFS3 */
834       plog(XLOG_INFO, "No NFS version specified, will use NFSv%d",
835 	   (int) nfs_version);
836     }
837     if (!nfs_proto) {
838 #if defined(MNTTAB_OPT_PROTO) || defined(HAVE_FS_NFS3)
839       nfs_proto = "tcp";
840 #else /* not defined(MNTTAB_OPT_PROTO) || defined(HAVE_FS_NFS3) */
841       nfs_proto = "udp";
842 #endif /* not defined(MNTTAB_OPT_PROTO) || defined(HAVE_FS_NFS3) */
843       plog(XLOG_INFO, "No NFS protocol transport specified, will use %s",
844 	   nfs_proto);
845     }
846   } else {
847     /*
848      * Find the best combination of NFS version and protocol.
849      * When given a choice, use the highest available version,
850      * and use TCP over UDP if available.
851      */
852     if (check_pmap_up(host, ip)) {
853       if (nfs_proto) {
854 	best_nfs_version = get_nfs_version(host, ip, nfs_version, nfs_proto);
855 	nfs_port = ip->sin_port;
856       }
857 #ifdef MNTTAB_OPT_PROTO
858       else {
859 	u_int proto_nfs_version;
860 	char **p;
861 
862 	for (p = protocols; *p; p++) {
863 	  proto_nfs_version = get_nfs_version(host, ip, nfs_version, *p);
864 
865 	  if (proto_nfs_version > best_nfs_version) {
866 	    best_nfs_version = proto_nfs_version;
867 	    nfs_proto = *p;
868 	    nfs_port = ip->sin_port;
869 	  }
870 	}
871       }
872 #endif /* MNTTAB_OPT_PROTO */
873     } else {
874       plog(XLOG_INFO, "portmapper service not running on %s", host);
875     }
876 
877     /* use the portmapper results only nfs_version is not set yet */
878     if (!best_nfs_version) {
879       /*
880        * If the NFS server is down or does not support the portmapper call
881        * (such as certain Novell NFS servers) we mark it as version 2 and we
882        * let the nfs code deal with the case when it is down.  If/when the
883        * server comes back up and it can support NFSv3 and/or TCP, it will
884        * use those.
885        */
886       if (nfs_version == 0) {
887 	nfs_version = NFS_VERSION;
888 	nfs_proto = "udp";
889       }
890       plog(XLOG_INFO, "NFS service not running on %s", host);
891       fserver_is_down = 1;
892     } else {
893       if (nfs_version == 0)
894 	nfs_version = best_nfs_version;
895       plog(XLOG_INFO, "Using NFS version %d, protocol %s on host %s",
896 	   (int) nfs_version, nfs_proto, host);
897     }
898   }
899 
900   /*
901    * Determine the NFS port.
902    *
903    * A valid "port" mount option overrides anything else.
904    * If the port has been determined from the portmapper, use that.
905    * Default to NFS_PORT otherwise (cf. RFC 2054, 3).
906    */
907   nfs_port_opt = hasmntval(&mnt, MNTTAB_OPT_PORT);
908   if (nfs_port_opt > 0)
909     nfs_port = htons(nfs_port_opt);
910   if (!nfs_port)
911     nfs_port = htons(NFS_PORT);
912 
913   dlog("find_nfs_srvr: using port %d for nfs on %s",
914        (int) ntohs(nfs_port), host);
915   ip->sin_port = nfs_port;
916 
917 no_dns:
918   /*
919    * Try to find an existing fs server structure for this host.
920    * Note that differing versions or protocols have their own structures.
921    * XXX: Need to fix the ping mechanism to actually use the NFS protocol
922    * chosen here (right now it always uses datagram sockets).
923    */
924   ITER(fs, fserver, &nfs_srvr_list) {
925     if (STREQ(host, fs->fs_host) &&
926  	nfs_version == fs->fs_version &&
927 	STREQ(nfs_proto, fs->fs_proto)) {
928       /*
929        * fill in the IP address -- this is only needed
930        * if there is a chance an IP address will change
931        * between mounts.
932        * Mike Mitchell, mcm@unx.sas.com, 09/08/93
933        */
934       if (hp && fs->fs_ip &&
935 	  memcmp((voidp) &fs->fs_ip->sin_addr,
936 		 (voidp) hp->h_addr,
937 		 sizeof(fs->fs_ip->sin_addr)) != 0) {
938 	struct in_addr ia;
939 	char *old_ipaddr, *new_ipaddr;
940 	old_ipaddr = strdup(inet_ntoa(fs->fs_ip->sin_addr));
941 	memmove((voidp) &ia, (voidp) hp->h_addr, sizeof(struct in_addr));
942 	new_ipaddr = inet_ntoa(ia);	/* ntoa uses static buf */
943 	plog(XLOG_WARNING, "fileserver %s changed ip: %s -> %s",
944 	     fs->fs_host, old_ipaddr, new_ipaddr);
945 	XFREE(old_ipaddr);
946 	flush_nfs_fhandle_cache(fs);
947 	memmove((voidp) &fs->fs_ip->sin_addr, (voidp) hp->h_addr, sizeof(fs->fs_ip->sin_addr));
948       }
949 
950       /*
951        * If the new file systems doesn't use WebNFS, the nfs pings may
952        * try to contact the portmapper.
953        */
954       if (!(mf->mf_flags & MFF_WEBNFS))
955 	fs->fs_flags &= ~FSF_WEBNFS;
956 
957       /* check if pingval needs to be updated/set/reset */
958       start_nfs_pings(fs, pingval);
959 
960       /*
961        * Following if statement from Mike Mitchell <mcm@unx.sas.com>
962        * Initialize the ping data if we aren't pinging now.  The np_ttl and
963        * np_ping fields are especially important.
964        */
965       if (!(fs->fs_flags & FSF_PINGING)) {
966 	np = (nfs_private *) fs->fs_private;
967 	np->np_mountd_inval = TRUE;
968 	np->np_xid = XID_ALLOC();
969 	np->np_error = -1;
970 	np->np_ping = 0;
971 	/*
972 	 * Initially the server will be deemed dead
973 	 * after MAX_ALLOWED_PINGS of the fast variety
974 	 * have failed.
975 	 */
976 	np->np_ttl = MAX_ALLOWED_PINGS * FAST_NFS_PING + clocktime(NULL) - 1;
977 	start_nfs_pings(fs, pingval);
978 	if (fserver_is_down)
979 	  fs->fs_flags |= FSF_VALID | FSF_DOWN;
980       }
981 
982       fs->fs_refc++;
983       if (ip)
984 	XFREE(ip);
985       return fs;
986     }
987   }
988 
989   /*
990    * Get here if we can't find an entry
991    */
992 
993   /*
994    * Allocate a new server
995    */
996   fs = ALLOC(struct fserver);
997   fs->fs_refc = 1;
998   fs->fs_host = strdup(hp ? hp->h_name : "unknown_hostname");
999   if (gopt.flags & CFM_NORMALIZE_HOSTNAMES)
1000     host_normalize(&fs->fs_host);
1001   fs->fs_ip = ip;
1002   fs->fs_cid = 0;
1003   if (ip) {
1004     fs->fs_flags = FSF_DOWN;	/* Starts off down */
1005   } else {
1006     fs->fs_flags = FSF_ERROR | FSF_VALID;
1007     mf->mf_flags |= MFF_ERROR;
1008     mf->mf_error = ENOENT;
1009   }
1010   if (mf->mf_flags & MFF_WEBNFS)
1011     fs->fs_flags |= FSF_WEBNFS;
1012   fs->fs_version = nfs_version;
1013   fs->fs_proto = nfs_proto;
1014   fs->fs_type = MNTTAB_TYPE_NFS;
1015   fs->fs_pinger = AM_PINGER;
1016   fs->fs_flags |= FSF_PING_UNINIT; /* pinger hasn't been initialized */
1017   np = ALLOC(struct nfs_private);
1018   memset((voidp) np, 0, sizeof(*np));
1019   np->np_mountd_inval = TRUE;
1020   np->np_xid = XID_ALLOC();
1021   np->np_error = -1;
1022 
1023   /*
1024    * Initially the server will be deemed dead after
1025    * MAX_ALLOWED_PINGS of the fast variety have failed.
1026    */
1027   np->np_ttl = clocktime(NULL) + MAX_ALLOWED_PINGS * FAST_NFS_PING - 1;
1028   fs->fs_private = (voidp) np;
1029   fs->fs_prfree = (void (*)(voidp)) free;
1030 
1031   if (!FSRV_ERROR(fs)) {
1032     /* start of keepalive timer, first updating pingval */
1033     start_nfs_pings(fs, pingval);
1034     if (fserver_is_down)
1035       fs->fs_flags |= FSF_VALID | FSF_DOWN;
1036   }
1037 
1038   /*
1039    * Add to list of servers
1040    */
1041   ins_que(&fs->fs_q, &nfs_srvr_list);
1042 
1043   return fs;
1044 }
1045