4d130146de413dc63502f6f00b3d0338e32dcd6c
[zfs.git] / lib / libshare / nfs.c
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21
22 /*
23  * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
24  * Copyright (c) 2011 Gunnar Beutner
25  */
26
27 #include <stdio.h>
28 #include <strings.h>
29 #include <fcntl.h>
30 #include <sys/wait.h>
31 #include <unistd.h>
32 #include <libzfs.h>
33 #include <libshare.h>
34 #include "libshare_impl.h"
35
36 static sa_fstype_t *nfs_fstype;
37 static boolean_t nfs_available;
38
39 /*
40  * nfs_exportfs_temp_fd refers to a temporary copy of the output
41  * from exportfs -v.
42  */
43 static int nfs_exportfs_temp_fd = -1;
44
45 typedef int (*nfs_shareopt_callback_t)(const char *opt, const char *value,
46     void *cookie);
47
48 typedef int (*nfs_host_callback_t)(const char *sharepath, const char *host,
49     const char *security, const char *access, void *cookie);
50
51 static int
52 foreach_nfs_shareopt(const char *shareopts,
53     nfs_shareopt_callback_t callback, void *cookie)
54 {
55         char *shareopts_dup, *opt, *cur, *value;
56         int was_nul, rc;
57
58         if (shareopts == NULL)
59                 return SA_OK;
60
61         shareopts_dup = strdup(shareopts);
62
63         if (shareopts_dup == NULL)
64                 return SA_NO_MEMORY;
65
66         opt = shareopts_dup;
67         was_nul = 0;
68
69         while (1) {
70                 cur = opt;
71
72                 while (*cur != ',' && *cur != '\0')
73                         cur++;
74
75                 if (*cur == '\0')
76                         was_nul = 1;
77
78                 *cur = '\0';
79
80                 if (cur > opt) {
81                         value = strchr(opt, '=');
82
83                         if (value != NULL) {
84                                 *value = '\0';
85                                 value++;
86                         }
87
88                         rc = callback(opt, value, cookie);
89
90                         if (rc != SA_OK) {
91                                 free(shareopts_dup);
92                                 return rc;
93                         }
94                 }
95
96                 opt = cur + 1;
97
98                 if (was_nul)
99                         break;
100         }
101
102         free(shareopts_dup);
103
104         return 0;
105 }
106
107 typedef struct nfs_host_cookie_s {
108         nfs_host_callback_t callback;
109         const char *sharepath;
110         void *cookie;
111         const char *security;
112 } nfs_host_cookie_t;
113
114 static int
115 foreach_nfs_host_cb(const char *opt, const char *value, void *pcookie)
116 {
117         int rc;
118         const char *access;
119         char *host_dup, *host, *next;
120         nfs_host_cookie_t *udata = (nfs_host_cookie_t *)pcookie;
121
122 #ifdef DEBUG
123         fprintf(stderr, "foreach_nfs_host_cb: key=%s, value=%s\n", opt, value);
124 #endif
125
126         if (strcmp(opt, "sec") == 0)
127                 udata->security = value;
128
129         if (strcmp(opt, "rw") == 0 || strcmp(opt, "ro") == 0) {
130                 if (value == NULL)
131                         value = "*";
132
133                 access = opt;
134
135                 host_dup = strdup(value);
136
137                 if (host_dup == NULL)
138                         return SA_NO_MEMORY;
139
140                 host = host_dup;
141
142                 do {
143                         next = strchr(host, ':');
144                         if (next != NULL) {
145                                 *next = '\0';
146                                 next++;
147                         }
148
149                         rc = udata->callback(udata->sharepath, host,
150                             udata->security, access, udata->cookie);
151
152                         if (rc != SA_OK) {
153                                 free(host_dup);
154
155                                 return rc;
156                         }
157
158                         host = next;
159                 } while (host != NULL);
160
161                 free(host_dup);
162         }
163
164         return SA_OK;
165 }
166
167 static int
168 foreach_nfs_host(sa_share_impl_t impl_share, nfs_host_callback_t callback,
169     void *cookie)
170 {
171         nfs_host_cookie_t udata;
172         char *shareopts;
173
174         udata.callback = callback;
175         udata.sharepath = impl_share->sharepath;
176         udata.cookie = cookie;
177         udata.security = "sys";
178
179         shareopts = FSINFO(impl_share, nfs_fstype)->shareopts;
180
181         return foreach_nfs_shareopt(shareopts, foreach_nfs_host_cb,
182             &udata);
183 }
184
185 static int
186 get_linux_hostspec(const char *solaris_hostspec, char **plinux_hostspec)
187 {
188         /*
189          * For now we just support CIDR masks (e.g. @192.168.0.0/16) and host
190          * wildcards (e.g. *.example.org).
191          */
192         if (solaris_hostspec[0] == '@') {
193                 /*
194                  * Solaris host specifier, e.g. @192.168.0.0/16; we just need
195                  * to skip the @ in this case
196                  */
197                 *plinux_hostspec = strdup(solaris_hostspec + 1);
198         } else {
199                 *plinux_hostspec = strdup(solaris_hostspec);
200         }
201
202         if (*plinux_hostspec == NULL) {
203                 return SA_NO_MEMORY;
204         }
205
206         return SA_OK;
207 }
208
209 static int
210 nfs_enable_share_one(const char *sharepath, const char *host,
211     const char *security, const char *access, void *pcookie)
212 {
213         pid_t pid;
214         int rc, status;
215         char *linuxhost, *hostpath, *opts;
216         const char *linux_opts = (const char *)pcookie;
217
218         pid = fork();
219
220         if (pid < 0)
221                 return SA_SYSTEM_ERR;
222
223         if (pid > 0) {
224                 while ((rc = waitpid(pid, &status, 0)) <= 0 && errno == EINTR)
225                         ; /* empty loop body */
226
227                 if (rc <= 0)
228                         return SA_SYSTEM_ERR;
229
230                 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
231                         return SA_CONFIG_ERR;
232
233                 return SA_OK;
234         }
235
236         /* child */
237
238         /* exportfs -i -o sec=XX,rX,<opts> <host>:<sharepath> */
239
240         rc = get_linux_hostspec(host, &linuxhost);
241
242         if (rc < 0)
243                 exit(1);
244
245         hostpath = malloc(strlen(linuxhost) + 1 + strlen(sharepath) + 1);
246
247         if (hostpath == NULL) {
248                 free(linuxhost);
249
250                 exit(1);
251         }
252
253         sprintf(hostpath, "%s:%s", linuxhost, sharepath);
254
255         free(linuxhost);
256
257         if (linux_opts == NULL)
258                 linux_opts = "";
259
260         opts = malloc(4 + strlen(security) + 4 + strlen(linux_opts) + 1);
261
262         if (opts == NULL)
263                 exit(1);
264
265         sprintf(opts, "sec=%s,%s,%s", security, access, linux_opts);
266
267 #ifdef DEBUG
268         fprintf(stderr, "sharing %s with opts %s\n", hostpath, opts);
269 #endif
270
271         rc = execlp("/usr/sbin/exportfs", "exportfs", "-i", \
272             "-o", opts, hostpath, NULL);
273
274         if (rc < 0) {
275                 free(hostpath);
276                 free(opts);
277                 exit(1);
278         }
279
280         exit(0);
281 }
282
283 static int
284 add_linux_shareopt(char **plinux_opts, const char *key, const char *value)
285 {
286         size_t len = 0;
287         char *new_linux_opts;
288
289         if (*plinux_opts != NULL)
290                 len = strlen(*plinux_opts);
291
292         new_linux_opts = realloc(*plinux_opts, len + 1 + strlen(key) +
293             (value ? 1 + strlen(value) : 0) + 1);
294
295         if (new_linux_opts == NULL)
296                 return SA_NO_MEMORY;
297
298         new_linux_opts[len] = '\0';
299
300         if (len > 0)
301                 strcat(new_linux_opts, ",");
302
303         strcat(new_linux_opts, key);
304
305         if (value != NULL) {
306                 strcat(new_linux_opts, "=");
307                 strcat(new_linux_opts, value);
308         }
309
310         *plinux_opts = new_linux_opts;
311
312         return SA_OK;
313 }
314
315 static int
316 get_linux_shareopts_cb(const char *key, const char *value, void *cookie)
317 {
318         char **plinux_opts = (char **)cookie;
319
320         /* host-specific options, these are taken care of elsewhere */
321         if (strcmp(key, "ro") == 0 || strcmp(key, "rw") == 0 ||
322             strcmp(key, "sec") == 0)
323                 return SA_OK;
324
325         if (strcmp(key, "anon") == 0)
326                 key = "anonuid";
327
328          if (strcmp(key, "root_mapping") == 0) {
329                  (void) add_linux_shareopt(plinux_opts, "root_squash", NULL);
330                  key = "anonuid";
331          }
332
333         if (strcmp(key, "nosub") == 0)
334                 key = "subtree_check";
335
336         if (strcmp(key, "insecure") != 0 && strcmp(key, "secure") != 0 &&
337             strcmp(key, "async") != 0 && strcmp(key, "sync") != 0 &&
338             strcmp(key, "no_wdelay") != 0 && strcmp(key, "wdelay") != 0 &&
339             strcmp(key, "nohide") != 0 && strcmp(key, "hide") != 0 &&
340             strcmp(key, "crossmnt") != 0 &&
341             strcmp(key, "no_subtree_check") != 0 &&
342             strcmp(key, "subtree_check") != 0 &&
343             strcmp(key, "insecure_locks") != 0 &&
344             strcmp(key, "secure_locks") != 0 &&
345             strcmp(key, "no_auth_nlm") != 0 && strcmp(key, "auth_nlm") != 0 &&
346             strcmp(key, "no_acl") != 0 && strcmp(key, "mountpoint") != 0 &&
347             strcmp(key, "mp") != 0 && strcmp(key, "fsuid") != 0 &&
348             strcmp(key, "refer") != 0 && strcmp(key, "replicas") != 0 &&
349             strcmp(key, "root_squash") != 0 &&
350             strcmp(key, "no_root_squash") != 0 &&
351             strcmp(key, "all_squash") != 0 &&
352             strcmp(key, "no_all_squash") != 0 &&
353             strcmp(key, "anonuid") != 0 && strcmp(key, "anongid") != 0) {
354                 return SA_SYNTAX_ERR;
355         }
356
357         (void) add_linux_shareopt(plinux_opts, key, value);
358
359         return SA_OK;
360 }
361
362 static int
363 get_linux_shareopts(const char *shareopts, char **plinux_opts)
364 {
365         int rc;
366
367         assert(plinux_opts != NULL);
368
369         *plinux_opts = NULL;
370
371         /* default options for Solaris shares */
372         (void) add_linux_shareopt(plinux_opts, "no_subtree_check", NULL);
373         (void) add_linux_shareopt(plinux_opts, "no_root_squash", NULL);
374         (void) add_linux_shareopt(plinux_opts, "mountpoint", NULL);
375
376         rc = foreach_nfs_shareopt(shareopts, get_linux_shareopts_cb, plinux_opts);
377
378         if (rc != SA_OK) {
379                 free(*plinux_opts);
380                 *plinux_opts = NULL;
381         }
382
383         return rc;
384 }
385
386 static int
387 nfs_enable_share(sa_share_impl_t impl_share)
388 {
389         char *shareopts, *linux_opts;
390         int rc;
391
392         if (!nfs_available) {
393                 return SA_SYSTEM_ERR;
394         }
395
396         shareopts = FSINFO(impl_share, nfs_fstype)->shareopts;
397
398         if (shareopts == NULL)
399                 return SA_OK;
400
401         rc = get_linux_shareopts(shareopts, &linux_opts);
402
403         if (rc != SA_OK)
404                 return rc;
405
406         rc = foreach_nfs_host(impl_share, nfs_enable_share_one, linux_opts);
407
408         free(linux_opts);
409
410         return rc;
411 }
412
413 static int
414 nfs_disable_share_one(const char *sharepath, const char *host,
415     const char *security, const char *access, void *cookie)
416 {
417         pid_t pid;
418         int rc, status;
419         char *linuxhost, *hostpath;
420
421         pid = fork();
422
423         if (pid < 0)
424                 return SA_SYSTEM_ERR;
425
426         if (pid > 0) {
427                 while ((rc = waitpid(pid, &status, 0)) <= 0 && errno == EINTR)
428                         ; /* empty loop body */
429
430                 if (rc <= 0)
431                         return SA_SYSTEM_ERR;
432
433                 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
434                         return SA_CONFIG_ERR;
435
436                 return SA_OK;
437         }
438
439         /* child */
440
441         rc = get_linux_hostspec(host, &linuxhost);
442
443         if (rc < 0)
444                 exit(1);
445
446         hostpath = malloc(strlen(linuxhost) + 1 + strlen(sharepath) + 1);
447
448         if (hostpath == NULL) {
449                 free(linuxhost);
450                 exit(1);
451         }
452
453         sprintf(hostpath, "%s:%s", linuxhost, sharepath);
454
455         free(linuxhost);
456
457 #ifdef DEBUG
458         fprintf(stderr, "unsharing %s\n", hostpath);
459 #endif
460
461         rc = execlp("/usr/sbin/exportfs", "exportfs", "-u", \
462             hostpath, NULL);
463
464         if (rc < 0) {
465                 free(hostpath);
466                 exit(1);
467         }
468
469         exit(0);
470 }
471
472 static int
473 nfs_disable_share(sa_share_impl_t impl_share)
474 {
475         if (!nfs_available) {
476                 /*
477                  * The share can't possibly be active, so nothing
478                  * needs to be done to disable it.
479                  */
480                 return SA_OK;
481         }
482
483         return foreach_nfs_host(impl_share, nfs_disable_share_one, NULL);
484 }
485
486 static int
487 nfs_validate_shareopts(const char *shareopts)
488 {
489         char *linux_opts;
490         int rc;
491
492         rc = get_linux_shareopts(shareopts, &linux_opts);
493
494         if (rc != SA_OK)
495                 return rc;
496
497         free(linux_opts);
498
499         return SA_OK;
500 }
501
502 static boolean_t
503 is_share_active(sa_share_impl_t impl_share)
504 {
505         char line[512];
506         char *tab, *cur;
507         FILE *nfs_exportfs_temp_fp;
508
509         if (nfs_exportfs_temp_fd < 0)
510                 return B_FALSE;
511
512         nfs_exportfs_temp_fp = fdopen(dup(nfs_exportfs_temp_fd), "r");
513
514         if (nfs_exportfs_temp_fp == NULL ||
515             fseek(nfs_exportfs_temp_fp, 0, SEEK_SET) < 0) {
516                 fclose(nfs_exportfs_temp_fp);
517                 return B_FALSE;
518         }
519
520         while (fgets(line, sizeof(line), nfs_exportfs_temp_fp) != NULL) {
521                 /*
522                  * exportfs uses separate lines for the share path
523                  * and the export options when the share path is longer
524                  * than a certain amount of characters; this ignores
525                  * the option lines
526                  */
527                 if (line[0] == '\t')
528                         continue;
529
530                 tab = strchr(line, '\t');
531
532                 if (tab != NULL) {
533                         *tab = '\0';
534                         cur = tab - 1;
535                 } else {
536                         /*
537                          * there's no tab character, which means the
538                          * NFS options are on a separate line; we just
539                          * need to remove the new-line character
540                          * at the end of the line
541                          */
542                         cur = line + strlen(line) - 1;
543                 }
544
545                 /* remove trailing spaces and new-line characters */
546                 while (cur >= line && (*cur == ' ' || *cur == '\n'))
547                         *cur-- = '\0';
548
549                 if (strcmp(line, impl_share->sharepath) == 0) {
550                         fclose(nfs_exportfs_temp_fp);
551                         return B_TRUE;
552                 }
553         }
554
555         fclose(nfs_exportfs_temp_fp);
556
557         return B_FALSE;
558 }
559
560 static int
561 nfs_update_shareopts(sa_share_impl_t impl_share, const char *resource,
562     const char *shareopts)
563 {
564         char *shareopts_dup;
565         boolean_t needs_reshare = B_FALSE;
566         char *old_shareopts;
567
568         FSINFO(impl_share, nfs_fstype)->active = is_share_active(impl_share);
569
570         old_shareopts = FSINFO(impl_share, nfs_fstype)->shareopts;
571
572         if (strcmp(shareopts, "on") == 0)
573                 shareopts = "rw";
574
575         if (FSINFO(impl_share, nfs_fstype)->active && old_shareopts != NULL &&
576             strcmp(old_shareopts, shareopts) != 0) {
577                 needs_reshare = B_TRUE;
578                 nfs_disable_share(impl_share);
579         }
580
581         shareopts_dup = strdup(shareopts);
582
583         if (shareopts_dup == NULL)
584                 return SA_NO_MEMORY;
585
586         if (old_shareopts != NULL)
587                 free(old_shareopts);
588
589         FSINFO(impl_share, nfs_fstype)->shareopts = shareopts_dup;
590
591         if (needs_reshare)
592                 nfs_enable_share(impl_share);
593
594         return SA_OK;
595 }
596
597
598 static void
599 nfs_clear_shareopts(sa_share_impl_t impl_share)
600 {
601         free(FSINFO(impl_share, nfs_fstype)->shareopts);
602         FSINFO(impl_share, nfs_fstype)->shareopts = NULL;
603 }
604
605 static const sa_share_ops_t nfs_shareops = {
606         .enable_share = nfs_enable_share,
607         .disable_share = nfs_disable_share,
608
609         .validate_shareopts = nfs_validate_shareopts,
610         .update_shareopts = nfs_update_shareopts,
611         .clear_shareopts = nfs_clear_shareopts,
612 };
613
614 /*
615  * nfs_check_exportfs() checks that the exportfs command runs
616  * and also maintains a temporary copy of the output from
617  * exportfs -v.
618  * To update this temporary copy simply call this function again.
619  *
620  * TODO : Use /var/lib/nfs/etab instead of our private copy.
621  *        But must implement locking to prevent concurrent access.
622  *
623  * TODO : The temporary file descriptor is never closed since
624  *        there is no libshare_nfs_fini() function.
625  */
626 static int
627 nfs_check_exportfs(void)
628 {
629         pid_t pid;
630         int rc, status;
631         static char nfs_exportfs_tempfile[] = "/tmp/exportfs.XXXXXX";
632
633         /*
634          * Close any existing temporary copies of output from exportfs.
635          * We have already called unlink() so file will be deleted.
636          */
637         if (nfs_exportfs_temp_fd >= 0)
638                 close(nfs_exportfs_temp_fd);
639
640         nfs_exportfs_temp_fd = mkstemp(nfs_exportfs_tempfile);
641
642         if (nfs_exportfs_temp_fd < 0)
643                 return SA_SYSTEM_ERR;
644
645         unlink(nfs_exportfs_tempfile);
646
647         fcntl(nfs_exportfs_temp_fd, F_SETFD, FD_CLOEXEC);
648
649         pid = fork();
650
651         if (pid < 0)
652                 return SA_SYSTEM_ERR;
653
654         if (pid > 0) {
655                 while ((rc = waitpid(pid, &status, 0)) <= 0 && errno == EINTR)
656                         ; /* empty loop body */
657
658                 if (rc <= 0)
659                         return SA_SYSTEM_ERR;
660
661                 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
662                         return SA_CONFIG_ERR;
663
664                 return SA_OK;
665         }
666
667         /* child */
668
669         /* exportfs -v */
670
671         if (dup2(nfs_exportfs_temp_fd, STDOUT_FILENO) < 0)
672                 exit(1);
673
674         rc = execlp("/usr/sbin/exportfs", "exportfs", "-v", NULL);
675
676         if (rc < 0) {
677                 exit(1);
678         }
679
680         exit(0);
681 }
682
683 void
684 libshare_nfs_init(void)
685 {
686         nfs_available = (nfs_check_exportfs() == SA_OK);
687
688         nfs_fstype = register_fstype("nfs", &nfs_shareops);
689 }