95392a1f6eb67de47fe2a76c3825fb14d32d3412
[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 #include <ctype.h>
25 #include <unistd.h>
26 #include <stdio.h>
27 #include <string.h>
28 #include <stdlib.h>
29 #include <errno.h>
30 #include <netdb.h>
31 #include <time.h>
32 #include <sys/socket.h>
33 #include <sys/mount.h>
34
35 #include "xcommon.h"
36 #include "mount.h"
37 #include "nls.h"
38 #include "nfs_mount.h"
39 #include "mount_constants.h"
40 #include "error.h"
41 #include "network.h"
42
43 #ifdef HAVE_RPCSVC_NFS_PROT_H
44 #include <rpcsvc/nfs_prot.h>
45 #else
46 #include <linux/nfs.h>
47 #define nfsstat nfs_stat
48 #endif
49
50 #ifndef NFS_PORT
51 #define NFS_PORT 2049
52 #endif
53
54 extern int nfs_mount_data_version;
55 extern char *progname;
56 extern int verbose;
57
58 static int retry_opt = 10000;           /* 10,000 minutes ~= 1 week */
59 static int bg_opt = 0;
60 static int addr_opt = 0;
61 static int ca_opt = 0;
62
63 static int parse_devname(char *hostdir, char **hostname, char **dirname)
64 {
65         char *s;
66
67         if (!(s = strchr(hostdir, ':'))) {
68                 nfs_error(_("%s: directory to mount not in host:dir format"),
69                                 progname);
70                 return -1;
71         }
72         *hostname = hostdir;
73         *dirname = s + 1;
74         *s = '\0';
75         /* Ignore all but first hostname in replicated mounts
76            until they can be fully supported. (mack@sgi.com) */
77         if ((s = strchr(hostdir, ','))) {
78                 *s = '\0';
79                 nfs_error(_("%s: warning: multiple hostnames not supported"),
80                                 progname);
81         }
82         return 0;
83 }
84
85 static int fill_ipv4_sockaddr(const char *hostname, struct sockaddr_in *addr)
86 {
87         struct hostent *hp;
88         addr->sin_family = AF_INET;
89
90         if (inet_aton(hostname, &addr->sin_addr))
91                 return 1;
92         if ((hp = gethostbyname(hostname)) == NULL) {
93                 nfs_error(_("%s: can't get address for %s\n"),
94                                 progname, hostname);
95                 return 0;
96         }
97         if (hp->h_length > sizeof(struct in_addr)) {
98                 nfs_error(_("%s: got bad hp->h_length"), progname);
99                 hp->h_length = sizeof(struct in_addr);
100         }
101         memcpy(&addr->sin_addr, hp->h_addr, hp->h_length);
102         return 1;
103 }
104
105 /*
106  * XXX: This should really use the technique neil recently added
107  * to get the address off the local end of a socket connected to
108  * the server -- to get the right address to use on multi-homed
109  * clients
110  */
111 static int get_my_ipv4addr(char *ip_addr, int len)
112 {
113         char myname[1024];
114         struct sockaddr_in myaddr;
115
116         if (gethostname(myname, sizeof(myname))) {
117                 nfs_error(_("%s: can't determine client address\n"),
118                                 progname);
119                 return 0;
120         }
121         if (!fill_ipv4_sockaddr(myname, &myaddr))
122                 return 0;
123
124         snprintf(ip_addr, len, "%s", inet_ntoa(myaddr.sin_addr));
125         ip_addr[len - 1] = '\0';
126
127         return 1;
128 }
129
130 /*
131  * Walk through our mount options string, and indicate the presence
132  * of 'bg', 'retry=', and 'clientaddr='.
133  */
134 static void extract_interesting_options(char *opts)
135 {
136         char *opt, *opteq;
137         int val;
138
139         opts = xstrdup(opts);
140
141         for (opt = strtok(opts, ","); opt; opt = strtok(NULL, ",")) {
142                 if ((opteq = strchr(opt, '='))) {
143                         val = atoi(opteq + 1);
144                         *opteq = '\0';
145                         if (strcmp(opt, "bg") == 0)
146                                 bg_opt++;
147                         else if (strcmp(opt, "retry") == 0)
148                                 retry_opt = val;
149                         else if (strcmp(opt, "addr") == 0)
150                                 addr_opt++;
151                         else if (strcmp(opt, "clientaddr") == 0)
152                                 ca_opt++;
153                 } else {
154                         if (strcmp(opt, "bg") == 0)
155                                 bg_opt++;
156                 }
157         }
158
159         free(opts);
160 }
161
162 /*
163  * Append the "addr=" option to the options string.
164  *
165  * We always add our own addr= to the end of the options string.
166  */
167 static int append_addr_opt(const char *spec, char **extra_opts)
168 {
169         static char hostdir[1024], new_opts[1024], ip_addr[255];
170         char *hostname, *dirname, *s, *old_opts;
171         struct sockaddr_in addr;
172
173         if (strlen(spec) >= sizeof(hostdir)) {
174                 nfs_error(_("%s: excessively long host:dir argument\n"),
175                                 progname);
176                 return 0;
177         }
178         strcpy(hostdir, spec);
179         if (parse_devname(hostdir, &hostname, &dirname)) {
180                 nfs_error(_("%s: parsing host:dir argument failed\n"),
181                                 progname);
182                 return 0;
183         }
184
185         if (!fill_ipv4_sockaddr(hostname, &addr))
186                 return 0;
187         if (!get_my_ipv4addr(ip_addr, sizeof(ip_addr)))
188                 return 0;
189
190         /* add IP address to mtab options for use when unmounting */
191         s = inet_ntoa(addr.sin_addr);
192         old_opts = *extra_opts;
193         if (!old_opts)
194                 old_opts = "";
195         if (strlen(old_opts) + strlen(s) + 10 >= sizeof(new_opts)) {
196                 nfs_error(_("%s: excessively long option argument\n"),
197                                 progname);
198                 return 0;
199         }
200         snprintf(new_opts, sizeof(new_opts), "%s%saddr=%s",
201                  old_opts, *old_opts ? "," : "", s);
202         *extra_opts = xstrdup(new_opts);
203
204         return 1;
205 }
206
207 /*
208  * Append the "clientaddr=" option to the options string.
209  *
210  * Returns 1 if clientaddr option created successfully;
211  * otherwise zero.
212  */
213 static int append_clientaddr_opt(const char *spec, char **extra_opts)
214 {
215         static char new_opts[1024], cbuf[1024];
216         static char ip_addr[16] = "127.0.0.1";
217
218         if (!get_my_ipv4addr(ip_addr, sizeof(ip_addr)))
219                 return 0;
220
221         /* Ensure we have enough padding for the following strcat()s */
222         if (strlen(*extra_opts) + strlen(ip_addr) + 10 >= sizeof(new_opts)) {
223                 nfs_error(_("%s: excessively long option argument"),
224                                 progname);
225                 return 0;
226         }
227
228         strcat(new_opts, *extra_opts);
229
230         snprintf(cbuf, sizeof(cbuf) - 1, "%sclientaddr=%s",
231                         *extra_opts ? "," : "", ip_addr);
232         strcat(new_opts, cbuf);
233
234         *extra_opts = xstrdup(new_opts);
235
236         return 1;
237 }
238
239 /*
240  * nfsmount_s - Mount an NFSv2 or v3 file system using C string options
241  *
242  * @spec:       C string hostname:path specifying remoteshare to mount
243  * @node:       C string pathname of local mounted on directory
244  * @flags:      MS_ style flags
245  * @extra_opts: pointer to C string containing fs-specific mount options
246  *              (possibly also a return argument)
247  * @fake:       flag indicating whether to carry out the whole operation
248  * @bg:         one if this is a backgrounded mount attempt
249  *
250  * XXX: need to handle bg, fg, and retry options.
251  */
252 int nfsmount_s(const char *spec, const char *node, int flags,
253                 char **extra_opts, int fake, int bg)
254 {
255         int retval = EX_FAIL;
256
257         extract_interesting_options(*extra_opts);
258
259         if (!addr_opt && !append_addr_opt(spec, extra_opts))
260                 goto fail;
261
262         if (verbose)
263                 printf(_("%s: text-based options: '%s'\n"),
264                         progname, *extra_opts);
265
266         if (!fake) {
267                 if (mount(spec, node, "nfs",
268                                 flags & ~(MS_USER|MS_USERS), *extra_opts)) {
269                         mount_error(spec, node, errno);
270                         goto fail;
271                 }
272         }
273
274         return 0;
275
276 fail:
277         return retval;
278 }
279
280 /*
281  * nfs4mount_s - Mount an NFSv4 file system using C string options
282  *
283  * @spec:       C string hostname:path specifying remoteshare to mount
284  * @node:       C string pathname of local mounted on directory
285  * @flags:      MS_ style flags
286  * @extra_opts: pointer to C string containing fs-specific mount options
287  *              (possibly also a return argument)
288  * @fake:       flag indicating whether to carry out the whole operation
289  * @bg:         one if this is a backgrounded mount attempt
290  *
291  * XXX: need to handle bg, fg, and retry options.
292  *
293  */
294 int nfs4mount_s(const char *spec, const char *node, int flags,
295                 char **extra_opts, int fake)
296 {
297         int retval = EX_FAIL;
298
299         extract_interesting_options(*extra_opts);
300
301         if (!addr_opt && !append_addr_opt(spec, extra_opts))
302                 goto fail;
303
304         if (!ca_opt && !append_clientaddr_opt(spec, extra_opts))
305                 goto fail;
306
307         if (verbose)
308                 printf(_("%s: text-based options: '%s'\n"),
309                         progname, *extra_opts);
310
311         if (!fake) {
312                 if (mount(spec, node, "nfs4",
313                                 flags & ~(MS_USER|MS_USERS), *extra_opts)) {
314                         mount_error(spec, node, errno);
315                         goto fail;
316                 }
317         }
318
319         return 0;
320
321 fail:
322         return retval;
323 }