/*
* 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) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
*/
#include <strings.h>
#include <dirent.h>
#include <libnvpair.h>
#include <libzfs.h>
#include <errno.h>
#include <fcntl.h>
#include <note.h>
#include <unistd.h>
#include <synch.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <libshare.h>
#include <libshare_impl.h>
static int sa_zfs_init(void);
static void sa_zfs_fini(void);
static void *sa_zfs_open(libshare_handle_t *);
static void sa_zfs_close(void *);
static void sa_zfs_reset(libshare_handle_t *);
static int sa_zfs_share_write(libshare_handle_t *, nvlist_t *, boolean_t);
static int sa_zfs_share_read(libshare_handle_t *, const char *, const char *,
nvlist_t **);
static int sa_zfs_share_read_init(libshare_handle_t *, sa_read_hdl_t *);
static int sa_zfs_share_read_next(libshare_handle_t *, sa_read_hdl_t *,
nvlist_t **);
static int sa_zfs_share_read_fini(libshare_handle_t *, sa_read_hdl_t *);
static int sa_zfs_share_remove(libshare_handle_t *, const char *,
const char *, boolean_t);
static int sa_zfs_share_get_acl(libshare_handle_t *, const char *,
const char *, acl_t **);
static int sa_zfs_share_set_acl(libshare_handle_t *, const char *,
const char *, acl_t *);
static int sa_zfs_get_mntpnt_for_path(libshare_handle_t *, const char *,
char *, size_t, char *, size_t, char *, size_t);
static int sa_zfs_sharing_enabled(libshare_handle_t *, const char *,
const char *, sa_proto_t *);
static int sa_zfs_sharing_get_prop(libshare_handle_t *, const char *,
const char *, sa_proto_t, char **);
static int sa_zfs_sharing_set_prop(libshare_handle_t *, const char *,
const char *, sa_proto_t, const char *);
static int sa_zfs_is_legacy(libshare_handle_t *, const char *,
boolean_t *);
static int sa_zfs_is_zoned(libshare_handle_t *, const char *, boolean_t *);
static void sa_zfs_share_notify(libshare_handle_t *, const char *,
sa_notify_op_t, nvlist_t *);
sa_fs_ops_t sa_plugin_ops = {
.saf_hdr = {
.pi_ptype = SA_PLUGIN_FS,
.pi_type = SA_FS_ZFS,
.pi_name = "ZFS",
.pi_version = SA_LIBSHARE_VERSION,
.pi_flags = 0,
.pi_init = sa_zfs_init,
.pi_fini = sa_zfs_fini
},
.saf_open = sa_zfs_open,
.saf_close = sa_zfs_close,
.saf_reset = sa_zfs_reset,
.saf_share_write = sa_zfs_share_write,
.saf_share_read = sa_zfs_share_read,
.saf_share_read_init = sa_zfs_share_read_init,
.saf_share_read_next = sa_zfs_share_read_next,
.saf_share_read_fini = sa_zfs_share_read_fini,
.saf_share_remove = sa_zfs_share_remove,
.saf_share_get_acl = sa_zfs_share_get_acl,
.saf_share_set_acl = sa_zfs_share_set_acl,
.saf_get_mntpnt_for_path = sa_zfs_get_mntpnt_for_path,
.saf_sharing_enabled = sa_zfs_sharing_enabled,
.saf_sharing_get_prop = sa_zfs_sharing_get_prop,
.saf_sharing_set_prop = sa_zfs_sharing_set_prop,
.saf_is_legacy = sa_zfs_is_legacy,
.saf_is_zoned = sa_zfs_is_zoned,
.saf_share_notify = sa_zfs_share_notify,
};
static int
sa_zfs_init(void)
{
return (SA_OK);
}
static void
sa_zfs_fini(void)
{
/* do nothing */
}
static void *
sa_zfs_open(libshare_handle_t *shdl)
{
libzfs_handle_t *hdl;
if ((hdl = libzfs_init()) == NULL) {
salog_error(0, "libshare_zfs: failed to open libzfs handle");
return (NULL);
}
libzfs_set_libshare(hdl, shdl);
return (hdl);
}
static void
sa_zfs_close(void *zfs_hdl)
{
libzfs_fini(zfs_hdl);
}
static void
sa_zfs_reset(libshare_handle_t *shdl)
{
libzfs_handle_t *zfs_hdl;
int rc;
if ((zfs_hdl = sa_get_fs_handle(shdl, SA_FS_ZFS, &rc)) == NULL) {
salog_error(rc, "libshare_zfs");
return;
}
libzfs_cache_clear(zfs_hdl);
}
/*
* sazfs_add_mntpnt
*
* Add the mountpoint to the share path.
* This is called after the share has been read from disk
* since share paths are stored relative to the mountpoint
* in case the dataset mountpoint is modified after the
* share has been created.
*
* The mountpoint itself is also added to the share in the
* SA_PROP_MNTPNT ("mntpnt") property.
*/
static int
sazfs_add_mntpnt(nvlist_t *share, const char *mntpnt)
{
int rc;
char *sh_path;
char new_path[MAXPATHLEN];
size_t len;
/*
* If the expected mntpnt property is already there, assume that the
* filesystem plugin also set the expected absolute share path.
*/
if (sa_share_get_mntpnt(share) != NULL)
return (SA_OK);
if ((sh_path = sa_share_get_path(share)) == NULL)
return (SA_NO_SHARE_PATH);
len = strlcpy(new_path, mntpnt, MAXPATHLEN);
if (new_path[len - 1] == '/' && len > 1) {
new_path[--len] = '\0';
}
/*
* add mountpoint property to share
*/
if ((rc = sa_share_set_mntpnt(share, new_path)) != SA_OK)
return (rc);
/*
* store the full path to share
*/
new_path[len++] = '/';
(void) strlcpy(new_path + len, sh_path, MAXPATHLEN - len);
(void) sa_fixup_path(new_path);
if ((rc = sa_share_set_path(share, new_path)) != SA_OK)
return (rc);
return (SA_OK);
}
static int
sa_zfs_share_write(libshare_handle_t *shdl, nvlist_t *share, boolean_t persist)
{
libzfs_handle_t *zfs_hdl;
int rc;
if ((zfs_hdl = sa_get_fs_handle(shdl, SA_FS_ZFS, &rc)) == NULL)
return (rc);
rc = zfssa_share_storage_write(zfs_hdl, share, persist);
return (rc);
}
static int
sa_zfs_share_read(libshare_handle_t *shdl, const char *mntpnt,
const char *sh_name, nvlist_t **sharep)
{
libzfs_handle_t *zfs_hdl;
int rc;
if ((zfs_hdl = sa_get_fs_handle(shdl, SA_FS_ZFS, &rc)) == NULL)
return (rc);
rc = zfssa_share_storage_read(zfs_hdl, mntpnt, sh_name, sharep);
if (rc != 0) {
if (rc != SA_SHARE_NOT_FOUND)
salog_error(rc, "libshare_zfs: %s", mntpnt);
return (rc);
}
/*
* shares are stored with paths relative to the mountpoint.
* So prefix mountpoint to beginning of share path and update share.
*/
if ((rc = sazfs_add_mntpnt(*sharep, mntpnt)) != SA_OK) {
salog_error(rc, "libshare_zfs: error adding mntpnt to share %s",
sh_name);
return (rc);
}
return (rc);
}
static int
sa_zfs_share_read_init(libshare_handle_t *shdl, sa_read_hdl_t *srhp)
{
libzfs_handle_t *zfs_hdl;
int rc;
/* Note: we are only called for fs's in current zone */
if ((zfs_hdl = sa_get_fs_handle(shdl, SA_FS_ZFS, &rc)) == NULL)
return (rc);
rc = zfssa_share_storage_read_init(zfs_hdl, srhp->srh_mntpnt,
srhp->srh_include_invalid_shares, &srhp->srh_handle);
if (rc != 0) {
salog_error(rc, "libshare_zfs: "
"failed to set up zfs_share_read_init for %s",
srhp->srh_mntpnt);
return (SA_SHARE_NOT_FOUND);
}
return (SA_OK);
}
/* ARGSUSED */
static int
sa_zfs_share_read_next(libshare_handle_t *shdl, sa_read_hdl_t *srhp,
nvlist_t **sharep)
{
int rc;
if (srhp == NULL || srhp->srh_mntpnt == NULL ||
srhp->srh_handle == NULL) {
rc = SA_INVALID_READ_HDL;
salog_error(rc, "libshare_zfs");
return (rc);
}
rc = zfssa_share_storage_read_next(srhp->srh_handle, sharep);
/*
* shares are stored with paths relative to the mountpoint.
* So prefix mountpoint to beginning of share path and update share.
*/
if (rc == 0 &&
(rc = sazfs_add_mntpnt(*sharep, srhp->srh_mntpnt)) != SA_OK) {
char *sh_name = sa_share_get_name(*sharep);
nvlist_free(*sharep);
*sharep = NULL;
salog_error(rc, "libshare_zfs: error adding mntpnt to share %s",
sh_name == NULL ? "<invalid share>" : sh_name);
}
return (rc);
}
/* ARGSUSED */
static int
sa_zfs_share_read_fini(libshare_handle_t *shdl, sa_read_hdl_t *srhp)
{
if (srhp == NULL)
return (SA_OK);
if (srhp->srh_handle != NULL) {
(void) zfssa_share_storage_read_fini(srhp->srh_handle);
srhp->srh_handle = NULL;
}
return (SA_OK);
}
static int
sa_zfs_share_remove(libshare_handle_t *shdl, const char *fs_name,
const char *sh_name, boolean_t persist)
{
libzfs_handle_t *zfs_hdl;
int rc;
char mntpnt[MAXPATHLEN];
char ds_name[MAXPATHLEN];
if ((zfs_hdl = sa_get_fs_handle(shdl, SA_FS_ZFS, &rc)) == NULL)
return (rc);
rc = zfs_path_to_mntpnt(zfs_hdl, fs_name, mntpnt, sizeof (mntpnt),
ds_name, sizeof (ds_name), NULL, 0);
if (rc != 0) {
rc = SA_MNTPNT_NOT_FOUND;
salog_error(rc, "libshare_zfs: %s", fs_name);
return (rc);
}
rc = zfs_share_resource_remove(zfs_hdl, ds_name, mntpnt,
(char *)sh_name, persist);
if (rc != 0) {
switch (errno) {
case EPERM:
return (SA_NO_PERMISSION);
case ENOENT:
break;
default:
salog_error(0, "libshare_zfs: "
"error removing share file for %s: %s",
sh_name, strerror(errno));
return (SA_SYSTEM_ERR);
}
}
return (SA_OK);
}
static int
sa_zfs_share_get_acl(libshare_handle_t *shdl, const char *sh_name,
const char *sh_path, acl_t **aclp)
{
libzfs_handle_t *zfs_hdl;
int rc;
char *sh_fname = NULL;
char mntpnt[MAXPATHLEN];
if ((zfs_hdl = sa_get_fs_handle(shdl, SA_FS_ZFS, &rc)) == NULL)
return (rc);
rc = zfs_path_to_mntpnt(zfs_hdl, sh_path, mntpnt, sizeof (mntpnt),
NULL, 0, NULL, 0);
if (rc != 0) {
rc = SA_MNTPNT_NOT_FOUND;
salog_error(rc, "libshare_zfs: %s", sh_path);
return (rc);
}
if ((sh_fname = make_share_filename(mntpnt, sh_name)) == NULL) {
rc = SA_NO_MEMORY;
salog_error(rc, "libshare_zfs: %s", sh_name);
return (rc);
}
rc = acl_get(sh_fname, 0, aclp);
if (rc != 0) {
switch (errno) {
case EACCES:
rc = SA_NO_PERMISSION;
break;
case ENOENT:
rc = SA_INVALID_SHARE_PATH;
break;
case ENOTSUP:
rc = SA_NOT_SUPPORTED;
break;
case EIO:
case ENOSYS:
default:
rc = SA_SYSTEM_ERR;
break;
}
} else {
rc = SA_OK;
}
if (sh_fname != NULL)
free(sh_fname);
return (rc);
}
static int
sa_zfs_share_set_acl(libshare_handle_t *shdl, const char *sh_name,
const char *sh_path, acl_t *acl)
{
libzfs_handle_t *zfs_hdl;
int rc;
char *sh_fname = NULL;
char mntpnt[MAXPATHLEN];
if ((zfs_hdl = sa_get_fs_handle(shdl, SA_FS_ZFS, &rc)) == NULL)
return (rc);
rc = zfs_path_to_mntpnt(zfs_hdl, sh_path, mntpnt, sizeof (mntpnt),
NULL, 0, NULL, 0);
if (rc != 0) {
rc = SA_MNTPNT_NOT_FOUND;
salog_error(rc, "libshare_zfs: %s", sh_path);
return (rc);
}
if ((sh_fname = make_share_filename(mntpnt, sh_name)) == NULL) {
rc = SA_NO_MEMORY;
salog_error(rc, "libshare_zfs: %s", sh_name);
return (rc);
}
rc = acl_set(sh_fname, acl);
if (rc != 0) {
switch (errno) {
case EACCES:
rc = SA_NO_PERMISSION;
break;
case ENOENT:
rc = SA_INVALID_SHARE_PATH;
break;
case ENOTSUP:
rc = SA_NOT_SUPPORTED;
break;
case EIO:
case ENOSYS:
default:
rc = SA_SYSTEM_ERR;
break;
}
} else {
rc = SA_OK;
}
if (sh_fname != NULL)
free(sh_fname);
return (rc);
}
/*
* Return the mountpoint and optionally the dataset name from a
* path.
*/
static int
sa_zfs_get_mntpnt_for_path(libshare_handle_t *shdl, const char *sh_path,
char *mntpnt, size_t mplen, char *dataset, size_t dslen, char *mntopt,
size_t optlen)
{
libzfs_handle_t *zfs_hdl;
int rc;
if ((zfs_hdl = sa_get_fs_handle(shdl, SA_FS_ZFS, &rc)) == NULL)
return (rc);
rc = zfs_path_to_mntpnt(zfs_hdl, sh_path, mntpnt, mplen,
dataset, dslen, mntopt, optlen);
if (rc != 0)
return (SA_MNTPNT_NOT_FOUND);
else
return (SA_OK);
}
static int
sa_zfs_sharing_enabled(libshare_handle_t *shdl, const char *sh_path,
const char *sh_name, sa_proto_t *protos)
{
libzfs_handle_t *zfs_hdl;
int rc;
*protos = SA_PROT_NONE;
if ((zfs_hdl = sa_get_fs_handle(shdl, SA_FS_ZFS, &rc)) == NULL)
return (rc);
return (zfssa_is_sharing_enabled_share(zfs_hdl, sh_path, sh_name,
protos));
}
static int
sa_zfs_sharing_get_prop(libshare_handle_t *shdl, const char *sh_path,
const char *sh_name, sa_proto_t proto, char **props)
{
libzfs_handle_t *zfs_hdl;
int rc;
if ((zfs_hdl = sa_get_fs_handle(shdl, SA_FS_ZFS, &rc)) == NULL)
return (rc);
return (zfssa_sharing_get_prop(zfs_hdl, sh_path, sh_name, proto,
props));
}
static int
sa_zfs_sharing_set_prop(libshare_handle_t *shdl, const char *mntpnt,
const char *sh_name, sa_proto_t proto, const char *props)
{
libzfs_handle_t *zfs_hdl;
int rc;
if ((zfs_hdl = sa_get_fs_handle(shdl, SA_FS_ZFS, &rc)) == NULL)
return (rc);
return (zfssa_sharing_set_prop(zfs_hdl, mntpnt, sh_name, proto,
props));
}
static int
sa_zfs_is_legacy(libshare_handle_t *shdl, const char *sh_path,
boolean_t *legacy)
{
libzfs_handle_t *zfs_hdl;
zfs_handle_t *zhp;
char mountpoint[ZFS_MAXPROPLEN];
int rc;
if ((zfs_hdl = sa_get_fs_handle(shdl, SA_FS_ZFS, &rc)) == NULL)
return (rc);
zhp = zfs_cache_get_quiet(zfs_hdl, sh_path, ZFS_TYPE_FILESYSTEM);
if (zhp == NULL) {
*legacy = B_TRUE;
return (SA_PATH_NOT_FOUND);
}
if (zfs_get_mountpoint(zhp, mountpoint,
sizeof (mountpoint)) == 0) {
if (strcmp(mountpoint, ZFS_MOUNTPOINT_LEGACY) == 0)
*legacy = B_TRUE;
else
*legacy = B_FALSE;
rc = SA_OK;
} else {
*legacy = B_TRUE;
rc = SA_MNTPNT_NOT_FOUND;
}
zfs_close_uncached(zhp);
return (rc);
}
static int
sa_zfs_is_zoned(libshare_handle_t *shdl, const char *mntpnt, boolean_t *zoned)
{
libzfs_handle_t *zfs_hdl;
zfs_handle_t *zhp;
int rc;
if ((zfs_hdl = sa_get_fs_handle(shdl, SA_FS_ZFS, &rc)) == NULL)
return (rc);
zhp = zfs_cache_get_quiet(zfs_hdl, mntpnt, ZFS_TYPE_FILESYSTEM);
if (zhp == NULL) {
*zoned = B_FALSE;
return (SA_MNTPNT_NOT_FOUND);
}
*zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED);
zfs_close_uncached(zhp);
return (SA_OK);
}
/*
* Take an opcode {publish|unpublish} and a list of per-protocol error codes
* from libshare in the following format:
* nvlist:
* name=protocol tag{"nfs"|"smb"|"any"}, value=error code
*
* If a sharing protocol is not enabled for a given share, its tag (and error
* code) will not be present in the list. If sharing is enabled for multiple
* protocols, track the state after publish as "shared" if any publish attempt
* succeeds, and track the state after unpublish as "unshared" only if all
* unpublish attempts succeed. Avoid changing the state if all protocols report
* lack of permission. Otherwise notify shares when publish fails but not when
* unpublish fails.
*
* The "any" protocol tag indicates that libshare did not distinguish which
* protocol was resposible for the error code, so it could apply to any or all
* protocols.
*/
static int
sa_zfs_get_share_state(sa_notify_op_t notify_op, nvlist_t *protos,
boolean_t *notifyp, zfs_share_state_t *statep)
{
nvpair_t *proto = NULL;
boolean_t shared = B_FALSE;
boolean_t notify = B_FALSE;
boolean_t any = B_FALSE;
zfs_share_state_t state;
int n = 0;
while ((proto = nvlist_next_nvpair(protos, proto)) != NULL) {
const char *proto_tag = nvpair_name(proto);
int intval;
if (strcmp(proto_tag, SA_PROTO_TAG_ANY) == 0) {
any = B_TRUE;
} else if (strcmp(proto_tag, SA_PROTO_TAG_NFS) != 0 &&
strcmp(proto_tag, SA_PROTO_TAG_SMB) != 0) {
return (SA_INVALID_PROTO);
}
if (nvpair_value_int32(proto, &intval) != 0)
return (SA_INTERNAL_ERR);
switch (intval) {
case SA_OK:
if (notify_op == SA_NOTIFY_OP_PUBLISH) {
shared = B_TRUE;
notify = B_TRUE;
}
break;
case SA_NO_PERMISSION:
if (notify_op == SA_NOTIFY_OP_UNPUBLISH)
shared = B_TRUE;
break;
default:
if (notify_op == SA_NOTIFY_OP_PUBLISH) {
notify = B_TRUE;
} else {
shared = B_TRUE; /* unpublish failed */
}
}
n++;
}
/*
* If the "any" proto tag is present, it should be the only tag in the
* list.
*/
if (any && n > 1)
return (SA_INVALID_PROTO);
if (notify_op == SA_NOTIFY_OP_UNPUBLISH && !shared)
notify = B_TRUE; /* all unpublish attempts succeeded */
if (notify) {
if (notify_op == SA_NOTIFY_OP_PUBLISH) {
state = (shared ? ZFS_SHARE_STATE_SHARED :
ZFS_SHARE_STATE_INVALID);
} else {
assert(!shared);
state = ZFS_SHARE_STATE_UNSHARED;
}
}
if (notifyp != NULL)
*notifyp = notify;
if (statep != NULL)
*statep = state;
return (SA_OK);
}
static void
sa_zfs_share_notify(libshare_handle_t *shdl, const char *mountpoint,
sa_notify_op_t notify_op, nvlist_t *notifications)
{
libzfs_handle_t *zfs_hdl;
zfs_handle_t *zhp;
nvlist_t *share_states;
nvpair_t *share = NULL;
int rc;
assert(notify_op == SA_NOTIFY_OP_PUBLISH ||
notify_op == SA_NOTIFY_OP_UNPUBLISH);
if ((zfs_hdl = sa_get_fs_handle(shdl, SA_FS_ZFS, &rc)) == NULL) {
salog_error(rc, "libshare_zfs");
return;
}
if (nvlist_alloc(&share_states, NV_UNIQUE_NAME, 0) != 0) {
salog_error(SA_NO_MEMORY, "libshare_zfs");
return;
}
zhp = zfs_cache_get_quiet(zfs_hdl, mountpoint, ZFS_TYPE_FILESYSTEM);
if (zhp == NULL) {
salog_error(SA_MNTPNT_NOT_FOUND, "libshare_zfs");
nvlist_free(share_states);
return;
}
while ((share = nvlist_next_nvpair(notifications, share)) != NULL) {
const char *sharename = nvpair_name(share);
nvlist_t *protos;
boolean_t notify;
zfs_share_state_t state;
if (nvpair_value_nvlist(share, &protos) != 0) {
salog_error(SA_INTERNAL_ERR, "libshare_zfs");
goto out;
}
if ((rc = sa_zfs_get_share_state(notify_op, protos, &notify,
&state)) != SA_OK) {
salog_error(rc, "libshare_zfs");
goto out;
}
if (notify) {
(void) zfssa_add_share_notification(zhp,
share_states, sharename, state);
}
}
zfssa_share_notify(zhp, share_states);
out:
nvlist_free(share_states);
zfs_close_uncached(zhp);
}