diff options
author | George Wilson <[email protected]> | 2020-07-13 11:19:18 -0500 |
---|---|---|
committer | GitHub <[email protected]> | 2020-07-13 09:19:18 -0700 |
commit | c15d36c674bcfb10975fc835978d4a49d159bf0b (patch) | |
tree | 4d4aeadaf69fe83875229d7c7bee8a7fa1b8480e | |
parent | e59a377a8fdbf4e66864d1654c0055fdff5f12f4 (diff) |
Remove dependency on sharetab file and refactor sharing logic
== Motivation and Context
The current implementation of 'sharenfs' and 'sharesmb' relies on
the use of the sharetab file. The use of this file is os-specific
and not required by linux or freebsd. Currently the code must
maintain updates to this file which adds complexity and presents
a significant performance impact when sharing many datasets. In
addition, concurrently running 'zfs sharenfs' command results in
missing entries in the sharetab file leading to unexpected failures.
== Description
This change removes the sharetab logic from the linux and freebsd
implementation of 'sharenfs' and 'sharesmb'. It still preserves an
os-specific library which contains the logic required for sharing
NFS or SMB. The following entry points exist in the vastly simplified
libshare library:
- sa_enable_share -- shares a dataset but may not commit the change
- sa_disable_share -- unshares a dataset but may not commit the change
- sa_is_shared -- determine if a dataset is shared
- sa_commit_share -- notify NFS/SMB subsystem to commit the shares
- sa_validate_shareopts -- determine if sharing options are valid
The sa_commit_share entry point is provided as a performance enhancement
and is not required. The sa_enable_share/sa_disable_share may commit
the share as part of the implementation. Libshare provides a framework
for both NFS and SMB but some operating systems may not fully support
these protocols or all features of the protocol.
NFS Operation:
For linux, libshare updates /etc/exports.d/zfs.exports to add
and remove shares and then commits the changes by invoking
'exportfs -r'. This file, is automatically read by the kernel NFS
implementation which makes for better integration with the NFS systemd
service. For FreeBSD, libshare updates /etc/zfs/exports to add and
remove shares and then commits the changes by sending a SIGHUP to
mountd.
SMB Operation:
For linux, libshare adds and removes files in /var/lib/samba/usershares
by calling the 'net' command directly. There is no need to commit the
changes. FreeBSD does not support SMB.
== Performance Results
To test sharing performance we created a pool with an increasing number
of datasets and invoked various zfs actions that would enable and
disable sharing. The performance testing was limited to NFS sharing.
The following tests were performed on an 8 vCPU system with 128GB and
a pool comprised of 4 50GB SSDs:
Scale testing:
- Share all filesystems in parallel -- zfs sharenfs=on <dataset> &
- Unshare all filesystems in parallel -- zfs sharenfs=off <dataset> &
Functional testing:
- share each filesystem serially -- zfs share -a
- unshare each filesystem serially -- zfs unshare -a
- reset sharenfs property and unshare -- zfs inherit -r sharenfs <pool>
For 'zfs sharenfs=on' scale testing we saw an average reduction in time
of 89.43% and for 'zfs sharenfs=off' we saw an average reduction in time
of 83.36%.
Functional testing also shows a huge improvement:
- zfs share -- 97.97% reduction in time
- zfs unshare -- 96.47% reduction in time
- zfs inhert -r sharenfs -- 99.01% reduction in time
Reviewed-by: Matt Ahrens <[email protected]>
Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Ryan Moeller <[email protected]>
Reviewed-by: Bryant G. Ly <[email protected]>
Signed-off-by: George Wilson <[email protected]>
External-Issue: DLPX-68690
Closes #1603
Closes #7692
Closes #7943
Closes #10300
30 files changed, 1545 insertions, 1651 deletions
diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index 224ed1604..c6a29095e 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -772,6 +772,7 @@ zfs_mount_and_share(libzfs_handle_t *hdl, const char *dataset, zfs_type_t type) "successfully created, but not shared\n")); ret = 1; } + zfs_commit_all_shares(); } zfs_close(zhp); @@ -6927,6 +6928,8 @@ share_mount(int op, int argc, char **argv) zfs_foreach_mountpoint(g_zfs, cb.cb_handles, cb.cb_used, share_mount_one_cb, &share_mount_state, op == OP_MOUNT && !(flags & MS_CRYPT)); + zfs_commit_all_shares(); + ret = share_mount_state.sm_status; for (int i = 0; i < cb.cb_used; i++) @@ -6979,6 +6982,7 @@ share_mount(int op, int argc, char **argv) } else { ret = share_mount_one(zhp, op, flags, NULL, B_TRUE, options); + zfs_commit_all_shares(); zfs_close(zhp); } } @@ -7108,6 +7112,7 @@ unshare_unmount_path(int op, char *path, int flags, boolean_t is_manual) "not currently shared\n"), path); } else { ret = zfs_unshareall_bypath(zhp, path); + zfs_commit_all_shares(); } } else { char mtpt_prop[ZFS_MAXPROPLEN]; @@ -7328,6 +7333,9 @@ unshare_unmount(int op, int argc, char **argv) free(node); } + if (op == OP_SHARE) + zfs_commit_shares(protocol); + uu_avl_walk_end(walk); uu_avl_destroy(tree); uu_avl_pool_destroy(pool); diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c index cdf5511fe..de689afa7 100644 --- a/cmd/zpool/zpool_main.c +++ b/cmd/zpool/zpool_main.c @@ -22,7 +22,7 @@ /* * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2011 Nexenta Systems, Inc. All rights reserved. - * Copyright (c) 2011, 2018 by Delphix. All rights reserved. + * Copyright (c) 2011, 2020 by Delphix. All rights reserved. * Copyright (c) 2012 by Frederik Wessels. All rights reserved. * Copyright (c) 2012 by Cyril Plisko. All rights reserved. * Copyright (c) 2013 by Prasad Joshi (sTec). All rights reserved. @@ -1587,8 +1587,10 @@ zpool_do_create(int argc, char **argv) zfs_handle_t *pool = zfs_open(g_zfs, tname ? tname : poolname, ZFS_TYPE_FILESYSTEM); if (pool != NULL) { - if (zfs_mount(pool, NULL, 0) == 0) + if (zfs_mount(pool, NULL, 0) == 0) { ret = zfs_shareall(pool); + zfs_commit_all_shares(); + } zfs_close(pool); } } else if (libzfs_errno(g_zfs) == EZFS_INVALIDNAME) { diff --git a/etc/systemd/system/zfs-share.service.in b/etc/systemd/system/zfs-share.service.in index 5f4ba411b..b72008587 100644 --- a/etc/systemd/system/zfs-share.service.in +++ b/etc/systemd/system/zfs-share.service.in @@ -12,7 +12,6 @@ PartOf=smb.service [Service] Type=oneshot RemainAfterExit=yes -ExecStartPre=-/bin/rm -f /etc/dfs/sharetab ExecStart=@sbindir@/zfs share -a [Install] diff --git a/include/libzfs.h b/include/libzfs.h index a93f694a1..6f797c0a3 100644 --- a/include/libzfs.h +++ b/include/libzfs.h @@ -837,6 +837,10 @@ extern int zfs_unshareall_bytype(zfs_handle_t *, const char *, const char *); extern int zfs_unshareall(zfs_handle_t *); extern int zfs_deleg_share_nfs(libzfs_handle_t *, char *, char *, char *, void *, void *, int, zfs_share_op_t); +extern void zfs_commit_nfs_shares(void); +extern void zfs_commit_smb_shares(void); +extern void zfs_commit_all_shares(void); +extern void zfs_commit_shares(const char *); extern int zfs_nicestrtonum(libzfs_handle_t *, const char *, uint64_t *); diff --git a/include/libzfs_impl.h b/include/libzfs_impl.h index 35b92f6d9..0f1116797 100644 --- a/include/libzfs_impl.h +++ b/include/libzfs_impl.h @@ -49,7 +49,6 @@ struct libzfs_handle { int libzfs_error; int libzfs_fd; FILE *libzfs_mnttab; - FILE *libzfs_sharetab; zpool_handle_t *libzfs_pool_handles; uu_avl_pool_t *libzfs_ns_avlpool; uu_avl_t *libzfs_ns_avl; @@ -59,8 +58,6 @@ struct libzfs_handle { char libzfs_desc[1024]; int libzfs_printerr; int libzfs_storeerr; /* stuff error messages into buffer */ - void *libzfs_sharehdl; /* libshare handle */ - uint_t libzfs_shareflags; boolean_t libzfs_mnttab_enable; /* * We need a lock to handle the case where parallel mount @@ -76,8 +73,6 @@ struct libzfs_handle { regex_t libzfs_urire; }; -#define ZFSSHARE_MISS 0x01 /* Didn't find entry in cache */ - struct zfs_handle { libzfs_handle_t *zfs_hdl; zpool_handle_t *zpool_hdl; @@ -206,12 +201,6 @@ int zfs_validate_name(libzfs_handle_t *hdl, const char *path, int type, void namespace_clear(libzfs_handle_t *); -/* - * libshare (sharemgr) interfaces used internally. - */ - -extern int zfs_init_libshare(libzfs_handle_t *, int); -extern void zfs_uninit_libshare(libzfs_handle_t *); extern int zfs_parse_options(char *, zfs_share_proto_t); extern int zfs_unshare_proto(zfs_handle_t *, @@ -256,13 +245,14 @@ extern int unshare_one(libzfs_handle_t *hdl, const char *name, const char *mountpoint, zfs_share_proto_t proto); extern boolean_t zfs_is_mountable(zfs_handle_t *zhp, char *buf, size_t buflen, zprop_source_t *source, int flags); -extern zfs_share_type_t is_shared_impl(libzfs_handle_t *hdl, - const char *mountpoint, zfs_share_proto_t proto); +extern zfs_share_type_t is_shared(const char *mountpoint, + zfs_share_proto_t proto); extern int libzfs_load_module(void); extern int zpool_relabel_disk(libzfs_handle_t *hdl, const char *path, const char *msg); extern int find_shares_object(differ_info_t *di); extern void libzfs_set_pipe_max(int infd); +extern void zfs_commit_proto(zfs_share_proto_t *); #ifdef __cplusplus } diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h index 1bfd7a485..ef87c24a4 100644 --- a/include/sys/fs/zfs.h +++ b/include/sys/fs/zfs.h @@ -1170,7 +1170,6 @@ typedef struct ddt_histogram { #define ZVOL_DRIVER "zvol" #define ZFS_DRIVER "zfs" #define ZFS_DEV "/dev/zfs" -#define ZFS_SHARETAB "/etc/dfs/sharetab" #define ZFS_SUPER_MAGIC 0x2fc12fc1 diff --git a/lib/libshare/Makefile.am b/lib/libshare/Makefile.am index 1504644c0..e730ee3be 100644 --- a/lib/libshare/Makefile.am +++ b/lib/libshare/Makefile.am @@ -1,13 +1,25 @@ include $(top_srcdir)/config/Rules.am +DEFAULT_INCLUDES += -I$(srcdir) + noinst_LTLIBRARIES = libshare.la USER_C = \ libshare_impl.h \ libshare.c \ - nfs.c \ nfs.h \ - smb.c \ smb.h +if BUILD_LINUX +USER_C += \ + os/linux/nfs.c \ + os/linux/smb.c +endif + +if BUILD_FREEBSD +USER_C += \ + os/freebsd/nfs.c \ + os/freebsd/smb.c +endif + libshare_la_SOURCES = $(USER_C) diff --git a/lib/libshare/libshare.c b/lib/libshare/libshare.c index 36074641d..d32a282a3 100644 --- a/lib/libshare/libshare.c +++ b/lib/libshare/libshare.c @@ -22,6 +22,7 @@ /* * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2011 Gunnar Beutner + * Copyright (c) 2018, 2020 by Delphix. All rights reserved. */ #include <stdio.h> @@ -29,30 +30,20 @@ #include <errno.h> #include <strings.h> #include <libintl.h> +#include <sys/file.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <libzfs.h> #include <libshare.h> +#include "libzfs_impl.h" #include "libshare_impl.h" #include "nfs.h" #include "smb.h" -static sa_share_impl_t find_share(sa_handle_impl_t handle, - const char *sharepath); -static sa_share_impl_t alloc_share(const char *sharepath); +static sa_share_impl_t alloc_share(const char *zfsname, const char *path); static void free_share(sa_share_impl_t share); -static void parse_sharetab(sa_handle_impl_t impl_handle); -static int process_share(sa_handle_impl_t impl_handle, - sa_share_impl_t impl_share, char *pathname, char *resource, - char *fstype, char *options, char *description, - char *dataset, boolean_t from_sharetab); -static void update_sharetab(sa_handle_impl_t impl_handle); - -static int update_zfs_share(sa_share_impl_t impl_handle, const char *proto); -static int update_zfs_shares(sa_handle_impl_t impl_handle, const char *proto); - static int fstypes_count; static sa_fstype_t *fstypes; @@ -78,28 +69,6 @@ register_fstype(const char *name, const sa_share_ops_t *ops) return (fstype); } -sa_handle_t -sa_init(int init_service) -{ - sa_handle_impl_t impl_handle; - - impl_handle = calloc(1, sizeof (struct sa_handle_impl)); - - if (impl_handle == NULL) - return (NULL); - - impl_handle->zfs_libhandle = libzfs_init(); - - if (impl_handle->zfs_libhandle != NULL) { - libzfs_print_on_error(impl_handle->zfs_libhandle, B_TRUE); - } - - parse_sharetab(impl_handle); - update_zfs_shares(impl_handle, NULL); - - return ((sa_handle_t)impl_handle); -} - __attribute__((constructor)) static void libshare_init(void) { @@ -107,448 +76,101 @@ libshare_init(void) libshare_smb_init(); } -static void -parse_sharetab(sa_handle_impl_t impl_handle) -{ - FILE *fp; - char line[512]; - char *eol, *pathname, *resource, *fstype, *options, *description; - - fp = fopen(ZFS_SHARETAB, "r"); - - if (fp == NULL) - return; - - while (fgets(line, sizeof (line), fp) != NULL) { - eol = line + strlen(line) - 1; - - while (eol >= line) { - if (*eol != '\r' && *eol != '\n') - break; - - *eol = '\0'; - eol--; - } - - pathname = line; - - if ((resource = strchr(pathname, '\t')) == NULL) - continue; - - *resource = '\0'; - resource++; - - if ((fstype = strchr(resource, '\t')) == NULL) - continue; - - *fstype = '\0'; - fstype++; - - if ((options = strchr(fstype, '\t')) == NULL) - continue; - - *options = '\0'; - options++; - - if ((description = strchr(fstype, '\t')) != NULL) { - *description = '\0'; - description++; - } - - if (strcmp(resource, "-") == 0) - resource = NULL; - - (void) process_share(impl_handle, NULL, pathname, resource, - fstype, options, description, NULL, B_TRUE); - } - - fclose(fp); -} - -static void -update_sharetab(sa_handle_impl_t impl_handle) -{ - sa_share_impl_t impl_share; - int temp_fd; - FILE *temp_fp; - char tempfile[] = ZFS_SHARETAB".XXXXXX"; - sa_fstype_t *fstype; - const char *resource; - - if (mkdir("/etc/dfs", 0755) < 0 && errno != EEXIST) { - return; - } - - temp_fd = mkstemp(tempfile); - - if (temp_fd < 0) - return; - - temp_fp = fdopen(temp_fd, "w"); - - if (temp_fp == NULL) - return; - - impl_share = impl_handle->shares; - while (impl_share != NULL) { - fstype = fstypes; - while (fstype != NULL) { - if (FSINFO(impl_share, fstype)->active && - FSINFO(impl_share, fstype)->shareopts != NULL) { - resource = FSINFO(impl_share, fstype)->resource; - - if (resource == NULL) - resource = "-"; - - fprintf(temp_fp, "%s\t%s\t%s\t%s\n", - impl_share->sharepath, resource, - fstype->name, - FSINFO(impl_share, fstype)->shareopts); - } - - fstype = fstype->next; - } - - impl_share = impl_share->next; - } - - fflush(temp_fp); - fsync(temp_fd); - fclose(temp_fp); - - (void) rename(tempfile, ZFS_SHARETAB); -} - -typedef struct update_cookie_s { - sa_handle_impl_t handle; - const char *proto; -} update_cookie_t; - -static int -update_zfs_shares_cb(zfs_handle_t *zhp, void *pcookie) -{ - update_cookie_t *udata = (update_cookie_t *)pcookie; - char mountpoint[ZFS_MAXPROPLEN]; - char shareopts[ZFS_MAXPROPLEN]; - char *dataset; - zfs_type_t type = zfs_get_type(zhp); - - if (type == ZFS_TYPE_FILESYSTEM && - zfs_iter_filesystems(zhp, update_zfs_shares_cb, pcookie) != 0) { - zfs_close(zhp); - return (1); - } - - if (type != ZFS_TYPE_FILESYSTEM) { - zfs_close(zhp); - return (0); - } - - if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mountpoint, - sizeof (mountpoint), NULL, NULL, 0, B_FALSE) != 0) { - zfs_close(zhp); - return (0); - } - - dataset = (char *)zfs_get_name(zhp); - - if (dataset == NULL) { - zfs_close(zhp); - return (0); - } - - if (!zfs_is_mounted(zhp, NULL)) { - zfs_close(zhp); - return (0); - } - - if ((udata->proto == NULL || strcmp(udata->proto, "nfs") == 0) && - zfs_prop_get(zhp, ZFS_PROP_SHARENFS, shareopts, - sizeof (shareopts), NULL, NULL, 0, B_FALSE) == 0 && - strcmp(shareopts, "off") != 0) { - (void) process_share(udata->handle, NULL, mountpoint, NULL, - "nfs", shareopts, NULL, dataset, B_FALSE); - } - - if ((udata->proto == NULL || strcmp(udata->proto, "smb") == 0) && - zfs_prop_get(zhp, ZFS_PROP_SHARESMB, shareopts, - sizeof (shareopts), NULL, NULL, 0, B_FALSE) == 0 && - strcmp(shareopts, "off") != 0) { - (void) process_share(udata->handle, NULL, mountpoint, NULL, - "smb", shareopts, NULL, dataset, B_FALSE); - } - - zfs_close(zhp); - - return (0); -} - -static int -update_zfs_share(sa_share_impl_t impl_share, const char *proto) -{ - sa_handle_impl_t impl_handle = impl_share->handle; - zfs_handle_t *zhp; - update_cookie_t udata; - - if (impl_handle->zfs_libhandle == NULL) - return (SA_SYSTEM_ERR); - - assert(impl_share->dataset != NULL); - - zhp = zfs_open(impl_share->handle->zfs_libhandle, impl_share->dataset, - ZFS_TYPE_FILESYSTEM); - - if (zhp == NULL) - return (SA_SYSTEM_ERR); - - udata.handle = impl_handle; - udata.proto = proto; - (void) update_zfs_shares_cb(zhp, &udata); - - return (SA_OK); -} - -static int -update_zfs_shares(sa_handle_impl_t impl_handle, const char *proto) -{ - update_cookie_t udata; - - if (impl_handle->zfs_libhandle == NULL) - return (SA_SYSTEM_ERR); - - udata.handle = impl_handle; - udata.proto = proto; - (void) zfs_iter_root(impl_handle->zfs_libhandle, update_zfs_shares_cb, - &udata); - - return (SA_OK); -} - -static int -process_share(sa_handle_impl_t impl_handle, sa_share_impl_t impl_share, - char *pathname, char *resource, char *proto, - char *options, char *description, char *dataset, - boolean_t from_sharetab) +int +sa_enable_share(const char *zfsname, const char *mountpoint, + const char *shareopts, char *protocol) { - struct stat statbuf; - int rc; - char *resource_dup = NULL, *dataset_dup = NULL; - boolean_t new_share; + int rc, ret = SA_OK; + boolean_t found_protocol = B_FALSE; sa_fstype_t *fstype; - new_share = B_FALSE; - + sa_share_impl_t impl_share = alloc_share(zfsname, mountpoint); if (impl_share == NULL) - impl_share = find_share(impl_handle, pathname); - - if (impl_share == NULL) { - if (lstat(pathname, &statbuf) != 0 || - !S_ISDIR(statbuf.st_mode)) - return (SA_BAD_PATH); - - impl_share = alloc_share(pathname); - - if (impl_share == NULL) { - rc = SA_NO_MEMORY; - goto err; - } - - new_share = B_TRUE; - } - - if (dataset != NULL) { - dataset_dup = strdup(dataset); - - if (dataset_dup == NULL) { - rc = SA_NO_MEMORY; - goto err; - } - } - - free(impl_share->dataset); - impl_share->dataset = dataset_dup; - - rc = SA_INVALID_PROTOCOL; + return (SA_NO_MEMORY); fstype = fstypes; while (fstype != NULL) { - if (strcmp(fstype->name, proto) == 0) { - if (resource != NULL) { - resource_dup = strdup(resource); - - if (resource_dup == NULL) { - rc = SA_NO_MEMORY; - goto err; - } - } - - free(FSINFO(impl_share, fstype)->resource); - FSINFO(impl_share, fstype)->resource = resource_dup; + if (strcmp(fstype->name, protocol) == 0) { rc = fstype->ops->update_shareopts(impl_share, - resource, options); + shareopts); + if (rc != SA_OK) + break; - if (rc == SA_OK && from_sharetab) - FSINFO(impl_share, fstype)->active = B_TRUE; + rc = fstype->ops->enable_share(impl_share); + if (rc != SA_OK) + ret = rc; - break; + found_protocol = B_TRUE; } fstype = fstype->next; } + free_share(impl_share); - if (rc != SA_OK) - goto err; - - if (new_share) { - impl_share->handle = impl_handle; - - impl_share->next = impl_handle->shares; - impl_handle->shares = impl_share; - - } - -err: - if (rc != SA_OK) { - if (new_share) - free_share(impl_share); - } - - return (rc); -} - -void -sa_fini(sa_handle_t handle) -{ - sa_handle_impl_t impl_handle = (sa_handle_impl_t)handle; - sa_share_impl_t impl_share, next; - sa_share_impl_t *pcurr; - - if (impl_handle == NULL) - return; - - /* - * clean up shares which don't have a non-NULL dataset property, - * which means they're in sharetab but we couldn't find their - * ZFS dataset. - */ - pcurr = &(impl_handle->shares); - impl_share = *pcurr; - while (impl_share != NULL) { - next = impl_share->next; - - if (impl_share->dataset == NULL) { - /* remove item from the linked list */ - *pcurr = next; - - sa_disable_share(impl_share, NULL); - - free_share(impl_share); - } else { - pcurr = &(impl_share->next); - } - - impl_share = next; - } - - update_sharetab(impl_handle); - - if (impl_handle->zfs_libhandle != NULL) - libzfs_fini(impl_handle->zfs_libhandle); - - impl_share = impl_handle->shares; - while (impl_share != NULL) { - next = impl_share->next; - free_share(impl_share); - impl_share = next; - } - - free(impl_handle); -} - -static sa_share_impl_t -find_share(sa_handle_impl_t impl_handle, const char *sharepath) -{ - sa_share_impl_t impl_share; - - impl_share = impl_handle->shares; - while (impl_share != NULL) { - if (strcmp(impl_share->sharepath, sharepath) == 0) { - break; - } - - impl_share = impl_share->next; - } - - return (impl_share); -} - -sa_share_t -sa_find_share(sa_handle_t handle, char *sharepath) -{ - return ((sa_share_t)find_share((sa_handle_impl_t)handle, sharepath)); + return (found_protocol ? ret : SA_INVALID_PROTOCOL); } int -sa_enable_share(sa_share_t share, char *protocol) +sa_disable_share(const char *mountpoint, char *protocol) { - sa_share_impl_t impl_share = (sa_share_impl_t)share; int rc, ret = SA_OK; boolean_t found_protocol = B_FALSE; sa_fstype_t *fstype; + sa_share_impl_t impl_share = alloc_share(NULL, mountpoint); + if (impl_share == NULL) + return (SA_NO_MEMORY); + fstype = fstypes; while (fstype != NULL) { - if (protocol == NULL || strcmp(fstype->name, protocol) == 0) { - update_zfs_share(impl_share, fstype->name); - - rc = fstype->ops->enable_share(impl_share); + if (strcmp(fstype->name, protocol) == 0) { + rc = fstype->ops->disable_share(impl_share); if (rc != SA_OK) ret = rc; - else - FSINFO(impl_share, fstype)->active = B_TRUE; found_protocol = B_TRUE; } fstype = fstype->next; } - - update_sharetab(impl_share->handle); + free_share(impl_share); return (found_protocol ? ret : SA_INVALID_PROTOCOL); } -int -sa_disable_share(sa_share_t share, char *protocol) +boolean_t +sa_is_shared(const char *mountpoint, char *protocol) { - sa_share_impl_t impl_share = (sa_share_impl_t)share; - int rc, ret = SA_OK; - boolean_t found_protocol = B_FALSE; sa_fstype_t *fstype; + boolean_t ret = B_FALSE; + + /* guid value is not used */ + sa_share_impl_t impl_share = alloc_share(NULL, mountpoint); + if (impl_share == NULL) + return (B_FALSE); fstype = fstypes; while (fstype != NULL) { - if (protocol == NULL || strcmp(fstype->name, protocol) == 0) { - rc = fstype->ops->disable_share(impl_share); - - if (rc == SA_OK) { - fstype->ops->clear_shareopts(impl_share); - - FSINFO(impl_share, fstype)->active = B_FALSE; - } else - ret = rc; - - found_protocol = B_TRUE; + if (strcmp(fstype->name, protocol) == 0) { + ret = fstype->ops->is_shared(impl_share); } - fstype = fstype->next; } + free_share(impl_share); + return (ret); +} - update_sharetab(impl_share->handle); - - return (found_protocol ? ret : SA_INVALID_PROTOCOL); +void +sa_commit_shares(const char *protocol) +{ + sa_fstype_t *fstype = fstypes; + while (fstype != NULL) { + if (strcmp(fstype->name, protocol) == 0) + fstype->ops->commit_shares(); + fstype = fstype->next; + } } /* @@ -674,7 +296,7 @@ sa_errorstr(int err) } int -sa_parse_legacy_options(sa_group_t group, char *options, char *proto) +sa_validate_shareopts(char *options, char *proto) { sa_fstype_t *fstype; @@ -691,25 +313,8 @@ sa_parse_legacy_options(sa_group_t group, char *options, char *proto) return (SA_INVALID_PROTOCOL); } -boolean_t -sa_needs_refresh(sa_handle_t handle) -{ - return (B_TRUE); -} - -libzfs_handle_t * -sa_get_zfs_handle(sa_handle_t handle) -{ - sa_handle_impl_t impl_handle = (sa_handle_impl_t)handle; - - if (impl_handle == NULL) - return (NULL); - - return (impl_handle->zfs_libhandle); -} - static sa_share_impl_t -alloc_share(const char *sharepath) +alloc_share(const char *zfsname, const char *mountpoint) { sa_share_impl_t impl_share; @@ -718,17 +323,24 @@ alloc_share(const char *sharepath) if (impl_share == NULL) return (NULL); - impl_share->sharepath = strdup(sharepath); - - if (impl_share->sharepath == NULL) { + if (mountpoint != NULL && + ((impl_share->sa_mountpoint = strdup(mountpoint)) == NULL)) { free(impl_share); return (NULL); } - impl_share->fsinfo = calloc(fstypes_count, sizeof (sa_share_fsinfo_t)); + if (zfsname != NULL && + ((impl_share->sa_zfsname = strdup(zfsname)) == NULL)) { + free(impl_share->sa_mountpoint); + free(impl_share); + return (NULL); + } - if (impl_share->fsinfo == NULL) { - free(impl_share->sharepath); + impl_share->sa_fsinfo = calloc(fstypes_count, + sizeof (sa_share_fsinfo_t)); + if (impl_share->sa_fsinfo == NULL) { + free(impl_share->sa_mountpoint); + free(impl_share->sa_zfsname); free(impl_share); return (NULL); } @@ -744,26 +356,11 @@ free_share(sa_share_impl_t impl_share) fstype = fstypes; while (fstype != NULL) { fstype->ops->clear_shareopts(impl_share); - - free(FSINFO(impl_share, fstype)->resource); - fstype = fstype->next; } - free(impl_share->sharepath); - free(impl_share->dataset); - free(impl_share->fsinfo); + free(impl_share->sa_mountpoint); + free(impl_share->sa_zfsname); + free(impl_share->sa_fsinfo); free(impl_share); } - -int -sa_zfs_process_share(sa_handle_t handle, sa_group_t group, sa_share_t share, - char *mountpoint, char *proto, zprop_source_t source, char *shareopts, - char *sourcestr, char *dataset) -{ - sa_handle_impl_t impl_handle = (sa_handle_impl_t)handle; - sa_share_impl_t impl_share = (sa_share_impl_t)share; - - return (process_share(impl_handle, impl_share, mountpoint, NULL, - proto, shareopts, NULL, dataset, B_FALSE)); -} diff --git a/lib/libshare/libshare_impl.h b/lib/libshare/libshare_impl.h index 18d619b10..cc5ef4008 100644 --- a/lib/libshare/libshare_impl.h +++ b/lib/libshare/libshare_impl.h @@ -22,36 +22,32 @@ /* * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2011 Gunnar Beutner + * Copyright (c) 2019, 2020 by Delphix. All rights reserved. */ -struct sa_handle_impl; - typedef struct sa_share_fsinfo { - boolean_t active; - char *resource; char *shareopts; } sa_share_fsinfo_t; typedef struct sa_share_impl { - struct sa_share_impl *next; - - struct sa_handle_impl *handle; + char *sa_mountpoint; + char *sa_zfsname; - char *sharepath; - char *dataset; - - sa_share_fsinfo_t *fsinfo; /* per-fstype information */ + sa_share_fsinfo_t *sa_fsinfo; /* per-fstype information */ } *sa_share_impl_t; -#define FSINFO(impl_share, fstype) (&(impl_share->fsinfo[fstype->fsinfo_index])) +#define FSINFO(impl_share, fstype) \ + (&(impl_share->sa_fsinfo[fstype->fsinfo_index])) typedef struct sa_share_ops { int (*enable_share)(sa_share_impl_t share); int (*disable_share)(sa_share_impl_t share); + boolean_t (*is_shared)(sa_share_impl_t share); int (*validate_shareopts)(const char *shareopts); int (*update_shareopts)(sa_share_impl_t impl_share, - const char *resource, const char *shareopts); + const char *shareopts); void (*clear_shareopts)(sa_share_impl_t impl_share); + int (*commit_shares)(void); } sa_share_ops_t; typedef struct sa_fstype { @@ -62,9 +58,4 @@ typedef struct sa_fstype { int fsinfo_index; } sa_fstype_t; -typedef struct sa_handle_impl { - libzfs_handle_t *zfs_libhandle; - sa_share_impl_t shares; -} *sa_handle_impl_t; - sa_fstype_t *register_fstype(const char *name, const sa_share_ops_t *ops); diff --git a/lib/libshare/os/freebsd/nfs.c b/lib/libshare/os/freebsd/nfs.c new file mode 100644 index 000000000..65f3b11bf --- /dev/null +++ b/lib/libshare/os/freebsd/nfs.c @@ -0,0 +1,448 @@ +/* + * Copyright (c) 2007 Pawel Jakub Dawidek <[email protected]> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Copyright (c) 2020 by Delphix. All rights reserved. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/vfs.h> + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <libutil.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <libintl.h> + +#include "libzfs_impl.h" +#include "libshare_impl.h" +#include "nfs.h" + +#define _PATH_MOUNTDPID "/var/run/mountd.pid" +#define FILE_HEADER "# !!! DO NOT EDIT THIS FILE MANUALLY !!!\n\n" +#define OPTSSIZE 1024 +#define MAXLINESIZE (PATH_MAX + OPTSSIZE) +#define ZFS_EXPORTS_FILE "/etc/zfs/exports" +#define ZFS_EXPORTS_LOCK ZFS_EXPORTS_FILE".lock" + +static sa_fstype_t *nfs_fstype; + +static int nfs_lock_fd = -1; + +/* + * The nfs_exports_[lock|unlock] is used to guard against conconcurrent + * updates to the exports file. Each protocol is responsible for + * providing the necessary locking to ensure consistency. + */ +static int +nfs_exports_lock(void) +{ + nfs_lock_fd = open(ZFS_EXPORTS_LOCK, + O_RDWR | O_CREAT, 0600); + if (nfs_lock_fd == -1) { + fprintf(stderr, "failed to lock %s: %s\n", + ZFS_EXPORTS_LOCK, strerror(errno)); + return (errno); + } + if (flock(nfs_lock_fd, LOCK_EX) != 0) { + fprintf(stderr, "failed to lock %s: %s\n", + ZFS_EXPORTS_LOCK, strerror(errno)); + return (errno); + } + return (0); +} + +static void +nfs_exports_unlock(void) +{ + verify(nfs_lock_fd > 0); + + if (flock(nfs_lock_fd, LOCK_UN) != 0) { + fprintf(stderr, "failed to unlock %s: %s\n", + ZFS_EXPORTS_LOCK, strerror(errno)); + } + close(nfs_lock_fd); + nfs_lock_fd = -1; +} + +/* + * Read one line from a file. Skip comments, empty lines and a line with a + * mountpoint specified in the 'skip' argument. + * + * NOTE: This function returns a static buffer and thus is not thread-safe. + */ +static char * +zgetline(FILE *fd, const char *skip) +{ + static char line[MAXLINESIZE]; + size_t len, skiplen = 0; + char *s, last; + + if (skip != NULL) + skiplen = strlen(skip); + for (;;) { + s = fgets(line, sizeof (line), fd); + if (s == NULL) + return (NULL); + /* Skip empty lines and comments. */ + if (line[0] == '\n' || line[0] == '#') + continue; + len = strlen(line); + if (line[len - 1] == '\n') + line[len - 1] = '\0'; + last = line[skiplen]; + /* Skip the given mountpoint. */ + if (skip != NULL && strncmp(skip, line, skiplen) == 0 && + (last == '\t' || last == ' ' || last == '\0')) { + continue; + } + break; + } + return (line); +} + +/* + * This function translate options to a format acceptable by exports(5), eg. + * + * -ro -network=192.168.0.0 -mask=255.255.255.0 -maproot=0 \ + * zfs.freebsd.org 69.147.83.54 + * + * Accepted input formats: + * + * ro,network=192.168.0.0,mask=255.255.255.0,maproot=0,zfs.freebsd.org + * ro network=192.168.0.0 mask=255.255.255.0 maproot=0 zfs.freebsd.org + * -ro,-network=192.168.0.0,-mask=255.255.255.0,-maproot=0,zfs.freebsd.org + * -ro -network=192.168.0.0 -mask=255.255.255.0 -maproot=0 \ + * zfs.freebsd.org + * + * Recognized keywords: + * + * ro, maproot, mapall, mask, network, sec, alldirs, public, webnfs, + * index, quiet + * + * NOTE: This function returns a static buffer and thus is not thread-safe. + */ +static char * +translate_opts(const char *shareopts) +{ + static const char *known_opts[] = { "ro", "maproot", "mapall", "mask", + "network", "sec", "alldirs", "public", "webnfs", "index", "quiet", + NULL }; + static char newopts[OPTSSIZE]; + char oldopts[OPTSSIZE]; + char *o, *s = NULL; + unsigned int i; + size_t len; + + strlcpy(oldopts, shareopts, sizeof (oldopts)); + newopts[0] = '\0'; + s = oldopts; + while ((o = strsep(&s, "-, ")) != NULL) { + if (o[0] == '\0') + continue; + for (i = 0; known_opts[i] != NULL; i++) { + len = strlen(known_opts[i]); + if (strncmp(known_opts[i], o, len) == 0 && + (o[len] == '\0' || o[len] == '=')) { + strlcat(newopts, "-", sizeof (newopts)); + break; + } + } + strlcat(newopts, o, sizeof (newopts)); + strlcat(newopts, " ", sizeof (newopts)); + } + return (newopts); +} + +static char * +nfs_init_tmpfile(void) +{ + char *tmpfile = NULL; + + if (asprintf(&tmpfile, "%s%s", ZFS_EXPORTS_FILE, ".XXXXXXXX") == -1) { + fprintf(stderr, "Unable to allocate buffer for temporary " + "file name\n"); + return (NULL); + } + + int fd = mkstemp(tmpfile); + if (fd == -1) { + fprintf(stderr, "Unable to create temporary file: %s", + strerror(errno)); + free(tmpfile); + return (NULL); + } + close(fd); + return (tmpfile); +} + +static int +nfs_fini_tmpfile(char *tmpfile) +{ + if (rename(tmpfile, ZFS_EXPORTS_FILE) == -1) { + fprintf(stderr, "Unable to rename %s: %s\n", tmpfile, + strerror(errno)); + unlink(tmpfile); + free(tmpfile); + return (SA_SYSTEM_ERR); + } + free(tmpfile); + return (SA_OK); +} + +/* + * This function copies all entries from the exports file to "filename", + * omitting any entries for the specified mountpoint. + */ +static int +nfs_copy_entries(char *filename, const char *mountpoint) +{ + int error = SA_OK; + char *line; + + /* + * If the file doesn't exist then there is nothing more + * we need to do. + */ + FILE *oldfp = fopen(ZFS_EXPORTS_FILE, "r"); + if (oldfp == NULL) + return (SA_OK); + + FILE *newfp = fopen(filename, "w+"); + fputs(FILE_HEADER, newfp); + while ((line = zgetline(oldfp, mountpoint)) != NULL) + fprintf(newfp, "%s\n", line); + if (ferror(oldfp) != 0) { + error = ferror(oldfp); + } + if (error == 0 && ferror(newfp) != 0) { + error = ferror(newfp); + } + + if (fclose(newfp) != 0) { + fprintf(stderr, "Unable to close file %s: %s\n", + filename, strerror(errno)); + error = error != 0 ? error : SA_SYSTEM_ERR; + } + fclose(oldfp); + + return (error); +} + +static int +nfs_enable_share(sa_share_impl_t impl_share) +{ + char *filename = NULL; + int error; + + if ((filename = nfs_init_tmpfile()) == NULL) + return (SA_SYSTEM_ERR); + + error = nfs_exports_lock(); + if (error != 0) { + unlink(filename); + free(filename); + return (error); + } + + error = nfs_copy_entries(filename, impl_share->sa_mountpoint); + if (error != SA_OK) { + unlink(filename); + free(filename); + nfs_exports_unlock(); + return (error); + } + + FILE *fp = fopen(filename, "a+"); + if (fp == NULL) { + fprintf(stderr, "failed to open %s file: %s", filename, + strerror(errno)); + unlink(filename); + free(filename); + nfs_exports_unlock(); + return (SA_SYSTEM_ERR); + } + char *shareopts = FSINFO(impl_share, nfs_fstype)->shareopts; + if (strcmp(shareopts, "on") == 0) + shareopts = ""; + + if (fprintf(fp, "%s\t%s\n", impl_share->sa_mountpoint, + translate_opts(shareopts)) < 0) { + fprintf(stderr, "failed to write to %s\n", filename); + fclose(fp); + unlink(filename); + free(filename); + nfs_exports_unlock(); + return (SA_SYSTEM_ERR); + } + + if (fclose(fp) != 0) { + fprintf(stderr, "Unable to close file %s: %s\n", + filename, strerror(errno)); + unlink(filename); + free(filename); + nfs_exports_unlock(); + return (SA_SYSTEM_ERR); + } + error = nfs_fini_tmpfile(filename); + nfs_exports_unlock(); + return (error); +} + +static int +nfs_disable_share(sa_share_impl_t impl_share) +{ + int error; + char *filename = NULL; + + if ((filename = nfs_init_tmpfile()) == NULL) + return (SA_SYSTEM_ERR); + + error = nfs_exports_lock(); + if (error != 0) { + unlink(filename); + free(filename); + return (error); + } + + error = nfs_copy_entries(filename, impl_share->sa_mountpoint); + if (error != SA_OK) { + unlink(filename); + free(filename); + nfs_exports_unlock(); + return (error); + } + + error = nfs_fini_tmpfile(filename); + nfs_exports_unlock(); + return (error); +} + +/* + * NOTE: This function returns a static buffer and thus is not thread-safe. + */ +static boolean_t +nfs_is_shared(sa_share_impl_t impl_share) +{ + static char line[MAXLINESIZE]; + char *s, last; + size_t len; + char *mntpoint = impl_share->sa_mountpoint; + size_t mntlen = strlen(mntpoint); + + FILE *fp = fopen(ZFS_EXPORTS_FILE, "r"); + if (fp == NULL) + return (B_FALSE); + + for (;;) { + s = fgets(line, sizeof (line), fp); + if (s == NULL) + return (B_FALSE); + /* Skip empty lines and comments. */ + if (line[0] == '\n' || line[0] == '#') + continue; + len = strlen(line); + if (line[len - 1] == '\n') + line[len - 1] = '\0'; + last = line[mntlen]; + /* Skip the given mountpoint. */ + if (strncmp(mntpoint, line, mntlen) == 0 && + (last == '\t' || last == ' ' || last == '\0')) { + fclose(fp); + return (B_TRUE); + } + } + fclose(fp); + return (B_FALSE); +} + +static int +nfs_validate_shareopts(const char *shareopts) +{ + return (SA_OK); +} + +static int +nfs_update_shareopts(sa_share_impl_t impl_share, const char *shareopts) +{ + FSINFO(impl_share, nfs_fstype)->shareopts = (char *)shareopts; + return (SA_OK); +} + +static void +nfs_clear_shareopts(sa_share_impl_t impl_share) +{ + FSINFO(impl_share, nfs_fstype)->shareopts = NULL; +} + +/* + * Commit the shares by restarting mountd. + */ +static int +nfs_commit_shares(void) +{ + struct pidfh *pfh; + pid_t mountdpid; + + pfh = pidfile_open(_PATH_MOUNTDPID, 0600, &mountdpid); + if (pfh != NULL) { + /* Mountd is not running. */ + pidfile_remove(pfh); + return (SA_OK); + } + if (errno != EEXIST) { + /* Cannot open pidfile for some reason. */ + return (SA_SYSTEM_ERR); + } + /* We have mountd(8) PID in mountdpid variable. */ + kill(mountdpid, SIGHUP); + return (SA_OK); +} + +static const sa_share_ops_t nfs_shareops = { + .enable_share = nfs_enable_share, + .disable_share = nfs_disable_share, + .is_shared = nfs_is_shared, + + .validate_shareopts = nfs_validate_shareopts, + .update_shareopts = nfs_update_shareopts, + .clear_shareopts = nfs_clear_shareopts, + .commit_shares = nfs_commit_shares, +}; + +/* + * Initializes the NFS functionality of libshare. + */ +void +libshare_nfs_init(void) +{ + nfs_fstype = register_fstype("nfs", &nfs_shareops); +} diff --git a/lib/libshare/os/freebsd/smb.c b/lib/libshare/os/freebsd/smb.c new file mode 100644 index 000000000..5b606ab96 --- /dev/null +++ b/lib/libshare/os/freebsd/smb.c @@ -0,0 +1,128 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright (c) 2020 by Delphix. All rights reserved. + */ + +#include <time.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <strings.h> +#include <fcntl.h> +#include <sys/wait.h> +#include <unistd.h> +#include <dirent.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <libzfs.h> +#include <libshare.h> +#include "libshare_impl.h" +#include "smb.h" + +static sa_fstype_t *smb_fstype; + +/* + * Enables SMB sharing for the specified share. + */ +static int +smb_enable_share(sa_share_impl_t impl_share) +{ + fprintf(stderr, "No SMB support in FreeBSD yet.\n"); + return (SA_NOT_SUPPORTED); +} +/* + * Disables SMB sharing for the specified share. + */ +static int +smb_disable_share(sa_share_impl_t impl_share) +{ + fprintf(stderr, "No SMB support in FreeBSD yet.\n"); + return (SA_NOT_SUPPORTED); +} + +/* + * Checks whether the specified SMB share options are syntactically correct. + */ +static int +smb_validate_shareopts(const char *shareopts) +{ + fprintf(stderr, "No SMB support in FreeBSD yet.\n"); + return (SA_NOT_SUPPORTED); +} + +/* + * Checks whether a share is currently active. + */ +static boolean_t +smb_is_share_active(sa_share_impl_t impl_share) +{ + return (B_FALSE); +} + +/* + * Called to update a share's options. A share's options might be out of + * date if the share was loaded from disk and the "sharesmb" dataset + * property has changed in the meantime. This function also takes care + * of re-enabling the share if necessary. + */ +static int +smb_update_shareopts(sa_share_impl_t impl_share, const char *shareopts) +{ + return (SA_OK); +} + +static int +smb_update_shares(void) +{ + /* Not implemented */ + return (0); +} +/* + * Clears a share's SMB options. Used by libshare to + * clean up shares that are about to be free()'d. + */ +static void +smb_clear_shareopts(sa_share_impl_t impl_share) +{ + FSINFO(impl_share, smb_fstype)->shareopts = NULL; +} + +static const sa_share_ops_t smb_shareops = { + .enable_share = smb_enable_share, + .disable_share = smb_disable_share, + .is_shared = smb_is_share_active, + + .validate_shareopts = smb_validate_shareopts, + .update_shareopts = smb_update_shareopts, + .clear_shareopts = smb_clear_shareopts, + .commit_shares = smb_update_shares, +}; + +/* + * Initializes the SMB functionality of libshare. + */ +void +libshare_smb_init(void) +{ + smb_fstype = register_fstype("smb", &smb_shareops); +} diff --git a/lib/libshare/nfs.c b/lib/libshare/os/linux/nfs.c index c45f25cd0..bc949213f 100644 --- a/lib/libshare/nfs.c +++ b/lib/libshare/os/linux/nfs.c @@ -23,13 +23,17 @@ * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2011 Gunnar Beutner * Copyright (c) 2012 Cyril Plisko. All rights reserved. + * Copyright (c) 2019, 2020 by Delphix. All rights reserved. */ +#include <dirent.h> #include <stdio.h> #include <string.h> #include <strings.h> -#include <fcntl.h> #include <errno.h> +#include <sys/file.h> +#include <sys/stat.h> +#include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <libzfs.h> @@ -37,21 +41,56 @@ #include "libshare_impl.h" #include "nfs.h" -static boolean_t nfs_available(void); +#define FILE_HEADER "# !!! DO NOT EDIT THIS FILE MANUALLY !!!\n\n" +#define ZFS_EXPORTS_DIR "/etc/exports.d" +#define ZFS_EXPORTS_FILE ZFS_EXPORTS_DIR"/zfs.exports" +#define ZFS_EXPORTS_LOCK ZFS_EXPORTS_FILE".lock" static sa_fstype_t *nfs_fstype; +typedef int (*nfs_shareopt_callback_t)(const char *opt, const char *value, + void *cookie); + +typedef int (*nfs_host_callback_t)(const char *sharepath, const char *filename, + const char *host, const char *security, const char *access, void *cookie); + +static int nfs_lock_fd = -1; + /* - * nfs_exportfs_temp_fd refers to a temporary copy of the output - * from exportfs -v. + * The nfs_exports_[lock|unlock] is used to guard against conconcurrent + * updates to the exports file. Each protocol is responsible for + * providing the necessary locking to ensure consistency. */ -static int nfs_exportfs_temp_fd = -1; +static int +nfs_exports_lock(void) +{ + nfs_lock_fd = open(ZFS_EXPORTS_LOCK, + O_RDWR | O_CREAT, 0600); + if (nfs_lock_fd == -1) { + fprintf(stderr, "failed to lock %s: %s\n", + ZFS_EXPORTS_LOCK, strerror(errno)); + return (errno); + } + if (flock(nfs_lock_fd, LOCK_EX) != 0) { + fprintf(stderr, "failed to lock %s: %s\n", + ZFS_EXPORTS_LOCK, strerror(errno)); + return (errno); + } + return (0); +} -typedef int (*nfs_shareopt_callback_t)(const char *opt, const char *value, - void *cookie); +static void +nfs_exports_unlock(void) +{ + verify(nfs_lock_fd > 0); -typedef int (*nfs_host_callback_t)(const char *sharepath, const char *host, - const char *security, const char *access, void *cookie); + if (flock(nfs_lock_fd, LOCK_UN) != 0) { + fprintf(stderr, "failed to unlock %s: %s\n", + ZFS_EXPORTS_LOCK, strerror(errno)); + } + close(nfs_lock_fd); + nfs_lock_fd = -1; +} /* * Invokes the specified callback function for each Solaris share option @@ -62,13 +101,17 @@ foreach_nfs_shareopt(const char *shareopts, nfs_shareopt_callback_t callback, void *cookie) { char *shareopts_dup, *opt, *cur, *value; - int was_nul, rc; + int was_nul, error; if (shareopts == NULL) return (SA_OK); + if (strcmp(shareopts, "on") == 0) + shareopts = "rw,crossmnt"; + shareopts_dup = strdup(shareopts); + if (shareopts_dup == NULL) return (SA_NO_MEMORY); @@ -94,11 +137,11 @@ foreach_nfs_shareopt(const char *shareopts, value++; } - rc = callback(opt, value, cookie); + error = callback(opt, value, cookie); - if (rc != SA_OK) { + if (error != SA_OK) { free(shareopts_dup); - return (rc); + return (error); } } @@ -110,13 +153,14 @@ foreach_nfs_shareopt(const char *shareopts, free(shareopts_dup); - return (0); + return (SA_OK); } typedef struct nfs_host_cookie_s { nfs_host_callback_t callback; const char *sharepath; void *cookie; + const char *filename; const char *security; } nfs_host_cookie_t; @@ -128,7 +172,7 @@ typedef struct nfs_host_cookie_s { static int foreach_nfs_host_cb(const char *opt, const char *value, void *pcookie) { - int rc; + int error; const char *access; char *host_dup, *host, *next; nfs_host_cookie_t *udata = (nfs_host_cookie_t *)pcookie; @@ -160,13 +204,14 @@ foreach_nfs_host_cb(const char *opt, const char *value, void *pcookie) next++; } - rc = udata->callback(udata->sharepath, host, - udata->security, access, udata->cookie); + error = udata->callback(udata->filename, + udata->sharepath, host, udata->security, + access, udata->cookie); - if (rc != SA_OK) { + if (error != SA_OK) { free(host_dup); - return (rc); + return (error); } host = next; @@ -182,21 +227,22 @@ foreach_nfs_host_cb(const char *opt, const char *value, void *pcookie) * Invokes a callback function for all NFS hosts that are set for a share. */ static int -foreach_nfs_host(sa_share_impl_t impl_share, nfs_host_callback_t callback, - void *cookie) +foreach_nfs_host(sa_share_impl_t impl_share, char *filename, + nfs_host_callback_t callback, void *cookie) { nfs_host_cookie_t udata; char *shareopts; udata.callback = callback; - udata.sharepath = impl_share->sharepath; + udata.sharepath = impl_share->sa_mountpoint; udata.cookie = cookie; + udata.filename = filename; udata.security = "sys"; shareopts = FSINFO(impl_share, nfs_fstype)->shareopts; - return foreach_nfs_shareopt(shareopts, foreach_nfs_host_cb, - &udata); + return (foreach_nfs_shareopt(shareopts, foreach_nfs_host_cb, + &udata)); } /* @@ -227,69 +273,6 @@ get_linux_hostspec(const char *solaris_hostspec, char **plinux_hostspec) } /* - * Used internally by nfs_enable_share to enable sharing for a single host. - */ -static int -nfs_enable_share_one(const char *sharepath, const char *host, - const char *security, const char *access, void *pcookie) -{ - int rc; - char *linuxhost, *hostpath, *opts; - const char *linux_opts = (const char *)pcookie; - char *argv[6]; - - /* exportfs -i -o sec=XX,rX,<opts> <host>:<sharepath> */ - - rc = get_linux_hostspec(host, &linuxhost); - - if (rc < 0) - exit(1); - - hostpath = malloc(strlen(linuxhost) + 1 + strlen(sharepath) + 1); - - if (hostpath == NULL) { - free(linuxhost); - - exit(1); - } - - sprintf(hostpath, "%s:%s", linuxhost, sharepath); - - free(linuxhost); - - if (linux_opts == NULL) - linux_opts = ""; - - opts = malloc(4 + strlen(security) + 4 + strlen(linux_opts) + 1); - - if (opts == NULL) - exit(1); - - sprintf(opts, "sec=%s,%s,%s", security, access, linux_opts); - -#ifdef DEBUG - fprintf(stderr, "sharing %s with opts %s\n", hostpath, opts); -#endif - - argv[0] = "/usr/sbin/exportfs"; - argv[1] = "-i"; - argv[2] = "-o"; - argv[3] = opts; - argv[4] = hostpath; - argv[5] = NULL; - - rc = libzfs_run_process(argv[0], argv, 0); - - free(hostpath); - free(opts); - - if (rc < 0) - return (SA_SYSTEM_ERR); - else - return (SA_OK); -} - -/* * Adds a Linux share option to an array of NFS options. */ static int @@ -382,7 +365,7 @@ get_linux_shareopts_cb(const char *key, const char *value, void *cookie) static int get_linux_shareopts(const char *shareopts, char **plinux_opts) { - int rc; + int error; assert(plinux_opts != NULL); @@ -394,237 +377,287 @@ get_linux_shareopts(const char *shareopts, char **plinux_opts) /* mountpoint - Restrict exports to ZFS mountpoints */ (void) add_linux_shareopt(plinux_opts, "mountpoint", NULL); - rc = foreach_nfs_shareopt(shareopts, get_linux_shareopts_cb, + error = foreach_nfs_shareopt(shareopts, get_linux_shareopts_cb, plinux_opts); - if (rc != SA_OK) { + if (error != SA_OK) { free(*plinux_opts); *plinux_opts = NULL; } - return (rc); + return (error); } -/* - * Enables NFS sharing for the specified share. - */ -static int -nfs_enable_share(sa_share_impl_t impl_share) +static char * +nfs_init_tmpfile(void) { - char *shareopts, *linux_opts; - int rc; + char *tmpfile = NULL; - if (!nfs_available()) { - return (SA_SYSTEM_ERR); + if (asprintf(&tmpfile, "%s%s", ZFS_EXPORTS_FILE, ".XXXXXXXX") == -1) { + fprintf(stderr, "Unable to allocate temporary file\n"); + return (NULL); } - shareopts = FSINFO(impl_share, nfs_fstype)->shareopts; - - if (shareopts == NULL) - return (SA_OK); - - rc = get_linux_shareopts(shareopts, &linux_opts); - - if (rc != SA_OK) - return (rc); - - rc = foreach_nfs_host(impl_share, nfs_enable_share_one, linux_opts); - - free(linux_opts); + int fd = mkstemp(tmpfile); + if (fd == -1) { + fprintf(stderr, "Unable to create temporary file: %s", + strerror(errno)); + free(tmpfile); + return (NULL); + } + close(fd); + return (tmpfile); +} - return (rc); +static int +nfs_fini_tmpfile(char *tmpfile) +{ + if (rename(tmpfile, ZFS_EXPORTS_FILE) == -1) { + fprintf(stderr, "Unable to rename %s: %s\n", tmpfile, + strerror(errno)); + unlink(tmpfile); + free(tmpfile); + return (SA_SYSTEM_ERR); + } + free(tmpfile); + return (SA_OK); } /* - * Used internally by nfs_disable_share to disable sharing for a single host. + * This function populates an entry into /etc/exports.d/zfs.exports. + * This file is consumed by the linux nfs server so that zfs shares are + * automatically exported upon boot or whenever the nfs server restarts. */ static int -nfs_disable_share_one(const char *sharepath, const char *host, - const char *security, const char *access, void *cookie) +nfs_add_entry(const char *filename, const char *sharepath, + const char *host, const char *security, const char *access_opts, + void *pcookie) { - int rc; - char *linuxhost, *hostpath; - char *argv[4]; - - rc = get_linux_hostspec(host, &linuxhost); + int error; + char *linuxhost; + const char *linux_opts = (const char *)pcookie; - if (rc < 0) - exit(1); + error = get_linux_hostspec(host, &linuxhost); + if (error != SA_OK) + return (error); - hostpath = malloc(strlen(linuxhost) + 1 + strlen(sharepath) + 1); + if (linux_opts == NULL) + linux_opts = ""; - if (hostpath == NULL) { + FILE *fp = fopen(filename, "a+"); + if (fp == NULL) { + fprintf(stderr, "failed to open %s file: %s", filename, + strerror(errno)); free(linuxhost); - exit(1); + return (SA_SYSTEM_ERR); } - sprintf(hostpath, "%s:%s", linuxhost, sharepath); + if (fprintf(fp, "%s %s(sec=%s,%s,%s)\n", sharepath, linuxhost, + security, access_opts, linux_opts) < 0) { + fprintf(stderr, "failed to write to %s\n", filename); + free(linuxhost); + fclose(fp); + return (SA_SYSTEM_ERR); + } free(linuxhost); - -#ifdef DEBUG - fprintf(stderr, "unsharing %s\n", hostpath); -#endif - - argv[0] = "/usr/sbin/exportfs"; - argv[1] = "-u"; - argv[2] = hostpath; - argv[3] = NULL; - - rc = libzfs_run_process(argv[0], argv, 0); - - free(hostpath); - - if (rc < 0) + if (fclose(fp) != 0) { + fprintf(stderr, "Unable to close file %s: %s\n", + filename, strerror(errno)); return (SA_SYSTEM_ERR); - else - return (SA_OK); + } + return (SA_OK); } /* - * Disables NFS sharing for the specified share. + * This function copies all entries from the exports file to "filename", + * omitting any entries for the specified mountpoint. */ static int -nfs_disable_share(sa_share_impl_t impl_share) +nfs_copy_entries(char *filename, const char *mountpoint) { - if (!nfs_available()) { - /* - * The share can't possibly be active, so nothing - * needs to be done to disable it. - */ + char *buf = NULL; + size_t buflen = 0; + int error = SA_OK; + + /* + * If the file doesn't exist then there is nothing more + * we need to do. + */ + FILE *oldfp = fopen(ZFS_EXPORTS_FILE, "r"); + if (oldfp == NULL) return (SA_OK); + + FILE *newfp = fopen(filename, "w+"); + fputs(FILE_HEADER, newfp); + while ((getline(&buf, &buflen, oldfp)) != -1) { + char *space = NULL; + + if (buf[0] == '\n' || buf[0] == '#') + continue; + + if ((space = strchr(buf, ' ')) != NULL) { + int mountpoint_len = strlen(mountpoint); + + if (space - buf == mountpoint_len && + strncmp(mountpoint, buf, mountpoint_len) == 0) { + continue; + } + } + fputs(buf, newfp); + } + + if (oldfp != NULL && ferror(oldfp) != 0) { + error = ferror(oldfp); + } + if (error == 0 && ferror(newfp) != 0) { + error = ferror(newfp); } - return (foreach_nfs_host(impl_share, nfs_disable_share_one, NULL)); + free(buf); + if (fclose(newfp) != 0) { + fprintf(stderr, "Unable to close file %s: %s\n", + filename, strerror(errno)); + error = error != 0 ? error : SA_SYSTEM_ERR; + } + fclose(oldfp); + + return (error); } /* - * Checks whether the specified NFS share options are syntactically correct. + * Enables NFS sharing for the specified share. */ static int -nfs_validate_shareopts(const char *shareopts) +nfs_enable_share(sa_share_impl_t impl_share) { - char *linux_opts; - int rc; + char *shareopts, *linux_opts; + char *filename = NULL; + int error; - rc = get_linux_shareopts(shareopts, &linux_opts); + if ((filename = nfs_init_tmpfile()) == NULL) + return (SA_SYSTEM_ERR); - if (rc != SA_OK) - return (rc); + error = nfs_exports_lock(); + if (error != 0) { + unlink(filename); + free(filename); + return (error); + } - free(linux_opts); + error = nfs_copy_entries(filename, impl_share->sa_mountpoint); + if (error != SA_OK) { + unlink(filename); + free(filename); + nfs_exports_unlock(); + return (error); + } - return (SA_OK); + shareopts = FSINFO(impl_share, nfs_fstype)->shareopts; + error = get_linux_shareopts(shareopts, &linux_opts); + if (error != SA_OK) { + unlink(filename); + free(filename); + nfs_exports_unlock(); + return (error); + } + + error = foreach_nfs_host(impl_share, filename, nfs_add_entry, + linux_opts); + free(linux_opts); + if (error == 0) { + error = nfs_fini_tmpfile(filename); + } else { + unlink(filename); + free(filename); + } + nfs_exports_unlock(); + return (error); } /* - * Checks whether a share is currently active. + * Disables NFS sharing for the specified share. */ -static boolean_t -nfs_is_share_active(sa_share_impl_t impl_share) +static int +nfs_disable_share(sa_share_impl_t impl_share) { - int fd; - char line[512]; - char *tab, *cur; - FILE *nfs_exportfs_temp_fp; + int error; + char *filename = NULL; - if (!nfs_available()) - return (B_FALSE); + if ((filename = nfs_init_tmpfile()) == NULL) + return (SA_SYSTEM_ERR); - if ((fd = dup(nfs_exportfs_temp_fd)) == -1) - return (B_FALSE); + error = nfs_exports_lock(); + if (error != 0) { + unlink(filename); + free(filename); + return (error); + } - nfs_exportfs_temp_fp = fdopen(fd, "r"); + error = nfs_copy_entries(filename, impl_share->sa_mountpoint); + if (error != SA_OK) { + unlink(filename); + free(filename); + nfs_exports_unlock(); + return (error); + } + error = nfs_fini_tmpfile(filename); + nfs_exports_unlock(); + return (error); +} - if (nfs_exportfs_temp_fp == NULL) - return (B_FALSE); +static boolean_t +nfs_is_shared(sa_share_impl_t impl_share) +{ + size_t buflen = 0; + char *buf = NULL; - if (fseek(nfs_exportfs_temp_fp, 0, SEEK_SET) < 0) { - fclose(nfs_exportfs_temp_fp); + FILE *fp = fopen(ZFS_EXPORTS_FILE, "r"); + if (fp == NULL) { return (B_FALSE); } - - while (fgets(line, sizeof (line), nfs_exportfs_temp_fp) != NULL) { - /* - * exportfs uses separate lines for the share path - * and the export options when the share path is longer - * than a certain amount of characters; this ignores - * the option lines - */ - if (line[0] == '\t') - continue; - - tab = strchr(line, '\t'); - - if (tab != NULL) { - *tab = '\0'; - cur = tab - 1; - } else { - /* - * there's no tab character, which means the - * NFS options are on a separate line; we just - * need to remove the new-line character - * at the end of the line - */ - cur = line + strlen(line) - 1; - } - - /* remove trailing spaces and new-line characters */ - while (cur >= line && (*cur == ' ' || *cur == '\n')) - *cur-- = '\0'; - - if (strcmp(line, impl_share->sharepath) == 0) { - fclose(nfs_exportfs_temp_fp); - return (B_TRUE); + while ((getline(&buf, &buflen, fp)) != -1) { + char *space = NULL; + + if ((space = strchr(buf, ' ')) != NULL) { + int mountpoint_len = strlen(impl_share->sa_mountpoint); + + if (space - buf == mountpoint_len && + strncmp(impl_share->sa_mountpoint, buf, + mountpoint_len) == 0) { + fclose(fp); + free(buf); + return (B_TRUE); + } } } - - fclose(nfs_exportfs_temp_fp); - + free(buf); + fclose(fp); return (B_FALSE); } /* - * Called to update a share's options. A share's options might be out of - * date if the share was loaded from disk (i.e. /etc/dfs/sharetab) and the - * "sharenfs" dataset property has changed in the meantime. This function - * also takes care of re-enabling the share if necessary. + * Checks whether the specified NFS share options are syntactically correct. */ static int -nfs_update_shareopts(sa_share_impl_t impl_share, const char *resource, - const char *shareopts) +nfs_validate_shareopts(const char *shareopts) { - char *shareopts_dup; - boolean_t needs_reshare = B_FALSE; - char *old_shareopts; - - FSINFO(impl_share, nfs_fstype)->active = - nfs_is_share_active(impl_share); - - old_shareopts = FSINFO(impl_share, nfs_fstype)->shareopts; - - if (strcmp(shareopts, "on") == 0) - shareopts = "rw,crossmnt"; - - if (FSINFO(impl_share, nfs_fstype)->active && old_shareopts != NULL && - strcmp(old_shareopts, shareopts) != 0) { - needs_reshare = B_TRUE; - nfs_disable_share(impl_share); - } - - shareopts_dup = strdup(shareopts); - - if (shareopts_dup == NULL) - return (SA_NO_MEMORY); + char *linux_opts; + int error; - if (old_shareopts != NULL) - free(old_shareopts); + error = get_linux_shareopts(shareopts, &linux_opts); - FSINFO(impl_share, nfs_fstype)->shareopts = shareopts_dup; + if (error != SA_OK) + return (error); - if (needs_reshare) - nfs_enable_share(impl_share); + free(linux_opts); + return (SA_OK); +} +static int +nfs_update_shareopts(sa_share_impl_t impl_share, const char *shareopts) +{ + FSINFO(impl_share, nfs_fstype)->shareopts = (char *)shareopts; return (SA_OK); } @@ -635,114 +668,45 @@ nfs_update_shareopts(sa_share_impl_t impl_share, const char *resource, static void nfs_clear_shareopts(sa_share_impl_t impl_share) { - free(FSINFO(impl_share, nfs_fstype)->shareopts); FSINFO(impl_share, nfs_fstype)->shareopts = NULL; } +static int +nfs_commit_shares(void) +{ + char *argv[] = { + "/usr/sbin/exportfs", + "-ra", + NULL + }; + + return (libzfs_run_process(argv[0], argv, 0)); +} + static const sa_share_ops_t nfs_shareops = { .enable_share = nfs_enable_share, .disable_share = nfs_disable_share, + .is_shared = nfs_is_shared, .validate_shareopts = nfs_validate_shareopts, .update_shareopts = nfs_update_shareopts, .clear_shareopts = nfs_clear_shareopts, + .commit_shares = nfs_commit_shares, }; /* - * nfs_check_exportfs() checks that the exportfs command runs - * and also maintains a temporary copy of the output from - * exportfs -v. - * To update this temporary copy simply call this function again. - * - * TODO : Use /var/lib/nfs/etab instead of our private copy. - * But must implement locking to prevent concurrent access. - * - * TODO : The temporary file descriptor is never closed since - * there is no libshare_nfs_fini() function. - */ -static int -nfs_check_exportfs(void) -{ - pid_t pid; - int rc, status; - static char nfs_exportfs_tempfile[] = "/tmp/exportfs.XXXXXX"; - - /* - * Close any existing temporary copies of output from exportfs. - * We have already called unlink() so file will be deleted. - */ - if (nfs_exportfs_temp_fd >= 0) - close(nfs_exportfs_temp_fd); - - nfs_exportfs_temp_fd = mkstemp(nfs_exportfs_tempfile); - - if (nfs_exportfs_temp_fd < 0) - return (SA_SYSTEM_ERR); - - unlink(nfs_exportfs_tempfile); - - (void) fcntl(nfs_exportfs_temp_fd, F_SETFD, FD_CLOEXEC); - - pid = fork(); - - if (pid < 0) { - (void) close(nfs_exportfs_temp_fd); - nfs_exportfs_temp_fd = -1; - return (SA_SYSTEM_ERR); - } - - if (pid > 0) { - while ((rc = waitpid(pid, &status, 0)) <= 0 && - errno == EINTR) { } - - if (rc <= 0) { - (void) close(nfs_exportfs_temp_fd); - nfs_exportfs_temp_fd = -1; - return (SA_SYSTEM_ERR); - } - - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - (void) close(nfs_exportfs_temp_fd); - nfs_exportfs_temp_fd = -1; - return (SA_CONFIG_ERR); - } - - return (SA_OK); - } - - /* child */ - - /* exportfs -v */ - - if (dup2(nfs_exportfs_temp_fd, STDOUT_FILENO) < 0) - exit(1); - - rc = execlp("/usr/sbin/exportfs", "exportfs", "-v", NULL); - - if (rc < 0) { - exit(1); - } - - exit(0); -} - -/* - * Provides a convenient wrapper for determining nfs availability - */ -static boolean_t -nfs_available(void) -{ - if (nfs_exportfs_temp_fd == -1) - (void) nfs_check_exportfs(); - - return ((nfs_exportfs_temp_fd != -1) ? B_TRUE : B_FALSE); -} - -/* * Initializes the NFS functionality of libshare. */ void libshare_nfs_init(void) { + struct stat sb; + nfs_fstype = register_fstype("nfs", &nfs_shareops); + + if (stat(ZFS_EXPORTS_DIR, &sb) < 0 && + mkdir(ZFS_EXPORTS_DIR, 0755) < 0) { + fprintf(stderr, "failed to create %s: %s\n", + ZFS_EXPORTS_DIR, strerror(errno)); + } } diff --git a/lib/libshare/smb.c b/lib/libshare/os/linux/smb.c index f567f7c49..3dcf666eb 100644 --- a/lib/libshare/smb.c +++ b/lib/libshare/os/linux/smb.c @@ -23,6 +23,7 @@ * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2011,2012 Turbo Fredriksson <[email protected]>, based on nfs.c * by Gunnar Beutner + * Copyright (c) 2019, 2020 by Delphix. All rights reserved. * * This is an addition to the zfs device driver to add, modify and remove SMB * shares using the 'net share' command that comes with Samba. @@ -66,6 +67,8 @@ static boolean_t smb_available(void); static sa_fstype_t *smb_fstype; smb_share_t *smb_shares; +static int smb_disable_share(sa_share_impl_t impl_share); +static boolean_t smb_is_share_active(sa_share_impl_t impl_share); /* * Retrieve the list of SMB shares. @@ -275,6 +278,9 @@ smb_enable_share(sa_share_impl_t impl_share) if (!smb_available()) return (SA_SYSTEM_ERR); + if (smb_is_share_active(impl_share)) + smb_disable_share(impl_share); + shareopts = FSINFO(impl_share, smb_fstype)->shareopts; if (shareopts == NULL) /* on/off */ return (SA_SYSTEM_ERR); @@ -283,8 +289,8 @@ smb_enable_share(sa_share_impl_t impl_share) return (SA_OK); /* Magic: Enable (i.e., 'create new') share */ - return (smb_enable_share_one(impl_share->dataset, - impl_share->sharepath)); + return (smb_enable_share_one(impl_share->sa_zfsname, + impl_share->sa_mountpoint)); } /* @@ -329,7 +335,7 @@ smb_disable_share(sa_share_impl_t impl_share) } while (shares != NULL) { - if (strcmp(impl_share->sharepath, shares->path) == 0) + if (strcmp(impl_share->sa_mountpoint, shares->path) == 0) return (smb_disable_share_one(shares->name)); shares = shares->next; @@ -366,7 +372,7 @@ smb_is_share_active(sa_share_impl_t impl_share) smb_retrieve_shares(); while (iter != NULL) { - if (strcmp(impl_share->sharepath, iter->path) == 0) + if (strcmp(impl_share->sa_mountpoint, iter->path) == 0) return (B_TRUE); iter = iter->next; @@ -382,43 +388,22 @@ smb_is_share_active(sa_share_impl_t impl_share) * of re-enabling the share if necessary. */ static int -smb_update_shareopts(sa_share_impl_t impl_share, const char *resource, - const char *shareopts) +smb_update_shareopts(sa_share_impl_t impl_share, const char *shareopts) { - char *shareopts_dup; - boolean_t needs_reshare = B_FALSE; - char *old_shareopts; - if (!impl_share) return (SA_SYSTEM_ERR); - FSINFO(impl_share, smb_fstype)->active = - smb_is_share_active(impl_share); - - old_shareopts = FSINFO(impl_share, smb_fstype)->shareopts; - - if (FSINFO(impl_share, smb_fstype)->active && old_shareopts != NULL && - strcmp(old_shareopts, shareopts) != 0) { - needs_reshare = B_TRUE; - smb_disable_share(impl_share); - } - - shareopts_dup = strdup(shareopts); - - if (shareopts_dup == NULL) - return (SA_NO_MEMORY); - - if (old_shareopts != NULL) - free(old_shareopts); - - FSINFO(impl_share, smb_fstype)->shareopts = shareopts_dup; - - if (needs_reshare) - smb_enable_share(impl_share); - + FSINFO(impl_share, smb_fstype)->shareopts = (char *)shareopts; return (SA_OK); } +static int +smb_update_shares(void) +{ + /* Not implemented */ + return (0); +} + /* * Clears a share's SMB options. Used by libshare to * clean up shares that are about to be free()'d. @@ -426,17 +411,18 @@ smb_update_shareopts(sa_share_impl_t impl_share, const char *resource, static void smb_clear_shareopts(sa_share_impl_t impl_share) { - free(FSINFO(impl_share, smb_fstype)->shareopts); FSINFO(impl_share, smb_fstype)->shareopts = NULL; } static const sa_share_ops_t smb_shareops = { .enable_share = smb_enable_share, .disable_share = smb_disable_share, + .is_shared = smb_is_share_active, .validate_shareopts = smb_validate_shareopts, .update_shareopts = smb_update_shareopts, .clear_shareopts = smb_clear_shareopts, + .commit_shares = smb_update_shares, }; /* diff --git a/lib/libspl/include/libshare.h b/lib/libspl/include/libshare.h index 4016ff031..ea53f8c15 100644 --- a/lib/libspl/include/libshare.h +++ b/lib/libspl/include/libshare.h @@ -22,14 +22,11 @@ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. + * Copyright (c) 2019, 2020 by Delphix. All rights reserved. */ #ifndef _LIBSPL_LIBSHARE_H #define _LIBSPL_LIBSHARE_H -typedef void *sa_handle_t; /* opaque handle to access core functions */ -typedef void *sa_group_t; -typedef void *sa_share_t; - /* API Initialization */ #define SA_INIT_SHARE_API 0x0001 /* init share specific interface */ #define SA_INIT_CONTROL_API 0x0002 /* init control specific interface */ @@ -74,23 +71,16 @@ typedef void *sa_share_t; #define SA_SHARE_EXISTS 33 /* path or file is already shared */ /* initialization */ -extern sa_handle_t sa_init(int); -extern void sa_fini(sa_handle_t); extern char *sa_errorstr(int); /* share control */ -extern sa_share_t sa_find_share(sa_handle_t, char *); -extern int sa_enable_share(sa_group_t, char *); -extern int sa_disable_share(sa_share_t, char *); +extern int sa_enable_share(const char *, const char *, const char *, + char *); +extern int sa_disable_share(const char *, char *); +extern boolean_t sa_is_shared(const char *, char *); +extern void sa_commit_shares(const char *); /* protocol specific interfaces */ -extern int sa_parse_legacy_options(sa_group_t, char *, char *); - -/* ZFS functions */ -extern boolean_t sa_needs_refresh(sa_handle_t handle); -libzfs_handle_t *sa_get_zfs_handle(sa_handle_t handle); -extern int sa_zfs_process_share(sa_handle_t handle, sa_group_t group, - sa_share_t share, char *mountpoint, char *proto, zprop_source_t source, - char *shareopts, char *sourcestr, char *dataset); +extern int sa_validate_shareopts(char *, char *); #endif /* _LIBSPL_LIBSHARE_H */ diff --git a/lib/libzfs/Makefile.am b/lib/libzfs/Makefile.am index 1f7e3c5ee..4154e9fee 100644 --- a/lib/libzfs/Makefile.am +++ b/lib/libzfs/Makefile.am @@ -30,7 +30,6 @@ USER_C = \ if BUILD_FREEBSD USER_C += \ - os/freebsd/libzfs_fsshare.c \ os/freebsd/libzfs_compat.c \ os/freebsd/libzfs_ioctl_compat.c \ os/freebsd/libzfs_zmount.c @@ -69,14 +68,8 @@ dist_libzfs_la_SOURCES = \ nodist_libzfs_la_SOURCES = \ $(KERNEL_C) -libzfs_la_LIBADD = - -if BUILD_LINUX -libzfs_la_LIBADD += \ - $(abs_top_builddir)/lib/libshare/libshare.la -endif - -libzfs_la_LIBADD += \ +libzfs_la_LIBADD = \ + $(abs_top_builddir)/lib/libshare/libshare.la \ $(abs_top_builddir)/lib/libzfs_core/libzfs_core.la \ $(abs_top_builddir)/lib/libnvpair/libnvpair.la \ $(abs_top_builddir)/lib/libuutil/libuutil.la diff --git a/lib/libzfs/libzfs_changelist.c b/lib/libzfs/libzfs_changelist.c index 72f641056..fec2fd5f2 100644 --- a/lib/libzfs/libzfs_changelist.c +++ b/lib/libzfs/libzfs_changelist.c @@ -24,7 +24,7 @@ * Use is subject to license terms. * * Portions Copyright 2007 Ramprakash Jelari - * Copyright (c) 2014, 2015 by Delphix. All rights reserved. + * Copyright (c) 2014, 2020 by Delphix. All rights reserved. * Copyright 2016 Igor Kozhukhov <[email protected]> * Copyright (c) 2018 Datto Inc. */ @@ -98,6 +98,7 @@ changelist_prefix(prop_changelist_t *clp) prop_changenode_t *cn; uu_avl_walk_t *walk; int ret = 0; + boolean_t commit_smb_shares = B_FALSE; if (clp->cl_prop != ZFS_PROP_MOUNTPOINT && clp->cl_prop != ZFS_PROP_SHARESMB) @@ -135,6 +136,7 @@ changelist_prefix(prop_changelist_t *clp) break; case ZFS_PROP_SHARESMB: (void) zfs_unshare_smb(cn->cn_handle, NULL); + commit_smb_shares = B_TRUE; break; default: @@ -143,6 +145,8 @@ changelist_prefix(prop_changelist_t *clp) } } + if (commit_smb_shares) + zfs_commit_smb_shares(); uu_avl_walk_end(walk); if (ret == -1) @@ -167,7 +171,8 @@ changelist_postfix(prop_changelist_t *clp) uu_avl_walk_t *walk; char shareopts[ZFS_MAXPROPLEN]; int errors = 0; - libzfs_handle_t *hdl; + boolean_t commit_smb_shares = B_FALSE; + boolean_t commit_nfs_shares = B_FALSE; /* * If we're changing the mountpoint, attempt to destroy the underlying @@ -183,18 +188,6 @@ changelist_postfix(prop_changelist_t *clp) remove_mountpoint(cn->cn_handle); /* - * It is possible that the changelist_prefix() used libshare - * to unshare some entries. Since libshare caches data, an - * attempt to reshare during postfix can fail unless libshare - * is uninitialized here so that it will reinitialize later. - */ - if (cn->cn_handle != NULL) { - hdl = cn->cn_handle->zfs_hdl; - assert(hdl != NULL); - zfs_uninit_libshare(hdl); - } - - /* * We walk the datasets in reverse, because we want to mount any parent * datasets before mounting the children. We walk all datasets even if * there are errors. @@ -260,16 +253,25 @@ changelist_postfix(prop_changelist_t *clp) * if the filesystem is currently shared, so that we can * adopt any new options. */ - if (sharenfs && mounted) + if (sharenfs && mounted) { errors += zfs_share_nfs(cn->cn_handle); - else if (cn->cn_shared || clp->cl_waslegacy) + commit_nfs_shares = B_TRUE; + } else if (cn->cn_shared || clp->cl_waslegacy) { errors += zfs_unshare_nfs(cn->cn_handle, NULL); - if (sharesmb && mounted) + commit_nfs_shares = B_TRUE; + } + if (sharesmb && mounted) { errors += zfs_share_smb(cn->cn_handle); - else if (cn->cn_shared || clp->cl_waslegacy) + commit_smb_shares = B_TRUE; + } else if (cn->cn_shared || clp->cl_waslegacy) { errors += zfs_unshare_smb(cn->cn_handle, NULL); + commit_smb_shares = B_TRUE; + } } - + if (commit_nfs_shares) + zfs_commit_nfs_shares(); + if (commit_smb_shares) + zfs_commit_smb_shares(); uu_avl_walk_end(walk); return (errors ? -1 : 0); @@ -357,6 +359,7 @@ changelist_unshare(prop_changelist_t *clp, zfs_share_proto_t *proto) ret = -1; } + zfs_commit_proto(proto); uu_avl_walk_end(walk); return (ret); diff --git a/lib/libzfs/libzfs_dataset.c b/lib/libzfs/libzfs_dataset.c index 4252c8cf0..d548cc0ec 100644 --- a/lib/libzfs/libzfs_dataset.c +++ b/lib/libzfs/libzfs_dataset.c @@ -22,7 +22,7 @@ /* * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2019 Joyent, Inc. - * Copyright (c) 2011, 2018 by Delphix. All rights reserved. + * Copyright (c) 2011, 2020 by Delphix. All rights reserved. * Copyright (c) 2012 DEY Storage Systems, Inc. All rights reserved. * Copyright (c) 2012 Pawel Jakub Dawidek <[email protected]>. * Copyright (c) 2013 Martin Matuska. All rights reserved. @@ -1432,49 +1432,14 @@ badlabel: else proto = PROTO_NFS; - /* - * Must be an valid sharing protocol - * option string so init the libshare - * in order to enable the parser and - * then parse the options. We use the - * control API since we don't care about - * the current configuration and don't - * want the overhead of loading it - * until we actually do something. - */ - - if (zfs_init_libshare(hdl, - SA_INIT_CONTROL_API) != SA_OK) { - /* - * An error occurred so we can't do - * anything - */ - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "'%s' cannot be set: problem " - "in share initialization"), - propname); - (void) zfs_error(hdl, EZFS_BADPROP, - errbuf); - goto error; - } - if (zfs_parse_options(strval, proto) != SA_OK) { - /* - * There was an error in parsing so - * deal with it by issuing an error - * message and leaving after - * uninitializing the libshare - * interface. - */ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "'%s' cannot be set to invalid " "options"), propname); (void) zfs_error(hdl, EZFS_BADPROP, errbuf); - zfs_uninit_libshare(hdl); goto error; } - zfs_uninit_libshare(hdl); } break; @@ -3589,6 +3554,7 @@ create_parents(libzfs_handle_t *hdl, char *target, int prefixlen) zfs_close(h); } + zfs_commit_all_shares(); return (0); diff --git a/lib/libzfs/libzfs_mount.c b/lib/libzfs/libzfs_mount.c index b257b6511..6d11016bc 100644 --- a/lib/libzfs/libzfs_mount.c +++ b/lib/libzfs/libzfs_mount.c @@ -22,7 +22,7 @@ /* * Copyright 2015 Nexenta Systems, Inc. All rights reserved. * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2014, 2019 by Delphix. All rights reserved. + * Copyright (c) 2014, 2020 by Delphix. All rights reserved. * Copyright 2016 Igor Kozhukhov <[email protected]> * Copyright 2017 RackTop Systems. * Copyright (c) 2018 Datto Inc. @@ -593,10 +593,12 @@ zfs_unmount(zfs_handle_t *zhp, const char *mountpoint, int flags) free(mntpt); return (-1); } + zfs_commit_all_shares(); if (unmount_one(hdl, mntpt, flags) != 0) { free(mntpt); (void) zfs_shareall(zhp); + zfs_commit_all_shares(); return (-1); } @@ -669,6 +671,94 @@ zfs_is_shared(zfs_handle_t *zhp) return (rc ? B_TRUE : B_FALSE); } +/* + * Unshare a filesystem by mountpoint. + */ +int +unshare_one(libzfs_handle_t *hdl, const char *name, const char *mountpoint, + zfs_share_proto_t proto) +{ + int err; + + err = sa_disable_share(mountpoint, proto_table[proto].p_name); + if (err != SA_OK) { + return (zfs_error_fmt(hdl, proto_table[proto].p_unshare_err, + dgettext(TEXT_DOMAIN, "cannot unshare '%s': %s"), + name, sa_errorstr(err))); + } + return (0); +} + +/* + * Query libshare for the given mountpoint and protocol, returning + * a zfs_share_type_t value. + */ +zfs_share_type_t +is_shared(const char *mountpoint, zfs_share_proto_t proto) +{ + if (sa_is_shared(mountpoint, proto_table[proto].p_name)) { + switch (proto) { + case PROTO_NFS: + return (SHARED_NFS); + case PROTO_SMB: + return (SHARED_SMB); + default: + return (SHARED_NOT_SHARED); + } + } + return (SHARED_NOT_SHARED); +} + +/* + * Share the given filesystem according to the options in the specified + * protocol specific properties (sharenfs, sharesmb). We rely + * on "libshare" to do the dirty work for us. + */ +int +zfs_share_proto(zfs_handle_t *zhp, zfs_share_proto_t *proto) +{ + char mountpoint[ZFS_MAXPROPLEN]; + char shareopts[ZFS_MAXPROPLEN]; + char sourcestr[ZFS_MAXPROPLEN]; + zfs_share_proto_t *curr_proto; + zprop_source_t sourcetype; + int err = 0; + + if (!zfs_is_mountable(zhp, mountpoint, sizeof (mountpoint), NULL, 0)) + return (0); + + for (curr_proto = proto; *curr_proto != PROTO_END; curr_proto++) { + /* + * Return success if there are no share options. + */ + if (zfs_prop_get(zhp, proto_table[*curr_proto].p_prop, + shareopts, sizeof (shareopts), &sourcetype, sourcestr, + ZFS_MAXPROPLEN, B_FALSE) != 0 || + strcmp(shareopts, "off") == 0) + continue; + + /* + * If the 'zoned' property is set, then zfs_is_mountable() + * will have already bailed out if we are in the global zone. + * But local zones cannot be NFS servers, so we ignore it for + * local zones as well. + */ + if (zfs_prop_get_int(zhp, ZFS_PROP_ZONED)) + continue; + + err = sa_enable_share(zfs_get_name(zhp), mountpoint, shareopts, + proto_table[*curr_proto].p_name); + if (err != SA_OK) { + return (zfs_error_fmt(zhp->zfs_hdl, + proto_table[*curr_proto].p_share_err, + dgettext(TEXT_DOMAIN, "cannot share '%s: %s'"), + zfs_get_name(zhp), sa_errorstr(err))); + } + + } + return (0); +} + int zfs_share(zfs_handle_t *zhp) { @@ -695,7 +785,7 @@ zfs_is_shared_proto(zfs_handle_t *zhp, char **where, zfs_share_proto_t proto) if (!zfs_is_mounted(zhp, &mountpoint)) return (SHARED_NOT_SHARED); - if ((rc = is_shared_impl(zhp->zfs_hdl, mountpoint, proto)) + if ((rc = is_shared(mountpoint, proto)) != SHARED_NOT_SHARED) { if (where != NULL) *where = mountpoint; @@ -723,21 +813,6 @@ zfs_is_shared_smb(zfs_handle_t *zhp, char **where) } /* - * zfs_uninit_libshare(zhandle) - * - * Uninitialize the libshare API if it hasn't already been - * uninitialized. It is OK to call multiple times. - */ -void -zfs_uninit_libshare(libzfs_handle_t *zhandle) -{ - if (zhandle != NULL && zhandle->libzfs_sharehdl != NULL) { - sa_fini(zhandle->libzfs_sharehdl); - zhandle->libzfs_sharehdl = NULL; - } -} - -/* * zfs_parse_options(options, proto) * * Call the legacy parse interface to get the protocol specific @@ -746,8 +821,45 @@ zfs_uninit_libshare(libzfs_handle_t *zhandle) int zfs_parse_options(char *options, zfs_share_proto_t proto) { - return (sa_parse_legacy_options(NULL, options, - proto_table[proto].p_name)); + return (sa_validate_shareopts(options, proto_table[proto].p_name)); +} + +void +zfs_commit_proto(zfs_share_proto_t *proto) +{ + zfs_share_proto_t *curr_proto; + for (curr_proto = proto; *curr_proto != PROTO_END; curr_proto++) { + sa_commit_shares(proto_table[*curr_proto].p_name); + } +} + +void +zfs_commit_nfs_shares(void) +{ + zfs_commit_proto(nfs_only); +} + +void +zfs_commit_smb_shares(void) +{ + zfs_commit_proto(smb_only); +} + +void +zfs_commit_all_shares(void) +{ + zfs_commit_proto(share_all_proto); +} + +void +zfs_commit_shares(const char *proto) +{ + if (proto == NULL) + zfs_commit_proto(share_all_proto); + else if (strcmp(proto, "nfs") == 0) + zfs_commit_proto(nfs_only); + else if (strcmp(proto, "smb") == 0) + zfs_commit_proto(smb_only); } int @@ -793,12 +905,13 @@ zfs_unshare_proto(zfs_handle_t *zhp, const char *mountpoint, for (curr_proto = proto; *curr_proto != PROTO_END; curr_proto++) { - if (is_shared_impl(hdl, mntpt, *curr_proto) && - unshare_one(hdl, zhp->zfs_name, - mntpt, *curr_proto) != 0) { - if (mntpt != NULL) - free(mntpt); - return (-1); + if (is_shared(mntpt, *curr_proto)) { + if (unshare_one(hdl, zhp->zfs_name, + mntpt, *curr_proto) != 0) { + if (mntpt != NULL) + free(mntpt); + return (-1); + } } } } @@ -1338,6 +1451,8 @@ zpool_enable_datasets(zpool_handle_t *zhp, const char *mntopts, int flags) zfs_share_one, &ms, B_FALSE); if (ms.ms_mntstatus != 0) ret = ms.ms_mntstatus; + else + zfs_commit_all_shares(); out: for (int i = 0; i < cb.cb_used; i++) @@ -1463,12 +1578,13 @@ zpool_disable_datasets(zpool_handle_t *zhp, boolean_t force) zfs_share_proto_t *curr_proto; for (curr_proto = share_all_proto; *curr_proto != PROTO_END; curr_proto++) { - if (is_shared_impl(hdl, mountpoints[i], *curr_proto) && + if (is_shared(mountpoints[i], *curr_proto) && unshare_one(hdl, mountpoints[i], mountpoints[i], *curr_proto) != 0) goto out; } } + zfs_commit_all_shares(); /* * Now unmount everything, removing the underlying directories as diff --git a/lib/libzfs/libzfs_util.c b/lib/libzfs/libzfs_util.c index 2f4aaed32..1fd9d400d 100644 --- a/lib/libzfs/libzfs_util.c +++ b/lib/libzfs/libzfs_util.c @@ -22,7 +22,7 @@ /* * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2020 Joyent, Inc. All rights reserved. - * Copyright (c) 2011, 2018 by Delphix. All rights reserved. + * Copyright (c) 2011, 2020 by Delphix. All rights reserved. * Copyright 2016 Igor Kozhukhov <[email protected]> * Copyright (c) 2017 Datto Inc. */ @@ -1024,13 +1024,9 @@ libzfs_init(void) return (NULL); } - hdl->libzfs_sharetab = fopen(ZFS_SHARETAB, "r"); - if (libzfs_core_init() != 0) { (void) close(hdl->libzfs_fd); (void) fclose(hdl->libzfs_mnttab); - if (hdl->libzfs_sharetab) - (void) fclose(hdl->libzfs_sharetab); free(hdl); return (NULL); } @@ -1074,9 +1070,6 @@ libzfs_fini(libzfs_handle_t *hdl) #else (void) fclose(hdl->libzfs_mnttab); #endif - if (hdl->libzfs_sharetab) - (void) fclose(hdl->libzfs_sharetab); - zfs_uninit_libshare(hdl); zpool_free_handles(hdl); namespace_clear(hdl); libzfs_mnttab_fini(hdl); diff --git a/lib/libzfs/os/freebsd/libzfs_fsshare.c b/lib/libzfs/os/freebsd/libzfs_fsshare.c deleted file mode 100644 index 9074c33e0..000000000 --- a/lib/libzfs/os/freebsd/libzfs_fsshare.c +++ /dev/null @@ -1,406 +0,0 @@ -/* - * Copyright (c) 2007 Pawel Jakub Dawidek <[email protected]> - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#include <sys/cdefs.h> -__FBSDID("$FreeBSD$"); - -#include <sys/param.h> -#include <sys/vfs.h> - -#include <assert.h> -#include <errno.h> -#include <fcntl.h> -#include <libutil.h> -#include <signal.h> -#include <stdio.h> -#include <string.h> -#include <unistd.h> -#include <libintl.h> - -#include "libzfs_impl.h" - -#define _PATH_MOUNTDPID "/var/run/mountd.pid" -#define FILE_HEADER "# !!! DO NOT EDIT THIS FILE MANUALLY !!!\n\n" -#define OPTSSIZE 1024 -#define MAXLINESIZE (PATH_MAX + OPTSSIZE) - - -void -sa_fini(sa_handle_t handle) -{ -} - -int -sa_parse_legacy_options(sa_group_t group, char *options, char *proto) -{ - return (SA_OK); -} - - -int -zfs_init_libshare(libzfs_handle_t *zhandle, int service) -{ - return (SA_OK); -} - -/* - * Share the given filesystem according to the options in the specified - * protocol specific properties (sharenfs, sharesmb). We rely - * on "libshare" to do the dirty work for us. - */ -int -zfs_share_proto(zfs_handle_t *zhp, zfs_share_proto_t *proto) -{ - char mountpoint[ZFS_MAXPROPLEN]; - char shareopts[ZFS_MAXPROPLEN]; - char sourcestr[ZFS_MAXPROPLEN]; - libzfs_handle_t *hdl = zhp->zfs_hdl; - zfs_share_proto_t *curr_proto; - zprop_source_t sourcetype; - int err, ret; - - if (!zfs_is_mountable(zhp, mountpoint, sizeof (mountpoint), NULL, 0)) - return (0); - - for (curr_proto = proto; *curr_proto != PROTO_END; curr_proto++) { - /* - * Return success if there are no share options. - */ - if (zfs_prop_get(zhp, proto_table[*curr_proto].p_prop, - shareopts, sizeof (shareopts), &sourcetype, sourcestr, - ZFS_MAXPROPLEN, B_FALSE) != 0 || - strcmp(shareopts, "off") == 0) - continue; - - ret = zfs_init_libshare(hdl, SA_INIT_SHARE_API); - if (ret != SA_OK) { - (void) zfs_error_fmt(hdl, EZFS_SHARENFSFAILED, - dgettext(TEXT_DOMAIN, "cannot share '%s': %s"), - zfs_get_name(zhp), sa_errorstr(ret)); - return (-1); - } - - /* - * If the 'zoned' property is set, then zfs_is_mountable() - * will have already bailed out if we are in the global zone. - * But local zones cannot be NFS servers, so we ignore it for - * local zones as well. - */ - if (zfs_prop_get_int(zhp, ZFS_PROP_ZONED)) - continue; - - if (*curr_proto != PROTO_NFS) { - fprintf(stderr, "Unsupported share protocol: %d.\n", - *curr_proto); - continue; - } - - if (strcmp(shareopts, "on") == 0) - err = fsshare(ZFS_EXPORTS_PATH, mountpoint, ""); - else - err = fsshare(ZFS_EXPORTS_PATH, mountpoint, shareopts); - if (err != 0) { - (void) zfs_error_fmt(hdl, - proto_table[*curr_proto].p_share_err, - dgettext(TEXT_DOMAIN, "cannot share '%s'"), - zfs_get_name(zhp)); - return (-1); - } - - } - return (0); -} - -/* - * Unshare a filesystem by mountpoint. - */ -int -unshare_one(libzfs_handle_t *hdl, const char *name, const char *mountpoint, - zfs_share_proto_t proto) -{ - int err; - - if (proto != PROTO_NFS) { - fprintf(stderr, "No SMB support in FreeBSD yet.\n"); - return (EOPNOTSUPP); - } - - err = fsunshare(ZFS_EXPORTS_PATH, mountpoint); - if (err != 0) { - zfs_error_aux(hdl, "%s", strerror(err)); - return (zfs_error_fmt(hdl, EZFS_UNSHARENFSFAILED, - dgettext(TEXT_DOMAIN, - "cannot unshare '%s'"), name)); - } - return (0); -} - -zfs_share_type_t -is_shared_impl(libzfs_handle_t *hdl, const char *mountpoint, - zfs_share_proto_t proto) -{ - char buf[MAXPATHLEN], *tab; - - if (hdl->libzfs_sharetab == NULL) - return (SHARED_NOT_SHARED); - - (void) fseek(hdl->libzfs_sharetab, 0, SEEK_SET); - - while (fgets(buf, sizeof (buf), hdl->libzfs_sharetab) != NULL) { - - /* the mountpoint is the first entry on each line */ - if ((tab = strchr(buf, '\t')) == NULL) - continue; - - *tab = '\0'; - if (strcmp(buf, mountpoint) == 0) { - if (proto == PROTO_NFS) - return (SHARED_NFS); - } - } - - return (SHARED_NOT_SHARED); -} - -static void -restart_mountd(void) -{ - struct pidfh *pfh; - pid_t mountdpid; - - pfh = pidfile_open(_PATH_MOUNTDPID, 0600, &mountdpid); - if (pfh != NULL) { - /* Mountd is not running. */ - pidfile_remove(pfh); - return; - } - if (errno != EEXIST) { - /* Cannot open pidfile for some reason. */ - return; - } - /* We have mountd(8) PID in mountdpid variable. */ - kill(mountdpid, SIGHUP); -} - -/* - * Read one line from a file. Skip comments, empty lines and a line with a - * mountpoint specified in the 'skip' argument. - */ -static char * -zgetline(FILE *fd, const char *skip) -{ - static char line[MAXLINESIZE]; - size_t len, skiplen = 0; - char *s, last; - - if (skip != NULL) - skiplen = strlen(skip); - for (;;) { - s = fgets(line, sizeof (line), fd); - if (s == NULL) - return (NULL); - /* Skip empty lines and comments. */ - if (line[0] == '\n' || line[0] == '#') - continue; - len = strlen(line); - if (line[len - 1] == '\n') - line[len - 1] = '\0'; - last = line[skiplen]; - /* Skip the given mountpoint. */ - if (skip != NULL && strncmp(skip, line, skiplen) == 0 && - (last == '\t' || last == ' ' || last == '\0')) { - continue; - } - break; - } - return (line); -} -/* BEGIN CSTYLED */ -/* - * Function translate options to a format acceptable by exports(5), eg. - * - * -ro -network=192.168.0.0 -mask=255.255.255.0 -maproot=0 freefall.freebsd.org 69.147.83.54 - * - * Accepted input formats: - * - * ro,network=192.168.0.0,mask=255.255.255.0,maproot=0,freefall.freebsd.org - * ro network=192.168.0.0 mask=255.255.255.0 maproot=0 freefall.freebsd.org - * -ro,-network=192.168.0.0,-mask=255.255.255.0,-maproot=0,freefall.freebsd.org - * -ro -network=192.168.0.0 -mask=255.255.255.0 -maproot=0 freefall.freebsd.org - * - * Recognized keywords: - * - * ro, maproot, mapall, mask, network, sec, alldirs, public, webnfs, index, quiet - * - */ -/* END CSTYLED */ - -static const char *known_opts[] = { "ro", "maproot", "mapall", "mask", - "network", "sec", "alldirs", "public", "webnfs", "index", "quiet", - NULL }; -static char * -translate_opts(const char *shareopts) -{ - static char newopts[OPTSSIZE]; - char oldopts[OPTSSIZE]; - char *o, *s = NULL; - unsigned int i; - size_t len; - - strlcpy(oldopts, shareopts, sizeof (oldopts)); - newopts[0] = '\0'; - s = oldopts; - while ((o = strsep(&s, "-, ")) != NULL) { - if (o[0] == '\0') - continue; - for (i = 0; known_opts[i] != NULL; i++) { - len = strlen(known_opts[i]); - if (strncmp(known_opts[i], o, len) == 0 && - (o[len] == '\0' || o[len] == '=')) { - strlcat(newopts, "-", sizeof (newopts)); - break; - } - } - strlcat(newopts, o, sizeof (newopts)); - strlcat(newopts, " ", sizeof (newopts)); - } - return (newopts); -} - -static int -fsshare_main(const char *file, const char *mountpoint, const char *shareopts, - int share) -{ - char tmpfile[PATH_MAX]; - char *line; - FILE *newfd, *oldfd; - int fd, error; - - newfd = oldfd = NULL; - error = 0; - - /* - * Create temporary file in the same directory, so we can atomically - * rename it. - */ - if (strlcpy(tmpfile, file, sizeof (tmpfile)) >= sizeof (tmpfile)) - return (ENAMETOOLONG); - if (strlcat(tmpfile, ".XXXXXXXX", sizeof (tmpfile)) >= sizeof (tmpfile)) - return (ENAMETOOLONG); - fd = mkstemp(tmpfile); - if (fd == -1) - return (errno); - /* - * File name is random, so we don't really need file lock now, but it - * will be needed after rename(2). - */ - error = flock(fd, LOCK_EX); - assert(error == 0 || (error == -1 && errno == EOPNOTSUPP)); - newfd = fdopen(fd, "r+"); - assert(newfd != NULL); - /* Open old exports file. */ - oldfd = fopen(file, "r"); - if (oldfd == NULL) { - if (share) { - if (errno != ENOENT) { - error = errno; - goto out; - } - } else { - /* If there is no exports file, ignore the error. */ - if (errno == ENOENT) - errno = 0; - error = errno; - goto out; - } - } else { - error = flock(fileno(oldfd), LOCK_EX); - assert(error == 0 || (error == -1 && errno == EOPNOTSUPP)); - error = 0; - } - - /* Place big, fat warning at the beginning of the file. */ - fprintf(newfd, "%s", FILE_HEADER); - while (oldfd != NULL && (line = zgetline(oldfd, mountpoint)) != NULL) - fprintf(newfd, "%s\n", line); - if (oldfd != NULL && ferror(oldfd) != 0) { - error = ferror(oldfd); - goto out; - } - if (ferror(newfd) != 0) { - error = ferror(newfd); - goto out; - } - if (share) { - fprintf(newfd, "%s\t%s\n", mountpoint, - translate_opts(shareopts)); - } - -out: - if (error != 0) - unlink(tmpfile); - else { - if (rename(tmpfile, file) == -1) { - error = errno; - unlink(tmpfile); - } else { - fflush(newfd); - /* - * Send SIGHUP to mountd, but unlock exports file later. - */ - restart_mountd(); - } - } - if (oldfd != NULL) { - flock(fileno(oldfd), LOCK_UN); - fclose(oldfd); - } - if (newfd != NULL) { - flock(fileno(newfd), LOCK_UN); - fclose(newfd); - } - return (error); -} - -/* - * Add the given mountpoint to the given exports file. - */ -int -fsshare(const char *file, const char *mountpoint, const char *shareopts) -{ - - return (fsshare_main(file, mountpoint, shareopts, 1)); -} - -/* - * Remove the given mountpoint from the given exports file. - */ -int -fsunshare(const char *file, const char *mountpoint) -{ - - return (fsshare_main(file, mountpoint, NULL, 0)); -} diff --git a/lib/libzfs/os/linux/libzfs_mount_os.c b/lib/libzfs/os/linux/libzfs_mount_os.c index 7be85097d..92052f28f 100644 --- a/lib/libzfs/os/linux/libzfs_mount_os.c +++ b/lib/libzfs/os/linux/libzfs_mount_os.c @@ -22,7 +22,7 @@ /* * Copyright 2015 Nexenta Systems, Inc. All rights reserved. * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2014, 2019 by Delphix. All rights reserved. + * Copyright (c) 2014, 2020 by Delphix. All rights reserved. * Copyright 2016 Igor Kozhukhov <[email protected]> * Copyright 2017 RackTop Systems. * Copyright (c) 2018 Datto Inc. @@ -50,239 +50,6 @@ #include "libzfs_impl.h" #include <thread_pool.h> -/* - * zfs_init_libshare(zhandle, service) - * - * Initialize the libshare API if it hasn't already been initialized. - * In all cases it returns 0 if it succeeded and an error if not. The - * service value is which part(s) of the API to initialize and is a - * direct map to the libshare sa_init(service) interface. - */ -int -zfs_init_libshare(libzfs_handle_t *zhandle, int service) -{ - int ret = SA_OK; - - if (ret == SA_OK && zhandle->libzfs_shareflags & ZFSSHARE_MISS) { - /* - * We had a cache miss. Most likely it is a new ZFS - * dataset that was just created. We want to make sure - * so check timestamps to see if a different process - * has updated any of the configuration. If there was - * some non-ZFS change, we need to re-initialize the - * internal cache. - */ - zhandle->libzfs_shareflags &= ~ZFSSHARE_MISS; - if (sa_needs_refresh(zhandle->libzfs_sharehdl)) { - zfs_uninit_libshare(zhandle); - zhandle->libzfs_sharehdl = sa_init(service); - } - } - - if (ret == SA_OK && zhandle && zhandle->libzfs_sharehdl == NULL) - zhandle->libzfs_sharehdl = sa_init(service); - - if (ret == SA_OK && zhandle->libzfs_sharehdl == NULL) - ret = SA_NO_MEMORY; - return (ret); -} - - -/* - * Share the given filesystem according to the options in the specified - * protocol specific properties (sharenfs, sharesmb). We rely - * on "libshare" to do the dirty work for us. - */ -int -zfs_share_proto(zfs_handle_t *zhp, zfs_share_proto_t *proto) -{ - char mountpoint[ZFS_MAXPROPLEN]; - char shareopts[ZFS_MAXPROPLEN]; - char sourcestr[ZFS_MAXPROPLEN]; - libzfs_handle_t *hdl = zhp->zfs_hdl; - sa_share_t share; - zfs_share_proto_t *curr_proto; - zprop_source_t sourcetype; - int err, ret; - - if (!zfs_is_mountable(zhp, mountpoint, sizeof (mountpoint), NULL, 0)) - return (0); - - for (curr_proto = proto; *curr_proto != PROTO_END; curr_proto++) { - /* - * Return success if there are no share options. - */ - if (zfs_prop_get(zhp, proto_table[*curr_proto].p_prop, - shareopts, sizeof (shareopts), &sourcetype, sourcestr, - ZFS_MAXPROPLEN, B_FALSE) != 0 || - strcmp(shareopts, "off") == 0) - continue; - - ret = zfs_init_libshare(hdl, SA_INIT_SHARE_API); - if (ret != SA_OK) { - (void) zfs_error_fmt(hdl, EZFS_SHARENFSFAILED, - dgettext(TEXT_DOMAIN, "cannot share '%s': %s"), - zfs_get_name(zhp), sa_errorstr(ret)); - return (-1); - } - - /* - * If the 'zoned' property is set, then zfs_is_mountable() - * will have already bailed out if we are in the global zone. - * But local zones cannot be NFS servers, so we ignore it for - * local zones as well. - */ - if (zfs_prop_get_int(zhp, ZFS_PROP_ZONED)) - continue; - - share = sa_find_share(hdl->libzfs_sharehdl, mountpoint); - if (share == NULL) { - /* - * This may be a new file system that was just - * created so isn't in the internal cache - * (second time through). Rather than - * reloading the entire configuration, we can - * assume ZFS has done the checking and it is - * safe to add this to the internal - * configuration. - */ - if (sa_zfs_process_share(hdl->libzfs_sharehdl, - NULL, NULL, mountpoint, - proto_table[*curr_proto].p_name, sourcetype, - shareopts, sourcestr, zhp->zfs_name) != SA_OK) { - (void) zfs_error_fmt(hdl, - proto_table[*curr_proto].p_share_err, - dgettext(TEXT_DOMAIN, "cannot share '%s'"), - zfs_get_name(zhp)); - return (-1); - } - hdl->libzfs_shareflags |= ZFSSHARE_MISS; - share = sa_find_share(hdl->libzfs_sharehdl, - mountpoint); - } - if (share != NULL) { - err = sa_enable_share(share, - proto_table[*curr_proto].p_name); - if (err != SA_OK) { - (void) zfs_error_fmt(hdl, - proto_table[*curr_proto].p_share_err, - dgettext(TEXT_DOMAIN, "cannot share '%s'"), - zfs_get_name(zhp)); - return (-1); - } - } else { - (void) zfs_error_fmt(hdl, - proto_table[*curr_proto].p_share_err, - dgettext(TEXT_DOMAIN, "cannot share '%s'"), - zfs_get_name(zhp)); - return (-1); - } - - } - return (0); -} - -/* - * Unshare a filesystem by mountpoint. - */ -int -unshare_one(libzfs_handle_t *hdl, const char *name, const char *mountpoint, - zfs_share_proto_t proto) -{ - sa_share_t share; - int err; - char *mntpt; - /* - * Mountpoint could get trashed if libshare calls getmntany - * which it does during API initialization, so strdup the - * value. - */ - mntpt = zfs_strdup(hdl, mountpoint); - - /* make sure libshare initialized */ - if ((err = zfs_init_libshare(hdl, SA_INIT_SHARE_API)) != SA_OK) { - free(mntpt); /* don't need the copy anymore */ - return (zfs_error_fmt(hdl, proto_table[proto].p_unshare_err, - dgettext(TEXT_DOMAIN, "cannot unshare '%s': %s"), - name, sa_errorstr(err))); - } - - share = sa_find_share(hdl->libzfs_sharehdl, mntpt); - free(mntpt); /* don't need the copy anymore */ - - if (share != NULL) { - err = sa_disable_share(share, proto_table[proto].p_name); - if (err != SA_OK) { - return (zfs_error_fmt(hdl, - proto_table[proto].p_unshare_err, - dgettext(TEXT_DOMAIN, "cannot unshare '%s': %s"), - name, sa_errorstr(err))); - } - } else { - return (zfs_error_fmt(hdl, proto_table[proto].p_unshare_err, - dgettext(TEXT_DOMAIN, "cannot unshare '%s': not found"), - name)); - } - return (0); -} - -/* - * Search the sharetab for the given mountpoint and protocol, returning - * a zfs_share_type_t value. - */ -zfs_share_type_t -is_shared_impl(libzfs_handle_t *hdl, const char *mountpoint, - zfs_share_proto_t proto) -{ - char buf[MAXPATHLEN], *tab; - char *ptr; - - if (hdl->libzfs_sharetab == NULL) - return (SHARED_NOT_SHARED); - - /* Reopen ZFS_SHARETAB to prevent reading stale data from open file */ - if (freopen(ZFS_SHARETAB, "r", hdl->libzfs_sharetab) == NULL) - return (SHARED_NOT_SHARED); - - (void) fseek(hdl->libzfs_sharetab, 0, SEEK_SET); - - while (fgets(buf, sizeof (buf), hdl->libzfs_sharetab) != NULL) { - - /* the mountpoint is the first entry on each line */ - if ((tab = strchr(buf, '\t')) == NULL) - continue; - - *tab = '\0'; - if (strcmp(buf, mountpoint) == 0) { - /* - * the protocol field is the third field - * skip over second field - */ - ptr = ++tab; - if ((tab = strchr(ptr, '\t')) == NULL) - continue; - ptr = ++tab; - if ((tab = strchr(ptr, '\t')) == NULL) - continue; - *tab = '\0'; - if (strcmp(ptr, - proto_table[proto].p_name) == 0) { - switch (proto) { - case PROTO_NFS: - return (SHARED_NFS); - case PROTO_SMB: - return (SHARED_SMB); - default: - return (0); - } - } - } - } - - return (SHARED_NOT_SHARED); -} - - #define ZS_COMMENT 0x00000000 /* comment */ #define ZS_ZFSUTIL 0x00000001 /* caller is zfs(8) */ diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index f6478dd0d..61c095290 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -258,9 +258,8 @@ tags = ['functional', 'cli_root', 'zfs_set'] [tests/functional/cli_root/zfs_share] tests = ['zfs_share_001_pos', 'zfs_share_002_pos', 'zfs_share_003_pos', - 'zfs_share_004_pos', 'zfs_share_005_pos', 'zfs_share_006_pos', - 'zfs_share_007_neg', 'zfs_share_008_neg', 'zfs_share_009_neg', - 'zfs_share_010_neg', 'zfs_share_011_pos'] + 'zfs_share_004_pos', 'zfs_share_006_pos', 'zfs_share_008_neg', + 'zfs_share_010_neg', 'zfs_share_011_pos', 'zfs_share_concurrent_shares'] tags = ['functional', 'cli_root', 'zfs_share'] [tests/functional/cli_root/zfs_snapshot] diff --git a/tests/runfiles/linux.run b/tests/runfiles/linux.run index 5b22b7fda..36981bbb0 100644 --- a/tests/runfiles/linux.run +++ b/tests/runfiles/linux.run @@ -51,6 +51,10 @@ tags = ['functional', 'cli_root', 'zfs'] tests = ['zfs_mount_006_pos', 'zfs_mount_008_pos', 'zfs_multi_mount'] tags = ['functional', 'cli_root', 'zfs_mount'] +[tests/functional/cli_root/zfs_share:Linux] +tests = ['zfs_share_005_pos', 'zfs_share_007_neg', 'zfs_share_009_neg'] +tags = ['functional', 'cli_root', 'zfs_share'] + [tests/functional/cli_root/zfs_sysfs:Linux] tests = ['zfeature_set_unsupported', 'zfs_get_unsupported', 'zfs_set_unsupported', 'zfs_sysfs_live', 'zpool_get_unsupported', diff --git a/tests/test-runner/bin/zts-report.py b/tests/test-runner/bin/zts-report.py index 4a9d08752..78ce291b7 100755 --- a/tests/test-runner/bin/zts-report.py +++ b/tests/test-runner/bin/zts-report.py @@ -244,6 +244,9 @@ if sys.platform.startswith('freebsd'): maybe.update({ 'cli_root/zfs_copies/zfs_copies_002_pos': ['FAIL', known_reason], 'cli_root/zfs_inherit/zfs_inherit_001_neg': ['FAIL', known_reason], + 'cli_root/zfs_share/zfs_share_011_pos': ['FAIL', known_reason], + 'cli_root/zfs_share/zfs_share_concurrent_shares': + ['FAIL', known_reason], 'delegate/zfs_allow_003_pos': ['FAIL', known_reason], 'removal/removal_condense_export': ['FAIL', known_reason], 'removal/removal_with_export': ['FAIL', known_reason], diff --git a/tests/test-runner/include/logapi.shlib b/tests/test-runner/include/logapi.shlib index 334a04532..aa6e7c0f6 100644 --- a/tests/test-runner/include/logapi.shlib +++ b/tests/test-runner/include/logapi.shlib @@ -23,7 +23,7 @@ # Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # -# Copyright (c) 2012, 2016 by Delphix. All rights reserved. +# Copyright (c) 2012, 2020 by Delphix. All rights reserved. # . ${STF_TOOLS}/include/stf.shlib @@ -420,6 +420,11 @@ function log_other _endlog $STF_OTHER "$@" } +function set_main_pid +{ + _MAINPID=$1 +} + # # Internal functions # @@ -454,6 +459,15 @@ function _endlog shift (( ${#@} > 0 )) && _printline "$@" + # + # If we're running in a subshell then just exit and let + # the parent handle the failures + # + if [[ -n "$_MAINPID" && $$ != "$_MAINPID" ]]; then + log_note "subshell exited: "$_MAINPID + exit $exitcode + fi + if [[ $exitcode == $STF_FAIL ]] ; then _execute_testfail_callbacks fi diff --git a/tests/zfs-tests/include/libtest.shlib b/tests/zfs-tests/include/libtest.shlib index 5e07cda4d..1618c92bd 100644 --- a/tests/zfs-tests/include/libtest.shlib +++ b/tests/zfs-tests/include/libtest.shlib @@ -21,7 +21,7 @@ # # Copyright (c) 2009, Sun Microsystems Inc. All rights reserved. -# Copyright (c) 2012, 2018, Delphix. All rights reserved. +# Copyright (c) 2012, 2020, Delphix. All rights reserved. # Copyright (c) 2017, Tim Chase. All rights reserved. # Copyright (c) 2017, Nexenta Systems Inc. All rights reserved. # Copyright (c) 2017, Lawrence Livermore National Security LLC. @@ -1373,6 +1373,80 @@ function is_shared esac } +function is_exported_illumos +{ + typeset fs=$1 + typeset mtpt + + for mtpt in `awk '{print $1}' /etc/dfs/sharetab` ; do + if [[ $mtpt == $fs ]] ; then + return 0 + fi + done + + return 1 +} + +function is_exported_freebsd +{ + typeset fs=$1 + typeset mtpt + + for mtpt in `awk '{print $1}' /etc/zfs/exports` ; do + if [[ $mtpt == $fs ]] ; then + return 0 + fi + done + + return 1 +} + +function is_exported_linux +{ + typeset fs=$1 + typeset mtpt + + for mtpt in `awk '{print $1}' /etc/exports.d/zfs.exports` ; do + if [[ $mtpt == $fs ]] ; then + return 0 + fi + done + + return 1 +} + +# +# Given a mountpoint, or a dataset name, determine if it is exported via +# the os-specific NFS exports file. +# +# Returns 0 if exported, 1 otherwise. +# +function is_exported +{ + typeset fs=$1 + typeset mtpt + + if [[ $fs != "/"* ]] ; then + if datasetnonexists "$fs" ; then + return 1 + else + mtpt=$(get_prop mountpoint "$fs") + case $mtpt in + none|legacy|-) return 1 + ;; + *) fs=$mtpt + ;; + esac + fi + fi + + case $(uname) in + FreeBSD) is_exported_freebsd "$fs" ;; + Linux) is_exported_linux "$fs" ;; + *) is_exported_illumos "$fs" ;; + esac +} + # # Given a dataset name determine if it is shared via SMB. # @@ -1397,7 +1471,7 @@ function is_shared_smb done return 1 else - log_unsupported "Currently unsupported by the test framework" + log_note "Currently unsupported by the test framework" return 1 fi } @@ -1445,7 +1519,7 @@ function unshare_fs #fs is_shared $fs || is_shared_smb $fs if (($? == 0)); then - log_must zfs unshare $fs + zfs unshare $fs || log_fail "zfs unshare $fs failed" fi return 0 @@ -1523,6 +1597,21 @@ function showshares_smb return 0 } +function check_nfs +{ + if is_linux; then + share -s + elif is_freebsd; then + showmount -e + else + log_unsupported "Unknown platform" + fi + + if [[ $? -ne 0 ]]; then + log_unsupported "The NFS utilities are not installed" + fi +} + # # Check NFS server status and trigger it online. # @@ -1535,7 +1624,7 @@ function setup_nfs_server return fi - if is_linux || is_freebsd; then + if is_linux; then # # Re-synchronize /var/lib/nfs/etab with /etc/exports and # /etc/exports.d./* to provide a clean test environment. @@ -1544,6 +1633,11 @@ function setup_nfs_server log_note "NFS server must be started prior to running ZTS." return + elif is_freebsd; then + kill -s HUP $(cat /var/run/mountd.pid) + + log_note "NFS server must be started prior to running ZTS." + return fi typeset nfs_fmri="svc:/network/nfs/server:default" @@ -4078,3 +4172,23 @@ function get_arcstat # stat ;; esac } + +# +# Given an array of pids, wait until all processes +# have completed and check their return status. +# +function wait_for_children #children +{ + rv=0 + children=("$@") + for child in "${children[@]}" + do + child_exit=0 + wait ${child} || child_exit=$? + if [ $child_exit -ne 0 ]; then + echo "child ${child} failed with ${child_exit}" + rv=1 + fi + done + return $rv +} diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_share/Makefile.am b/tests/zfs-tests/tests/functional/cli_root/zfs_share/Makefile.am index e20014656..8628b17c4 100644 --- a/tests/zfs-tests/tests/functional/cli_root/zfs_share/Makefile.am +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_share/Makefile.am @@ -12,7 +12,8 @@ dist_pkgdata_SCRIPTS = \ zfs_share_008_neg.ksh \ zfs_share_009_neg.ksh \ zfs_share_010_neg.ksh \ - zfs_share_011_pos.ksh + zfs_share_011_pos.ksh \ + zfs_share_concurrent_shares.ksh dist_pkgdata_DATA = \ zfs_share.cfg diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_share/setup.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_share/setup.ksh index 29f38e802..1601087f7 100755 --- a/tests/zfs-tests/tests/functional/cli_root/zfs_share/setup.ksh +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_share/setup.ksh @@ -27,10 +27,7 @@ . $STF_SUITE/include/libtest.shlib -share -s -if [ $? -ne 0 ]; then - log_unsupported "The NFS utilities are not installed" -fi +check_nfs # Make sure NFS server is running before testing. setup_nfs_server diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_share/zfs_share_001_pos.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_share/zfs_share_001_pos.ksh index a2c06e0b3..fefeb1b1c 100755 --- a/tests/zfs-tests/tests/functional/cli_root/zfs_share/zfs_share_001_pos.ksh +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_share/zfs_share_001_pos.ksh @@ -26,7 +26,7 @@ # # -# Copyright (c) 2016 by Delphix. All rights reserved. +# Copyright (c) 2016, 2020 by Delphix. All rights reserved. # . $STF_SUITE/include/libtest.shlib @@ -71,6 +71,8 @@ function cleanup if snapexists "$TESTPOOL/$TESTFS@snapshot"; then log_must zfs destroy -f $TESTPOOL/$TESTFS@snapshot fi + + log_must zfs share -a } @@ -138,11 +140,20 @@ done # log_must zfs share -a +# +# We need to unset __ZFS_POOL_EXCLUDE so that we include all file systems +# in the os-specific zfs exports file. This will be reset by the next test. +# +unset __ZFS_POOL_EXCLUDE + i=0 while (( i < ${#fs[*]} )); do is_shared ${fs[i]} || \ log_fail "File system ${fs[i]} is not shared (share -a)" + is_exported ${fs[i]} || \ + log_fail "File system ${fs[i]} is not exported (share -a)" + ((i = i + 2)) done diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_share/zfs_share_concurrent_shares.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_share/zfs_share_concurrent_shares.ksh new file mode 100755 index 000000000..bc45820a1 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_share/zfs_share_concurrent_shares.ksh @@ -0,0 +1,201 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2020 by Delphix. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib + +# +# DESCRIPTION: +# Verify that 'zfs set sharenfs=on', 'zfs share', and 'zfs unshare' can +# run concurrently. The test creates 300 filesystem and 300 threads. +# Each thread will run through the test strategy in parallel. +# +# STRATEGY: +# 1. Verify that the file system is not shared. +# 2. Enable the 'sharenfs' property +# 3. Invoke 'zfs unshare' and verify filesystem is no longer shared +# 4. Invoke 'zfs share'. +# 4. Verify that the file system is shared. +# 5. Verify that a shared filesystem cannot be shared again. +# 6. Verify that share -a succeeds. +# + +verify_runnable "global" + +function cleanup +{ + wait + for fs in $(seq 0 100) + do + log_must zfs set sharenfs=off $TESTPOOL/$TESTFS1/$fs + log_must zfs set sharenfs=off $TESTPOOL/$TESTFS2/$fs + log_must zfs set sharenfs=off $TESTPOOL/$TESTFS3/$fs + unshare_fs $TESTPOOL/$TESTFS1/$fs + unshare_fs $TESTPOOL/$TESTFS2/$fs + unshare_fs $TESTPOOL/$TESTFS3/$fs + + if mounted $TESTPOOL/$TESTFS1/$fs; then + log_must zfs unmount $TESTPOOL/$TESTFS1/$fs + fi + if mounted $TESTPOOL/$TESTFS2/$fs; then + log_must zfs unmount $TESTPOOL/$TESTFS2/$fs + fi + if mounted $TESTPOOL/$TESTFS3/$fs; then + log_must zfs unmount $TESTPOOL/$TESTFS3/$fs + fi + + datasetexists $TESTPOOL/$TESTFS1/$fs && \ + log_must zfs destroy -f $TESTPOOL/$TESTFS1/$fs + datasetexists $TESTPOOL/$TESTFS2/$fs && \ + log_must zfs destroy -f $TESTPOOL/$TESTFS2/$fs + datasetexists $TESTPOOL/$TESTFS3/$fs && \ + log_must zfs destroy -f $TESTPOOL/$TESTFS3/$fs + done + + log_must zfs share -a +} + +function create_filesystems +{ + for fs in $(seq 0 100) + do + log_must zfs create -p $TESTPOOL/$TESTFS1/$fs + log_must zfs create -p $TESTPOOL/$TESTFS2/$fs + log_must zfs create -p $TESTPOOL/$TESTFS3/$fs + done +} + +# +# Main test routine. +# +# Given a file system this routine will attempt +# share the mountpoint and then verify it has been shared. +# +function test_share # filesystem +{ + typeset filesystem=$1 + typeset mntp=$(get_prop mountpoint $filesystem) + + not_shared $mntp || \ + log_fail "File system $filesystem is already shared." + + zfs set sharenfs=on $filesystem || \ + log_fail "zfs set sharenfs=on $filesystem failed." + is_shared $mntp || \ + log_fail "File system $filesystem is not shared (set sharenfs)." + + # + # Verify 'zfs share' works as well. + # + zfs unshare $filesystem || \ + log_fail "zfs unshare $filesystem failed." + is_shared $mntp && \ + log_fail "File system $filesystem is still shared." + + zfs share $filesystem || \ + log_fail "zfs share $filesystem failed." + is_shared $mntp || \ + log_fail "file system $filesystem is not shared (zfs share)." + + #log_note "Sharing a shared file system fails." + zfs share $filesystem && \ + log_fail "zfs share $filesystem did not fail" + return 0 +} + +# +# Set the main process id so that we know to capture +# failures from child processes and allow the parent process +# to report the failure. +# +set_main_pid $$ +log_assert "Verify that 'zfs share' succeeds as root." +log_onexit cleanup + +create_filesystems + +child_pids=() +for fs in $(seq 0 100) +do + test_share $TESTPOOL/$TESTFS1/$fs & + child_pids+=($!) + log_note "$TESTPOOL/$TESTFS1/$fs ==> $!" + test_share $TESTPOOL/$TESTFS2/$fs & + child_pids+=($!) + log_note "$TESTPOOL/$TESTFS2/$fs ==> $!" + test_share $TESTPOOL/$TESTFS3/$fs & + child_pids+=($!) + log_note "$TESTPOOL/$TESTFS3/$fs ==> $!" +done +wait_for_children "${child_pids[@]}" || + log_fail "multithreaded share test failed" + +log_note "Verify 'zfs share -a' succeeds." + +# +# Unshare each of the file systems. +# +child_pids=() +for fs in $(seq 0 100) +do + unshare_fs $TESTPOOL/$TESTFS1/$fs & + child_pids+=($!) + unshare_fs $TESTPOOL/$TESTFS2/$fs & + child_pids+=($!) + unshare_fs $TESTPOOL/$TESTFS3/$fs & + child_pids+=($!) +done +wait_for_children "${child_pids[@]}" || + log_fail "multithreaded unshare failed" + +# +# Try a zfs share -a and verify all file systems are shared. +# +log_must zfs share -a + +# +# We need to unset __ZFS_POOL_EXCLUDE so that we include all file systems +# in the os-specific zfs exports file. This will be reset by the next test. +# +unset __ZFS_POOL_EXCLUDE + +for fs in $(seq 0 100) +do + is_shared $TESTPOOL/$TESTFS1/$fs || \ + log_fail "File system $TESTPOOL/$TESTFS1/$fs is not shared" + is_shared $TESTPOOL/$TESTFS2/$fs || \ + log_fail "File system $TESTPOOL/$TESTFS2/$fs is not shared" + is_shared $TESTPOOL/$TESTFS3/$fs || \ + log_fail "File system $TESTPOOL/$TESTFS3/$fs is not shared" + + is_exported $TESTPOOL/$TESTFS1/$fs || \ + log_fail "File system $TESTPOOL/$TESTFS1/$fs is not exported" + is_exported $TESTPOOL/$TESTFS2/$fs || \ + log_fail "File system $TESTPOOL/$TESTFS2/$fs is not exported" + is_exported $TESTPOOL/$TESTFS3/$fs || \ + log_fail "File system $TESTPOOL/$TESTFS3/$fs is not exported" +done + +log_pass "'zfs share [ -a ] <filesystem>' succeeds as root." |