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