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