+ struct addrinfo *ai;
+ int ret = 0;
+
+ 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:
+ goto out;
+ }
+ }
+out:
+ return ret;
+}
+
+/*
+ * Handle NFS version and transport protocol
+ * autonegotiation.
+ *
+ * When no version or protocol is specified on the
+ * command line, mount.nfs negotiates with the server
+ * to determine appropriate settings for the new
+ * mount point.
+ *
+ * Returns TRUE if successful, otherwise FALSE.
+ * "errno" is set to reflect the individual error.
+ */
+static int nfs_autonegotiate(struct nfsmount_info *mi)
+{
+ int result;
+
+ result = nfs_try_mount_v4(mi);
+ if (result)
+ return result;
+
+ switch (errno) {
+ case EPROTONOSUPPORT:
+ /* A clear indication that the server or our
+ * client does not support NFS version 4. */
+ goto fall_back;
+ case ENOENT:
+ /* Legacy Linux servers don't export an NFS
+ * version 4 pseudoroot. */
+ goto fall_back;
+ case EPERM:
+ /* Linux servers prior to 2.6.25 may return
+ * EPERM when NFS version 4 is not supported. */
+ goto fall_back;
+ default:
+ return result;
+ }
+
+fall_back:
+ return nfs_try_mount_v3v2(mi);
+}
+
+/*
+ * 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:
+ result = nfs_autonegotiate(mi);
+ 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:
+ case EHOSTUNREACH:
+ 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);
+}
+
+/*
+ * Usually all that is needed for an NFS remount is to change
+ * generic mount options like "sync" or "ro". These generic
+ * options are controlled by mi->flags, not by text-based
+ * options, and no contact with the server is needed.
+ *
+ * Take care with the /etc/mtab entry for this mount; just
+ * calling update_mtab() will change an "-t nfs -o vers=4"
+ * mount to an "-t nfs -o remount" mount, and that will
+ * confuse umount.nfs.
+ *
+ * Returns a valid mount command exit code.
+ */
+static int nfs_remount(struct nfsmount_info *mi)
+{
+ if (nfs_sys_mount(mi, mi->options))
+ return EX_SUCCESS;
+ return EX_FAIL;
+}
+
+/*
+ * 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;
+
+ /*
+ * Avoid retry and negotiation logic when remounting
+ */
+ if (mi->flags & MS_REMOUNT)
+ return nfs_remount(mi);
+
+ if (po_rightmost(mi->options, nfs_background_opttbl) == 0)
+ return nfsmount_bg(mi);