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