Implemented sharing datasets via SMB using libshare
[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  * Copyright (c) 2012 Cyril Plisko. All rights reserved.
26  */
27
28 #include <stdio.h>
29 #include <strings.h>
30 #include <fcntl.h>
31 #include <sys/wait.h>
32 #include <unistd.h>
33 #include <libzfs.h>
34 #include <libshare.h>
35 #include "libshare_impl.h"
36
37 static boolean_t nfs_available(void);
38
39 static sa_fstype_t *nfs_fstype;
40
41 /*
42  * nfs_exportfs_temp_fd refers to a temporary copy of the output
43  * from exportfs -v.
44  */
45 static int nfs_exportfs_temp_fd = -1;
46
47 typedef int (*nfs_shareopt_callback_t)(const char *opt, const char *value,
48     void *cookie);
49
50 typedef int (*nfs_host_callback_t)(const char *sharepath, const char *host,
51     const char *security, const char *access, void *cookie);
52
53 /**
54  * Invokes the specified callback function for each Solaris share option
55  * listed in the specified string.
56  */
57 static int
58 foreach_nfs_shareopt(const char *shareopts,
59     nfs_shareopt_callback_t callback, void *cookie)
60 {
61         char *shareopts_dup, *opt, *cur, *value;
62         int was_nul, rc;
63
64         if (shareopts == NULL)
65                 return SA_OK;
66
67         shareopts_dup = strdup(shareopts);
68
69         if (shareopts_dup == NULL)
70                 return SA_NO_MEMORY;
71
72         opt = shareopts_dup;
73         was_nul = 0;
74
75         while (1) {
76                 cur = opt;
77
78                 while (*cur != ',' && *cur != '\0')
79                         cur++;
80
81                 if (*cur == '\0')
82                         was_nul = 1;
83
84                 *cur = '\0';
85
86                 if (cur > opt) {
87                         value = strchr(opt, '=');
88
89                         if (value != NULL) {
90                                 *value = '\0';
91                                 value++;
92                         }
93
94                         rc = callback(opt, value, cookie);
95
96                         if (rc != SA_OK) {
97                                 free(shareopts_dup);
98                                 return rc;
99                         }
100                 }
101
102                 opt = cur + 1;
103
104                 if (was_nul)
105                         break;
106         }
107
108         free(shareopts_dup);
109
110         return 0;
111 }
112
113 typedef struct nfs_host_cookie_s {
114         nfs_host_callback_t callback;
115         const char *sharepath;
116         void *cookie;
117         const char *security;
118 } nfs_host_cookie_t;
119
120 /**
121  * Helper function for foreach_nfs_host. This function checks whether the
122  * current share option is a host specification and invokes a callback
123  * function with information about the host.
124  */
125 static int
126 foreach_nfs_host_cb(const char *opt, const char *value, void *pcookie)
127 {
128         int rc;
129         const char *access;
130         char *host_dup, *host, *next;
131         nfs_host_cookie_t *udata = (nfs_host_cookie_t *)pcookie;
132
133 #ifdef DEBUG
134         fprintf(stderr, "foreach_nfs_host_cb: key=%s, value=%s\n", opt, value);
135 #endif
136
137         if (strcmp(opt, "sec") == 0)
138                 udata->security = value;
139
140         if (strcmp(opt, "rw") == 0 || strcmp(opt, "ro") == 0) {
141                 if (value == NULL)
142                         value = "*";
143
144                 access = opt;
145
146                 host_dup = strdup(value);
147
148                 if (host_dup == NULL)
149                         return SA_NO_MEMORY;
150
151                 host = host_dup;
152
153                 do {
154                         next = strchr(host, ':');
155                         if (next != NULL) {
156                                 *next = '\0';
157                                 next++;
158                         }
159
160                         rc = udata->callback(udata->sharepath, host,
161                             udata->security, access, udata->cookie);
162
163                         if (rc != SA_OK) {
164                                 free(host_dup);
165
166                                 return rc;
167                         }
168
169                         host = next;
170                 } while (host != NULL);
171
172                 free(host_dup);
173         }
174
175         return SA_OK;
176 }
177
178 /**
179  * Invokes a callback function for all NFS hosts that are set for a share.
180  */
181 static int
182 foreach_nfs_host(sa_share_impl_t impl_share, nfs_host_callback_t callback,
183     void *cookie)
184 {
185         nfs_host_cookie_t udata;
186         char *shareopts;
187
188         udata.callback = callback;
189         udata.sharepath = impl_share->sharepath;
190         udata.cookie = cookie;
191         udata.security = "sys";
192
193         shareopts = FSINFO(impl_share, nfs_fstype)->shareopts;
194
195         return foreach_nfs_shareopt(shareopts, foreach_nfs_host_cb,
196             &udata);
197 }
198
199 /**
200  * Converts a Solaris NFS host specification to its Linux equivalent.
201  */
202 static int
203 get_linux_hostspec(const char *solaris_hostspec, char **plinux_hostspec)
204 {
205         /*
206          * For now we just support CIDR masks (e.g. @192.168.0.0/16) and host
207          * wildcards (e.g. *.example.org).
208          */
209         if (solaris_hostspec[0] == '@') {
210                 /*
211                  * Solaris host specifier, e.g. @192.168.0.0/16; we just need
212                  * to skip the @ in this case
213                  */
214                 *plinux_hostspec = strdup(solaris_hostspec + 1);
215         } else {
216                 *plinux_hostspec = strdup(solaris_hostspec);
217         }
218
219         if (*plinux_hostspec == NULL) {
220                 return SA_NO_MEMORY;
221         }
222
223         return SA_OK;
224 }
225
226 /**
227  * Used internally by nfs_enable_share to enable sharing for a single host.
228  */
229 static int
230 nfs_enable_share_one(const char *sharepath, const char *host,
231     const char *security, const char *access, void *pcookie)
232 {
233         int rc;
234         char *linuxhost, *hostpath, *opts;
235         const char *linux_opts = (const char *)pcookie;
236         char *argv[6];
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         argv[0] = "/usr/sbin/exportfs";
272         argv[1] = "-i";
273         argv[2] = "-o";
274         argv[3] = opts;
275         argv[4] = hostpath;
276         argv[5] = NULL;
277
278         rc = libzfs_run_process(argv[0], argv, 0);
279
280         free(hostpath);
281         free(opts);
282
283         if (rc < 0)
284                 return SA_SYSTEM_ERR;
285         else
286                 return SA_OK;
287 }
288
289 /**
290  * Adds a Linux share option to an array of NFS options.
291  */
292 static int
293 add_linux_shareopt(char **plinux_opts, const char *key, const char *value)
294 {
295         size_t len = 0;
296         char *new_linux_opts;
297
298         if (*plinux_opts != NULL)
299                 len = strlen(*plinux_opts);
300
301         new_linux_opts = realloc(*plinux_opts, len + 1 + strlen(key) +
302             (value ? 1 + strlen(value) : 0) + 1);
303
304         if (new_linux_opts == NULL)
305                 return SA_NO_MEMORY;
306
307         new_linux_opts[len] = '\0';
308
309         if (len > 0)
310                 strcat(new_linux_opts, ",");
311
312         strcat(new_linux_opts, key);
313
314         if (value != NULL) {
315                 strcat(new_linux_opts, "=");
316                 strcat(new_linux_opts, value);
317         }
318
319         *plinux_opts = new_linux_opts;
320
321         return SA_OK;
322 }
323
324 /**
325  * Validates and converts a single Solaris share option to its Linux
326  * equivalent.
327  */
328 static int
329 get_linux_shareopts_cb(const char *key, const char *value, void *cookie)
330 {
331         char **plinux_opts = (char **)cookie;
332
333         /* host-specific options, these are taken care of elsewhere */
334         if (strcmp(key, "ro") == 0 || strcmp(key, "rw") == 0 ||
335             strcmp(key, "sec") == 0)
336                 return SA_OK;
337
338         if (strcmp(key, "anon") == 0)
339                 key = "anonuid";
340
341          if (strcmp(key, "root_mapping") == 0) {
342                  (void) add_linux_shareopt(plinux_opts, "root_squash", NULL);
343                  key = "anonuid";
344          }
345
346         if (strcmp(key, "nosub") == 0)
347                 key = "subtree_check";
348
349         if (strcmp(key, "insecure") != 0 && strcmp(key, "secure") != 0 &&
350             strcmp(key, "async") != 0 && strcmp(key, "sync") != 0 &&
351             strcmp(key, "no_wdelay") != 0 && strcmp(key, "wdelay") != 0 &&
352             strcmp(key, "nohide") != 0 && strcmp(key, "hide") != 0 &&
353             strcmp(key, "crossmnt") != 0 &&
354             strcmp(key, "no_subtree_check") != 0 &&
355             strcmp(key, "subtree_check") != 0 &&
356             strcmp(key, "insecure_locks") != 0 &&
357             strcmp(key, "secure_locks") != 0 &&
358             strcmp(key, "no_auth_nlm") != 0 && strcmp(key, "auth_nlm") != 0 &&
359             strcmp(key, "no_acl") != 0 && strcmp(key, "mountpoint") != 0 &&
360             strcmp(key, "mp") != 0 && strcmp(key, "fsuid") != 0 &&
361             strcmp(key, "refer") != 0 && strcmp(key, "replicas") != 0 &&
362             strcmp(key, "root_squash") != 0 &&
363             strcmp(key, "no_root_squash") != 0 &&
364             strcmp(key, "all_squash") != 0 &&
365             strcmp(key, "no_all_squash") != 0 && strcmp(key, "fsid") != 0 &&
366             strcmp(key, "anonuid") != 0 && strcmp(key, "anongid") != 0) {
367                 return SA_SYNTAX_ERR;
368         }
369
370         (void) add_linux_shareopt(plinux_opts, key, value);
371
372         return SA_OK;
373 }
374
375 /**
376  * Takes a string containing Solaris share options (e.g. "sync,no_acl") and
377  * converts them to a NULL-terminated array of Linux NFS options.
378  */
379 static int
380 get_linux_shareopts(const char *shareopts, char **plinux_opts)
381 {
382         int rc;
383
384         assert(plinux_opts != NULL);
385
386         *plinux_opts = NULL;
387
388         /* default options for Solaris shares */
389         (void) add_linux_shareopt(plinux_opts, "no_subtree_check", NULL);
390         (void) add_linux_shareopt(plinux_opts, "no_root_squash", NULL);
391         (void) add_linux_shareopt(plinux_opts, "mountpoint", NULL);
392
393         rc = foreach_nfs_shareopt(shareopts, get_linux_shareopts_cb, plinux_opts);
394
395         if (rc != SA_OK) {
396                 free(*plinux_opts);
397                 *plinux_opts = NULL;
398         }
399
400         return rc;
401 }
402
403 /**
404  * Enables NFS sharing for the specified share.
405  */
406 static int
407 nfs_enable_share(sa_share_impl_t impl_share)
408 {
409         char *shareopts, *linux_opts;
410         int rc;
411
412         if (!nfs_available()) {
413                 return SA_SYSTEM_ERR;
414         }
415
416         shareopts = FSINFO(impl_share, nfs_fstype)->shareopts;
417
418         if (shareopts == NULL)
419                 return SA_OK;
420
421         rc = get_linux_shareopts(shareopts, &linux_opts);
422
423         if (rc != SA_OK)
424                 return rc;
425
426         rc = foreach_nfs_host(impl_share, nfs_enable_share_one, linux_opts);
427
428         free(linux_opts);
429
430         return rc;
431 }
432
433 /**
434  * Used internally by nfs_disable_share to disable sharing for a single host.
435  */
436 static int
437 nfs_disable_share_one(const char *sharepath, const char *host,
438     const char *security, const char *access, void *cookie)
439 {
440         int rc;
441         char *linuxhost, *hostpath;
442         char *argv[4];
443
444         rc = get_linux_hostspec(host, &linuxhost);
445
446         if (rc < 0)
447                 exit(1);
448
449         hostpath = malloc(strlen(linuxhost) + 1 + strlen(sharepath) + 1);
450
451         if (hostpath == NULL) {
452                 free(linuxhost);
453                 exit(1);
454         }
455
456         sprintf(hostpath, "%s:%s", linuxhost, sharepath);
457
458         free(linuxhost);
459
460 #ifdef DEBUG
461         fprintf(stderr, "unsharing %s\n", hostpath);
462 #endif
463
464         argv[0] = "/usr/sbin/exportfs";
465         argv[1] = "-u";
466         argv[2] = hostpath;
467         argv[3] = NULL;
468
469         rc = libzfs_run_process(argv[0], argv, 0);
470
471         free(hostpath);
472
473         if (rc < 0)
474                 return SA_SYSTEM_ERR;
475         else
476                 return SA_OK;
477 }
478
479 /**
480  * Disables NFS sharing for the specified share.
481  */
482 static int
483 nfs_disable_share(sa_share_impl_t impl_share)
484 {
485         if (!nfs_available()) {
486                 /*
487                  * The share can't possibly be active, so nothing
488                  * needs to be done to disable it.
489                  */
490                 return SA_OK;
491         }
492
493         return foreach_nfs_host(impl_share, nfs_disable_share_one, NULL);
494 }
495
496 /**
497  * Checks whether the specified NFS share options are syntactically correct.
498  */
499 static int
500 nfs_validate_shareopts(const char *shareopts)
501 {
502         char *linux_opts;
503         int rc;
504
505         rc = get_linux_shareopts(shareopts, &linux_opts);
506
507         if (rc != SA_OK)
508                 return rc;
509
510         free(linux_opts);
511
512         return SA_OK;
513 }
514
515 /**
516  * Checks whether a share is currently active.
517  */
518 static boolean_t
519 nfs_is_share_active(sa_share_impl_t impl_share)
520 {
521         char line[512];
522         char *tab, *cur;
523         FILE *nfs_exportfs_temp_fp;
524
525         if (!nfs_available())
526                 return B_FALSE;
527
528         nfs_exportfs_temp_fp = fdopen(dup(nfs_exportfs_temp_fd), "r");
529
530         if (nfs_exportfs_temp_fp == NULL ||
531             fseek(nfs_exportfs_temp_fp, 0, SEEK_SET) < 0) {
532                 fclose(nfs_exportfs_temp_fp);
533                 return B_FALSE;
534         }
535
536         while (fgets(line, sizeof(line), nfs_exportfs_temp_fp) != NULL) {
537                 /*
538                  * exportfs uses separate lines for the share path
539                  * and the export options when the share path is longer
540                  * than a certain amount of characters; this ignores
541                  * the option lines
542                  */
543                 if (line[0] == '\t')
544                         continue;
545
546                 tab = strchr(line, '\t');
547
548                 if (tab != NULL) {
549                         *tab = '\0';
550                         cur = tab - 1;
551                 } else {
552                         /*
553                          * there's no tab character, which means the
554                          * NFS options are on a separate line; we just
555                          * need to remove the new-line character
556                          * at the end of the line
557                          */
558                         cur = line + strlen(line) - 1;
559                 }
560
561                 /* remove trailing spaces and new-line characters */
562                 while (cur >= line && (*cur == ' ' || *cur == '\n'))
563                         *cur-- = '\0';
564
565                 if (strcmp(line, impl_share->sharepath) == 0) {
566                         fclose(nfs_exportfs_temp_fp);
567                         return B_TRUE;
568                 }
569         }
570
571         fclose(nfs_exportfs_temp_fp);
572
573         return B_FALSE;
574 }
575
576 /**
577  * Called to update a share's options. A share's options might be out of
578  * date if the share was loaded from disk (i.e. /etc/dfs/sharetab) and the
579  * "sharenfs" dataset property has changed in the meantime. This function
580  * also takes care of re-enabling the share if necessary.
581  */
582 static int
583 nfs_update_shareopts(sa_share_impl_t impl_share, const char *resource,
584     const char *shareopts)
585 {
586         char *shareopts_dup;
587         boolean_t needs_reshare = B_FALSE;
588         char *old_shareopts;
589
590         FSINFO(impl_share, nfs_fstype)->active =
591             nfs_is_share_active(impl_share);
592
593         old_shareopts = FSINFO(impl_share, nfs_fstype)->shareopts;
594
595         if (strcmp(shareopts, "on") == 0)
596                 shareopts = "rw";
597
598         if (FSINFO(impl_share, nfs_fstype)->active && old_shareopts != NULL &&
599             strcmp(old_shareopts, shareopts) != 0) {
600                 needs_reshare = B_TRUE;
601                 nfs_disable_share(impl_share);
602         }
603
604         shareopts_dup = strdup(shareopts);
605
606         if (shareopts_dup == NULL)
607                 return SA_NO_MEMORY;
608
609         if (old_shareopts != NULL)
610                 free(old_shareopts);
611
612         FSINFO(impl_share, nfs_fstype)->shareopts = shareopts_dup;
613
614         if (needs_reshare)
615                 nfs_enable_share(impl_share);
616
617         return SA_OK;
618 }
619
620 /**
621  * Clears a share's NFS options. Used by libshare to
622  * clean up shares that are about to be free()'d.
623  */
624 static void
625 nfs_clear_shareopts(sa_share_impl_t impl_share)
626 {
627         free(FSINFO(impl_share, nfs_fstype)->shareopts);
628         FSINFO(impl_share, nfs_fstype)->shareopts = NULL;
629 }
630
631 static const sa_share_ops_t nfs_shareops = {
632         .enable_share = nfs_enable_share,
633         .disable_share = nfs_disable_share,
634
635         .validate_shareopts = nfs_validate_shareopts,
636         .update_shareopts = nfs_update_shareopts,
637         .clear_shareopts = nfs_clear_shareopts,
638 };
639
640 /*
641  * nfs_check_exportfs() checks that the exportfs command runs
642  * and also maintains a temporary copy of the output from
643  * exportfs -v.
644  * To update this temporary copy simply call this function again.
645  *
646  * TODO : Use /var/lib/nfs/etab instead of our private copy.
647  *        But must implement locking to prevent concurrent access.
648  *
649  * TODO : The temporary file descriptor is never closed since
650  *        there is no libshare_nfs_fini() function.
651  */
652 static int
653 nfs_check_exportfs(void)
654 {
655         pid_t pid;
656         int rc, status;
657         static char nfs_exportfs_tempfile[] = "/tmp/exportfs.XXXXXX";
658
659         /*
660          * Close any existing temporary copies of output from exportfs.
661          * We have already called unlink() so file will be deleted.
662          */
663         if (nfs_exportfs_temp_fd >= 0)
664                 close(nfs_exportfs_temp_fd);
665
666         nfs_exportfs_temp_fd = mkstemp(nfs_exportfs_tempfile);
667
668         if (nfs_exportfs_temp_fd < 0)
669                 return SA_SYSTEM_ERR;
670
671         unlink(nfs_exportfs_tempfile);
672
673         fcntl(nfs_exportfs_temp_fd, F_SETFD, FD_CLOEXEC);
674
675         pid = fork();
676
677         if (pid < 0) {
678                 (void) close(nfs_exportfs_temp_fd);
679                 nfs_exportfs_temp_fd = -1;
680                 return SA_SYSTEM_ERR;
681         }
682
683         if (pid > 0) {
684                 while ((rc = waitpid(pid, &status, 0)) <= 0 && errno == EINTR)
685                         ; /* empty loop body */
686
687                 if (rc <= 0) {
688                         (void) close(nfs_exportfs_temp_fd);
689                         nfs_exportfs_temp_fd = -1;
690                         return SA_SYSTEM_ERR;
691                 }
692
693                 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
694                         (void) close(nfs_exportfs_temp_fd);
695                         nfs_exportfs_temp_fd = -1;
696                         return SA_CONFIG_ERR;
697                 }
698
699                 return SA_OK;
700         }
701
702         /* child */
703
704         /* exportfs -v */
705
706         if (dup2(nfs_exportfs_temp_fd, STDOUT_FILENO) < 0)
707                 exit(1);
708
709         rc = execlp("/usr/sbin/exportfs", "exportfs", "-v", NULL);
710
711         if (rc < 0) {
712                 exit(1);
713         }
714
715         exit(0);
716 }
717
718 /*
719  * Provides a convenient wrapper for determing nfs availability
720  */
721 static boolean_t
722 nfs_available(void)
723 {
724         if (nfs_exportfs_temp_fd == -1)
725                 (void) nfs_check_exportfs();
726
727         return (nfs_exportfs_temp_fd != -1) ? B_TRUE : B_FALSE;
728 }
729
730 /**
731  * Initializes the NFS functionality of libshare.
732  */
733 void
734 libshare_nfs_init(void)
735 {
736         nfs_fstype = register_fstype("nfs", &nfs_shareops);
737 }