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