| /* |
| * stropts.c -- NFS mount using C string to pass options to kernel |
| * |
| * Copyright (C) 2007 Oracle. All rights reserved. |
| * Copyright (C) 2007 Chuck Lever <chuck.lever@oracle.com> |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public |
| * License along with this program; if not, write to the |
| * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
| * Boston, MA 021110-1307, USA. |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <unistd.h> |
| #include <errno.h> |
| #include <netdb.h> |
| #include <time.h> |
| |
| #include <sys/socket.h> |
| #include <sys/mount.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| |
| #include "sockaddr.h" |
| #include "xcommon.h" |
| #include "mount.h" |
| #include "nls.h" |
| #include "nfsrpc.h" |
| #include "mount_constants.h" |
| #include "stropts.h" |
| #include "error.h" |
| #include "network.h" |
| #include "parse_opt.h" |
| #include "version.h" |
| #include "parse_dev.h" |
| #include "conffile.h" |
| |
| #ifndef HAVE_DECL_AI_ADDRCONFIG |
| #define AI_ADDRCONFIG 0 |
| #endif |
| |
| #ifndef NFS_PROGRAM |
| #define NFS_PROGRAM (100003) |
| #endif |
| |
| #ifndef NFS_PORT |
| #define NFS_PORT (2049) |
| #endif |
| |
| #ifndef NFS_MAXHOSTNAME |
| #define NFS_MAXHOSTNAME (255) |
| #endif |
| |
| #ifndef NFS_MAXPATHNAME |
| #define NFS_MAXPATHNAME (1024) |
| #endif |
| |
| #ifndef NFS_DEF_FG_TIMEOUT_MINUTES |
| #define NFS_DEF_FG_TIMEOUT_MINUTES (2u) |
| #endif |
| |
| #ifndef NFS_DEF_BG_TIMEOUT_MINUTES |
| #define NFS_DEF_BG_TIMEOUT_MINUTES (10000u) |
| #endif |
| |
| extern int nfs_mount_data_version; |
| extern char *progname; |
| extern int verbose; |
| extern int sloppy; |
| |
| struct nfsmount_info { |
| const char *spec, /* server:/path */ |
| *node, /* mounted-on dir */ |
| *type; /* "nfs" or "nfs4" */ |
| char *hostname; /* server's hostname */ |
| struct addrinfo *address; /* server's addresses */ |
| |
| struct mount_options *options; /* parsed mount options */ |
| char **extra_opts; /* string for /etc/mtab */ |
| |
| unsigned long version; /* NFS version */ |
| int flags, /* MS_ flags */ |
| fake, /* actually do the mount? */ |
| child; /* forked bg child? */ |
| }; |
| |
| #ifdef MOUNT_CONFIG |
| static void nfs_default_version(struct nfsmount_info *mi); |
| |
| static void nfs_default_version(struct nfsmount_info *mi) |
| { |
| extern unsigned long config_default_vers; |
| /* |
| * Use the default value set in the config file when |
| * the version has not been explicitly set. |
| */ |
| if (mi->version == 0 && config_default_vers) { |
| if (config_default_vers < 4) |
| mi->version = config_default_vers; |
| } |
| } |
| #else |
| inline void nfs_default_version(struct nfsmount_info *mi) {} |
| #endif /* MOUNT_CONFIG */ |
| |
| /* |
| * Obtain a retry timeout value based on the value of the "retry=" option. |
| * |
| * Returns a time_t timeout timestamp, in seconds. |
| */ |
| static time_t nfs_parse_retry_option(struct mount_options *options, |
| unsigned int timeout_minutes) |
| { |
| long tmp; |
| |
| switch (po_get_numeric(options, "retry", &tmp)) { |
| case PO_NOT_FOUND: |
| break; |
| case PO_FOUND: |
| if (tmp >= 0) { |
| timeout_minutes = tmp; |
| break; |
| } |
| case PO_BAD_VALUE: |
| if (verbose) |
| nfs_error(_("%s: invalid retry timeout was specified; " |
| "using default timeout"), progname); |
| break; |
| } |
| |
| return time(NULL) + (time_t)(timeout_minutes * 60); |
| } |
| |
| /* |
| * Convert the passed-in sockaddr-style address to presentation |
| * format, then append an option of the form "keyword=address". |
| * |
| * Returns 1 if the option was appended successfully; otherwise zero. |
| */ |
| static int nfs_append_generic_address_option(const struct sockaddr *sap, |
| const socklen_t salen, |
| const char *keyword, |
| struct mount_options *options) |
| { |
| char address[NI_MAXHOST]; |
| char new_option[512]; |
| int len; |
| |
| if (!nfs_present_sockaddr(sap, salen, address, sizeof(address))) |
| goto out_err; |
| |
| len = snprintf(new_option, sizeof(new_option), "%s=%s", |
| keyword, address); |
| if (len < 0 || (size_t)len >= sizeof(new_option)) |
| goto out_err; |
| |
| if (po_append(options, new_option) != PO_SUCCEEDED) |
| goto out_err; |
| |
| return 1; |
| |
| out_err: |
| nfs_error(_("%s: failed to construct %s option"), progname, keyword); |
| return 0; |
| } |
| |
| /* |
| * Append the 'addr=' option to the options string to pass a resolved |
| * server address to the kernel. After a successful mount, this address |
| * is also added to /etc/mtab for use when unmounting. |
| * |
| * If 'addr=' is already present, we strip it out. This prevents users |
| * from setting a bogus 'addr=' option themselves, and also allows bg |
| * retries to recompute the server's address, in case it has changed. |
| * |
| * Returns 1 if 'addr=' option appended successfully; |
| * otherwise zero. |
| */ |
| static int nfs_append_addr_option(const struct sockaddr *sap, |
| socklen_t salen, |
| struct mount_options *options) |
| { |
| po_remove_all(options, "addr"); |
| return nfs_append_generic_address_option(sap, salen, "addr", options); |
| } |
| |
| /* |
| * Called to discover our address and append an appropriate 'clientaddr=' |
| * option to the options string. |
| * |
| * Returns 1 if 'clientaddr=' option created successfully or if |
| * 'clientaddr=' option is already present; otherwise zero. |
| */ |
| static int nfs_append_clientaddr_option(const struct sockaddr *sap, |
| socklen_t salen, |
| struct mount_options *options) |
| { |
| union nfs_sockaddr address; |
| struct sockaddr *my_addr = &address.sa; |
| socklen_t my_len = sizeof(address); |
| |
| if (po_contains(options, "clientaddr") == PO_FOUND) |
| return 1; |
| |
| nfs_callback_address(sap, salen, my_addr, &my_len); |
| |
| return nfs_append_generic_address_option(my_addr, my_len, |
| "clientaddr", options); |
| } |
| |
| /* |
| * Determine whether to append a 'mountaddr=' option. The option is needed if: |
| * |
| * 1. "mounthost=" was specified, or |
| * 2. The address families for proto= and mountproto= are different. |
| */ |
| static int nfs_fix_mounthost_option(struct mount_options *options, |
| const char *nfs_hostname) |
| { |
| union nfs_sockaddr address; |
| struct sockaddr *sap = &address.sa; |
| socklen_t salen = sizeof(address); |
| sa_family_t nfs_family, mnt_family; |
| char *mounthost; |
| |
| if (!nfs_nfs_proto_family(options, &nfs_family)) |
| return 0; |
| if (!nfs_mount_proto_family(options, &mnt_family)) |
| return 0; |
| |
| mounthost = po_get(options, "mounthost"); |
| if (mounthost == NULL) { |
| if (nfs_family == mnt_family) |
| return 1; |
| mounthost = (char *)nfs_hostname; |
| } |
| |
| if (!nfs_lookup(mounthost, mnt_family, sap, &salen)) { |
| nfs_error(_("%s: unable to determine mount server's address"), |
| progname); |
| return 0; |
| } |
| |
| return nfs_append_generic_address_option(sap, salen, |
| "mountaddr", options); |
| } |
| |
| /* |
| * Returns zero if the "lock" option is in effect, but statd |
| * can't be started. Otherwise, returns 1. |
| */ |
| static const char *nfs_lock_opttbl[] = { |
| "nolock", |
| "lock", |
| NULL, |
| }; |
| |
| static int nfs_verify_lock_option(struct mount_options *options) |
| { |
| if (po_rightmost(options, nfs_lock_opttbl) == 0) |
| return 1; |
| |
| if (!start_statd()) { |
| nfs_error(_("%s: rpc.statd is not running but is " |
| "required for remote locking."), progname); |
| nfs_error(_("%s: Either use '-o nolock' to keep " |
| "locks local, or start statd."), progname); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static int nfs_append_sloppy_option(struct mount_options *options) |
| { |
| if (!sloppy || linux_version_code() < MAKE_VERSION(2, 6, 27)) |
| return 1; |
| |
| if (po_append(options, "sloppy") == PO_FAILED) |
| return 0; |
| return 1; |
| } |
| |
| static int nfs_set_version(struct nfsmount_info *mi) |
| { |
| if (!nfs_nfs_version(mi->options, &mi->version)) |
| return 0; |
| |
| if (strncmp(mi->type, "nfs4", 4) == 0) |
| mi->version = 4; |
| else { |
| char *option = po_get(mi->options, "proto"); |
| if (option && strcmp(option, "rdma") == 0) |
| mi->version = 3; |
| } |
| |
| /* |
| * If we still don't know, check for version-specific |
| * mount options. |
| */ |
| if (mi->version == 0) { |
| if (po_contains(mi->options, "mounthost") || |
| po_contains(mi->options, "mountaddr") || |
| po_contains(mi->options, "mountvers") || |
| po_contains(mi->options, "mountproto")) |
| mi->version = 3; |
| } |
| |
| /* |
| * If enabled, see if the default version was |
| * set in the config file |
| */ |
| nfs_default_version(mi); |
| |
| return 1; |
| } |
| |
| /* |
| * Set up mandatory non-version specific NFS mount options. |
| * |
| * Returns 1 if successful; otherwise zero. |
| */ |
| static int nfs_validate_options(struct nfsmount_info *mi) |
| { |
| struct addrinfo hint = { |
| .ai_protocol = (int)IPPROTO_UDP, |
| .ai_flags = AI_ADDRCONFIG, |
| }; |
| sa_family_t family; |
| int error; |
| |
| if (!nfs_parse_devname(mi->spec, &mi->hostname, NULL)) |
| return 0; |
| |
| if (!nfs_nfs_proto_family(mi->options, &family)) |
| return 0; |
| |
| hint.ai_family = (int)family; |
| error = getaddrinfo(mi->hostname, NULL, &hint, &mi->address); |
| if (error != 0) { |
| nfs_error(_("%s: Failed to resolve server %s: %s"), |
| progname, mi->hostname, gai_strerror(error)); |
| mi->address = NULL; |
| return 0; |
| } |
| |
| if (!nfs_set_version(mi)) |
| return 0; |
| |
| if (!nfs_append_sloppy_option(mi->options)) |
| return 0; |
| |
| if (!nfs_append_addr_option(mi->address->ai_addr, |
| mi->address->ai_addrlen, mi->options)) |
| return 0; |
| |
| return 1; |
| } |
| |
| /* |
| * Get NFS/mnt server addresses from mount options |
| * |
| * Returns 1 and fills in @nfs_saddr, @nfs_salen, @mnt_saddr, and @mnt_salen |
| * if all goes well; otherwise zero. |
| */ |
| static int nfs_extract_server_addresses(struct mount_options *options, |
| struct sockaddr *nfs_saddr, |
| socklen_t *nfs_salen, |
| struct sockaddr *mnt_saddr, |
| socklen_t *mnt_salen) |
| { |
| char *option; |
| |
| option = po_get(options, "addr"); |
| if (option == NULL) |
| return 0; |
| if (!nfs_string_to_sockaddr(option, nfs_saddr, nfs_salen)) |
| return 0; |
| |
| option = po_get(options, "mountaddr"); |
| if (option == NULL) { |
| memcpy(mnt_saddr, nfs_saddr, *nfs_salen); |
| *mnt_salen = *nfs_salen; |
| } else if (!nfs_string_to_sockaddr(option, mnt_saddr, mnt_salen)) |
| return 0; |
| |
| return 1; |
| } |
| |
| static int nfs_construct_new_options(struct mount_options *options, |
| struct sockaddr *nfs_saddr, |
| struct pmap *nfs_pmap, |
| struct sockaddr *mnt_saddr, |
| struct pmap *mnt_pmap) |
| { |
| char new_option[64]; |
| char *netid; |
| |
| po_remove_all(options, "nfsprog"); |
| po_remove_all(options, "mountprog"); |
| |
| po_remove_all(options, "v2"); |
| po_remove_all(options, "v3"); |
| po_remove_all(options, "vers"); |
| po_remove_all(options, "nfsvers"); |
| snprintf(new_option, sizeof(new_option) - 1, |
| "vers=%lu", nfs_pmap->pm_vers); |
| if (po_append(options, new_option) == PO_FAILED) |
| return 0; |
| |
| po_remove_all(options, "proto"); |
| po_remove_all(options, "udp"); |
| po_remove_all(options, "tcp"); |
| netid = nfs_get_netid(nfs_saddr->sa_family, nfs_pmap->pm_prot); |
| if (netid == NULL) |
| return 0; |
| snprintf(new_option, sizeof(new_option) - 1, |
| "proto=%s", netid); |
| free(netid); |
| if (po_append(options, new_option) == PO_FAILED) |
| return 0; |
| |
| po_remove_all(options, "port"); |
| if (nfs_pmap->pm_port != NFS_PORT) { |
| snprintf(new_option, sizeof(new_option) - 1, |
| "port=%lu", nfs_pmap->pm_port); |
| if (po_append(options, new_option) == PO_FAILED) |
| return 0; |
| } |
| |
| po_remove_all(options, "mountvers"); |
| snprintf(new_option, sizeof(new_option) - 1, |
| "mountvers=%lu", mnt_pmap->pm_vers); |
| if (po_append(options, new_option) == PO_FAILED) |
| return 0; |
| |
| po_remove_all(options, "mountproto"); |
| netid = nfs_get_netid(mnt_saddr->sa_family, mnt_pmap->pm_prot); |
| if (netid == NULL) |
| return 0; |
| snprintf(new_option, sizeof(new_option) - 1, |
| "mountproto=%s", netid); |
| free(netid); |
| if (po_append(options, new_option) == PO_FAILED) |
| return 0; |
| |
| po_remove_all(options, "mountport"); |
| snprintf(new_option, sizeof(new_option) - 1, |
| "mountport=%lu", mnt_pmap->pm_port); |
| if (po_append(options, new_option) == PO_FAILED) |
| return 0; |
| |
| return 1; |
| } |
| |
| /* |
| * Reconstruct the mount option string based on a portmapper probe |
| * of the server. Returns one if the server's portmapper returned |
| * something we can use, otherwise zero. |
| * |
| * To handle version and transport protocol fallback properly, we |
| * need to parse some of the mount options in order to set up a |
| * portmap probe. Mount options that nfs_rewrite_pmap_mount_options() |
| * doesn't recognize are left alone. |
| * |
| * Returns TRUE if rewriting was successful; otherwise |
| * FALSE is returned if some failure occurred. |
| */ |
| static int |
| nfs_rewrite_pmap_mount_options(struct mount_options *options) |
| { |
| union nfs_sockaddr nfs_address; |
| struct sockaddr *nfs_saddr = &nfs_address.sa; |
| socklen_t nfs_salen = sizeof(nfs_address); |
| struct pmap nfs_pmap; |
| union nfs_sockaddr mnt_address; |
| struct sockaddr *mnt_saddr = &mnt_address.sa; |
| socklen_t mnt_salen = sizeof(mnt_address); |
| struct pmap mnt_pmap; |
| char *option; |
| |
| /* |
| * Skip option negotiation for proto=rdma mounts. |
| */ |
| option = po_get(options, "proto"); |
| if (option && strcmp(option, "rdma") == 0) |
| goto out; |
| |
| /* |
| * Extract just the options needed to contact server. |
| * Bail now if any of these have bad values. |
| */ |
| if (!nfs_extract_server_addresses(options, nfs_saddr, &nfs_salen, |
| mnt_saddr, &mnt_salen)) { |
| errno = EINVAL; |
| return 0; |
| } |
| if (!nfs_options2pmap(options, &nfs_pmap, &mnt_pmap)) { |
| errno = EINVAL; |
| return 0; |
| } |
| |
| /* |
| * The kernel NFS client doesn't support changing the RPC |
| * program number for these services, so force the value of |
| * these fields before probing the server's ports. |
| */ |
| nfs_pmap.pm_prog = NFS_PROGRAM; |
| mnt_pmap.pm_prog = MOUNTPROG; |
| |
| /* |
| * If the server's rpcbind service isn't available, we can't |
| * negotiate. Bail now if we can't contact it. |
| */ |
| if (!nfs_probe_bothports(mnt_saddr, mnt_salen, &mnt_pmap, |
| nfs_saddr, nfs_salen, &nfs_pmap)) { |
| errno = ESPIPE; |
| if (rpc_createerr.cf_stat == RPC_PROGNOTREGISTERED) |
| errno = EOPNOTSUPP; |
| else if (rpc_createerr.cf_error.re_errno != 0) |
| errno = rpc_createerr.cf_error.re_errno; |
| return 0; |
| } |
| |
| if (!nfs_construct_new_options(options, nfs_saddr, &nfs_pmap, |
| mnt_saddr, &mnt_pmap)) { |
| errno = EINVAL; |
| return 0; |
| } |
| |
| out: |
| errno = 0; |
| return 1; |
| } |
| |
| /* |
| * Do the mount(2) system call. |
| * |
| * Returns TRUE if successful, otherwise FALSE. |
| * "errno" is set to reflect the individual error. |
| */ |
| static int nfs_sys_mount(struct nfsmount_info *mi, struct mount_options *opts) |
| { |
| char *options = NULL; |
| int result; |
| |
| if (po_join(opts, &options) == PO_FAILED) { |
| errno = EIO; |
| return 0; |
| } |
| |
| if (mi->fake) |
| return 1; |
| |
| result = mount(mi->spec, mi->node, mi->type, |
| mi->flags & ~(MS_USER|MS_USERS), options); |
| if (verbose && result) { |
| int save = errno; |
| nfs_error(_("%s: mount(2): %s"), progname, strerror(save)); |
| errno = save; |
| } |
| return !result; |
| } |
| |
| static int nfs_do_mount_v3v2(struct nfsmount_info *mi, |
| struct sockaddr *sap, socklen_t salen) |
| { |
| struct mount_options *options = po_dup(mi->options); |
| int result = 0; |
| |
| if (!options) { |
| errno = ENOMEM; |
| return result; |
| } |
| |
| if (!nfs_append_addr_option(sap, salen, options)) { |
| errno = EINVAL; |
| goto out_fail; |
| } |
| |
| if (!nfs_fix_mounthost_option(options, mi->hostname)) { |
| errno = EINVAL; |
| goto out_fail; |
| } |
| if (!mi->fake && !nfs_verify_lock_option(options)) { |
| errno = EINVAL; |
| goto out_fail; |
| } |
| |
| /* |
| * Options we negotiate below may be stale by the time this |
| * file system is unmounted. In order to force umount.nfs |
| * to renegotiate with the server, only write the user- |
| * specified options, and not negotiated options, to /etc/mtab. |
| */ |
| if (po_join(options, mi->extra_opts) == PO_FAILED) { |
| errno = ENOMEM; |
| goto out_fail; |
| } |
| |
| if (verbose) |
| printf(_("%s: trying text-based options '%s'\n"), |
| progname, *mi->extra_opts); |
| |
| if (!nfs_rewrite_pmap_mount_options(options)) |
| goto out_fail; |
| |
| result = nfs_sys_mount(mi, options); |
| |
| out_fail: |
| po_destroy(options); |
| return result; |
| } |
| |
| /* |
| * Attempt a "-t nfs vers=2" or "-t nfs vers=3" mount. |
| * |
| * Returns TRUE if successful, otherwise FALSE. |
| * "errno" is set to reflect the individual error. |
| */ |
| static int nfs_try_mount_v3v2(struct nfsmount_info *mi) |
| { |
| struct addrinfo *ai; |
| int ret; |
| |
| for (ai = mi->address; ai != NULL; ai = ai->ai_next) { |
| ret = nfs_do_mount_v3v2(mi, ai->ai_addr, ai->ai_addrlen); |
| if (ret != 0) |
| return ret; |
| |
| switch (errno) { |
| case ECONNREFUSED: |
| case EOPNOTSUPP: |
| case EHOSTUNREACH: |
| continue; |
| default: |
| break; |
| } |
| } |
| return ret; |
| } |
| |
| static int nfs_do_mount_v4(struct nfsmount_info *mi, |
| struct sockaddr *sap, socklen_t salen) |
| { |
| struct mount_options *options = po_dup(mi->options); |
| int result = 0; |
| |
| if (!options) { |
| errno = ENOMEM; |
| return result; |
| } |
| |
| if (mi->version == 0) { |
| if (po_contains(options, "mounthost") || |
| po_contains(options, "mountaddr") || |
| po_contains(options, "mountvers") || |
| po_contains(options, "mountproto")) { |
| /* |
| * Since these mountd options are set assume version 3 |
| * is wanted so error out with EPROTONOSUPPORT so the |
| * protocol negation starts with v3. |
| */ |
| errno = EPROTONOSUPPORT; |
| goto out_fail; |
| } |
| if (po_append(options, "vers=4") == PO_FAILED) { |
| errno = EINVAL; |
| goto out_fail; |
| } |
| } |
| |
| if (!nfs_append_addr_option(sap, salen, options)) { |
| errno = EINVAL; |
| goto out_fail; |
| } |
| |
| if (!nfs_append_clientaddr_option(sap, salen, options)) { |
| errno = EINVAL; |
| goto out_fail; |
| } |
| |
| /* |
| * Update option string to be recorded in /etc/mtab. |
| */ |
| if (po_join(options, mi->extra_opts) == PO_FAILED) { |
| errno = ENOMEM; |
| goto out_fail; |
| } |
| |
| if (verbose) |
| printf(_("%s: trying text-based options '%s'\n"), |
| progname, *mi->extra_opts); |
| |
| result = nfs_sys_mount(mi, options); |
| |
| out_fail: |
| po_destroy(options); |
| return result; |
| } |
| |
| /* |
| * Attempt a "-t nfs -o vers=4" or "-t nfs4" mount. |
| * |
| * Returns TRUE if successful, otherwise FALSE. |
| * "errno" is set to reflect the individual error. |
| */ |
| static int nfs_try_mount_v4(struct nfsmount_info *mi) |
| { |
| struct addrinfo *ai; |
| int ret; |
| |
| for (ai = mi->address; ai != NULL; ai = ai->ai_next) { |
| ret = nfs_do_mount_v4(mi, ai->ai_addr, ai->ai_addrlen); |
| if (ret != 0) |
| return ret; |
| |
| switch (errno) { |
| case ECONNREFUSED: |
| case EHOSTUNREACH: |
| continue; |
| default: |
| break; |
| } |
| } |
| return ret; |
| } |
| |
| /* |
| * This is a single pass through the fg/bg loop. |
| * |
| * Returns TRUE if successful, otherwise FALSE. |
| * "errno" is set to reflect the individual error. |
| */ |
| static int nfs_try_mount(struct nfsmount_info *mi) |
| { |
| int result = 0; |
| |
| switch (mi->version) { |
| case 0: |
| if (linux_version_code() > MAKE_VERSION(2, 6, 31)) { |
| errno = 0; |
| result = nfs_try_mount_v4(mi); |
| if (errno != EPROTONOSUPPORT) { |
| /* |
| * To deal with legacy Linux servers that don't |
| * automatically export a pseudo root, retry |
| * ENOENT errors using version 3. And for |
| * Linux servers prior to 2.6.25, retry EPERM |
| */ |
| if (errno != ENOENT && errno != EPERM) |
| break; |
| } |
| } |
| case 2: |
| case 3: |
| result = nfs_try_mount_v3v2(mi); |
| break; |
| case 4: |
| result = nfs_try_mount_v4(mi); |
| break; |
| default: |
| errno = EIO; |
| } |
| |
| return result; |
| } |
| |
| /* |
| * Distinguish between permanent and temporary errors. |
| * |
| * Basically, we retry if communication with the server has |
| * failed so far, but fail immediately if there is a local |
| * error (like a bad mount option). |
| * |
| * ESTALE is also a temporary error because some servers |
| * return ESTALE when a share is temporarily offline. |
| * |
| * Returns 1 if we should fail immediately, or 0 if we |
| * should retry. |
| */ |
| static int nfs_is_permanent_error(int error) |
| { |
| switch (error) { |
| case ESTALE: |
| case ETIMEDOUT: |
| case ECONNREFUSED: |
| return 0; /* temporary */ |
| default: |
| return 1; /* permanent */ |
| } |
| } |
| |
| /* |
| * Handle "foreground" NFS mounts. |
| * |
| * Retry the mount request for as long as the 'retry=' option says. |
| * |
| * Returns a valid mount command exit code. |
| */ |
| static int nfsmount_fg(struct nfsmount_info *mi) |
| { |
| unsigned int secs = 1; |
| time_t timeout; |
| |
| timeout = nfs_parse_retry_option(mi->options, |
| NFS_DEF_FG_TIMEOUT_MINUTES); |
| if (verbose) |
| printf(_("%s: timeout set for %s"), |
| progname, ctime(&timeout)); |
| |
| for (;;) { |
| if (nfs_try_mount(mi)) |
| return EX_SUCCESS; |
| |
| if (nfs_is_permanent_error(errno)) |
| break; |
| |
| if (time(NULL) > timeout) { |
| errno = ETIMEDOUT; |
| break; |
| } |
| |
| if (errno != ETIMEDOUT) { |
| if (sleep(secs)) |
| break; |
| secs <<= 1; |
| if (secs > 10) |
| secs = 10; |
| } |
| }; |
| |
| mount_error(mi->spec, mi->node, errno); |
| return EX_FAIL; |
| } |
| |
| /* |
| * Handle "background" NFS mount [first try] |
| * |
| * Returns a valid mount command exit code. |
| * |
| * EX_BG should cause the caller to fork and invoke nfsmount_child. |
| */ |
| static int nfsmount_parent(struct nfsmount_info *mi) |
| { |
| if (nfs_try_mount(mi)) |
| return EX_SUCCESS; |
| |
| if (nfs_is_permanent_error(errno)) { |
| mount_error(mi->spec, mi->node, errno); |
| return EX_FAIL; |
| } |
| |
| sys_mount_errors(mi->hostname, errno, 1, 1); |
| return EX_BG; |
| } |
| |
| /* |
| * Handle "background" NFS mount [retry daemon] |
| * |
| * Returns a valid mount command exit code: EX_SUCCESS if successful, |
| * EX_FAIL if a failure occurred. There's nothing to catch the |
| * error return, though, so we use sys_mount_errors to log the |
| * failure. |
| */ |
| static int nfsmount_child(struct nfsmount_info *mi) |
| { |
| unsigned int secs = 1; |
| time_t timeout; |
| |
| timeout = nfs_parse_retry_option(mi->options, |
| NFS_DEF_BG_TIMEOUT_MINUTES); |
| |
| for (;;) { |
| if (sleep(secs)) |
| break; |
| secs <<= 1; |
| if (secs > 120) |
| secs = 120; |
| |
| if (nfs_try_mount(mi)) |
| return EX_SUCCESS; |
| |
| if (nfs_is_permanent_error(errno)) |
| break; |
| |
| if (time(NULL) > timeout) |
| break; |
| |
| sys_mount_errors(mi->hostname, errno, 1, 1); |
| }; |
| |
| sys_mount_errors(mi->hostname, errno, 1, 0); |
| return EX_FAIL; |
| } |
| |
| /* |
| * Handle "background" NFS mount |
| * |
| * Returns a valid mount command exit code. |
| */ |
| static int nfsmount_bg(struct nfsmount_info *mi) |
| { |
| if (!mi->child) |
| return nfsmount_parent(mi); |
| else |
| return nfsmount_child(mi); |
| } |
| |
| /* |
| * Process mount options and try a mount system call. |
| * |
| * Returns a valid mount command exit code. |
| */ |
| static const char *nfs_background_opttbl[] = { |
| "bg", |
| "fg", |
| NULL, |
| }; |
| |
| static int nfsmount_start(struct nfsmount_info *mi) |
| { |
| if (!nfs_validate_options(mi)) |
| return EX_FAIL; |
| |
| if (po_rightmost(mi->options, nfs_background_opttbl) == 0) |
| return nfsmount_bg(mi); |
| else |
| return nfsmount_fg(mi); |
| } |
| |
| /** |
| * nfsmount_string - Mount an NFS file system using C string options |
| * @spec: C string specifying remote share to mount ("hostname:path") |
| * @node: C string pathname of local mounted-on directory |
| * @type: C string that represents file system type ("nfs" or "nfs4") |
| * @flags: MS_ style mount flags |
| * @extra_opts: pointer to C string containing fs-specific mount options |
| * (input and output argument) |
| * @fake: flag indicating whether to carry out the whole operation |
| * @child: one if this is a mount daemon (bg) |
| */ |
| int nfsmount_string(const char *spec, const char *node, const char *type, |
| int flags, char **extra_opts, int fake, int child) |
| { |
| struct nfsmount_info mi = { |
| .spec = spec, |
| .node = node, |
| .address = NULL, |
| .type = type, |
| .extra_opts = extra_opts, |
| .flags = flags, |
| .fake = fake, |
| .child = child, |
| }; |
| int retval = EX_FAIL; |
| |
| mi.options = po_split(*extra_opts); |
| if (mi.options) { |
| retval = nfsmount_start(&mi); |
| po_destroy(mi.options); |
| } else |
| nfs_error(_("%s: internal option parsing error"), progname); |
| |
| freeaddrinfo(mi.address); |
| free(mi.hostname); |
| return retval; |
| } |