diff options
author | dougm <none@none> | 2006-11-01 16:37:33 -0800 |
---|---|---|
committer | dougm <none@none> | 2006-11-01 16:37:33 -0800 |
commit | 6185db853e024a486ff8837e6784dd290d866112 (patch) | |
tree | 63111669df90312edea60a2931a4ab3fed34e952 /usr/src/lib/libshare | |
parent | b4034a75a927c4d7d1850ef306cc6b1d49c48f20 (diff) | |
download | illumos-joyent-6185db853e024a486ff8837e6784dd290d866112.tar.gz |
PSARC 2005/374 Share management improvements
6281048 NFS needs simplified administration
--HG--
rename : usr/src/cmd/dfs.cmds/share/Makefile => deleted_files/usr/src/cmd/dfs.cmds/share/Makefile
rename : usr/src/cmd/dfs.cmds/share/share.c => deleted_files/usr/src/cmd/dfs.cmds/share/share.c
rename : usr/src/cmd/fs.d/nfs/share/share.c => deleted_files/usr/src/cmd/fs.d/nfs/share/share.c
rename : usr/src/cmd/fs.d/nfs/unshare/Makefile => deleted_files/usr/src/cmd/fs.d/nfs/unshare/Makefile
rename : usr/src/cmd/fs.d/nfs/unshare/unshare.c => deleted_files/usr/src/cmd/fs.d/nfs/unshare/unshare.c
rename : usr/src/cmd/fs.d/nfs/share/issubdir.c => usr/src/lib/libshare/common/issubdir.c
Diffstat (limited to 'usr/src/lib/libshare')
-rw-r--r-- | usr/src/lib/libshare/Makefile | 68 | ||||
-rw-r--r-- | usr/src/lib/libshare/Makefile.com | 81 | ||||
-rw-r--r-- | usr/src/lib/libshare/common/issubdir.c | 155 | ||||
-rw-r--r-- | usr/src/lib/libshare/common/libshare.c | 2665 | ||||
-rw-r--r-- | usr/src/lib/libshare/common/libshare.h | 217 | ||||
-rw-r--r-- | usr/src/lib/libshare/common/libshare_impl.h | 143 | ||||
-rw-r--r-- | usr/src/lib/libshare/common/libshare_zfs.c | 496 | ||||
-rw-r--r-- | usr/src/lib/libshare/common/libsharecore.c | 1801 | ||||
-rw-r--r-- | usr/src/lib/libshare/common/llib-lshare | 32 | ||||
-rw-r--r-- | usr/src/lib/libshare/common/mapfile-vers | 110 | ||||
-rw-r--r-- | usr/src/lib/libshare/common/parser.c | 121 | ||||
-rw-r--r-- | usr/src/lib/libshare/common/plugin.c | 458 | ||||
-rw-r--r-- | usr/src/lib/libshare/common/scfutil.c | 1443 | ||||
-rw-r--r-- | usr/src/lib/libshare/common/scfutil.h | 81 | ||||
-rw-r--r-- | usr/src/lib/libshare/i386/Makefile | 30 | ||||
-rw-r--r-- | usr/src/lib/libshare/sparc/Makefile | 30 |
16 files changed, 7931 insertions, 0 deletions
diff --git a/usr/src/lib/libshare/Makefile b/usr/src/lib/libshare/Makefile new file mode 100644 index 0000000000..afdcaee749 --- /dev/null +++ b/usr/src/lib/libshare/Makefile @@ -0,0 +1,68 @@ +# +# 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 +# +# +# ident "%Z%%M% %I% %E% SMI" +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +include ../../Makefile.master + +include ../Makefile.lib + +HDRS = libshare.h +HDRDIR = common +SUBDIRS = $(MACH) +ROOTHDRDIR= $(ROOT)/usr/include +ROOTHDRS= $(HDRS:%=$(ROOTHDRDIR)/%) + +MSGFILES= common/libshare.c common/libsharecore.c common/scfutil.c \ + common/parser.c common/libshare_zfs.c +POFILE= libshare.po + +all := TARGET = all +clean := TARGET = clean +clobber := TARGET = clobber +install := TARGET = install +lint := TARGET = lint + +.KEEP_STATE: + +all clean clobber install lint: $(SUBDIRS) + +install_h: $(ROOTHDRS) + +check: $(CHECKHDRS) + +$(POFILE): $(MSGFILES) + $(BUILDPO.msgfiles) + +$(SUBDIRS): FRC + @cd $@; pwd; $(MAKE) $(TARGET) + +_msg: $(MSGDOMAINPOFILE) + + +FRC: + +include ../Makefile.targ +include ../../Makefile.msg.targ diff --git a/usr/src/lib/libshare/Makefile.com b/usr/src/lib/libshare/Makefile.com new file mode 100644 index 0000000000..d444041cf7 --- /dev/null +++ b/usr/src/lib/libshare/Makefile.com @@ -0,0 +1,81 @@ +# +# 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 +# +# +# ident "%Z%%M% %I% %E% SMI" +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +LIBRARY = libshare.a +VERS = .1 +NFSLIB_DIR = $(SRC)/cmd/fs.d/nfs/lib +NFSSECSRC = $(NFSLIB_DIR)/nfs_sec.c +NFSSHARETAB = $(NFSLIB_DIR)/sharetab.c + +LIBOBJS = libshare.o libsharecore.o scfutil.o libshare_zfs.o \ + plugin.o parser.o issubdir.o +OTHOBJS = sharetab.o nfs_sec.o +OBJECTS = $(LIBOBJS) $(OTHOBJS) +COMMON = ../common + +SRCS = $(OBJECTS:%.o=../%.c) +LIBSRCS = $(LIBOBJS:%.o=$(COMMON)/%.c) +POFILES = $(OBJECTS:%.o=%.po) +POFILE = libshare.po + +include ../../Makefile.lib + +ROOTDIRS= $(ROOT)/usr/include + +ROOTHDRS= $(HDRS:%=$(ROOTDIRS)/%) + +CHECKHDRS= $(HDRS:%.h=%.check) + +LIBS = $(DYNLIB) $(LINTLIB) +LDLIBS += -lc -lnsl -lscf -lxml2 -lzfs -luuid -lfsmgt +$(LINTLIB) := SRCS = $(SRCDIR)/$(LINTSRC) + +SRCDIR = ../common +MAPDIR = ../spec/$(TRANSMACH) +SPECMAPFILE = $(MAPDIR)/mapfile + +#add nfs/lib directory as part of the include path +CFLAGS += $(CCVERBOSE) -g +CPPFLAGS += -D_REENTRANT -I$(SRC)/lib/libfsmgt/common \ + -I$(SRC)/cmd/fs.d/nfs/lib -I/usr/include/libxml2 \ + -I../common + +.KEEP_STATE: + +all: $(LIBS) + +# we don't want to lint the sharetab and nfs_sec files +lint: $$(LIBSRCS) + $(LINT.c) $(LINTCHECKFLAGS) $(LIBSRCS) $(LDLIBS) + +pics/%.o: $(NFSLIB_DIR)/%.c + $(COMPILE.c) -o $@ $< + $(POST_PROCESS_O) + +include ../../Makefile.targ + +$(POFILE): $(POFILES) + $(RM) $@; cat $(POFILES) > $@ diff --git a/usr/src/lib/libshare/common/issubdir.c b/usr/src/lib/libshare/common/issubdir.c new file mode 100644 index 0000000000..d24a4b89e5 --- /dev/null +++ b/usr/src/lib/libshare/common/issubdir.c @@ -0,0 +1,155 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (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 2005 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + +/* + * University Copyright- Copyright (c) 1982, 1986, 1988 + * The Regents of the University of California + * All Rights Reserved + * + * University Acknowledgment- Portions of this document are derived from + * software developed by the University of California, Berkeley, and its + * contributors. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Subdirectory detection: needed by exportfs and rpc.mountd. + * The above programs call issubdir() frequently, so we make + * it fast by caching the results of stat(). + */ +#include <sys/types.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <string.h> + +#define MAXSTATS MAXPATHLEN/2 /* maximum number of stat()'s to save */ + +#define inoeq(ino1, ino2) ((ino1) == (ino2)) +#define deveq(dev1, dev2) ((dev1) == (dev2)) + +/* + * dir1 is a subdirectory of dir2 within the same filesystem if + * (a) dir1 is identical to dir2 + * (b) dir1's parent is dir2 + */ +int +issubdir(dir1, dir2) + char *dir1; + char *dir2; +{ + struct stat st; + struct stat parent_st; + char *p; + int index; + + static dev_t child_dev; + static dev_t child_rdev; + static ino_t child_ino[MAXSTATS]; + static int valid; + static char childdir[MAXPATHLEN]; + static char child_fstype[_ST_FSTYPSZ]; + + /* + * Get parent directory info + */ + if (stat(dir2, &parent_st) < 0) { + return (0); + } + + if (strcmp(childdir, dir1) != 0) { + /* + * Not in cache: get child directory info + */ + p = strcpy(childdir, dir1) + strlen(dir1); + index = 0; + valid = 0; + for (;;) { + if (stat(childdir, &st) < 0) { + childdir[0] = 0; /* invalidate cache */ + return (0); + } + if (index == 0) { + child_dev = st.st_dev; + child_rdev = st.st_rdev; + (void) strncpy(child_fstype, st.st_fstype, + sizeof (child_fstype)); + } + if (index > 0 && + (child_dev != st.st_dev || + inoeq(child_ino[index - 1], st.st_ino))) { + /* + * Hit root: done + */ + break; + } + child_ino[index++] = st.st_ino; + if (S_ISDIR(st.st_mode)) { + p = strcpy(p, "/..") + 3; + } else { + p = strrchr(childdir, '/'); + if (p == NULL) { + p = strcpy(childdir, ".") + 1; + } else { + while (((p - 1) > childdir) && + *(p - 1) == '/') { + p--; + } + *p = '\0'; + } + } + } + valid = index; + (void) strcpy(childdir, dir1); + } + + /* + * Perform the test + */ + if (!deveq(parent_st.st_dev, child_dev)) { + return (0); + } + + /* + * Check rdev also in case of lofs + */ + if (((strcmp(parent_st.st_fstype, "lofs") == 0)) && + (strcmp(child_fstype, "lofs") == 0)) { + if (!deveq(parent_st.st_rdev, child_rdev)) { + return (0); + } + } + + for (index = 0; index < valid; index++) { + if (inoeq(child_ino[index], parent_st.st_ino)) { + return (1); + } + } + return (0); +} diff --git a/usr/src/lib/libshare/common/libshare.c b/usr/src/lib/libshare/common/libshare.c new file mode 100644 index 0000000000..65ab1fc55f --- /dev/null +++ b/usr/src/lib/libshare/common/libshare.c @@ -0,0 +1,2665 @@ +/* + * 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 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Share control API + */ +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <libxml/parser.h> +#include <libxml/tree.h> +#include "libshare.h" +#include "libshare_impl.h" +#include <libscf.h> +#include "scfutil.h" +#include <ctype.h> +#include <libintl.h> + +#if _NOT_SMF +#define CONFIG_FILE "/var/tmp/share.cfg" +#define CONFIG_FILE_TMP "/var/tmp/share.cfg.tmp" +#endif +#define TSTAMP(tm) (uint64_t)(((uint64_t)tm.tv_sec << 32) | \ + (tm.tv_nsec & 0xffffffff)) + +/* + * internal data structures + */ + +static xmlNodePtr sa_config_tree; /* the current config */ +static xmlDocPtr sa_config_doc = NULL; /* current config document */ +extern struct sa_proto_plugin *sap_proto_list; + +/* current SMF/SVC repository handle */ +static scfutilhandle_t *scf_handle = NULL; +extern void getlegacyconfig(char *, xmlNodePtr *); +extern int gettransients(xmlNodePtr *); +extern int sa_valid_property(void *, char *, sa_property_t); +extern char *sa_fstype(char *); +extern int sa_is_share(void *); +extern ssize_t scf_max_name_len; /* defined in scfutil during initialization */ +extern int sa_group_is_zfs(sa_group_t); +extern int sa_path_is_zfs(char *); +extern int sa_zfs_set_sharenfs(sa_group_t, char *, int); +extern void update_legacy_config(void); +extern int issubdir(char *, char *); + +static int sa_initialized = 0; + +/* helper functions */ + +char * +sa_errorstr(int err) +{ + static char errstr[32]; + char *ret = NULL; + + switch (err) { + case SA_OK: + ret = gettext("ok"); + break; + case SA_NO_SUCH_PATH: + ret = gettext("path doesn't exist"); + break; + case SA_NO_MEMORY: + ret = gettext("no memory"); + break; + case SA_DUPLICATE_NAME: + ret = gettext("name in use"); + break; + case SA_BAD_PATH: + ret = gettext("bad path"); + break; + case SA_NO_SUCH_GROUP: + ret = gettext("no such group"); + break; + case SA_CONFIG_ERR: + ret = gettext("configuration error"); + break; + case SA_SYSTEM_ERR: + ret = gettext("system error"); + break; + case SA_SYNTAX_ERR: + ret = gettext("syntax error"); + break; + case SA_NO_PERMISSION: + ret = gettext("no permission"); + break; + case SA_BUSY: + ret = gettext("busy"); + break; + case SA_NO_SUCH_PROP: + ret = gettext("no such property"); + break; + case SA_INVALID_NAME: + ret = gettext("invalid name"); + break; + case SA_INVALID_PROTOCOL: + ret = gettext("invalid protocol"); + break; + case SA_NOT_ALLOWED: + ret = gettext("operation not allowed"); + break; + case SA_BAD_VALUE: + ret = gettext("bad property value"); + break; + case SA_INVALID_SECURITY: + ret = gettext("invalid security type"); + break; + case SA_NO_SUCH_SECURITY: + ret = gettext("security type not found"); + break; + case SA_VALUE_CONFLICT: + ret = gettext("property value conflict"); + break; + case SA_NOT_IMPLEMENTED: + ret = gettext("not implemented"); + break; + case SA_INVALID_PATH: + ret = gettext("invalid path"); + break; + case SA_NOT_SUPPORTED: + ret = gettext("operation not supported"); + break; + case SA_PROP_SHARE_ONLY: + ret = gettext("property not valid for group"); + break; + case SA_NOT_SHARED: + ret = gettext("not shared"); + break; + default: + (void) snprintf(errstr, sizeof (errstr), + gettext("unknown %d"), err); + ret = errstr; + } + return (ret); +} + +/* + * get_legacy_timestamp(root, path) + * gets the timestamp of the last time sharemgr updated the legacy + * files. This is used to determine if someone has modified them by + * hand. + */ +static uint64_t +get_legacy_timestamp(xmlNodePtr root, char *path) +{ + uint64_t tval = 0; + xmlNodePtr node; + xmlChar *lpath = NULL; + xmlChar *timestamp = NULL; + + for (node = root->xmlChildrenNode; node != NULL; + node = node->next) { + if (xmlStrcmp(node->name, (xmlChar *)"legacy") == 0) { + /* a possible legacy node for this path */ + lpath = xmlGetProp(node, (xmlChar *)"path"); + if (lpath != NULL && xmlStrcmp(lpath, (xmlChar *)path) == 0) { + /* now have the node, extract the data */ + timestamp = xmlGetProp(node, (xmlChar *)"timestamp"); + if (timestamp != NULL) { + tval = strtoull((char *)timestamp, NULL, 0); + break; + } + } + if (lpath != NULL) { + xmlFree(lpath); + lpath = NULL; + } + } + } + if (lpath != NULL) + xmlFree(lpath); + if (timestamp != NULL) + xmlFree(timestamp); + return (tval); +} + +/* + * set_legacy_timestamp(root, path, timevalue) + * + * add the current timestamp value to the configuration for use in + * determining when to update the legacy files. For SMF, this + * property is kept in default/operation/legacy_timestamp + */ + +static void +set_legacy_timestamp(xmlNodePtr root, char *path, uint64_t tval) +{ + xmlNodePtr node; + xmlChar *lpath = NULL; + + for (node = root->xmlChildrenNode; node != NULL; + node = node->next) { + if (xmlStrcmp(node->name, (xmlChar *)"legacy") == 0) { + /* a possible legacy node for this path */ + lpath = xmlGetProp(node, (xmlChar *)"path"); + if (lpath != NULL && xmlStrcmp(lpath, (xmlChar *)path) == 0) { + xmlFree(lpath); + break; + } + if (lpath != NULL) + xmlFree(lpath); + } + } + if (node == NULL) { + /* need to create the first legacy timestamp node */ + node = xmlNewChild(root, NULL, (xmlChar *)"legacy", NULL); + } + if (node != NULL) { + char tstring[32]; + int ret; + + (void) snprintf(tstring, sizeof (tstring), "%lld", tval); + xmlSetProp(node, (xmlChar *)"timestamp", (xmlChar *)tstring); + xmlSetProp(node, (xmlChar *)"path", (xmlChar *)path); + /* now commit to SMF */ + ret = sa_get_instance(scf_handle, "default"); + if (ret == SA_OK) { + ret = sa_start_transaction(scf_handle, "operation"); + if (ret == SA_OK) { + ret = sa_set_property(scf_handle, "legacy-timestamp", + tstring); + if (ret == SA_OK) { + (void) sa_end_transaction(scf_handle); + } else { + sa_abort_transaction(scf_handle); + } + } + } + } +} + +/* + * is_shared(share) + * + * determine if the specified share is currently shared or not. + */ +static int +is_shared(sa_share_t share) +{ + char *shared; + int result = 0; /* assume not */ + + shared = sa_get_share_attr(share, "shared"); + if (shared != NULL) { + if (strcmp(shared, "true") == 0) + result = 1; + sa_free_attr_string(shared); + } + return (result); +} + +/* + * checksubdir determines if the specified path is a subdirectory of + * another share. It calls issubdir() from the old share + * implementation to do the complicated work. + */ +static int +checksubdir(char *newpath) +{ + sa_group_t group; + sa_share_t share; + int issub; + char *path = NULL; + + for (issub = 0, group = sa_get_group(NULL); + group != NULL && !issub; + group = sa_get_next_group(group)) { + for (share = sa_get_share(group, NULL); share != NULL; + share = sa_get_next_share(share)) { + /* + * The original behavior of share never checked + * against the permanent configuration + * (/etc/dfs/dfstab). PIT has a number of cases where + * it depends on this older behavior even though it + * could be considered incorrect. We may tighten this + * up in the future. + */ + if (!is_shared(share)) + continue; + + path = sa_get_share_attr(share, "path"); + if (newpath != NULL && + (strcmp(path, newpath) == 0 || issubdir(newpath, path) || + issubdir(path, newpath))) { + sa_free_attr_string(path); + path = NULL; + issub = SA_INVALID_PATH; + break; + } + sa_free_attr_string(path); + path = NULL; + } + } + if (path != NULL) + sa_free_attr_string(path); + return (issub); +} + +/* + * validpath(path) + * determine if the provided path is valid for a share. It shouldn't + * be a sub-dir of an already shared path or the parent directory of a + * share path. + */ +static int +validpath(char *path) +{ + int error = SA_OK; + struct stat st; + sa_share_t share; + char *fstype; + + if (*path != '/') { + return (SA_BAD_PATH); + } + if (stat(path, &st) < 0) { + error = SA_NO_SUCH_PATH; + } else { + share = sa_find_share(path); + if (share != NULL) { + error = SA_DUPLICATE_NAME; + } + if (error == SA_OK) { + /* + * check for special case with file system that might + * have restrictions. For now, ZFS is the only case + * since it has its own idea of how to configure + * shares. We do this before subdir checking since + * things like ZFS will do that for us. This should + * also be done via plugin interface. + */ + fstype = sa_fstype(path); + if (fstype != NULL && strcmp(fstype, "zfs") == 0) { + if (sa_zfs_is_shared(path)) + error = SA_DUPLICATE_NAME; + } + if (fstype != NULL) + sa_free_fstype(fstype); + } + if (error == SA_OK) { + error = checksubdir(path); + } + } + return (error); +} + +/* + * check to see if group/share is persistent. + */ +static int +is_persistent(sa_group_t group) +{ + char *type; + int persist = 1; + + type = sa_get_group_attr(group, "type"); + if (type != NULL && strcmp(type, "transient") == 0) + persist = 0; + if (type != NULL) + sa_free_attr_string(type); + return (persist); +} + +/* + * sa_valid_group_name(name) + * + * check that the "name" contains only valid characters and otherwise + * fits the required naming conventions. Valid names must start with + * an alphabetic and the remainder may consist of only alphanumeric + * plus the '-' and '_' characters. This name limitation comes from + * inherent limitations in SMF. + */ + +int +sa_valid_group_name(char *name) +{ + int ret = 1; + ssize_t len; + + if (name != NULL && isalpha(*name)) { + char c; + len = strlen(name); + if (len < (scf_max_name_len - sizeof ("group:"))) { + for (c = *name++; c != '\0' && ret != 0; c = *name++) { + if (!isalnum(c) && c != '-' && c != '_') + ret = 0; + } + } else { + ret = 0; + } + } else { + ret = 0; + } + return (ret); +} + + +/* + * is_zfs_group(group) + * Determine if the specified group is a ZFS sharenfs group + */ +static int +is_zfs_group(sa_group_t group) +{ + int ret = 0; + xmlNodePtr parent; + xmlChar *zfs; + + if (strcmp((char *)((xmlNodePtr)group)->name, "share") == 0) { + parent = (xmlNodePtr)sa_get_parent_group(group); + } else { + parent = (xmlNodePtr)group; + } + zfs = xmlGetProp(parent, (xmlChar *)"zfs"); + if (zfs != NULL) { + xmlFree(zfs); + ret = 1; + } + return (ret); +} + +/* + * sa_optionset_name(optionset, oname, len, id) + * return the SMF name for the optionset. If id is not NULL, it + * will have the GUID value for a share and should be used + * instead of the keyword "optionset" which is used for + * groups. If the optionset doesn't have a protocol type + * associated with it, "default" is used. This shouldn't happen + * at this point but may be desirable in the future if there are + * protocol independent properties added. The name is returned in + * oname. + */ + +static int +sa_optionset_name(sa_optionset_t optionset, char *oname, size_t len, char *id) +{ + char *proto; + + if (id == NULL) + id = "optionset"; + + proto = sa_get_optionset_attr(optionset, "type"); + len = snprintf(oname, len, "%s_%s", id, proto ? proto : "default"); + + if (proto != NULL) + sa_free_attr_string(proto); + return (len); +} + +/* + * sa_security_name(optionset, oname, len, id) + * + * return the SMF name for the security. If id is not NULL, it will + * have the GUID value for a share and should be used instead of the + * keyword "optionset" which is used for groups. If the optionset + * doesn't have a protocol type associated with it, "default" is + * used. This shouldn't happen at this point but may be desirable in + * the future if there are protocol independent properties added. The + * name is returned in oname. The security type is also encoded into + * the name. In the future, this wil *be handled a bit differently. + */ + +static int +sa_security_name(sa_security_t security, char *oname, size_t len, char *id) +{ + char *proto; + char *sectype; + + if (id == NULL) + id = "optionset"; + + proto = sa_get_security_attr(security, "type"); + sectype = sa_get_security_attr(security, "sectype"); + len = snprintf(oname, len, "%s_%s_%s", id, + proto ? proto : "default", + sectype ? sectype : "default"); + if (proto != NULL) + sa_free_attr_string(proto); + if (sectype != NULL) + sa_free_attr_string(sectype); + return (len); +} + +/* + * sa_init() + * Initialize the API + * find all the shared objects + * init the tables with all objects + * read in the current configuration + */ + +void +sa_init(int init_service) +{ + struct stat st; + int legacy = 0; + uint64_t tval = 0; + + if (!sa_initialized) { + /* get protocol specific structures */ + (void) proto_plugin_init(); + if (init_service & SA_INIT_SHARE_API) { + /* + * since we want to use SMF, initialize an svc handle + * and find out what is there. + */ + scf_handle = sa_scf_init(); + if (scf_handle != NULL) { + (void) sa_get_config(scf_handle, &sa_config_tree, + &sa_config_doc); + tval = get_legacy_timestamp(sa_config_tree, + SA_LEGACY_DFSTAB); + if (tval == 0) { + /* first time so make sure default is setup */ + sa_group_t defgrp; + sa_optionset_t opt; + defgrp = sa_get_group("default"); + if (defgrp != NULL) { + opt = sa_get_optionset(defgrp, NULL); + if (opt == NULL) + /* NFS is the default for default */ + opt = sa_create_optionset(defgrp, "nfs"); + } + } + if (stat(SA_LEGACY_DFSTAB, &st) >= 0 && + tval != TSTAMP(st.st_ctim)) { + getlegacyconfig(SA_LEGACY_DFSTAB, &sa_config_tree); + if (stat(SA_LEGACY_DFSTAB, &st) >= 0) + set_legacy_timestamp(sa_config_tree, + SA_LEGACY_DFSTAB, + TSTAMP(st.st_ctim)); + } + legacy |= sa_get_zfs_shares("zfs"); + legacy |= gettransients(&sa_config_tree); + } + } + } +} + +/* + * sa_fini() + * Uninitialize the API structures including the configuration + * data structures + */ + +void +sa_fini() +{ + if (sa_initialized) { + /* free the config trees */ + sa_initialized = 0; + if (sa_config_doc != NULL) + xmlFreeDoc(sa_config_doc); + sa_config_tree = NULL; + sa_config_doc = NULL; + sa_scf_fini(scf_handle); + (void) proto_plugin_init(); + } +} + +/* + * sa_get_protocols(char **protocol) + * Get array of protocols that are supported + * Returns pointer to an allocated and NULL terminated + * array of strings. Caller must free. + * This really should be determined dynamically. + * If there aren't any defined, return -1. + * Use free() to return memory. + */ + +int +sa_get_protocols(char ***protocols) +{ + int numproto = -1; + + if (protocols != NULL) { + struct sa_proto_plugin *plug; + for (numproto = 0, plug = sap_proto_list; plug != NULL; + plug = plug->plugin_next) { + numproto++; + } + + *protocols = calloc(numproto + 1, sizeof (char *)); + if (*protocols != NULL) { + int ret = 0; + for (plug = sap_proto_list; plug != NULL; + plug = plug->plugin_next) { + /* faking for now */ + (*protocols)[ret++] = plug->plugin_ops->sa_protocol; + } + } else { + numproto = -1; + } + } + return (numproto); +} + +/* + * find_group_by_name(node, group) + * + * search the XML document subtree specified by node to find the group + * specified by group. Searching subtree allows subgroups to be + * searched for. + */ + +static xmlNodePtr +find_group_by_name(xmlNodePtr node, xmlChar *group) +{ + xmlChar *name = NULL; + + for (node = node->xmlChildrenNode; node != NULL; + node = node->next) { + if (xmlStrcmp(node->name, (xmlChar *)"group") == 0) { + /* if no groupname, return the first found */ + if (group == NULL) + break; + name = xmlGetProp(node, (xmlChar *)"name"); + if (name != NULL && + xmlStrcmp(name, group) == 0) { + break; + } + if (name != NULL) { + xmlFree(name); + name = NULL; + } + } + } + if (name != NULL) + xmlFree(name); + return (node); +} + +/* + * sa_get_group(groupname) + * Return the "group" specified. If groupname is NULL, + * return the first group of the list of groups. + */ +sa_group_t +sa_get_group(char *groupname) +{ + xmlNodePtr node = NULL; + char *subgroup = NULL; + char *group = NULL; + + if (sa_config_tree != NULL) { + if (groupname != NULL) { + group = strdup(groupname); + subgroup = strchr(group, '/'); + if (subgroup != NULL) + *subgroup++ = '\0'; + } + node = find_group_by_name(sa_config_tree, (xmlChar *)group); + /* if a subgroup, find it before returning */ + if (subgroup != NULL && node != NULL) { + node = find_group_by_name(node, (xmlChar *)subgroup); + } + } + if (node != NULL && (char *)group != NULL) + (void) sa_get_instance(scf_handle, (char *)group); + if (group != NULL) + free(group); + return ((sa_group_t)(node)); +} + +/* + * sa_get_next_group(group) + * Return the "next" group after the specified group from + * the internal group list. NULL if there are no more. + */ +sa_group_t +sa_get_next_group(sa_group_t group) +{ + xmlNodePtr ngroup = NULL; + if (group != NULL) { + for (ngroup = ((xmlNodePtr)group)->next; ngroup != NULL; + ngroup = ngroup->next) { + if (xmlStrcmp(ngroup->name, (xmlChar *)"group") == 0) + break; + } + } + return ((sa_group_t)ngroup); +} + +/* + * sa_get_share(group, sharepath) + * Return the share object for the share specified. The share + * must be in the specified group. Return NULL if not found. + */ +sa_share_t +sa_get_share(sa_group_t group, char *sharepath) +{ + xmlNodePtr node = NULL; + xmlChar *path; + + /* + * For future scalability, this should end up building a cache + * since it will get called regularly by the mountd and info + * services. + */ + if (group != NULL) { + for (node = ((xmlNodePtr)group)->children; node != NULL; + node = node->next) { + if (xmlStrcmp(node->name, (xmlChar *)"share") == 0) { + if (sharepath == NULL) { + break; + } else { + /* is it the correct share? */ + path = xmlGetProp(node, (xmlChar *)"path"); + if (path != NULL && + xmlStrcmp(path, (xmlChar *)sharepath) == 0) { + xmlFree(path); + break; + } + xmlFree(path); + } + } + } + } + return ((sa_share_t)node); +} + +/* + * sa_get_next_share(share) + * Return the next share following the specified share + * from the internal list of shares. Returns NULL if there + * are no more shares. The list is relative to the same + * group. + */ +sa_share_t +sa_get_next_share(sa_share_t share) +{ + xmlNodePtr node = NULL; + + if (share != NULL) { + for (node = ((xmlNodePtr)share)->next; node != NULL; + node = node->next) { + if (xmlStrcmp(node->name, (xmlChar *)"share") == 0) { + break; + } + } + } + return ((sa_share_t)node); +} + +/* + * _sa_get_child_node(node, type) + * + * find the child node of the specified node that has "type". This is + * used to implement several internal functions. + */ + +static xmlNodePtr +_sa_get_child_node(xmlNodePtr node, xmlChar *type) +{ + xmlNodePtr child; + for (child = node->xmlChildrenNode; child != NULL; + child = child->next) + if (xmlStrcmp(child->name, type) == 0) + return (child); + return ((xmlNodePtr)NULL); +} + +/* + * find_share(group, path) + * + * Search all the shares in the specified group for one that has the + * specified path. + */ + +static sa_share_t +find_share(sa_group_t group, char *sharepath) +{ + sa_share_t share; + char *path; + + for (share = sa_get_share(group, NULL); share != NULL; + share = sa_get_next_share(share)) { + path = sa_get_share_attr(share, "path"); + if (path != NULL && strcmp(path, sharepath) == 0) { + sa_free_attr_string(path); + break; + } + if (path != NULL) + sa_free_attr_string(path); + } + return (share); +} + +/* + * sa_get_sub_group(group) + * + * Get the first sub-group of group. The sa_get_next_group() function + * can be used to get the rest. This is currently only used for ZFS + * sub-groups but could be used to implement a more general mechanism. + */ + +sa_group_t +sa_get_sub_group(sa_group_t group) +{ + return ((sa_group_t)_sa_get_child_node((xmlNodePtr)group, + (xmlChar *)"group")); +} + +/* + * sa_find_share(sharepath) + * Finds a share regardless of group. In the future, this + * function should utilize a cache and hash table of some kind. + * The current assumption is that a path will only be shared + * once. In the future, this may change as implementation of + * resource names comes into being. + */ +sa_share_t +sa_find_share(char *sharepath) +{ + sa_group_t group; + sa_group_t zgroup; + sa_share_t share = NULL; + int done = 0; + + for (group = sa_get_group(NULL); group != NULL && !done; + group = sa_get_next_group(group)) { + if (is_zfs_group(group)) { + for (zgroup = (sa_group_t)_sa_get_child_node((xmlNodePtr)group, + (xmlChar *)"group"); + zgroup != NULL; zgroup = sa_get_next_group(zgroup)) { + share = find_share(zgroup, sharepath); + if (share != NULL) + break; + } + } else { + share = find_share(group, sharepath); + } + if (share != NULL) + break; + } + return (share); +} + +/* + * sa_check_path(group, path) + * + * check that path is a valid path relative to the group. Currently, + * we are ignoring the group and checking only the NFS rules. Later, + * we may want to use the group to then check against the protocols + * enabled on the group. + */ + +int +sa_check_path(sa_group_t group, char *path) +{ +#ifdef lint + group = group; +#endif + return (validpath(path)); +} + +/* + * _sa_add_share(group, sharepath, persist, *error) + * + * common code for all types of add_share. sa_add_share() is the + * public API, we also need to be able to do this when parsing legacy + * files and construction of the internal configuration while + * extracting config info from SMF. + */ + +sa_share_t +_sa_add_share(sa_group_t group, char *sharepath, int persist, int *error) +{ + xmlNodePtr node = NULL; + int err; + + err = SA_OK; /* assume success */ + + node = xmlNewChild((xmlNodePtr)group, NULL, + (xmlChar *)"share", NULL); + if (node != NULL) { + xmlSetProp(node, (xmlChar *)"path", (xmlChar *)sharepath); + xmlSetProp(node, (xmlChar *)"type", persist ? + (xmlChar *)"persist" : (xmlChar *)"transient"); + if (persist != SA_SHARE_TRANSIENT) { + /* + * persistent shares come in two flavors: SMF and + * ZFS. Sort this one out based on target group and + * path type. Currently, only NFS is supported in the + * ZFS group and it is always on. + */ + if (sa_group_is_zfs(group) && sa_path_is_zfs(sharepath)) { + err = sa_zfs_set_sharenfs(group, sharepath, 1); + } else { + err = sa_commit_share(scf_handle, group, + (sa_share_t)node); + } + } + if (err == SA_NO_PERMISSION && persist & SA_SHARE_PARSER) { + /* called by the dfstab parser so could be a show */ + err = SA_OK; + } + if (err != SA_OK) { + /* + * we couldn't commit to the repository so undo + * our internal state to reflect reality. + */ + xmlUnlinkNode(node); + xmlFreeNode(node); + node = NULL; + } + } else { + err = SA_NO_MEMORY; + } + if (error != NULL) + *error = err; + return (node); +} + +/* + * sa_add_share(group, sharepath, persist, *error) + * + * Add a new share object to the specified group. The share will + * have the specified sharepath and will only be constructed if + * it is a valid path to be shared. NULL is returned on error + * and a detailed error value will be returned via the error + * pointer. + */ +sa_share_t +sa_add_share(sa_group_t group, char *sharepath, int persist, int *error) +{ + xmlNodePtr node = NULL; + sa_share_t dup; + + if ((dup = sa_find_share(sharepath)) == NULL && + (*error = sa_check_path(group, sharepath)) == SA_OK) { + node = _sa_add_share(group, sharepath, persist, error); + } + if (dup != NULL) + *error = SA_DUPLICATE_NAME; + + return ((sa_share_t)node); +} + +/* + * sa_enable_share(share, protocol) + * Enable the specified share to the specified protocol. + * If protocol is NULL, then all protocols. + */ +int +sa_enable_share(sa_share_t share, char *protocol) +{ + char *sharepath; + struct stat st; + int err = 0; + + sharepath = sa_get_share_attr(share, "path"); + if (stat(sharepath, &st) < 0) { + err = SA_NO_SUCH_PATH; + } else { + /* tell the server about the share */ + if (protocol != NULL) { + /* lookup protocol specific handler */ + err = sa_proto_share(protocol, share); + if (err == SA_OK) + (void) sa_set_share_attr(share, "shared", "true"); + } else { + /* tell all protocols */ + err = sa_proto_share("nfs", share); /* only NFS for now */ + (void) sa_set_share_attr(share, "shared", "true"); + } + } + if (sharepath != NULL) + sa_free_attr_string(sharepath); + return (err); +} + +/* + * sa_disable_share(share, protocol) + * Disable the specified share to the specified protocol. + * If protocol is NULL, then all protocols. + */ +int +sa_disable_share(sa_share_t share, char *protocol) +{ + char *path; + char *shared; + int ret = SA_OK; + + path = sa_get_share_attr(share, "path"); + shared = sa_get_share_attr(share, "shared"); + + if (protocol != NULL) { + ret = sa_proto_unshare(protocol, path); + } else { + /* need to do all protocols */ + ret = sa_proto_unshare("nfs", path); + } + if (ret == SA_OK) + (void) sa_set_share_attr(share, "shared", NULL); + if (path != NULL) + sa_free_attr_string(path); + if (shared != NULL) + sa_free_attr_string(shared); + return (ret); +} + +/* + * sa_remove_share(share) + * + * remove the specified share from its containing group. + * Remove from the SMF or ZFS configuration space. + */ + +int +sa_remove_share(sa_share_t share) +{ + sa_group_t group; + int ret = SA_OK; + char *type; + int transient = 0; + char *groupname; + char *zfs; + + type = sa_get_share_attr(share, "type"); + group = sa_get_parent_group(share); + zfs = sa_get_group_attr(group, "zfs"); + groupname = sa_get_group_attr(group, "name"); + if (type != NULL && strcmp(type, "persist") != 0) + transient = 1; + if (type != NULL) + sa_free_attr_string(type); + + /* remove the node from its group then free the memory */ + + /* + * need to test if "busy" + */ + /* only do SMF action if permanent */ + if (!transient || zfs != NULL) { + /* remove from legacy dfstab as well as possible SMF */ + ret = sa_delete_legacy(share); + if (ret == SA_OK) { + if (!sa_group_is_zfs(group)) { + ret = sa_delete_share(scf_handle, group, share); + } else { + char *sharepath = sa_get_share_attr(share, "path"); + if (sharepath != NULL) { + ret = sa_zfs_set_sharenfs(group, sharepath, 0); + sa_free_attr_string(sharepath); + } + } + } + } + if (groupname != NULL) + sa_free_attr_string(groupname); + if (zfs != NULL) + sa_free_attr_string(zfs); + + xmlUnlinkNode((xmlNodePtr)share); + xmlFreeNode((xmlNodePtr)share); + return (ret); +} + +/* + * sa_move_share(group, share) + * + * move the specified share to the specified group. Update SMF + * appropriately. + */ + +int +sa_move_share(sa_group_t group, sa_share_t share) +{ + sa_group_t oldgroup; + int ret = SA_OK; + + /* remove the node from its group then free the memory */ + + oldgroup = sa_get_parent_group(share); + if (oldgroup != group) { + xmlUnlinkNode((xmlNodePtr)share); + /* now that the share isn't in its old group, add to the new one */ + xmlAddChild((xmlNodePtr)group, (xmlNodePtr)share); + /* need to deal with SMF */ + if (ret == SA_OK) { + /* + * need to remove from old group first and then add to + * new group. Ideally, we would do the other order but + * need to avoid having the share in two groups at the + * same time. + */ + ret = sa_delete_share(scf_handle, oldgroup, share); + } + ret = sa_commit_share(scf_handle, group, share); + } + return (ret); +} + +/* + * sa_get_parent_group(share) + * + * Return the containg group for the share. If a group was actually + * passed in, we don't want a parent so return NULL. + */ + +sa_group_t +sa_get_parent_group(sa_share_t share) +{ + xmlNodePtr node = NULL; + if (share != NULL) { + node = ((xmlNodePtr)share)->parent; + /* + * make sure parent is a group and not sharecfg since + * we may be cheating and passing in a group. + * Eventually, groups of groups might come into being. + */ + if (node == NULL || + xmlStrcmp(node->name, (xmlChar *)"sharecfg") == 0) + node = NULL; + } + return ((sa_group_t)node); +} + +/* + * _sa_create_group(groupname) + * + * Create a group in the document. The caller will need to deal with + * configuration store and activation. + */ + +sa_group_t +_sa_create_group(char *groupname) +{ + xmlNodePtr node = NULL; + + if (sa_valid_group_name(groupname)) { + node = xmlNewChild(sa_config_tree, NULL, + (xmlChar *)"group", NULL); + if (node != NULL) { + xmlSetProp(node, (xmlChar *)"name", (xmlChar *)groupname); + xmlSetProp(node, (xmlChar *)"state", (xmlChar *)"enabled"); + } + } + return ((sa_group_t)node); +} + +/* + * _sa_create_zfs_group(group, groupname) + * + * Create a ZFS subgroup under the specified group. This may + * eventually form the basis of general sub-groups, but is currently + * restricted to ZFS. + */ +sa_group_t +_sa_create_zfs_group(sa_group_t group, char *groupname) +{ + xmlNodePtr node = NULL; + + node = xmlNewChild((xmlNodePtr)group, NULL, + (xmlChar *)"group", NULL); + if (node != NULL) { + xmlSetProp(node, (xmlChar *)"name", (xmlChar *)groupname); + xmlSetProp(node, (xmlChar *)"state", (xmlChar *)"enabled"); + } + + return ((sa_group_t)node); +} + +/* + * sa_create_group(groupname, *error) + * + * Create a new group with groupname. Need to validate that it is a + * legal name for SMF and the construct the SMF service instance of + * svc:/network/shares/group to implement the group. All necessary + * operational properties must be added to the group at this point + * (via the SMF transaction model). + */ +sa_group_t +sa_create_group(char *groupname, int *error) +{ + xmlNodePtr node = NULL; + sa_group_t group; + int ret; + char rbacstr[256]; + + ret = SA_OK; + + if (scf_handle == NULL) { + ret = SA_SYSTEM_ERR; + goto err; + } + + group = sa_get_group(groupname); + if (group != NULL) { + ret = SA_DUPLICATE_NAME; + } else { + if (sa_valid_group_name(groupname)) { + node = xmlNewChild(sa_config_tree, NULL, + (xmlChar *)"group", NULL); + if (node != NULL) { + xmlSetProp(node, (xmlChar *)"name", (xmlChar *)groupname); + /* default to the group being enabled */ + xmlSetProp(node, (xmlChar *)"state", (xmlChar *)"enabled"); + ret = sa_create_instance(scf_handle, groupname); + if (ret == SA_OK) { + ret = sa_start_transaction(scf_handle, "operation"); + } + if (ret == SA_OK) { + ret = sa_set_property(scf_handle, "state", "enabled"); + if (ret == SA_OK) { + ret = sa_end_transaction(scf_handle); + } else { + sa_abort_transaction(scf_handle); + } + } + if (ret == SA_OK) { + /* initialize the RBAC strings */ + ret = sa_start_transaction(scf_handle, "general"); + if (ret == SA_OK) { + (void) snprintf(rbacstr, sizeof (rbacstr), "%s.%s", + SA_RBAC_MANAGE, groupname); + ret = sa_set_property(scf_handle, + "action_authorization", + rbacstr); + } + if (ret == SA_OK) { + (void) snprintf(rbacstr, sizeof (rbacstr), "%s.%s", + SA_RBAC_VALUE, groupname); + ret = sa_set_property(scf_handle, + "value_authorization", + rbacstr); + } + if (ret == SA_OK) { + ret = sa_end_transaction(scf_handle); + } else { + sa_abort_transaction(scf_handle); + } + } + if (ret != SA_OK) { + /* + * Couldn't commit the group so we need to + * undo internally. + */ + xmlUnlinkNode(node); + xmlFreeNode(node); + node = NULL; + } + } else { + ret = SA_NO_MEMORY; + } + } else { + ret = SA_INVALID_NAME; + } + } +err: + if (error != NULL) + *error = ret; + return ((sa_group_t)node); +} + +/* + * sa_remove_group(group) + * + * Remove the specified group. This deletes from the SMF repository. + * All property groups and properties are removed. + */ + +int +sa_remove_group(sa_group_t group) +{ + char *name; + int ret = SA_OK; + + name = sa_get_group_attr(group, "name"); + if (name != NULL) { + ret = sa_delete_instance(scf_handle, name); + sa_free_attr_string(name); + } + xmlUnlinkNode((xmlNodePtr)group); /* make sure unlinked */ + xmlFreeNode((xmlNodePtr)group); /* now it is gone */ + return (ret); +} + +/* + * sa_update_config() + * + * Used to update legacy files that need to be updated in bulk + * Currently, this is a placeholder and will go away in a future + * release. + */ + +int +sa_update_config() +{ + struct stat st; + + /* + * do legacy files first so we can tell when they change. + * This will go away when we start updating individual records + * rather than the whole file. + */ + update_legacy_config(); + /* update legacy timestamp */ + if (stat(SA_LEGACY_DFSTAB, &st) >= 0) { + set_legacy_timestamp(sa_config_tree, SA_LEGACY_DFSTAB, + TSTAMP(st.st_ctim)); + } + return (SA_OK); +} + +/* + * get_node_attr(node, tag) + * + * Get the speficied tag(attribute) if it exists on the node. This is + * used internally by a number of attribute oriented functions. + */ + +static char * +get_node_attr(void *nodehdl, char *tag) +{ + xmlNodePtr node = (xmlNodePtr)nodehdl; + xmlChar *name = NULL; + + if (node != NULL) { + name = xmlGetProp(node, (xmlChar *)tag); + } + return ((char *)name); +} + +/* + * get_node_attr(node, tag) + * + * Set the speficied tag(attribute) to the specified value This is + * used internally by a number of attribute oriented functions. It + * doesn't update the repository, only the internal document state. + */ + +void +set_node_attr(void *nodehdl, char *tag, char *value) +{ + xmlNodePtr node = (xmlNodePtr)nodehdl; + if (node != NULL && tag != NULL) { + if (value != NULL) { + xmlSetProp(node, (xmlChar *)tag, (xmlChar *)value); + } else { + xmlUnsetProp(node, (xmlChar *)tag); + } + } +} + +/* + * sa_get_group_attr(group, tag) + * + * Get the specied attribute, if defined, for the group. + */ + +char * +sa_get_group_attr(sa_group_t group, char *tag) +{ + return (get_node_attr((void *)group, tag)); +} + +/* + * sa_set_group_attr(group, tag, value) + * + * set the specified tag/attribute on the group using value as its + * value. + * + * This will result in setting the property in the SMF repository as + * well as in the internal document. + */ + +int +sa_set_group_attr(sa_group_t group, char *tag, char *value) +{ + int ret; + char *groupname; + + groupname = sa_get_group_attr(group, "name"); + ret = sa_get_instance(scf_handle, groupname); + if (ret == SA_OK) { + set_node_attr((void *)group, tag, value); + ret = sa_start_transaction(scf_handle, "operation"); + if (ret == SA_OK) { + ret = sa_set_property(scf_handle, tag, value); + if (ret == SA_OK) + (void) sa_end_transaction(scf_handle); + else { + sa_abort_transaction(scf_handle); + } + } + } + if (groupname != NULL) + sa_free_attr_string(groupname); + return (ret); +} + +/* + * sa_get_share_attr(share, tag) + * + * Return the value of the tag/attribute set on the specified + * share. Returns NULL if the tag doesn't exist. + */ + +char * +sa_get_share_attr(sa_share_t share, char *tag) +{ + return (get_node_attr((void *)share, tag)); +} + +/* + * sa_get_resource(group, resource) + * + * Search all the shares in the speified group for a share with a + * resource name matching the one specified. + * + * In the future, it may be advantageous to allow group to be NULL and + * search all groups but that isn't needed at present. + */ + +sa_share_t +sa_get_resource(sa_group_t group, char *resource) +{ + sa_share_t share = NULL; + char *name = NULL; + + if (resource != NULL) { + for (share = sa_get_share(group, NULL); share != NULL; + share = sa_get_next_share(share)) { + name = sa_get_share_attr(share, "resource"); + if (name != NULL) { + if (strcmp(name, resource) == 0) + break; + sa_free_attr_string(name); + name = NULL; + } + } + if (name != NULL) + sa_free_attr_string(name); + } + return ((sa_share_t)share); +} + +/* + * _sa_set_share_description(share, description) + * + * Add a description tag with text contents to the specified share. + * A separate XML tag is used rather than a property. + */ + +xmlNodePtr +_sa_set_share_description(sa_share_t share, char *content) +{ + xmlNodePtr node; + node = xmlNewChild((xmlNodePtr)share, + NULL, (xmlChar *)"description", NULL); + xmlNodeSetContent(node, (xmlChar *)content); + return (node); +} + +/* + * sa_set_share_attr(share, tag, value) + * + * Set the share attribute specified by tag to the specified value. In + * the case of "resource", enforce a no duplicates in a group rule. If + * the share is not transient, commit the changes to the repository + * else just update the share internally. + */ + +int +sa_set_share_attr(sa_share_t share, char *tag, char *value) +{ + sa_group_t group; + sa_share_t resource; + int ret = SA_OK; + + group = sa_get_parent_group(share); + + /* + * There are some attributes that may have specific + * restrictions on them. Initially, only "resource" has + * special meaning that needs to be checked. Only one instance + * of a resource name may exist within a group. + */ + + if (strcmp(tag, "resource") == 0) { + resource = sa_get_resource(group, value); + if (resource != share && resource != NULL) + ret = SA_DUPLICATE_NAME; + } + if (ret == SA_OK) { + set_node_attr((void *)share, tag, value); + if (group != NULL) { + char *type; + /* we can probably optimize this some */ + type = sa_get_share_attr(share, "type"); + if (type == NULL || strcmp(type, "transient") != 0) + ret = sa_commit_share(scf_handle, group, share); + if (type != NULL) + sa_free_attr_string(type); + } + } + return (ret); +} + +/* + * sa_get_property_attr(prop, tag) + * + * Get the value of the specified property attribute. Standard + * attributes are "type" and "value". + */ + +char * +sa_get_property_attr(sa_property_t prop, char *tag) +{ + return (get_node_attr((void *)prop, tag)); +} + +/* + * sa_get_optionset_attr(prop, tag) + * + * Get the value of the specified property attribute. Standard + * attribute is "type". + */ + +char * +sa_get_optionset_attr(sa_property_t optionset, char *tag) +{ + return (get_node_attr((void *)optionset, tag)); + +} + +/* + * sa_set_optionset_attr(optionset, tag, value) + * + * Set the specified attribute(tag) to the specified value on the + * optionset. + */ + +void +sa_set_optionset_attr(sa_group_t optionset, char *tag, char *value) +{ + set_node_attr((void *)optionset, tag, value); +} + +/* + * sa_free_attr_string(string) + * + * Free the string that was returned in one of the sa_get_*_attr() + * functions. + */ + +void +sa_free_attr_string(char *string) +{ + xmlFree((xmlChar *)string); +} + +/* + * sa_get_optionset(group, proto) + * + * Return the optionset, if it exists, that is associated with the + * specified protocol. + */ + +sa_optionset_t +sa_get_optionset(void *group, char *proto) +{ + xmlNodePtr node; + xmlChar *value = NULL; + + for (node = ((xmlNodePtr)group)->children; node != NULL; + node = node->next) { + if (xmlStrcmp(node->name, (xmlChar *)"optionset") == 0) { + value = xmlGetProp(node, (xmlChar *)"type"); + if (proto != NULL) { + if (value != NULL && + xmlStrcmp(value, (xmlChar *)proto) == 0) { + break; + } + if (value != NULL) { + xmlFree(value); + value = NULL; + } + } else { + break; + } + } + } + if (value != NULL) + xmlFree(value); + return ((sa_optionset_t)node); +} + +/* + * sa_get_next_optionset(optionset) + * + * Return the next optionset in the group. NULL if this was the last. + */ + +sa_optionset_t +sa_get_next_optionset(sa_optionset_t optionset) +{ + xmlNodePtr node; + + for (node = ((xmlNodePtr)optionset)->next; node != NULL; + node = node->next) { + if (xmlStrcmp(node->name, (xmlChar *)"optionset") == 0) { + break; + } + } + return ((sa_optionset_t)node); +} + +/* + * sa_get_security(group, sectype, proto) + * + * Return the security optionset. The internal name is a hold over + * from the implementation and will be changed before the API is + * finalized. This is really a named optionset that can be negotiated + * as a group of properties (like NFS security options). + */ + +sa_security_t +sa_get_security(sa_group_t group, char *sectype, char *proto) +{ + xmlNodePtr node; + xmlChar *value = NULL; + + for (node = ((xmlNodePtr)group)->children; node != NULL; + node = node->next) { + if (xmlStrcmp(node->name, (xmlChar *)"security") == 0) { + if (proto != NULL) { + value = xmlGetProp(node, (xmlChar *)"type"); + if (value == NULL || + (value != NULL && + xmlStrcmp(value, (xmlChar *)proto) != 0)) { + /* it doesn't match so continue */ + xmlFree(value); + value = NULL; + continue; + } + } + if (value != NULL) { + xmlFree(value); + value = NULL; + } + /* potential match */ + if (sectype != NULL) { + value = xmlGetProp(node, (xmlChar *)"sectype"); + if (value != NULL && + xmlStrcmp(value, (xmlChar *)sectype) == 0) { + break; + } + } else { + break; + } + } + if (value != NULL) { + xmlFree(value); + value = NULL; + } + } + if (value != NULL) + xmlFree(value); + return ((sa_security_t)node); +} + +/* + * sa_get_next_security(security) + * + * Get the next security optionset if one exists. + */ + +sa_security_t +sa_get_next_security(sa_security_t security) +{ + xmlNodePtr node; + + for (node = ((xmlNodePtr)security)->next; node != NULL; + node = node->next) { + if (xmlStrcmp(node->name, (xmlChar *)"security") == 0) { + break; + } + } + return ((sa_security_t)node); +} + +/* + * sa_get_property(optionset, prop) + * + * Get the property object with the name specified in prop from the + * optionset. + */ + +sa_property_t +sa_get_property(sa_optionset_t optionset, char *prop) +{ + xmlNodePtr node = (xmlNodePtr)optionset; + xmlChar *value = NULL; + + if (optionset == NULL) + return (NULL); + + for (node = node->children; node != NULL; + node = node->next) { + if (xmlStrcmp(node->name, (xmlChar *)"option") == 0) { + if (prop == NULL) + break; + value = xmlGetProp(node, (xmlChar *)"type"); + if (value != NULL && xmlStrcmp(value, (xmlChar *)prop) == 0) { + break; + } + if (value != NULL) { + xmlFree(value); + value = NULL; + } + } + } + if (value != NULL) + xmlFree(value); + if (node != NULL && xmlStrcmp(node->name, (xmlChar *)"option") != 0) { + /* avoid a non option node -- it is possible to be a text node */ + node = NULL; + } + return ((sa_property_t)node); +} + +/* + * sa_get_next_property(property) + * + * Get the next property following the specified property. NULL if + * this was the last. + */ + +sa_property_t +sa_get_next_property(sa_property_t property) +{ + xmlNodePtr node; + + for (node = ((xmlNodePtr)property)->next; node != NULL; + node = node->next) { + if (xmlStrcmp(node->name, (xmlChar *)"option") == 0) { + break; + } + } + return ((sa_property_t)node); +} + +/* + * sa_set_share_description(share, content) + * + * Set the description of share to content. + */ + +int +sa_set_share_description(sa_share_t share, char *content) +{ + xmlNodePtr node; + sa_group_t group; + int ret = SA_OK; + + for (node = ((xmlNodePtr)share)->children; node != NULL; + node = node->next) { + if (xmlStrcmp(node->name, (xmlChar *)"description") == 0) { + break; + } + } + group = sa_get_parent_group(share); + /* no existing description but want to add */ + if (node == NULL && content != NULL) { + /* add a description */ + node = _sa_set_share_description(share, content); + } else if (node != NULL && content != NULL) { + /* update a description */ + xmlNodeSetContent(node, (xmlChar *)content); + } else if (node != NULL && content == NULL) { + /* remove an existing description */ + xmlUnlinkNode(node); + xmlFreeNode(node); + } + if (group != NULL && is_persistent((sa_group_t)share)) + ret = sa_commit_share(scf_handle, group, share); + return (ret); +} + +/* + * fixproblemchars(string) + * + * don't want any newline or tab characters in the text since these + * could break display of data and legacy file formats. + */ +static void +fixproblemchars(char *str) +{ + int c; + for (c = *str; c != '\0'; c = *++str) { + if (c == '\t' || c == '\n') + *str = ' '; + else if (c == '"') + *str = '\''; + } +} + +/* + * sa_get_share_description(share) + * + * Return the description text for the specified share if it + * exists. NULL if no description exists. + */ + +char * +sa_get_share_description(sa_share_t share) +{ + xmlChar *description = NULL; + xmlNodePtr node; + + for (node = ((xmlNodePtr)share)->children; node != NULL; + node = node->next) { + if (xmlStrcmp(node->name, (xmlChar *)"description") == 0) { + break; + } + } + if (node != NULL) { + description = xmlNodeGetContent((xmlNodePtr)share); + fixproblemchars((char *)description); + } + return ((char *)description); +} + +/* + * sa_free(share_description(description) + * + * Free the description string. + */ + +void +sa_free_share_description(char *description) +{ + xmlFree((xmlChar *)description); +} + +/* + * sa_create_optionset(group, proto) + * + * Create an optionset for the specified protocol in the specied + * group. This is manifested as a property group within SMF. + */ + +sa_optionset_t +sa_create_optionset(sa_group_t group, char *proto) +{ + sa_optionset_t optionset; + sa_group_t parent = group; + + optionset = sa_get_optionset(group, proto); + if (optionset != NULL) { + /* can't have a duplicate protocol */ + optionset = NULL; + } else { + optionset = (sa_optionset_t)xmlNewChild((xmlNodePtr)group, + NULL, + (xmlChar *)"optionset", + NULL); + /* + * only put to repository if on a group and we were + * able to create an optionset. + */ + if (optionset != NULL) { + char oname[256]; + char *groupname; + char *id = NULL; + + if (sa_is_share(group)) + parent = sa_get_parent_group((sa_share_t)group); + + sa_set_optionset_attr(optionset, "type", proto); + + if (sa_is_share(group)) { + id = sa_get_share_attr((sa_share_t)group, "id"); + } + (void) sa_optionset_name(optionset, oname, + sizeof (oname), id); + groupname = sa_get_group_attr(parent, "name"); + if (groupname != NULL && is_persistent(group)) { + (void) sa_get_instance(scf_handle, groupname); + sa_free_attr_string(groupname); + (void) sa_create_pgroup(scf_handle, oname); + } + if (id != NULL) + sa_free_attr_string(id); + } + } + return (optionset); +} + +/* + * sa_get_property_parent(property) + * + * Given a property, return the object it is a property of. This will + * be an optionset of some type. + */ + +static sa_optionset_t +sa_get_property_parent(sa_property_t property) +{ + xmlNodePtr node = NULL; + + if (property != NULL) { + node = ((xmlNodePtr)property)->parent; + } + return ((sa_optionset_t)node); +} + +/* + * sa_get_optionset_parent(optionset) + * + * Return the parent of the specified optionset. This could be a group + * or a share. + */ + +static sa_group_t +sa_get_optionset_parent(sa_optionset_t optionset) +{ + xmlNodePtr node = NULL; + + if (optionset != NULL) { + node = ((xmlNodePtr)optionset)->parent; + } + return ((sa_group_t)node); +} + +/* + * zfs_needs_update(share) + * + * In order to avoid making multiple updates to a ZFS share when + * setting properties, the share attribute "changed" will be set to + * true when a property is added or modifed. When done adding + * properties, we can then detect that an update is needed. We then + * clear the state here to detect additional changes. + */ + +static int +zfs_needs_update(sa_share_t share) +{ + char *attr; + int result = 0; + + attr = sa_get_share_attr(share, "changed"); + if (attr != NULL) { + sa_free_attr_string(attr); + result = 1; + } + set_node_attr((void *)share, "changed", NULL); + return (result); +} + +/* + * zfs_set_update(share) + * + * Set the changed attribute of the share to true. + */ + +static void +zfs_set_update(sa_share_t share) +{ + set_node_attr((void *)share, "changed", "true"); +} + +/* + * sa_commit_properties(optionset, clear) + * + * Check if SMF or ZFS config and either update or abort the pending + * changes. + */ + +int +sa_commit_properties(sa_optionset_t optionset, int clear) +{ + sa_group_t group; + sa_group_t parent; + int zfs = 0; + int needsupdate = 0; + int ret = SA_OK; + + group = sa_get_optionset_parent(optionset); + if (group != NULL && (sa_is_share(group) || is_zfs_group(group))) { + /* only update ZFS if on a share */ + parent = sa_get_parent_group(group); + zfs++; + if (parent != NULL && is_zfs_group(parent)) { + needsupdate = zfs_needs_update(group); + } else { + zfs = 0; + } + } + if (zfs) { + if (!clear && needsupdate) + ret = sa_zfs_update((sa_share_t)group); + } else { + if (clear) + (void) sa_abort_transaction(scf_handle); + else + ret = sa_end_transaction(scf_handle); + } + return (ret); +} + +/* + * sa_destroy_optionset(optionset) + * + * Remove the optionset from its group. Update the repostory to + * reflect this change. + */ + +int +sa_destroy_optionset(sa_optionset_t optionset) +{ + char name[256]; + int len; + int ret; + char *id = NULL; + sa_group_t group; + int ispersist = 1; + + /* now delete the prop group */ + group = sa_get_optionset_parent(optionset); + if (group != NULL && sa_is_share(group)) { + ispersist = is_persistent(group); + id = sa_get_share_attr((sa_share_t)group, "id"); + } + if (ispersist) { + len = sa_optionset_name(optionset, name, sizeof (name), id); + if (len > 0) { + ret = sa_delete_pgroup(scf_handle, name); + } + } + xmlUnlinkNode((xmlNodePtr)optionset); + xmlFreeNode((xmlNodePtr)optionset); + if (id != NULL) + sa_free_attr_string(id); + return (ret); +} + +/* private to the implementation */ +int +_sa_remove_optionset(sa_optionset_t optionset) +{ + int ret = SA_OK; + + xmlUnlinkNode((xmlNodePtr)optionset); + xmlFreeNode((xmlNodePtr)optionset); + return (ret); +} + +/* + * sa_create_security(group, sectype, proto) + * + * Create a security optionset (one that has a type name and a + * proto). Security is left over from a pure NFS implementation. The + * naming will change in the future when the API is released. + */ +sa_security_t +sa_create_security(sa_group_t group, char *sectype, char *proto) +{ + sa_security_t security; + char *id = NULL; + sa_group_t parent; + char *groupname = NULL; + + if (group != NULL && sa_is_share(group)) { + id = sa_get_share_attr((sa_share_t)group, "id"); + parent = sa_get_parent_group(group); + if (parent != NULL) + groupname = sa_get_group_attr(parent, "name"); + } else if (group != NULL) { + groupname = sa_get_group_attr(group, "name"); + } + + security = sa_get_security(group, sectype, proto); + if (security != NULL) { + /* can't have a duplicate security option */ + security = NULL; + } else { + security = (sa_security_t)xmlNewChild((xmlNodePtr)group, + NULL, + (xmlChar *)"security", + NULL); + if (security != NULL) { + char oname[256]; + sa_set_security_attr(security, "type", proto); + + sa_set_security_attr(security, "sectype", sectype); + (void) sa_security_name(security, oname, + sizeof (oname), id); + if (groupname != NULL && is_persistent(group)) { + (void) sa_get_instance(scf_handle, groupname); + (void) sa_create_pgroup(scf_handle, oname); + } + } + } + if (groupname != NULL) + sa_free_attr_string(groupname); + return (security); +} + +/* + * sa_destroy_security(security) + * + * Remove the specified optionset from the document and the + * configuration. + */ + +int +sa_destroy_security(sa_security_t security) +{ + char name[256]; + int len; + int ret = SA_OK; + char *id = NULL; + sa_group_t group; + int iszfs = 0; + int ispersist = 1; + + group = sa_get_optionset_parent(security); + + if (group != NULL) + iszfs = sa_group_is_zfs(group); + + if (group != NULL && !iszfs) { + if (sa_is_share(group)) + ispersist = is_persistent(group); + id = sa_get_share_attr((sa_share_t)group, "id"); + } + if (ispersist) { + len = sa_security_name(security, name, sizeof (name), id); + if (!iszfs && len > 0) { + ret = sa_delete_pgroup(scf_handle, name); + } + } + xmlUnlinkNode((xmlNodePtr)security); + xmlFreeNode((xmlNodePtr)security); + if (iszfs) { + ret = sa_zfs_update(group); + } + if (id != NULL) + sa_free_attr_string(id); + return (ret); +} + +/* + * sa_get_security_attr(optionset, tag) + * + * Return the specified attribute value from the optionset. + */ + +char * +sa_get_security_attr(sa_property_t optionset, char *tag) +{ + return (get_node_attr((void *)optionset, tag)); + +} + +/* + * sa_set_security_attr(optionset, tag, value) + * + * Set the optioset attribute specied by tag to the specified value. + */ + +void +sa_set_security_attr(sa_group_t optionset, char *tag, char *value) +{ + set_node_attr((void *)optionset, tag, value); +} + +/* + * is_nodetype(node, type) + * + * Check to see if node is of the type specified. + */ + +static int +is_nodetype(void *node, char *type) +{ + return (strcmp((char *)((xmlNodePtr)node)->name, type) == 0); +} + +/* + * sa_set_prop_by_prop(optionset, group, prop, type) + * + * Add/remove/update the specified property prop into the optionset or + * share. If a share, sort out which property group based on GUID. In + * all cases, the appropriate transaction is set (or ZFS share is + * marked as needing an update) + */ + +#define SA_PROP_OP_REMOVE 1 +#define SA_PROP_OP_ADD 2 +#define SA_PROP_OP_UPDATE 3 +static int +sa_set_prop_by_prop(sa_optionset_t optionset, sa_group_t group, + sa_property_t prop, int type) +{ + char *name; + char *valstr; + int ret = SA_OK; + scf_transaction_entry_t *entry; + scf_value_t *value; + int opttype; /* 1 == optionset, 0 == security */ + char *id = NULL; + int iszfs = 0; + int isshare = 0; + sa_group_t parent = NULL; + + if (!is_persistent(group)) { + /* + * if the group/share is not persistent we don't need + * to do anything here + */ + return (SA_OK); + } + name = sa_get_property_attr(prop, "type"); + valstr = sa_get_property_attr(prop, "value"); + entry = scf_entry_create(scf_handle->handle); + opttype = is_nodetype((void *)optionset, "optionset"); + + if (valstr != NULL && entry != NULL) { + if (sa_is_share(group)) { + isshare = 1; + parent = sa_get_parent_group(group); + if (parent != NULL) { + iszfs = is_zfs_group(parent); + } + } else { + iszfs = is_zfs_group(group); + } + if (!iszfs) { + if (scf_handle->trans == NULL) { + char oname[256]; + char *groupname = NULL; + if (isshare) { + if (parent != NULL) { + groupname = sa_get_group_attr(parent, "name"); + } + id = sa_get_share_attr((sa_share_t)group, "id"); + } else { + groupname = sa_get_group_attr(group, "name"); + } + if (groupname != NULL) { + ret = sa_get_instance(scf_handle, groupname); + sa_free_attr_string(groupname); + } + if (opttype) + (void) sa_optionset_name(optionset, oname, + sizeof (oname), id); + else + (void) sa_security_name(optionset, oname, + sizeof (oname), id); + ret = sa_start_transaction(scf_handle, oname); + } + if (ret == SA_OK) { + switch (type) { + case SA_PROP_OP_REMOVE: + ret = scf_transaction_property_delete(scf_handle->trans, + entry, + name); + break; + case SA_PROP_OP_ADD: + case SA_PROP_OP_UPDATE: + value = scf_value_create(scf_handle->handle); + if (value != NULL) { + if (type == SA_PROP_OP_ADD) + ret = scf_transaction_property_new( + scf_handle->trans, + entry, + name, + SCF_TYPE_ASTRING); + else + ret = scf_transaction_property_change( + scf_handle->trans, + entry, + name, + SCF_TYPE_ASTRING); + if (ret == 0) { + ret = scf_value_set_astring(value, valstr); + if (ret == 0) + ret = scf_entry_add_value(entry, value); + if (ret != 0) { + scf_value_destroy(value); + ret = SA_SYSTEM_ERR; + } + } else { + scf_entry_destroy(entry); + ret = SA_SYSTEM_ERR; + } + break; + } + } + } + } else { + /* + * ZFS update. The calling function would have updated + * the internal XML structure. Just need to flag it as + * changed for ZFS. + */ + zfs_set_update((sa_share_t)group); + } + } + + if (name != NULL) + sa_free_attr_string(name); + if (valstr != NULL) + sa_free_attr_string(valstr); + else if (entry != NULL) + scf_entry_destroy(entry); + + if (ret == -1) + ret = SA_SYSTEM_ERR; + + return (ret); +} + +/* + * sa_create_property(name, value) + * + * Create a new property with the specified name and value. + */ + +sa_property_t +sa_create_property(char *name, char *value) +{ + xmlNodePtr node; + + node = xmlNewNode(NULL, (xmlChar *)"option"); + if (node != NULL) { + xmlSetProp(node, (xmlChar *)"type", (xmlChar *)name); + xmlSetProp(node, (xmlChar *)"value", (xmlChar *)value); + } + return ((sa_property_t)node); +} + +/* + * sa_add_property(object, property) + * + * Add the specified property to the object. Issue the appropriate + * transaction or mark a ZFS object as needing an update. + */ + +int +sa_add_property(void *object, sa_property_t property) +{ + int ret = SA_OK; + sa_group_t parent; + sa_group_t group; + char *proto; + + proto = sa_get_optionset_attr(object, "type"); + if (property != NULL) { + if ((ret = sa_valid_property(object, proto, property)) == SA_OK) { + property = (sa_property_t)xmlAddChild((xmlNodePtr)object, + (xmlNodePtr)property); + } else { + if (proto != NULL) + sa_free_attr_string(proto); + return (ret); + } + } + + if (proto != NULL) + sa_free_attr_string(proto); + + parent = sa_get_parent_group(object); + if (!is_persistent(parent)) { + return (ret); + } + + if (sa_is_share(parent)) + group = sa_get_parent_group(parent); + else + group = parent; + + if (property == NULL) + ret = SA_NO_MEMORY; + else { + char oname[256]; + + if (!is_zfs_group(group)) { + char *id = NULL; + if (sa_is_share((sa_group_t)parent)) { + id = sa_get_share_attr((sa_share_t)parent, "id"); + } + if (scf_handle->trans == NULL) { + if (is_nodetype(object, "optionset")) + (void) sa_optionset_name((sa_optionset_t)object, + oname, sizeof (oname), id); + else + (void) sa_security_name((sa_optionset_t)object, + oname, sizeof (oname), id); + ret = sa_start_transaction(scf_handle, oname); + } + if (ret == SA_OK) { + char *name; + char *value; + name = sa_get_property_attr(property, "type"); + value = sa_get_property_attr(property, "value"); + if (name != NULL && value != NULL) { + if (scf_handle->scf_state == SCH_STATE_INIT) + ret = sa_set_property(scf_handle, name, value); + } else + ret = SA_CONFIG_ERR; + if (name != NULL) + sa_free_attr_string(name); + if (value != NULL) + sa_free_attr_string(value); + } + if (id != NULL) + sa_free_attr_string(id); + } else { + /* + * ZFS is a special case. We do want to allow editing + * property/security lists since we can have a better + * syntax and we also want to keep things consistent + * when possible. + * + * Right now, we defer until the sa_commit_properties + * so we can get them all at once. We do need to mark + * the share as "changed" + */ + zfs_set_update((sa_share_t)parent); + } + } + return (ret); +} + +/* + * sa_remove_property(property) + * + * Remove the specied property from its containing object. Update the + * repository as appropriate. + */ + +int +sa_remove_property(sa_property_t property) +{ + int ret = SA_OK; + + if (property != NULL) { + sa_optionset_t optionset; + sa_group_t group; + optionset = sa_get_property_parent(property); + if (optionset != NULL) { + group = sa_get_optionset_parent(optionset); + if (group != NULL) { + ret = sa_set_prop_by_prop(optionset, group, property, + SA_PROP_OP_REMOVE); + } + } + xmlUnlinkNode((xmlNodePtr)property); + xmlFreeNode((xmlNodePtr)property); + } else { + ret = SA_NO_SUCH_PROP; + } + return (ret); +} + +/* + * sa_update_property(property, value) + * + * Update the specified property to the new value. If value is NULL, + * we currently treat this as a remove. + */ + +int +sa_update_property(sa_property_t property, char *value) +{ + int ret = SA_OK; + if (value == NULL) { + return (sa_remove_property(property)); + } else { + sa_optionset_t optionset; + sa_group_t group; + set_node_attr((void *)property, "value", value); + optionset = sa_get_property_parent(property); + if (optionset != NULL) { + group = sa_get_optionset_parent(optionset); + if (group != NULL) { + ret = sa_set_prop_by_prop(optionset, group, property, + SA_PROP_OP_UPDATE); + } + } else { + ret = SA_NO_SUCH_PROP; + } + } + return (ret); +} + +/* + * _sa_get_next_error(node) + * + * Get the next (first if node==NULL) error node in the + * document. "error" nodes are added if there were syntax errors + * during parsing of the /etc/dfs/dfstab file. They are preserved in + * comments and recreated in the doc on the next parse. + */ + +xmlNodePtr +_sa_get_next_error(xmlNodePtr node) +{ + if (node == NULL) { + for (node = sa_config_tree->xmlChildrenNode; + node != NULL; node = node->next) + if (xmlStrcmp(node->name, (xmlChar *)"error") == 0) + return (node); + } else { + for (node = node->next; node != NULL; node = node->next) + if (xmlStrcmp(node->name, (xmlChar *)"error") == 0) + return (node); + } + return (node); +} + +/* + * sa_get_protocol_property(propset, prop) + * + * Get the specified protocol specific property. These are global to + * the protocol and not specific to a group or share. + */ + +sa_property_t +sa_get_protocol_property(sa_protocol_properties_t propset, char *prop) +{ + xmlNodePtr node = (xmlNodePtr)propset; + xmlChar *value = NULL; + + for (node = node->children; node != NULL; + node = node->next) { + if (xmlStrcmp(node->name, (xmlChar *)"option") == 0) { + if (prop == NULL) + break; + value = xmlGetProp(node, (xmlChar *)"type"); + if (value != NULL && + xmlStrcasecmp(value, (xmlChar *)prop) == 0) { + break; + } + if (value != NULL) { + xmlFree(value); + value = NULL; + } + } + } + if (value != NULL) + xmlFree(value); + if (node != NULL && xmlStrcmp(node->name, (xmlChar *)"option") != 0) { + /* avoid a non option node -- it is possible to be a text node */ + node = NULL; + } + return ((sa_property_t)node); +} + +/* + * sa_get_next_protocol_property(prop) + * + * Get the next protocol specific property in the list. + */ + +sa_property_t +sa_get_next_protocol_property(sa_property_t prop) +{ + xmlNodePtr node; + + for (node = ((xmlNodePtr)prop)->next; node != NULL; + node = node->next) { + if (xmlStrcmp(node->name, (xmlChar *)"option") == 0) { + break; + } + } + return ((sa_property_t)node); +} + +/* + * sa_set_protocol_property(prop, value) + * + * Set the specified property to have the new value. The protocol + * specific plugin will then be called to update the property. + */ + +int +sa_set_protocol_property(sa_property_t prop, char *value) +{ + sa_protocol_properties_t propset; + char *proto; + int ret = SA_INVALID_PROTOCOL; + + propset = ((xmlNodePtr)prop)->parent; + if (propset != NULL) { + proto = sa_get_optionset_attr(propset, "type"); + if (proto != NULL) { + set_node_attr((xmlNodePtr)prop, "value", value); + ret = sa_proto_set_property(proto, prop); + sa_free_attr_string(prop); + } + } + return (ret); +} + +/* + * sa_add_protocol_property(propset, prop) + * + * Add a new property to the protocol sepcific property set. + */ + +int +sa_add_protocol_property(sa_protocol_properties_t propset, sa_property_t prop) +{ + xmlNodePtr node; + + /* should check for legitimacy */ + node = xmlAddChild((xmlNodePtr)propset, (xmlNodePtr)prop); + if (node != NULL) + return (SA_OK); + return (SA_NO_MEMORY); +} + +/* + * sa_create_protocol_properties(proto) + * + * Create a protocol specifity property set. + */ + +sa_protocol_properties_t +sa_create_protocol_properties(char *proto) +{ + xmlNodePtr node; + node = xmlNewNode(NULL, (xmlChar *)"propertyset"); + if (node != NULL) { + xmlSetProp(node, (xmlChar *)"type", (xmlChar *)proto); + } + return (node); +} diff --git a/usr/src/lib/libshare/common/libshare.h b/usr/src/lib/libshare/common/libshare.h new file mode 100644 index 0000000000..1c172f6f2a --- /dev/null +++ b/usr/src/lib/libshare/common/libshare.h @@ -0,0 +1,217 @@ +/* + * 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 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * basic API declarations for share management + */ + +#ifndef _LIBSHARE_H +#define _LIBSHARE_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Basic datatypes for most functions + */ +typedef void *sa_group_t; +typedef void *sa_share_t; +typedef void *sa_property_t; +typedef void *sa_optionset_t; +typedef void *sa_security_t; +typedef void *sa_protocol_properties_t; + +typedef void *sa_handle_t; /* opaque handle to access core functions */ + +/* + * defined error values + */ + +#define SA_OK 0 +#define SA_NO_SUCH_PATH 1 /* provided path doesn't exist */ +#define SA_NO_MEMORY 2 /* no memory for data structures */ +#define SA_DUPLICATE_NAME 3 /* object name is already in use */ +#define SA_BAD_PATH 4 /* not a full path */ +#define SA_NO_SUCH_GROUP 5 /* group is not defined */ +#define SA_CONFIG_ERR 6 /* system configuration error */ +#define SA_SYSTEM_ERR 7 /* system error, use errno */ +#define SA_SYNTAX_ERR 8 /* syntax error on command line */ +#define SA_NO_PERMISSION 9 /* no permission for operation */ +#define SA_BUSY 10 /* resource is busy */ +#define SA_NO_SUCH_PROP 11 /* property doesn't exist */ +#define SA_INVALID_NAME 12 /* name of object is invalid */ +#define SA_INVALID_PROTOCOL 13 /* specified protocol not valid */ +#define SA_NOT_ALLOWED 14 /* operation not allowed */ +#define SA_BAD_VALUE 15 /* bad value for property */ +#define SA_INVALID_SECURITY 16 /* invalid security type */ +#define SA_NO_SUCH_SECURITY 17 /* security set not found */ +#define SA_VALUE_CONFLICT 18 /* property value conflict */ +#define SA_NOT_IMPLEMENTED 19 /* plugin interface not implemented */ +#define SA_INVALID_PATH 20 /* path is sub-dir of existing share */ +#define SA_NOT_SUPPORTED 21 /* operation not supported for proto */ +#define SA_PROP_SHARE_ONLY 22 /* property valid on share only */ +#define SA_NOT_SHARED 23 /* path is not shared */ + +/* API Initialization */ +#define SA_INIT_SHARE_API 0x0001 /* init share specific interface */ +#define SA_INIT_CONTROL_API 0x0002 /* init control specific interface */ + +/* not part of API returns */ +#define SA_LEGACY_ERR 32 /* share/unshare error return */ + +/* + * other defined values + */ + +#define SA_MAX_NAME_LEN 100 /* must fit service instance name */ +#define SA_SHARE_PERMANENT 2 /* share goes to repository */ +#define SA_SHARE_LEGACY 1 /* share is in dfstab only */ +#define SA_SHARE_TRANSIENT 0 /* shared but not across reboot */ + +/* RBAC related */ +#define SA_RBAC_MANAGE "solaris.smf.manage.shares" +#define SA_RBAC_VALUE "solaris.smf.value.shares" + +/* + * legacy files + */ + +#define SA_LEGACY_DFSTAB "/etc/dfs/dfstab" +#define SA_LEGACY_SHARETAB "/etc/dfs/sharetab" + +/* + * SMF related + */ + +#define SA_SVC_FMRI_BASE "svc:/network/shares/group" + +/* initialization */ +extern void sa_init(int); +extern void sa_fini(void); +extern int sa_update_config(void); +extern char *sa_errorstr(int); + +/* protocol names */ +extern int sa_get_protocols(char ***); +extern int sa_valid_protocol(char *); + +/* group control (create, remove, etc) */ +extern sa_group_t sa_create_group(char *, int *); +extern int sa_remove_group(sa_group_t); +extern sa_group_t sa_get_group(char *); +extern sa_group_t sa_get_next_group(sa_group_t); +extern char *sa_get_group_attr(sa_group_t, char *); +extern int sa_set_group_attr(sa_group_t, char *, char *); +extern sa_group_t sa_get_sub_group(sa_group_t); +extern int sa_valid_group_name(char *); + +/* share control */ +extern sa_share_t sa_add_share(sa_group_t, char *, int, int *); +extern int sa_check_path(sa_group_t, char *); +extern int sa_move_share(sa_group_t, sa_share_t); +extern int sa_remove_share(sa_share_t); +extern sa_share_t sa_get_share(sa_group_t, char *); +extern sa_share_t sa_get_resource(sa_group_t, char *); +extern sa_share_t sa_find_share(char *); +extern sa_share_t sa_get_next_share(sa_share_t); +extern char *sa_get_share_attr(sa_share_t, char *); +extern char *sa_get_share_description(sa_share_t); +extern sa_group_t sa_get_parent_group(sa_share_t); +extern int sa_set_share_attr(sa_share_t, char *, char *); +extern int sa_set_share_description(sa_share_t, char *); +extern int sa_enable_share(sa_group_t, char *); +extern int sa_disable_share(sa_group_t, char *); +extern int sa_is_share(void *); + +/* data structure free calls */ +extern void sa_free_attr_string(char *); +extern void sa_free_share_description(char *); + +/* optionset control */ +extern sa_optionset_t sa_get_optionset(sa_group_t, char *); +extern sa_optionset_t sa_get_next_optionset(sa_group_t); +extern char *sa_get_optionset_attr(sa_optionset_t, char *); +extern void sa_set_optionset_attr(sa_optionset_t, char *, char *); +extern sa_optionset_t sa_create_optionset(sa_group_t, char *); +extern int sa_destroy_optionset(sa_optionset_t); +extern sa_optionset_t sa_get_derived_optionset(void *, char *, int); +extern void sa_free_derived_optionset(sa_optionset_t); + +/* property functions */ +extern sa_optionset_t sa_get_property(sa_optionset_t, char *); +extern sa_optionset_t sa_get_next_property(sa_group_t); +extern char *sa_get_property_attr(sa_property_t, char *); +extern sa_property_t sa_create_property(char *, char *); +extern int sa_add_property(void *, sa_property_t); +extern int sa_update_property(sa_property_t, char *); +extern int sa_remove_property(sa_property_t); +extern int sa_commit_properties(sa_optionset_t, int); +extern int sa_valid_property(void *, char *, sa_property_t); + +/* security control */ +extern sa_security_t sa_get_security(sa_group_t, char *, char *); +extern sa_security_t sa_get_next_security(sa_security_t); +extern char *sa_get_security_attr(sa_optionset_t, char *); +extern sa_security_t sa_create_security(sa_group_t, char *, char *); +extern int sa_destroy_security(sa_security_t); +extern void sa_set_security_attr(sa_security_t, char *, char *); +extern sa_optionset_t sa_get_all_security_types(void *, char *, int); +extern sa_security_t sa_get_derived_security(void *, char *, char *, int); +extern void sa_free_derived_security(sa_security_t); + +/* protocol specific interfaces */ +extern int sa_parse_legacy_options(sa_group_t, char *, char *); +extern char *sa_proto_legacy_format(char *, sa_group_t, int); +extern int sa_is_security(char *, char *); +extern sa_protocol_properties_t sa_proto_get_properties(char *); +extern sa_property_t sa_get_protocol_property(sa_protocol_properties_t, char *); +extern sa_property_t sa_get_next_protocol_property(sa_property_t); +extern int sa_set_protocol_property(sa_property_t, char *); +extern char *sa_get_protocol_status(char *); +extern void sa_format_free(char *); +extern sa_protocol_properties_t sa_create_protocol_properties(char *); +extern int sa_add_protocol_property(sa_protocol_properties_t, sa_property_t); +extern int sa_proto_valid_prop(char *, sa_property_t, sa_optionset_t); +extern int sa_proto_valid_space(char *, char *); +extern char *sa_proto_space_alias(char *, char *); + +/* handle legacy (dfstab/sharetab) files */ +extern int sa_delete_legacy(sa_share_t); +extern int sa_update_legacy(sa_share_t, char *); +extern int sa_update_sharetab(sa_share_t, char *); +extern int sa_delete_sharetab(char *, char *); + +/* ZFS functions */ +extern int sa_zfs_is_shared(char *); +extern int sa_group_is_zfs(sa_group_t); +#ifdef __cplusplus +} +#endif + +#endif /* _LIBSHARE_H */ diff --git a/usr/src/lib/libshare/common/libshare_impl.h b/usr/src/lib/libshare/common/libshare_impl.h new file mode 100644 index 0000000000..0bb846e863 --- /dev/null +++ b/usr/src/lib/libshare/common/libshare_impl.h @@ -0,0 +1,143 @@ +/* + * 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 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * basic declarations for implementation of the share management + * libraries. + */ + +#ifndef _LIBSHARE_IMPL_H +#define _LIBSHARE_IMPL_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <libshare.h> +#include <libscf.h> +#include <scfutil.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* directory to find plugin modules in */ +#define SA_LIB_DIR "/usr/lib/fs" + +/* default group name for dfstab file */ +#define SA_DEFAULT_FILE_GRP "sys" + +typedef void *sa_phandle_t; + +#define SA_PLUGIN_VERSION 1 +struct sa_plugin_ops { + int sa_version; + char *sa_protocol; /* protocol name */ + int (*sa_init)(); + void (*sa_fini)(); + int (*sa_share)(sa_share_t); /* start sharing */ + int (*sa_unshare)(char *); /* stop sharing */ + int (*sa_valid_prop)(sa_property_t, sa_optionset_t); + int (*sa_valid_space)(char *); /* is name valid optionspace? */ + int (*sa_security_prop)(char *); /* property is security */ + int (*sa_legacy_opts)(sa_group_t, char *); /* parse legacy opts */ + char *(*sa_legacy_format)(sa_group_t, int); + int (*sa_set_proto_prop)(sa_property_t); + sa_protocol_properties_t (*sa_get_proto_set)(); + char *(*sa_get_proto_status)(); + char *(*sa_space_alias)(char *); + int (*sa_update_legacy)(sa_share_t); + int (*sa_delete_legacy)(sa_share_t); + int (*sa_run_command)(int, int, char **); /* proto specific */ + int (*sa_command_help)(); +}; + +struct sa_proto_handle { + int sa_num_proto; + char **sa_proto; + struct sa_plugin_ops **sa_ops; +}; + +typedef struct propertylist { + struct propertylist *pl_next; + int pl_type; + union propval { + sa_optionset_t pl_optionset; + sa_security_t pl_security; + void *pl_void; + } pl_value; +} property_list_t; + +extern int sa_proto_share(char *, sa_share_t); +extern int sa_proto_unshare(char *, char *); +extern int sa_proto_valid_prop(char *, sa_property_t, sa_optionset_t); +extern int sa_proto_security_prop(char *, char *); +extern int sa_proto_legacy_opts(char *, sa_group_t, char *); + +/* internal utility functions */ +extern sa_optionset_t sa_get_derived_optionset(sa_group_t, char *, int); +extern void sa_free_derived_optionset(sa_optionset_t); +extern sa_optionset_t sa_get_all_security_types(void *, char *, int); +extern sa_security_t sa_get_derived_security(void *, char *, char *, int); +extern void sa_free_derived_security(sa_security_t); +extern sa_protocol_properties_t sa_create_protocol_properties(char *); +extern int sa_start_transaction(scfutilhandle_t *, char *); +extern int sa_end_transaction(scfutilhandle_t *); +extern void sa_abort_transaction(scfutilhandle_t *); +extern int sa_commit_share(scfutilhandle_t *, sa_group_t, sa_share_t); +extern int sa_set_property(scfutilhandle_t *, char *, char *); +extern void sa_free_fstype(char *fstyp); +extern int sa_delete_share(scfutilhandle_t *, sa_group_t, sa_share_t); +extern int sa_delete_instance(scfutilhandle_t *, char *); +extern int sa_create_pgroup(scfutilhandle_t *, char *); +extern int sa_delete_pgroup(scfutilhandle_t *, char *); + +/* ZFS functions */ +extern int sa_get_zfs_shares(char *); +extern int sa_zfs_update(sa_share_t); + +/* plugin specific functions */ +extern int proto_plugin_init(); +extern int sa_proto_set_property(char *, sa_property_t); +extern int sa_proto_delete_legacy(char *, sa_share_t); +extern int sa_proto_update_legacy(char *, sa_share_t); + +#define PL_TYPE_PROPERTY 0 +#define PL_TYPE_SECURITY 1 + +/* values only used by the internal dfstab/sharetab parser */ +#define SA_SHARE_PARSER 0x1000 + +/* plugin handler only */ +struct sa_proto_plugin { + struct sa_proto_plugin *plugin_next; + struct sa_plugin_ops *plugin_ops; + void *plugin_handle; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBSHARE_IMPL_H */ diff --git a/usr/src/lib/libshare/common/libshare_zfs.c b/usr/src/lib/libshare/common/libshare_zfs.c new file mode 100644 index 0000000000..0a8f327edd --- /dev/null +++ b/usr/src/lib/libshare/common/libshare_zfs.c @@ -0,0 +1,496 @@ +/* + * 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 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include "libfsmgt.h" +#include <libzfs.h> +#include <string.h> +#include <libshare.h> +#include "libshare_impl.h" + +extern sa_share_t _sa_add_share(sa_group_t, char *, int, int *); +extern sa_group_t _sa_create_zfs_group(sa_group_t, char *); +extern char *sa_fstype(char *); +extern void set_node_attr(void *, char *, char *); +extern int sa_is_share(void *); +/* + * File system specific code for ZFS + */ + +/* + * get_zfs_dataset(path) + * + * get the name of the ZFS dataset the path is equivalent to. The + * dataset name is used for get/set of ZFS properties since libzfs + * requires a dataset to do a zfs_open(). + */ + +static char * +get_zfs_dataset(char *path) +{ + fs_mntlist_t *list; + fs_mntlist_t *cur; + int err; + char *dataset = NULL; + + list = fs_get_filtered_mount_list(NULL, NULL, "zfs", NULL, + NULL, 0, &err); + for (cur = list; cur != NULL; cur = cur->next) { + if (strcmp(path, cur->mountp) == 0 || + strncmp(path, cur->mountp, strlen(cur->mountp)) == 0) { + /* + * we want the longest resource so keep trying. This + * check avoids dropping out on a partial match. ZFS + * resources are ordered when mounted in order to + * ensure inheritence of properties. + */ + dataset = cur->resource; + } + } + if (dataset != NULL) { + dataset = strdup(dataset); + } + fs_free_mount_list(list); + return (dataset); +} + +/* + * get_zfs_property(dataset, property) + * + * Get the file system property specified from the ZFS dataset. + */ + +static char * +get_zfs_property(char *dataset, zfs_prop_t property) +{ + zfs_handle_t *handle = NULL; + char shareopts[ZFS_MAXPROPLEN]; + libzfs_handle_t *libhandle; + + libhandle = libzfs_init(); + if (libhandle != NULL) { + handle = zfs_open(libhandle, dataset, ZFS_TYPE_FILESYSTEM); + if (handle != NULL) { + if (zfs_prop_get(handle, property, shareopts, + sizeof (shareopts), NULL, NULL, 0, + FALSE) == 0) { + zfs_close(handle); + libzfs_fini(libhandle); + return (strdup(shareopts)); + } + zfs_close(handle); + } + libzfs_fini(libhandle); + } + return (NULL); +} + +/* + * sa_zfs_is_shared(path) + * + * Check to see if the ZFS path provided has the sharenfs option set + * or not. + */ + +int +sa_zfs_is_shared(char *path) +{ + int ret = 0; + char *dataset; + zfs_handle_t *handle = NULL; + char shareopts[ZFS_MAXPROPLEN]; + libzfs_handle_t *libhandle; + + dataset = get_zfs_dataset(path); + if (dataset != NULL) { + libhandle = libzfs_init(); + if (libhandle != NULL) { + handle = zfs_open(libhandle, dataset, ZFS_TYPE_FILESYSTEM); + if (handle != NULL) { + if (zfs_prop_get(handle, ZFS_PROP_SHARENFS, shareopts, + sizeof (shareopts), NULL, NULL, 0, + FALSE) == 0 && + strcmp(shareopts, "off") != 0) + ret = 1; /* it is shared */ + zfs_close(handle); + } + libzfs_fini(libhandle); + } + free(dataset); + } + return (ret); +} + +/* + * find_or_create_group(groupname, proto, *err) + * + * While walking the ZFS tree, we need to add shares to a defined + * group. If the group doesn't exist, create it first, making sure it + * is marked as a ZFS group. + * + * Not that all ZFS shares are in a subgroup of the top level group + * "zfs". + */ + +static sa_group_t +find_or_create_group(char *groupname, char *proto, int *err) +{ + sa_group_t group; + sa_optionset_t optionset; + int ret = SA_OK; + + /* + * we check to see if the "zfs" group exists. Since this + * should be the top level group, we don't want the + * parent. This is to make sure the zfs group has been created + * and to created if it hasn't been. + */ + group = sa_get_group(groupname); + if (group == NULL) { + group = sa_create_group(groupname, &ret); + if (group != NULL) + ret = sa_set_group_attr(group, "zfs", "true"); + } + if (group != NULL) { + if (proto != NULL) { + optionset = sa_get_optionset(group, proto); + if (optionset == NULL) { + optionset = sa_create_optionset(group, proto); + } else { + char **protolist; + int numprotos, i; + numprotos = sa_get_protocols(&protolist); + for (i = 0; i < numprotos; i++) { + optionset = sa_create_optionset(group, protolist[i]); + } + if (protolist != NULL) + free(protolist); + } + } + } + if (err != NULL) + *err = ret; + return (group); +} + +/* + * sa_get_zfs_shares(groupname) + * + * Walk the mnttab for all zfs mounts and determine which are + * shared. Find or create the appropriate group/sub-group to contain + * the shares. + * + * All shares are in a sub-group that will hold the properties. This + * allows representing the inherited property model. + */ + +int +sa_get_zfs_shares(char *groupname) +{ + sa_group_t group; + sa_group_t zfsgroup; + int legacy = 0; + int err; + fs_mntlist_t *list; + fs_mntlist_t *cur; + zfs_handle_t *handle = NULL; + char shareopts[ZFS_MAXPROPLEN]; + sa_share_t share; + zfs_source_t source; + char sourcestr[ZFS_MAXPROPLEN]; + libzfs_handle_t *libhandle; + + /* + * if we can't access libzfs, don't bother doing anything. + */ + libhandle = libzfs_init(); + if (libhandle == NULL) + return (SA_SYSTEM_ERR); + + zfsgroup = find_or_create_group(groupname, "nfs", &err); + if (zfsgroup != NULL) { + /* + * need to walk the mounted ZFS pools and datasets to + * find shares that are possible. + */ + list = fs_get_filtered_mount_list(NULL, NULL, "zfs", NULL, + NULL, 0, &err); + group = zfsgroup; + for (cur = list; cur != NULL; cur = cur->next) { + handle = zfs_open(libhandle, cur->resource, + ZFS_TYPE_FILESYSTEM); + if (handle != NULL) { + source = ZFS_SRC_ALL; + if (zfs_prop_get(handle, ZFS_PROP_SHARENFS, shareopts, + sizeof (shareopts), &source, sourcestr, + ZFS_MAXPROPLEN, + FALSE) == 0 && + strcmp(shareopts, "off") != 0) { + /* it is shared so add to list */ + share = sa_find_share(cur->mountp); + err = SA_OK; + if (share != NULL) { + /* + * A zfs file system had been shared + * through tradiditional methods + * (share/dfstab or added to a non-zfs + * group. Now it has been added to a + * ZFS group via the zfs + * command. Remove from previous + * config and setup with current + * options. + */ + err = sa_remove_share(share); + share = NULL; + } + if (err == SA_OK) { + if (source & ZFS_SRC_INHERITED) { + share = _sa_add_share(group, cur->mountp, + SA_SHARE_TRANSIENT, + &err); + } else { + group = _sa_create_zfs_group(zfsgroup, + cur->resource); + set_node_attr(group, "zfs", "true"); + share = _sa_add_share(group, cur->mountp, + SA_SHARE_TRANSIENT, + &err); + if (err == SA_OK) { + char *options; + if (strcmp(shareopts, "on") != 0) { + options = strdup(shareopts); + if (options != NULL) { + err = sa_parse_legacy_options(group, + options, + "nfs"); + free(options); + } + /* unmark the share's changed state */ + set_node_attr(share, "changed", NULL); + } + } + } + } + } + } + } + if (list != NULL) + fs_free_mount_list(list); + } + if (libhandle != NULL) + libzfs_fini(libhandle); + return (legacy); +} + +#define COMMAND "/usr/sbin/zfs" + +/* + * sa_zfs_set_sharenfs(group, path, on) + * + * Update the "sharenfs" property on the path. If on is true, then set + * to the properties on the group or "on" if no properties are + * defined. Set to "off" if on is false. + */ + +int +sa_zfs_set_sharenfs(sa_group_t group, char *path, int on) +{ + int ret = SA_NOT_IMPLEMENTED; + char *command; + + command = malloc(ZFS_MAXPROPLEN * 2); + if (command != NULL) { + char *opts = NULL; + char *dataset; + FILE *pfile; + /* for now, NFS is always available for "zfs" */ + if (on) { + opts = sa_proto_legacy_format("nfs", group, 1); + if (opts != NULL && strlen(opts) == 0) { + free(opts); + opts = strdup("on"); + } + } + dataset = get_zfs_dataset(path); + if (dataset != NULL) { + (void) snprintf(command, ZFS_MAXPROPLEN * 2, + "%s set sharenfs=\"%s\" %s", COMMAND, + opts != NULL ? opts : "off", + dataset); + pfile = popen(command, "r"); + if (pfile != NULL) { + ret = pclose(pfile); + if (ret != 0) + ret = SA_SYSTEM_ERR; + } + } + if (opts != NULL) + free(opts); + if (dataset != NULL) + free(dataset); + free(command); + } + return (ret); +} + +/* + * sa_zfs_update(group) + * + * call back to ZFS to update the share if necessary. + * Don't do it if it isn't a real change. + */ +int +sa_zfs_update(sa_group_t group) +{ + sa_optionset_t protopt; + sa_group_t parent; + char *command; + char *optstring; + int ret = SA_OK; + int doupdate = 0; + FILE *pfile; + + if (sa_is_share(group)) + parent = sa_get_parent_group(group); + else + parent = group; + + if (parent != NULL) { + command = malloc(ZFS_MAXPROPLEN * 2); + if (command == NULL) + return (SA_NO_MEMORY); + + *command = '\0'; + for (protopt = sa_get_optionset(parent, NULL); protopt != NULL; + protopt = sa_get_next_optionset(protopt)) { + + char *proto = sa_get_optionset_attr(protopt, "type"); + char *path; + char *dataset = NULL; + char *zfsopts = NULL; + + if (sa_is_share(group)) { + path = sa_get_share_attr((sa_share_t)group, "path"); + if (path != NULL) { + dataset = get_zfs_dataset(path); + sa_free_attr_string(path); + } + } else { + dataset = sa_get_group_attr(group, "name"); + } + /* update only when there is an optstring found */ + doupdate = 0; + if (proto != NULL && dataset != NULL) { + optstring = sa_proto_legacy_format(proto, group, 1); + zfsopts = get_zfs_property(dataset, ZFS_PROP_SHARENFS); + + if (optstring != NULL && zfsopts != NULL) { + if (strcmp(optstring, zfsopts) != 0) + doupdate++; + } + + if (doupdate) { + if (optstring != NULL && strlen(optstring) > 0) { + (void) snprintf(command, ZFS_MAXPROPLEN * 2, + "%s set sharenfs=%s %s", COMMAND, + optstring, dataset); + } else { + (void) snprintf(command, ZFS_MAXPROPLEN * 2, + "%s set sharenfs=on %s", COMMAND, + dataset); + } + pfile = popen(command, "r"); + if (pfile != NULL) + ret = pclose(pfile); + switch (ret) { + default: + case 1: + ret = SA_SYSTEM_ERR; + break; + case 2: + ret = SA_SYNTAX_ERR; + break; + case 0: + break; + } + } + if (optstring != NULL) { + free(optstring); + } + if (zfsopts != NULL) + free(zfsopts); + } + if (proto != NULL) + sa_free_attr_string(proto); + if (dataset != NULL) + free(dataset); + } + free(command); + } + return (ret); +} + +/* + * sa_group_is_zfs(group) + * + * Given the group, determine if the zfs attribute is set. + */ + +int +sa_group_is_zfs(sa_group_t group) +{ + char *zfs; + int ret = 0; + + zfs = sa_get_group_attr(group, "zfs"); + if (zfs != NULL) { + ret = 1; + sa_free_attr_string(zfs); + } + return (ret); +} + +/* + * sa_path_is_zfs(path) + * + * Check to see if the file system path represents is of type "zfs". + */ + +int +sa_path_is_zfs(char *path) +{ + char *fstype; + int ret = 0; + + fstype = sa_fstype(path); + if (fstype != NULL && strcmp(fstype, "zfs") == 0) { + ret = 1; + } + if (fstype != NULL) + sa_free_fstype(fstype); + return (ret); +} diff --git a/usr/src/lib/libshare/common/libsharecore.c b/usr/src/lib/libshare/common/libsharecore.c new file mode 100644 index 0000000000..9e1348feb6 --- /dev/null +++ b/usr/src/lib/libshare/common/libsharecore.c @@ -0,0 +1,1801 @@ +/* + * 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 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * core library for common functions across all config store types + * and file systems to be exported. This includes legacy dfstab/sharetab + * parsing. Need to eliminate XML where possible. + */ + +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <limits.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <libxml/parser.h> +#include <libxml/tree.h> +#include "libshare.h" +#include "libshare_impl.h" +#include "libfsmgt.h" +#include <fcntl.h> +#include <sys/stat.h> +#include <grp.h> +#include <limits.h> +#include <sys/param.h> +#include <signal.h> +#include <libintl.h> + +#include "sharetab.h" + +#define DFSTAB_NOTICE_LINES 5 +static char *notice[DFSTAB_NOTICE_LINES] = { + "# Do not modify this file directly.\n", + "# Use the sharemgr(1m) command for all share management\n", + "# This file is reconstructed and only maintained for backward\n", + "# compatibility. Configuration lines could be lost.\n", + "#\n" +}; + +#define STRNCAT(x, y, z) (xmlChar *)strncat((char *)x, (char *)y, z) + +/* will be much smaller, but this handles bad syntax in the file */ +#define MAXARGSFORSHARE 256 + +/* used internally only */ +typedef +struct sharelist { + struct sharelist *next; + int persist; + char *path; + char *resource; + char *fstype; + char *options; + char *description; + char *group; + char *origline; + int lineno; +} xfs_sharelist_t; +static void parse_dfstab(char *, xmlNodePtr); +extern char *get_token(char *); +static void dfs_free_list(xfs_sharelist_t *); +/* prototypes */ +void getlegacyconfig(char *, xmlNodePtr *); +extern sa_share_t _sa_add_share(sa_group_t, char *, int, int *); +extern xmlNodePtr _sa_get_next_error(xmlNodePtr); +extern sa_group_t _sa_create_group(char *); +static void outdfstab(FILE *, xfs_sharelist_t *); +extern int _sa_remove_optionset(sa_optionset_t); +extern int set_node_share(void *, char *, char *); +extern void set_node_attr(void *, char *, char *); + +/* + * alloc_sharelist() + * + * allocator function to return an zfs_sharelist_t + */ + +static xfs_sharelist_t * +alloc_sharelist() +{ + xfs_sharelist_t *item; + + item = (xfs_sharelist_t *)malloc(sizeof (xfs_sharelist_t)); + if (item != NULL) + (void) memset(item, '\0', sizeof (xfs_sharelist_t)); + return (item); +} + +/* + * fix_notice(list) + * + * Look at the beginning of the current /etc/dfs/dfstab file and add + * the do not modify notice if it doesn't exist. + */ + +static xfs_sharelist_t * +fix_notice(xfs_sharelist_t *list) +{ + xfs_sharelist_t *item, *prev; + int i; + + if (list->path == NULL && list->description != NULL && + strcmp(list->description, notice[0]) != 0) { + for (prev = NULL, i = 0; i < DFSTAB_NOTICE_LINES; i++) { + item = alloc_sharelist(); + if (item != NULL) { + item->description = strdup(notice[i]); + if (prev == NULL) { + item->next = list; + prev = item; + list = item; + } else { + item->next = prev->next; + prev->next = item; + prev = item; + } + } + } + } + return (list); +} + +/* + * getdfstab(dfs) + * + * Returns an zfs_sharelist_t list of lines from the dfstab file + * pointed to by the FILE pointer dfs. Each entry is parsed and the + * original line is also preserved. Used in parsing and updating the + * dfstab file. + */ + +static xfs_sharelist_t * +getdfstab(FILE *dfs) +{ + char buff[_POSIX_ARG_MAX]; /* reasonable size given syntax of share */ + char *bp; + char *token; + char *args[MAXARGSFORSHARE]; + int argc; + int c; + static int line = 0; + xfs_sharelist_t *item, *first, *last; + + if (dfs != NULL) { + first = NULL; + line = 0; + while (fgets(buff, sizeof (buff), dfs) != NULL) { + line++; + bp = buff; + if (buff[0] == '#') { + item = alloc_sharelist(); + if (item != NULL) { + /* if no path, then comment */ + item->lineno = line; + item->description = strdup(buff); + if (first == NULL) { + first = item; + last = item; + } else { + last->next = item; + last = item; + } + } else { + break; + } + continue; + } else if (buff[0] == '\n') { + continue; + } + optind = 1; + item = alloc_sharelist(); + if (item == NULL) { + break; + } else if (first == NULL) { + first = item; + last = item; + } else { + last->next = item; + last = item; + } + item->lineno = line; + item->origline = strdup(buff); + (void) get_token(NULL); /* reset to new pointers */ + argc = 0; + while ((token = get_token(bp)) != NULL) { + if (argc < MAXARGSFORSHARE) { + args[argc++] = token; + } + } + while ((c = getopt(argc, args, "F:o:d:pg:")) != -1) { + switch (c) { + case 'p': + item->persist = 1; + break; + case 'F': + item->fstype = strdup(optarg); + break; + case 'o': + item->options = strdup(optarg); + break; + case 'd': + item->description = strdup(optarg); + break; + case 'g': + item->group = strdup(optarg); + break; + default: + break; + } + } + if (optind < argc) { + item->path = strdup(args[optind]); + optind++; + if (optind < argc) { + char *resource; + char *optgroup; + /* resource and/or groupname */ + resource = args[optind]; + optgroup = strchr(resource, '@'); + if (optgroup != NULL) { + *optgroup++ = '\0'; + } + if (optgroup != NULL) + item->group = strdup(optgroup); + if (resource != NULL && strlen(resource) > 0) + item->resource = strdup(resource); + } + } + } + if (item->fstype == NULL) + item->fstype = strdup("nfs"); /* this is the default */ + } + first = fix_notice(first); + return (first); +} + +/* + * finddfsentry(list, path) + * + * Look for path in the zfs_sharelist_t list and return the tnry if it + * exists. + */ + +static xfs_sharelist_t * +finddfsentry(xfs_sharelist_t *list, char *path) +{ + xfs_sharelist_t *item; + + for (item = list; item != NULL; item = item->next) { + if (item->path != NULL && strcmp(item->path, path) == 0) + return (item); + } + return (NULL); +} + +/* + * remdfsentry(list, path, proto) + * + * Remove the specified path (with protocol) from the list. This will + * remove it from dfstab when the file is rewritten. + */ + +static xfs_sharelist_t * +remdfsentry(xfs_sharelist_t *list, char *path, char *proto) +{ + xfs_sharelist_t *item, *prev = NULL; + + + for (item = prev = list; item != NULL; item = item->next) { + /* skip comment entry but don't lose it */ + if (item->path == NULL) { + prev = item; + continue; + } + /* if proto is NULL, remove all protocols */ + if (proto == NULL || (strcmp(item->path, path) == 0 && + (item->fstype != NULL && strcmp(item->fstype, proto) == 0))) + break; + if (item->fstype == NULL && + (proto == NULL || strcmp(proto, "nfs") == 0)) + break; + prev = item; + } + if (item != NULL) { + if (item == prev) { + list = item->next; /* this must be the first one */ + } else { + prev->next = item->next; + } + item->next = NULL; + dfs_free_list(item); + } + return (list); +} + +/* + * remdfsline(list, line) + * + * Remove the line specified from the list. + */ + +static xfs_sharelist_t * +remdfsline(xfs_sharelist_t *list, char *line) +{ + xfs_sharelist_t *item, *prev = NULL; + + for (item = prev = list; item != NULL; item = item->next) { + /* skip comment entry but don't lose it */ + if (item->path == NULL) { + prev = item; + continue; + } + if (strcmp(item->origline, line) == 0) { + break; + } + prev = item; + } + if (item != NULL) { + if (item == prev) { + list = item->next; /* this must be the first one */ + } else { + prev->next = item->next; + } + item->next = NULL; + dfs_free_list(item); + } + return (list); +} + +/* + * adddfsentry(list, share, proto) + * + * Add an entry to the dfstab list for share (relative to proto). This + * is used to update dfstab for legacy purposes. + */ + +static xfs_sharelist_t * +adddfsentry(xfs_sharelist_t *list, sa_share_t share, char *proto) +{ + xfs_sharelist_t *item, *tmp; + sa_group_t parent; + char *groupname; + + item = alloc_sharelist(); + if (item != NULL) { + parent = sa_get_parent_group(share); + groupname = sa_get_group_attr(parent, "name"); + if (strcmp(groupname, "default") == 0) { + sa_free_attr_string(groupname); + groupname = NULL; + } + item->path = sa_get_share_attr(share, "path"); + item->resource = sa_get_share_attr(share, "resource"); + item->group = groupname; + item->fstype = strdup(proto); + item->options = sa_proto_legacy_format(proto, share, 1); + if (item->options != NULL && strlen(item->options) == 0) { + free(item->options); + item->options = NULL; + } + item->description = sa_get_share_description(share); + if (item->description != NULL && strlen(item->description) == 0) { + sa_free_share_description(item->description); + item->description = NULL; + } + if (list == NULL) { + list = item; + } else { + for (tmp = list; tmp->next != NULL; tmp = tmp->next) + /* do nothing */; + tmp->next = item; + } + } + return (list); +} + +/* + * outdfstab(dfstab, list) + * + * Output the list to dfstab making sure the file is truncated. + * Comments and errors are preserved. + */ + +static void +outdfstab(FILE *dfstab, xfs_sharelist_t *list) +{ + xfs_sharelist_t *item; + + (void) ftruncate(fileno(dfstab), 0); + + for (item = list; item != NULL; item = item->next) { + if (item->path != NULL) { + if (*item->path == '/') + (void) fprintf(dfstab, "share %s%s%s%s%s%s%s %s%s%s%s%s\n", + (item->fstype != NULL) ? "-F " : "", + (item->fstype != NULL) ? item->fstype : "", + (item->options != NULL) ? " -o " : "", + (item->options != NULL) ? item->options : "", + (item->description != NULL) ? " -d \"" : "", + (item->description != NULL) ? + item->description : "", + (item->description != NULL) ? "\"" : "", + item->path, + ((item->resource != NULL) || + (item->group != NULL)) ? " " : "", + (item->resource != NULL) ? item->resource : "", + item->group != NULL ? "@" : "", + item->group != NULL ? item->group : ""); + else + (void) fprintf(dfstab, "%s", item->origline); + } else { + if (item->description != NULL) { + (void) fprintf(dfstab, "%s", item->description); + } else { + (void) fprintf(dfstab, "%s", item->origline); + } + } + } +} + +/* + * open_dfstab(file) + * + * Open the specified dfstab file. If the owner/group/perms are wrong, + * fix them. + */ + +static FILE * +open_dfstab(char *file) +{ + struct group *grp; + struct group group; + char *buff; + int grsize; + FILE *dfstab; + + dfstab = fopen(file, "r+"); + if (dfstab == NULL) { + dfstab = fopen(file, "w+"); + } + if (dfstab != NULL) { + grsize = sysconf(_SC_GETGR_R_SIZE_MAX); + buff = malloc(grsize); + if (buff != NULL) + grp = getgrnam_r(SA_DEFAULT_FILE_GRP, &group, buff, grsize); + else + grp = getgrnam(SA_DEFAULT_FILE_GRP); /* take the risk */ + (void) fchmod(fileno(dfstab), 0644); + (void) fchown(fileno(dfstab), 0, + grp != NULL ? grp->gr_gid : 3); + if (buff != NULL) + free(buff); + rewind(dfstab); + } + return (dfstab); +} + +/* + * sa_comment_line(line, err) + * + * Add a comment to the dfstab file with err as a prefix to the + * original line. + */ + +static void +sa_comment_line(char *line, char *err) +{ + FILE *dfstab; + xfs_sharelist_t *list; + sigset_t old, new; + + dfstab = open_dfstab(SA_LEGACY_DFSTAB); + if (dfstab != NULL) { + (void) setvbuf(dfstab, NULL, _IOLBF, BUFSIZ * 8); + (void) sigprocmask(SIG_BLOCK, NULL, &new); + (void) sigaddset(&new, SIGHUP); + (void) sigaddset(&new, SIGINT); + (void) sigaddset(&new, SIGQUIT); + (void) sigaddset(&new, SIGTSTP); + (void) sigprocmask(SIG_SETMASK, &new, &old); + (void) lockf(fileno(dfstab), F_LOCK, 0); + list = getdfstab(dfstab); + rewind(dfstab); + (void) remdfsline(list, line); + outdfstab(dfstab, list); + (void) fprintf(dfstab, "# Error: %s: %s", err, line); + (void) fsync(fileno(dfstab)); + (void) lockf(fileno(dfstab), F_ULOCK, 0); + (void) fclose(dfstab); + (void) sigprocmask(SIG_SETMASK, &old, NULL); + if (list != NULL) + dfs_free_list(list); + } +} + +/* + * sa_delete_legacy(share) + * + * Delete the specified share from the legacy config file. + */ + +int +sa_delete_legacy(sa_share_t share) +{ + FILE *dfstab; + int err; + int ret = SA_OK; + xfs_sharelist_t *list; + char *path; + sa_optionset_t optionset; + sa_group_t parent; + sigset_t old, new; + + dfstab = open_dfstab(SA_LEGACY_DFSTAB); + if (dfstab != NULL) { + (void) setvbuf(dfstab, NULL, _IOLBF, BUFSIZ * 8); + (void) sigprocmask(SIG_BLOCK, NULL, &new); + (void) sigaddset(&new, SIGHUP); + (void) sigaddset(&new, SIGINT); + (void) sigaddset(&new, SIGQUIT); + (void) sigaddset(&new, SIGTSTP); + (void) sigprocmask(SIG_SETMASK, &new, &old); + path = sa_get_share_attr(share, "path"); + parent = sa_get_parent_group(share); + if (parent != NULL) { + (void) lockf(fileno(dfstab), F_LOCK, 0); + list = getdfstab(dfstab); + rewind(dfstab); + for (optionset = sa_get_optionset(parent, NULL); + optionset != NULL; + optionset = sa_get_next_optionset(optionset)) { + char *proto = sa_get_optionset_attr(optionset, "type"); + if (list != NULL && proto != NULL) + (void) remdfsentry(list, path, proto); + if (proto == NULL) + ret = SA_NO_MEMORY; + /* + * may want to only do the dfstab if this call + * returns NOT IMPLEMENTED but it shouldn't + * hurt. + */ + if (ret == SA_OK) { + err = sa_proto_delete_legacy(proto, share); + if (err != SA_NOT_IMPLEMENTED) + ret = err; + } + if (proto != NULL) + sa_free_attr_string(proto); + } + outdfstab(dfstab, list); + if (list != NULL) + dfs_free_list(list); + (void) fflush(dfstab); + (void) lockf(fileno(dfstab), F_ULOCK, 0); + } + (void) fsync(fileno(dfstab)); + (void) sigprocmask(SIG_SETMASK, &old, NULL); + (void) fclose(dfstab); + sa_free_attr_string(path); + } else { + if (errno == EACCES || errno == EPERM) { + ret = SA_NO_PERMISSION; + } else { + ret = SA_CONFIG_ERR; + } + } + return (ret); +} + +/* + * sa_update_legacy(share, proto) + * + * There is an assumption that dfstab will be the most common form of + * legacy configuration file for shares, but not the only one. Because + * of that, dfstab handling is done in the main code with calls to + * this function and protocol specific calls to deal with formating + * options into dfstab/share compatible syntax. Since not everything + * will be dfstab, there is a provision for calling a protocol + * specific plugin interface that allows the protocol plugin to do its + * own legacy files and skip the dfstab update. + */ + +int +sa_update_legacy(sa_share_t share, char *proto) +{ + FILE *dfstab; + int ret = SA_OK; + xfs_sharelist_t *list; + char *path; + sigset_t old, new; + char *persist; + + ret = sa_proto_update_legacy(proto, share); + if (ret != SA_NOT_IMPLEMENTED) + return (ret); + /* do the dfstab format */ + persist = sa_get_share_attr(share, "type"); + /* + * only update if the share is not transient -- no share type + * set or the type is not "transient". + */ + if (persist == NULL || strcmp(persist, "transient") != 0) { + dfstab = open_dfstab(SA_LEGACY_DFSTAB); + if (dfstab != NULL) { + (void) setvbuf(dfstab, NULL, _IOLBF, BUFSIZ * 8); + (void) sigprocmask(SIG_BLOCK, NULL, &new); + (void) sigaddset(&new, SIGHUP); + (void) sigaddset(&new, SIGINT); + (void) sigaddset(&new, SIGQUIT); + (void) sigaddset(&new, SIGTSTP); + (void) sigprocmask(SIG_SETMASK, &new, &old); + path = sa_get_share_attr(share, "path"); + (void) lockf(fileno(dfstab), F_LOCK, 0); + list = getdfstab(dfstab); + rewind(dfstab); + if (list != NULL) + list = remdfsentry(list, path, proto); + list = adddfsentry(list, share, proto); + outdfstab(dfstab, list); + (void) fflush(dfstab); + (void) lockf(fileno(dfstab), F_ULOCK, 0); + (void) fsync(fileno(dfstab)); + (void) sigprocmask(SIG_SETMASK, &old, NULL); + (void) fclose(dfstab); + sa_free_attr_string(path); + if (list != NULL) + dfs_free_list(list); + } else { + if (errno == EACCES || errno == EPERM) { + ret = SA_NO_PERMISSION; + } else { + ret = SA_CONFIG_ERR; + } + } + } + if (persist != NULL) + sa_free_attr_string(persist); + return (ret); +} + +/* + * sa_is_security(optname, proto) + * + * Check to see if optname is a security (named optionset) specific + * property for the specified protocol. + */ + +int +sa_is_security(char *optname, char *proto) +{ + int ret = 0; + if (proto != NULL) + ret = sa_proto_security_prop(proto, optname); + return (ret); +} + +/* + * add_syntax_comment(root, line, err, todfstab) + * + * add a comment to the document indicating a syntax error. If + * todfstab is set, write it back to the dfstab file as well. + */ + +static void +add_syntax_comment(xmlNodePtr root, char *line, char *err, int todfstab) +{ + xmlNodePtr node; + + node = xmlNewChild(root, NULL, (xmlChar *)"error", (xmlChar *)line); + if (node != NULL) { + xmlSetProp(node, (xmlChar *)"type", (xmlChar *)err); + } + if (todfstab) + sa_comment_line(line, err); +} + +/* + * sa_is_share(object) + * + * returns true of the object is of type "share". + */ + +int +sa_is_share(void *object) +{ + if (object != NULL) { + if (strcmp((char *)((xmlNodePtr)object)->name, "share") == 0) + return (1); + } + return (0); +} + +/* + * _sa_remove_property(property) + * + * remove a property only from the document. + */ + +static void +_sa_remove_property(sa_property_t property) +{ + xmlUnlinkNode((xmlNodePtr)property); + xmlFreeNode((xmlNodePtr)property); +} + +/* + * sa_parse_legacy_options(group, options, proto) + * + * In order to support legacy configurations, we allow the protocol + * specific plugin to parse legacy syntax options (like those in + * /etc/dfs/dfstab). This adds a new optionset to the group (or + * share). + * + * Once the optionset has been created, we then get the derived + * optionset of the parent (options from the optionset of the parent + * and any parent it might have) and remove those from the created + * optionset. This avoids duplication of options. + */ + +int +sa_parse_legacy_options(sa_group_t group, char *options, char *proto) +{ + int ret = SA_INVALID_PROTOCOL; + sa_group_t parent; + parent = sa_get_parent_group(group); + + if (proto != NULL) + ret = sa_proto_legacy_opts(proto, group, options); + /* + * if in a group, remove the inherited options and security + */ + if (ret == SA_OK) { + if (parent != NULL) { + sa_optionset_t optionset; + sa_property_t popt, prop; + sa_optionset_t localoptions; + /* find parent options to remove from child */ + optionset = sa_get_derived_optionset(parent, proto, 1); + localoptions = sa_get_optionset(group, proto); + if (optionset != NULL) { + for (popt = sa_get_property(optionset, NULL); + popt != NULL; + popt = sa_get_next_property(popt)) { + char *tag; + char *value1; + char *value2; + + tag = sa_get_property_attr(popt, "type"); + if (tag != NULL) { + prop = sa_get_property(localoptions, tag); + if (prop != NULL) { + value1 = sa_get_property_attr(popt, "value"); + value2 = sa_get_property_attr(prop, "value"); + if (value1 != NULL && value2 != NULL && + strcmp(value1, value2) == 0) { + /* remove the property from the child */ + (void) _sa_remove_property(prop); + } + if (value1 != NULL) + sa_free_attr_string(value1); + if (value2 != NULL) + sa_free_attr_string(value2); + } + sa_free_attr_string(tag); + } + } + prop = sa_get_property(localoptions, NULL); + if (prop == NULL && sa_is_share(group)) { + /* + * all properties removed so remove the + * optionset if it is on a share + */ + (void) _sa_remove_optionset(localoptions); + } + sa_free_derived_optionset(optionset); + } + /* + * need to remove security here. If there are no + * security options on the local group/share, don't + * bother since those are the only ones that would be + * affected. + */ + localoptions = sa_get_all_security_types(group, proto, 0); + if (localoptions != NULL) { + for (prop = sa_get_property(localoptions, NULL); + prop != NULL; prop = sa_get_next_property(prop)) { + char *tag; + sa_security_t security; + tag = sa_get_property_attr(prop, "type"); + if (tag != NULL) { + security = sa_get_security(group, tag, proto); + sa_free_attr_string(tag); + for (popt = sa_get_property(security, NULL); + popt != NULL; + popt = sa_get_next_property(popt)) { + char *value1; + char *value2; + + /* remove duplicates from this level */ + value1 = sa_get_property_attr(popt, "value"); + value2 = sa_get_property_attr(prop, "value"); + if (value1 != NULL && value2 != NULL && + strcmp(value1, value2) == 0) { + /* remove the property from the child */ + (void) _sa_remove_property(prop); + } + if (value1 != NULL) + sa_free_attr_string(value1); + if (value2 != NULL) + sa_free_attr_string(value2); + } + } + } + (void) sa_destroy_optionset(localoptions); + } + } + } + return (ret); +} + +/* + * dfs_free_list(list) + * + * Free the data in each list entry of the list as well as freeing the + * entries themselves. We need to avoid memory leaks and don't want to + * dereference any NULL members. + */ + +static void +dfs_free_list(xfs_sharelist_t *list) +{ + xfs_sharelist_t *entry; + for (entry = list; entry != NULL; entry = list) { + if (entry->path != NULL) + free(entry->path); + if (entry->resource != NULL) + free(entry->resource); + if (entry->fstype != NULL) + free(entry->fstype); + if (entry->options != NULL) + free(entry->options); + if (entry->description != NULL) + free(entry->description); + if (entry->origline != NULL) + free(entry->origline); + if (entry->group != NULL) + free(entry->group); + list = list->next; + free(entry); + } +} + +/* + * parse_dfstab(dfstab, root) + * + * Open and read the existing dfstab, parsing each line and adding it + * to the internal configuration. Make sure syntax errors, etc are + * preserved as comments. + */ + +static void +parse_dfstab(char *dfstab, xmlNodePtr root) +{ + sa_share_t share; + sa_group_t group; + sa_group_t sgroup = NULL; + sa_group_t defgroup; + xfs_sharelist_t *head, *list; + int err; + int defined_group; + FILE *dfs; + char *oldprops; + + /* read the dfstab format file and fill in the doc tree */ + + dfs = fopen(dfstab, "r"); + if (dfs == NULL) { + return; + } + + defgroup = sa_get_group("default"); + + for (head = list = getdfstab(dfs); + list != NULL; + list = list->next) { + share = NULL; + group = NULL; + defined_group = 0; + err = 0; + + if (list->origline == NULL) { + /* + * Comment line that we will likely skip. + * If the line has the syntax: + * # error: string: string + * It should be preserved until manually deleted. + */ + if (list->description != NULL && + strncmp(list->description, "# Error: ", 9) == 0) { + char *line; + char *error; + char *cmd; + line = strdup(list->description); + if (line != NULL) { + error = line + 9; + cmd = strchr(error, ':'); + if (cmd != NULL) { + int len; + *cmd = '\0'; + cmd += 2; + len = strlen(cmd); + cmd[len - 1] = '\0'; + add_syntax_comment(root, cmd, error, 0); + } + free(line); + } + } + continue; + } + if (list->path != NULL && strlen(list->path) > 0 && + *list->path == '/') { + share = sa_find_share(list->path); + if (share != NULL) + sgroup = sa_get_parent_group(share); + else + sgroup = NULL; + } else { + (void) printf(gettext("No share specified in dfstab: " + "line %d: %s\n"), + list->lineno, list->origline); + add_syntax_comment(root, list->origline, + gettext("No share specified"), + 1); + continue; + } + if (list->group != NULL && strlen(list->group) > 0) { + group = sa_get_group(list->group); + defined_group = 1; + } else { + group = defgroup; + } + if (defined_group && group == NULL) { + (void) printf(gettext("Unknown group used in dfstab: " + "line %d: %s\n"), + list->lineno, list->origline); + add_syntax_comment(root, list->origline, + gettext("Unknown group specified"), 1); + continue; + } + if (group != NULL) { + if (share == NULL) { + if (!defined_group && group == defgroup) { + /* this is an OK add for legacy */ + share = sa_add_share(defgroup, list->path, + SA_SHARE_PERMANENT | SA_SHARE_PARSER, + &err); + if (share != NULL) { + if (list->description != NULL && + strlen(list->description) > 0) + (void) sa_set_share_description(share, + list->description); + if (list->options != NULL && + strlen(list->options) > 0) { + (void) sa_parse_legacy_options(share, + list->options, + list->fstype); + } + if (list->resource != NULL) + (void) sa_set_share_attr(share, "resource", + list->resource); + } else { + (void) printf(gettext("Error in dfstab: " + "line %d: %s\n"), + list->lineno, list->origline); + if (err != SA_BAD_PATH) + add_syntax_comment(root, list->origline, + gettext("Syntax"), 1); + else + add_syntax_comment(root, list->origline, + gettext("Path"), 1); + continue; + } + } + } else { + if (group != sgroup) { + (void) printf(gettext("Attempt to change" + "configuration in" + "dfstab: line %d: %s\n"), + list->lineno, list->origline); + add_syntax_comment(root, list->origline, + gettext("Attempt to change configuration"), 1); + continue; + } + /* its the same group but could have changed options */ + oldprops = sa_proto_legacy_format(list->fstype, share, 0); + if (oldprops != NULL) { + if (list->options != NULL && + strcmp(oldprops, list->options) != 0) { + sa_optionset_t opts; + sa_security_t secs; + /* possibly different values */ + opts = sa_get_optionset((sa_group_t)share, + list->fstype); + (void) sa_destroy_optionset(opts); + for (secs = sa_get_security((sa_group_t)share, + NULL, list->fstype); + secs != NULL; + secs = sa_get_security((sa_group_t)share, + NULL, list->fstype)) { + (void) sa_destroy_security(secs); + } + (void) sa_parse_legacy_options(share, + list->options, + list->fstype); + } + } + } + } else { + /* shouldn't happen */ + err = SA_CONFIG_ERR; + } + + } + dfs_free_list(head); +} + +/* + * legacy_removes(group, file) + * + * Find any shares that are "missing" from the legacy file. These + * should be removed from the configuration since they are likely from + * a legacy app or the admin modified the dfstab file directly. We + * have to support this even if it is not the recommended way to do + * things. + */ + +static void +legacy_removes(sa_group_t group, char *file) +{ + sa_share_t share; + char *path; + xfs_sharelist_t *list, *item; + FILE *dfstab; + + dfstab = fopen(file, "r"); + if (dfstab != NULL) { + list = getdfstab(dfstab); + (void) fclose(dfstab); + for (share = sa_get_share(group, NULL); share != NULL; + share = sa_get_next_share(share)) { + /* now see if the share is in the dfstab file */ + path = sa_get_share_attr(share, "path"); + if (path != NULL) { + item = finddfsentry(list, path); + if (item == NULL) { + /* the share was removed this way */ + (void) sa_remove_share(share); + /* start over since the list was broken */ + share = sa_get_share(group, NULL); + } + sa_free_attr_string(path); + } + } + if (list != NULL) + dfs_free_list(list); + } +} + +/* + * getlegacyconfig(path, root) + * + * Parse dfstab and build the legacy configuration. This only gets + * called when a change was detected. + */ + +void +getlegacyconfig(char *path, xmlNodePtr *root) +{ + sa_group_t defgroup; + + if (root != NULL) { + if (*root == NULL) + *root = xmlNewNode(NULL, (xmlChar *)"sharecfg"); + if (*root != NULL) { + if (strcmp(path, SA_LEGACY_DFSTAB) == 0) { + /* + * walk the default shares and find anything + * missing. we do this first to make sure it + * is cleaned up since there may be legacy + * code add/del via dfstab and we need to + * cleanup SMF. + */ + defgroup = sa_get_group("default"); + if (defgroup != NULL) { + legacy_removes(defgroup, path); + } + /* parse the dfstab and add anything new */ + parse_dfstab(path, *root); + } + } + } +} + +/* + * parse_sharetab(void) + * + * Read the /etc/dfs/sharetab file via libfsmgt and see which entries + * don't exist in the repository. These shares are marked transient. + * We also need to see if they are ZFS shares since ZFS bypasses the + * SMF repository. + */ + +int +parse_sharetab(void) +{ + fs_sharelist_t *list, *tmplist; + int err = 0; + sa_share_t share; + sa_group_t group; + sa_group_t lgroup; + char *groupname; + int legacy = 0; + + list = fs_get_share_list(&err); + if (list == NULL) + return (legacy); + + lgroup = sa_get_group("default"); + + for (tmplist = list; tmplist != NULL; tmplist = tmplist->next) { + group = NULL; + share = sa_find_share(tmplist->path); + if (share == NULL) { + /* + * this share is transient so needs to be + * added. Initially, this will be under + * default(legacy) unless it is a ZFS + * share. If zfs, we need a zfs group. + */ + if (tmplist->resource != NULL && + (groupname = strchr(tmplist->resource, '@')) != NULL) { + /* there is a defined group */ + *groupname++ = '\0'; + group = sa_get_group(groupname); + if (group != NULL) { + share = _sa_add_share(group, tmplist->path, + SA_SHARE_TRANSIENT, &err); + } else { + (void) printf(gettext("Group for temporary share" + "not found: %s\n"), + tmplist->path); + share = _sa_add_share(lgroup, tmplist->path, + SA_SHARE_TRANSIENT, &err); + } + } else { + if (sa_zfs_is_shared(tmplist->path)) { + group = sa_get_group("zfs"); + if (group == NULL) { + group = sa_create_group("zfs", &err); + if (group == NULL && err == SA_NO_PERMISSION) { + group = _sa_create_group("zfs"); + } + if (group != NULL) { + (void) sa_create_optionset(group, + tmplist->fstype); + (void) sa_set_group_attr(group, "zfs", "true"); + } + } + if (group != NULL) { + share = _sa_add_share(group, tmplist->path, + SA_SHARE_TRANSIENT, &err); + } + } else { + share = _sa_add_share(lgroup, tmplist->path, + SA_SHARE_TRANSIENT, &err); + } + } + if (share == NULL) + (void) printf(gettext("Problem with transient: %s\n"), + sa_errorstr(err)); + if (share != NULL) + set_node_attr(share, "shared", "true"); + + if (err == SA_OK) { + if (tmplist->options != NULL && + strlen(tmplist->options) > 0) { + (void) sa_parse_legacy_options(share, + tmplist->options, + tmplist->fstype); + } + if (tmplist->resource != NULL && + strcmp(tmplist->resource, "-") != 0) + set_node_attr(share, "resource", tmplist->resource); + if (tmplist->description != NULL) { + xmlNodePtr node; + node = xmlNewChild((xmlNodePtr)share, NULL, + (xmlChar *)"description", NULL); + xmlNodeSetContent(node, + (xmlChar *)tmplist->description); + } + legacy = 1; + } + } else { + /* + * if this is a legacy share, mark as shared so we + * only update sharetab appropriately. + */ + set_node_attr(share, "shared", "true"); + } + } + fs_free_share_list(list); + return (legacy); +} + +/* + * get the transient shares from the sharetab (or other) file. since + * these are transient, they only appear in the working file and not + * in a repository. + */ +int +gettransients(xmlNodePtr *root) +{ + int legacy = 0; + + if (root != NULL) { + if (*root == NULL) + *root = xmlNewNode(NULL, (xmlChar *)"sharecfg"); + if (*root != NULL) { + legacy = parse_sharetab(); + } + } + return (legacy); +} + +/* + * sa_has_prop(optionset, prop) + * + * Is the specified property a member of the optionset? + */ + +int +sa_has_prop(sa_optionset_t optionset, sa_property_t prop) +{ + char *name; + sa_property_t otherprop; + int result = 0; + + if (optionset != NULL) { + name = sa_get_property_attr(prop, "type"); + if (name != NULL) { + otherprop = sa_get_property(optionset, name); + if (otherprop != NULL) + result = 1; + sa_free_attr_string(name); + } + } + return (result); +} + +/* + * Update legacy files + * + * Provides functions to add/remove/modify individual entries + * in dfstab and sharetab + */ + +void +update_legacy_config(void) +{ + /* + * no longer used -- this is a placeholder in case we need to + * add it back later. + */ +} + +/* + * sa_valid_property(object, proto, property) + * + * check to see if the specified property is valid relative to the + * specified protocol. The protocol plugin is called to do the work. + */ + +int +sa_valid_property(void *object, char *proto, sa_property_t property) +{ + int ret = SA_OK; + + if (proto != NULL && property != NULL) { + ret = sa_proto_valid_prop(proto, property, object); + } + + return (ret); +} + +/* + * sa_fstype(path) + * + * Given path, return the string representing the path's file system + * type. This is used to discover ZFS shares. + */ + +char * +sa_fstype(char *path) +{ + int err; + struct stat st; + + err = stat(path, &st); + if (err < 0) { + err = SA_NO_SUCH_PATH; + } else { + err = SA_OK; + } + if (err == SA_OK) { + /* have a valid path at this point */ + return (strdup(st.st_fstype)); + } + return (NULL); +} + +void +sa_free_fstype(char *type) +{ + free(type); +} + +/* + * sa_get_derived_optionset(object, proto, hier) + * + * Work backward to the top of the share object tree and start + * copying protocol specific optionsets into a newly created + * optionset that doesn't have a parent (it will be freed + * later). This provides for the property inheritence model. That + * is, properties closer to the share take precedence over group + * level. This also provides for groups of groups in the future. + */ + +sa_optionset_t +sa_get_derived_optionset(void *object, char *proto, int hier) +{ + sa_optionset_t newoptionset; + sa_optionset_t optionset; + sa_group_t group; + + if (hier && + (group = sa_get_parent_group((sa_share_t)object)) != NULL) { + newoptionset = sa_get_derived_optionset((void *)group, proto, hier); + } else { + newoptionset = (sa_optionset_t)xmlNewNode(NULL, + (xmlChar *)"optionset"); + if (newoptionset != NULL) { + sa_set_optionset_attr(newoptionset, "type", proto); + } + } + /* dont' do anything if memory wasn't allocated */ + if (newoptionset == NULL) + return (NULL); + + /* found the top so working back down the stack */ + optionset = sa_get_optionset((sa_optionset_t)object, proto); + if (optionset != NULL) { + sa_property_t prop; + /* add optionset to the newoptionset */ + for (prop = sa_get_property(optionset, NULL); + prop != NULL; prop = sa_get_next_property(prop)) { + sa_property_t newprop; + char *name; + char *value; + name = sa_get_property_attr(prop, "type"); + value = sa_get_property_attr(prop, "value"); + if (name != NULL) { + newprop = sa_get_property(newoptionset, name); + /* replace the value with the new value */ + if (newprop != NULL) { + /* + * only set if value is non NULL, old value ok + * if it is NULL. + */ + if (value != NULL) + set_node_attr(newprop, "value", value); + } else { + /* an entirely new property */ + if (value != NULL) { + newprop = sa_create_property(name, value); + if (newprop != NULL) { + newprop = (sa_property_t) + xmlAddChild((xmlNodePtr)newoptionset, + (xmlNodePtr)newprop); + } + } + } + sa_free_attr_string(name); + } + if (value != NULL) + sa_free_attr_string(value); + } + } + return (newoptionset); +} + +void +sa_free_derived_optionset(sa_optionset_t optionset) +{ + /* while it shouldn't be linked, it doesn't hurt */ + if (optionset != NULL) { + xmlUnlinkNode((xmlNodePtr) optionset); + xmlFreeNode((xmlNodePtr) optionset); + } +} + +/* + * sa_get_all_security_types(object, proto, hier) + * + * find all the security types set for this object. This is + * preliminary to getting a derived security set. The return value is an + * optionset containg properties which are the sectype values found by + * walking up the XML document struture. The returned optionset + * is a derived optionset. + * + * If hier is 0, only look at object. If non-zero, walk up the tree. + */ +sa_optionset_t +sa_get_all_security_types(void *object, char *proto, int hier) +{ + sa_optionset_t options; + sa_security_t security; + sa_group_t group; + sa_property_t prop; + + options = NULL; + + if (hier && + (group = sa_get_parent_group((sa_share_t)object)) != NULL) { + options = sa_get_all_security_types((void *)group, proto, hier); + } else { + options = (sa_optionset_t)xmlNewNode(NULL, + (xmlChar *)"optionset"); + } + /* hit the top so collect the security types working back */ + if (options != NULL) { + for (security = sa_get_security((sa_group_t)object, NULL, NULL); + security != NULL; security = sa_get_next_security(security)) { + char *type; + char *sectype; + + type = sa_get_security_attr(security, "type"); + if (type != NULL) { + if (strcmp(type, proto) != 0) { + sa_free_attr_string(type); + continue; + } + sectype = sa_get_security_attr(security, "sectype"); + if (sectype != NULL) { + /* + * have a security type, check to see if + * already present in optionset and add if it + * isn't. + */ + if (sa_get_property(options, sectype) == NULL) { + prop = sa_create_property(sectype, "true"); + if (prop != NULL) + prop = (sa_property_t) + xmlAddChild((xmlNodePtr)options, + (xmlNodePtr)prop); + } + sa_free_attr_string(sectype); + } + sa_free_attr_string(type); + } + } + } + return (options); +} + +/* + * sa_get_derived_security(object, sectype, proto, hier) + * + * Get the derived security(named optionset) for the object given the + * sectype and proto. If hier is non-zero, walk up the tree to get all + * properties defined for this object, otherwise just those on the + * object. + */ + +sa_security_t +sa_get_derived_security(void *object, char *sectype, char *proto, int hier) +{ + sa_security_t newsecurity; + sa_security_t security; + sa_group_t group; + + if (hier && + (group = sa_get_parent_group((sa_share_t)object)) != NULL) { + newsecurity = sa_get_derived_security((void *)group, + sectype, proto, hier); + } else { + newsecurity = (sa_security_t)xmlNewNode(NULL, + (xmlChar *)"security"); + if (newsecurity != NULL) { + sa_set_security_attr(newsecurity, "type", proto); + sa_set_security_attr(newsecurity, "sectype", sectype); + } + } + /* dont' do anything if memory wasn't allocated */ + if (newsecurity == NULL) + return (NULL); + + /* found the top so working back down the stack */ + security = sa_get_security((sa_security_t)object, sectype, proto); + if (security != NULL) { + sa_property_t prop; + /* add security to the newsecurity */ + for (prop = sa_get_property(security, NULL); + prop != NULL; prop = sa_get_next_property(prop)) { + sa_property_t newprop; + char *name; + char *value; + name = sa_get_property_attr(prop, "type"); + value = sa_get_property_attr(prop, "value"); + if (name != NULL) { + newprop = sa_get_property(newsecurity, name); + /* replace the value with the new value */ + if (newprop != NULL) { + /* + * only set if value is non NULL, old value ok + * if it is NULL. + */ + if (value != NULL) + set_node_attr(newprop, name, value); + } else { + /* an entirely new property */ + if (value != NULL) { + newprop = sa_create_property(name, value); + newprop = (sa_property_t) + xmlAddChild((xmlNodePtr)newsecurity, + (xmlNodePtr)newprop); + } + } + sa_free_attr_string(name); + } + if (value != NULL) + sa_free_attr_string(value); + } + } + return (newsecurity); +} + +void +sa_free_derived_security(sa_security_t security) +{ + /* while it shouldn't be linked, it doesn't hurt */ + if (security != NULL) { + xmlUnlinkNode((xmlNodePtr)security); + xmlFreeNode((xmlNodePtr)security); + } +} + +/* + * sharetab utility functions + * + * makes use of the original sharetab.c from fs.d/nfs/lib + */ + +/* + * fillshare(share, proto, sh) + * + * Fill the struct share with values obtained from the share object. + */ +static void +fillshare(sa_share_t share, char *proto, struct share *sh) +{ + char *groupname = NULL; + char *value; + sa_group_t group; + char *buff; + char *zfs; + + group = sa_get_parent_group(share); + if (group != NULL) { + zfs = sa_get_group_attr(group, "zfs"); + groupname = sa_get_group_attr(group, "name"); + + if (groupname != NULL && + (strcmp(groupname, "default") == 0 || zfs != NULL)) { + /* + * since the groupname is either "default" or the + * group is a ZFS group, we don't want to keep + * groupname. We do want it if it is any other type of + * group. + */ + sa_free_attr_string(groupname); + groupname = NULL; + } + if (zfs != NULL) + sa_free_attr_string(zfs); + } + + value = sa_get_share_attr(share, "path"); + if (value != NULL) { + sh->sh_path = strdup(value); + sa_free_attr_string(value); + } + + value = sa_get_share_attr(share, "resource"); + if (value != NULL || groupname != NULL) { + int len = 0; + + if (value != NULL) + len += strlen(value); + if (groupname != NULL) + len += strlen(groupname); + len += 3; /* worst case */ + buff = malloc(len); + (void) snprintf(buff, len, "%s%s%s", + (value != NULL && strlen(value) > 0) ? value : "-", + groupname != NULL ? "@" : "", + groupname != NULL ? groupname : ""); + sh->sh_res = buff; + if (value != NULL) + sa_free_attr_string(value); + if (groupname != NULL) { + sa_free_attr_string(groupname); + groupname = NULL; + } + } else { + sh->sh_res = strdup("-"); + } + + sh->sh_fstype = strdup(proto); + value = sa_proto_legacy_format(proto, share, 1); + if (value != NULL) { + if (strlen(value) > 0) + sh->sh_opts = strdup(value); + else + sh->sh_opts = strdup("rw"); + free(value); + } else + sh->sh_opts = strdup("rw"); + + value = sa_get_share_description(share); + if (value != NULL) { + sh->sh_descr = strdup(value); + sa_free_share_description(value); + } else + sh->sh_descr = strdup(""); +} + +/* + * emptyshare(sh) + * + * Free the strings in the non-NULL members of sh. + */ + +static void +emptyshare(struct share *sh) +{ + if (sh->sh_path != NULL) + free(sh->sh_path); + sh->sh_path = NULL; + if (sh->sh_res != NULL) + free(sh->sh_res); + sh->sh_res = NULL; + if (sh->sh_fstype != NULL) + free(sh->sh_fstype); + sh->sh_fstype = NULL; + if (sh->sh_opts != NULL) + free(sh->sh_opts); + sh->sh_opts = NULL; + if (sh->sh_descr != NULL) + free(sh->sh_descr); + sh->sh_descr = NULL; +} + +/* + * sa_update_sharetab(share, proto) + * + * Update the sharetab file with info from the specified share. + * This could be an update or add. + */ + +int +sa_update_sharetab(sa_share_t share, char *proto) +{ + int ret = SA_OK; + struct share shtab; + char *path; + int logging = 0; + FILE *sharetab; + sigset_t old, new; + + path = sa_get_share_attr(share, "path"); + if (path != NULL) { + (void) memset(&shtab, '\0', sizeof (shtab)); + sharetab = fopen(SA_LEGACY_SHARETAB, "r+"); + if (sharetab == NULL) { + sharetab = fopen(SA_LEGACY_SHARETAB, "w+"); + } + if (sharetab != NULL) { + (void) setvbuf(sharetab, NULL, _IOLBF, BUFSIZ * 8); + (void) sigprocmask(SIG_BLOCK, NULL, &new); + (void) sigaddset(&new, SIGHUP); + (void) sigaddset(&new, SIGINT); + (void) sigaddset(&new, SIGQUIT); + (void) sigaddset(&new, SIGTSTP); + (void) sigprocmask(SIG_SETMASK, &new, &old); + (void) lockf(fileno(sharetab), F_LOCK, 0); + (void) remshare(sharetab, path, &logging); + /* fill in share structure and write it out */ + (void) fillshare(share, proto, &shtab); + (void) putshare(sharetab, &shtab); + emptyshare(&shtab); + (void) fflush(sharetab); + (void) lockf(fileno(sharetab), F_ULOCK, 0); + (void) fsync(fileno(sharetab)); + (void) sigprocmask(SIG_SETMASK, &old, NULL); + (void) fclose(sharetab); + } else { + if (errno == EACCES || errno == EPERM) { + ret = SA_NO_PERMISSION; + } else { + ret = SA_CONFIG_ERR; + } + } + sa_free_attr_string(path); + } + return (ret); +} + +/* + * sa_delete_sharetab(path, proto) + * + * remove the specified share from sharetab. + */ + +int +sa_delete_sharetab(char *path, char *proto) +{ + int ret = SA_OK; + int logging = 0; + FILE *sharetab; + sigset_t old, new; +#ifdef lint + proto = proto; +#endif + + if (path != NULL) { + sharetab = fopen(SA_LEGACY_SHARETAB, "r+"); + if (sharetab == NULL) { + sharetab = fopen(SA_LEGACY_SHARETAB, "w+"); + } + if (sharetab != NULL) { + /* should block keyboard level signals around the lock */ + (void) sigprocmask(SIG_BLOCK, NULL, &new); + (void) sigaddset(&new, SIGHUP); + (void) sigaddset(&new, SIGINT); + (void) sigaddset(&new, SIGQUIT); + (void) sigaddset(&new, SIGTSTP); + (void) sigprocmask(SIG_SETMASK, &new, &old); + (void) lockf(fileno(sharetab), F_LOCK, 0); + ret = remshare(sharetab, path, &logging); + (void) fflush(sharetab); + (void) lockf(fileno(sharetab), F_ULOCK, 0); + (void) fsync(fileno(sharetab)); + (void) sigprocmask(SIG_SETMASK, &old, NULL); + (void) fclose(sharetab); + } else { + if (errno == EACCES || errno == EPERM) { + ret = SA_NO_PERMISSION; + } else { + ret = SA_CONFIG_ERR; + } + } + } + return (ret); +} diff --git a/usr/src/lib/libshare/common/llib-lshare b/usr/src/lib/libshare/common/llib-lshare new file mode 100644 index 0000000000..d426f1bf58 --- /dev/null +++ b/usr/src/lib/libshare/common/llib-lshare @@ -0,0 +1,32 @@ +/* + * 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 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* LINTLIBRARY */ +/* PROTOLIB1 */ + +#include "libshare.h" diff --git a/usr/src/lib/libshare/common/mapfile-vers b/usr/src/lib/libshare/common/mapfile-vers new file mode 100644 index 0000000000..9585f9a386 --- /dev/null +++ b/usr/src/lib/libshare/common/mapfile-vers @@ -0,0 +1,110 @@ +# +# 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 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +SUNWprivate_1.1 { + global: + sa_get_optionset; + sa_create_property; + sa_get_property; + sa_create_security; + sa_update_legacy; + sa_get_group_attr; + sa_free_share_description; + sa_remove_group; + sa_add_property; + sa_get_sub_group; + sa_get_next_optionset; + sa_update_property; + sa_destroy_security; + sa_update_sharetab; + sa_create_protocol_properties; + sa_get_group; + sa_get_security; + sa_get_protocol_property; + sa_add_share; + sa_valid_group_name; + sa_get_optionset_attr; + sa_remove_property; + sa_set_security_attr; + sa_delete_sharetab; + sa_format_free; + sa_add_protocol_property; + sa_check_path; + sa_free_derived_optionset; + sa_is_security; + sa_get_protocols; + sa_get_parent_group; + sa_set_optionset_attr; + sa_commit_properties; + sa_parse_legacy_options; + sa_zfs_is_shared; + sa_get_derived_optionset; + sa_move_share; + sa_group_is_zfs; + sa_update_config; + sa_get_share_attr; + sa_create_optionset; + sa_valid_property; + sa_proto_legacy_format; + sa_proto_valid_space; + sa_proto_space_alias; + sa_get_next_protocol_property; + sa_remove_share; + sa_is_share; + sa_get_share_description; + sa_get_share; + sa_get_next_group; + sa_set_share_attr; + sa_destroy_optionset; + sa_get_derived_security; + sa_proto_get_properties; + sa_get_all_security_types; + sa_get_resource; + sa_set_share_description; + sa_set_group_attr; + sa_disable_share; + sa_get_next_property; + sa_get_next_security; + sa_get_protocol_status; + sa_init; + sa_find_share; + sa_set_protocol_property; + sa_fini; + sa_errorstr; + sa_get_next_share; + sa_free_attr_string; + sa_get_property_attr; + sa_get_security_attr; + sa_delete_legacy; + sa_free_derived_security; + sa_enable_share; + sa_create_group; + sa_valid_protocol; + local: + *; +}; + diff --git a/usr/src/lib/libshare/common/parser.c b/usr/src/lib/libshare/common/parser.c new file mode 100644 index 0000000000..3bd8f9f303 --- /dev/null +++ b/usr/src/lib/libshare/common/parser.c @@ -0,0 +1,121 @@ +/* + * 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 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdio.h> +#include <ctype.h> + +#define TK_INIT 0 +#define TK_TOKEN 1 +#define TK_SKIPWHITE 2 +#define TK_QUOTED 3 + +/* + * assumes quoted strings are delimited by white space (i.e sp + * "string" sp). Backslash can be used to quote a quote mark. + * quoted strings will have the quotes stripped. + */ + +char * +get_token(char *string) +{ + static char *orig = NULL; + static char *curp; + char *ret; + int state = TK_INIT; + int c; + int quotechar; + + if (string != orig || string == NULL) { + orig = string; + curp = string; + if (string == NULL) { + return (NULL); + } + } + ret = curp; + while ((c = *curp) != '\0') { + switch (state) { + case TK_SKIPWHITE: + case TK_INIT: + if (isspace(c)) { + while (*curp && isspace(*curp)) + curp++; + ret = curp; + } + if (c == '"' || c == '\'') { + state = TK_QUOTED; + curp++; + ret = curp; + quotechar = c; /* want to match for close */ + } else { + state = TK_TOKEN; + } + break; + case TK_TOKEN: + switch (c) { + case '\\': + curp++; + if (*curp) { + curp++; + break; + } else { + return (ret); + } + break; + default: + if (*curp == '\0' || isspace(c)) { + *curp++ = '\0'; + return (ret); + } + curp++; + break; + } + break; + case TK_QUOTED: + switch (c) { + case '\\': + curp++; + if (*curp) { + curp++; + break; + } + curp++; + break; + default: + if (c == '\0' || c == quotechar) { + *curp++ = '\0'; + return (ret); + } + curp++; + break; + } + break; + } + } + return (NULL); +} diff --git a/usr/src/lib/libshare/common/plugin.c b/usr/src/lib/libshare/common/plugin.c new file mode 100644 index 0000000000..54b95d8fbb --- /dev/null +++ b/usr/src/lib/libshare/common/plugin.c @@ -0,0 +1,458 @@ +/* + * 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 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <libshare.h> +#include "libshare_impl.h" +#include <dlfcn.h> +#include <link.h> +#include <sys/types.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <dirent.h> +#include <libintl.h> + +/* + * protocol plugin interface + * + * finds plugins and makes them accessible. This is only "used" by + * libshare.so. + */ + +struct sa_proto_plugin *sap_proto_list; + +static struct sa_proto_handle sa_proto_handle; + +void proto_plugin_fini(); + +/* + * proto_plugin_init() + * + * Initialize the protocol specific plugin modules. + * + * Walk /usr/lib/fs/\* for libshare_*.so modules. That is, + * /usr/lib/fs/nfs/libshare_nfs.so. The protocol specific directory + * would have a modules with name libshare_<proto>.so. If one is + * found, initialize it and add to the internal list of + * protocols. These are used for protocol specifici operations. + */ + +int +proto_plugin_init() +{ + struct sa_proto_plugin *proto; + int num_protos = 0; + int err; + struct sa_plugin_ops *plugin_ops; + void *dlhandle; + DIR *dir; + struct dirent *dent; + int ret = SA_OK; + struct stat st; + + /* + * should walk "/usr/lib/fs/" for files of the form: + * libshare_*.so + */ + dir = opendir(SA_LIB_DIR); + if (dir != NULL) { + while (ret == SA_OK && (dent = readdir(dir)) != NULL) { + char path[MAXPATHLEN]; + (void) snprintf(path, MAXPATHLEN, + "%s/%s/libshare_%s.so", SA_LIB_DIR, + dent->d_name, dent->d_name); + if (stat(path, &st) < 0) { + /* file doesn't exist, so don't try to map it */ + continue; + } + dlhandle = dlopen(path, RTLD_NOW|RTLD_GLOBAL|RTLD_WORLD); + if (dlhandle != NULL) { + plugin_ops = (struct sa_plugin_ops *) + dlsym(dlhandle, "sa_plugin_ops"); + proto = (struct sa_proto_plugin *) + calloc(1, sizeof (struct sa_proto_plugin)); + if (proto != NULL) { + proto->plugin_ops = plugin_ops; + proto->plugin_handle = dlhandle; + num_protos++; + proto->plugin_next = sap_proto_list; + sap_proto_list = proto; + } else { + ret = SA_NO_MEMORY; + } + } else { + (void) fprintf(stderr, + gettext("Error in plugin for protocol %s: %s\n"), + dent->d_name, dlerror()); + } + } + (void) closedir(dir); + } + if (ret == SA_OK) { + sa_proto_handle.sa_proto = + (char **)calloc(num_protos, sizeof (char *)); + sa_proto_handle.sa_ops = + (struct sa_plugin_ops **)calloc(num_protos, + sizeof (struct sa_plugin_ops *)); + if (sa_proto_handle.sa_proto != NULL && + sa_proto_handle.sa_ops != NULL) { + int i; + struct sa_proto_plugin *tmp; + for (i = 0, tmp = sap_proto_list; i < num_protos; + tmp = tmp->plugin_next) { + err = 0; + if (tmp->plugin_ops->sa_init != NULL) + err = tmp->plugin_ops->sa_init(); + if (err == SA_OK) { + /* only include if the init succeeded or was NULL */ + sa_proto_handle.sa_num_proto++; + sa_proto_handle.sa_ops[i] = tmp->plugin_ops; + sa_proto_handle.sa_proto[i] = + tmp->plugin_ops->sa_protocol; + i++; + } + } + } + } else { + /* there was an error, so cleanup prior to return of failure. */ + proto_plugin_fini(); + } + return (ret); +} + +/* + * proto_plugin_fini() + * + * uninitialize all the plugin modules. + */ + +void +proto_plugin_fini() +{ + /* + * free up all the protocols, calling their fini, if there is + * one. + */ + while (sap_proto_list != NULL) { + struct sa_proto_plugin *next; + next = sap_proto_list->plugin_next; + sap_proto_list->plugin_ops->sa_fini(); + if (sap_proto_list->plugin_handle != NULL) + (void) dlclose(sap_proto_list->plugin_handle); + free(sap_proto_list); + sap_proto_list = next; + } + if (sa_proto_handle.sa_ops != NULL) { + free(sa_proto_handle.sa_ops); + sa_proto_handle.sa_ops = NULL; + } + if (sa_proto_handle.sa_proto != NULL) { + free(sa_proto_handle.sa_proto); + sa_proto_handle.sa_proto = NULL; + } + sa_proto_handle.sa_num_proto = 0; +} + +/* + * find_protocol(proto) + * + * Search the plugin list for the specified protocol and return the + * ops vector. NULL if protocol is not defined. + */ + +static struct sa_plugin_ops * +find_protocol(char *proto) +{ + int i; + + if (proto != NULL) { + for (i = 0; i < sa_proto_handle.sa_num_proto; i++) { + if (strcmp(proto, sa_proto_handle.sa_proto[i]) == 0) + return (sa_proto_handle.sa_ops[i]); + } + } + return (NULL); +} + +/* + * sa_proto_share(proto, share) + * + * Activate a share for the specified protocol. + */ + +int +sa_proto_share(char *proto, sa_share_t share) +{ + struct sa_plugin_ops *ops = find_protocol(proto); + int ret = SA_INVALID_PROTOCOL; + + if (ops != NULL && ops->sa_share != NULL) + ret = ops->sa_share(share); + return (ret); +} + +/* + * sa_proto_unshare(proto, path) + * + * Deactivate (unshare) the path for this protocol. + */ + +int +sa_proto_unshare(char *proto, char *path) +{ + struct sa_plugin_ops *ops = find_protocol(proto); + int ret = SA_INVALID_PROTOCOL; + + if (ops != NULL && ops->sa_unshare != NULL) + ret = ops->sa_unshare(path); + return (ret); +} + +/* + * sa_proto_valid_prop(proto, prop, opt) + * + * check to see if the specified prop is valid for this protocol. + */ + +int +sa_proto_valid_prop(char *proto, sa_property_t prop, sa_optionset_t opt) +{ + struct sa_plugin_ops *ops = find_protocol(proto); + int ret = 0; + + if (ops != NULL && ops->sa_valid_prop != NULL) + ret = ops->sa_valid_prop(prop, opt); + return (ret); +} + +/* + * sa_proto_valid_space(proto, space) + * + * check if space is valid optionspace for proto. + * Protocols that don't implement this don't support spaces. + */ +int +sa_proto_valid_space(char *proto, char *token) +{ + struct sa_plugin_ops *ops = find_protocol(proto); + int ret = 0; + + if (ops != NULL && ops->sa_valid_space != NULL) + ret = ops->sa_valid_space(token); + return (ret); +} + +/* + * sa_proto_space_alias(proto, space) + * + * if the name for space is an alias, return its proper name. This is + * used to translate "default" values into proper form. + */ +char * +sa_proto_space_alias(char *proto, char *space) +{ + struct sa_plugin_ops *ops = find_protocol(proto); + char *ret = space; + + if (ops != NULL && ops->sa_space_alias != NULL) + ret = ops->sa_space_alias(space); + return (ret); +} + +/* + * sa_proto_security_prop(proto, token) + * + * Check to see if the property name in token is a valid named + * optionset property. + */ + +int +sa_proto_security_prop(char *proto, char *token) +{ + struct sa_plugin_ops *ops = find_protocol(proto); + int ret = 0; + + if (ops != NULL && ops->sa_security_prop != NULL) + ret = ops->sa_security_prop(token); + return (ret); +} + +/* + * sa_proto_legacy_opts(proto, grouup, options) + * + * Have the protocol specific parser parse the options string and add + * an appropriate optionset to group. + */ + +int +sa_proto_legacy_opts(char *proto, sa_group_t group, char *options) +{ + struct sa_plugin_ops *ops = find_protocol(proto); + int ret = SA_INVALID_PROTOCOL; + + if (ops != NULL && ops->sa_legacy_opts != NULL) + ret = ops->sa_legacy_opts(group, options); + return (ret); +} + +/* + * sa_proto_legacy_format(proto, group, hier) + * + * Return a legacy format string representing either the group's + * properties or the groups hierarchical properties. + */ + +char * +sa_proto_legacy_format(char *proto, sa_group_t group, int hier) +{ + struct sa_plugin_ops *ops = find_protocol(proto); + char *ret = NULL; + + if (ops != NULL && ops->sa_legacy_format != NULL) + ret = ops->sa_legacy_format(group, hier); + return (ret); +} + +void +sa_format_free(char *str) +{ + free(str); +} + +/* + * sharectl related API functions + */ + +/* + * sa_proto_get_properties(proto) + * + * Return the set of properties that are specific to the + * protocol. These are usually in /etc/dfs/<proto> and related files, + * but only the protocol module knows which ones for sure. + */ + +sa_protocol_properties_t +sa_proto_get_properties(char *proto) +{ + struct sa_plugin_ops *ops = find_protocol(proto); + sa_protocol_properties_t props = NULL; + + if (ops != NULL && ops->sa_get_proto_set != NULL) + props = ops->sa_get_proto_set(); + return (props); +} + +/* + * sa_proto_set_property(proto, prop) + * + * Update the protocol specifiec property. + */ + +int +sa_proto_set_property(char *proto, sa_property_t prop) +{ + struct sa_plugin_ops *ops = find_protocol(proto); + int ret = SA_OK; + if (ops != NULL && ops->sa_set_proto_prop != NULL) + ret = ops->sa_set_proto_prop(prop); + return (ret); +} + +/* + * sa_valid_protocol(proto) + * + * check to see if the protocol specified is defined by a + * plugin. Returns true (1) or false (0) + */ + +int +sa_valid_protocol(char *proto) +{ + struct sa_plugin_ops *ops = find_protocol(proto); + return (ops != NULL); +} + +/* + * Return the current operational status of the protocol + */ + +char * +sa_get_protocol_status(char *proto) +{ + struct sa_plugin_ops *ops = find_protocol(proto); + char *ret = NULL; + if (ops != NULL && ops->sa_get_proto_status != NULL) + ret = ops->sa_get_proto_status(proto); + return (ret); +} + +/* + * sa_proto_update_legacy(proto, share) + * + * Update the protocol specific legacy files if necessary for the + * specified share. + */ + +int +sa_proto_update_legacy(char *proto, sa_share_t share) +{ + struct sa_plugin_ops *ops = find_protocol(proto); + int ret = SA_NOT_IMPLEMENTED; + + if (ops != NULL) { + if (ops->sa_update_legacy != NULL) + ret = ops->sa_update_legacy(share); + } + return (ret); +} + +/* + * sa_delete_legacy(proto, share) + * + * remove the specified share from the protocol specific legacy files. + */ + +int +sa_proto_delete_legacy(char *proto, sa_share_t share) +{ + struct sa_plugin_ops *ops = find_protocol(proto); + int ret = SA_OK; + + if (ops != NULL) { + if (ops->sa_delete_legacy != NULL) + ret = ops->sa_delete_legacy(share); + } else { + if (proto != NULL) + ret = SA_NOT_IMPLEMENTED; + else + ret = SA_INVALID_PROTOCOL; + } + return (ret); +} diff --git a/usr/src/lib/libshare/common/scfutil.c b/usr/src/lib/libshare/common/scfutil.c new file mode 100644 index 0000000000..88447471a6 --- /dev/null +++ b/usr/src/lib/libshare/common/scfutil.c @@ -0,0 +1,1443 @@ +/* + * 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 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* helper functions for using libscf with sharemgr */ + +#include <libscf.h> +#include <libxml/parser.h> +#include <libxml/tree.h> +#include "libshare.h" +#include "libshare_impl.h" +#include "scfutil.h" +#include <string.h> +#include <errno.h> +#include <uuid/uuid.h> +#include <sys/param.h> + +ssize_t scf_max_name_len; +extern struct sa_proto_plugin *sap_proto_list; + +/* + * The SMF facility uses some properties that must exist. We want to + * skip over these when processing protocol options. + */ +static char *skip_props[] = { + "modify_authorization", + "action_authorization", + "value_authorization", + NULL +}; + +/* + * sa_scf_fini(handle) + * + * must be called when done. Called with the handle allocated in + * sa_scf_init(), it cleans up the state and frees any SCF resources + * still in use. Called by sa_fini(). + */ + +void +sa_scf_fini(scfutilhandle_t *handle) +{ + if (handle != NULL) { + int unbind = 0; + if (handle->scope != NULL) { + unbind = 1; + scf_scope_destroy(handle->scope); + } + if (handle->service != NULL) + scf_service_destroy(handle->service); + if (handle->pg != NULL) + scf_pg_destroy(handle->pg); + if (handle->handle != NULL) { + handle->scf_state = SCH_STATE_UNINIT; + if (unbind) + (void) scf_handle_unbind(handle->handle); + scf_handle_destroy(handle->handle); + } + free(handle); + } +} + +/* + * sa_scf_init() + * + * must be called before using any of the SCF functions. Called by + * sa_init() during the API setup. + */ + +scfutilhandle_t * +sa_scf_init() +{ + scfutilhandle_t *handle; + + scf_max_name_len = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH); + if (scf_max_name_len <= 0) + scf_max_name_len = SA_MAX_NAME_LEN + 1; + + handle = calloc(1, sizeof (scfutilhandle_t)); + if (handle != NULL) { + handle->scf_state = SCH_STATE_INITIALIZING; + handle->handle = scf_handle_create(SCF_VERSION); + if (handle->handle != NULL) { + if (scf_handle_bind(handle->handle) == 0) { + handle->scope = scf_scope_create(handle->handle); + handle->service = scf_service_create(handle->handle); + handle->pg = scf_pg_create(handle->handle); + handle->instance = scf_instance_create(handle->handle); + if (scf_handle_get_scope(handle->handle, + SCF_SCOPE_LOCAL, handle->scope) == 0) { + if (scf_scope_get_service(handle->scope, + SA_GROUP_SVC_NAME, + handle->service) != 0) { + goto err; + } + handle->scf_state = SCH_STATE_INIT; + if (sa_get_instance(handle, "default") != SA_OK) { + char **protolist; + int numprotos, i; + sa_group_t defgrp; + defgrp = sa_create_group("default", NULL); + if (defgrp != NULL) { + numprotos = sa_get_protocols(&protolist); + for (i = 0; i < numprotos; i++) { + (void) sa_create_optionset(defgrp, + protolist[i]); + } + if (protolist != NULL) + free(protolist); + } + } + } else { + goto err; + } + } else { + goto err; + } + } else { + free(handle); + handle = NULL; + (void) printf("libshare could not access SMF repository: %s\n", + scf_strerror(scf_error())); + } + } + return (handle); + + /* error handling/unwinding */ +err: + (void) sa_scf_fini(handle); + (void) printf("libshare SMF initialization problem: %s\n", + scf_strerror(scf_error())); + return (NULL); +} + +/* + * get_scf_limit(name) + * + * Since we use scf_limit a lot and do the same check and return the + * same value if it fails, implement as a function for code + * simplification. Basically, if name isn't found, return MAXPATHLEN + * (1024) so we have a reasonable default buffer size. + */ +static ssize_t +get_scf_limit(uint32_t name) +{ + ssize_t vallen; + + vallen = scf_limit(name); + if (vallen == (ssize_t)-1) + vallen = MAXPATHLEN; + return (vallen); +} + +/* + * skip_property(name) + * + * internal function to check to see if a property is an SMF magic + * property that needs to be skipped. + */ +static int +skip_property(char *name) +{ + int i; + + for (i = 0; skip_props[i] != NULL; i++) + if (strcmp(name, skip_props[i]) == 0) + return (1); + return (0); +} + +/* + * generate_unique_sharename(sharename) + * + * Shares are represented in SMF as property groups. Due to share + * paths containing characters that are not allowed in SMF names and + * the need to be unique, we use UUIDs to construct a unique name. + */ + +static void +generate_unique_sharename(char *sharename) +{ + uuid_t uuid; + + uuid_generate(uuid); + (void) strcpy(sharename, "S-"); + uuid_unparse(uuid, sharename + 2); +} + +/* + * valid_protocol(proto) + * + * check to see if the specified protocol is a valid one for the + * general sharemgr facility. We determine this by checking which + * plugin protocols were found. + */ + +static int +valid_protocol(char *proto) +{ + struct sa_proto_plugin *plugin; + for (plugin = sap_proto_list; plugin != NULL; + plugin = plugin->plugin_next) + if (strcmp(proto, plugin->plugin_ops->sa_protocol) == 0) + return (1); + return (0); +} + +/* + * sa_extract_pgroup(root, handle, pg, nodetype, proto, sectype) + * + * extract the name property group and create the specified type of + * node on the provided group. type will be optionset or security. + */ + +static int +sa_extract_pgroup(xmlNodePtr root, scfutilhandle_t *handle, + scf_propertygroup_t *pg, + char *nodetype, char *proto, char *sectype) +{ + xmlNodePtr node; + scf_iter_t *iter; + scf_property_t *prop; + scf_value_t *value; + char *name; + char *valuestr; + ssize_t vallen; + int ret = SA_OK; + + vallen = get_scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH); + + node = xmlNewChild(root, NULL, (xmlChar *)nodetype, NULL); + if (node != NULL) { + if (proto != NULL) + xmlSetProp(node, (xmlChar *)"type", (xmlChar *)proto); + if (sectype != NULL) + xmlSetProp(node, (xmlChar *)"sectype", (xmlChar *)sectype); + /* + * have node to work with so iterate over the properties + * in the pg and create option sub nodes. + */ + iter = scf_iter_create(handle->handle); + value = scf_value_create(handle->handle); + prop = scf_property_create(handle->handle); + name = malloc(scf_max_name_len); + valuestr = malloc(vallen); + /* + * want to iterate through the properties and add them + * to the base optionset. + */ + if (iter != NULL && value != NULL && prop != NULL && + valuestr != NULL && name != NULL) { + if (scf_iter_pg_properties(iter, pg) == 0) { + /* now iterate the properties in the group */ + while (scf_iter_next_property(iter, prop) > 0) { + /* have a property */ + if (scf_property_get_name(prop, name, + scf_max_name_len) > 0) { + /* some properties are part of the framework */ + if (skip_property(name)) + continue; + if (scf_property_get_value(prop, value) == 0) { + if (scf_value_get_astring(value, valuestr, + vallen) >= 0) { + sa_property_t saprop; + saprop = sa_create_property(name, + valuestr); + if (saprop != NULL) { + /* + * since in SMF, don't + * recurse. Use xmlAddChild + * directly, instead. + */ + xmlAddChild(node, + (xmlNodePtr) saprop); + } + } + } + } + } + } + } else { + ret = SA_NO_MEMORY; + } + /* cleanup to avoid memory leaks */ + if (value != NULL) + scf_value_destroy(value); + if (iter != NULL) + scf_iter_destroy(iter); + if (prop != NULL) + scf_property_destroy(prop); + if (name != NULL) + free(name); + if (valuestr != NULL) + free(valuestr); + } + return (ret); +} + +/* + * sa_extract_attrs(root, handle, instance) + * + * local function to extract the actual attributes/properties from the + * property group of the service instance. These are the well known + * attributes of "state" and "zfs". If additional attributes are + * added, they should be added here. + */ + +static void +sa_extract_attrs(xmlNodePtr root, scfutilhandle_t *handle, + scf_instance_t *instance) +{ + scf_property_t *prop; + scf_value_t *value; + char *valuestr; + ssize_t vallen; + + vallen = get_scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH); + prop = scf_property_create(handle->handle); + value = scf_value_create(handle->handle); + valuestr = malloc(vallen); + if (prop != NULL && value != NULL && valuestr != NULL && + scf_instance_get_pg(instance, "operation", + handle->pg) == 0) { + /* + * have a property group with desired name so now get + * the known attributes. + */ + if (scf_pg_get_property(handle->pg, "state", prop) == 0) { + /* found the property so get the value */ + if (scf_property_get_value(prop, value) == 0) { + if (scf_value_get_astring(value, valuestr, vallen) >= 0) { + xmlSetProp(root, (xmlChar *)"state", + (xmlChar *)valuestr); + } + } + } + if (scf_pg_get_property(handle->pg, "zfs", prop) == 0) { + /* found the property so get the value */ + if (scf_property_get_value(prop, value) == 0) { + if (scf_value_get_astring(value, valuestr, vallen) > 0) { + xmlSetProp(root, (xmlChar *)"zfs", + (xmlChar *)valuestr); + } + } + } + } + if (valuestr != NULL) + free(valuestr); + if (value != NULL) + scf_value_destroy(value); + if (prop != NULL) + scf_property_destroy(prop); +} + +/* + * list of known share attributes. + */ + +static char *share_attr[] = { + "path", + "id", + "resource", + NULL, +}; + +static int +is_share_attr(char *name) +{ + int i; + for (i = 0; share_attr[i] != NULL; i++) + if (strcmp(name, share_attr[i]) == 0) + return (1); + return (0); +} + +/* + * sa_share_from_pgroup + * + * extract the share definition from the share property group. We do + * some sanity checking to avoid bad data. + * + * Since this is only constructing the internal data structures, we + * don't use the sa_* functions most of the time. + */ +void +sa_share_from_pgroup(xmlNodePtr root, scfutilhandle_t *handle, + scf_propertygroup_t *pg, char *id) +{ + xmlNodePtr node; + char *name; + scf_iter_t *iter; + scf_property_t *prop; + scf_value_t *value; + ssize_t vallen; + char *valuestr; + int ret = SA_OK; + + /* + * While preliminary check (starts with 'S') passed before + * getting here. Need to make sure it is in ID syntax + * (Snnnnnn). Note that shares with properties have similar + * pgroups. + */ + vallen = strlen(id); + if (*id == SA_SHARE_PG_PREFIX[0] && vallen == SA_SHARE_PG_LEN) { + uuid_t uuid; + if (strncmp(id, SA_SHARE_PG_PREFIX, SA_SHARE_PG_PREFIXLEN) != 0 || + uuid_parse(id + 2, uuid) < 0) + return; + } else { + return; + } + + vallen = get_scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH); + + iter = scf_iter_create(handle->handle); + value = scf_value_create(handle->handle); + prop = scf_property_create(handle->handle); + name = malloc(scf_max_name_len); + valuestr = malloc(vallen); + + /* + * construct the share XML node. It is similar to sa_add_share + * but never changes the repository. Also, there won't be any + * ZFS or transient shares. Root will be the group it is + * associated with. + */ + node = xmlNewChild(root, NULL, (xmlChar *)"share", NULL); + if (node != NULL) { + /* + * make sure the UUID part of the property group is + * stored in the share "id" property. We use this + * later. + */ + xmlSetProp(node, (xmlChar *)"id", (xmlChar *)id); + xmlSetProp(node, (xmlChar *)"type", (xmlChar *)"persist"); + } + + if (iter != NULL && value != NULL && prop != NULL && name != NULL) { + /* iterate over the share pg properties */ + if (scf_iter_pg_properties(iter, pg) == 0) { + while (scf_iter_next_property(iter, prop) > 0) { + ret = SA_SYSTEM_ERR; /* assume the worst */ + if (scf_property_get_name(prop, name, + scf_max_name_len) > 0) { + if (scf_property_get_value(prop, value) == 0) { + if (scf_value_get_astring(value, valuestr, + vallen) >= 0) { + ret = SA_OK; + } + } + } + if (ret == SA_OK) { + if (is_share_attr(name)) { + /* + * if a share attr, then simple - + * usually path and resource name + */ + xmlSetProp(node, (xmlChar *)name, + (xmlChar *)valuestr); + } else { + if (strcmp(name, "description") == 0) { + /* we have a description node */ + xmlNodePtr desc; + desc = xmlNewChild(node, NULL, + (xmlChar *)"description", + NULL); + if (desc != NULL) + xmlNodeSetContent(desc, + (xmlChar *)valuestr); + } + } + } + } + } + } + if (name != NULL) + free(name); + if (valuestr != NULL) + free(valuestr); + if (value != NULL) + scf_value_destroy(value); + if (iter != NULL) + scf_iter_destroy(iter); + if (prop != NULL) + scf_property_destroy(prop); +} + +/* + * find_share_by_id(shareid) + * + * Search all shares in all groups until we find the share represented + * by "id". + */ + +static sa_share_t +find_share_by_id(char *shareid) +{ + sa_group_t group; + sa_share_t share = NULL; + char *id = NULL; + int done = 0; + + for (group = sa_get_group(NULL); group != NULL && !done; + group = sa_get_next_group(group)) { + for (share = sa_get_share(group, NULL); share != NULL; + share = sa_get_next_share(share)) { + id = sa_get_share_attr(share, "id"); + if (id != NULL && strcmp(id, shareid) == 0) { + sa_free_attr_string(id); + id = NULL; + done++; + break; + } + if (id != NULL) { + sa_free_attr_string(id); + id = NULL; + } + } + } + return (share); +} + +/* + * sa_share_props_from_pgroup(root, handle, pg, id) + * + * extract share properties from the SMF property group. More sanity + * checks are done and the share object is created. We ignore some + * errors that could exist in the repository and only worry about + * property groups that validate in naming. + */ + +static int +sa_share_props_from_pgroup(xmlNodePtr root, scfutilhandle_t *handle, + scf_propertygroup_t *pg, char *id) +{ + xmlNodePtr node; + char *name; + scf_iter_t *iter; + scf_property_t *prop; + scf_value_t *value; + ssize_t vallen; + char *valuestr; + int ret = SA_OK; + char *sectype = NULL; + char *proto; + sa_share_t share; + + /* + * While preliminary check (starts with 'S') passed before + * getting here. Need to make sure it is in ID syntax + * (Snnnnnn). Note that shares with properties have similar + * pgroups. If the pg name is more than SA_SHARE_PG_LEN + * characters, it is likely one of the protocol/security + * versions. + */ + vallen = strlen(id); + if (*id == SA_SHARE_PG_PREFIX[0] && vallen > SA_SHARE_PG_LEN) { + uuid_t uuid; + if (strncmp(id, SA_SHARE_PG_PREFIX, SA_SHARE_PG_PREFIXLEN) == 0) { + proto = strchr(id, '_'); + if (proto == NULL) + return (ret); + *proto++ = '\0'; + if (uuid_parse(id + SA_SHARE_PG_PREFIXLEN, uuid) < 0) + return (ret); + /* + * probably a legal optionset so check a few more + * syntax points below. + */ + if (*proto == '\0') { + /* not a valid proto (null) */ + return (ret); + } + sectype = strchr(proto, '_'); + if (sectype != NULL) + *sectype++ = '\0'; + if (!valid_protocol(proto)) + return (ret); + } + } else { + /* + * it is ok to not have what we thought since someone might + * have added a name via SMF. + */ + return (ret); + } + + /* + * to get here, we have a valid protocol and possibly a + * security. We now have to find the share that it is really + * associated with. The "id" portion of the pgroup name will + * match. + */ + + share = find_share_by_id(id); + if (share == NULL) + return (SA_BAD_PATH); + + root = (xmlNodePtr)share; + + vallen = get_scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH); + + iter = scf_iter_create(handle->handle); + value = scf_value_create(handle->handle); + prop = scf_property_create(handle->handle); + name = malloc(scf_max_name_len); + valuestr = malloc(vallen); + + if (sectype == NULL) + node = xmlNewChild(root, NULL, (xmlChar *)"optionset", NULL); + else { + node = xmlNewChild(root, NULL, (xmlChar *)"security", NULL); + if (node != NULL) + xmlSetProp(node, (xmlChar *)"sectype", (xmlChar *)sectype); + } + if (node != NULL) { + xmlSetProp(node, (xmlChar *)"type", (xmlChar *)proto); + /* now find the properties */ + if (iter != NULL && value != NULL && prop != NULL && name != NULL) { + /* iterate over the share pg properties */ + if (scf_iter_pg_properties(iter, pg) == 0) { + while (scf_iter_next_property(iter, prop) > 0) { + ret = SA_SYSTEM_ERR; /* assume the worst */ + if (scf_property_get_name(prop, name, + scf_max_name_len) > 0) { + if (scf_property_get_value(prop, value) == 0) { + if (scf_value_get_astring(value, valuestr, + vallen) >= 0) { + ret = SA_OK; + } + } + } else { + ret = SA_SYSTEM_ERR; + } + if (ret == SA_OK) { + sa_property_t prop; + prop = sa_create_property(name, valuestr); + if (prop != NULL) + prop = (sa_property_t)xmlAddChild(node, + (xmlNodePtr)prop); + else + ret = SA_NO_MEMORY; + } + } + } else { + ret = SA_SYSTEM_ERR; + } + } + } else { + ret = SA_NO_MEMORY; + } + if (iter != NULL) + scf_iter_destroy(iter); + if (value != NULL) + scf_value_destroy(value); + if (prop != NULL) + scf_property_destroy(prop); + if (name != NULL) + free(name); + if (valuestr != NULL) + free(valuestr); + return (ret); +} + +/* + * sa_extract_group(root, handle, instance) + * + * get the config info for this instance of a group and create the XML + * subtree from it. + */ + +static int +sa_extract_group(xmlNodePtr root, scfutilhandle_t *handle, + scf_instance_t *instance) +{ + char *buff; + xmlNodePtr node; + scf_iter_t *iter; + char *proto; + char *sectype; + int have_shares = 0; + int has_proto = 0; + int is_default = 0; + int ret = SA_OK; + int err; + + buff = malloc(scf_max_name_len); + iter = scf_iter_create(handle->handle); + if (buff != NULL) { + if (scf_instance_get_name(instance, buff, + scf_max_name_len) > 0) { + node = xmlNewChild(root, NULL, (xmlChar *)"group", NULL); + if (node != NULL) { + xmlSetProp(node, (xmlChar *)"name", (xmlChar *)buff); + if (strcmp(buff, "default") == 0) + is_default++; + sa_extract_attrs(node, handle, instance); + /* + * Iterate through all the property groups + * looking for those with security or + * optionset prefixes. The names of the + * matching pgroups are parsed to get the + * protocol, and for security, the sectype. + * Syntax is as follows: + * optionset | optionset_<proto> + * security_default | security_<proto>_<sectype> + * "operation" is handled by + * sa_extract_attrs(). + */ + if (iter != NULL) { + if (scf_iter_instance_pgs(iter, instance) == 0) { + while (scf_iter_next_pg(iter, handle->pg) > 0) { + /* have a pgroup so sort it out */ + ret = scf_pg_get_name(handle->pg, buff, + scf_max_name_len); + if (ret > 0) { + if (buff[0] == SA_SHARE_PG_PREFIX[0]) { + sa_share_from_pgroup(node, handle, + handle->pg, + buff); + have_shares++; + } else if (strncmp(buff, "optionset", 9) == + 0) { + char *nodetype = "optionset"; + /* have an optionset */ + sectype = NULL; + proto = strchr(buff, '_'); + if (proto != NULL) { + *proto++ = '\0'; + sectype = strchr(proto, '_'); + if (sectype != NULL) { + *sectype++ = '\0'; + nodetype = "security"; + } + } + ret = sa_extract_pgroup(node, handle, + handle->pg, + nodetype, + proto, sectype); + has_proto++; + } else if (strncmp(buff, + "security", 8) == 0) { + /* + * have a security (note that + * this should change in the + * future) + */ + proto = strchr(buff, '_'); + sectype = NULL; + if (proto != NULL) { + *proto++ = '\0'; + sectype = strchr(proto, '_'); + if (sectype != NULL) + *sectype++ = '\0'; + if (strcmp(proto, "default") == 0) + proto = NULL; + } + ret = sa_extract_pgroup(node, handle, + handle->pg, + "security", proto, + sectype); + has_proto++; + } + /* ignore everything else */ + } + } + } else { + ret = SA_NO_MEMORY; + } + /* + * Make sure we have a valid default group. + * On first boot, default won't have any + * protocols defined and won't be enabled (but + * should be). + */ + if (is_default) { + char *state = sa_get_group_attr((sa_group_t)node, + "state"); + char **protos; + int numprotos; + int i; + + if (state == NULL) { + /* set attribute to enabled */ + (void) sa_set_group_attr((sa_group_t)node, + "state", + "enabled"); + /* we can assume no protocols */ + numprotos = sa_get_protocols(&protos); + for (i = 0; i < numprotos; i++) + (void) sa_create_optionset((sa_group_t)node, + protos[i]); + if (numprotos > 0) + free(protos); + } else { + sa_free_attr_string(state); + } + } + /* do a second pass if shares were found */ + if (have_shares && + scf_iter_instance_pgs(iter, instance) == 0) { + while (scf_iter_next_pg(iter, handle->pg) > 0) { + /* + * have a pgroup so see if it is a + * share optionset + */ + err = scf_pg_get_name(handle->pg, buff, + scf_max_name_len); + if (err > 0) { + if (buff[0] == SA_SHARE_PG_PREFIX[0]) { + ret = sa_share_props_from_pgroup(node, + handle, + handle->pg, + buff); + } + } + } + } + } + } + } + } + if (iter != NULL) + scf_iter_destroy(iter); + if (buff != NULL) + free(buff); + return (ret); +} + +/* + * sa_extract_defaults(root, handle, instance) + * + * local function to find the default properties that live in the + * default instance's "operation" proprerty group. + */ + +static void +sa_extract_defaults(xmlNodePtr root, scfutilhandle_t *handle, + scf_instance_t *instance) +{ + xmlNodePtr node; + scf_property_t *prop; + scf_value_t *value; + char *valuestr; + ssize_t vallen; + + vallen = get_scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH); + prop = scf_property_create(handle->handle); + value = scf_value_create(handle->handle); + valuestr = malloc(vallen); + if (prop != NULL && value != NULL && vallen != NULL && + scf_instance_get_pg(instance, "operation", + handle->pg) == 0) { + if (scf_pg_get_property(handle->pg, + "legacy-timestamp", prop) == 0) { + /* found the property so get the value */ + if (scf_property_get_value(prop, value) == 0) { + if (scf_value_get_astring(value, valuestr, vallen) > 0) { + node = xmlNewChild(root, NULL, (xmlChar *)"legacy", + NULL); + if (node != NULL) { + xmlSetProp(node, (xmlChar *)"timestamp", + (xmlChar *)valuestr); + xmlSetProp(node, (xmlChar *)"path", + (xmlChar *)SA_LEGACY_DFSTAB); + } + } + } + } + } + if (valuestr != NULL) + free(valuestr); + if (value != NULL) + scf_value_destroy(value); + if (prop != NULL) + scf_property_destroy(prop); +} + + +/* + * sa_get_config(handle, root, doc) + * + * walk the SMF repository for /network/shares/group and find all the + * instances. These become group names. Then add the XML structure + * below the groups based on property groups and properties. + */ +int +sa_get_config(scfutilhandle_t *handle, xmlNodePtr *root, xmlDocPtr *doc) +{ + int ret = SA_OK; + scf_instance_t *instance; + scf_iter_t *iter; + char buff[BUFSIZ * 2]; + + *doc = xmlNewDoc((xmlChar *)"1.0"); + *root = xmlNewNode(NULL, (xmlChar *)"sharecfg"); + instance = scf_instance_create(handle->handle); + iter = scf_iter_create(handle->handle); + if (*doc != NULL && *root != NULL && instance != NULL && iter != NULL) { + xmlDocSetRootElement(*doc, *root); + if ((ret = scf_iter_service_instances(iter, + handle->service)) == 0) { + while ((ret = scf_iter_next_instance(iter, + instance)) > 0) { + if (scf_instance_get_name(instance, buff, + sizeof (buff)) > 0) { + if (strcmp(buff, "default") == 0) + sa_extract_defaults(*root, handle, instance); + ret = sa_extract_group(*root, handle, instance); + } + } + } + } else { + /* if we can't create the document, cleanup */ + if (*doc != NULL) + xmlFreeDoc(*doc); + if (*root != NULL) + xmlFreeNode(*root); + *doc = NULL; + *root = NULL; + } + /* always cleanup these */ + if (instance != NULL) + scf_instance_destroy(instance); + if (iter != NULL) + scf_iter_destroy(iter); + return (ret); +} + +/* + * sa_get_instance(handle, instance) + * + * get the instance of the group service. This is actually the + * specific group name. The instance is needed for all property and + * control operations. + */ + +int +sa_get_instance(scfutilhandle_t *handle, char *instname) +{ + if (scf_service_get_instance(handle->service, instname, + handle->instance) != 0) { + return (SA_NO_SUCH_GROUP); + } + return (SA_OK); +} + +/* + * sa_create_instance(handle, instname) + * + * Create a new SMF service instance. There can only be one with a + * given name. + */ + +int +sa_create_instance(scfutilhandle_t *handle, char *instname) +{ + int ret = SA_OK; + char instance[SA_GROUP_INST_LEN]; + if (scf_service_add_instance(handle->service, instname, + handle->instance) != 0) { + /* better error returns need to be added based on real error */ + if (scf_error() == SCF_ERROR_PERMISSION_DENIED) + ret = SA_NO_PERMISSION; + else + ret = SA_DUPLICATE_NAME; + } else { + /* have the service created, so enable it */ + (void) snprintf(instance, sizeof (instance), "%s:%s", + SA_SVC_FMRI_BASE, instname); + (void) smf_enable_instance(instance, 0); + } + return (ret); +} + +/* + * sa_delete_instance(handle, instname) + * + * When a group goes away, we also remove the service instance. + */ + +int +sa_delete_instance(scfutilhandle_t *handle, char *instname) +{ + int ret; + + if (strcmp(instname, "default") == 0) { + ret = SA_NO_PERMISSION; + } else { + if ((ret = sa_get_instance(handle, instname)) == SA_OK) { + if (scf_instance_delete(handle->instance) != 0) + /* need better analysis */ + ret = SA_NO_PERMISSION; + } + } + return (ret); +} + +/* + * sa_create_pgroup(handle, pgroup) + * + * create a new property group + */ + +int +sa_create_pgroup(scfutilhandle_t *handle, char *pgroup) +{ + int ret = SA_OK; + /* + * only create a handle if it doesn't exist. It is ok to exist + * since the pg handle will be set as a side effect. + */ + if (handle->pg == NULL) { + handle->pg = scf_pg_create(handle->handle); + } + /* + * if the pgroup exists, we are done. If it doesn't, then we + * need to actually add one to the service instance. + */ + if (scf_instance_get_pg(handle->instance, + pgroup, handle->pg) != 0) { + /* doesn't exist so create one */ + if (scf_instance_add_pg(handle->instance, pgroup, + SCF_GROUP_APPLICATION, 0, + handle->pg) != 0) { + switch (scf_error()) { + case SCF_ERROR_PERMISSION_DENIED: + ret = SA_NO_PERMISSION; + break; + default: + ret = SA_SYSTEM_ERR; + break; + } + } + } + return (ret); +} + +/* + * sa_delete_pgroup(handle, pgroup) + * + * remove the property group from the current instance of the service, + * but only if it actually exists. + */ + +int +sa_delete_pgroup(scfutilhandle_t *handle, char *pgroup) +{ + int ret = SA_OK; + /* + * only delete if it does exist. + */ + if (scf_instance_get_pg(handle->instance, + pgroup, handle->pg) == 0) { + /* does exist so delete it */ + if (scf_pg_delete(handle->pg) != 0) { + ret = SA_SYSTEM_ERR; + } + } else { + ret = SA_SYSTEM_ERR; + } + if (ret == SA_SYSTEM_ERR && + scf_error() == SCF_ERROR_PERMISSION_DENIED) { + ret = SA_NO_PERMISSION; + } + return (ret); +} + +/* + * sa_start_transaction(handle, pgroup) + * + * Start an SMF transaction so we can deal with properties. it would + * be nice to not have to expose this, but we have to in order to + * optimize. + * + * Basic model is to hold the transaction in the handle and allow + * property adds/deletes/updates to be added then close the + * transaction (or abort). There may eventually be a need to handle + * other types of transaction mechanisms but we don't do that now. + * + * An sa_start_transaction must be followed by either an + * sa_end_transaction or sa_abort_transaction before another + * sa_start_transaction can be done. + */ + +int +sa_start_transaction(scfutilhandle_t *handle, char *propgroup) +{ + int ret = SA_OK; + /* + * lookup the property group and create it if it doesn't already + * exist. + */ + if (handle->scf_state == SCH_STATE_INIT) { + ret = sa_create_pgroup(handle, propgroup); + if (ret == SA_OK) { + handle->trans = scf_transaction_create(handle->handle); + if (handle->trans != NULL) { + if (scf_transaction_start(handle->trans, handle->pg) != 0) { + ret = SA_SYSTEM_ERR; + } + if (ret != SA_OK) { + scf_transaction_destroy(handle->trans); + handle->trans = NULL; + } + } else { + ret = SA_SYSTEM_ERR; + } + } + } + if (ret == SA_SYSTEM_ERR && + scf_error() == SCF_ERROR_PERMISSION_DENIED) { + ret = SA_NO_PERMISSION; + } + return (ret); +} + +/* + * sa_end_transaction(handle) + * + * Commit the changes that were added to the transaction in the + * handle. Do all necessary cleanup. + */ + +int +sa_end_transaction(scfutilhandle_t *handle) +{ + int ret = SA_OK; + + if (handle->trans == NULL) { + ret = SA_SYSTEM_ERR; + } else { + if (scf_transaction_commit(handle->trans) < 0) + ret = SA_SYSTEM_ERR; + scf_transaction_destroy_children(handle->trans); + scf_transaction_destroy(handle->trans); + handle->trans = NULL; + } + return (ret); +} + +/* + * sa_abort_transaction(handle) + * + * Abort the changes that were added to the transaction in the + * handle. Do all necessary cleanup. + */ + +void +sa_abort_transaction(scfutilhandle_t *handle) +{ + if (handle->trans != NULL) { + scf_transaction_reset_all(handle->trans); + scf_transaction_destroy_children(handle->trans); + scf_transaction_destroy(handle->trans); + handle->trans = NULL; + } +} + +/* + * sa_set_property(handle, prop, value) + * + * set a property transaction entry into the pending SMF transaction. + */ + +int +sa_set_property(scfutilhandle_t *handle, char *propname, char *valstr) +{ + int ret = SA_OK; + scf_value_t *value; + scf_transaction_entry_t *entry; + /* + * properties must be set in transactions and don't take + * effect until the transaction has been ended/committed. + */ + value = scf_value_create(handle->handle); + entry = scf_entry_create(handle->handle); + if (value != NULL && entry != NULL) { + if (scf_transaction_property_change(handle->trans, entry, + propname, + SCF_TYPE_ASTRING) == 0 || + scf_transaction_property_new(handle->trans, entry, + propname, + SCF_TYPE_ASTRING) == 0) { + if (scf_value_set_astring(value, valstr) == 0) { + if (scf_entry_add_value(entry, value) != 0) { + ret = SA_SYSTEM_ERR; + scf_value_destroy(value); + } + /* the value is in the transaction */ + value = NULL; + } else { + /* value couldn't be constructed */ + ret = SA_SYSTEM_ERR; + } + /* the entry is in the transaction */ + entry = NULL; + } else { + ret = SA_SYSTEM_ERR; + } + } else { + ret = SA_SYSTEM_ERR; + } + if (ret == SA_SYSTEM_ERR) { + switch (scf_error()) { + case SCF_ERROR_PERMISSION_DENIED: + ret = SA_NO_PERMISSION; + break; + } + } + /* + * cleanup if there were any errors that didn't leave these + * values where they would be cleaned up later. + */ + if (value != NULL) + scf_value_destroy(value); + if (entry != NULL) + scf_entry_destroy(entry); + return (ret); +} + +/* + * sa_commit_share(handle, group, share) + * + * commit this share to the repository. + * properties are added if they exist but can be added later. + * Need to add to dfstab and sharetab, if appropriate. + */ +int +sa_commit_share(scfutilhandle_t *handle, sa_group_t group, sa_share_t share) +{ + int ret = SA_OK; + char *groupname; + char *name; + char *resource; + char *description; + char *sharename; + ssize_t proplen; + char *propstring; + + /* + * don't commit in the zfs group. We do commit legacy + * (default) and all other groups/shares. ZFS is handled + * through the ZFS configuration rather than SMF. + */ + + groupname = sa_get_group_attr(group, "name"); + if (groupname != NULL) { + if (strcmp(groupname, "zfs") == 0) { + /* + * adding to the ZFS group will result in the sharenfs + * property being set but we don't want to do anything + * SMF related at this point. + */ + sa_free_attr_string(groupname); + return (ret); + } + } + + proplen = get_scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH); + propstring = malloc(proplen); + if (propstring == NULL) + ret = SA_NO_MEMORY; + + if (groupname != NULL && ret == SA_OK) { + ret = sa_get_instance(handle, groupname); + sa_free_attr_string(groupname); + groupname = NULL; + sharename = sa_get_share_attr(share, "id"); + if (sharename == NULL) { + /* slipped by */ + char shname[SA_SHARE_UUID_BUFLEN]; + generate_unique_sharename(shname); + xmlSetProp((xmlNodePtr)share, (xmlChar *)"id", + (xmlChar *)shname); + sharename = strdup(shname); + } + if (sharename != NULL) { + /* + * have a share name allocated so create a pgroup + * for it. It may already exist, but that is OK. + */ + ret = sa_create_pgroup(handle, sharename); + if (ret == SA_OK) { + /* + * now start the transaction for the + * properties that define this share. They may + * exist so attempt to update before create. + */ + ret = sa_start_transaction(handle, sharename); + } + if (ret == SA_OK) { + name = sa_get_share_attr(share, "path"); + if (name != NULL) { + /* there needs to be a path for a share to exist */ + ret = sa_set_property(handle, "path", name); + sa_free_attr_string(name); + } else { + ret = SA_NO_MEMORY; + } + } + if (ret == SA_OK) { + resource = sa_get_share_attr(share, "resource"); + if (resource != NULL) { + ret = sa_set_property(handle, "resource", resource); + sa_free_attr_string(resource); + } + } + if (ret == SA_OK) { + description = sa_get_share_description(share); + if (description != NULL) { + ret = sa_set_property(handle, "description", + description); + sa_free_share_description(description); + } + } + /* make sure we cleanup the transaction */ + if (ret == SA_OK) { + ret = sa_end_transaction(handle); + } else { + sa_abort_transaction(handle); + } + free(sharename); + } + } + if (ret == SA_SYSTEM_ERR) { + int err = scf_error(); + if (err == SCF_ERROR_PERMISSION_DENIED) + ret = SA_NO_PERMISSION; + } + if (propstring != NULL) + free(propstring); + if (groupname != NULL) + sa_free_attr_string(groupname); + + return (ret); +} + +/* + * sa_delete_share(handle, group, share) + * + * remove the specified share from the group (and service instance). + */ + +int +sa_delete_share(scfutilhandle_t *handle, sa_group_t group, sa_share_t share) +{ + int ret = SA_OK; + char *groupname = NULL; + char *shareid = NULL; + sa_optionset_t opt; + sa_security_t sec; + ssize_t proplen; + char *propstring; + + proplen = get_scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH); + propstring = malloc(proplen); + if (propstring == NULL) + ret = SA_NO_MEMORY; + + if (ret == SA_OK) { + groupname = sa_get_group_attr(group, "name"); + shareid = sa_get_share_attr(share, "id"); + if (groupname != NULL && shareid != NULL) { + ret = sa_get_instance(handle, groupname); + if (ret == SA_OK) { + /* if a share has properties, remove them */ + ret = sa_delete_pgroup(handle, shareid); + for (opt = sa_get_optionset(share, NULL); opt != NULL; + opt = sa_get_next_optionset(opt)) { + char *proto; + proto = sa_get_optionset_attr(opt, "type"); + if (proto != NULL) { + (void) snprintf(propstring, proplen, "%s_%s", + shareid, proto); + ret = sa_delete_pgroup(handle, propstring); + sa_free_attr_string(proto); + } else { + ret = SA_NO_MEMORY; + } + } + /* + * if a share has security/negotiable + * properties, remove them. + */ + for (sec = sa_get_security(share, NULL, NULL); sec != NULL; + sec = sa_get_next_security(sec)) { + char *proto; + char *sectype; + proto = sa_get_security_attr(sec, "type"); + sectype = sa_get_security_attr(sec, "sectype"); + if (proto != NULL && sectype != NULL) { + (void) snprintf(propstring, proplen, "%s_%s_%s", + shareid, + proto, sectype); + ret = sa_delete_pgroup(handle, propstring); + } else { + ret = SA_NO_MEMORY; + } + if (proto != NULL) + sa_free_attr_string(proto); + if (sectype != NULL) + sa_free_attr_string(sectype); + } + } + } else { + ret = SA_CONFIG_ERR; + } + } + if (groupname != NULL) + sa_free_attr_string(groupname); + if (shareid != NULL) + sa_free_attr_string(shareid); + if (propstring != NULL) + free(propstring); + + return (ret); +} diff --git a/usr/src/lib/libshare/common/scfutil.h b/usr/src/lib/libshare/common/scfutil.h new file mode 100644 index 0000000000..da21d32785 --- /dev/null +++ b/usr/src/lib/libshare/common/scfutil.h @@ -0,0 +1,81 @@ +/* + * 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 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * basic API declarations for share management + */ + +#ifndef _SCFUTIL_H +#define _SCFUTIL_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#ifdef __cplusplus +extern "C" { +#endif +#include <libxml/tree.h> + +typedef struct scfutilhandle { + scf_handle_t *handle; + int scf_state; + scf_service_t *service; + scf_scope_t *scope; + scf_transaction_t *trans; + scf_transaction_entry_t *entry; + scf_propertygroup_t *pg; + scf_instance_t *instance; +} scfutilhandle_t; + +#define SCH_STATE_UNINIT 0 +#define SCH_STATE_INITIALIZING 1 +#define SCH_STATE_INIT 2 + +extern void sa_scf_fini(scfutilhandle_t *); +extern scfutilhandle_t *sa_scf_init(); +extern int sa_get_config(scfutilhandle_t *, xmlNodePtr *, xmlDocPtr *); +extern int sa_get_instance(scfutilhandle_t *, char *); +extern int sa_create_instance(scfutilhandle_t *, char *); + +/* + * Shares are held in a property group with name of the form + * S-<GUID>. The total length of the name is 38 characters. + */ +#define SA_SHARE_PG_PREFIX "S-" +#define SA_SHARE_PG_PREFIXLEN 2 +#define SA_SHARE_PG_LEN 38 +#define SA_SHARE_UUID_BUFLEN 64 + +/* + * service instance related defines + */ +#define SA_GROUP_SVC_NAME "network/shares/group" +#define SA_GROUP_INST_LEN 256 + +#ifdef __cplusplus +} +#endif + +#endif /* _SCFUTIL_H */ diff --git a/usr/src/lib/libshare/i386/Makefile b/usr/src/lib/libshare/i386/Makefile new file mode 100644 index 0000000000..729341c543 --- /dev/null +++ b/usr/src/lib/libshare/i386/Makefile @@ -0,0 +1,30 @@ +# +# 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 +# +# +# ident "%Z%%M% %I% %E% SMI" +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/lib/libshare/sparc/Makefile b/usr/src/lib/libshare/sparc/Makefile new file mode 100644 index 0000000000..729341c543 --- /dev/null +++ b/usr/src/lib/libshare/sparc/Makefile @@ -0,0 +1,30 @@ +# +# 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 +# +# +# ident "%Z%%M% %I% %E% SMI" +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) |