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