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