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