text-based mount: Retry when server can't be reached
[nfs-utils.git] / utils / mount / stropts.c
1 /*
2  * stropts.c -- NFS mount using C string to pass options to kernel
3  *
4  * Copyright (C) 2007 Oracle.  All rights reserved.
5  * Copyright (C) 2007 Chuck Lever <chuck.lever@oracle.com>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public
18  * License along with this program; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 021110-1307, USA.
21  *
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <unistd.h>
29 #include <errno.h>
30 #include <netdb.h>
31 #include <time.h>
32
33 #include <sys/socket.h>
34 #include <sys/mount.h>
35 #include <netinet/in.h>
36 #include <arpa/inet.h>
37
38 #include "sockaddr.h"
39 #include "xcommon.h"
40 #include "mount.h"
41 #include "nls.h"
42 #include "nfsrpc.h"
43 #include "mount_constants.h"
44 #include "stropts.h"
45 #include "error.h"
46 #include "network.h"
47 #include "parse_opt.h"
48 #include "version.h"
49 #include "parse_dev.h"
50 #include "conffile.h"
51
52 #ifndef NFS_PROGRAM
53 #define NFS_PROGRAM     (100003)
54 #endif
55
56 #ifndef NFS_PORT
57 #define NFS_PORT        (2049)
58 #endif
59
60 #ifndef NFS_MAXHOSTNAME
61 #define NFS_MAXHOSTNAME         (255)
62 #endif
63
64 #ifndef NFS_MAXPATHNAME
65 #define NFS_MAXPATHNAME         (1024)
66 #endif
67
68 #ifndef NFS_DEF_FG_TIMEOUT_MINUTES
69 #define NFS_DEF_FG_TIMEOUT_MINUTES      (2u)
70 #endif
71
72 #ifndef NFS_DEF_BG_TIMEOUT_MINUTES
73 #define NFS_DEF_BG_TIMEOUT_MINUTES      (10000u)
74 #endif
75
76 extern int nfs_mount_data_version;
77 extern char *progname;
78 extern int verbose;
79 extern int sloppy;
80
81 struct nfsmount_info {
82         const char              *spec,          /* server:/path */
83                                 *node,          /* mounted-on dir */
84                                 *type;          /* "nfs" or "nfs4" */
85         char                    *hostname;      /* server's hostname */
86         union nfs_sockaddr      address;
87         socklen_t               salen;          /* size of server's address */
88
89         struct mount_options    *options;       /* parsed mount options */
90         char                    **extra_opts;   /* string for /etc/mtab */
91
92         unsigned long           version;        /* NFS version */
93         int                     flags,          /* MS_ flags */
94                                 fake,           /* actually do the mount? */
95                                 child;          /* forked bg child? */
96 };
97
98 #ifdef MOUNT_CONFIG
99 static void nfs_default_version(struct nfsmount_info *mi);
100
101 static void nfs_default_version(struct nfsmount_info *mi)
102 {
103         extern unsigned long config_default_vers;
104         /*
105          * Use the default value set in the config file when
106          * the version has not been explicitly set.
107          */
108         if (mi->version == 0 && config_default_vers) {
109                 if (config_default_vers < 4)
110                         mi->version = config_default_vers;
111         }
112 }
113 #else
114 inline void nfs_default_version(struct nfsmount_info *mi) {}
115 #endif /* MOUNT_CONFIG */
116
117 /*
118  * Obtain a retry timeout value based on the value of the "retry=" option.
119  *
120  * Returns a time_t timeout timestamp, in seconds.
121  */
122 static time_t nfs_parse_retry_option(struct mount_options *options,
123                                      unsigned int timeout_minutes)
124 {
125         long tmp;
126
127         switch (po_get_numeric(options, "retry", &tmp)) {
128         case PO_NOT_FOUND:
129                 break;
130         case PO_FOUND:
131                 if (tmp >= 0) {
132                         timeout_minutes = tmp;
133                         break;
134                 }
135         case PO_BAD_VALUE:
136                 if (verbose)
137                         nfs_error(_("%s: invalid retry timeout was specified; "
138                                         "using default timeout"), progname);
139                 break;
140         }
141
142         return time(NULL) + (time_t)(timeout_minutes * 60);
143 }
144
145 /*
146  * Convert the passed-in sockaddr-style address to presentation
147  * format, then append an option of the form "keyword=address".
148  *
149  * Returns 1 if the option was appended successfully; otherwise zero.
150  */
151 static int nfs_append_generic_address_option(const struct sockaddr *sap,
152                                              const socklen_t salen,
153                                              const char *keyword,
154                                              struct mount_options *options)
155 {
156         char address[NI_MAXHOST];
157         char new_option[512];
158         int len;
159
160         if (!nfs_present_sockaddr(sap, salen, address, sizeof(address)))
161                 goto out_err;
162
163         len = snprintf(new_option, sizeof(new_option), "%s=%s",
164                                                 keyword, address);
165         if (len < 0 || (size_t)len >= sizeof(new_option))
166                 goto out_err;
167
168         if (po_append(options, new_option) != PO_SUCCEEDED)
169                 goto out_err;
170
171         return 1;
172
173 out_err:
174         nfs_error(_("%s: failed to construct %s option"), progname, keyword);
175         return 0;
176 }
177
178 /*
179  * Append the 'addr=' option to the options string to pass a resolved
180  * server address to the kernel.  After a successful mount, this address
181  * is also added to /etc/mtab for use when unmounting.
182  *
183  * If 'addr=' is already present, we strip it out.  This prevents users
184  * from setting a bogus 'addr=' option themselves, and also allows bg
185  * retries to recompute the server's address, in case it has changed.
186  *
187  * Returns 1 if 'addr=' option appended successfully;
188  * otherwise zero.
189  */
190 static int nfs_append_addr_option(const struct sockaddr *sap,
191                                   socklen_t salen,
192                                   struct mount_options *options)
193 {
194         po_remove_all(options, "addr");
195         return nfs_append_generic_address_option(sap, salen, "addr", options);
196 }
197
198 /*
199  * Called to discover our address and append an appropriate 'clientaddr='
200  * option to the options string.
201  *
202  * Returns 1 if 'clientaddr=' option created successfully or if
203  * 'clientaddr=' option is already present; otherwise zero.
204  */
205 static int nfs_append_clientaddr_option(const struct sockaddr *sap,
206                                         socklen_t salen,
207                                         struct mount_options *options)
208 {
209         union nfs_sockaddr address;
210         struct sockaddr *my_addr = &address.sa;
211         socklen_t my_len = sizeof(address);
212
213         if (po_contains(options, "clientaddr") == PO_FOUND)
214                 return 1;
215
216         nfs_callback_address(sap, salen, my_addr, &my_len);
217
218         return nfs_append_generic_address_option(my_addr, my_len,
219                                                         "clientaddr", options);
220 }
221
222 /*
223  * Determine whether to append a 'mountaddr=' option.  The option is needed if:
224  *
225  *   1. "mounthost=" was specified, or
226  *   2. The address families for proto= and mountproto= are different.
227  */
228 static int nfs_fix_mounthost_option(struct mount_options *options,
229                 const char *nfs_hostname)
230 {
231         union nfs_sockaddr address;
232         struct sockaddr *sap = &address.sa;
233         socklen_t salen = sizeof(address);
234         sa_family_t nfs_family, mnt_family;
235         char *mounthost;
236
237         if (!nfs_nfs_proto_family(options, &nfs_family))
238                 return 0;
239         if (!nfs_mount_proto_family(options, &mnt_family))
240                 return 0;
241
242         mounthost = po_get(options, "mounthost");
243         if (mounthost == NULL) {
244                 if (nfs_family == mnt_family)
245                         return 1;
246                 mounthost = (char *)nfs_hostname;
247         }
248
249         if (!nfs_lookup(mounthost, mnt_family, sap, &salen)) {
250                 nfs_error(_("%s: unable to determine mount server's address"),
251                                 progname);
252                 return 0;
253         }
254
255         return nfs_append_generic_address_option(sap, salen,
256                                                         "mountaddr", options);
257 }
258
259 /*
260  * Returns zero if the "lock" option is in effect, but statd
261  * can't be started.  Otherwise, returns 1.
262  */
263 static const char *nfs_lock_opttbl[] = {
264         "nolock",
265         "lock",
266         NULL,
267 };
268
269 static int nfs_verify_lock_option(struct mount_options *options)
270 {
271         if (po_rightmost(options, nfs_lock_opttbl) == 0)
272                 return 1;
273
274         if (!start_statd()) {
275                 nfs_error(_("%s: rpc.statd is not running but is "
276                             "required for remote locking."), progname);
277                 nfs_error(_("%s: Either use '-o nolock' to keep "
278                             "locks local, or start statd."), progname);
279                 return 0;
280         }
281
282         return 1;
283 }
284
285 static int nfs_append_sloppy_option(struct mount_options *options)
286 {
287         if (!sloppy || linux_version_code() < MAKE_VERSION(2, 6, 27))
288                 return 1;
289
290         if (po_append(options, "sloppy") == PO_FAILED)
291                 return 0;
292         return 1;
293 }
294
295 static int nfs_set_version(struct nfsmount_info *mi)
296 {
297         if (!nfs_nfs_version(mi->options, &mi->version))
298                 return 0;
299
300         if (strncmp(mi->type, "nfs4", 4) == 0)
301                 mi->version = 4;
302         else {
303                 char *option = po_get(mi->options, "proto");
304                 if (option && strcmp(option, "rdma") == 0)
305                         mi->version = 3;
306         }
307
308         /*
309          * If we still don't know, check for version-specific
310          * mount options.
311          */
312         if (mi->version == 0) {
313                 if (po_contains(mi->options, "mounthost") ||
314                     po_contains(mi->options, "mountaddr") ||
315                     po_contains(mi->options, "mountvers") ||
316                     po_contains(mi->options, "mountproto"))
317                         mi->version = 3;
318         }
319
320         /*
321          * If enabled, see if the default version was
322          * set in the config file
323          */
324         nfs_default_version(mi);
325         
326         return 1;
327 }
328
329 /*
330  * Set up mandatory non-version specific NFS mount options.
331  *
332  * Returns 1 if successful; otherwise zero.
333  */
334 static int nfs_validate_options(struct nfsmount_info *mi)
335 {
336         struct sockaddr *sap = &mi->address.sa;
337         sa_family_t family;
338
339         if (!nfs_parse_devname(mi->spec, &mi->hostname, NULL))
340                 return 0;
341
342         if (!nfs_nfs_proto_family(mi->options, &family))
343                 return 0;
344         mi->salen = sizeof(mi->address);
345         if (!nfs_lookup(mi->hostname, family, sap, &mi->salen))
346                 return 0;
347
348         if (!nfs_set_version(mi))
349                 return 0;
350
351         if (!nfs_append_sloppy_option(mi->options))
352                 return 0;
353
354         if (!nfs_append_addr_option(sap, mi->salen, mi->options))
355                 return 0;
356
357         return 1;
358 }
359
360 /*
361  * Get NFS/mnt server addresses from mount options
362  *
363  * Returns 1 and fills in @nfs_saddr, @nfs_salen, @mnt_saddr, and @mnt_salen
364  * if all goes well; otherwise zero.
365  */
366 static int nfs_extract_server_addresses(struct mount_options *options,
367                                         struct sockaddr *nfs_saddr,
368                                         socklen_t *nfs_salen,
369                                         struct sockaddr *mnt_saddr,
370                                         socklen_t *mnt_salen)
371 {
372         char *option;
373
374         option = po_get(options, "addr");
375         if (option == NULL)
376                 return 0;
377         if (!nfs_string_to_sockaddr(option, nfs_saddr, nfs_salen))
378                 return 0;
379
380         option = po_get(options, "mountaddr");
381         if (option == NULL) {
382                 memcpy(mnt_saddr, nfs_saddr, *nfs_salen);
383                 *mnt_salen = *nfs_salen;
384         } else if (!nfs_string_to_sockaddr(option, mnt_saddr, mnt_salen))
385                 return 0;
386
387         return 1;
388 }
389
390 static int nfs_construct_new_options(struct mount_options *options,
391                                      struct sockaddr *nfs_saddr,
392                                      struct pmap *nfs_pmap,
393                                      struct sockaddr *mnt_saddr,
394                                      struct pmap *mnt_pmap)
395 {
396         char new_option[64];
397         char *netid;
398
399         po_remove_all(options, "nfsprog");
400         po_remove_all(options, "mountprog");
401
402         po_remove_all(options, "v2");
403         po_remove_all(options, "v3");
404         po_remove_all(options, "vers");
405         po_remove_all(options, "nfsvers");
406         snprintf(new_option, sizeof(new_option) - 1,
407                  "vers=%lu", nfs_pmap->pm_vers);
408         if (po_append(options, new_option) == PO_FAILED)
409                 return 0;
410
411         po_remove_all(options, "proto");
412         po_remove_all(options, "udp");
413         po_remove_all(options, "tcp");
414         netid = nfs_get_netid(nfs_saddr->sa_family, nfs_pmap->pm_prot);
415         if (netid == NULL)
416                 return 0;
417         snprintf(new_option, sizeof(new_option) - 1,
418                          "proto=%s", netid);
419         free(netid);
420         if (po_append(options, new_option) == PO_FAILED)
421                 return 0;
422
423         po_remove_all(options, "port");
424         if (nfs_pmap->pm_port != NFS_PORT) {
425                 snprintf(new_option, sizeof(new_option) - 1,
426                          "port=%lu", nfs_pmap->pm_port);
427                 if (po_append(options, new_option) == PO_FAILED)
428                         return 0;
429         }
430
431         po_remove_all(options, "mountvers");
432         snprintf(new_option, sizeof(new_option) - 1,
433                  "mountvers=%lu", mnt_pmap->pm_vers);
434         if (po_append(options, new_option) == PO_FAILED)
435                 return 0;
436
437         po_remove_all(options, "mountproto");
438         netid = nfs_get_netid(mnt_saddr->sa_family, mnt_pmap->pm_prot);
439         if (netid == NULL)
440                 return 0;
441         snprintf(new_option, sizeof(new_option) - 1,
442                          "mountproto=%s", netid);
443         free(netid);
444         if (po_append(options, new_option) == PO_FAILED)
445                 return 0;
446
447         po_remove_all(options, "mountport");
448         snprintf(new_option, sizeof(new_option) - 1,
449                  "mountport=%lu", mnt_pmap->pm_port);
450         if (po_append(options, new_option) == PO_FAILED)
451                 return 0;
452
453         return 1;
454 }
455
456 /*
457  * Reconstruct the mount option string based on a portmapper probe
458  * of the server.  Returns one if the server's portmapper returned
459  * something we can use, otherwise zero.
460  *
461  * To handle version and transport protocol fallback properly, we
462  * need to parse some of the mount options in order to set up a
463  * portmap probe.  Mount options that nfs_rewrite_pmap_mount_options()
464  * doesn't recognize are left alone.
465  *
466  * Returns TRUE if rewriting was successful; otherwise
467  * FALSE is returned if some failure occurred.
468  */
469 static int
470 nfs_rewrite_pmap_mount_options(struct mount_options *options)
471 {
472         union nfs_sockaddr nfs_address;
473         struct sockaddr *nfs_saddr = &nfs_address.sa;
474         socklen_t nfs_salen = sizeof(nfs_address);
475         struct pmap nfs_pmap;
476         union nfs_sockaddr mnt_address;
477         struct sockaddr *mnt_saddr = &mnt_address.sa;
478         socklen_t mnt_salen = sizeof(mnt_address);
479         struct pmap mnt_pmap;
480         char *option;
481
482         /*
483          * Skip option negotiation for proto=rdma mounts.
484          */
485         option = po_get(options, "proto");
486         if (option && strcmp(option, "rdma") == 0)
487                 goto out;
488
489         /*
490          * Extract just the options needed to contact server.
491          * Bail now if any of these have bad values.
492          */
493         if (!nfs_extract_server_addresses(options, nfs_saddr, &nfs_salen,
494                                                 mnt_saddr, &mnt_salen)) {
495                 errno = EINVAL;
496                 return 0;
497         }
498         if (!nfs_options2pmap(options, &nfs_pmap, &mnt_pmap)) {
499                 errno = EINVAL;
500                 return 0;
501         }
502
503         /*
504          * The kernel NFS client doesn't support changing the RPC
505          * program number for these services, so force the value of
506          * these fields before probing the server's ports.
507          */
508         nfs_pmap.pm_prog = NFS_PROGRAM;
509         mnt_pmap.pm_prog = MOUNTPROG;
510
511         /*
512          * If the server's rpcbind service isn't available, we can't
513          * negotiate.  Bail now if we can't contact it.
514          */
515         if (!nfs_probe_bothports(mnt_saddr, mnt_salen, &mnt_pmap,
516                                  nfs_saddr, nfs_salen, &nfs_pmap)) {
517                 errno = ESPIPE;
518                 if (rpc_createerr.cf_stat == RPC_PROGNOTREGISTERED)
519                         errno = EOPNOTSUPP;
520                 else if (rpc_createerr.cf_error.re_errno != 0)
521                         errno = rpc_createerr.cf_error.re_errno;
522                 return 0;
523         }
524
525         if (!nfs_construct_new_options(options, nfs_saddr, &nfs_pmap,
526                                         mnt_saddr, &mnt_pmap)) {
527                 errno = EINVAL;
528                 return 0;
529         }
530
531 out:
532         errno = 0;
533         return 1;
534 }
535
536 /*
537  * Do the mount(2) system call.
538  *
539  * Returns TRUE if successful, otherwise FALSE.
540  * "errno" is set to reflect the individual error.
541  */
542 static int nfs_sys_mount(struct nfsmount_info *mi, struct mount_options *opts)
543 {
544         char *options = NULL;
545         int result;
546
547         if (po_join(opts, &options) == PO_FAILED) {
548                 errno = EIO;
549                 return 0;
550         }
551
552         if (verbose)
553                 printf(_("%s: trying text-based options '%s'\n"),
554                         progname, options);
555
556         if (mi->fake)
557                 return 1;
558
559         result = mount(mi->spec, mi->node, mi->type,
560                         mi->flags & ~(MS_USER|MS_USERS), options);
561         if (verbose && result) {
562                 int save = errno;
563                 nfs_error(_("%s: mount(2): %s"), progname, strerror(save));
564                 errno = save;
565         }
566         return !result;
567 }
568
569 /*
570  * For "-t nfs vers=2" or "-t nfs vers=3" mounts.
571  */
572 static int nfs_try_mount_v3v2(struct nfsmount_info *mi)
573 {
574         struct mount_options *options = po_dup(mi->options);
575         int result = 0;
576
577         if (!options) {
578                 errno = ENOMEM;
579                 return result;
580         }
581
582         if (!nfs_fix_mounthost_option(options, mi->hostname)) {
583                 errno = EINVAL;
584                 goto out_fail;
585         }
586         if (!mi->fake && !nfs_verify_lock_option(options)) {
587                 errno = EINVAL;
588                 goto out_fail;
589         }
590
591         /*
592          * Options we negotiate below may be stale by the time this
593          * file system is unmounted.  In order to force umount.nfs
594          * to renegotiate with the server, only write the user-
595          * specified options, and not negotiated options, to /etc/mtab.
596          */
597         if (po_join(options, mi->extra_opts) == PO_FAILED) {
598                 errno = ENOMEM;
599                 goto out_fail;
600         }
601
602         if (!nfs_rewrite_pmap_mount_options(options))
603                 goto out_fail;
604
605         result = nfs_sys_mount(mi, options);
606
607 out_fail:
608         po_destroy(options);
609         return result;
610 }
611
612 /*
613  * For "-t nfs -o vers=4" or "-t nfs4" mounts.
614  */
615 static int nfs_try_mount_v4(struct nfsmount_info *mi)
616 {
617         struct sockaddr *sap = &mi->address.sa;
618         struct mount_options *options = po_dup(mi->options);
619         int result = 0;
620
621         if (!options) {
622                 errno = ENOMEM;
623                 return result;
624         }
625
626         if (mi->version == 0) {
627                 if (po_contains(options, "mounthost") ||
628                         po_contains(options, "mountaddr") ||
629                         po_contains(options, "mountvers") ||
630                         po_contains(options, "mountproto")) {
631                 /*
632                  * Since these mountd options are set assume version 3
633                  * is wanted so error out with EPROTONOSUPPORT so the
634                  * protocol negation starts with v3.
635                  */
636                         errno = EPROTONOSUPPORT;
637                         goto out_fail;
638                 }
639                 if (po_append(options, "vers=4") == PO_FAILED) {
640                         errno = EINVAL;
641                         goto out_fail;
642                 }
643         }
644
645         if (!nfs_append_clientaddr_option(sap, mi->salen, options)) {
646                 errno = EINVAL;
647                 goto out_fail;
648         }
649
650         /*
651          * Update option string to be recorded in /etc/mtab.
652          */
653         if (po_join(options, mi->extra_opts) == PO_FAILED) {
654                 errno = ENOMEM;
655                 goto out_fail;
656         }
657
658         result = nfs_sys_mount(mi, options);
659
660 out_fail:
661         po_destroy(options);
662         return result;
663 }
664
665 /*
666  * This is a single pass through the fg/bg loop.
667  *
668  * Returns TRUE if successful, otherwise FALSE.
669  * "errno" is set to reflect the individual error.
670  */
671 static int nfs_try_mount(struct nfsmount_info *mi)
672 {
673         int result = 0;
674
675         switch (mi->version) {
676         case 0:
677                 if (linux_version_code() > MAKE_VERSION(2, 6, 31)) {
678                         errno = 0;
679                         result = nfs_try_mount_v4(mi);
680                         if (errno != EPROTONOSUPPORT) {
681                                 /* 
682                                  * To deal with legacy Linux servers that don't
683                                  * automatically export a pseudo root, retry
684                                  * ENOENT errors using version 3. And for
685                                  * Linux servers prior to 2.6.25, retry EPERM
686                                  */
687                                 if (errno != ENOENT && errno != EPERM)
688                                         break;
689                         }
690                 }
691         case 2:
692         case 3:
693                 result = nfs_try_mount_v3v2(mi);
694                 break;
695         case 4:
696                 result = nfs_try_mount_v4(mi);
697                 break;
698         default:
699                 errno = EIO;
700         }
701
702         return result;
703 }
704
705 /*
706  * Distinguish between permanent and temporary errors.
707  *
708  * Basically, we retry if communication with the server has
709  * failed so far, but fail immediately if there is a local
710  * error (like a bad mount option).
711  *
712  * ESTALE is also a temporary error because some servers
713  * return ESTALE when a share is temporarily offline.
714  *
715  * Returns 1 if we should fail immediately, or 0 if we
716  * should retry.
717  */
718 static int nfs_is_permanent_error(int error)
719 {
720         switch (error) {
721         case ESTALE:
722         case ETIMEDOUT:
723         case ECONNREFUSED:
724                 return 0;       /* temporary */
725         default:
726                 return 1;       /* permanent */
727         }
728 }
729
730 /*
731  * Handle "foreground" NFS mounts.
732  *
733  * Retry the mount request for as long as the 'retry=' option says.
734  *
735  * Returns a valid mount command exit code.
736  */
737 static int nfsmount_fg(struct nfsmount_info *mi)
738 {
739         unsigned int secs = 1;
740         time_t timeout;
741
742         timeout = nfs_parse_retry_option(mi->options,
743                                          NFS_DEF_FG_TIMEOUT_MINUTES);
744         if (verbose)
745                 printf(_("%s: timeout set for %s"),
746                         progname, ctime(&timeout));
747
748         for (;;) {
749                 if (nfs_try_mount(mi))
750                         return EX_SUCCESS;
751
752                 if (nfs_is_permanent_error(errno))
753                         break;
754
755                 if (time(NULL) > timeout) {
756                         errno = ETIMEDOUT;
757                         break;
758                 }
759
760                 if (errno != ETIMEDOUT) {
761                         if (sleep(secs))
762                                 break;
763                         secs <<= 1;
764                         if (secs > 10)
765                                 secs = 10;
766                 }
767         };
768
769         mount_error(mi->spec, mi->node, errno);
770         return EX_FAIL;
771 }
772
773 /*
774  * Handle "background" NFS mount [first try]
775  *
776  * Returns a valid mount command exit code.
777  *
778  * EX_BG should cause the caller to fork and invoke nfsmount_child.
779  */
780 static int nfsmount_parent(struct nfsmount_info *mi)
781 {
782         if (nfs_try_mount(mi))
783                 return EX_SUCCESS;
784
785         if (nfs_is_permanent_error(errno)) {
786                 mount_error(mi->spec, mi->node, errno);
787                 return EX_FAIL;
788         }
789
790         sys_mount_errors(mi->hostname, errno, 1, 1);
791         return EX_BG;
792 }
793
794 /*
795  * Handle "background" NFS mount [retry daemon]
796  *
797  * Returns a valid mount command exit code: EX_SUCCESS if successful,
798  * EX_FAIL if a failure occurred.  There's nothing to catch the
799  * error return, though, so we use sys_mount_errors to log the
800  * failure.
801  */
802 static int nfsmount_child(struct nfsmount_info *mi)
803 {
804         unsigned int secs = 1;
805         time_t timeout;
806
807         timeout = nfs_parse_retry_option(mi->options,
808                                          NFS_DEF_BG_TIMEOUT_MINUTES);
809
810         for (;;) {
811                 if (sleep(secs))
812                         break;
813                 secs <<= 1;
814                 if (secs > 120)
815                         secs = 120;
816
817                 if (nfs_try_mount(mi))
818                         return EX_SUCCESS;
819
820                 if (nfs_is_permanent_error(errno))
821                         break;
822
823                 if (time(NULL) > timeout)
824                         break;
825
826                 sys_mount_errors(mi->hostname, errno, 1, 1);
827         };
828
829         sys_mount_errors(mi->hostname, errno, 1, 0);
830         return EX_FAIL;
831 }
832
833 /*
834  * Handle "background" NFS mount
835  *
836  * Returns a valid mount command exit code.
837  */
838 static int nfsmount_bg(struct nfsmount_info *mi)
839 {
840         if (!mi->child)
841                 return nfsmount_parent(mi);
842         else
843                 return nfsmount_child(mi);
844 }
845
846 /*
847  * Process mount options and try a mount system call.
848  *
849  * Returns a valid mount command exit code.
850  */
851 static const char *nfs_background_opttbl[] = {
852         "bg",
853         "fg",
854         NULL,
855 };
856
857 static int nfsmount_start(struct nfsmount_info *mi)
858 {
859         if (!nfs_validate_options(mi))
860                 return EX_FAIL;
861
862         if (po_rightmost(mi->options, nfs_background_opttbl) == 0)
863                 return nfsmount_bg(mi);
864         else
865                 return nfsmount_fg(mi);
866 }
867
868 /**
869  * nfsmount_string - Mount an NFS file system using C string options
870  * @spec: C string specifying remote share to mount ("hostname:path")
871  * @node: C string pathname of local mounted-on directory
872  * @type: C string that represents file system type ("nfs" or "nfs4")
873  * @flags: MS_ style mount flags
874  * @extra_opts: pointer to C string containing fs-specific mount options
875  *              (input and output argument)
876  * @fake: flag indicating whether to carry out the whole operation
877  * @child: one if this is a mount daemon (bg)
878  */
879 int nfsmount_string(const char *spec, const char *node, const char *type,
880                     int flags, char **extra_opts, int fake, int child)
881 {
882         struct nfsmount_info mi = {
883                 .spec           = spec,
884                 .node           = node,
885                 .type           = type,
886                 .extra_opts     = extra_opts,
887                 .flags          = flags,
888                 .fake           = fake,
889                 .child          = child,
890         };
891         int retval = EX_FAIL;
892
893         mi.options = po_split(*extra_opts);
894         if (mi.options) {
895                 retval = nfsmount_start(&mi);
896                 po_destroy(mi.options);
897         } else
898                 nfs_error(_("%s: internal option parsing error"), progname);
899
900         free(mi.hostname);
901         return retval;
902 }