xref: /netbsd-src/external/bsd/am-utils/dist/amd/ops_nfs.c (revision 8bae5d409deb915cf7c8f0539fae22ff2cb8a313)
1 /*	$NetBSD: ops_nfs.c,v 1.1.1.3 2015/01/17 16:34:15 christos Exp $	*/
2 
3 /*
4  * Copyright (c) 1997-2014 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. Neither the name of the University nor the names of its contributors
22  *    may be used to endorse or promote products derived from this software
23  *    without specific prior written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
29  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35  * SUCH DAMAGE.
36  *
37  *
38  * File: am-utils/amd/ops_nfs.c
39  *
40  */
41 
42 /*
43  * Network file system
44  */
45 
46 #ifdef HAVE_CONFIG_H
47 # include <config.h>
48 #endif /* HAVE_CONFIG_H */
49 #include <am_defs.h>
50 #include <amd.h>
51 
52 /*
53  * Convert from nfsstat to UN*X error code
54  */
55 #define unx_error(e)	((int)(e))
56 
57 /*
58  * FH_TTL is the time a file handle will remain in the cache since
59  * last being used.  If the file handle becomes invalid, then it
60  * will be flushed anyway.
61  */
62 #define	FH_TTL			(5 * 60) /* five minutes */
63 #define	FH_TTL_ERROR		(30) /* 30 seconds */
64 #define	FHID_ALLOC()		(++fh_id)
65 
66 /*
67  * The NFS layer maintains a cache of file handles.
68  * This is *fundamental* to the implementation and
69  * also allows quick remounting when a filesystem
70  * is accessed soon after timing out.
71  *
72  * The NFS server layer knows to flush this cache
73  * when a server goes down so avoiding stale handles.
74  *
75  * Each cache entry keeps a hard reference to
76  * the corresponding server.  This ensures that
77  * the server keepalive information is maintained.
78  *
79  * The copy of the sockaddr_in here is taken so
80  * that the port can be twiddled to talk to mountd
81  * instead of portmap or the NFS server as used
82  * elsewhere.
83  * The port# is flushed if a server goes down.
84  * The IP address is never flushed - we assume
85  * that the address of a mounted machine never
86  * changes.  If it does, then you have other
87  * problems...
88  */
89 typedef struct fh_cache fh_cache;
90 struct fh_cache {
91   qelem			fh_q;		/* List header */
92   wchan_t		fh_wchan;	/* Wait channel */
93   int			fh_error;	/* Valid data? */
94   int			fh_id;		/* Unique id */
95   int			fh_cid;		/* Callout id */
96   u_long		fh_nfs_version;	/* highest NFS version on host */
97   am_nfs_handle_t	fh_nfs_handle;	/* Handle on filesystem */
98   int			fh_status;	/* Status of last rpc */
99   struct sockaddr_in	fh_sin;		/* Address of mountd */
100   fserver		*fh_fs;		/* Server holding filesystem */
101   char			*fh_path;	/* Filesystem on host */
102 };
103 
104 /* forward definitions */
105 static int nfs_init(mntfs *mf);
106 static char *nfs_match(am_opts *fo);
107 static int nfs_mount(am_node *am, mntfs *mf);
108 static int nfs_umount(am_node *am, mntfs *mf);
109 static void nfs_umounted(mntfs *mf);
110 static int call_mountd(fh_cache *fp, u_long proc, fwd_fun f, wchan_t wchan);
111 static int webnfs_lookup(fh_cache *fp, fwd_fun f, wchan_t wchan);
112 static int fh_id = 0;
113 
114 /*
115  * clamp the filehandle version to 3, so that we can fail back to nfsv3
116  * since nfsv4 does not have file handles
117  */
118 #define SET_FH_VERSION(fs) \
119     (fs)->fs_version > NFS_VERSION3 ? NFS_VERSION3 : (fs)->fs_version;
120 
121 /* globals */
122 AUTH *nfs_auth;
123 qelem fh_head = {&fh_head, &fh_head};
124 
125 /*
126  * Network file system operations
127  */
128 am_ops nfs_ops =
129 {
130   "nfs",
131   nfs_match,
132   nfs_init,
133   nfs_mount,
134   nfs_umount,
135   amfs_error_lookup_child,
136   amfs_error_mount_child,
137   amfs_error_readdir,
138   0,				/* nfs_readlink */
139   0,				/* nfs_mounted */
140   nfs_umounted,
141   find_nfs_srvr,
142   0,				/* nfs_get_wchan */
143   FS_MKMNT | FS_BACKGROUND | FS_AMQINFO,	/* nfs_fs_flags */
144 #ifdef HAVE_FS_AUTOFS
145   AUTOFS_NFS_FS_FLAGS,
146 #endif /* HAVE_FS_AUTOFS */
147 };
148 
149 
150 static fh_cache *
find_nfs_fhandle_cache(opaque_t arg,int done)151 find_nfs_fhandle_cache(opaque_t arg, int done)
152 {
153   fh_cache *fp, *fp2 = NULL;
154   int id = (long) arg;		/* for 64-bit archs */
155 
156   ITER(fp, fh_cache, &fh_head) {
157     if (fp->fh_id == id) {
158       fp2 = fp;
159       break;
160     }
161   }
162 
163   if (fp2) {
164     dlog("fh cache gives fp %#lx, fs %s", (unsigned long) fp2, fp2->fh_path);
165   } else {
166     dlog("fh cache search failed");
167   }
168 
169   if (fp2 && !done) {
170     fp2->fh_error = ETIMEDOUT;
171     return 0;
172   }
173 
174   return fp2;
175 }
176 
177 
178 /*
179  * Called when a filehandle appears via the mount protocol
180  */
181 static void
got_nfs_fh_mount(voidp pkt,int len,struct sockaddr_in * sa,struct sockaddr_in * ia,opaque_t arg,int done)182 got_nfs_fh_mount(voidp pkt, int len, struct sockaddr_in *sa, struct sockaddr_in *ia, opaque_t arg, int done)
183 {
184   fh_cache *fp;
185   struct fhstatus res;
186 #ifdef HAVE_FS_NFS3
187   struct am_mountres3 res3;
188 #endif /* HAVE_FS_NFS3 */
189 
190   fp = find_nfs_fhandle_cache(arg, done);
191   if (!fp)
192     return;
193 
194   /*
195    * retrieve the correct RPC reply for the file handle, based on the
196    * NFS protocol version.
197    */
198 #ifdef HAVE_FS_NFS3
199   if (fp->fh_nfs_version == NFS_VERSION3) {
200     memset(&res3, 0, sizeof(res3));
201     fp->fh_error = pickup_rpc_reply(pkt, len, (voidp) &res3,
202 				    (XDRPROC_T_TYPE) xdr_am_mountres3);
203     fp->fh_status = unx_error(res3.fhs_status);
204     memset(&fp->fh_nfs_handle.v3, 0, sizeof(am_nfs_fh3));
205     fp->fh_nfs_handle.v3.am_fh3_length = res3.mountres3_u.mountinfo.fhandle.fhandle3_len;
206     memmove(fp->fh_nfs_handle.v3.am_fh3_data,
207 	    res3.mountres3_u.mountinfo.fhandle.fhandle3_val,
208 	    fp->fh_nfs_handle.v3.am_fh3_length);
209 
210     XFREE(res3.mountres3_u.mountinfo.fhandle.fhandle3_val);
211     if (res3.mountres3_u.mountinfo.auth_flavors.auth_flavors_val)
212       XFREE(res3.mountres3_u.mountinfo.auth_flavors.auth_flavors_val);
213   } else {
214 #endif /* HAVE_FS_NFS3 */
215     memset(&res, 0, sizeof(res));
216     fp->fh_error = pickup_rpc_reply(pkt, len, (voidp) &res,
217 				    (XDRPROC_T_TYPE) xdr_fhstatus);
218     fp->fh_status = unx_error(res.fhs_status);
219     memmove(&fp->fh_nfs_handle.v2, &res.fhs_fh, NFS_FHSIZE);
220 #ifdef HAVE_FS_NFS3
221   }
222 #endif /* HAVE_FS_NFS3 */
223 
224   if (!fp->fh_error) {
225     dlog("got filehandle for %s:%s", fp->fh_fs->fs_host, fp->fh_path);
226   } else {
227     plog(XLOG_USER, "filehandle denied for %s:%s", fp->fh_fs->fs_host, fp->fh_path);
228     /*
229      * Force the error to be EACCES. It's debatable whether it should be
230      * ENOENT instead, but the server really doesn't give us any clues, and
231      * EACCES is more in line with the "filehandle denied" message.
232      */
233     fp->fh_error = EACCES;
234   }
235 
236   /*
237    * Wakeup anything sleeping on this filehandle
238    */
239   if (fp->fh_wchan) {
240     dlog("Calling wakeup on %#lx", (unsigned long) fp->fh_wchan);
241     wakeup(fp->fh_wchan);
242   }
243 }
244 
245 
246 /*
247  * Called when a filehandle appears via WebNFS
248  */
249 static void
got_nfs_fh_webnfs(voidp pkt,int len,struct sockaddr_in * sa,struct sockaddr_in * ia,opaque_t arg,int done)250 got_nfs_fh_webnfs(voidp pkt, int len, struct sockaddr_in *sa, struct sockaddr_in *ia, opaque_t arg, int done)
251 {
252   fh_cache *fp;
253   nfsdiropres res;
254 #ifdef HAVE_FS_NFS3
255   am_LOOKUP3res res3;
256 #endif /* HAVE_FS_NFS3 */
257 
258   fp = find_nfs_fhandle_cache(arg, done);
259   if (!fp)
260     return;
261 
262   /*
263    * retrieve the correct RPC reply for the file handle, based on the
264    * NFS protocol version.
265    */
266 #ifdef HAVE_FS_NFS3
267   if (fp->fh_nfs_version == NFS_VERSION3) {
268     memset(&res3, 0, sizeof(res3));
269     fp->fh_error = pickup_rpc_reply(pkt, len, (voidp) &res3,
270 				    (XDRPROC_T_TYPE) xdr_am_LOOKUP3res);
271     fp->fh_status = unx_error(res3.status);
272     memset(&fp->fh_nfs_handle.v3, 0, sizeof(am_nfs_fh3));
273     fp->fh_nfs_handle.v3.am_fh3_length = res3.res_u.ok.object.am_fh3_length;
274     memmove(fp->fh_nfs_handle.v3.am_fh3_data,
275 	    res3.res_u.ok.object.am_fh3_data,
276 	    fp->fh_nfs_handle.v3.am_fh3_length);
277   } else {
278 #endif /* HAVE_FS_NFS3 */
279     memset(&res, 0, sizeof(res));
280     fp->fh_error = pickup_rpc_reply(pkt, len, (voidp) &res,
281 				    (XDRPROC_T_TYPE) xdr_diropres);
282     fp->fh_status = unx_error(res.dr_status);
283     memmove(&fp->fh_nfs_handle.v2, &res.dr_u.dr_drok_u.drok_fhandle, NFS_FHSIZE);
284 #ifdef HAVE_FS_NFS3
285   }
286 #endif /* HAVE_FS_NFS3 */
287 
288   if (!fp->fh_error) {
289     dlog("got filehandle for %s:%s", fp->fh_fs->fs_host, fp->fh_path);
290   } else {
291     plog(XLOG_USER, "filehandle denied for %s:%s", fp->fh_fs->fs_host, fp->fh_path);
292     /*
293      * Force the error to be EACCES. It's debatable whether it should be
294      * ENOENT instead, but the server really doesn't give us any clues, and
295      * EACCES is more in line with the "filehandle denied" message.
296      */
297     fp->fh_error = EACCES;
298   }
299 
300   /*
301    * Wakeup anything sleeping on this filehandle
302    */
303   if (fp->fh_wchan) {
304     dlog("Calling wakeup on %#lx", (unsigned long) fp->fh_wchan);
305     wakeup(fp->fh_wchan);
306   }
307 }
308 
309 
310 void
flush_nfs_fhandle_cache(fserver * fs)311 flush_nfs_fhandle_cache(fserver *fs)
312 {
313   fh_cache *fp;
314 
315   ITER(fp, fh_cache, &fh_head) {
316     if (fp->fh_fs == fs || fs == NULL) {
317       /*
318        * Only invalidate port info for non-WebNFS servers
319        */
320       if (!(fp->fh_fs->fs_flags & FSF_WEBNFS))
321 	fp->fh_sin.sin_port = (u_short) 0;
322       fp->fh_error = -1;
323     }
324   }
325 }
326 
327 
328 static void
discard_fh(opaque_t arg)329 discard_fh(opaque_t arg)
330 {
331   fh_cache *fp = (fh_cache *) arg;
332 
333   rem_que(&fp->fh_q);
334   if (fp->fh_fs) {
335     dlog("Discarding filehandle for %s:%s", fp->fh_fs->fs_host, fp->fh_path);
336     free_srvr(fp->fh_fs);
337   }
338   XFREE(fp->fh_path);
339   XFREE(fp);
340 }
341 
342 
343 /*
344  * Determine the file handle for a node
345  */
346 static int
prime_nfs_fhandle_cache(char * path,fserver * fs,am_nfs_handle_t * fhbuf,mntfs * mf)347 prime_nfs_fhandle_cache(char *path, fserver *fs, am_nfs_handle_t *fhbuf, mntfs *mf)
348 {
349   fh_cache *fp, *fp_save = NULL;
350   int error;
351   int reuse_id = FALSE;
352 
353   dlog("Searching cache for %s:%s", fs->fs_host, path);
354 
355   /*
356    * First search the cache
357    */
358   ITER(fp, fh_cache, &fh_head) {
359     if (fs != fp->fh_fs  ||  !STREQ(path, fp->fh_path))
360       continue;			/* skip to next ITER item */
361     /* else we got a match */
362     switch (fp->fh_error) {
363     case 0:
364       plog(XLOG_INFO, "prime_nfs_fhandle_cache: NFS version %d", (int) fp->fh_nfs_version);
365 
366       error = fp->fh_error = fp->fh_status;
367 
368       if (error == 0) {
369 	if (mf->mf_flags & MFF_NFS_SCALEDOWN) {
370 	  fp_save = fp;
371 	  /* XXX: why reuse the ID? */
372 	  reuse_id = TRUE;
373 	  break;
374 	}
375 
376 	if (fhbuf) {
377 #ifdef HAVE_FS_NFS3
378 	  if (fp->fh_nfs_version == NFS_VERSION3) {
379 	    memmove((voidp) &(fhbuf->v3), (voidp) &(fp->fh_nfs_handle.v3),
380 		    sizeof(fp->fh_nfs_handle.v3));
381 	  } else
382 #endif /* HAVE_FS_NFS3 */
383 	    {
384 	      memmove((voidp) &(fhbuf->v2), (voidp) &(fp->fh_nfs_handle.v2),
385 		      sizeof(fp->fh_nfs_handle.v2));
386 	    }
387 	}
388 	if (fp->fh_cid)
389 	  untimeout(fp->fh_cid);
390 	fp->fh_cid = timeout(FH_TTL, discard_fh, (opaque_t) fp);
391       } else if (error == EACCES) {
392 	/*
393 	 * Now decode the file handle return code.
394 	 */
395 	plog(XLOG_INFO, "Filehandle denied for \"%s:%s\"",
396 	     fs->fs_host, path);
397       } else {
398 	errno = error;	/* XXX */
399 	plog(XLOG_INFO, "Filehandle error for \"%s:%s\": %m",
400 	     fs->fs_host, path);
401       }
402 
403       /*
404        * The error was returned from the remote mount daemon.
405        * Policy: this error will be cached for now...
406        */
407       return error;
408 
409     case -1:
410       /*
411        * Still thinking about it, but we can re-use.
412        */
413       fp_save = fp;
414       reuse_id = TRUE;
415       break;
416 
417     default:
418       /*
419        * Return the error.
420        * Policy: make sure we recompute if required again
421        * in case this was caused by a network failure.
422        * This can thrash mountd's though...  If you find
423        * your mountd going slowly then:
424        * 1.  Add a fork() loop to main.
425        * 2.  Remove the call to innetgr() and don't use
426        *     netgroups, especially if you don't use YP.
427        */
428       error = fp->fh_error;
429       fp->fh_error = -1;
430       return error;
431     }	/* end of switch statement */
432   } /* end of ITER loop */
433 
434   /*
435    * Not in cache
436    */
437   if (fp_save) {
438     fp = fp_save;
439     /*
440      * Re-use existing slot
441      */
442     untimeout(fp->fh_cid);
443     free_srvr(fp->fh_fs);
444     XFREE(fp->fh_path);
445   } else {
446     fp = ALLOC(struct fh_cache);
447     memset((voidp) fp, 0, sizeof(struct fh_cache));
448     ins_que(&fp->fh_q, &fh_head);
449   }
450   if (!reuse_id)
451     fp->fh_id = FHID_ALLOC();
452   fp->fh_wchan = get_mntfs_wchan(mf);
453   fp->fh_error = -1;
454   fp->fh_cid = timeout(FH_TTL, discard_fh, (opaque_t) fp);
455 
456   /*
457    * If fs->fs_ip is null, remote server is probably down.
458    */
459   if (!fs->fs_ip) {
460     /* Mark the fileserver down and invalid again */
461     fs->fs_flags &= ~FSF_VALID;
462     fs->fs_flags |= FSF_DOWN;
463     error = AM_ERRNO_HOST_DOWN;
464     return error;
465   }
466 
467   /*
468    * Either fp has been freshly allocated or the address has changed.
469    * Initialize address and nfs version.  Don't try to re-use the port
470    * information unless using WebNFS where the port is fixed either by
471    * the spec or the "port" mount option.
472    */
473   if (fp->fh_sin.sin_addr.s_addr != fs->fs_ip->sin_addr.s_addr) {
474     fp->fh_sin = *fs->fs_ip;
475     if (!(mf->mf_flags & MFF_WEBNFS))
476 	fp->fh_sin.sin_port = 0;
477     fp->fh_nfs_version = SET_FH_VERSION(fs);
478   }
479 
480   fp->fh_fs = dup_srvr(fs);
481   fp->fh_path = xstrdup(path);
482 
483   if (mf->mf_flags & MFF_WEBNFS)
484     error = webnfs_lookup(fp, got_nfs_fh_webnfs, get_mntfs_wchan(mf));
485   else
486     error = call_mountd(fp, MOUNTPROC_MNT, got_nfs_fh_mount, get_mntfs_wchan(mf));
487   if (error) {
488     /*
489      * Local error - cache for a short period
490      * just to prevent thrashing.
491      */
492     untimeout(fp->fh_cid);
493     fp->fh_cid = timeout(error < 0 ? 2 * ALLOWED_MOUNT_TIME : FH_TTL_ERROR,
494 			 discard_fh, (opaque_t) fp);
495     fp->fh_error = error;
496   } else {
497     error = fp->fh_error;
498   }
499 
500   return error;
501 }
502 
503 
504 int
make_nfs_auth(void)505 make_nfs_auth(void)
506 {
507   AUTH_CREATE_GIDLIST_TYPE group_wheel = 0;
508 
509   /* Some NFS mounts (particularly cross-domain) require FQDNs to succeed */
510 
511 #ifdef HAVE_TRANSPORT_TYPE_TLI
512   if (gopt.flags & CFM_FULLY_QUALIFIED_HOSTS) {
513     plog(XLOG_INFO, "Using NFS auth for FQHN \"%s\"", hostd);
514     nfs_auth = authsys_create(hostd, 0, 0, 1, &group_wheel);
515   } else {
516     nfs_auth = authsys_create_default();
517   }
518 #else /* not HAVE_TRANSPORT_TYPE_TLI */
519   if (gopt.flags & CFM_FULLY_QUALIFIED_HOSTS) {
520     plog(XLOG_INFO, "Using NFS auth for FQHN \"%s\"", hostd);
521     nfs_auth = authunix_create(hostd, 0, 0, 1, &group_wheel);
522   } else {
523     nfs_auth = authunix_create_default();
524   }
525 #endif /* not HAVE_TRANSPORT_TYPE_TLI */
526 
527   if (!nfs_auth)
528     return ENOBUFS;
529 
530   return 0;
531 }
532 
533 
534 static int
call_mountd(fh_cache * fp,u_long proc,fwd_fun fun,wchan_t wchan)535 call_mountd(fh_cache *fp, u_long proc, fwd_fun fun, wchan_t wchan)
536 {
537   struct rpc_msg mnt_msg;
538   int len;
539   char iobuf[UDPMSGSIZE];
540   int error;
541   u_long mnt_version;
542 
543   if (!nfs_auth) {
544     error = make_nfs_auth();
545     if (error)
546       return error;
547   }
548 
549   if (fp->fh_sin.sin_port == 0) {
550     u_short mountd_port;
551     error = get_mountd_port(fp->fh_fs, &mountd_port, wchan);
552     if (error)
553       return error;
554     fp->fh_sin.sin_port = mountd_port;
555     dlog("%s: New %d mountd port", __func__, fp->fh_sin.sin_port);
556   } else
557     dlog("%s: Already had %d mountd port", __func__, fp->fh_sin.sin_port);
558 
559   /* find the right version of the mount protocol */
560 #ifdef HAVE_FS_NFS3
561   if (fp->fh_nfs_version == NFS_VERSION3)
562     mnt_version = AM_MOUNTVERS3;
563   else
564 #endif /* HAVE_FS_NFS3 */
565     mnt_version = MOUNTVERS;
566   plog(XLOG_INFO, "call_mountd: NFS version %d, mount version %d",
567        (int) fp->fh_nfs_version, (int) mnt_version);
568 
569   rpc_msg_init(&mnt_msg, MOUNTPROG, mnt_version, MOUNTPROC_NULL);
570   len = make_rpc_packet(iobuf,
571 			sizeof(iobuf),
572 			proc,
573 			&mnt_msg,
574 			(voidp) &fp->fh_path,
575 			(XDRPROC_T_TYPE) xdr_nfspath,
576 			nfs_auth);
577 
578   if (len > 0) {
579     error = fwd_packet(MK_RPC_XID(RPC_XID_MOUNTD, fp->fh_id),
580 		       iobuf,
581 		       len,
582 		       &fp->fh_sin,
583 		       &fp->fh_sin,
584 		       (opaque_t) ((long) fp->fh_id), /* cast to long needed for 64-bit archs */
585 		       fun);
586   } else {
587     error = -len;
588   }
589 
590   /*
591    * It may be the case that we're sending to the wrong MOUNTD port.  This
592    * occurs if mountd is restarted on the server after the port has been
593    * looked up and stored in the filehandle cache somewhere.  The correct
594    * solution, if we're going to cache port numbers is to catch the ICMP
595    * port unreachable reply from the server and cause the portmap request
596    * to be redone.  The quick solution here is to invalidate the MOUNTD
597    * port.
598    */
599   fp->fh_sin.sin_port = 0;
600 
601   return error;
602 }
603 
604 
605 static int
webnfs_lookup(fh_cache * fp,fwd_fun fun,wchan_t wchan)606 webnfs_lookup(fh_cache *fp, fwd_fun fun, wchan_t wchan)
607 {
608   struct rpc_msg wnfs_msg;
609   int len;
610   char iobuf[UDPMSGSIZE];
611   int error;
612   u_long proc;
613   XDRPROC_T_TYPE xdr_fn;
614   voidp argp;
615   nfsdiropargs args;
616 #ifdef HAVE_FS_NFS3
617   am_LOOKUP3args args3;
618 #endif /* HAVE_FS_NFS3 */
619   char *wnfs_path;
620   size_t l;
621 
622   if (!nfs_auth) {
623     error = make_nfs_auth();
624     if (error)
625       return error;
626   }
627 
628   if (fp->fh_sin.sin_port == 0) {
629     /* FIXME: wrong, don't discard sin_port in the first place for WebNFS. */
630     plog(XLOG_WARNING, "webnfs_lookup: port == 0 for nfs on %s, fixed",
631 	 fp->fh_fs->fs_host);
632     fp->fh_sin.sin_port = htons(NFS_PORT);
633   }
634 
635   /*
636    * Use native path like the rest of amd (cf. RFC 2054, 6.1).
637    */
638   l = strlen(fp->fh_path) + 2;
639   wnfs_path = (char *) xmalloc(l);
640   wnfs_path[0] = 0x80;
641   xstrlcpy(wnfs_path + 1, fp->fh_path, l - 1);
642 
643   /* find the right program and lookup procedure */
644 #ifdef HAVE_FS_NFS3
645   if (fp->fh_nfs_version == NFS_VERSION3) {
646     proc = AM_NFSPROC3_LOOKUP;
647     xdr_fn = (XDRPROC_T_TYPE) xdr_am_LOOKUP3args;
648     argp = &args3;
649     /* WebNFS public file handle */
650     args3.what.dir.am_fh3_length = 0;
651     args3.what.name = wnfs_path;
652   } else {
653 #endif /* HAVE_FS_NFS3 */
654     proc = NFSPROC_LOOKUP;
655     xdr_fn = (XDRPROC_T_TYPE) xdr_diropargs;
656     argp = &args;
657     /* WebNFS public file handle */
658     memset(&args.da_fhandle, 0, NFS_FHSIZE);
659     args.da_name = wnfs_path;
660 #ifdef HAVE_FS_NFS3
661   }
662 #endif /* HAVE_FS_NFS3 */
663 
664   plog(XLOG_INFO, "webnfs_lookup: NFS version %d", (int) fp->fh_nfs_version);
665 
666   rpc_msg_init(&wnfs_msg, NFS_PROGRAM, fp->fh_nfs_version, proc);
667   len = make_rpc_packet(iobuf,
668 			sizeof(iobuf),
669 			proc,
670 			&wnfs_msg,
671 			argp,
672 			(XDRPROC_T_TYPE) xdr_fn,
673 			nfs_auth);
674 
675   if (len > 0) {
676     error = fwd_packet(MK_RPC_XID(RPC_XID_WEBNFS, fp->fh_id),
677 		       iobuf,
678 		       len,
679 		       &fp->fh_sin,
680 		       &fp->fh_sin,
681 		       (opaque_t) ((long) fp->fh_id), /* cast to long needed for 64-bit archs */
682 		       fun);
683   } else {
684     error = -len;
685   }
686 
687   XFREE(wnfs_path);
688   return error;
689 }
690 
691 
692 /*
693  * NFS needs the local filesystem, remote filesystem
694  * remote hostname.
695  * Local filesystem defaults to remote and vice-versa.
696  */
697 static char *
nfs_match(am_opts * fo)698 nfs_match(am_opts *fo)
699 {
700   char *xmtab;
701   size_t l;
702 
703   if (fo->opt_fs && !fo->opt_rfs)
704     fo->opt_rfs = fo->opt_fs;
705   if (!fo->opt_rfs) {
706     plog(XLOG_USER, "nfs: no remote filesystem specified");
707     return NULL;
708   }
709   if (!fo->opt_rhost) {
710     plog(XLOG_USER, "nfs: no remote host specified");
711     return NULL;
712   }
713 
714   /*
715    * Determine magic cookie to put in mtab
716    */
717   l = strlen(fo->opt_rhost) + strlen(fo->opt_rfs) + 2;
718   xmtab = (char *) xmalloc(l);
719   xsnprintf(xmtab, l, "%s:%s", fo->opt_rhost, fo->opt_rfs);
720   dlog("NFS: mounting remote server \"%s\", remote fs \"%s\" on \"%s\"",
721        fo->opt_rhost, fo->opt_rfs, fo->opt_fs);
722 
723   return xmtab;
724 }
725 
726 
727 /*
728  * Initialize am structure for nfs
729  */
730 static int
nfs_init(mntfs * mf)731 nfs_init(mntfs *mf)
732 {
733   int error;
734   am_nfs_handle_t fhs;
735   char *colon;
736 
737 #ifdef NO_FALLBACK
738   /*
739    * We don't need file handles for NFS version 4, but we can fall back to
740    * version 3, so we allocate anyway
741    */
742 #ifdef HAVE_FS_NFS4
743   if (mf->mf_server->fs_version == NFS_VERSION4)
744     return 0;
745 #endif /* HAVE_FS_NFS4 */
746 #endif /* NO_FALLBACK */
747 
748   if (mf->mf_private) {
749     if (mf->mf_flags & MFF_NFS_SCALEDOWN) {
750       fserver *fs;
751 
752       /* tell remote mountd that we're done with this filehandle */
753       mf->mf_ops->umounted(mf);
754 
755       mf->mf_prfree(mf->mf_private);
756       mf->mf_private = NULL;
757       mf->mf_prfree = NULL;
758 
759       fs = mf->mf_ops->ffserver(mf);
760       free_srvr(mf->mf_server);
761       mf->mf_server = fs;
762     } else
763       return 0;
764   }
765 
766   colon = strchr(mf->mf_info, ':');
767   if (colon == 0)
768     return ENOENT;
769 
770   error = prime_nfs_fhandle_cache(colon + 1, mf->mf_server, &fhs, mf);
771   if (!error) {
772     mf->mf_private = (opaque_t) ALLOC(am_nfs_handle_t);
773     mf->mf_prfree = (void (*)(opaque_t)) free;
774     memmove(mf->mf_private, (voidp) &fhs, sizeof(fhs));
775   }
776   return error;
777 }
778 
779 
780 int
mount_nfs_fh(am_nfs_handle_t * fhp,char * mntdir,char * fs_name,mntfs * mf)781 mount_nfs_fh(am_nfs_handle_t *fhp, char *mntdir, char *fs_name, mntfs *mf)
782 {
783   MTYPE_TYPE type;
784   char *colon;
785   char *xopts=NULL, transp_timeo_opts[40], transp_retrans_opts[40];
786   char host[MAXHOSTNAMELEN + MAXPATHLEN + 2];
787   fserver *fs = mf->mf_server;
788   u_long nfs_version = fs->fs_version;
789   char *nfs_proto = fs->fs_proto; /* "tcp" or "udp" */
790   int on_autofs = mf->mf_flags & MFF_ON_AUTOFS;
791   int error;
792   int genflags;
793   int retry;
794   int proto = AMU_TYPE_NONE;
795   mntent_t mnt;
796   void *argsp;
797   nfs_args_t nfs_args;
798 #ifdef HAVE_FS_NFS4
799   nfs4_args_t nfs4_args;
800 #endif /* HAVE_FS_NFS4 */
801 
802   /*
803    * Extract HOST name to give to kernel.
804    * Some systems like osf1/aix3/bsd44 variants may need old code
805    * for NFS_ARGS_NEEDS_PATH.
806    */
807   if (!(colon = strchr(fs_name, ':')))
808     return ENOENT;
809 #ifdef MOUNT_TABLE_ON_FILE
810   *colon = '\0';
811 #endif /* MOUNT_TABLE_ON_FILE */
812   xstrlcpy(host, fs_name, sizeof(host));
813 #ifdef MOUNT_TABLE_ON_FILE
814   *colon = ':';
815 #endif /* MOUNT_TABLE_ON_FILE */
816 #ifdef MAXHOSTNAMELEN
817   /* most kernels have a name length restriction */
818   if (strlen(host) >= MAXHOSTNAMELEN)
819     xstrlcpy(host + MAXHOSTNAMELEN - 3, "..",
820 	     sizeof(host) - MAXHOSTNAMELEN + 3);
821 #endif /* MAXHOSTNAMELEN */
822 
823   /*
824    * Create option=VAL for udp/tcp specific timeouts and retrans values, but
825    * only if these options were specified.
826    */
827 
828   transp_timeo_opts[0] = transp_retrans_opts[0] = '\0';	/* initialize */
829   if (STREQ(nfs_proto, "udp"))
830     proto = AMU_TYPE_UDP;
831   else if (STREQ(nfs_proto, "tcp"))
832     proto = AMU_TYPE_TCP;
833   if (proto != AMU_TYPE_NONE) {
834     if (gopt.amfs_auto_timeo[proto] > 0)
835       xsnprintf(transp_timeo_opts, sizeof(transp_timeo_opts), "%s=%d,",
836 		MNTTAB_OPT_TIMEO, gopt.amfs_auto_timeo[proto]);
837     if (gopt.amfs_auto_retrans[proto] > 0)
838       xsnprintf(transp_retrans_opts, sizeof(transp_retrans_opts), "%s=%d,",
839 		MNTTAB_OPT_RETRANS, gopt.amfs_auto_retrans[proto]);
840   }
841 
842   if (mf->mf_remopts && *mf->mf_remopts &&
843       !islocalnet(fs->fs_ip->sin_addr.s_addr)) {
844     plog(XLOG_INFO, "Using remopts=\"%s\"", mf->mf_remopts);
845     /* use transp_opts first, so map-specific opts will override */
846     xopts = str3cat(xopts, transp_timeo_opts, transp_retrans_opts, mf->mf_remopts);
847   } else {
848     /* use transp_opts first, so map-specific opts will override */
849     xopts = str3cat(xopts, transp_timeo_opts, transp_retrans_opts, mf->mf_mopts);
850   }
851 
852   memset((voidp) &mnt, 0, sizeof(mnt));
853   mnt.mnt_dir = mntdir;
854   mnt.mnt_fsname = fs_name;
855   mnt.mnt_opts = xopts;
856 
857   /*
858    * Set mount types accordingly
859    */
860 #ifdef HAVE_FS_NFS3
861   if (nfs_version == NFS_VERSION3) {
862     type = MOUNT_TYPE_NFS3;
863     /*
864      * Systems that include the mount table "vers" option generally do not
865      * set the mnttab entry to "nfs3", but to "nfs" and then they set
866      * "vers=3".  Setting it to "nfs3" works, but it may break some things
867      * like "df -t nfs" and the "quota" program (esp. on Solaris and Irix).
868      * So on those systems, set it to "nfs".
869      * Note: MNTTAB_OPT_VERS is always set for NFS3 (see am_compat.h).
870      */
871     argsp = &nfs_args;
872 # if defined(MNTTAB_OPT_VERS) && defined(MOUNT_TABLE_ON_FILE)
873     mnt.mnt_type = MNTTAB_TYPE_NFS;
874 # else /* defined(MNTTAB_OPT_VERS) && defined(MOUNT_TABLE_ON_FILE) */
875     mnt.mnt_type = MNTTAB_TYPE_NFS3;
876 # endif /* defined(MNTTAB_OPT_VERS) && defined(MOUNT_TABLE_ON_FILE) */
877 # ifdef HAVE_FS_NFS4
878   } else if (nfs_version == NFS_VERSION4) {
879     argsp = &nfs4_args;
880     type = MOUNT_TYPE_NFS4;
881     mnt.mnt_type = MNTTAB_TYPE_NFS4;
882 # endif /* HAVE_FS_NFS4 */
883   } else
884 #endif /* HAVE_FS_NFS3 */
885   {
886     argsp = &nfs_args;
887     type = MOUNT_TYPE_NFS;
888     mnt.mnt_type = MNTTAB_TYPE_NFS;
889   }
890   plog(XLOG_INFO, "mount_nfs_fh: NFS version %d", (int) nfs_version);
891   plog(XLOG_INFO, "mount_nfs_fh: using NFS transport %s", nfs_proto);
892 
893   retry = hasmntval(&mnt, MNTTAB_OPT_RETRY);
894   if (retry <= 0)
895     retry = 1;			/* XXX */
896 
897   genflags = compute_mount_flags(&mnt);
898 #ifdef HAVE_FS_AUTOFS
899   if (on_autofs)
900     genflags |= autofs_compute_mount_flags(&mnt);
901 #endif /* HAVE_FS_AUTOFS */
902 
903    /* setup the many fields and flags within nfs_args */
904    compute_nfs_args(argsp,
905 		    &mnt,
906 		    genflags,
907 		    NULL,	/* struct netconfig *nfsncp */
908 		    fs->fs_ip,
909 		    nfs_version,
910 		    nfs_proto,
911 		    fhp,
912 		    host,
913 		    fs_name);
914 
915   /* finally call the mounting function */
916   if (amuDebug(D_TRACE)) {
917     print_nfs_args(argsp, nfs_version);
918     plog(XLOG_DEBUG, "Generic mount flags 0x%x used for NFS mount", genflags);
919   }
920   error = mount_fs(&mnt, genflags, argsp, retry, type,
921 		   nfs_version, nfs_proto, mnttab_file_name, on_autofs);
922   XFREE(mnt.mnt_opts);
923   discard_nfs_args(argsp, nfs_version);
924 
925 #ifdef HAVE_FS_NFS4
926 # ifndef NO_FALLBACK
927   /*
928    * If we are using a v4 file handle, we try a v3 if we get back:
929    * 	ENOENT: NFS v4 has a different export list than v3
930    * 	EPERM: Kernels <= 2.6.18 return that, instead of ENOENT
931    */
932   if ((error == ENOENT || error == EPERM) && nfs_version == NFS_VERSION4) {
933     plog(XLOG_DEBUG, "Could not find NFS 4 mount, trying again with NFS 3");
934     fs->fs_version = NFS_VERSION3;
935     error = mount_nfs_fh(fhp, mntdir, fs_name, mf);
936     if (error)
937       fs->fs_version = NFS_VERSION4;
938   }
939 # endif /* NO_FALLBACK */
940 #endif /* HAVE_FS_NFS4 */
941 
942   return error;
943 }
944 
945 
946 static int
nfs_mount(am_node * am,mntfs * mf)947 nfs_mount(am_node *am, mntfs *mf)
948 {
949   int error = 0;
950   mntent_t mnt;
951 
952   if (!mf->mf_private && mf->mf_server->fs_version != 4) {
953     plog(XLOG_ERROR, "Missing filehandle for %s", mf->mf_info);
954     return EINVAL;
955   }
956 
957   if (mf->mf_mopts == NULL) {
958     plog(XLOG_ERROR, "Missing mount options for %s", mf->mf_info);
959     return EINVAL;
960   }
961 
962   mnt.mnt_opts = mf->mf_mopts;
963   if (amu_hasmntopt(&mnt, "softlookup") ||
964       (amu_hasmntopt(&mnt, "soft") && !amu_hasmntopt(&mnt, "nosoftlookup")))
965     am->am_flags |= AMF_SOFTLOOKUP;
966 
967   error = mount_nfs_fh((am_nfs_handle_t *) mf->mf_private,
968 		       mf->mf_mount,
969 		       mf->mf_info,
970 		       mf);
971 
972   if (error) {
973     errno = error;
974     dlog("mount_nfs: %m");
975   }
976 
977   return error;
978 }
979 
980 
981 static int
nfs_umount(am_node * am,mntfs * mf)982 nfs_umount(am_node *am, mntfs *mf)
983 {
984   int unmount_flags, new_unmount_flags, error;
985 
986   dlog("attempting nfs umount");
987   unmount_flags = (mf->mf_flags & MFF_ON_AUTOFS) ? AMU_UMOUNT_AUTOFS : 0;
988   error = UMOUNT_FS(mf->mf_mount, mnttab_file_name, unmount_flags);
989 
990 #if defined(HAVE_UMOUNT2) && (defined(MNT2_GEN_OPT_FORCE) || defined(MNT2_GEN_OPT_DETACH))
991   /*
992    * If the attempt to unmount failed with EBUSY, and this fserver was
993    * marked for forced unmounts, then use forced/lazy unmounts.
994    */
995   if (error == EBUSY &&
996       gopt.flags & CFM_FORCED_UNMOUNTS &&
997       mf->mf_server->fs_flags & FSF_FORCE_UNMOUNT) {
998     plog(XLOG_INFO, "EZK: nfs_umount: trying forced/lazy unmounts");
999     /*
1000      * XXX: turning off the FSF_FORCE_UNMOUNT may not be perfectly
1001      * incorrect.  Multiple nodes may need to be timed out and restarted for
1002      * a single hung fserver.
1003      */
1004     mf->mf_server->fs_flags &= ~FSF_FORCE_UNMOUNT;
1005     new_unmount_flags = unmount_flags | AMU_UMOUNT_FORCE | AMU_UMOUNT_DETACH;
1006     error = UMOUNT_FS(mf->mf_mount, mnttab_file_name, new_unmount_flags);
1007   }
1008 #endif /* HAVE_UMOUNT2 && (MNT2_GEN_OPT_FORCE || MNT2_GEN_OPT_DETACH) */
1009 
1010   /*
1011    * Here is some code to unmount 'restarted' file systems.
1012    * The restarted file systems are marked as 'nfs', not
1013    * 'host', so we only have the map information for the
1014    * the top-level mount.  The unmount will fail (EBUSY)
1015    * if there are anything else from the NFS server mounted
1016    * below the mount-point.  This code checks to see if there
1017    * is anything mounted with the same prefix as the
1018    * file system to be unmounted ("/a/b/c" when unmounting "/a/b").
1019    * If there is, and it is a 'restarted' file system, we unmount
1020    * it.
1021    * Added by Mike Mitchell, mcm@unx.sas.com, 09/08/93
1022    */
1023   if (error == EBUSY) {
1024     mntfs *new_mf;
1025     int len = strlen(mf->mf_mount);
1026     int didsome = 0;
1027 
1028     ITER(new_mf, mntfs, &mfhead) {
1029       if (new_mf->mf_ops != mf->mf_ops ||
1030 	  new_mf->mf_refc > 1 ||
1031 	  mf == new_mf ||
1032 	  ((new_mf->mf_flags & (MFF_MOUNTED | MFF_UNMOUNTING | MFF_RESTART)) == (MFF_MOUNTED | MFF_RESTART)))
1033 	continue;
1034 
1035       if (NSTREQ(mf->mf_mount, new_mf->mf_mount, len) &&
1036 	  new_mf->mf_mount[len] == '/') {
1037 	new_unmount_flags =
1038 	  (new_mf->mf_flags & MFF_ON_AUTOFS) ? AMU_UMOUNT_AUTOFS : 0;
1039 	UMOUNT_FS(new_mf->mf_mount, mnttab_file_name, new_unmount_flags);
1040 	didsome = 1;
1041       }
1042     }
1043     if (didsome)
1044       error = UMOUNT_FS(mf->mf_mount, mnttab_file_name, unmount_flags);
1045   }
1046   if (error)
1047     return error;
1048 
1049   return 0;
1050 }
1051 
1052 
1053 static void
nfs_umounted(mntfs * mf)1054 nfs_umounted(mntfs *mf)
1055 {
1056   fserver *fs;
1057   char *colon, *path;
1058 
1059   if (mf->mf_error || mf->mf_refc > 1)
1060     return;
1061 
1062   /*
1063    * No need to inform mountd when WebNFS is in use.
1064    */
1065   if (mf->mf_flags & MFF_WEBNFS)
1066     return;
1067 
1068   /*
1069    * Call the mount daemon on the server to announce that we are not using
1070    * the fs any more.
1071    *
1072    * XXX: This is *wrong*.  The mountd should be called when the fhandle is
1073    * flushed from the cache, and a reference held to the cached entry while
1074    * the fs is mounted...
1075    */
1076   fs = mf->mf_server;
1077   colon = path = strchr(mf->mf_info, ':');
1078   if (fs && colon) {
1079     fh_cache f;
1080 
1081     dlog("calling mountd for %s", mf->mf_info);
1082     *path++ = '\0';
1083     f.fh_path = path;
1084     f.fh_sin = *fs->fs_ip;
1085     f.fh_sin.sin_port = (u_short) 0;
1086     f.fh_nfs_version = SET_FH_VERSION(fs);
1087     f.fh_fs = fs;
1088     f.fh_id = 0;
1089     f.fh_error = 0;
1090     prime_nfs_fhandle_cache(colon + 1, mf->mf_server, (am_nfs_handle_t *) NULL, mf);
1091     call_mountd(&f, MOUNTPROC_UMNT, (fwd_fun *) NULL, (wchan_t) NULL);
1092     *colon = ':';
1093   }
1094 }
1095