]> git.decadent.org.uk Git - nfs-utils.git/blob - utils/mount/stropts.c
57a4b32df3babb364a717bd560afb35e269e38c3
[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                 return 0;
519         }
520
521         if (!nfs_construct_new_options(options, nfs_saddr, &nfs_pmap,
522                                         mnt_saddr, &mnt_pmap)) {
523                 errno = EINVAL;
524                 return 0;
525         }
526
527 out:
528         errno = 0;
529         return 1;
530 }
531
532 /*
533  * Do the mount(2) system call.
534  *
535  * Returns TRUE if successful, otherwise FALSE.
536  * "errno" is set to reflect the individual error.
537  */
538 static int nfs_sys_mount(struct nfsmount_info *mi, struct mount_options *opts)
539 {
540         char *options = NULL;
541         int result;
542
543         if (po_join(opts, &options) == PO_FAILED) {
544                 errno = EIO;
545                 return 0;
546         }
547
548         if (verbose)
549                 printf(_("%s: trying text-based options '%s'\n"),
550                         progname, options);
551
552         if (mi->fake)
553                 return 1;
554
555         result = mount(mi->spec, mi->node, mi->type,
556                         mi->flags & ~(MS_USER|MS_USERS), options);
557         if (verbose && result) {
558                 int save = errno;
559                 nfs_error(_("%s: mount(2): %s"), progname, strerror(save));
560                 errno = save;
561         }
562         return !result;
563 }
564
565 /*
566  * For "-t nfs vers=2" or "-t nfs vers=3" mounts.
567  */
568 static int nfs_try_mount_v3v2(struct nfsmount_info *mi)
569 {
570         struct mount_options *options = po_dup(mi->options);
571         int result = 0;
572
573         if (!options) {
574                 errno = ENOMEM;
575                 return result;
576         }
577
578         if (!nfs_fix_mounthost_option(options, mi->hostname)) {
579                 errno = EINVAL;
580                 goto out_fail;
581         }
582         if (!mi->fake && !nfs_verify_lock_option(options)) {
583                 errno = EINVAL;
584                 goto out_fail;
585         }
586
587         /*
588          * Options we negotiate below may be stale by the time this
589          * file system is unmounted.  In order to force umount.nfs
590          * to renegotiate with the server, only write the user-
591          * specified options, and not negotiated options, to /etc/mtab.
592          */
593         if (po_join(options, mi->extra_opts) == PO_FAILED) {
594                 errno = ENOMEM;
595                 goto out_fail;
596         }
597
598         if (!nfs_rewrite_pmap_mount_options(options))
599                 goto out_fail;
600
601         result = nfs_sys_mount(mi, options);
602
603 out_fail:
604         po_destroy(options);
605         return result;
606 }
607
608 /*
609  * For "-t nfs -o vers=4" or "-t nfs4" mounts.
610  */
611 static int nfs_try_mount_v4(struct nfsmount_info *mi)
612 {
613         struct sockaddr *sap = &mi->address.sa;
614         struct mount_options *options = po_dup(mi->options);
615         int result = 0;
616
617         if (!options) {
618                 errno = ENOMEM;
619                 return result;
620         }
621
622         if (mi->version == 0) {
623                 if (po_contains(options, "mounthost") ||
624                         po_contains(options, "mountaddr") ||
625                         po_contains(options, "mountvers") ||
626                         po_contains(options, "mountproto")) {
627                 /*
628                  * Since these mountd options are set assume version 3
629                  * is wanted so error out with EPROTONOSUPPORT so the
630                  * protocol negation starts with v3.
631                  */
632                         errno = EPROTONOSUPPORT;
633                         goto out_fail;
634                 }
635                 if (po_append(options, "vers=4") == PO_FAILED) {
636                         errno = EINVAL;
637                         goto out_fail;
638                 }
639         }
640
641         if (!nfs_append_clientaddr_option(sap, mi->salen, options)) {
642                 errno = EINVAL;
643                 goto out_fail;
644         }
645
646         /*
647          * Update option string to be recorded in /etc/mtab.
648          */
649         if (po_join(options, mi->extra_opts) == PO_FAILED) {
650                 errno = ENOMEM;
651                 goto out_fail;
652         }
653
654         result = nfs_sys_mount(mi, options);
655
656 out_fail:
657         po_destroy(options);
658         return result;
659 }
660
661 /*
662  * This is a single pass through the fg/bg loop.
663  *
664  * Returns TRUE if successful, otherwise FALSE.
665  * "errno" is set to reflect the individual error.
666  */
667 static int nfs_try_mount(struct nfsmount_info *mi)
668 {
669         int result = 0;
670
671         switch (mi->version) {
672         case 0:
673                 if (linux_version_code() > MAKE_VERSION(2, 6, 31)) {
674                         errno = 0;
675                         result = nfs_try_mount_v4(mi);
676                         if (errno != EPROTONOSUPPORT) {
677                                 /* 
678                                  * To deal with legacy Linux servers that don't
679                                  * automatically export a pseudo root, retry
680                                  * ENOENT errors using version 3. And for
681                                  * Linux servers prior to 2.6.25, retry EPERM
682                                  */
683                                 if (errno != ENOENT && errno != EPERM)
684                                         break;
685                         }
686                 }
687         case 2:
688         case 3:
689                 result = nfs_try_mount_v3v2(mi);
690                 break;
691         case 4:
692                 result = nfs_try_mount_v4(mi);
693                 break;
694         default:
695                 errno = EIO;
696         }
697
698         return result;
699 }
700
701 /*
702  * Distinguish between permanent and temporary errors.
703  *
704  * Basically, we retry if communication with the server has
705  * failed so far, but fail immediately if there is a local
706  * error (like a bad mount option).
707  *
708  * ESTALE is also a temporary error because some servers
709  * return ESTALE when a share is temporarily offline.
710  *
711  * Returns 1 if we should fail immediately, or 0 if we
712  * should retry.
713  */
714 static int nfs_is_permanent_error(int error)
715 {
716         switch (error) {
717         case ESTALE:
718         case ETIMEDOUT:
719         case ECONNREFUSED:
720                 return 0;       /* temporary */
721         default:
722                 return 1;       /* permanent */
723         }
724 }
725
726 /*
727  * Handle "foreground" NFS mounts.
728  *
729  * Retry the mount request for as long as the 'retry=' option says.
730  *
731  * Returns a valid mount command exit code.
732  */
733 static int nfsmount_fg(struct nfsmount_info *mi)
734 {
735         unsigned int secs = 1;
736         time_t timeout;
737
738         timeout = nfs_parse_retry_option(mi->options,
739                                          NFS_DEF_FG_TIMEOUT_MINUTES);
740         if (verbose)
741                 printf(_("%s: timeout set for %s"),
742                         progname, ctime(&timeout));
743
744         for (;;) {
745                 if (nfs_try_mount(mi))
746                         return EX_SUCCESS;
747
748                 if (nfs_is_permanent_error(errno))
749                         break;
750
751                 if (time(NULL) > timeout) {
752                         errno = ETIMEDOUT;
753                         break;
754                 }
755
756                 if (errno != ETIMEDOUT) {
757                         if (sleep(secs))
758                                 break;
759                         secs <<= 1;
760                         if (secs > 10)
761                                 secs = 10;
762                 }
763         };
764
765         mount_error(mi->spec, mi->node, errno);
766         return EX_FAIL;
767 }
768
769 /*
770  * Handle "background" NFS mount [first try]
771  *
772  * Returns a valid mount command exit code.
773  *
774  * EX_BG should cause the caller to fork and invoke nfsmount_child.
775  */
776 static int nfsmount_parent(struct nfsmount_info *mi)
777 {
778         if (nfs_try_mount(mi))
779                 return EX_SUCCESS;
780
781         if (nfs_is_permanent_error(errno)) {
782                 mount_error(mi->spec, mi->node, errno);
783                 return EX_FAIL;
784         }
785
786         sys_mount_errors(mi->hostname, errno, 1, 1);
787         return EX_BG;
788 }
789
790 /*
791  * Handle "background" NFS mount [retry daemon]
792  *
793  * Returns a valid mount command exit code: EX_SUCCESS if successful,
794  * EX_FAIL if a failure occurred.  There's nothing to catch the
795  * error return, though, so we use sys_mount_errors to log the
796  * failure.
797  */
798 static int nfsmount_child(struct nfsmount_info *mi)
799 {
800         unsigned int secs = 1;
801         time_t timeout;
802
803         timeout = nfs_parse_retry_option(mi->options,
804                                          NFS_DEF_BG_TIMEOUT_MINUTES);
805
806         for (;;) {
807                 if (sleep(secs))
808                         break;
809                 secs <<= 1;
810                 if (secs > 120)
811                         secs = 120;
812
813                 if (nfs_try_mount(mi))
814                         return EX_SUCCESS;
815
816                 if (nfs_is_permanent_error(errno))
817                         break;
818
819                 if (time(NULL) > timeout)
820                         break;
821
822                 sys_mount_errors(mi->hostname, errno, 1, 1);
823         };
824
825         sys_mount_errors(mi->hostname, errno, 1, 0);
826         return EX_FAIL;
827 }
828
829 /*
830  * Handle "background" NFS mount
831  *
832  * Returns a valid mount command exit code.
833  */
834 static int nfsmount_bg(struct nfsmount_info *mi)
835 {
836         if (!mi->child)
837                 return nfsmount_parent(mi);
838         else
839                 return nfsmount_child(mi);
840 }
841
842 /*
843  * Process mount options and try a mount system call.
844  *
845  * Returns a valid mount command exit code.
846  */
847 static const char *nfs_background_opttbl[] = {
848         "bg",
849         "fg",
850         NULL,
851 };
852
853 static int nfsmount_start(struct nfsmount_info *mi)
854 {
855         if (!nfs_validate_options(mi))
856                 return EX_FAIL;
857
858         if (po_rightmost(mi->options, nfs_background_opttbl) == 0)
859                 return nfsmount_bg(mi);
860         else
861                 return nfsmount_fg(mi);
862 }
863
864 /**
865  * nfsmount_string - Mount an NFS file system using C string options
866  * @spec: C string specifying remote share to mount ("hostname:path")
867  * @node: C string pathname of local mounted-on directory
868  * @type: C string that represents file system type ("nfs" or "nfs4")
869  * @flags: MS_ style mount flags
870  * @extra_opts: pointer to C string containing fs-specific mount options
871  *              (input and output argument)
872  * @fake: flag indicating whether to carry out the whole operation
873  * @child: one if this is a mount daemon (bg)
874  */
875 int nfsmount_string(const char *spec, const char *node, const char *type,
876                     int flags, char **extra_opts, int fake, int child)
877 {
878         struct nfsmount_info mi = {
879                 .spec           = spec,
880                 .node           = node,
881                 .type           = type,
882                 .extra_opts     = extra_opts,
883                 .flags          = flags,
884                 .fake           = fake,
885                 .child          = child,
886         };
887         int retval = EX_FAIL;
888
889         mi.options = po_split(*extra_opts);
890         if (mi.options) {
891                 retval = nfsmount_start(&mi);
892                 po_destroy(mi.options);
893         } else
894                 nfs_error(_("%s: internal option parsing error"), progname);
895
896         free(mi.hostname);
897         return retval;
898 }