]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/amd/amd/amfs_host.c
MFC r308493, r308619: Update amd from am-utils 6.1.5 to 6.2.
[FreeBSD/stable/10.git] / contrib / amd / amd / amfs_host.c
1 /*
2  * Copyright (c) 1997-2014 Erez Zadok
3  * Copyright (c) 1990 Jan-Simon Pendry
4  * Copyright (c) 1990 Imperial College of Science, Technology & Medicine
5  * Copyright (c) 1990 The Regents of the University of California.
6  * All rights reserved.
7  *
8  * This code is derived from software contributed to Berkeley by
9  * Jan-Simon Pendry at Imperial College, London.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  *
35  *
36  * File: am-utils/amd/amfs_host.c
37  *
38  */
39
40 /*
41  * NFS host file system.
42  * Mounts all exported filesystems from a given host.
43  * This has now degenerated into a mess but will not
44  * be rewritten.  Amd 6 will support the abstractions
45  * needed to make this work correctly.
46  */
47
48 #ifdef HAVE_CONFIG_H
49 # include <config.h>
50 #endif /* HAVE_CONFIG_H */
51 #include <am_defs.h>
52 #include <amd.h>
53
54 static char *amfs_host_match(am_opts *fo);
55 static int amfs_host_init(mntfs *mf);
56 static int amfs_host_mount(am_node *am, mntfs *mf);
57 static int amfs_host_umount(am_node *am, mntfs *mf);
58 static void amfs_host_umounted(mntfs *mf);
59
60 /*
61  * Ops structure
62  */
63 am_ops amfs_host_ops =
64 {
65   "host",
66   amfs_host_match,
67   amfs_host_init,
68   amfs_host_mount,
69   amfs_host_umount,
70   amfs_error_lookup_child,
71   amfs_error_mount_child,
72   amfs_error_readdir,
73   0,                            /* amfs_host_readlink */
74   0,                            /* amfs_host_mounted */
75   amfs_host_umounted,
76   find_nfs_srvr,
77   0,                            /* amfs_host_get_wchan */
78   FS_MKMNT | FS_BACKGROUND | FS_AMQINFO,
79 #ifdef HAVE_FS_AUTOFS
80   AUTOFS_HOST_FS_FLAGS,
81 #endif /* HAVE_FS_AUTOFS */
82 };
83
84
85 /*
86  * Determine the mount point:
87  *
88  * The next change we put in to better handle PCs.  This is a bit
89  * disgusting, so you'd better sit down.  We change the make_mntpt function
90  * to look for exported file systems without a leading '/'.  If they don't
91  * have a leading '/', we add one.  If the export is 'a:' through 'z:'
92  * (without a leading slash), we change it to 'a%' (or b% or z%).  This
93  * allows the entire PC disk to be mounted.
94  */
95 static void
96 make_mntpt(char *mntpt, size_t l, const exports ex, const char *mf_mount)
97 {
98   if (ex->ex_dir[0] == '/') {
99     if (ex->ex_dir[1] == 0)
100       xstrlcpy(mntpt, mf_mount, l);
101     else
102       xsnprintf(mntpt, l, "%s%s", mf_mount, ex->ex_dir);
103   } else if (ex->ex_dir[0] >= 'a' &&
104              ex->ex_dir[0] <= 'z' &&
105              ex->ex_dir[1] == ':' &&
106              ex->ex_dir[2] == '/' &&
107              ex->ex_dir[3] == 0)
108     xsnprintf(mntpt, l, "%s/%c%%", mf_mount, ex->ex_dir[0]);
109   else
110     xsnprintf(mntpt, l, "%s/%s", mf_mount, ex->ex_dir);
111 }
112
113
114 /*
115  * Execute needs the same as NFS plus a helper command
116  */
117 static char *
118 amfs_host_match(am_opts *fo)
119 {
120   extern am_ops nfs_ops;
121
122   /*
123    * Make sure rfs is specified to keep nfs_match happy...
124    */
125   if (!fo->opt_rfs)
126     fo->opt_rfs = "/";
127
128   return (*nfs_ops.fs_match) (fo);
129 }
130
131
132 static int
133 amfs_host_init(mntfs *mf)
134 {
135   u_short mountd_port;
136
137   if (strchr(mf->mf_info, ':') == 0)
138     return ENOENT;
139
140   /*
141    * This is primarily to schedule a wakeup so that as soon
142    * as our fileserver is ready, we can continue setting up
143    * the host filesystem.  If we don't do this, the standard
144    * amfs_auto code will set up a fileserver structure, but it will
145    * have to wait for another nfs request from the client to come
146    * in before finishing.  Our way is faster since we don't have
147    * to wait for the client to resend its request (which could
148    * take a second or two).
149    */
150   /*
151    * First, we find the fileserver for this mntfs and then call
152    * get_mountd_port with our mntfs passed as the wait channel.
153    * get_mountd_port will check some things and then schedule
154    * it so that when the fileserver is ready, a wakeup is done
155    * on this mntfs.   amfs_cont() is already sleeping on this mntfs
156    * so as soon as that wakeup happens amfs_cont() is called and
157    * this mount is retried.
158    */
159   if (mf->mf_server)
160     /*
161      * We don't really care if there's an error returned.
162      * Since this is just to help speed things along, the
163      * error will get handled properly elsewhere.
164      */
165     get_mountd_port(mf->mf_server, &mountd_port, get_mntfs_wchan(mf));
166
167   return 0;
168 }
169
170
171 static int
172 do_mount(am_nfs_handle_t *fhp, char *mntdir, char *fs_name, mntfs *mf)
173 {
174   struct stat stb;
175
176   dlog("amfs_host: mounting fs %s on %s\n", fs_name, mntdir);
177
178   (void) mkdirs(mntdir, 0555);
179   if (stat(mntdir, &stb) < 0 || (stb.st_mode & S_IFMT) != S_IFDIR) {
180     plog(XLOG_ERROR, "No mount point for %s - skipping", mntdir);
181     return ENOENT;
182   }
183
184   return mount_nfs_fh(fhp, mntdir, fs_name, mf);
185 }
186
187
188 static int
189 sortfun(const voidp x, const voidp y)
190 {
191   exports *a = (exports *) x;
192   exports *b = (exports *) y;
193
194   return strcmp((*a)->ex_dir, (*b)->ex_dir);
195 }
196
197
198 /*
199  * Get filehandle
200  */
201 static int
202 fetch_fhandle(CLIENT *client, char *dir, am_nfs_handle_t *fhp, u_long nfs_version)
203 {
204   struct timeval tv;
205   enum clnt_stat clnt_stat;
206   struct fhstatus res;
207 #ifdef HAVE_FS_NFS3
208   struct am_mountres3 res3;
209 #endif /* HAVE_FS_NFS3 */
210
211   /*
212    * Pick a number, any number...
213    */
214   tv.tv_sec = 20;
215   tv.tv_usec = 0;
216
217   dlog("Fetching fhandle for %s", dir);
218
219   /*
220    * Call the mount daemon on the remote host to
221    * get the filehandle.  Use NFS version specific call.
222    */
223
224   plog(XLOG_INFO, "fetch_fhandle: NFS version %d", (int) nfs_version);
225 #ifdef HAVE_FS_NFS3
226   if (nfs_version == NFS_VERSION3
227 #ifdef HAVE_FS_NFS4
228 #ifndef NO_FALLBACK
229       || nfs_version == NFS_VERSION4
230 #endif /* NO_FALLBACK */
231 #endif /* HAVE_FS_NFS4 */
232     ) {
233
234     memset((char *) &res3, 0, sizeof(res3));
235     clnt_stat = clnt_call(client,
236                           MOUNTPROC_MNT,
237                           (XDRPROC_T_TYPE) xdr_dirpath,
238                           (SVC_IN_ARG_TYPE) &dir,
239                           (XDRPROC_T_TYPE) xdr_am_mountres3,
240                           (SVC_IN_ARG_TYPE) &res3,
241                           tv);
242     if (clnt_stat != RPC_SUCCESS) {
243       plog(XLOG_ERROR, "mountd rpc failed: %s", clnt_sperrno(clnt_stat));
244       return EIO;
245     }
246     /* Check the status of the filehandle */
247     if ((errno = res3.fhs_status)) {
248       dlog("fhandle fetch for mount version 3 failed: %m");
249       return errno;
250     }
251     memset((voidp) &fhp->v3, 0, sizeof(am_nfs_fh3));
252     fhp->v3.am_fh3_length = res3.mountres3_u.mountinfo.fhandle.fhandle3_len;
253     memmove(fhp->v3.am_fh3_data,
254             res3.mountres3_u.mountinfo.fhandle.fhandle3_val,
255             fhp->v3.am_fh3_length);
256   } else {                      /* not NFS_VERSION3 mount */
257 #endif /* HAVE_FS_NFS3 */
258     clnt_stat = clnt_call(client,
259                           MOUNTPROC_MNT,
260                           (XDRPROC_T_TYPE) xdr_dirpath,
261                           (SVC_IN_ARG_TYPE) &dir,
262                           (XDRPROC_T_TYPE) xdr_fhstatus,
263                           (SVC_IN_ARG_TYPE) &res,
264                           tv);
265     if (clnt_stat != RPC_SUCCESS) {
266       plog(XLOG_ERROR, "mountd rpc failed: %s", clnt_sperrno(clnt_stat));
267       return EIO;
268     }
269     /* Check status of filehandle */
270     if (res.fhs_status) {
271       errno = res.fhs_status;
272       dlog("fhandle fetch for mount version 1 failed: %m");
273       return errno;
274     }
275     memmove(&fhp->v2, &res.fhs_fh, NFS_FHSIZE);
276 #ifdef HAVE_FS_NFS3
277   } /* end of "if (nfs_version == NFS_VERSION3)" statement */
278 #endif /* HAVE_FS_NFS3 */
279
280   /* all is well */
281   return 0;
282 }
283
284
285 /*
286  * Scan mount table to see if something already mounted
287  */
288 static int
289 already_mounted(mntlist *mlist, char *dir)
290 {
291   mntlist *ml;
292
293   for (ml = mlist; ml; ml = ml->mnext)
294     if (STREQ(ml->mnt->mnt_dir, dir))
295       return 1;
296   return 0;
297 }
298
299
300 static int
301 amfs_host_mount(am_node *am, mntfs *mf)
302 {
303   struct timeval tv2;
304   CLIENT *client;
305   enum clnt_stat clnt_stat;
306   int n_export;
307   int j, k;
308   exports exlist = 0, ex;
309   exports *ep = NULL;
310   am_nfs_handle_t *fp = NULL;
311   char *host;
312   int error = 0;
313   struct sockaddr_in sin;
314   int sock = RPC_ANYSOCK;
315   int ok = FALSE;
316   mntlist *mlist;
317   char fs_name[MAXPATHLEN], *rfs_dir;
318   char mntpt[MAXPATHLEN];
319   struct timeval tv;
320   u_long mnt_version;
321
322   /*
323    * WebNFS servers don't necessarily run mountd.
324    */
325   if (mf->mf_flags & MFF_WEBNFS) {
326     plog(XLOG_ERROR, "amfs_host_mount: cannot support WebNFS");
327     return EIO;
328   }
329
330   /*
331    * Read the mount list
332    */
333   mlist = read_mtab(mf->mf_mount, mnttab_file_name);
334
335 #ifdef MOUNT_TABLE_ON_FILE
336   /*
337    * Unlock the mount list
338    */
339   unlock_mntlist();
340 #endif /* MOUNT_TABLE_ON_FILE */
341
342   /*
343    * Take a copy of the server hostname, address, and nfs version
344    * to mount version conversion.
345    */
346   host = mf->mf_server->fs_host;
347   sin = *mf->mf_server->fs_ip;
348   plog(XLOG_INFO, "amfs_host_mount: NFS version %d", (int) mf->mf_server->fs_version);
349 #ifdef HAVE_FS_NFS3
350   if (mf->mf_server->fs_version == NFS_VERSION3)
351     mnt_version = AM_MOUNTVERS3;
352   else
353 #endif /* HAVE_FS_NFS3 */
354     mnt_version = MOUNTVERS;
355
356   /*
357    * The original 10 second per try timeout is WAY too large, especially
358    * if we're only waiting 10 or 20 seconds max for the response.
359    * That would mean we'd try only once in 10 seconds, and we could
360    * lose the transmit or receive packet, and never try again.
361    * A 2-second per try timeout here is much more reasonable.
362    * 09/28/92 Mike Mitchell, mcm@unx.sas.com
363    */
364   tv.tv_sec = 2;
365   tv.tv_usec = 0;
366
367   /*
368    * Create a client attached to mountd
369    */
370   client = get_mount_client(host, &sin, &tv, &sock, mnt_version);
371   if (client == NULL) {
372 #ifdef HAVE_CLNT_SPCREATEERROR
373     plog(XLOG_ERROR, "get_mount_client failed for %s: %s",
374          host, clnt_spcreateerror(""));
375 #else /* not HAVE_CLNT_SPCREATEERROR */
376     plog(XLOG_ERROR, "get_mount_client failed for %s", host);
377 #endif /* not HAVE_CLNT_SPCREATEERROR */
378     error = EIO;
379     goto out;
380   }
381   if (!nfs_auth) {
382     error = make_nfs_auth();
383     if (error)
384       goto out;
385   }
386   client->cl_auth = nfs_auth;
387
388   dlog("Fetching export list from %s", host);
389
390   /*
391    * Fetch the export list
392    */
393   tv2.tv_sec = 10;
394   tv2.tv_usec = 0;
395   clnt_stat = clnt_call(client,
396                         MOUNTPROC_EXPORT,
397                         (XDRPROC_T_TYPE) xdr_void,
398                         0,
399                         (XDRPROC_T_TYPE) xdr_exports,
400                         (SVC_IN_ARG_TYPE) & exlist,
401                         tv2);
402   if (clnt_stat != RPC_SUCCESS) {
403     const char *msg = clnt_sperrno(clnt_stat);
404     plog(XLOG_ERROR, "host_mount rpc failed: %s", msg);
405     /* clnt_perror(client, "rpc"); */
406     error = EIO;
407     goto out;
408   }
409
410   /*
411    * Figure out how many exports were returned
412    */
413   for (n_export = 0, ex = exlist; ex; ex = ex->ex_next) {
414     n_export++;
415   }
416
417   /*
418    * Allocate an array of pointers into the list
419    * so that they can be sorted.  If the filesystem
420    * is already mounted then ignore it.
421    */
422   ep = (exports *) xmalloc(n_export * sizeof(exports));
423   for (j = 0, ex = exlist; ex; ex = ex->ex_next) {
424     make_mntpt(mntpt, sizeof(mntpt), ex, mf->mf_mount);
425     if (already_mounted(mlist, mntpt))
426       /* we have at least one mounted f/s, so don't fail the mount */
427       ok = TRUE;
428     else
429       ep[j++] = ex;
430   }
431   n_export = j;
432
433   /*
434    * Sort into order.
435    * This way the mounts are done in order down the tree,
436    * instead of any random order returned by the mount
437    * daemon (the protocol doesn't specify...).
438    */
439   qsort(ep, n_export, sizeof(exports), sortfun);
440
441   /*
442    * Allocate an array of filehandles
443    */
444   fp = (am_nfs_handle_t *) xmalloc(n_export * sizeof(am_nfs_handle_t));
445
446   /*
447    * Try to obtain filehandles for each directory.
448    * If a fetch fails then just zero out the array
449    * reference but discard the error.
450    */
451   for (j = k = 0; j < n_export; j++) {
452     /* Check and avoid a duplicated export entry */
453     if (j > k && ep[k] && STREQ(ep[j]->ex_dir, ep[k]->ex_dir)) {
454       dlog("avoiding dup fhandle requested for %s", ep[j]->ex_dir);
455       ep[j] = NULL;
456     } else {
457       k = j;
458       error = fetch_fhandle(client, ep[j]->ex_dir, &fp[j],
459                             mf->mf_server->fs_version);
460       if (error)
461         ep[j] = NULL;
462     }
463   }
464
465   /*
466    * Mount each filesystem for which we have a filehandle.
467    * If any of the mounts succeed then mark "ok" and return
468    * error code 0 at the end.  If they all fail then return
469    * the last error code.
470    */
471   xstrlcpy(fs_name, mf->mf_info, sizeof(fs_name));
472   if ((rfs_dir = strchr(fs_name, ':')) == (char *) NULL) {
473     plog(XLOG_FATAL, "amfs_host_mount: mf_info has no colon");
474     error = EINVAL;
475     goto out;
476   }
477   ++rfs_dir;
478   for (j = 0; j < n_export; j++) {
479     ex = ep[j];
480     if (ex) {
481       /*
482        * Note: the sizeof space left in rfs_dir is what's left in fs_name
483        * after strchr() above returned a pointer _inside_ fs_name.  The
484        * calculation below also takes into account that rfs_dir was
485        * incremented by the ++ above.
486        */
487       xstrlcpy(rfs_dir, ex->ex_dir, sizeof(fs_name) - (rfs_dir - fs_name));
488       make_mntpt(mntpt, sizeof(mntpt), ex, mf->mf_mount);
489       if (do_mount(&fp[j], mntpt, fs_name, mf) == 0)
490         ok = TRUE;
491     }
492   }
493
494   /*
495    * Clean up and exit
496    */
497 out:
498   discard_mntlist(mlist);
499   XFREE(ep);
500   XFREE(fp);
501   if (sock != RPC_ANYSOCK)
502     (void) amu_close(sock);
503   if (client)
504     clnt_destroy(client);
505   if (exlist)
506     xdr_pri_free((XDRPROC_T_TYPE) xdr_exports, (caddr_t) &exlist);
507   if (ok)
508     return 0;
509   return error;
510 }
511
512
513 /*
514  * Return true if pref is a directory prefix of dir.
515  *
516  * XXX TODO:
517  * Does not work if pref is "/".
518  */
519 static int
520 directory_prefix(char *pref, char *dir)
521 {
522   int len = strlen(pref);
523
524   if (!NSTREQ(pref, dir, len))
525     return FALSE;
526   if (dir[len] == '/' || dir[len] == '\0')
527     return TRUE;
528   return FALSE;
529 }
530
531
532 /*
533  * Unmount a mount tree
534  */
535 static int
536 amfs_host_umount(am_node *am, mntfs *mf)
537 {
538   mntlist *ml, *mprev;
539   int unmount_flags = (mf->mf_flags & MFF_ON_AUTOFS) ? AMU_UMOUNT_AUTOFS : 0;
540   int xerror = 0;
541
542   /*
543    * Read the mount list
544    */
545   mntlist *mlist = read_mtab(mf->mf_mount, mnttab_file_name);
546
547 #ifdef MOUNT_TABLE_ON_FILE
548   /*
549    * Unlock the mount list
550    */
551   unlock_mntlist();
552 #endif /* MOUNT_TABLE_ON_FILE */
553
554   /*
555    * Reverse list...
556    */
557   ml = mlist;
558   mprev = NULL;
559   while (ml) {
560     mntlist *ml2 = ml->mnext;
561     ml->mnext = mprev;
562     mprev = ml;
563     ml = ml2;
564   }
565   mlist = mprev;
566
567   /*
568    * Unmount all filesystems...
569    */
570   for (ml = mlist; ml && !xerror; ml = ml->mnext) {
571     char *dir = ml->mnt->mnt_dir;
572     if (directory_prefix(mf->mf_mount, dir)) {
573       int error;
574       dlog("amfs_host: unmounts %s", dir);
575       /*
576        * Unmount "dir"
577        */
578       error = UMOUNT_FS(dir, mnttab_file_name, unmount_flags);
579       /*
580        * Keep track of errors
581        */
582       if (error) {
583         /*
584          * If we have not already set xerror and error is not ENOENT,
585          * then set xerror equal to error and log it.
586          * 'xerror' is the return value for this function.
587          *
588          * We do not want to pass ENOENT as an error because if the
589          * directory does not exists our work is done anyway.
590          */
591         if (!xerror && error != ENOENT)
592           xerror = error;
593         if (error != EBUSY) {
594           errno = error;
595           plog(XLOG_ERROR, "Tree unmount of %s failed: %m", ml->mnt->mnt_dir);
596         }
597       } else {
598         (void) rmdirs(dir);
599       }
600     }
601   }
602
603   /*
604    * Throw away mount list
605    */
606   discard_mntlist(mlist);
607
608   /*
609    * Try to remount, except when we are shutting down.
610    */
611   if (xerror && amd_state != Finishing) {
612     xerror = amfs_host_mount(am, mf);
613     if (!xerror) {
614       /*
615        * Don't log this - it's usually too verbose
616        plog(XLOG_INFO, "Remounted host %s", mf->mf_info);
617        */
618       xerror = EBUSY;
619     }
620   }
621   return xerror;
622 }
623
624
625 /*
626  * Tell mountd we're done.
627  * This is not quite right, because we may still
628  * have other filesystems mounted, but the existing
629  * mountd protocol is badly broken anyway.
630  */
631 static void
632 amfs_host_umounted(mntfs *mf)
633 {
634   char *host;
635   CLIENT *client;
636   enum clnt_stat clnt_stat;
637   struct sockaddr_in sin;
638   int sock = RPC_ANYSOCK;
639   struct timeval tv;
640   u_long mnt_version;
641
642   if (mf->mf_error || mf->mf_refc > 1 || !mf->mf_server)
643     return;
644
645   /*
646    * WebNFS servers shouldn't ever get here.
647    */
648   if (mf->mf_flags & MFF_WEBNFS) {
649     plog(XLOG_ERROR, "amfs_host_umounted: cannot support WebNFS");
650     return;
651   }
652
653   /*
654    * Take a copy of the server hostname, address, and NFS version
655    * to mount version conversion.
656    */
657   host = mf->mf_server->fs_host;
658   sin = *mf->mf_server->fs_ip;
659   plog(XLOG_INFO, "amfs_host_umounted: NFS version %d", (int) mf->mf_server->fs_version);
660 #ifdef HAVE_FS_NFS3
661   if (mf->mf_server->fs_version == NFS_VERSION3)
662     mnt_version = AM_MOUNTVERS3;
663   else
664 #endif /* HAVE_FS_NFS3 */
665     mnt_version = MOUNTVERS;
666
667   /*
668    * Create a client attached to mountd
669    */
670   tv.tv_sec = 10;
671   tv.tv_usec = 0;
672   client = get_mount_client(host, &sin, &tv, &sock, mnt_version);
673   if (client == NULL) {
674 #ifdef HAVE_CLNT_SPCREATEERROR
675     plog(XLOG_ERROR, "get_mount_client failed for %s: %s",
676          host, clnt_spcreateerror(""));
677 #else /* not HAVE_CLNT_SPCREATEERROR */
678     plog(XLOG_ERROR, "get_mount_client failed for %s", host);
679 #endif /* not HAVE_CLNT_SPCREATEERROR */
680     goto out;
681   }
682
683   if (!nfs_auth) {
684     if (make_nfs_auth())
685       goto out;
686   }
687   client->cl_auth = nfs_auth;
688
689   dlog("Unmounting all from %s", host);
690
691   clnt_stat = clnt_call(client,
692                         MOUNTPROC_UMNTALL,
693                         (XDRPROC_T_TYPE) xdr_void,
694                         0,
695                         (XDRPROC_T_TYPE) xdr_void,
696                         0,
697                         tv);
698   if (clnt_stat != RPC_SUCCESS && clnt_stat != RPC_SYSTEMERROR) {
699     /* RPC_SYSTEMERROR seems to be returned for no good reason ... */
700     const char *msg = clnt_sperrno(clnt_stat);
701     plog(XLOG_ERROR, "unmount all from %s rpc failed: %s", host, msg);
702     goto out;
703   }
704
705 out:
706   if (sock != RPC_ANYSOCK)
707     (void) amu_close(sock);
708   if (client)
709     clnt_destroy(client);
710 }