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