]> git.proxmox.com Git - mirror_zfs.git/blame - lib/libshare/os/linux/nfs.c
zpool should call zfs_nicestrtonum() with non-NULL handle
[mirror_zfs.git] / lib / libshare / os / linux / nfs.c
CommitLineData
46e18b3f
GB
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
27ccd414 25 * Copyright (c) 2012 Cyril Plisko. All rights reserved.
c15d36c6 26 * Copyright (c) 2019, 2020 by Delphix. All rights reserved.
46e18b3f
GB
27 */
28
c15d36c6 29#include <dirent.h>
46e18b3f 30#include <stdio.h>
93ce2b4c 31#include <string.h>
46e18b3f 32#include <strings.h>
93ce2b4c 33#include <errno.h>
75bf636c 34#include <fcntl.h>
c15d36c6
GW
35#include <sys/file.h>
36#include <sys/stat.h>
37#include <sys/types.h>
46e18b3f
GB
38#include <sys/wait.h>
39#include <unistd.h>
40#include <libzfs.h>
41#include <libshare.h>
42#include "libshare_impl.h"
60356b1a 43#include "nfs.h"
46e18b3f 44
c15d36c6
GW
45#define ZFS_EXPORTS_DIR "/etc/exports.d"
46#define ZFS_EXPORTS_FILE ZFS_EXPORTS_DIR"/zfs.exports"
47#define ZFS_EXPORTS_LOCK ZFS_EXPORTS_FILE".lock"
27ccd414 48
46e18b3f 49static sa_fstype_t *nfs_fstype;
46e18b3f 50
c15d36c6
GW
51typedef int (*nfs_shareopt_callback_t)(const char *opt, const char *value,
52 void *cookie);
53
54typedef int (*nfs_host_callback_t)(const char *sharepath, const char *filename,
55 const char *host, const char *security, const char *access, void *cookie);
56
d1d7e268 57/*
590338f6
GB
58 * Invokes the specified callback function for each Solaris share option
59 * listed in the specified string.
60 */
46e18b3f
GB
61static int
62foreach_nfs_shareopt(const char *shareopts,
63 nfs_shareopt_callback_t callback, void *cookie)
64{
65 char *shareopts_dup, *opt, *cur, *value;
c15d36c6 66 int was_nul, error;
46e18b3f
GB
67
68 if (shareopts == NULL)
d1d7e268 69 return (SA_OK);
46e18b3f 70
c15d36c6
GW
71 if (strcmp(shareopts, "on") == 0)
72 shareopts = "rw,crossmnt";
73
46e18b3f
GB
74 shareopts_dup = strdup(shareopts);
75
c15d36c6 76
46e18b3f 77 if (shareopts_dup == NULL)
d1d7e268 78 return (SA_NO_MEMORY);
46e18b3f
GB
79
80 opt = shareopts_dup;
81 was_nul = 0;
82
83 while (1) {
84 cur = opt;
85
86 while (*cur != ',' && *cur != '\0')
87 cur++;
88
89 if (*cur == '\0')
90 was_nul = 1;
91
92 *cur = '\0';
93
94 if (cur > opt) {
95 value = strchr(opt, '=');
96
97 if (value != NULL) {
98 *value = '\0';
99 value++;
100 }
101
c15d36c6 102 error = callback(opt, value, cookie);
46e18b3f 103
c15d36c6 104 if (error != SA_OK) {
46e18b3f 105 free(shareopts_dup);
c15d36c6 106 return (error);
46e18b3f
GB
107 }
108 }
109
110 opt = cur + 1;
111
112 if (was_nul)
113 break;
114 }
115
116 free(shareopts_dup);
117
c15d36c6 118 return (SA_OK);
46e18b3f
GB
119}
120
121typedef struct nfs_host_cookie_s {
122 nfs_host_callback_t callback;
123 const char *sharepath;
124 void *cookie;
c15d36c6 125 const char *filename;
46e18b3f
GB
126 const char *security;
127} nfs_host_cookie_t;
128
d1d7e268 129/*
590338f6
GB
130 * Helper function for foreach_nfs_host. This function checks whether the
131 * current share option is a host specification and invokes a callback
132 * function with information about the host.
133 */
46e18b3f
GB
134static int
135foreach_nfs_host_cb(const char *opt, const char *value, void *pcookie)
136{
c15d36c6 137 int error;
46e18b3f
GB
138 const char *access;
139 char *host_dup, *host, *next;
140 nfs_host_cookie_t *udata = (nfs_host_cookie_t *)pcookie;
141
142#ifdef DEBUG
143 fprintf(stderr, "foreach_nfs_host_cb: key=%s, value=%s\n", opt, value);
144#endif
145
146 if (strcmp(opt, "sec") == 0)
147 udata->security = value;
148
149 if (strcmp(opt, "rw") == 0 || strcmp(opt, "ro") == 0) {
150 if (value == NULL)
151 value = "*";
152
153 access = opt;
154
155 host_dup = strdup(value);
156
157 if (host_dup == NULL)
d1d7e268 158 return (SA_NO_MEMORY);
46e18b3f
GB
159
160 host = host_dup;
161
162 do {
163 next = strchr(host, ':');
164 if (next != NULL) {
165 *next = '\0';
166 next++;
167 }
168
c15d36c6
GW
169 error = udata->callback(udata->filename,
170 udata->sharepath, host, udata->security,
171 access, udata->cookie);
46e18b3f 172
c15d36c6 173 if (error != SA_OK) {
46e18b3f
GB
174 free(host_dup);
175
c15d36c6 176 return (error);
46e18b3f
GB
177 }
178
179 host = next;
180 } while (host != NULL);
181
182 free(host_dup);
183 }
184
d1d7e268 185 return (SA_OK);
46e18b3f
GB
186}
187
d1d7e268 188/*
590338f6
GB
189 * Invokes a callback function for all NFS hosts that are set for a share.
190 */
46e18b3f 191static int
c15d36c6
GW
192foreach_nfs_host(sa_share_impl_t impl_share, char *filename,
193 nfs_host_callback_t callback, void *cookie)
46e18b3f
GB
194{
195 nfs_host_cookie_t udata;
196 char *shareopts;
197
198 udata.callback = callback;
c15d36c6 199 udata.sharepath = impl_share->sa_mountpoint;
46e18b3f 200 udata.cookie = cookie;
c15d36c6 201 udata.filename = filename;
46e18b3f
GB
202 udata.security = "sys";
203
204 shareopts = FSINFO(impl_share, nfs_fstype)->shareopts;
205
c15d36c6
GW
206 return (foreach_nfs_shareopt(shareopts, foreach_nfs_host_cb,
207 &udata));
46e18b3f
GB
208}
209
d1d7e268 210/*
590338f6
GB
211 * Converts a Solaris NFS host specification to its Linux equivalent.
212 */
46e18b3f
GB
213static int
214get_linux_hostspec(const char *solaris_hostspec, char **plinux_hostspec)
215{
216 /*
217 * For now we just support CIDR masks (e.g. @192.168.0.0/16) and host
218 * wildcards (e.g. *.example.org).
219 */
220 if (solaris_hostspec[0] == '@') {
221 /*
222 * Solaris host specifier, e.g. @192.168.0.0/16; we just need
223 * to skip the @ in this case
224 */
225 *plinux_hostspec = strdup(solaris_hostspec + 1);
226 } else {
227 *plinux_hostspec = strdup(solaris_hostspec);
228 }
229
230 if (*plinux_hostspec == NULL) {
d1d7e268 231 return (SA_NO_MEMORY);
46e18b3f
GB
232 }
233
d1d7e268 234 return (SA_OK);
46e18b3f
GB
235}
236
d1d7e268 237/*
590338f6
GB
238 * Adds a Linux share option to an array of NFS options.
239 */
46e18b3f
GB
240static int
241add_linux_shareopt(char **plinux_opts, const char *key, const char *value)
242{
243 size_t len = 0;
244 char *new_linux_opts;
245
246 if (*plinux_opts != NULL)
247 len = strlen(*plinux_opts);
248
249 new_linux_opts = realloc(*plinux_opts, len + 1 + strlen(key) +
250 (value ? 1 + strlen(value) : 0) + 1);
251
252 if (new_linux_opts == NULL)
d1d7e268 253 return (SA_NO_MEMORY);
46e18b3f
GB
254
255 new_linux_opts[len] = '\0';
256
257 if (len > 0)
258 strcat(new_linux_opts, ",");
259
260 strcat(new_linux_opts, key);
261
262 if (value != NULL) {
263 strcat(new_linux_opts, "=");
264 strcat(new_linux_opts, value);
265 }
266
267 *plinux_opts = new_linux_opts;
268
d1d7e268 269 return (SA_OK);
46e18b3f
GB
270}
271
d1d7e268 272/*
590338f6
GB
273 * Validates and converts a single Solaris share option to its Linux
274 * equivalent.
275 */
46e18b3f
GB
276static int
277get_linux_shareopts_cb(const char *key, const char *value, void *cookie)
278{
279 char **plinux_opts = (char **)cookie;
280
281 /* host-specific options, these are taken care of elsewhere */
282 if (strcmp(key, "ro") == 0 || strcmp(key, "rw") == 0 ||
283 strcmp(key, "sec") == 0)
d1d7e268 284 return (SA_OK);
46e18b3f
GB
285
286 if (strcmp(key, "anon") == 0)
287 key = "anonuid";
288
d1d7e268
MK
289 if (strcmp(key, "root_mapping") == 0) {
290 (void) add_linux_shareopt(plinux_opts, "root_squash", NULL);
291 key = "anonuid";
292 }
46e18b3f
GB
293
294 if (strcmp(key, "nosub") == 0)
295 key = "subtree_check";
296
297 if (strcmp(key, "insecure") != 0 && strcmp(key, "secure") != 0 &&
298 strcmp(key, "async") != 0 && strcmp(key, "sync") != 0 &&
299 strcmp(key, "no_wdelay") != 0 && strcmp(key, "wdelay") != 0 &&
300 strcmp(key, "nohide") != 0 && strcmp(key, "hide") != 0 &&
301 strcmp(key, "crossmnt") != 0 &&
302 strcmp(key, "no_subtree_check") != 0 &&
303 strcmp(key, "subtree_check") != 0 &&
304 strcmp(key, "insecure_locks") != 0 &&
305 strcmp(key, "secure_locks") != 0 &&
306 strcmp(key, "no_auth_nlm") != 0 && strcmp(key, "auth_nlm") != 0 &&
307 strcmp(key, "no_acl") != 0 && strcmp(key, "mountpoint") != 0 &&
308 strcmp(key, "mp") != 0 && strcmp(key, "fsuid") != 0 &&
309 strcmp(key, "refer") != 0 && strcmp(key, "replicas") != 0 &&
310 strcmp(key, "root_squash") != 0 &&
311 strcmp(key, "no_root_squash") != 0 &&
312 strcmp(key, "all_squash") != 0 &&
d2e032ca 313 strcmp(key, "no_all_squash") != 0 && strcmp(key, "fsid") != 0 &&
46e18b3f 314 strcmp(key, "anonuid") != 0 && strcmp(key, "anongid") != 0) {
d1d7e268 315 return (SA_SYNTAX_ERR);
46e18b3f
GB
316 }
317
318 (void) add_linux_shareopt(plinux_opts, key, value);
319
d1d7e268 320 return (SA_OK);
46e18b3f
GB
321}
322
d1d7e268 323/*
590338f6
GB
324 * Takes a string containing Solaris share options (e.g. "sync,no_acl") and
325 * converts them to a NULL-terminated array of Linux NFS options.
326 */
46e18b3f
GB
327static int
328get_linux_shareopts(const char *shareopts, char **plinux_opts)
329{
c15d36c6 330 int error;
46e18b3f
GB
331
332 assert(plinux_opts != NULL);
333
334 *plinux_opts = NULL;
335
d2923072 336 /* no_subtree_check - Default as of nfs-utils v1.1.0 */
46e18b3f 337 (void) add_linux_shareopt(plinux_opts, "no_subtree_check", NULL);
d2923072
BB
338
339 /* mountpoint - Restrict exports to ZFS mountpoints */
46e18b3f
GB
340 (void) add_linux_shareopt(plinux_opts, "mountpoint", NULL);
341
c15d36c6 342 error = foreach_nfs_shareopt(shareopts, get_linux_shareopts_cb,
d1d7e268 343 plinux_opts);
46e18b3f 344
c15d36c6 345 if (error != SA_OK) {
46e18b3f
GB
346 free(*plinux_opts);
347 *plinux_opts = NULL;
348 }
349
c15d36c6 350 return (error);
46e18b3f
GB
351}
352
d1d7e268 353/*
c15d36c6
GW
354 * This function populates an entry into /etc/exports.d/zfs.exports.
355 * This file is consumed by the linux nfs server so that zfs shares are
356 * automatically exported upon boot or whenever the nfs server restarts.
590338f6 357 */
46e18b3f 358static int
c15d36c6
GW
359nfs_add_entry(const char *filename, const char *sharepath,
360 const char *host, const char *security, const char *access_opts,
361 void *pcookie)
46e18b3f 362{
c15d36c6
GW
363 int error;
364 char *linuxhost;
365 const char *linux_opts = (const char *)pcookie;
46e18b3f 366
c15d36c6
GW
367 error = get_linux_hostspec(host, &linuxhost);
368 if (error != SA_OK)
369 return (error);
46e18b3f 370
c15d36c6
GW
371 if (linux_opts == NULL)
372 linux_opts = "";
46e18b3f 373
10b575d0 374 FILE *fp = fopen(filename, "a+e");
c15d36c6
GW
375 if (fp == NULL) {
376 fprintf(stderr, "failed to open %s file: %s", filename,
377 strerror(errno));
46e18b3f 378 free(linuxhost);
c15d36c6 379 return (SA_SYSTEM_ERR);
46e18b3f
GB
380 }
381
c15d36c6
GW
382 if (fprintf(fp, "%s %s(sec=%s,%s,%s)\n", sharepath, linuxhost,
383 security, access_opts, linux_opts) < 0) {
384 fprintf(stderr, "failed to write to %s\n", filename);
385 free(linuxhost);
386 fclose(fp);
387 return (SA_SYSTEM_ERR);
388 }
46e18b3f
GB
389
390 free(linuxhost);
c15d36c6
GW
391 if (fclose(fp) != 0) {
392 fprintf(stderr, "Unable to close file %s: %s\n",
393 filename, strerror(errno));
d1d7e268 394 return (SA_SYSTEM_ERR);
c15d36c6
GW
395 }
396 return (SA_OK);
46e18b3f
GB
397}
398
d1d7e268 399/*
c15d36c6
GW
400 * This function copies all entries from the exports file to "filename",
401 * omitting any entries for the specified mountpoint.
590338f6 402 */
327c9046 403int
c15d36c6 404nfs_copy_entries(char *filename, const char *mountpoint)
46e18b3f 405{
c15d36c6
GW
406 char *buf = NULL;
407 size_t buflen = 0;
408 int error = SA_OK;
409
10b575d0
AZ
410 FILE *oldfp = fopen(ZFS_EXPORTS_FILE, "re");
411 FILE *newfp = fopen(filename, "w+e");
bd76bcb3
GW
412 if (newfp == NULL) {
413 fprintf(stderr, "failed to open %s file: %s", filename,
414 strerror(errno));
415 fclose(oldfp);
416 return (SA_SYSTEM_ERR);
417 }
c15d36c6 418 fputs(FILE_HEADER, newfp);
c15d36c6 419
bd76bcb3
GW
420 /*
421 * The ZFS_EXPORTS_FILE may not exist yet. If that's the
422 * case then just write out the new file.
423 */
424 if (oldfp != NULL) {
425 while (getline(&buf, &buflen, oldfp) != -1) {
426 char *space = NULL;
c15d36c6 427
bd76bcb3 428 if (buf[0] == '\n' || buf[0] == '#')
c15d36c6 429 continue;
bd76bcb3
GW
430
431 if ((space = strchr(buf, ' ')) != NULL) {
432 int mountpoint_len = strlen(mountpoint);
433
434 if (space - buf == mountpoint_len &&
435 strncmp(mountpoint, buf,
436 mountpoint_len) == 0) {
437 continue;
438 }
c15d36c6 439 }
bd76bcb3 440 fputs(buf, newfp);
c15d36c6 441 }
c15d36c6 442
bd76bcb3
GW
443 if (ferror(oldfp) != 0) {
444 error = ferror(oldfp);
445 }
446 if (fclose(oldfp) != 0) {
447 fprintf(stderr, "Unable to close file %s: %s\n",
448 filename, strerror(errno));
449 error = error != 0 ? error : SA_SYSTEM_ERR;
450 }
c15d36c6 451 }
bd76bcb3 452
c15d36c6
GW
453 if (error == 0 && ferror(newfp) != 0) {
454 error = ferror(newfp);
46e18b3f
GB
455 }
456
c15d36c6
GW
457 free(buf);
458 if (fclose(newfp) != 0) {
459 fprintf(stderr, "Unable to close file %s: %s\n",
460 filename, strerror(errno));
461 error = error != 0 ? error : SA_SYSTEM_ERR;
462 }
c15d36c6 463 return (error);
46e18b3f
GB
464}
465
d1d7e268 466/*
c15d36c6 467 * Enables NFS sharing for the specified share.
590338f6 468 */
46e18b3f 469static int
dc3a56d3 470nfs_enable_share_impl(sa_share_impl_t impl_share, char *filename)
46e18b3f 471{
c15d36c6 472 char *shareopts, *linux_opts;
c15d36c6 473 int error;
46e18b3f 474
c15d36c6
GW
475 shareopts = FSINFO(impl_share, nfs_fstype)->shareopts;
476 error = get_linux_shareopts(shareopts, &linux_opts);
dc3a56d3 477 if (error != SA_OK)
c15d36c6 478 return (error);
c15d36c6
GW
479
480 error = foreach_nfs_host(impl_share, filename, nfs_add_entry,
481 linux_opts);
482 free(linux_opts);
c15d36c6 483 return (error);
46e18b3f
GB
484}
485
dc3a56d3
AZ
486static int
487nfs_enable_share(sa_share_impl_t impl_share)
488{
489 return (nfs_toggle_share(
490 ZFS_EXPORTS_LOCK, ZFS_EXPORTS_FILE, ZFS_EXPORTS_DIR, impl_share,
491 nfs_enable_share_impl));
492}
493
d1d7e268 494/*
c15d36c6 495 * Disables NFS sharing for the specified share.
590338f6 496 */
dc3a56d3
AZ
497static int
498nfs_disable_share_impl(sa_share_impl_t impl_share, char *filename)
499{
500 return (SA_OK);
501}
502
c15d36c6
GW
503static int
504nfs_disable_share(sa_share_impl_t impl_share)
46e18b3f 505{
dc3a56d3
AZ
506 return (nfs_toggle_share(
507 ZFS_EXPORTS_LOCK, ZFS_EXPORTS_FILE, ZFS_EXPORTS_DIR, impl_share,
508 nfs_disable_share_impl));
c15d36c6 509}
46e18b3f 510
c15d36c6
GW
511static boolean_t
512nfs_is_shared(sa_share_impl_t impl_share)
513{
514 size_t buflen = 0;
515 char *buf = NULL;
aed0e9f3 516
10b575d0 517 FILE *fp = fopen(ZFS_EXPORTS_FILE, "re");
c15d36c6 518 if (fp == NULL) {
d1d7e268 519 return (B_FALSE);
5333eb0b 520 }
c15d36c6
GW
521 while ((getline(&buf, &buflen, fp)) != -1) {
522 char *space = NULL;
523
524 if ((space = strchr(buf, ' ')) != NULL) {
525 int mountpoint_len = strlen(impl_share->sa_mountpoint);
526
527 if (space - buf == mountpoint_len &&
528 strncmp(impl_share->sa_mountpoint, buf,
529 mountpoint_len) == 0) {
530 fclose(fp);
531 free(buf);
532 return (B_TRUE);
533 }
5333eb0b 534 }
46e18b3f 535 }
c15d36c6
GW
536 free(buf);
537 fclose(fp);
d1d7e268 538 return (B_FALSE);
46e18b3f
GB
539}
540
d1d7e268 541/*
c15d36c6 542 * Checks whether the specified NFS share options are syntactically correct.
590338f6 543 */
46e18b3f 544static int
c15d36c6 545nfs_validate_shareopts(const char *shareopts)
46e18b3f 546{
c15d36c6
GW
547 char *linux_opts;
548 int error;
46e18b3f 549
c15d36c6 550 error = get_linux_shareopts(shareopts, &linux_opts);
46e18b3f 551
c15d36c6
GW
552 if (error != SA_OK)
553 return (error);
46e18b3f 554
c15d36c6
GW
555 free(linux_opts);
556 return (SA_OK);
557}
46e18b3f 558
c15d36c6
GW
559static int
560nfs_update_shareopts(sa_share_impl_t impl_share, const char *shareopts)
561{
562 FSINFO(impl_share, nfs_fstype)->shareopts = (char *)shareopts;
d1d7e268 563 return (SA_OK);
46e18b3f
GB
564}
565
d1d7e268 566/*
590338f6
GB
567 * Clears a share's NFS options. Used by libshare to
568 * clean up shares that are about to be free()'d.
569 */
46e18b3f
GB
570static void
571nfs_clear_shareopts(sa_share_impl_t impl_share)
572{
46e18b3f
GB
573 FSINFO(impl_share, nfs_fstype)->shareopts = NULL;
574}
575
c15d36c6
GW
576static int
577nfs_commit_shares(void)
578{
579 char *argv[] = {
580 "/usr/sbin/exportfs",
581 "-ra",
582 NULL
583 };
584
585 return (libzfs_run_process(argv[0], argv, 0));
586}
587
46e18b3f
GB
588static const sa_share_ops_t nfs_shareops = {
589 .enable_share = nfs_enable_share,
590 .disable_share = nfs_disable_share,
c15d36c6 591 .is_shared = nfs_is_shared,
46e18b3f
GB
592
593 .validate_shareopts = nfs_validate_shareopts,
594 .update_shareopts = nfs_update_shareopts,
595 .clear_shareopts = nfs_clear_shareopts,
c15d36c6 596 .commit_shares = nfs_commit_shares,
46e18b3f
GB
597};
598
d1d7e268 599/*
590338f6
GB
600 * Initializes the NFS functionality of libshare.
601 */
46e18b3f
GB
602void
603libshare_nfs_init(void)
604{
46e18b3f
GB
605 nfs_fstype = register_fstype("nfs", &nfs_shareops);
606}