diff options
Diffstat (limited to 'sys/contrib/openzfs/lib/libzfs/os/linux')
4 files changed, 1150 insertions, 2 deletions
diff --git a/sys/contrib/openzfs/lib/libzfs/os/linux/libzfs_mount_os.c b/sys/contrib/openzfs/lib/libzfs/os/linux/libzfs_mount_os.c index cdfb432b10ad..7d8768d12dda 100644 --- a/sys/contrib/openzfs/lib/libzfs/os/linux/libzfs_mount_os.c +++ b/sys/contrib/openzfs/lib/libzfs/os/linux/libzfs_mount_os.c @@ -409,6 +409,149 @@ do_unmount(zfs_handle_t *zhp, const char *mntpt, int flags) return (rc ? EINVAL : 0); } +#ifdef HAVE_MOUNT_SETATTR +/* + * Build a struct mount_attr for the changed namespace properties. + * Parallel to zfs_add_options() but produces mount_setattr(2) input + * instead of a mount options string. + */ +static void +zfs_add_options_setattr(zfs_handle_t *zhp, struct mount_attr *attr, + uint32_t nspflags) +{ + const char *source; + + if (nspflags & ZFS_MNT_PROP_READONLY) { + if (getprop_uint64(zhp, ZFS_PROP_READONLY, &source)) + attr->attr_set |= MOUNT_ATTR_RDONLY; + else + attr->attr_clr |= MOUNT_ATTR_RDONLY; + } + if (nspflags & ZFS_MNT_PROP_EXEC) { + if (getprop_uint64(zhp, ZFS_PROP_EXEC, &source)) + attr->attr_clr |= MOUNT_ATTR_NOEXEC; + else + attr->attr_set |= MOUNT_ATTR_NOEXEC; + } + if (nspflags & ZFS_MNT_PROP_SETUID) { + if (getprop_uint64(zhp, ZFS_PROP_SETUID, &source)) + attr->attr_clr |= MOUNT_ATTR_NOSUID; + else + attr->attr_set |= MOUNT_ATTR_NOSUID; + } + if (nspflags & ZFS_MNT_PROP_DEVICES) { + if (getprop_uint64(zhp, ZFS_PROP_DEVICES, &source)) + attr->attr_clr |= MOUNT_ATTR_NODEV; + else + attr->attr_set |= MOUNT_ATTR_NODEV; + } + if (nspflags & (ZFS_MNT_PROP_ATIME | ZFS_MNT_PROP_RELATIME)) { + uint64_t atime = getprop_uint64(zhp, ZFS_PROP_ATIME, &source); + uint64_t relatime = getprop_uint64(zhp, + ZFS_PROP_RELATIME, &source); + + attr->attr_clr |= MOUNT_ATTR__ATIME; + if (!atime) + attr->attr_set |= MOUNT_ATTR_NOATIME; + else if (relatime) + attr->attr_set |= MOUNT_ATTR_RELATIME; + else + attr->attr_set |= MOUNT_ATTR_STRICTATIME; + } +} +#endif /* HAVE_MOUNT_SETATTR */ + +/* + * Selectively update per-mount VFS flags for the changed namespace + * properties using mount_setattr(2). Unlike a full remount via mount(2), + * this only modifies the specified flags without resetting others -- + * avoiding clobbering temporary mount flags set by the administrator. + * + * For non-legacy datasets, the single known mountpoint is used. + * For legacy datasets, /proc/mounts is iterated since legacy datasets + * can be mounted at multiple locations. + * + * Falls back to a full remount via zfs_mount() when mount_setattr(2) + * is not available (ENOSYS), except for legacy mounts where a full + * remount would clobber temporary flags. + */ +int +zfs_mount_setattr(zfs_handle_t *zhp, uint32_t nspflags) +{ +#ifdef HAVE_MOUNT_SETATTR + struct mount_attr attr = { 0 }; + char mntpt_prop[ZFS_MAXPROPLEN]; + boolean_t legacy = B_FALSE; + libzfs_handle_t *hdl = zhp->zfs_hdl; + FILE *mnttab; + struct mnttab entry; + int ret = 0; + + if (!zfs_is_mountable_internal(zhp)) + return (0); + + zfs_add_options_setattr(zhp, &attr, nspflags); + if (attr.attr_set == 0 && attr.attr_clr == 0) + return (0); + + if (zfs_prop_valid_for_type(ZFS_PROP_MOUNTPOINT, zhp->zfs_type, + B_FALSE)) { + verify(zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mntpt_prop, + sizeof (mntpt_prop), NULL, NULL, 0, B_FALSE) == 0); + legacy = (strcmp(mntpt_prop, ZFS_MOUNTPOINT_LEGACY) == 0); + } + + if (!legacy) { + char *mntpt = NULL; + + if (!zfs_is_mounted(zhp, &mntpt)) + return (0); + + ret = mount_setattr(AT_FDCWD, mntpt, 0, + &attr, sizeof (attr)); + free(mntpt); + if (ret != 0) { + if (errno == ENOSYS) + return (zfs_mount(zhp, MNTOPT_REMOUNT, 0)); + return (zfs_error_fmt(hdl, EZFS_MOUNTFAILED, + dgettext(TEXT_DOMAIN, "cannot set mount " + "attributes for '%s'"), zhp->zfs_name)); + } + return (0); + } + + /* Legacy: iterate /proc/mounts for all mountpoints. */ + if ((mnttab = fopen(MNTTAB, "re")) == NULL) + return (0); + + while (getmntent(mnttab, &entry) == 0) { + if (strcmp(entry.mnt_fstype, MNTTYPE_ZFS) != 0) + continue; + if (strcmp(entry.mnt_special, zhp->zfs_name) != 0) + continue; + + ret = mount_setattr(AT_FDCWD, entry.mnt_mountp, 0, + &attr, sizeof (attr)); + if (ret != 0) { + if (errno == ENOSYS) { + ret = 0; + break; + } + ret = zfs_error_fmt(hdl, EZFS_MOUNTFAILED, + dgettext(TEXT_DOMAIN, "cannot set mount " + "attributes for '%s'"), zhp->zfs_name); + break; + } + } + + (void) fclose(mnttab); + return (ret); +#else + (void) nspflags; + return (zfs_mount(zhp, MNTOPT_REMOUNT, 0)); +#endif +} + int zfs_mount_delegation_check(void) { diff --git a/sys/contrib/openzfs/lib/libzfs/os/linux/libzfs_pool_os.c b/sys/contrib/openzfs/lib/libzfs/os/linux/libzfs_pool_os.c index aef169a3f880..f87cca4d09f4 100644 --- a/sys/contrib/openzfs/lib/libzfs/os/linux/libzfs_pool_os.c +++ b/sys/contrib/openzfs/lib/libzfs/os/linux/libzfs_pool_os.c @@ -264,10 +264,25 @@ zpool_label_disk(libzfs_handle_t *hdl, zpool_handle_t *zhp, const char *name) return (zfs_error(hdl, EZFS_NOCAP, errbuf)); } + /* + * The disks of the same capacity may have different sector sizes + * (512 or 4K). So to have the same start_block and slice_size on + * such a disks, divide NEW_START_BLOCK and EFI_MIN_RESV_SIZE by + * (efi_lbasize / DEV_BSIZE) coefficient. + */ + uint64_t coeff = vtoc->efi_lbasize / DEV_BSIZE; + + /* This probably should never be the case, but who knows. */ + if (((NEW_START_BLOCK * DEV_BSIZE) % vtoc->efi_lbasize) || + ((EFI_MIN_RESV_SIZE * DEV_BSIZE) % vtoc->efi_lbasize)) + coeff = 1; + slice_size = vtoc->efi_last_u_lba + 1; - slice_size -= EFI_MIN_RESV_SIZE; + slice_size -= (EFI_MIN_RESV_SIZE / coeff); if (start_block == MAXOFFSET_T) start_block = NEW_START_BLOCK; + if (start_block == NEW_START_BLOCK) + start_block /= coeff; slice_size -= start_block; slice_size = P2ALIGN_TYPED(slice_size, PARTITION_END_ALIGNMENT, uint64_t); @@ -298,7 +313,7 @@ zpool_label_disk(libzfs_handle_t *hdl, zpool_handle_t *zhp, const char *name) zpool_label_name(vtoc->efi_parts[0].p_name, EFI_PART_NAME_LEN); vtoc->efi_parts[8].p_start = slice_size + start_block; - vtoc->efi_parts[8].p_size = resv; + vtoc->efi_parts[8].p_size = resv / coeff; vtoc->efi_parts[8].p_tag = V_RESERVED; rval = efi_write(fd, vtoc); diff --git a/sys/contrib/openzfs/lib/libzfs/os/linux/libzfs_share_nfs.c b/sys/contrib/openzfs/lib/libzfs/os/linux/libzfs_share_nfs.c new file mode 100644 index 000000000000..7644e9b89fa7 --- /dev/null +++ b/sys/contrib/openzfs/lib/libzfs/os/linux/libzfs_share_nfs.c @@ -0,0 +1,585 @@ +// SPDX-License-Identifier: CDDL-1.0 +/* + * 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 https://opensource.org/licenses/CDDL-1.0. + * 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) 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, 2022 by Delphix. All rights reserved. + */ + +#include <dirent.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/file.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <libzfs.h> +#include "../../libzfs_impl.h" + +#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 boolean_t nfs_available(void); +static boolean_t exports_available(void); + +typedef int (*nfs_shareopt_callback_t)(const char *opt, const char *value, + void *cookie); + +typedef int (*nfs_host_callback_t)(FILE *tmpfile, const char *sharepath, + const char *host, const char *security, const char *access, void *cookie); + +/* + * Invokes the specified callback function for each Solaris share option + * listed in the specified string. + */ +static int +foreach_nfs_shareopt(const char *shareopts, + nfs_shareopt_callback_t callback, void *cookie) +{ + char *shareopts_dup, *opt, *cur, *value; + 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); + + opt = shareopts_dup; + was_nul = 0; + + while (1) { + cur = opt; + + while (*cur != ',' && *cur != '\0') + cur++; + + if (*cur == '\0') + was_nul = 1; + + *cur = '\0'; + + if (cur > opt) { + value = strchr(opt, '='); + + if (value != NULL) { + *value = '\0'; + value++; + } + + error = callback(opt, value, cookie); + + if (error != SA_OK) { + free(shareopts_dup); + return (error); + } + } + + opt = cur + 1; + + if (was_nul) + break; + } + + free(shareopts_dup); + + return (SA_OK); +} + +typedef struct nfs_host_cookie_s { + nfs_host_callback_t callback; + const char *sharepath; + void *cookie; + FILE *tmpfile; + const char *security; +} nfs_host_cookie_t; + +/* + * Helper function for foreach_nfs_host. This function checks whether the + * current share option is a host specification and invokes a callback + * function with information about the host. + */ +static int +foreach_nfs_host_cb(const char *opt, const char *value, void *pcookie) +{ + int error; + const char *access; + char *host_dup, *host, *next, *v6Literal; + nfs_host_cookie_t *udata = (nfs_host_cookie_t *)pcookie; + int cidr_len; + +#ifdef DEBUG + fprintf(stderr, "foreach_nfs_host_cb: key=%s, value=%s\n", opt, value); +#endif + + if (strcmp(opt, "sec") == 0) + udata->security = value; + + if (strcmp(opt, "rw") == 0 || strcmp(opt, "ro") == 0) { + if (value == NULL) + value = "*"; + + access = opt; + + host_dup = strdup(value); + + if (host_dup == NULL) + return (SA_NO_MEMORY); + + host = host_dup; + + do { + if (*host == '[') { + host++; + v6Literal = strchr(host, ']'); + if (v6Literal == NULL) { + free(host_dup); + return (SA_SYNTAX_ERR); + } + if (v6Literal[1] == '\0') { + *v6Literal = '\0'; + next = NULL; + } else if (v6Literal[1] == '/') { + next = strchr(v6Literal + 2, ':'); + if (next == NULL) { + cidr_len = + strlen(v6Literal + 1); + memmove(v6Literal, + v6Literal + 1, + cidr_len); + v6Literal[cidr_len] = '\0'; + } else { + cidr_len = next - v6Literal - 1; + memmove(v6Literal, + v6Literal + 1, + cidr_len); + v6Literal[cidr_len] = '\0'; + next++; + } + } else if (v6Literal[1] == ':') { + *v6Literal = '\0'; + next = v6Literal + 2; + } else { + free(host_dup); + return (SA_SYNTAX_ERR); + } + } else { + next = strchr(host, ':'); + if (next != NULL) { + *next = '\0'; + next++; + } + } + + error = udata->callback(udata->tmpfile, + udata->sharepath, host, udata->security, + access, udata->cookie); + + if (error != SA_OK) { + free(host_dup); + + return (error); + } + + host = next; + } while (host != NULL); + + free(host_dup); + } + + return (SA_OK); +} + +/* + * 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, FILE *tmpfile, + nfs_host_callback_t callback, void *cookie) +{ + nfs_host_cookie_t udata; + + udata.callback = callback; + udata.sharepath = impl_share->sa_mountpoint; + udata.cookie = cookie; + udata.tmpfile = tmpfile; + udata.security = "sys"; + + return (foreach_nfs_shareopt(impl_share->sa_shareopts, + foreach_nfs_host_cb, &udata)); +} + +/* + * Converts a Solaris NFS host specification to its Linux equivalent. + */ +static const char * +get_linux_hostspec(const char *solaris_hostspec) +{ + /* + * For now we just support CIDR masks (e.g. @192.168.0.0/16) and host + * wildcards (e.g. *.example.org). + */ + if (solaris_hostspec[0] == '@') { + /* + * Solaris host specifier, e.g. @192.168.0.0/16; we just need + * to skip the @ in this case + */ + return (solaris_hostspec + 1); + } else { + return (solaris_hostspec); + } +} + +/* + * Adds a Linux share option to an array of NFS options. + */ +static int +add_linux_shareopt(char **plinux_opts, const char *key, const char *value) +{ + size_t len = 0; + char *new_linux_opts; + + if (*plinux_opts != NULL) + len = strlen(*plinux_opts); + + new_linux_opts = realloc(*plinux_opts, len + 1 + strlen(key) + + (value ? 1 + strlen(value) : 0) + 1); + + if (new_linux_opts == NULL) + return (SA_NO_MEMORY); + + new_linux_opts[len] = '\0'; + + if (len > 0) + strcat(new_linux_opts, ","); + + strcat(new_linux_opts, key); + + if (value != NULL) { + strcat(new_linux_opts, "="); + strcat(new_linux_opts, value); + } + + *plinux_opts = new_linux_opts; + + return (SA_OK); +} + +static int string_cmp(const void *lhs, const void *rhs) { + const char *const *l = lhs, *const *r = rhs; + return (strcmp(*l, *r)); +} + +/* + * Validates and converts a single Solaris share option to its Linux + * equivalent. + */ +static int +get_linux_shareopts_cb(const char *key, const char *value, void *cookie) +{ + /* This list must remain sorted, since we bsearch() it */ + static const char *const valid_keys[] = { "all_squash", "anongid", + "anonuid", "async", "auth_nlm", "crossmnt", "fsid", "fsuid", "hide", + "insecure", "insecure_locks", "mountpoint", "mp", "no_acl", + "no_all_squash", "no_auth_nlm", "no_root_squash", + "no_subtree_check", "no_wdelay", "nohide", "refer", "replicas", + "root_squash", "secure", "secure_locks", "subtree_check", "sync", + "wdelay" }; + + char **plinux_opts = (char **)cookie; + char *host, *val_dup, *literal, *next; + + if (strcmp(key, "sec") == 0) + return (SA_OK); + + if (strcmp(key, "ro") == 0 || strcmp(key, "rw") == 0) { + if (value == NULL || strlen(value) == 0) + return (SA_OK); + val_dup = strdup(value); + host = val_dup; + if (host == NULL) + return (SA_NO_MEMORY); + do { + if (*host == '[') { + host++; + literal = strchr(host, ']'); + if (literal == NULL) { + free(val_dup); + return (SA_SYNTAX_ERR); + } + if (literal[1] == '\0') + next = NULL; + else if (literal[1] == '/') { + next = strchr(literal + 2, ':'); + if (next != NULL) + ++next; + } else if (literal[1] == ':') + next = literal + 2; + else { + free(val_dup); + return (SA_SYNTAX_ERR); + } + } else { + next = strchr(host, ':'); + if (next != NULL) + ++next; + } + host = next; + } while (host != NULL); + free(val_dup); + return (SA_OK); + } + + if (strcmp(key, "anon") == 0) + key = "anonuid"; + + if (strcmp(key, "root_mapping") == 0) { + (void) add_linux_shareopt(plinux_opts, "root_squash", NULL); + key = "anonuid"; + } + + if (strcmp(key, "nosub") == 0) + key = "subtree_check"; + + if (bsearch(&key, valid_keys, ARRAY_SIZE(valid_keys), + sizeof (*valid_keys), string_cmp) == NULL) + return (SA_SYNTAX_ERR); + + (void) add_linux_shareopt(plinux_opts, key, value); + + return (SA_OK); +} + +/* + * Takes a string containing Solaris share options (e.g. "sync,no_acl") and + * converts them to a NULL-terminated array of Linux NFS options. + */ +static int +get_linux_shareopts(const char *shareopts, char **plinux_opts) +{ + int error; + + assert(plinux_opts != NULL); + + *plinux_opts = NULL; + + /* no_subtree_check - Default as of nfs-utils v1.1.0 */ + (void) add_linux_shareopt(plinux_opts, "no_subtree_check", NULL); + + /* mountpoint - Restrict exports to ZFS mountpoints */ + (void) add_linux_shareopt(plinux_opts, "mountpoint", NULL); + + error = foreach_nfs_shareopt(shareopts, get_linux_shareopts_cb, + plinux_opts); + + if (error != SA_OK) { + free(*plinux_opts); + *plinux_opts = NULL; + } + + return (error); +} + +/* + * 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_add_entry(FILE *tmpfile, const char *sharepath, + const char *host, const char *security, const char *access_opts, + void *pcookie) +{ + const char *linux_opts = (const char *)pcookie; + + if (linux_opts == NULL) + linux_opts = ""; + + boolean_t need_free; + char *mp; + int rc = nfs_escape_mountpoint(sharepath, &mp, &need_free); + if (rc != SA_OK) + return (rc); + if (fprintf(tmpfile, "%s %s(sec=%s,%s,%s)\n", mp, + get_linux_hostspec(host), security, access_opts, + linux_opts) < 0) { + fprintf(stderr, "failed to write to temporary file\n"); + rc = SA_SYSTEM_ERR; + } + + if (need_free) + free(mp); + return (rc); +} + +/* + * Enables NFS sharing for the specified share. + */ +static int +nfs_enable_share_impl(sa_share_impl_t impl_share, FILE *tmpfile) +{ + char *linux_opts = NULL; + int error = get_linux_shareopts(impl_share->sa_shareopts, &linux_opts); + if (error != SA_OK) + return (error); + + error = foreach_nfs_host(impl_share, tmpfile, nfs_add_entry, + linux_opts); + free(linux_opts); + return (error); +} + +static int +nfs_enable_share(sa_share_impl_t impl_share) +{ + if (!nfs_available()) + return (SA_SYSTEM_ERR); + + return (nfs_toggle_share( + ZFS_EXPORTS_LOCK, ZFS_EXPORTS_FILE, ZFS_EXPORTS_DIR, impl_share, + nfs_enable_share_impl)); +} + +/* + * Disables NFS sharing for the specified share. + */ +static int +nfs_disable_share_impl(sa_share_impl_t impl_share, FILE *tmpfile) +{ + (void) impl_share, (void) tmpfile; + return (SA_OK); +} + +static int +nfs_disable_share(sa_share_impl_t impl_share) +{ + if (!nfs_available()) + return (SA_OK); + + return (nfs_toggle_share( + ZFS_EXPORTS_LOCK, ZFS_EXPORTS_FILE, ZFS_EXPORTS_DIR, impl_share, + nfs_disable_share_impl)); +} + +static boolean_t +nfs_is_shared(sa_share_impl_t impl_share) +{ + if (!nfs_available()) + return (SA_SYSTEM_ERR); + + return (nfs_is_shared_impl(ZFS_EXPORTS_FILE, impl_share)); +} + +/* + * Checks whether the specified NFS share options are syntactically correct. + */ +static int +nfs_validate_shareopts(const char *shareopts) +{ + char *linux_opts = NULL; + + if (strlen(shareopts) == 0) + return (SA_SYNTAX_ERR); + + int error = get_linux_shareopts(shareopts, &linux_opts); + if (error != SA_OK) + return (error); + + free(linux_opts); + return (SA_OK); +} + +static int +nfs_commit_shares(void) +{ + if (!nfs_available()) + return (SA_SYSTEM_ERR); + + char *argv[] = { + (char *)"/usr/sbin/exportfs", + (char *)"-ra", + NULL + }; + + return (libzfs_run_process(argv[0], argv, 0)); +} + +static void +nfs_truncate_shares(void) +{ + if (!exports_available()) + return; + nfs_reset_shares(ZFS_EXPORTS_LOCK, ZFS_EXPORTS_FILE); +} + +const sa_fstype_t libshare_nfs_type = { + .enable_share = nfs_enable_share, + .disable_share = nfs_disable_share, + .is_shared = nfs_is_shared, + + .validate_shareopts = nfs_validate_shareopts, + .commit_shares = nfs_commit_shares, + .truncate_shares = nfs_truncate_shares, +}; + +static boolean_t +nfs_available(void) +{ + static int avail; + + if (!avail) { + if (access("/usr/sbin/exportfs", F_OK) != 0) + avail = -1; + else + avail = 1; + } + + return (avail == 1); +} + +static boolean_t +exports_available(void) +{ + static int avail; + + if (!avail) { + if (access(ZFS_EXPORTS_DIR, F_OK) != 0) + avail = -1; + else + avail = 1; + } + + return (avail == 1); +} diff --git a/sys/contrib/openzfs/lib/libzfs/os/linux/libzfs_share_smb.c b/sys/contrib/openzfs/lib/libzfs/os/linux/libzfs_share_smb.c new file mode 100644 index 000000000000..4d2961b83bde --- /dev/null +++ b/sys/contrib/openzfs/lib/libzfs/os/linux/libzfs_share_smb.c @@ -0,0 +1,405 @@ +// SPDX-License-Identifier: CDDL-1.0 +/* + * 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 https://opensource.org/licenses/CDDL-1.0. + * 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) 2002, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011,2012 Turbo Fredriksson <turbo@bayour.com>, 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. + * + * TESTING + * Make sure that samba listens to 'localhost' (127.0.0.1) and that the options + * 'usershare max shares' and 'usershare owner only' have been reviewed/set + * accordingly (see zfs(8) for information). + * + * Once configuration in samba have been done, test that this + * works with the following three commands (in this case, my ZFS + * filesystem is called 'share/Test1'): + * + * (root)# net -U root -S 127.0.0.1 usershare add Test1 /share/Test1 \ + * "Comment: /share/Test1" "Everyone:F" + * (root)# net usershare list | grep -i test + * (root)# net -U root -S 127.0.0.1 usershare delete Test1 + * + * The first command will create a user share that gives everyone full access. + * To limit the access below that, use normal UNIX commands (chmod, chown etc). + */ + +#include <time.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.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 "../../libzfs_share.h" + +static boolean_t smb_available(void); + +static 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. + */ +static int +smb_retrieve_shares(void) +{ + int rc = SA_OK; + char file_path[PATH_MAX], line[512], *token, *key, *value; + char *dup_value = NULL, *path = NULL, *comment = NULL, *name = NULL; + char *guest_ok = NULL; + DIR *shares_dir; + FILE *share_file_fp = NULL; + struct dirent *directory; + struct stat eStat; + smb_share_t *shares, *new_shares = NULL; + + /* opendir(), stat() */ + shares_dir = opendir(SMB_SHARE_DIR); + if (shares_dir == NULL) + return (SA_SYSTEM_ERR); + + /* Go through the directory, looking for shares */ + while ((directory = readdir(shares_dir))) { + int fd; + + if (directory->d_name[0] == '.') + continue; + + snprintf(file_path, sizeof (file_path), + "%s/%s", SMB_SHARE_DIR, directory->d_name); + + if ((fd = open(file_path, O_RDONLY | O_CLOEXEC)) == -1) { + rc = SA_SYSTEM_ERR; + goto out; + } + + if (fstat(fd, &eStat) == -1) { + close(fd); + rc = SA_SYSTEM_ERR; + goto out; + } + + if (!S_ISREG(eStat.st_mode)) { + close(fd); + continue; + } + + if ((share_file_fp = fdopen(fd, "r")) == NULL) { + close(fd); + rc = SA_SYSTEM_ERR; + goto out; + } + + name = strdup(directory->d_name); + if (name == NULL) { + rc = SA_NO_MEMORY; + goto out; + } + + while (fgets(line, sizeof (line), share_file_fp)) { + if (line[0] == '#') + continue; + + /* Trim trailing new-line character(s). */ + while (line[strlen(line) - 1] == '\r' || + line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = '\0'; + + /* Split the line in two, separated by '=' */ + token = strchr(line, '='); + if (token == NULL) + continue; + + key = line; + value = token + 1; + *token = '\0'; + + dup_value = strdup(value); + if (dup_value == NULL) { + rc = SA_NO_MEMORY; + goto out; + } + + if (strcmp(key, "path") == 0) { + free(path); + path = dup_value; + } else if (strcmp(key, "comment") == 0) { + free(comment); + comment = dup_value; + } else if (strcmp(key, "guest_ok") == 0) { + free(guest_ok); + guest_ok = dup_value; + } else + free(dup_value); + + dup_value = NULL; + + if (path == NULL || comment == NULL || guest_ok == NULL) + continue; /* Incomplete share definition */ + else { + shares = (smb_share_t *) + malloc(sizeof (smb_share_t)); + if (shares == NULL) { + rc = SA_NO_MEMORY; + goto out; + } + + (void) strlcpy(shares->name, name, + sizeof (shares->name)); + + (void) strlcpy(shares->path, path, + sizeof (shares->path)); + + (void) strlcpy(shares->comment, comment, + sizeof (shares->comment)); + + shares->guest_ok = atoi(guest_ok); + + shares->next = new_shares; + new_shares = shares; + + free(path); + free(comment); + free(guest_ok); + + path = NULL; + comment = NULL; + guest_ok = NULL; + } + } + +out: + if (share_file_fp != NULL) { + fclose(share_file_fp); + share_file_fp = NULL; + } + + free(name); + free(path); + free(comment); + free(guest_ok); + + name = NULL; + path = NULL; + comment = NULL; + guest_ok = NULL; + } + closedir(shares_dir); + + smb_shares = new_shares; + + return (rc); +} + +/* + * Used internally by smb_enable_share to enable sharing for a single host. + */ +static int +smb_enable_share_one(const char *sharename, const char *sharepath) +{ + char name[SMB_NAME_MAX], comment[SMB_COMMENT_MAX]; + + /* Support ZFS share name regexp '[[:alnum:]_-.: ]' */ + strlcpy(name, sharename, sizeof (name)); + for (char *itr = name; *itr != '\0'; ++itr) + switch (*itr) { + case '/': + case '-': + case ':': + case ' ': + *itr = '_'; + } + + /* + * CMD: net -S SMB_NET_CMD_ARG_HOST usershare add Test1 /share/Test1 \ + * "Comment" "Everyone:F" + */ + snprintf(comment, sizeof (comment), "Comment: %s", sharepath); + + char *argv[] = { + (char *)SMB_NET_CMD_PATH, + (char *)"-S", + (char *)SMB_NET_CMD_ARG_HOST, + (char *)"usershare", + (char *)"add", + name, + (char *)sharepath, + comment, + (char *)"Everyone:F", + NULL, + }; + + if (libzfs_run_process(argv[0], argv, 0) != 0) + return (SA_SYSTEM_ERR); + + /* Reload the share file */ + (void) smb_retrieve_shares(); + + return (SA_OK); +} + +/* + * Enables SMB sharing for the specified share. + */ +static int +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); + + if (impl_share->sa_shareopts == NULL) /* on/off */ + return (SA_SYSTEM_ERR); + + if (strcmp(impl_share->sa_shareopts, "off") == 0) + return (SA_OK); + + /* Magic: Enable (i.e., 'create new') share */ + return (smb_enable_share_one(impl_share->sa_zfsname, + impl_share->sa_mountpoint)); +} + +/* + * Used internally by smb_disable_share to disable sharing for a single host. + */ +static int +smb_disable_share_one(const char *sharename) +{ + /* CMD: net -S SMB_NET_CMD_ARG_HOST usershare delete Test1 */ + char *argv[] = { + (char *)SMB_NET_CMD_PATH, + (char *)"-S", + (char *)SMB_NET_CMD_ARG_HOST, + (char *)"usershare", + (char *)"delete", + (char *)sharename, + NULL, + }; + + if (libzfs_run_process(argv[0], argv, 0) != 0) + return (SA_SYSTEM_ERR); + else + return (SA_OK); +} + +/* + * Disables SMB sharing for the specified share. + */ +static int +smb_disable_share(sa_share_impl_t impl_share) +{ + if (!smb_available()) { + /* + * The share can't possibly be active, so nothing + * needs to be done to disable it. + */ + return (SA_OK); + } + + for (const smb_share_t *i = smb_shares; i != NULL; i = i->next) + if (strcmp(impl_share->sa_mountpoint, i->path) == 0) + return (smb_disable_share_one(i->name)); + + return (SA_OK); +} + +/* + * Checks whether the specified SMB share options are syntactically correct. + */ +static int +smb_validate_shareopts(const char *shareopts) +{ + /* TODO: Accept 'name' and sec/acl (?) */ + if ((strcmp(shareopts, "off") == 0) || (strcmp(shareopts, "on") == 0)) + return (SA_OK); + + return (SA_SYNTAX_ERR); +} + +/* + * Checks whether a share is currently active. + */ +static boolean_t +smb_is_share_active(sa_share_impl_t impl_share) +{ + if (!smb_available()) + return (B_FALSE); + + /* Retrieve the list of (possible) active shares */ + smb_retrieve_shares(); + + for (const smb_share_t *i = smb_shares; i != NULL; i = i->next) + if (strcmp(impl_share->sa_mountpoint, i->path) == 0) + return (B_TRUE); + + return (B_FALSE); +} + +static int +smb_update_shares(void) +{ + /* Not implemented */ + return (0); +} + +const sa_fstype_t libshare_smb_type = { + .enable_share = smb_enable_share, + .disable_share = smb_disable_share, + .is_shared = smb_is_share_active, + + .validate_shareopts = smb_validate_shareopts, + .commit_shares = smb_update_shares, +}; + +/* + * Provides a convenient wrapper for determining SMB availability + */ +static boolean_t +smb_available(void) +{ + static int avail; + + if (!avail) { + struct stat statbuf; + + if (access(SMB_NET_CMD_PATH, F_OK) != 0 || + lstat(SMB_SHARE_DIR, &statbuf) != 0 || + !S_ISDIR(statbuf.st_mode)) + avail = -1; + else + avail = 1; + } + + return (avail == 1); +} |
