diff options
Diffstat (limited to 'usr/src')
38 files changed, 17990 insertions, 308 deletions
diff --git a/usr/src/Makefile.master b/usr/src/Makefile.master index 44a608d038..7b09f89cc7 100644 --- a/usr/src/Makefile.master +++ b/usr/src/Makefile.master @@ -228,6 +228,8 @@ DOT_C_CHECK= \ MANIFEST_CHECK= \ @$(ECHO) "checking $<"; \ SVCCFG_DTD=$(SRC)/cmd/svc/dtd/service_bundle.dtd.1 \ + SVCCFG_REPOSITORY=$(SRC)/cmd/svc/seed/global.db \ + SVCCFG_CONFIGD_PATH=$(SRC)/cmd/svc/configd/svc.configd-native \ $(SRC)/cmd/svc/svccfg/svccfg-native validate $< INS.file= $(RM) $@; $(INS) -s -m $(FILEMODE) -f $(@D) $< diff --git a/usr/src/cmd/Makefile b/usr/src/cmd/Makefile index 1f998fde12..323f725d20 100644 --- a/usr/src/cmd/Makefile +++ b/usr/src/cmd/Makefile @@ -914,16 +914,26 @@ _dc := TARGET = _dc SUBDIRS = $(COMMON_SUBDIRS) $($(MACH)_SUBDIRS) .PARALLEL: $(BWOSDIRS) $(SUBDIRS) $(MSGSUBDIRS) $(BSMSUBDIRS) \ - $(MANIFEST_SUBDIRS) $(DTEST_SUBDIRS) + $(DTEST_SUBDIRS) all install clean clobber lint: $(FIRST_SUBDIRS) .WAIT $(SUBDIRS) $(BSMSUBDIRS) +# Manifests cannot be checked in parallel, because we are using the global +# repository that is in $(SRC)/cmd/svc/seed/global.db. This is a +# repository that is built from the manifests in this workspace, whereas +# the build machine's repository may be out of sync with these manifests. +# Because we are using a private repository, svccfg-native must start up a +# private copy of configd-native. We cannot have multiple copies of +# configd-native trying to access global.db simultaneously. + +.NO_PARALLEL: $(MANIFEST_TOPDIRS) $(MANIFEST_SUBDIRS) + check: svccfg_check .WAIT $(MANIFEST_TOPDIRS) $(MANIFEST_SUBDIRS) \ $(DTEST_SUBDIRS) svccfg_check: @$(ECHO) "building requirements for svccfg check ..."; \ - (cd $(SRC)/cmd/svc/svccfg && pwd && $(MAKE) $(MFLAGS) native) + (cd $(SRC)/cmd/svc/seed && pwd && $(MAKE) $(MFLAGS) global.db) $(DTEST_SUBDIRS): FRC @cd $@; pwd; $(MAKE) $(TARGET) diff --git a/usr/src/cmd/Makefile.cmd b/usr/src/cmd/Makefile.cmd index 610a3ad6e9..eb2ef79e2a 100644 --- a/usr/src/cmd/Makefile.cmd +++ b/usr/src/cmd/Makefile.cmd @@ -270,6 +270,16 @@ ROOTMANIFESTDIR= $(ROOTVARSVCMANIFEST)/__nonexistent_directory__ ROOTMANIFEST= $(MANIFEST:%=$(ROOTMANIFESTDIR)/%) CHKMANIFEST= $(MANIFEST:%.xml=%.xmlchk) +# Manifests cannot be checked in parallel, because we are using the global +# repository that is in $(SRC)/cmd/svc/seed/global.db. This is a +# repository that is built from the manifests in this workspace, whereas +# the build machine's repository may be out of sync with these manifests. +# Because we are using a private repository, svccfg-native must start up a +# private copy of configd-native. We cannot have multiple copies of +# configd-native trying to access global.db simultaneously. + +.NO_PARALLEL: $(CHKMANIFEST) + # # For installing "starter scripts" of services # diff --git a/usr/src/cmd/cmd-inet/usr.lib/inetd/inetd.xml b/usr/src/cmd/cmd-inet/usr.lib/inetd/inetd.xml index 8cae3cfdb8..f5bb357bec 100644 --- a/usr/src/cmd/cmd-inet/usr.lib/inetd/inetd.xml +++ b/usr/src/cmd/cmd-inet/usr.lib/inetd/inetd.xml @@ -23,8 +23,6 @@ CDDL HEADER END - ident "%Z%%M% %I% %E% SMI" - NOTE: This service manifest is not editable; its contents will be overwritten by package or patch operations, including operating system upgrade. Make customizations in a different @@ -37,7 +35,7 @@ <service name='network/inetd' - type='service' + type='restarter' version='1'> <create_default_instance enabled='false' /> @@ -57,7 +55,7 @@ grouping='require_all' restart_on='error' type='service'> - <service_fmri value='svc:/system/filesystem/local'/> + <service_fmri value='svc:/system/filesystem/local' /> </dependency> <dependency @@ -180,7 +178,975 @@ services. <documentation> <manpage title='inetd' section='1M' manpath='/usr/share/man' /> + <manpage title='inetadm' section='1M' + manpath='/usr/share/man' /> </documentation> + + <pg_pattern name='config' type='application' target='this' + required='false'> + <prop_pattern name='debug' type='boolean' + required='false'> + <description> + <loctext xml:lang='C'> +Enable syslog-style debugging messages for inetd. + </loctext> + </description> + <cardinality min='1' max='1' /> + </prop_pattern> + </pg_pattern> + + <pg_pattern name='defaults' type='application' + target='this' required='true'> + <description> + <loctext xml:lang='C'> +Defaults for properties optional for inetd services. Defaults are inherited from inetd if the property is not specified by the service. + </loctext> + </description> + <prop_pattern name='bind_addr' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The address of the network interface to which the service should be bound. An empty string value causes the service to accept connections on any network interface. + </loctext> + </description> + <cardinality min='1' max='1' /> + </prop_pattern> + <prop_pattern name='bind_fail_interval' type='integer' + required='true'> + <description> + <loctext xml:lang='C'> +Time in seconds between a failed bind attempt and a retry. + </loctext> + </description> + <cardinality min='1' max='1' /> + <values> + <value name='0'> + <description> + <loctext xml:lang='C'> +No retries are attempted. + </loctext> + </description> + </value> + <value name='-1'> + <description> + <loctext xml:lang='C'> +No retries are attempted. + </loctext> + </description> + </value> + </values> + </prop_pattern> + <prop_pattern name='bind_fail_max' type='integer' + required='true'> + <description> + <loctext xml:lang='C'> +The maxiumum nubmer of times inetd retries binding to a service's port before giving up. + </loctext> + </description> + <cardinality min='1' max='1' /> + <values> + <value name='-1'> + <description> + <loctext xml:lang='C'> +No retry limiting is imposed. + </loctext> + </description> + </value> + </values> + </prop_pattern> + <prop_pattern name='con_rate_offline' type='integer' + required='true'> + <description> + <loctext xml:lang='C'> +The time in seconds a service will remain offline if it exceeds its configured maximum connection rate, as defined by max_con_rate. + </loctext> + </description> + <cardinality min='1' max='1' /> + <values> + <value name='0'> + <description> + <loctext xml:lang='C'> +Connection rate limiting is disabled. + </loctext> + </description> + </value> + <value name='-1'> + <description> + <loctext xml:lang='C'> +Connection rate limiting is disabled. + </loctext> + </description> + </value> + </values> + </prop_pattern> + <prop_pattern name='connection_backlog' type='integer' + required='true'> + <description> + <loctext xml:lang='C'> +The limit on the number of incoming client requests that can be queued at the listening endpoints for servers. + </loctext> + </description> + <cardinality min='1' max='1' /> + </prop_pattern> + <prop_pattern name='failrate_cnt' type='integer' + required='true'> + <description> + <loctext xml:lang='C'> +Number of instances of a wait-type service which, in combination with the failrate_interval property, determine when to transition the service into maintenance. + </loctext> + </description> + <cardinality min='1' max='1' /> + <values> + <value name='0'> + <description> + <loctext xml:lang='C'> +Failure rate limiting is disabled. + </loctext> + </description> + </value> + <value name='-1'> + <description> + <loctext xml:lang='C'> +Failure rate limiting is disabled. + </loctext> + </description> + </value> + </values> + </prop_pattern> + <prop_pattern name='failrate_interval' type='integer' + required='true'> + <description> + <loctext xml:lang='C'> +Time in seconds for interval to check for failrate_cnt failures of wait-type services which determine when to transition the service into maintenance. + </loctext> + </description> + <cardinality min='1' max='1' /> + <values> + <value name='0'> + <description> + <loctext xml:lang='C'> +Failure rate limiting is disabled. + </loctext> + </description> + </value> + <value name='-1'> + <description> + <loctext xml:lang='C'> +Failure rate limiting is disabled. + </loctext> + </description> + </value> + </values> + </prop_pattern> + <prop_pattern name='inherit_env' type='boolean' + required='true'> + <description> + <loctext xml:lang='C'> +If true, pass inetd's environment on to the service's start method. If false, the environment is cleared with the exception of SMF_*, and the environment variables from the method context. + </loctext> + </description> + <cardinality min='1' max='1' /> + </prop_pattern> + <prop_pattern name='max_con_rate' type='integer' + required='true'> + <description> + <loctext xml:lang='C'> +The maximum allowed connection rate, in connections per second for a nowait-type service. + </loctext> + </description> + <units> + <loctext xml:lang='C'> +connections per second + </loctext> + </units> + <cardinality min='1' max='1' /> + <values> + <value name='0'> + <description> + <loctext xml:lang='C'> +Connection rate limiting is disabled. + </loctext> + </description> + </value> + <value name='-1'> + <description> + <loctext xml:lang='C'> +Connection rate limiting is disabled. + </loctext> + </description> + </value> + </values> + </prop_pattern> + <prop_pattern name='max_copies' type='integer' + required='true'> + <description> + <loctext xml:lang='C'> +Maximum number of copies of a nowait service that can run concurrently. + </loctext> + </description> + <cardinality min='1' max='1' /> + <values> + <value name='0'> + <description> + <loctext xml:lang='C'> +Copies limiting is disabled. + </loctext> + </description> + </value> + <value name='-1'> + <description> + <loctext xml:lang='C'> +Copies limiting is disabled. + </loctext> + </description> + </value> + </values> + </prop_pattern> + <prop_pattern name='tcp_trace' type='boolean' + required='true'> + <description> + <loctext xml:lang='C'> +If true, enables logging of the service name, client IP address, and TCP port number for nowait services using syslog. + </loctext> + </description> + <cardinality min='1' max='1' /> + </prop_pattern> + <prop_pattern name='tcp_wrappers' type='boolean' + required='true'> + <description> + <loctext xml:lang='C'> +If true, enables TCP wrappers for nowait stream services. + </loctext> + </description> + <cardinality min='1' max='1' /> + </prop_pattern> + </pg_pattern> + + <pg_pattern name='inetd' type='framework' + target='delegate' required='true'> + <description> + <loctext xml:lang='C'> +Basic configuration of an inetd-managed service. Defaults are inherited from inetd if the property is not specified by the service. + </loctext> + </description> + <prop_pattern name='bind_addr' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The address of the network interface to which the service should be bound. An empty string value causes the service to accept connections on any network interface. + </loctext> + </description> + <cardinality min='1' max='1' /> + </prop_pattern> + <prop_pattern name='bind_fail_interval' type='integer' + required='false'> + <description> + <loctext xml:lang='C'> +Time in seconds between a failed bind attempt and a retry. + </loctext> + </description> + <cardinality min='1' max='1' /> + <values> + <value name='0'> + <description> + <loctext xml:lang='C'> +No retries are attempted. + </loctext> + </description> + </value> + <value name='-1'> + <description> + <loctext xml:lang='C'> +No retries are attempted. + </loctext> + </description> + </value> + </values> + </prop_pattern> + <prop_pattern name='bind_fail_max' type='integer' + required='false'> + <description> + <loctext xml:lang='C'> +The maxiumum nubmer of times inetd retries binding to a service's port before giving up. + </loctext> + </description> + <cardinality min='1' max='1' /> + <values> + <value name='-1'> + <description> + <loctext xml:lang='C'> +No retry limiting is imposed. + </loctext> + </description> + </value> + </values> + </prop_pattern> + <prop_pattern name='con_rate_offline' type='integer' + required='false'> + <description> + <loctext xml:lang='C'> +The time in seconds a service will remain offline if it exceeds its configured maximum connection rate, as defined by max_con_rate. + </loctext> + </description> + <cardinality min='1' max='1' /> + <values> + <value name='0'> + <description> + <loctext xml:lang='C'> +Connection rate limiting is disabled. + </loctext> + </description> + </value> + <value name='-1'> + <description> + <loctext xml:lang='C'> +Connection rate limiting is disabled. + </loctext> + </description> + </value> + </values> + </prop_pattern> + <prop_pattern name='connection_backlog' type='integer' + required='false'> + <description> + <loctext xml:lang='C'> +The limit on the number of incoming client requests that can be queued at the listening endpoints for servers. + </loctext> + </description> + <cardinality min='1' max='1' /> + </prop_pattern> + <prop_pattern name='endpoint_type' type='astring' + required='true'> + <description> + <loctext xml:lang='C'> +The type of socket used by the service. + </loctext> + </description> + <cardinality min='1' max='1' /> + <constraints> + <value name='tli'> + <description> + <loctext xml:lang='C'> +TLI endpoint + </loctext> + </description> + </value> + <value name='stream'> + <description> + <loctext xml:lang='C'> +stream socket + </loctext> + </description> + </value> + <value name='dgram'> + <description> + <loctext xml:lang='C'> +datagram socket + </loctext> + </description> + </value> + <value name='raw'> + <description> + <loctext xml:lang='C'> +raw socket + </loctext> + </description> + </value> + <value name='seqpacket'> + <description> + <loctext xml:lang='C'> +sequenced packet socket + </loctext> + </description> + </value> + </constraints> + <choices> + <value name='dgram' /> + <value name='stream' /> + <value name='tli' /> + </choices> + </prop_pattern> + <prop_pattern name='failrate_cnt' type='integer' + required='false'> + <description> + <loctext xml:lang='C'> +Number of instances of a wait-type service which, in combination with the failrate_interval property, determine when to transition the service into maintenance. + </loctext> + </description> + <cardinality min='1' max='1' /> + <values> + <value name='0'> + <description> + <loctext xml:lang='C'> +Failure rate limiting is disabled. + </loctext> + </description> + </value> + <value name='-1'> + <description> + <loctext xml:lang='C'> +Failure rate limiting is disabled. + </loctext> + </description> + </value> + </values> + </prop_pattern> + <prop_pattern name='failrate_interval' type='integer' + required='false'> + <description> + <loctext xml:lang='C'> +Time in seconds for interval to check for failrate_cnt failures of wait-type services which determine when to transition the service into maintenance. + </loctext> + </description> + <cardinality min='1' max='1' /> + <values> + <value name='0'> + <description> + <loctext xml:lang='C'> +Failure rate limiting is disabled. + </loctext> + </description> + </value> + <value name='-1'> + <description> + <loctext xml:lang='C'> +Failure rate limiting is disabled. + </loctext> + </description> + </value> + </values> + </prop_pattern> + <prop_pattern name='inherit_env' type='boolean' + required='false'> + <description> + <loctext xml:lang='C'> +If true, pass inetd's environment on to the service's start method. If false, the environment is cleared with the exception of SMF_*, and the environment variables from the method context. + </loctext> + </description> + <cardinality min='1' max='1' /> + </prop_pattern> + <prop_pattern name='isrpc' type='boolean' + required='false'> + <description> + <loctext xml:lang='C'> +If true, this is an RPC service. + </loctext> + </description> + <cardinality min='1' max='1' /> + </prop_pattern> + <prop_pattern name='max_con_rate' type='integer' + required='false'> + <description> + <loctext xml:lang='C'> +The maximum allowed connection rate, in connections per second for a nowait-type service. + </loctext> + </description> + <units> + <loctext xml:lang='C'> +connections per second + </loctext> + </units> + <cardinality min='1' max='1' /> + <values> + <value name='0'> + <description> + <loctext xml:lang='C'> +Connection rate limiting is disabled. + </loctext> + </description> + </value> + <value name='-1'> + <description> + <loctext xml:lang='C'> +Connection rate limiting is disabled. + </loctext> + </description> + </value> + </values> + </prop_pattern> + <prop_pattern name='max_copies' type='integer' + required='false'> + <description> + <loctext xml:lang='C'> +Maximum number of copies of a nowait service that can run concurrently. + </loctext> + </description> + <cardinality min='1' max='1' /> + <values> + <value name='0'> + <description> + <loctext xml:lang='C'> +Copies limiting is disabled. + </loctext> + </description> + </value> + <value name='-1'> + <description> + <loctext xml:lang='C'> +Copies limiting is disabled. + </loctext> + </description> + </value> + </values> + </prop_pattern> + <prop_pattern name='name' type='astring' + required='true'> + <description> + <loctext xml:lang='C'> +Can be set to a service name understood by getservbyname(3SOCKET), or if isrpc is true, a service name understood by getrpcbyname(3NSL) or a valid RPC program number. + </loctext> + </description> + <cardinality min='1' max='1' /> + </prop_pattern> + <prop_pattern name='proto' type='astring' + required='true'> + <description> + <loctext xml:lang='C'> +A list of protocols supported by the service. + </loctext> + </description> + <choices> + <value name='tcp' /> + <value name='tcp6' /> + <value name='tcp6only' /> + <value name='udp' /> + <value name='udp6' /> + <value name='udp6only' /> + </choices> + </prop_pattern> + <prop_pattern name='rpc_low_version' type='integer' + required='false'> + <description> + <loctext xml:lang='C'> +Lowest supported RPC version. Required when isrpc is set to true. + </loctext> + </description> + <cardinality min='1' max='1' /> + </prop_pattern> + <prop_pattern name='rpc_high_version' type='integer' + required='false'> + <description> + <loctext xml:lang='C'> +Highest supported RPC version. Required when isrpc is set to true. + </loctext> + </description> + <cardinality min='1' max='1' /> + </prop_pattern> + <prop_pattern name='tcp_trace' type='boolean' + required='false'> + <description> + <loctext xml:lang='C'> +If true, enables logging of the service name, client IP address, and TCP port number for nowait services using syslog. + </loctext> + </description> + <cardinality min='1' max='1' /> + </prop_pattern> + <prop_pattern name='tcp_wrappers' type='boolean' + required='false'> + <description> + <loctext xml:lang='C'> +If true, enables TCP wrappers for nowait stream services. + </loctext> + </description> + <cardinality min='1' max='1' /> + </prop_pattern> + <prop_pattern name='wait' type='boolean' + required='false'> + <description> + <loctext xml:lang='C'> +If true, this is a wait-type service. For wait-type services, the inetd_start method will take over listening duties on the service's bound endpoint and inetd will wait for it to exit before resuming listening. + </loctext> + </description> + <cardinality min='1' max='1' /> + </prop_pattern> + </pg_pattern> + + <pg_pattern type='method' target='delegate' required='false'> + <description> + <loctext xml:lang='C'> +A method defines how inetd interacts with its services. inetd_start is executed to handle a connection. inetd_offline is executed when the service is taken offline. inetd_online is executed when the service is taken from offline to online. inetd_disable is executed when the service is disabled. inetd_refresh is executed when the service is refreshed. + </loctext> + </description> + + <prop_pattern name='exec' type='astring' + required='true'> + <common_name> + <loctext xml:lang='C'> +method executable + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +The method executable may be a script, program, or keyword. + </loctext> + </description> + <cardinality min='1' max='1' /> + <values> + <value name=':true'> + <description> + <loctext xml:lang='C'> +Always returns SMF_EXIT_OK. This token should be used for methods that are required by the restarter but which are unnecessary for the particular service implementation. + </loctext> + </description> + </value> + <value name=':kill [-signal]'> + <description> + <loctext xml:lang='C'> +Sends the specified signal, which is SIGTERM by default, to all processes in the primary instance contract. Always returns SMF_EXIT_OK. This token should be used to replace common pkill invocations. + + </loctext> + </description> + </value> + <value name=':kill_process [-signal]'> + <description> + <loctext xml:lang='C'> +For wait-type services, send the specified signal, which is SIGTERM by default, to the parent process of the wait-type serviće´s start method. + </loctext> + </description> + </value> + </values> + <choices> + <include_values type='values' /> + </choices> + </prop_pattern> + + <prop_pattern name='type' type='astring' + required='true'> + <description> + <loctext xml:lang='C'> +A method may only be of type method. + </loctext> + </description> + <cardinality min='1' max='1' /> + <constraints> + <value name='method' /> + </constraints> + </prop_pattern> + + <prop_pattern name='timeout_seconds' type='count' + required='true'> + <description> + <loctext xml:lang='C'> +Number of seconds before the method is considered unresponsive. After the method timeout expires, the method will be killed. + </loctext> + </description> + <cardinality min='1' max='1' /> + <values> + <value name='0'> + <common_name> + <loctext xml:lang='C'> +infinite + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +This method will never time out. + </loctext> + </description> + </value> + <value name='-1'> + <common_name> + <loctext xml:lang='C'> +infinite (legacy) + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +This method will never time out. 0 is the preferred value. + </loctext> + </description> + </value> + </values> + </prop_pattern> + + <!-- method_context direct properties --> + <prop_pattern name='working_directory' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The working directory to launch the method from. ":default" can be used as a token to indicate the home directory of the user specified by the credential or profile. + + </loctext> + </description> + <cardinality min='1' max='1' /> + </prop_pattern> + <prop_pattern name='project' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The project ID in numeric or text form. :default can be used as a token to indicate a project identified by getdefaultproj(3PROJECT) for the user whose uid is used to launch the method. + </loctext> + </description> + <cardinality min='1' max='1' /> + </prop_pattern> + <prop_pattern name='resource_pool' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method context resource pool + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +The resource pool name on which to launch the method. :default can be used +as a token to indicate the pool specified in the project(4) entry given in +the project attribute. + </loctext> + </description> + <cardinality min='1' max='1' /> + </prop_pattern> + + <!-- method_credential properties --> + <prop_pattern name='user' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method credential user + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +The user ID in numeric or text form. + </loctext> + </description> + <cardinality min='1' max='1' /> + </prop_pattern> + <prop_pattern name='group' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method credential group + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +The group ID in numeric or text form. + </loctext> + </description> + <cardinality min='1' max='1' /> + </prop_pattern> + <prop_pattern name='supp_groups' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method credential supplemental groups + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +An optional string that specifies the supplemental group memberships by ID, +in numeric or text form. + </loctext> + </description> + <cardinality min='1' max='1' /> + <internal_separators>,</internal_separators> + </prop_pattern> + <prop_pattern name='privileges' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method credential privileges + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +An optional string specifying the privilege set as defined in privileges(5). + </loctext> + </description> + <cardinality min='1' max='1' /> + <internal_separators>,</internal_separators> + </prop_pattern> + <prop_pattern name='limit_privileges' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method credential limit privilege set + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +An optional string specifying the limit privilege set as defined in +privileges(5). + </loctext> + </description> + <cardinality min='1' max='1' /> + <internal_separators>,</internal_separators> + </prop_pattern> + + <!-- method_profile properties --> + <prop_pattern name='use_profile' type='boolean' + required='false'> + <description> + <loctext xml:lang='C'> +A boolean that specifies whether the profile should be used instead of the +user, group, privileges, and limit_privileges properties. + </loctext> + </description> + <cardinality min='1' max='1' /> + </prop_pattern> + <prop_pattern name='profile' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method profile RBAC profile specification + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +The name of an RBAC (role-based access control) profile which, along with the +method executable, identifies an entry in exec_attr(4). + </loctext> + </description> + <cardinality min='1' max='1' /> + </prop_pattern> + + <!-- method_environment properties --> + <prop_pattern name='environment' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method environment variables + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +Environment variables to insert into the environment of the method, in the +form of a number of NAME=value strings. + </loctext> + </description> + </prop_pattern> + </pg_pattern> + + <pg_pattern name='restarter' type='framework' + target='delegate' required='false'> + <description> + <loctext xml:lang='C'> +Communicate restarter-set status of the service. + </loctext> + </description> + + <prop_pattern name='auxiliary_state' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +Additional information about why a service is in the current state. Unused by inetd. + </loctext> + </description> + <visibility value='hidden' /> + <cardinality min='1' max='1' /> + </prop_pattern> + <prop_pattern name='state_timestamp' type='time' + required='false'> + <description> + <loctext xml:lang='C'> +Time the current state was reached. + </loctext> + </description> + <visibility value='readonly' /> + <cardinality min='1' max='1' /> + </prop_pattern> + <prop_pattern name='state' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The current state of this service instance. + </loctext> + </description> + <visibility value='readonly' /> + <cardinality min='1' max='1' /> + <constraints> + <value name='online'> + <description> + <loctext xml:lang='C'> +The instance is handling new network requests and might have existing connections alive. + </loctext> + </description> + </value> + <value name='offline'> + <description> + <loctext xml:lang='C'> +Connections might be active, but no new requests are being handled. An instance may be offline because its dependencies are unsatisfied, the service has exceeded its configured connection rate limit, the service as reached its allowed number of active connections, or inetd failed to listen on behalf of the service on all its protocols. + </loctext> + </description> + </value> + <value name='uninitialized'> + <description> + <loctext xml:lang='C'> +inetd has yet to process this instance, or the inetd service has not yet been started by svc.startd. + </loctext> + </description> + </value> + <value name='degraded'> + <description> + <loctext xml:lang='C'> +The instance was able to listen and process requests for some, but not all, of the protocoals specified for the instance. Existing network connections might be active. + </loctext> + </description> + </value> + <value name='disabled'> + <description> + <loctext xml:lang='C'> +The instance is disabled, is not accepting new connections, and has none active. + </loctext> + </description> + </value> + <value name='maintenance'> + <description> + <loctext xml:lang='C'> +The instance is either malfunctioning and needs administrator attention, or an administrator has requested the instance enter this state. + </loctext> + </description> + </value> + </constraints> + </prop_pattern> + <prop_pattern name='next_state' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The next expected state of this instance. + </loctext> + </description> + <visibility value='readonly' /> + <cardinality min='1' max='1' /> + <constraints> + <value name='online'> + <description> + <loctext xml:lang='C'> +The service is being started or refreshed, and will soon be online and running. This transition may fail and the instance may end up in offline or maintenance instead. + </loctext> + </description> + </value> + <value name='offline'> + <description> + <loctext xml:lang='C'> +The instance has temporarily suspended accepting new network requests due to dependencies or reaching a connection or failure threshold. Most instances will leave this state once their dependencies are satisfied or the threshold is no longer exceeded. + </loctext> + </description> + </value> + <value name='degraded'> + <description> + <loctext xml:lang='C'> +The instance was able to listen and process requests for some, but not all, of the protocoals specified for the instance. Existing network connections might be active. + </loctext> + </description> + </value> + <value name='disabled'> + <description> + <loctext xml:lang='C'> +The instance will be disabled, will not be accepting new connections, and will have none active. + </loctext> + </description> + </value> + <value name='maintenance'> + <description> + <loctext xml:lang='C'> +The instance will be in maintenance, and administrative action will be required to restore the instance to offline and subsequent states. + </loctext> + </description> + </value> + </constraints> + </prop_pattern> + </pg_pattern> </template> </service> diff --git a/usr/src/cmd/consadm/consadm.xml b/usr/src/cmd/consadm/consadm.xml index 4d70fc936e..f11f0d91e5 100644 --- a/usr/src/cmd/consadm/consadm.xml +++ b/usr/src/cmd/consadm/consadm.xml @@ -1,15 +1,14 @@ <?xml version="1.0"?> <!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1"> <!-- - Copyright 2005 Sun Microsystems, Inc. All rights reserved. + Copyright 2008 Sun Microsystems, Inc. All rights reserved. Use is subject to license terms. 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. + 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. @@ -24,8 +23,6 @@ CDDL HEADER END - ident "%Z%%M% %I% %E% SMI" - NOTE: This service manifest is not editable; its contents will be overwritten by package or patch operations, including operating system upgrade. Make customizations in a different @@ -59,6 +56,12 @@ exec='/lib/svc/method/svc-consadm' timeout_seconds='60' /> + <exec_method + type='method' + name='stop' + exec=':true' + timeout_seconds='60' /> + <property_group name='startd' type='framework'> <propval name='duration' type='astring' value='transient' /> </property_group> diff --git a/usr/src/cmd/kbd/keymap.xml b/usr/src/cmd/kbd/keymap.xml index 04b10099a2..1224b2cf9f 100644 --- a/usr/src/cmd/kbd/keymap.xml +++ b/usr/src/cmd/kbd/keymap.xml @@ -1,15 +1,14 @@ <?xml version="1.0"?> <!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1"> <!-- - Copyright 2005 Sun Microsystems, Inc. All rights reserved. + Copyright 2008 Sun Microsystems, Inc. All rights reserved. Use is subject to license terms. 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. + 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. @@ -24,8 +23,6 @@ CDDL HEADER END - ident "%Z%%M% %I% %E% SMI" - NOTE: This service manifest is not editable; its contents will be overwritten by package or patch operations, including operating system upgrade. Make customizations in a different @@ -66,6 +63,12 @@ exec='/lib/svc/method/keymap' timeout_seconds='0' /> + <exec_method + type='method' + name='stop' + exec=':true' + timeout_seconds='60' /> + <property_group name='startd' type='framework'> <propval name='duration' type='astring' value='transient' /> diff --git a/usr/src/cmd/svc/dtd/service_bundle.dtd.1 b/usr/src/cmd/svc/dtd/service_bundle.dtd.1 index 4abe29870d..47ca8eed9f 100644 --- a/usr/src/cmd/svc/dtd/service_bundle.dtd.1 +++ b/usr/src/cmd/svc/dtd/service_bundle.dtd.1 @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - Copyright 2006 Sun Microsystems, Inc. All rights reserved. + Copyright 2008 Sun Microsystems, Inc. All rights reserved. Use is subject to license terms. CDDL HEADER START @@ -21,8 +21,6 @@ information: Portions Copyright [yyyy] [name of copyright owner] CDDL HEADER END - - ident "%Z%%M% %I% %E% SMI" --> <!-- @@ -303,9 +301,9 @@ grouping The relationship between the various FMRIs grouped here; "require_all" of the FMRIs to be online, "require_any" of the FMRIs to be online, or "exclude_all" of the FMRIs - from being online for the dependency to be satisfied. - "optional_all" dependencies are satisfied when all - of the FMRIs are either online or unable to come + from being online or in maintenance for the dependency to + be satisfied. "optional_all" dependencies are satisfied + when all of the FMRIs are either online or unable to come online (because they are disabled, misconfigured, or one of their dependencies is unable to come online). @@ -646,6 +644,225 @@ <!ATTLIST common_name> <!-- + units + + The units a numerical property is expressed in. +--> + +<!ELEMENT units + ( loctext+ ) > + +<!ATTLIST units> + +<!-- + visibility + + Expresses how a property is typically accessed. This isn't + intended as access control, but as an indicator as to how a + property is used. + + Its attributes are: + + value 'hidden', 'readonly', or 'readwrite' indicating that + the property should be hidden from the user, shown but + read-only, or modifiable. +--> + +<!ELEMENT visibility EMPTY> + +<!ATTLIST visibility + value ( hidden | readonly | readwrite ) #REQUIRED > + +<!-- + value + + Describes a legal value for a property value, and optionally contains a + human-readable name and description for the specified property + value. + + Its attributes are: + + name A string representation of the value. +--> + +<!ELEMENT value + ( common_name?, description? ) > + +<!ATTLIST value + name CDATA #REQUIRED > + +<!-- + values + + Human-readable names and descriptions for valid values of a property. +--> + +<!ELEMENT values + (value+) > + +<!ATTLIST values> + +<!-- + cardinality + + Places a constraint on the number of values the property can take + on. + + Its attributes are: + min minimum number of values + max maximum number of values + + Both attributes are optional. If min is not specified, it defaults to + 0. If max is not specified it indicates an unlimited number of values. + If neither is specified this indicates 0 or more values. +--> + +<!ELEMENT cardinality EMPTY> + +<!ATTLIST cardinality + min CDATA "0" + max CDATA "18446744073709551615"> + +<!-- + internal_separators + + Indicates the separators used within a property's value used to + separate the actual values. Used in situations where multiple + values are packed into a single property value instead of using a + multi-valued property. +--> + +<!ELEMENT internal_separators + (#PCDATA) > + +<!ATTLIST internal_separators> + +<!-- + range + + Indicates a range of possible integer values. + + Its attributes are: + + min The minimum value of the range (inclusive). + max The maximum value of the range (inclusive). +--> + +<!ELEMENT range EMPTY> + +<!ATTLIST range + min CDATA #REQUIRED + max CDATA #REQUIRED > + +<!-- + constraints + + Provides a set of constraints on the values a property can take on. +--> + +<!ELEMENT constraints + ( value*, range* ) > +<!ATTLIST constraints> + +<!-- + include_values + + Includes an entire set of values in the choices block. + + Its attributes are: + + type Either "constraints" or "values", indicating an + inclusion of all values allowed by the property's + constraints or all values for which there are + human-readable names and descriptions, respectively. +--> + +<!ELEMENT include_values EMPTY> + +<!ATTLIST include_values + type ( constraints | values ) #REQUIRED > + +<!-- + choices + + Provides a set of common choices for the values a property can take + on. Useful in those cases where the possibilities are unenumerable + or merely inconveniently legion, and a manageable subset is desired + for presentation in a user interface. +--> + +<!ELEMENT choices + ( value*, range*, include_values* ) > + +<!ATTLIST choices> + +<!-- + prop_pattern + + + The prop_pattern describes one property of the enclosing property group + pattern. + + Its attributes are: + + name The property's name. + type The property's type. + required + If the property group is present, this property is required. + + type can be omitted if required is false. +--> + +<!ELEMENT prop_pattern + ( common_name?, description?, units?, visibility?, cardinality?, + internal_separators?, values?, constraints?, choices? ) > + +<!ATTLIST prop_pattern + name CDATA #REQUIRED + type ( count | integer | opaque | host | hostname | + net_address_v4 | net_address_v6 | time | astring | + ustring | boolean | fmri | uri ) #IMPLIED + required ( true | false ) "false" > + +<!-- + pg_pattern + + The pg_pattern describes one property group. + Depending on the element's attributes, these descriptions may apply + to just the enclosing service/instance, instances of the enclosing + service, delegates of the service (assuming it is a restarter), or + all services. + + Its attributes are: + + name The property group's name. If not specified, it + matches all property groups with the specified type. + type The property group's type. If not specified, it + matches all property groups with the specified name. + required + If the property group is required. + target The scope of the pattern, which may be all, delegate, + instance, or this. 'all' is reserved for framework use + and applies the template to all services on the system. + 'delegate' is reserved for restarters, and means the + template applies to all services which use the restarter. + 'this' would refer to the defining service or instance. + 'instance' can only be used in a service's template block, + and means the definition applies to all instances of this + service. + +--> + +<!ELEMENT pg_pattern + ( common_name?, description?, prop_pattern* ) > + +<!ATTLIST pg_pattern + name CDATA "" + type CDATA "" + required ( true | false ) "false" + target ( this | instance | delegate | all ) "this" > + +<!-- template The template contains a collection of metadata about the service. @@ -660,7 +877,7 @@ The template has no attributes. --> <!ELEMENT template - ( common_name, description?, documentation?) > + ( common_name, description?, documentation?, pg_pattern* ) > <!ATTLIST template> diff --git a/usr/src/cmd/svc/milestone/Makefile b/usr/src/cmd/svc/milestone/Makefile index fdcff57933..df0e542f76 100644 --- a/usr/src/cmd/svc/milestone/Makefile +++ b/usr/src/cmd/svc/milestone/Makefile @@ -75,7 +75,8 @@ SYSTEMSVCS= \ SYSTEMMANIFESTS = $(SYSTEMSVCS:%=$(ROOTSVCSYSTEM)/%) SYSTEMSVCSVCS= \ - restarter.xml + restarter.xml \ + global.xml SYSTEMSVCMANIFESTS= $(SYSTEMSVCSVCS:%=$(ROOTSVCSYSTEM)/svc/%) diff --git a/usr/src/cmd/svc/milestone/global.xml b/usr/src/cmd/svc/milestone/global.xml new file mode 100644 index 0000000000..3023d0dae4 --- /dev/null +++ b/usr/src/cmd/svc/milestone/global.xml @@ -0,0 +1,624 @@ +<?xml version="1.0"?> +<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1"> +<!-- + Copyright 2008 Sun Microsystems, Inc. All rights reserved. + Use is subject to license terms. + + 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 + + NOTE: This service manifest is not editable; its contents will + be overwritten by package or patch operations, including + operating system upgrade. Make customizations in a different + file. +--> + +<service_bundle type='manifest' name='SUNWcsr:global'> + +<service + name='system/svc/global' + type='service' + version='1'> + + <!-- + There's no running configuration to manage here. However, + this service stores the system-wide definitions for + templates. + --> + + <create_default_instance enabled='false' /> + + <single_instance/> + + <exec_method + type='method' + name='start' + exec=':true' + timeout_seconds='0' /> + + <exec_method + type='method' + name='stop' + exec=':true' + timeout_seconds='0' /> + + <stability value='Unstable' /> + + <template> + <common_name> + <loctext xml:lang='C'> +system-wide configuration definitions + </loctext> + </common_name> + <documentation> + <manpage title='smf' section='5' + manpath='/usr/share/man' /> + <manpage title='smf_template' section='5' + manpath='/usr/share/man' /> + </documentation> + + <pg_pattern name='general' type='framework' + target='all' required='true'> + <description> + <loctext xml:lang='C'> +Basic information about a service instance which is supplied by the service author, sometimes modified by the administrator, and consumed by SMF and the service's restarter. + </loctext> + </description> + <prop_pattern name='enabled' type='boolean' + required='true'> + <description> + <loctext xml:lang='C'> +The service instance is expected to be started once all of its dependencies are satisfied. If this property is not defined, the restarter will not be notified about this service instance. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='restarter' type='fmri' + required='false'> + <description> + <loctext xml:lang='C'> +The restarter responsible for managing this service instance. If the property is unset, the default restarter, svc.startd, is assumed. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='single_instance' type='boolean' + required='false'> + <description> + <loctext xml:lang='C'> +Only one instance of this service may be run. This property is currently unenforced, but will be at some point in the future. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + </pg_pattern> + + <pg_pattern type='dependency' target='all' required='false'> + <description> + <loctext xml:lang='C'> +A dependency declares a required condition for a service instance to start or stay running. It is defined by the service author and consumed by SMF. + </loctext> + </description> + <prop_pattern name='entities' type='fmri' + required='true'> + <description> + <loctext xml:lang='C'> +The services, service instances, or files used to calculate this dependency. + </loctext> + </description> + <cardinality min='1'/> + </prop_pattern> + <prop_pattern name='external' type='boolean'> + <description> + <loctext xml:lang='C'> +This dependency was declared by the service defined in entities. It will be removed only when that service is deleted. + </loctext> + </description> + <visibility value='readonly'/> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='grouping' type='astring' + required='true'> + <description> + <loctext xml:lang='C'> +How to decide whether this dependency is satisfied. + </loctext> + </description> + <cardinality min='1' max='1'/> + <constraints> + <value name='require_all'> + <description> + <loctext xml:lang='C'> +Satisfied when all cited services are running (online or degraded), or when all indicated files are present. + </loctext> + </description> + </value> + <value name='require_any'> + <description> + <loctext xml:lang='C'> +Satisfied when one of the cited services is running (online or degraded), or when at least one of the indicated files is present. + </loctext> + </description> + </value> + <value name='optional_all'> + <description> + <loctext xml:lang='C'> +Satisfied if the cited services are running (online or degraded) or will not run without administrative action (disabled, maintenance, not present, or offline waiting for dependencies which will not start without administrative action). + + </loctext> + </description> + </value> + <value name='exclude_all'> + <description> + <loctext xml:lang='C'> +Satisfied when all of the cited services are disabled, in the maintenance state, or when cited services or files are not present. + </loctext> + </description> + </value> + </constraints> + <choices> + <include_values type='constraints'/> + </choices> + </prop_pattern> + <prop_pattern name='restart_on' type='astring' + required='true'> + <description> + <loctext xml:lang='C'> +Determines whether to restart the service due to a dependency refresh, restart, or failure. + </loctext> + </description> + <cardinality min='1' max='1'/> + <constraints> + <value name='none'> + <description> + <loctext xml:lang='C'> +Never restart due to dependency refresh, restart, or failure. + </loctext> + </description> + </value> + <value name='error'> + <description> + <loctext xml:lang='C'> +Restart only if the dependency encounters an error, such as an uncorrectable hardware error, or a software error such as a core dump. + </loctext> + </description> + </value> + <value name='restart'> + <description> + <loctext xml:lang='C'> +Restart if the dependency encounters an error or is explicitly restarted. + </loctext> + </description> + </value> + <value name='refresh'> + <description> + <loctext xml:lang='C'> +Restart if the dependency encounters an error, is explicitly restarted, or explicitly refreshed. + </loctext> + </description> + </value> + </constraints> + <choices> + <include_values type='constraints'/> + </choices> + </prop_pattern> + <prop_pattern name='type' type='astring' + required='true'> + <description> + <loctext xml:lang='C'> +The type of the dependency: service or file. + </loctext> + </description> + <cardinality min='1' max='1'/> + <constraints> + <value name='service'> + <description> + <loctext xml:lang='C'> +Depend on services or instances. + </loctext> + </description> + </value> + <value name='path'> + <description> + <loctext xml:lang='C'> +Depend on the existance of a file path. + </loctext> + </description> + </value> + </constraints> + <choices> + <include_values type='constraints'/> + </choices> + </prop_pattern> + </pg_pattern> + + <pg_pattern type='template_pg_pattern' target='all' + required='false'> + <description> + <loctext xml:lang='C'> +Template data about property groups. This information is provided in the manifest by the service author and should not be modified directly. + </loctext> + </description> + + <prop_pattern name='name' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +Optional name of a property group which is described by this template. No name means the name is wildcarded. + </loctext> + </description> + <visibility value='hidden'/> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='type' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +Optional type of property groups which are described by this template. No type means that the type is wildcarded. + </loctext> + </description> + <visibility value='hidden'/> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='required' type='boolean' + required='false'> + <description> + <loctext xml:lang='C'> +If true, entities without a property group which matches this pattern are considered invalid. + </loctext> + </description> + <visibility value='hidden'/> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='target' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The services or service instances to which this template should be applied. + </loctext> + </description> + <visibility value='hidden'/> + <cardinality min='1' max='1'/> + <constraints> + <value name='this'> + <description> + <loctext xml:lang='C'> +The service or instance on which the property group resides. + </loctext> + </description> + </value> + <value name='instance'> + <description> + <loctext xml:lang='C'> +This instance, or any instance of this service. + </loctext> + </description> + </value> + <value name='delegate'> + <description> + <loctext xml:lang='C'> +All instances which currently define this service as their restarter. + </loctext> + </description> + </value> + <value name='all'> + <description> + <loctext xml:lang='C'> +All services and instances on the system. "all" may only be set on the global service. + </loctext> + </description> + </value> + </constraints> + </prop_pattern> + </pg_pattern> + + <pg_pattern type='template_prop_pattern' target='all' + required='false'> + <description> + <loctext xml:lang='C'> +Template data about properties. This information is provided in the manifest by the service author and should not be modified directly. + </loctext> + </description> + <prop_pattern name='name' type='astring' + required='true'> + <description> + <loctext xml:lang='C'> +Name of property this template applies to. + </loctext> + </description> + <visibility value='hidden'/> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='pg_pattern' type='astring' + required='true'> + <description> + <loctext xml:lang='C'> +Name of property group that describes the enclosing property group pattern. + </loctext> + </description> + <visibility value='hidden'/> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='required' type='boolean' + required='false'> + <description> + <loctext xml:lang='C'> +Defines whether a property matched by this template is required. + </loctext> + </description> + <visibility value='hidden'/> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='type' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The type that a property which this template refers to should be. + </loctext> + </description> + <visibility value='hidden'/> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='visibility' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The visibility of this property, which is readwrite by default. Visibility is only a cue to the user interface, and will not be explicitly enforced by SMF. + </loctext> + </description> + <visibility value='hidden'/> + <cardinality min='1' max='1'/> + <constraints> + <value name='hidden'> + <description> + <loctext xml:lang='C'> +Hidden in default user interface views. + </loctext> + </description> + </value> + <value name='readonly'> + <description> + <loctext xml:lang='C'> +Expected to be read only in most user interfaces. + </loctext> + </description> + </value> + <value name='readwrite'> + <description> + <loctext xml:lang='C'> +Expected to be manipulated in many user interfaces. + </loctext> + </description> + </value> + </constraints> + </prop_pattern> + <prop_pattern name='cardinality_min' type='count' + required='false'> + <description> + <loctext xml:lang='C'> +Minimum number of required values. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='cardinality_max' type='count' + required='false'> + <description> + <loctext xml:lang='C'> +Maximum number of required values. + </loctext> + </description> + <visibility value='hidden'/> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='internal_separators' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +List of separator characters for values. + </loctext> + </description> + <visibility value='hidden'/> + <cardinality min='1'/> + </prop_pattern> + <prop_pattern name='constraint_name' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +Values the property is expected to be constrained to. + </loctext> + </description> + <visibility value='hidden'/> + <cardinality min='1'/> + </prop_pattern> + <prop_pattern name='constraint_range' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +Ranges the property is expected to be constrained to. + </loctext> + </description> + <visibility value='hidden'/> + <cardinality min='1'/> + <internal_separators>,</internal_separators> + </prop_pattern> + <prop_pattern name='choices_range' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +Ranges a user should be offered as a choice for this property. + </loctext> + </description> + <visibility value='hidden'/> + <cardinality min='1'/> + <internal_separators>,</internal_separators> + </prop_pattern> + <prop_pattern name='choices_name' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +Values a users should be offered as a choice for this property. + </loctext> + </description> + <visibility value='hidden'/> + <cardinality min='1'/> + </prop_pattern> + <prop_pattern name='choices_include_values' + type='astring' required='false'> + <description> + <loctext xml:lang='C'> +Whether the choices should include the defined constraints or values. + </loctext> + </description> + <visibility value='hidden'/> + <cardinality min='1' max='1'/> + <constraints> + <value name='constraints'> + <description> + <loctext xml:lang='C'> +Include all defined constraints as choices. + </loctext> + </description> + </value> + <value name='values'> + <description> + <loctext xml:lang='C'> +Include all defined values as choices. + </loctext> + </description> + </value> + </constraints> + </prop_pattern> + </pg_pattern> + + <pg_pattern name='method_context' type='framework' + target='all' required='false'> + <description> + <loctext xml:lang='C'> +Specifies the default execution context for all service methods. It is defined by the service author, may be augmented by an administrator, and is consumed by the service's restarter. + </loctext> + </description> + + <!-- method_context direct properties --> + <prop_pattern name='working_directory' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The working directory to launch the method from. ":default" can be used as a token to indicate the home directory of the user specified by the credential or profile. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='project' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The project ID in numeric or text form. ":default" can be used as a token to indicate a project identified by getdefaultproj(3PROJECT) for the user whose uid is used to launch the method. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='resource_pool' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The resource pool name in which to launch the method. ":default" can be used +as a token to indicate the pool specified in the project(4) entry given in +the project attribute. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + + <!-- method_credential properties --> + <prop_pattern name='user' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The user ID in numeric or text form. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='group' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The group ID in numeric or text form. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='supp_groups' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +An optional string that specifies the supplemental group memberships by ID, +in numeric or text form. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='privileges' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +An optional string specifying the privilege set as defined in privileges(5). + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='limit_privileges' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +An optional string specifying the limit privilege set as defined in +privileges(5). + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + + <!-- method_profile properties --> + <prop_pattern name='use_profile' type='boolean' + required='false'> + <description> + <loctext xml:lang='C'> +A boolean that specifies whether the profile should be used instead of the +user, group, privileges, and limit_privileges properties. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='profile' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The name of an RBAC (role-based access control) profile which, along with the +method executable, identifies an entry in exec_attr(4). + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + </pg_pattern> + + </template> +</service> + +</service_bundle> diff --git a/usr/src/cmd/svc/milestone/make-console-login-xml b/usr/src/cmd/svc/milestone/make-console-login-xml index 75948cde6f..a8e516fe9f 100644 --- a/usr/src/cmd/svc/milestone/make-console-login-xml +++ b/usr/src/cmd/svc/milestone/make-console-login-xml @@ -28,7 +28,7 @@ cat >console-login.xml <<EOF <?xml version="1.0"?> <!-- - Copyright 2006 Sun Microsystems, Inc. All rights reserved. + Copyright 2008 Sun Microsystems, Inc. All rights reserved. Use is subject to license terms. NOTE: This service manifest is not editable; its contents will @@ -207,6 +207,129 @@ Console login <manpage title='ttymon' section='1M' manpath='/usr/share/man' /> </documentation> + + <pg_pattern name='ttymon' type='application' target='this' + required='false'> + <prop_pattern name='device' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The terminal device to be used for the console login prompt. + </loctext> + </description> + <visibility value='readwrite'/> + <cardinality min='1' max='1'/> + <values> + <value name='/dev/console'> + <description> + <loctext xml:lang='C'> +The default console device + </loctext> + </description> + </value> + <value name='/dev/wscons'> + <description> + <loctext xml:lang='C'> +The physical workstation console (i.e. keyboard and monitor) + </loctext> + </description> + </value> + <value name='/dev/term/a'> + <description> + <loctext xml:lang='C'> +Serial device A + </loctext> + </description> + </value> + <value name='/dev/term/b'> + <description> + <loctext xml:lang='C'> +Serial device B + </loctext> + </description> + </value> + </values> + <choices> + <include_values type='values'/> + </choices> + </prop_pattern> + <prop_pattern name='label' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +/etc/ttydefs entry + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +The appropriate entry from /etc/ttydefs + </loctext> + </description> + <visibility value='readwrite'/> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='timeout' type='count' + required='false'> + <description> + <loctext xml:lang='C'> +If no user input is given within the number of seconds defined by this property after the prompt is printed, ttymon should exit. + </loctext> + </description> + <units> + <loctext xml:lang='C'> +seconds + </loctext> + </units> + <visibility value='readwrite'/> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='nohangup' type='boolean' + required='false'> + <description> + <loctext xml:lang='C'> +Do not force a hangup on the line by setting the speed to zero before setting the speed to the default or specified speed. + </loctext> + </description> + <visibility value='readwrite'/> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='modules' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +STREAMS modules + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +STREAMS modules to be pushed on the terminal + </loctext> + </description> + <visibility value='readwrite'/> + <internal_separators>,</internal_separators> + </prop_pattern> + <prop_pattern name='prompt' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +Prompt to be printed on console + </loctext> + </description> + <visibility value='readwrite'/> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='terminal_type' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +Sets the initial value of the TERM environment variable + </loctext> + </description> + <visibility value='readwrite'/> + <cardinality min='1' max='1'/> + </prop_pattern> + </pg_pattern> + </template> </service> diff --git a/usr/src/cmd/svc/milestone/restarter.xml b/usr/src/cmd/svc/milestone/restarter.xml index 6bd796dead..644c86c6f9 100644 --- a/usr/src/cmd/svc/milestone/restarter.xml +++ b/usr/src/cmd/svc/milestone/restarter.xml @@ -1,15 +1,14 @@ <?xml version="1.0"?> <!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1"> <!-- - Copyright 2005 Sun Microsystems, Inc. All rights reserved. + Copyright 2008 Sun Microsystems, Inc. All rights reserved. Use is subject to license terms. 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. + 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. @@ -24,8 +23,6 @@ CDDL HEADER END - ident "%Z%%M% %I% %E% SMI" - NOTE: This service manifest is not editable; its contents will be overwritten by package or patch operations, including operating system upgrade. Make customizations in a different @@ -36,7 +33,7 @@ <service name='system/svc/restarter' - type='service' + type='restarter' version='1'> <!-- @@ -49,18 +46,1214 @@ <single_instance/> + <!-- + The restarter is actually started by init, so these methods are + ignored. However, they are required by definition and are + included here to avoid spurious validation errors. + --> + <exec_method + type='method' + name='start' + exec=':true' + timeout_seconds='0' /> + + <exec_method + type='method' + name='stop' + exec=':true' + timeout_seconds='0' /> + <stability value='Unstable' /> <template> <common_name> <loctext xml:lang='C'> - master restarter +master restarter </loctext> </common_name> <documentation> <manpage title='svc.startd' section='1M' manpath='/usr/share/man' /> + <manpage title='smf_method' section='5' + manpath='/usr/share/man' /> + <manpage title='smf' section='5' + manpath='/usr/share/man' /> </documentation> + + <!-- + Much of the restarter pg is populated by librestart, but + because svc.startd augments the property group, we choose + to define the entire property group as restarter-specific + rather than define it globally and miss some of the + properties. Templates does not currently provide a way + to have multiple entities 'own' and describe a property + group. + --> + <pg_pattern name='restarter' type='framework' + target='delegate' required='false'> + <description> + <loctext xml:lang='C'> +Communicate restarter-set status of the service. + </loctext> + </description> + <prop_pattern name='alt_logfile' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The logfile for restarter actions on this service and any direct output from its methods which occurred before the /var filesystem was available. + </loctext> + </description> + <visibility value='readonly'/> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='logfile' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The logfile for restarter actions on this service and any direct output from its methods. + </loctext> + </description> + <visibility value='readonly'/> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='contract' type='count' + required='false'> + <description> + <loctext xml:lang='C'> +Primary process contract for a 'contract' or 'child' service. + </loctext> + </description> + <visibility value='readonly'/> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='start_pid' type='count' + required='false'> + <description> + <loctext xml:lang='C'> +PID last launched as the start method for this service. + </loctext> + </description> + <visibility value='readonly'/> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='start_method_timestamp' type='time' + required='false'> + <description> + <loctext xml:lang='C'> +Time the start method was last run. + </loctext> + </description> + <visibility value='readonly'/> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='auxiliary_state' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +Restarter-set auxiliary information about the current state. + </loctext> + </description> + <visibility value='readonly'/> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='state_timestamp' type='time' + required='false'> + <description> + <loctext xml:lang='C'> +Time the current state was reached. + </loctext> + </description> + <visibility value='readonly'/> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='state' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The current state of this service instance. + </loctext> + </description> + <visibility value='readonly'/> + <cardinality min='1' max='1'/> + <constraints> + <value name="online"> + <description> + <loctext xml:lang='C'> +The instance is online and running. + </loctext> + </description> + </value> + <value name="offline"> + <description> + <loctext xml:lang='C'> +The instance is enabled, but not yet running or available to run. The most common reason for service instances to be in this state is because one or more of their dependencies are not satisfied. + </loctext> + </description> + </value> + <value name="uninitialized"> + <description> + <loctext xml:lang='C'> +The initial state for all instances before svc.startd has had a chance to evaluate on them. Instances are in this state when their restarter has not yet started. + </loctext> + </description> + </value> + <value name="degraded"> + <description> + <loctext xml:lang='C'> +The instance is enabled and running or available to run. The instance, however, is functioning at a limited capacity in comparison to normal operation. + </loctext> + </description> + </value> + <value name="disabled"> + <description> + <loctext xml:lang='C'> +The instance is disabled. + </loctext> + </description> + </value> + <value name="maintenance"> + <description> + <loctext xml:lang='C'> +The instance is enabled, but not able to run. Administrative action is required to restore the instance to offline and subsequent states. + </loctext> + </description> + </value> + </constraints> + </prop_pattern> + <prop_pattern name='next_state' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The next expected state of this instance. + </loctext> + </description> + <visibility value='readonly'/> + <cardinality min='1' max='1'/> + <constraints> + <value name="online"> + <description> + <loctext xml:lang='C'> +The instance is being started, and will soon be online and running. This transition may fail and the instance may end up in offiline or maintenance instead. + </loctext> + </description> + </value> + <value name="offline"> + <description> + <loctext xml:lang='C'> +The instance has been temporarily stopped. Most instances will leave this state once their dependencies are satisfied. + </loctext> + </description> + </value> + <value name="degraded"> + <description> + <loctext xml:lang='C'> +The instance will be enabled and availble to run, although in a limited capacity. + </loctext> + </description> + </value> + <value name="disabled"> + <description> + <loctext xml:lang='C'> +The instance will be disabled. + </loctext> + </description> + </value> + <value name="maintenance"> + <description> + <loctext xml:lang='C'> +The instance will be in maintenance, and administrative action will be required to restore the instance to offline and subsequent states. + </loctext> + </description> + </value> + <value name="none"> + <description> + <loctext xml:lang='C'> +The instance is not currently transitioning between states. + </loctext> + </description> + </value> + </constraints> + </prop_pattern> + </pg_pattern> + + <pg_pattern name='options' type='framework' + target='this' required='false'> + <description> + <loctext xml:lang='C'> +Specify options for the svc.startd restarter. + </loctext> + </description> + + <prop_pattern name='boot_messages' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +Define verbosity of messages to print to the console during boot. + </loctext> + </description> + <cardinality min='1' max='1'/> + <constraints> + <value name='quiet'> + <description> + <loctext xml:lang='C'> +Issue console messages only on service failures. + </loctext> + </description> + </value> + <value name='verbose'> + <description> + <loctext xml:lang='C'> +Print a message per service started to indicate success or failure. + </loctext> + </description> + </value> + </constraints> + <choices> + <include_values type='constraints'/> + </choices> + </prop_pattern> + + <prop_pattern name='logging' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +Control the level of global service logging for svc.startd. + </loctext> + </description> + <cardinality min='1' max='1'/> + <constraints> + <value name='quiet'> + <description> + <loctext xml:lang='C'> +Send error messages requiring administrative intervention to console, syslog, and svc.startd's global logfile. + </loctext> + </description> + </value> + <value name='verbose'> + <description> + <loctext xml:lang='C'> +Sends a message per service started to the console, error messages requiring administrative intervention to console, syslog, and svc.startd's global logfile, and information about errors which do not require intervention to the logfile. + </loctext> + </description> + </value> + <value name='debug'> + <description> + <loctext xml:lang='C'> +Send debug messages to svc.startd's global logfile, error messages requiring administrative intervention to the console, syslog, and the logfile, and a message per service started to the console. + </loctext> + </description> + </value> + </constraints> + <choices> + <include_values type='constraints'/> + </choices> + </prop_pattern> + + <prop_pattern name='milestone' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +An FRMI which defines the milestone used as the default boot level. + </loctext> + </description> + <cardinality min='1' max='1'/> + <constraints> + <value + name='svc:/milestone/single-user:default'> + </value> + <value + name='svc:/milestone/multi-user:default'> + </value> + <value name= + 'svc:/milestone/multi-user-server:default'> + </value> + <value name='all'> + <description> + <loctext xml:lang='C'> +Start all enabled services. + </loctext> + </description> + </value> + <value name='none'> + <description> + <loctext xml:lang='C'> +Start no services. + </loctext> + </description> + </value> + </constraints> + </prop_pattern> + </pg_pattern> + + <pg_pattern name='system' type='framework' + target='this' required='false'> + <prop_pattern name='reconfigure' type='boolean' + required='false'> + <description> + <loctext xml:lang='C'> +Indicates that a reconfiguration reboot has been requested. + </loctext> + </description> + <visibility value='readonly'/> + <cardinality min='1' max='1'/> + </prop_pattern> + </pg_pattern> + + <pg_pattern name='startd' type='framework' + target='delegate' required='false'> + <description> + <loctext xml:lang='C'> +Information about how a service instance is managed by svc.startd, which is supplied by the service author, sometimes modified by the administrator. + </loctext> + </description> + + <prop_pattern name='duration' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +Defines the service's model. + </loctext> + </description> + <cardinality min='1' max='1'/> + <constraints> + <value name='contract'> + <description> + <loctext xml:lang='C'> +A standard system daemon, which runs forever to provide a service. It is not considered online until the start method process exits, though child processes are monitored for errors. + </loctext> + </description> + </value> + <value name='transient'> + <description> + <loctext xml:lang='C'> +The service is online as soon as its start method returns -- child processes are not monitored. + </loctext> + </description> + </value> + <value name='child'> + <description> + <loctext xml:lang='C'> +A service which runs for the lifetime of the child process, and is restarted when that process exits. + </loctext> + </description> + </value> + </constraints> + <choices> + <include_values type='constraints'/> + </choices> + </prop_pattern> + <prop_pattern name='ignore_error' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +A list of events which should not be considered service errors by svc.startd. + </loctext> + </description> + <cardinality min='1' max='1'/> + <!-- + We won't recommend this as a choice since + it's only here to work around the fact + that startd defines this as a single + value rather than a value list *and* + templates doesn't currently take care + of assembling separately defined + values with the defined internal separator. + --> + <values> + <value name='signal,core'> + <description> + <loctext xml:lang='C'> +svc.startd should ignore coredumps and signals sent from outside the service. + </loctext> + </description> + </value> + </values> + <choices> + <value name='core'> + <description> + <loctext xml:lang='C'> +svc.startd should ignore coredumps from subprocesses. + </loctext> + </description> + </value> + <value name='signal'> + <description> + <loctext xml:lang='C'> +svc.startd should ignore signals sent from outside the service. + </loctext> + </description> + </value> + <value name='core,signal'> + <description> + <loctext xml:lang='C'> +svc.startd should ignore coredumps and signals sent from outside the service. + </loctext> + </description> + </value> + </choices> + </prop_pattern> + <prop_pattern name='need_session' type='boolean' + required='false'> + <description> + <loctext xml:lang='C'> +The instance should be launched in its own session per setpgrp(2). + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='utmpx_prefix' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The instance requires that svc.startd create a valid utmpx entry prior to start method execution. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + </pg_pattern> + + <pg_pattern name='start' type='method' target='delegate' + required='true'> + <description> + <loctext xml:lang='C'> +The start method defines how svc.startd should start the instance. + </loctext> + </description> + <prop_pattern name='exec' type='astring' + required='true'> + <common_name> + <loctext xml:lang='C'> +method executable + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +The method executable may be a script, program, or keyword. + </loctext> + </description> + <cardinality min='1' max='1'/> + <values> + <value name=':true'> + <description> + <loctext xml:lang='C'> +Always returns SMF_EXIT_OK. This token should be used when the start method is unnecessary for the particular service implementation. + </loctext> + </description> + </value> + <value name=':kill [-signal]'> + <description> + <loctext xml:lang='C'> +Sends the specified signal, which is SIGTERM by default, to all processes in the primary instance contract. Always returns SMF_EXIT_OK. This token should be used to replace common pkill invocations. + + </loctext> + </description> + </value> + </values> + <choices> + <include_values type='values'/> + </choices> + </prop_pattern> + + <prop_pattern name='type' type='astring' + required='true'> + <description> + <loctext xml:lang='C'> +A method may only be of type method. + </loctext> + </description> + <cardinality min='1' max='1'/> + <constraints> + <value name="method"/> + </constraints> + </prop_pattern> + + <prop_pattern name='timeout_seconds' type='count' + required='true'> + <description> + <loctext xml:lang='C'> +Number of seconds before the method is considered unresponsive. After the method timeout expires, the method will be killed. + </loctext> + </description> + <cardinality min='1' max='1'/> + <values> + <value name="0"> + <common_name> + <loctext xml:lang='C'> +infinite + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +This method will never time out. + </loctext> + </description> + </value> + <value name="-1"> + <common_name> + <loctext xml:lang='C'> +infinite (legacy) + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +This method will never time out. 0 is the preferred value. + </loctext> + </description> + </value> + </values> + </prop_pattern> + + <!-- method_context direct properties --> + <prop_pattern name='working_directory' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The working directory to launch the method from. ":default" can be used as a token to indicate the home directory of the user specified by the credential or profile. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='project' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The project ID in numeric or text form. :default can be used as a token to indicate a project identified by getdefaultproj(3PROJECT) for the user whose uid is used to launch the method. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='resource_pool' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method context resource pool + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +The resource pool name on which to launch the method. :default can be used +as a token to indicate the pool specified in the project(4) entry given in +the project attribute. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + + <!-- method_credential properties --> + <prop_pattern name='user' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method credential user + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +The user ID in numeric or text form. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='group' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method credential group + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +The group ID in numeric or text form. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='supp_groups' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method credential supplemental groups + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +An optional string that specifies the supplemental group memberships by ID, +in numeric or text form. + </loctext> + </description> + <cardinality min='1' max='1'/> + <internal_separators>,</internal_separators> + </prop_pattern> + <prop_pattern name='privileges' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method credential privileges + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +An optional string specifying the privilege set as defined in privileges(5). + </loctext> + </description> + <cardinality min='1' max='1'/> + <internal_separators>,</internal_separators> + </prop_pattern> + <prop_pattern name='limit_privileges' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method credential limit privilege set + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +An optional string specifying the limit privilege set as defined in +privileges(5). + </loctext> + </description> + <cardinality min='1' max='1'/> + <internal_separators>,</internal_separators> + </prop_pattern> + + <!-- method_profile properties --> + <prop_pattern name='use_profile' type='boolean' + required='false'> + <description> + <loctext xml:lang='C'> +A boolean that specifies whether the profile should be used instead of the +user, group, privileges, and limit_privileges properties. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='profile' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method profile RBAC profile specification + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +The name of an RBAC (role-based access control) profile which, along with the +method executable, identifies an entry in exec_attr(4). + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + + <!-- method_environment properties --> + <prop_pattern name='environment' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method environment variables + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +Environment variables to insert into the environment of the method, in the +form of a number of NAME=value strings. + </loctext> + </description> + </prop_pattern> + </pg_pattern> + + <pg_pattern name='stop' type='method' target='delegate' + required='true'> + <description> + <loctext xml:lang='C'> +The stop method defines how svc.startd should stop the instance. + </loctext> + </description> + <prop_pattern name='exec' type='astring' + required='true'> + <common_name> + <loctext xml:lang='C'> +method executable + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +The method executable may be a script, program, or keyword. + </loctext> + </description> + <cardinality min='1' max='1'/> + <values> + <value name=':true'> + <description> + <loctext xml:lang='C'> +Always returns SMF_EXIT_OK. This token should be used when the stop method is unnecessary for the particular service implementation. + </loctext> + </description> + </value> + <value name=':kill [-signal]'> + <description> + <loctext xml:lang='C'> +Sends the specified signal, which is SIGTERM by default, to all processes in the primary instance contract. Always returns SMF_EXIT_OK. This token should be used to replace common pkill invocations. + </loctext> + </description> + </value> + </values> + <choices> + <include_values type='values'/> + </choices> + </prop_pattern> + + <prop_pattern name='type' type='astring' + required='true'> + <description> + <loctext xml:lang='C'> +A method may only be of type method. + </loctext> + </description> + <cardinality min='1' max='1'/> + <constraints> + <value name="method"/> + </constraints> + </prop_pattern> + + <prop_pattern name='timeout_seconds' type='count' + required='true'> + <description> + <loctext xml:lang='C'> +Number of seconds before the method is considered unresponsive. After the method timeout expires, the method will be killed. + </loctext> + </description> + <cardinality min='1' max='1'/> + <values> + <value name="0"> + <common_name> + <loctext xml:lang='C'> +infinite + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +This method will never time out. + </loctext> + </description> + </value> + <value name="-1"> + <common_name> + <loctext xml:lang='C'> +infinite (legacy) + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +This method will never time out. 0 is the preferred value. + </loctext> + </description> + </value> + </values> + </prop_pattern> + + <!-- method_context direct properties --> + <prop_pattern name='working_directory' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The working directory to launch the method from. ":default" can be used as a token to indicate the home directory of the user specified by the credential or profile. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='project' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The project ID in numeric or text form. :default can be used as a token to indicate a project identified by getdefaultproj(3PROJECT) for the user whose uid is used to launch the method. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='resource_pool' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method context resource pool + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +The resource pool name on which to launch the method. :default can be used +as a token to indicate the pool specified in the project(4) entry given in +the project attribute. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + + <!-- method_credential properties --> + <prop_pattern name='user' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method credential user + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +The user ID in numeric or text form. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='group' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method credential group + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +The group ID in numeric or text form. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='supp_groups' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method credential supplemental groups + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +An optional string that specifies the supplemental group memberships by ID, +in numeric or text form. + </loctext> + </description> + <cardinality min='1' max='1'/> + <internal_separators>,</internal_separators> + </prop_pattern> + <prop_pattern name='privileges' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method credential privileges + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +An optional string specifying the privilege set as defined in privileges(5). + </loctext> + </description> + <cardinality min='1' max='1'/> + <internal_separators>,</internal_separators> + </prop_pattern> + <prop_pattern name='limit_privileges' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method credential limit privilege set + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +An optional string specifying the limit privilege set as defined in +privileges(5). + </loctext> + </description> + <cardinality min='1' max='1'/> + <internal_separators>,</internal_separators> + </prop_pattern> + + <!-- method_profile properties --> + <prop_pattern name='use_profile' type='boolean' + required='false'> + <description> + <loctext xml:lang='C'> +A boolean that specifies whether the profile should be used instead of the +user, group, privileges, and limit_privileges properties. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='profile' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method profile RBAC profile specification + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +The name of an RBAC (role-based access control) profile which, along with the +method executable, identifies an entry in exec_attr(4). + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + + <!-- method_environment properties --> + <prop_pattern name='environment' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method environment variables + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +Environment variables to insert into the environment of the method, in the +form of a number of NAME=value strings. + </loctext> + </description> + </prop_pattern> + </pg_pattern> + + <pg_pattern name='refresh' type='method' target='delegate' + required='false'> + <description> + <loctext xml:lang='C'> +The refresh method defines how svc.startd should upload new configuration to the service without interrupting its operation. + </loctext> + </description> + <prop_pattern name='exec' type='astring' + required='true'> + <common_name> + <loctext xml:lang='C'> +method executable + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +The method executable may be a script, program, or keyword. + </loctext> + </description> + <cardinality min='1' max='1'/> + <values> + <value name=':true'> + <description> + <loctext xml:lang='C'> +Always returns SMF_EXIT_OK. + </loctext> + </description> + </value> + <value name=':kill [-signal]'> + <description> + <loctext xml:lang='C'> +Sends the specified signal, which is SIGTERM by default, to all processes in the primary instance contract. Always returns SMF_EXIT_OK. This token should be used to replace common kill -HUP invocations. + </loctext> + </description> + </value> + </values> + <choices> + <include_values type='values'/> + </choices> + </prop_pattern> + + <prop_pattern name='type' type='astring' + required='true'> + <description> + <loctext xml:lang='C'> +A method may only be of type method. + </loctext> + </description> + <cardinality min='1' max='1'/> + <constraints> + <value name="method"/> + </constraints> + </prop_pattern> + + <prop_pattern name='timeout_seconds' type='count' + required='true'> + <description> + <loctext xml:lang='C'> +Number of seconds before the method is considered unresponsive. After the method timeout expires, the method will be killed. + </loctext> + </description> + <cardinality min='1' max='1'/> + <values> + <value name="0"> + <common_name> + <loctext xml:lang='C'> +infinite + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +This method will never time out. + </loctext> + </description> + </value> + <value name="-1"> + <common_name> + <loctext xml:lang='C'> +infinite (legacy) + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +This method will never time out. 0 is the preferred value. + </loctext> + </description> + </value> + </values> + </prop_pattern> + + <!-- method_context direct properties --> + <prop_pattern name='working_directory' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The working directory to launch the method from. ":default" can be used as a token to indicate the home directory of the user specified by the credential or profile. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='project' type='astring' + required='false'> + <description> + <loctext xml:lang='C'> +The project ID in numeric or text form. :default can be used as a token to indicate a project identified by getdefaultproj(3PROJECT) for the user whose uid is used to launch the method. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='resource_pool' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method context resource pool + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +The resource pool name on which to launch the method. :default can be used +as a token to indicate the pool specified in the project(4) entry given in +the project attribute. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + + <!-- method_credential properties --> + <prop_pattern name='user' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method credential user + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +The user ID in numeric or text form. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='group' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method credential group + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +The group ID in numeric or text form. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='supp_groups' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method credential supplemental groups + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +An optional string that specifies the supplemental group memberships by ID, +in numeric or text form. + </loctext> + </description> + <cardinality min='1' max='1'/> + <internal_separators>,</internal_separators> + </prop_pattern> + <prop_pattern name='privileges' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method credential privileges + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +An optional string specifying the privilege set as defined in privileges(5). + </loctext> + </description> + <cardinality min='1' max='1'/> + <internal_separators>,</internal_separators> + </prop_pattern> + <prop_pattern name='limit_privileges' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method credential limit privilege set + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +An optional string specifying the limit privilege set as defined in +privileges(5). + </loctext> + </description> + <cardinality min='1' max='1'/> + <internal_separators>,</internal_separators> + </prop_pattern> + + <!-- method_profile properties --> + <prop_pattern name='use_profile' type='boolean' + required='false'> + <description> + <loctext xml:lang='C'> +A boolean that specifies whether the profile should be used instead of the +user, group, privileges, and limit_privileges properties. + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + <prop_pattern name='profile' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method profile RBAC profile specification + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +The name of an RBAC (role-based access control) profile which, along with the +method executable, identifies an entry in exec_attr(4). + </loctext> + </description> + <cardinality min='1' max='1'/> + </prop_pattern> + + <!-- method_environment properties --> + <prop_pattern name='environment' type='astring' + required='false'> + <common_name> + <loctext xml:lang='C'> +method environment variables + </loctext> + </common_name> + <description> + <loctext xml:lang='C'> +Environment variables to insert into the environment of the method, in the +form of a number of NAME=value strings. + </loctext> + </description> + </prop_pattern> + </pg_pattern> + </template> </service> diff --git a/usr/src/cmd/svc/seed/Makefile b/usr/src/cmd/svc/seed/Makefile index 807217466b..b8e915ca63 100644 --- a/usr/src/cmd/svc/seed/Makefile +++ b/usr/src/cmd/svc/seed/Makefile @@ -21,7 +21,6 @@ # Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # -#ident "%Z%%M% %I% %E% SMI" include ../../Makefile.cmd @@ -52,6 +51,7 @@ INSTALLSEED = $(ROOT)/usr/sadm/install COMMON_DESCRIPTIONS = \ ../milestone/boot-archive.xml \ ../milestone/devices-local.xml \ + ../milestone/global.xml \ ../milestone/identity.xml \ ../milestone/local-fs.xml \ ../milestone/manifest-import.xml \ diff --git a/usr/src/cmd/svc/svccfg/Makefile b/usr/src/cmd/svc/svccfg/Makefile index f87147fa8b..5cdb524581 100644 --- a/usr/src/cmd/svc/svccfg/Makefile +++ b/usr/src/cmd/svc/svccfg/Makefile @@ -23,7 +23,6 @@ # Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # -# ident "%Z%%M% %I% %E% SMI" MYPROG = svccfg PROG = $(MYPROG) @@ -32,6 +31,7 @@ SRCS = svccfg_main.c \ svccfg_engine.c \ svccfg_internal.c \ svccfg_libscf.c \ + svccfg_tmpl.c \ svccfg_xml.c \ svccfg_help.c diff --git a/usr/src/cmd/svc/svccfg/svccfg.h b/usr/src/cmd/svc/svccfg/svccfg.h index e63c24c111..bf73e5202f 100644 --- a/usr/src/cmd/svc/svccfg/svccfg.h +++ b/usr/src/cmd/svc/svccfg/svccfg.h @@ -72,16 +72,20 @@ extern int lex_lineno; * lxml_prop_types[] */ typedef enum element { - SC_ASTRING = 0x0, SC_BOOLEAN, SC_COMMON_NAME, SC_COUNT, - SC_INSTANCE_CREATE_DEFAULT, SC_DEPENDENCY, SC_DEPENDENT, SC_DESCRIPTION, - SC_DOC_LINK, SC_DOCUMENTATION, SC_ENABLED, SC_EXEC_METHOD, SC_FMRI, - SC_HOST, SC_HOSTNAME, SC_INSTANCE, SC_INTEGER, SC_LOCTEXT, SC_MANPAGE, - SC_METHOD_CONTEXT, SC_METHOD_CREDENTIAL, SC_METHOD_PROFILE, - SC_METHOD_ENVIRONMENT, SC_METHOD_ENVVAR, SC_NET_ADDR_V4, SC_NET_ADDR_V6, - SC_OPAQUE, SC_PROPERTY, SC_PROPERTY_GROUP, SC_PROPVAL, SC_RESTARTER, - SC_SERVICE, SC_SERVICE_BUNDLE, SC_SERVICE_FMRI, SC_INSTANCE_SINGLE, - SC_STABILITY, SC_TEMPLATE, SC_TIME, SC_URI, SC_USTRING, SC_VALUE_NODE, - SC_XI_FALLBACK, SC_XI_INCLUDE + SC_ASTRING = 0, SC_BOOLEAN, SC_CARDINALITY, SC_CHOICES, + SC_COMMON_NAME, SC_CONSTRAINTS, SC_COUNT, + SC_INSTANCE_CREATE_DEFAULT, SC_DEPENDENCY, SC_DEPENDENT, + SC_DESCRIPTION, SC_DOC_LINK, SC_DOCUMENTATION, SC_ENABLED, + SC_EXEC_METHOD, SC_FMRI, SC_HOST, SC_HOSTNAME, SC_INCLUDE_VALUES, + SC_INSTANCE, SC_INTEGER, SC_INTERNAL_SEPARATORS, SC_LOCTEXT, SC_MANPAGE, + SC_METHOD_CONTEXT, SC_METHOD_CREDENTIAL, + SC_METHOD_PROFILE, SC_METHOD_ENVIRONMENT, SC_METHOD_ENVVAR, + SC_NET_ADDR_V4, SC_NET_ADDR_V6, SC_OPAQUE, SC_PG_PATTERN, + SC_PROP_PATTERN, SC_PROPERTY, SC_PROPERTY_GROUP, SC_PROPVAL, SC_RANGE, + SC_RESTARTER, SC_SERVICE, SC_SERVICE_BUNDLE, SC_SERVICE_FMRI, + SC_INSTANCE_SINGLE, SC_STABILITY, SC_TEMPLATE, SC_TIME, SC_UNITS, + SC_URI, SC_USTRING, SC_VALUE, SC_VALUE_NODE, SC_VALUES, + SC_VISIBILITY, SC_XI_FALLBACK, SC_XI_INCLUDE } element_t; typedef enum bundle_type { @@ -101,8 +105,7 @@ typedef enum service_type { } service_type_t; typedef enum entity_type { - SVCCFG_SERVICE_OBJECT = 0x0, SVCCFG_INSTANCE_OBJECT, - SVCCFG_TEMPLATE_OBJECT + SVCCFG_SERVICE_OBJECT = 0x0, SVCCFG_INSTANCE_OBJECT } entity_type_t; enum import_state { @@ -120,6 +123,40 @@ typedef enum svccfg_op { SVCCFG_OP_RESTORE } svccfg_op_t; +/* + * Return values for functions that validate an entity against the templates. + */ +typedef enum tmpl_validate_status { + TVS_SUCCESS = 0, + /* + * Either conversion of ASTRING property value to a number failed, + * or base 32 decoding of a property value failed. + */ + TVS_BAD_CONVERSION, + /* Template is defective. */ + TVS_BAD_TEMPLATE, + /* Template type spec is invalid. */ + TVS_INVALID_TYPE_SPECIFICATION, + /* Property group is missing a type specification. */ + TVS_MISSING_PG_TYPE, + /* Template with required == true is missing type specification. */ + TVS_MISSING_TYPE_SPECIFICATION, + /* No match was found for specified item. */ + TVS_NOMATCH, + /* Validation error occurred */ + TVS_VALIDATION, + /* Validation error that should not inhibit import. */ + TVS_WARN, + /* Could not validate because of fatal errors. */ + TVS_FATAL = -1 +} tmpl_validate_status_t; + +/* + * The composed_pg structure is used for templates validation. It is + * defined in svccfg_tmpl.c + */ +typedef struct composed_pg composed_pg_t; + typedef struct entity { uu_list_node_t sc_node; entity_type_t sc_etype; @@ -138,21 +175,28 @@ typedef struct entity { uu_list_t *sc_service_instances; service_type_t sc_service_type; uint_t sc_service_version; - - struct entity *sc_service_template; + /* Following used by template validation */ + struct entity *sc_restarter; + struct entity *sc_global; } sc_service; struct { - uint_t sc_instance_dummy; + uu_avl_t *sc_composed; + /* Following used by template validation */ + struct entity *sc_instance_restarter; } sc_instance; - struct { - uint_t sc_template_dummy; - } sc_template; } sc_u; } entity_t; +/* + * sc_pgroup_composed is only used for templates validation of properties. + * It is created in build_composed_property_groups() and destroyed in + * composed_pg_destroy(). It will only be set for property groups that are + * part of an instance -- not for service property groups. + */ typedef struct pgroup { uu_list_node_t sc_node; uu_list_t *sc_pgroup_props; + composed_pg_t *sc_pgroup_composed; /* Composed properties */ const char *sc_pgroup_name; const char *sc_pgroup_type; @@ -168,6 +212,7 @@ typedef struct pgroup { typedef struct property { uu_list_node_t sc_node; + uu_avl_node_t sc_composed_node; /* Composed props linkage */ uu_list_t *sc_property_values; char *sc_property_name; @@ -205,6 +250,11 @@ typedef struct scf_callback { int sc_err; } scf_callback_t; +/* + * Collection of template validation errors. + */ +typedef struct tmpl_errors tmpl_errors_t; + #ifndef NDEBUG #define bad_error(func, err) { \ (void) fprintf(stderr, "%s:%d: %s() failed with unexpected " \ @@ -300,6 +350,8 @@ pgroup_t *internal_pgroup_find(entity_t *, const char *, const char *); pgroup_t *internal_dependent_find(entity_t *, const char *); pgroup_t *internal_pgroup_find_or_create(entity_t *, const char *, const char *); +pgroup_t *internal_pgroup_create_strict(entity_t *, const char *, + const char *); property_t *internal_property_new(void); void internal_property_free(property_t *); property_t *internal_property_find(pgroup_t *, const char *); @@ -309,12 +361,15 @@ value_t *internal_value_new(void); int internal_attach_service(bundle_t *, entity_t *); int internal_attach_entity(entity_t *, entity_t *); int internal_attach_pgroup(entity_t *, pgroup_t *); +void internal_detach_pgroup(entity_t *, pgroup_t *); int internal_attach_dependent(entity_t *, pgroup_t *); int internal_attach_property(pgroup_t *, property_t *); +void internal_detach_property(pgroup_t *, property_t *); void internal_attach_value(property_t *, value_t *); int load_init(void); void load_fini(void); +int load_instance(const char *, const char *, entity_t **); int load_pg_attrs(const scf_propertygroup_t *, pgroup_t **); int load_pg(const scf_propertygroup_t *, pgroup_t **, const char *, const char *); @@ -340,6 +395,7 @@ void lscf_delhash(char *, int); void lscf_listprop(const char *); void lscf_addprop(char *, const char *, const uu_list_t *); void lscf_delprop(char *); +int lscf_describe(uu_list_t *, int); void lscf_listsnap(); void lscf_selectsnap(const char *); void lscf_revert(const char *); @@ -366,6 +422,13 @@ int engine_cmd_getc(engine_state_t *); int engine_cmd_ungetc(engine_state_t *, char); void engine_cmd_nputs(engine_state_t *, char *, size_t); +void tmpl_errors_destroy(tmpl_errors_t *); +void tmpl_errors_print(FILE *, tmpl_errors_t *, const char *); +void tmpl_init(void); +void tmpl_property_fini(property_t *); +void tmpl_property_init(property_t *); +tmpl_validate_status_t tmpl_validate_bundle(bundle_t *, tmpl_errors_t **); + #ifdef __cplusplus } #endif diff --git a/usr/src/cmd/svc/svccfg/svccfg.l b/usr/src/cmd/svc/svccfg/svccfg.l index 4bf42e18b3..96cf8fd9e9 100644 --- a/usr/src/cmd/svc/svccfg/svccfg.l +++ b/usr/src/cmd/svc/svccfg/svccfg.l @@ -104,6 +104,7 @@ extern int yyerror(const char *); <INITIAL>setprop { BEGIN WORD; return (SCC_SETPROP); } <INITIAL>delprop { BEGIN WORD; return (SCC_DELPROP); } <INITIAL>editprop { BEGIN WORD; return (SCC_EDITPROP); } +<INITIAL>describe { BEGIN WORD; return (SCC_DESCRIBE); } <INITIAL>addpropvalue { BEGIN WORD; return (SCC_ADDPROPVALUE); } <INITIAL>delpropvalue { BEGIN WORD; return (SCC_DELPROPVALUE); } <INITIAL>setenv { BEGIN WORD; return (SCC_SETENV); } diff --git a/usr/src/cmd/svc/svccfg/svccfg.y b/usr/src/cmd/svc/svccfg/svccfg.y index 2cc0b04ab7..ad16849bc7 100644 --- a/usr/src/cmd/svc/svccfg/svccfg.y +++ b/usr/src/cmd/svc/svccfg/svccfg.y @@ -47,6 +47,7 @@ uu_list_pool_t *string_pool; %token SCC_LIST SCC_ADD SCC_DELETE SCC_SELECT SCC_UNSELECT %token SCC_LISTPG SCC_ADDPG SCC_DELPG SCC_DELHASH %token SCC_LISTPROP SCC_SETPROP SCC_DELPROP SCC_EDITPROP +%token SCC_DESCRIBE %token SCC_ADDPROPVALUE SCC_DELPROPVALUE SCC_SETENV SCC_UNSETENV %token SCC_LISTSNAP SCC_SELECTSNAP SCC_REVERT SCC_REFRESH %token SCS_REDIRECT SCS_NEWLINE SCS_EQUALS SCS_LPAREN SCS_RPAREN @@ -96,6 +97,7 @@ command : terminator | setprop_cmd | delprop_cmd | editprop_cmd + | describe_cmd | addpropvalue_cmd | delpropvalue_cmd | setenv_cmd @@ -130,11 +132,10 @@ unknown_cmd : SCV_WORD terminator validate_cmd : SCC_VALIDATE SCV_WORD terminator { - bundle_t *b = internal_bundle_new(); - lxml_get_bundle_file(b, $2, SVCCFG_OP_IMPORT); - (void) internal_bundle_free(b); + lscf_validate($2); free($2); } + | SCC_VALIDATE terminator { lscf_validate_fmri(NULL); } | SCC_VALIDATE error terminator { synerr(SCC_VALIDATE); return(0); } import_cmd : SCC_IMPORT string_list terminator @@ -410,6 +411,26 @@ delprop_cmd : SCC_DELPROP SCV_WORD terminator editprop_cmd : SCC_EDITPROP terminator { lscf_editprop(); } | SCC_EDITPROP error terminator { synerr(SCC_EDITPROP); return(0); } +describe_cmd : SCC_DESCRIBE string_list terminator + { + string_list_t *slp; + void *cookie = NULL; + + if (lscf_describe($2, 1) == -2) { + synerr(SCC_DESCRIBE); + return(0); + } + + while ((slp = uu_list_teardown($2, &cookie)) != NULL) { + free(slp->str); + free(slp); + } + + uu_list_destroy($2); + } + | SCC_DESCRIBE terminator { lscf_describe(NULL, 0); } + | SCC_DESCRIBE error terminator { synerr(SCC_DESCRIBE); return(0); } + addpropvalue_cmd : SCC_ADDPROPVALUE SCV_WORD string terminator { lscf_addpropvalue($2, NULL, $3); @@ -559,3 +580,4 @@ command_token : SCC_VALIDATE { $$ = SCC_VALIDATE; } | SCC_SELECTSNAP { $$ = SCC_SELECTSNAP; } | SCC_REVERT { $$ = SCC_REVERT; } | SCC_REFRESH { $$ = SCC_REFRESH; } + | SCC_DESCRIBE { $$ = SCC_DESCRIBE; } diff --git a/usr/src/cmd/svc/svccfg/svccfg_engine.c b/usr/src/cmd/svc/svccfg/svccfg_engine.c index e610814274..2a907fd7a8 100644 --- a/usr/src/cmd/svc/svccfg/svccfg_engine.c +++ b/usr/src/cmd/svc/svccfg/svccfg_engine.c @@ -220,6 +220,7 @@ static struct cmd_info { { "setprop", CS_SVC | CS_INST, NULL }, { "delprop", CS_SVC | CS_INST, NULL }, { "editprop", CS_SVC | CS_INST, NULL }, + { "describe", CS_SVC | CS_INST | CS_SNAP, NULL }, { "listsnap", CS_INST | CS_SNAP, NULL }, { "selectsnap", CS_INST | CS_SNAP, NULL }, { "revert", CS_INST | CS_SNAP, NULL }, @@ -511,8 +512,10 @@ engine_import(uu_list_t *args) uchar_t hash[MHASH_SIZE]; char **argv; string_list_t *slp; - boolean_t verify = B_FALSE; + boolean_t validate = B_FALSE; + tmpl_validate_status_t vr; uint_t flags = SCI_GENERALLAST; + tmpl_errors_t *errs; argc = uu_list_numnodes(args); if (argc < 1) @@ -542,7 +545,7 @@ engine_import(uu_list_t *args) break; case 'V': - verify = B_TRUE; + validate = B_TRUE; break; case '?': @@ -563,11 +566,20 @@ engine_import(uu_list_t *args) file = argv[optind]; free(argv); + /* If we're in interactive mode, force strict validation. */ + if (est->sc_cmd_flags & SC_CMD_IACTIVE) + validate = B_TRUE; + lscf_prep_hndl(); ret = mhash_test_file(g_hndl, file, 0, &pname, hash); - if (ret != MHASH_NEWFILE) + if (ret != MHASH_NEWFILE) { + if (ret == MHASH_FAILURE) + semerr(gettext("Could not hash file %s\n"), file); + else if (g_verbose && ret == MHASH_RECONCILED) + warn(gettext("No changes were necessary.\n")); return (ret); + } /* Load */ b = internal_bundle_new(); @@ -577,9 +589,28 @@ engine_import(uu_list_t *args) return (-1); } + /* Validate */ + if ((vr = tmpl_validate_bundle(b, &errs)) != TVS_SUCCESS) { + char *prefix; + + if ((validate == 0) || (vr == TVS_WARN)) { + prefix = gettext("Warning: "); + } else { + prefix = ""; + } + tmpl_errors_print(stderr, errs, prefix); + if (validate && (vr != TVS_WARN)) { + tmpl_errors_destroy(errs); + semerr(gettext("Import failed.\n")); + return (-1); + } + } + tmpl_errors_destroy(errs); + /* Import */ if (lscf_bundle_import(b, file, flags) != 0) { internal_bundle_free(b); + semerr(gettext("Import failed.\n")); return (-1); } @@ -602,10 +633,6 @@ engine_import(uu_list_t *args) free(pname); } - /* Verify */ - if (verify) - warn(gettext("import -V not implemented.\n")); - return (0); } @@ -731,7 +758,8 @@ help(int com) "Manifest commands: inventory validate import export " "archive\n" "Profile commands: apply extract\n" - "Entity commands: list select unselect add delete\n" + "Entity commands: list select unselect add delete " + "describe\n" "Snapshot commands: listsnap selectsnap revert\n" "Instance commands: refresh\n" "Property group commands: listpg addpg delpg\n" diff --git a/usr/src/cmd/svc/svccfg/svccfg_help.c b/usr/src/cmd/svc/svccfg/svccfg_help.c index 0a03767614..65b7c5872d 100644 --- a/usr/src/cmd/svc/svccfg/svccfg_help.c +++ b/usr/src/cmd/svc/svccfg/svccfg_help.c @@ -29,10 +29,14 @@ #include "svccfg_grammar.h" struct help_message help_messages[] = { - { SCC_VALIDATE, "validate file\n\n" - "Process a manifest file without changing the repository." + { SCC_VALIDATE, "validate [file | fmri]\n\n" +"Validate a manifest file without changing the repository.\n" +"Validate an instance FMRI against the template specifications." + }, + { SCC_IMPORT, "import [-V] file\n\n" +"Import a manifest into the repository. With -V force strict adherence\n" +"to the template specifications." }, - { SCC_IMPORT, "import file\n\nImport a manifest into the repository." }, { SCC_EXPORT, "export [-a] {service | pattern} [> file]\n\n" "Print a manifest for service to file, or standard output if not specified." }, @@ -98,6 +102,10 @@ struct help_message help_messages[] = { { SCC_EDITPROP, "editprop\n\n" "Invoke $EDITOR to edit the properties of the currently selected entity." }, + { SCC_DESCRIBE, "describe [-v] [-t] [propertygroup/property]\n\n" +"Describe the current properties. With -v, describe verbosely. With -t,\n" +"show only template data, not current properties." + }, { SCC_ADDPROPVALUE, "addpropvalue pg/name [type:] value\n\n" "Add the given value to the named property." }, diff --git a/usr/src/cmd/svc/svccfg/svccfg_internal.c b/usr/src/cmd/svc/svccfg/svccfg_internal.c index 74d206f67a..583d3d575f 100644 --- a/usr/src/cmd/svc/svccfg/svccfg_internal.c +++ b/usr/src/cmd/svc/svccfg/svccfg_internal.c @@ -24,8 +24,6 @@ * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" - #include <assert.h> #include <errno.h> #include <libintl.h> @@ -33,6 +31,8 @@ #include <stdarg.h> #include <stddef.h> #include <string.h> +#include <unistd.h> +#include <libscf_priv.h> #include "svccfg.h" @@ -248,6 +248,10 @@ internal_bundle_new() b->sc_bundle_type = SVCCFG_UNKNOWN_BUNDLE; b->sc_bundle_services = uu_list_create(entity_pool, b, 0); + if (b->sc_bundle_services == NULL) { + uu_die(gettext("Unable to create list for bundle services. " + "%s\n"), uu_strerror(uu_error())); + } return (b); } @@ -266,27 +270,50 @@ internal_bundle_free(bundle_t *b) } entity_t * -internal_service_new(const char *name) +internal_entity_new(entity_type_t entity) { - entity_t *s; + entity_t *e; - if ((s = uu_zalloc(sizeof (entity_t))) == NULL) + if ((e = uu_zalloc(sizeof (entity_t))) == NULL) uu_die(gettext("couldn't allocate memory")); - uu_list_node_init(s, &s->sc_node, entity_pool); + uu_list_node_init(e, &e->sc_node, entity_pool); + + e->sc_etype = entity; + e->sc_pgroups = uu_list_create(pgroup_pool, e, 0); + if (e->sc_pgroups == NULL) { + uu_die(gettext("Unable to create list for entity property " + "groups. %s\n"), uu_strerror(uu_error())); + } + + return (e); +} + +entity_t * +internal_service_new(const char *name) +{ + entity_t *s; + + s = internal_entity_new(SVCCFG_SERVICE_OBJECT); s->sc_name = name; s->sc_fmri = uu_msprintf("svc:/%s", name); if (s->sc_fmri == NULL) uu_die(gettext("couldn't allocate memory")); - s->sc_etype = SVCCFG_SERVICE_OBJECT; - s->sc_pgroups = uu_list_create(pgroup_pool, s, 0); s->sc_dependents = uu_list_create(pgroup_pool, s, 0); + if (s->sc_dependents == NULL) { + uu_die(gettext("Unable to create list for service dependents. " + "%s\n"), uu_strerror(uu_error())); + } s->sc_u.sc_service.sc_service_type = SVCCFG_UNKNOWN_SERVICE; s->sc_u.sc_service.sc_service_instances = uu_list_create(entity_pool, s, 0); + if (s->sc_u.sc_service.sc_service_instances == NULL) { + uu_die(gettext("Unable to create list for service instances. " + "%s\n"), uu_strerror(uu_error())); + } return (s); } @@ -298,6 +325,11 @@ internal_service_free(entity_t *s) pgroup_t *pg; void *cookie; + if (s->sc_u.sc_service.sc_restarter != NULL) + internal_instance_free(s->sc_u.sc_service.sc_restarter); + if (s->sc_u.sc_service.sc_global != NULL) + internal_instance_free(s->sc_u.sc_service.sc_global); + cookie = NULL; while ((pg = uu_list_teardown(s->sc_pgroups, &cookie)) != NULL) internal_pgroup_free(pg); @@ -310,6 +342,7 @@ internal_service_free(entity_t *s) while ((inst = uu_list_teardown(s->sc_u.sc_service.sc_service_instances, &cookie)) != NULL) internal_instance_free(inst); + uu_free((void *)s->sc_fmri); free(s); } @@ -319,16 +352,14 @@ internal_instance_new(const char *name) { entity_t *i; - if ((i = uu_zalloc(sizeof (entity_t))) == NULL) - uu_die(gettext("couldn't allocate memory")); - - uu_list_node_init(i, &i->sc_node, entity_pool); - + i = internal_entity_new(SVCCFG_INSTANCE_OBJECT); i->sc_name = name; /* Can't set i->sc_fmri until we're attached to a service. */ - i->sc_etype = SVCCFG_INSTANCE_OBJECT; - i->sc_pgroups = uu_list_create(pgroup_pool, i, 0); i->sc_dependents = uu_list_create(pgroup_pool, i, 0); + if (i->sc_dependents == NULL) { + uu_die(gettext("Unable to create list for instance " + "dependents. %s\n"), uu_strerror(uu_error())); + } return (i); } @@ -338,33 +369,22 @@ internal_instance_free(entity_t *i) { pgroup_t *pg; void *cookie = NULL; + entity_t *rs; + rs = i->sc_u.sc_instance.sc_instance_restarter; + if (rs != NULL) + internal_instance_free(rs); while ((pg = uu_list_teardown(i->sc_pgroups, &cookie)) != NULL) internal_pgroup_free(pg); cookie = NULL; while ((pg = uu_list_teardown(i->sc_dependents, &cookie)) != NULL) internal_pgroup_free(pg); + uu_free((void *)i->sc_fmri); free(i); } -entity_t * -internal_template_new() -{ - entity_t *t; - - if ((t = uu_zalloc(sizeof (entity_t))) == NULL) - uu_die(gettext("couldn't allocate memory")); - - uu_list_node_init(t, &t->sc_node, entity_pool); - - t->sc_etype = SVCCFG_TEMPLATE_OBJECT; - t->sc_pgroups = uu_list_create(pgroup_pool, t, 0); - - return (t); -} - pgroup_t * internal_pgroup_new() { @@ -376,6 +396,10 @@ internal_pgroup_new() uu_list_node_init(p, &p->sc_node, pgroup_pool); p->sc_pgroup_props = uu_list_create(property_pool, p, UU_LIST_SORTED); + if (p->sc_pgroup_props == NULL) { + uu_die(gettext("Unable to create list for properties. %s\n"), + uu_strerror(uu_error())); + } p->sc_pgroup_name = "<unset>"; p->sc_pgroup_type = "<unset>"; @@ -388,6 +412,12 @@ internal_pgroup_free(pgroup_t *pg) property_t *prop; void *cookie = NULL; + /* + * Templates validation code should clean up this reference when + * the validation is finished. + */ + assert(pg->sc_pgroup_composed == NULL); + while ((prop = uu_list_teardown(pg->sc_pgroup_props, &cookie)) != NULL) internal_property_free(prop); @@ -427,14 +457,20 @@ internal_pgroup_find(entity_t *e, const char *name, const char *type) return (find_pgroup(e->sc_pgroups, name, type)); } -pgroup_t * -internal_pgroup_find_or_create(entity_t *e, const char *name, const char *type) +static pgroup_t * +internal_pgroup_create_common(entity_t *e, const char *name, const char *type, + boolean_t unique) { pgroup_t *pg; pg = internal_pgroup_find(e, name, type); - if (pg != NULL) - return (pg); + if (pg != NULL) { + if (unique == B_TRUE) { + return (NULL); + } else { + return (pg); + } + } pg = internal_pgroup_new(); (void) internal_attach_pgroup(e, pg); @@ -448,6 +484,18 @@ internal_pgroup_find_or_create(entity_t *e, const char *name, const char *type) return (pg); } +pgroup_t * +internal_pgroup_find_or_create(entity_t *e, const char *name, const char *type) +{ + return (internal_pgroup_create_common(e, name, type, B_FALSE)); +} + +pgroup_t * +internal_pgroup_create_strict(entity_t *e, const char *name, const char *type) +{ + return (internal_pgroup_create_common(e, name, type, B_TRUE)); +} + property_t * internal_property_new() { @@ -459,8 +507,14 @@ internal_property_new() uu_list_node_init(p, &p->sc_node, property_pool); p->sc_property_values = uu_list_create(value_pool, p, 0); + if (p->sc_property_values == NULL) { + uu_die(gettext("Unable to create list for property values. " + "%s\n"), uu_strerror(uu_error())); + } p->sc_property_name = "<unset>"; + tmpl_property_init(p); + return (p); } @@ -470,6 +524,8 @@ internal_property_free(property_t *p) value_t *val; void *cookie = NULL; + tmpl_property_fini(p); + while ((val = uu_list_teardown(p->sc_property_values, &cookie)) != NULL) { if (val->sc_free != NULL) @@ -587,11 +643,6 @@ internal_attach_service(bundle_t *bndl, entity_t *svc) int internal_attach_entity(entity_t *svc, entity_t *ent) { - if (ent->sc_etype == SVCCFG_TEMPLATE_OBJECT) { - svc->sc_u.sc_service.sc_service_template = ent; - return (0); - } - if (svc->sc_etype != SVCCFG_SERVICE_OBJECT) uu_die(gettext("bad entity attach: %s is not a service\n"), svc->sc_name); @@ -628,6 +679,12 @@ internal_attach_pgroup(entity_t *ent, pgroup_t *pgrp) return (0); } +void +internal_detach_pgroup(entity_t *ent, pgroup_t *pgrp) +{ + uu_list_remove(ent->sc_pgroups, pgrp); +} + int internal_attach_dependent(entity_t *ent, pgroup_t *pg) { @@ -667,6 +724,12 @@ internal_attach_property(pgroup_t *pgrp, property_t *prop) } void +internal_detach_property(pgroup_t *pgrp, property_t *prop) +{ + uu_list_remove(pgrp->sc_pgroup_props, prop); +} + +void internal_attach_value(property_t *prop, value_t *val) { (void) uu_list_append(prop->sc_property_values, val); @@ -683,9 +746,11 @@ internal_attach_value(property_t *prop, value_t *val) static char *loadbuf = NULL; static size_t loadbuf_sz; +static scf_propertygroup_t *load_pgroup = NULL; static scf_property_t *load_prop = NULL; static scf_value_t *load_val = NULL; static scf_iter_t *load_propiter = NULL, *load_valiter = NULL; +static scf_iter_t *load_pgiter = NULL; /* * Initialize the global state for the load_*() routines. @@ -705,6 +770,8 @@ load_init(void) if ((load_prop = scf_property_create(g_hndl)) == NULL || (load_val = scf_value_create(g_hndl)) == NULL || + (load_pgroup = scf_pg_create(g_hndl)) == NULL || + (load_pgiter = scf_iter_create(g_hndl)) == NULL || (load_propiter = scf_iter_create(g_hndl)) == NULL || (load_valiter = scf_iter_create(g_hndl)) == NULL) { load_fini(); @@ -721,6 +788,10 @@ load_fini(void) load_propiter = NULL; scf_iter_destroy(load_valiter); load_valiter = NULL; + scf_iter_destroy(load_pgiter); + load_pgiter = NULL; + scf_pg_destroy(load_pgroup); + load_pgroup = NULL; scf_value_destroy(load_val); load_val = NULL; scf_property_destroy(load_prop); @@ -1091,6 +1162,158 @@ out: } /* + * Load the instance for fmri from the repository into memory. The + * property groups that define the instances pg_patterns and prop_patterns + * are also loaded. + * + * Returns 0 on success and non-zero on failure. + */ +int +load_instance(const char *fmri, const char *name, entity_t **inst_ptr) +{ + entity_t *e = NULL; + scf_instance_t *inst; + pgroup_t *ipg; + int rc; + char *type = NULL; + ssize_t tsize; + + assert(inst_ptr != NULL); + + if ((inst = scf_instance_create(g_hndl)) == NULL) { + switch (scf_error()) { + case SCF_ERROR_NO_MEMORY: + case SCF_ERROR_NO_RESOURCES: + rc = EAGAIN; + goto errout; + default: + bad_error("scf_instance_create", scf_error()); + } + } + if (scf_handle_decode_fmri(g_hndl, fmri, NULL, NULL, inst, NULL, NULL, + SCF_DECODE_FMRI_EXACT|SCF_DECODE_FMRI_REQUIRE_INSTANCE) != 0) { + switch (scf_error()) { + case SCF_ERROR_CONNECTION_BROKEN: + rc = ECONNABORTED; + goto errout; + case SCF_ERROR_DELETED: + case SCF_ERROR_NOT_FOUND: + rc = ENOENT; + goto errout; + default: + bad_error("scf_handle_decode_fmri", scf_error()); + } + } + if (scf_iter_instance_pgs_composed(load_pgiter, inst, NULL) != 0) { + switch (scf_error()) { + case SCF_ERROR_DELETED: + rc = ECANCELED; + goto errout; + case SCF_ERROR_CONNECTION_BROKEN: + rc = ECONNABORTED; + goto errout; + default: + bad_error("scf_iter_instance_pgs_composed", + scf_error()); + } + } + + tsize = scf_limit(SCF_LIMIT_MAX_PG_TYPE_LENGTH); + type = uu_zalloc(tsize); + if (type == NULL) { + rc = ENOMEM; + goto errout; + } + + /* + * Initialize our entity structure. + */ + e = internal_instance_new(name); + if (e == NULL) { + rc = ENOMEM; + goto errout; + } + e->sc_fmri = uu_strdup(fmri); + if (e->sc_fmri == NULL) { + rc = ENOMEM; + goto errout; + } + + /* + * Walk through the property group's of the instance and capture + * the property groups that are of type + * SCF_GROUP_TEMPLATE_PG_PATTERN and + * SCF_GROUP_TEMPLATE_PROP_PATTERN. In other words grab the + * pg_pattern and prop_pattern property groups. + */ + while ((rc = scf_iter_next_pg(load_pgiter, load_pgroup)) == 1) { + if (scf_pg_get_type(load_pgroup, type, tsize) <= 0) { + switch (scf_error()) { + case SCF_ERROR_DELETED: + rc = ENOENT; + break; + case SCF_ERROR_CONNECTION_BROKEN: + rc = ECONNABORTED; + break; + default: + bad_error("scf_pg_get_type", scf_error()); + } + goto errout; + } + if ((strcmp(type, SCF_GROUP_TEMPLATE_PG_PATTERN) != 0) && + (strcmp(type, SCF_GROUP_TEMPLATE_PROP_PATTERN) != 0)) { + continue; + } + if ((rc = load_pg(load_pgroup, &ipg, fmri, NULL)) != 0) { + switch (rc) { + case ECANCELED: + case ECONNABORTED: + case EACCES: + case ENOMEM: + break; + default: + bad_error("load_pg", rc); + } + goto errout; + } + if (internal_attach_pgroup(e, ipg) != 0) { + rc = EBADF; + goto errout; + } + } + if (rc == -1) { + /* Error in iteration. */ + switch (scf_error()) { + case SCF_ERROR_CONNECTION_BROKEN: + rc = ECONNABORTED; + break; + case SCF_ERROR_DELETED: + rc = ENOENT; + break; + case SCF_ERROR_NO_RESOURCES: + rc = EAGAIN; + break; + default: + bad_error("scf_iter_next_pg", scf_error()); + } + goto errout; + } + + *inst_ptr = e; + scf_instance_destroy(inst); + return (0); + +errout: + if (type != NULL) + uu_free(type); + if (inst != NULL) + scf_instance_destroy(inst); + if (e != NULL) + internal_instance_free(e); + return (rc); +} + +/* * These functions compare internal property groups and properties (pgroup_t * & property_t). They return 1 if the given structures are equal and * 0 otherwise. Some will report the differences between the two structures. diff --git a/usr/src/cmd/svc/svccfg/svccfg_libscf.c b/usr/src/cmd/svc/svccfg/svccfg_libscf.c index 9c133a37c4..797994faf7 100644 --- a/usr/src/cmd/svc/svccfg/svccfg_libscf.c +++ b/usr/src/cmd/svc/svccfg/svccfg_libscf.c @@ -32,12 +32,14 @@ #include <errno.h> #include <fcntl.h> #include <fnmatch.h> +#include <inttypes.h> #include <libintl.h> #include <libscf.h> #include <libscf_priv.h> #include <libtecla.h> #include <libuutil.h> #include <limits.h> +#include <locale.h> #include <stdarg.h> #include <string.h> #include <strings.h> @@ -63,6 +65,13 @@ #define HASH_PG_FLAGS 0 #define HASH_PROP "md5sum" +/* + * Indentation used in the output of the describe subcommand. + */ +#define TMPL_VALUE_INDENT " " +#define TMPL_INDENT " " +#define TMPL_INDENT_2X " " +#define TMPL_CHOICE_INDENT " " /* * These are the classes of elements which may appear as children of service @@ -128,7 +137,6 @@ const char * const snap_lastimport = "last-import"; const char * const snap_previous = "previous"; const char * const snap_running = "running"; - scf_handle_t *g_hndl = NULL; /* only valid after lscf_prep_hndl() */ ssize_t max_scf_fmri_len; @@ -361,6 +369,21 @@ entity_destroy(void *ent, int issvc) scf_instance_destroy(ent); } +static int +get_pg(const char *pg_name, scf_propertygroup_t *pg) +{ + int ret; + + if (cur_level != NULL) + ret = scf_snaplevel_get_pg(cur_level, pg_name, pg); + else if (cur_inst != NULL) + ret = scf_instance_get_pg(cur_inst, pg_name, pg); + else + ret = scf_service_get_pg(cur_svc, pg_name, pg); + + return (ret); +} + /* * Find a snaplevel in a snapshot. If get_svc is true, find the service * snaplevel. Otherwise find the instance snaplevel. @@ -5805,7 +5828,12 @@ lscf_service_import(void *v, void *pvt) } /* create temporary service */ - r = snprintf(imp_tsname, max_scf_name_len + 1, "TEMP/%s", s->sc_name); + /* + * the size of the buffer was reduced to max_scf_name_len to prevent + * hitting bug 6681151. After the bug fix, the size of the buffer + * should be restored to its original value (max_scf_name_len +1) + */ + r = snprintf(imp_tsname, max_scf_name_len, "TEMP/%s", s->sc_name); if (r < 0) bad_error("snprintf", errno); if (r > max_scf_name_len) { @@ -9419,6 +9447,152 @@ select_callback(void *unused, scf_walkinfo_t *wip) return (0); } +static int +validate_callback(void *fmri_p, scf_walkinfo_t *wip) +{ + char **fmri = fmri_p; + + *fmri = strdup(wip->fmri); + if (*fmri == NULL) + uu_die(gettext("Out of memory.\n")); + + return (0); +} + +/* + * validate [fmri] + * Perform the validation of an FMRI instance. + */ +void +lscf_validate_fmri(const char *fmri) +{ + int ret = 0; + size_t inst_sz; + char *inst_fmri = NULL; + scf_tmpl_errors_t *errs = NULL; + char *snapbuf = NULL; + + lscf_prep_hndl(); + + if (fmri == NULL) { + inst_sz = max_scf_fmri_len + 1; + inst_fmri = safe_malloc(inst_sz); + + if (cur_snap != NULL) { + snapbuf = safe_malloc(max_scf_name_len + 1); + if (scf_snapshot_get_name(cur_snap, snapbuf, + max_scf_name_len + 1) < 0) + scfdie(); + } + if (cur_inst == NULL) { + semerr(gettext("No instance selected\n")); + goto cleanup; + } else if (scf_instance_to_fmri(cur_inst, inst_fmri, + inst_sz) >= inst_sz) { + /* sanity check. Should never get here */ + uu_die(gettext("Unexpected error! file %s, line %d\n"), + __FILE__, __LINE__); + } + } else { + scf_error_t scf_err; + int err = 0; + + if ((scf_err = scf_walk_fmri(g_hndl, 1, (char **)&fmri, 0, + validate_callback, &inst_fmri, &err, semerr)) != 0) { + uu_warn("Failed to walk instances: %s\n", + scf_strerror(scf_err)); + goto cleanup; + } + if (err != 0) + /* error message displayed by scf_walk_fmri */ + goto cleanup; + } + + ret = scf_tmpl_validate_fmri(g_hndl, inst_fmri, snapbuf, &errs, + SCF_TMPL_VALIDATE_FLAG_CURRENT); + if (ret == -1) { + if (scf_error() == SCF_ERROR_TEMPLATE_INVALID) { + warn(gettext("Template data for %s is invalid. " + "Consider reverting to a previous snapshot or " + "restoring original configuration.\n"), inst_fmri); + } else { + uu_warn("%s: %s\n", + gettext("Error validating the instance"), + scf_strerror(scf_error())); + } + } else if (ret == 1 && errs != NULL) { + scf_tmpl_error_t *err = NULL; + char *msg; + size_t len = 256; /* initial error buffer size */ + int flag = (est->sc_cmd_flags & SC_CMD_IACTIVE) ? + SCF_TMPL_STRERROR_HUMAN : 0; + + msg = safe_malloc(len); + + while ((err = scf_tmpl_next_error(errs)) != NULL) { + int ret; + + if ((ret = scf_tmpl_strerror(err, msg, len, + flag)) >= len) { + len = ret + 1; + msg = realloc(msg, len); + if (msg == NULL) + uu_die(gettext( + "Out of memory.\n")); + (void) scf_tmpl_strerror(err, msg, len, + flag); + } + (void) fprintf(stderr, "%s\n", msg); + } + if (msg != NULL) + free(msg); + } + if (errs != NULL) + scf_tmpl_errors_destroy(errs); +cleanup: + free(inst_fmri); + free(snapbuf); +} + +static void +lscf_validate_file(const char *filename) +{ + tmpl_errors_t *errs; + + bundle_t *b = internal_bundle_new(); + if (lxml_get_bundle_file(b, filename, SVCCFG_OP_IMPORT) == 0) { + if (tmpl_validate_bundle(b, &errs) != TVS_SUCCESS) { + tmpl_errors_print(stderr, errs, ""); + semerr(gettext("Validation failed.\n")); + } + tmpl_errors_destroy(errs); + } + (void) internal_bundle_free(b); +} + +/* + * validate [fmri|file] + */ +void +lscf_validate(const char *arg) +{ + const char *str; + + if (strncmp(arg, SCF_FMRI_FILE_PREFIX, + sizeof (SCF_FMRI_FILE_PREFIX) - 1) == 0) { + str = arg + sizeof (SCF_FMRI_FILE_PREFIX) - 1; + lscf_validate_file(str); + } else if (strncmp(arg, SCF_FMRI_SVC_PREFIX, + sizeof (SCF_FMRI_SVC_PREFIX) - 1) == 0) { + str = arg + sizeof (SCF_FMRI_SVC_PREFIX) - 1; + lscf_validate_fmri(str); + } else if (access(arg, R_OK | F_OK) == 0) { + lscf_validate_file(arg); + } else { + lscf_validate_fmri(arg); + } +} + void lscf_select(const char *fmri) { @@ -10731,16 +10905,640 @@ list_prop_info(const scf_property_t *prop, const char *name, size_t len) uu_die(gettext("Could not output newline")); } +/* + * Outputs template property group info for the describe subcommand. + * If 'templates' == 2, verbose output is printed in the format expected + * for describe -v, which includes all templates fields. If pg is + * not NULL, we're describing the template data, not an existing property + * group, and formatting should be appropriate for describe -t. + */ +static void +list_pg_tmpl(scf_pg_tmpl_t *pgt, scf_propertygroup_t *pg, int templates) +{ + char *buf; + uint8_t required; + scf_property_t *stability_prop; + scf_value_t *stability_val; + + if (templates == 0) + return; + + if ((stability_prop = scf_property_create(g_hndl)) == NULL || + (stability_val = scf_value_create(g_hndl)) == NULL) + scfdie(); + + if (templates == 2 && pg != NULL) { + if (scf_pg_get_property(pg, SCF_PROPERTY_STABILITY, + stability_prop) == 0) { + if (prop_check_type(stability_prop, + SCF_TYPE_ASTRING) == 0 && + prop_get_val(stability_prop, stability_val) == 0) { + char *stability; + + stability = safe_malloc(max_scf_value_len + 1); + + if (scf_value_get_astring(stability_val, + stability, max_scf_value_len + 1) == -1 && + scf_error() != SCF_ERROR_NOT_FOUND) + scfdie(); + + safe_printf("%s%s: %s\n", TMPL_INDENT, + gettext("stability"), stability); + + free(stability); + } + } else if (scf_error() != SCF_ERROR_NOT_FOUND) + scfdie(); + } + + scf_property_destroy(stability_prop); + scf_value_destroy(stability_val); + + if (pgt == NULL) + return; + + if (pg == NULL || templates == 2) { + /* print type info only if scf_tmpl_pg_name succeeds */ + if (scf_tmpl_pg_name(pgt, &buf) != -1) { + if (pg != NULL) + safe_printf("%s", TMPL_INDENT); + safe_printf("%s: ", gettext("name")); + safe_printf("%s\n", buf); + free(buf); + } + + /* print type info only if scf_tmpl_pg_type succeeds */ + if (scf_tmpl_pg_type(pgt, &buf) != -1) { + if (pg != NULL) + safe_printf("%s", TMPL_INDENT); + safe_printf("%s: ", gettext("type")); + safe_printf("%s\n", buf); + free(buf); + } + } + + if (templates == 2 && scf_tmpl_pg_required(pgt, &required) == 0) + safe_printf("%s%s: %s\n", TMPL_INDENT, gettext("required"), + required ? "true" : "false"); + + if (templates == 2 && scf_tmpl_pg_target(pgt, &buf) > 0) { + safe_printf("%s%s: %s\n", TMPL_INDENT, gettext("target"), + buf); + free(buf); + } + + if (templates == 2 && scf_tmpl_pg_common_name(pgt, NULL, &buf) > 0) { + safe_printf("%s%s: %s\n", TMPL_INDENT, gettext("common name"), + buf); + free(buf); + } + + if (scf_tmpl_pg_description(pgt, NULL, &buf) > 0) { + if (templates == 2) + safe_printf("%s%s: %s\n", TMPL_INDENT, + gettext("description"), buf); + else + safe_printf("%s%s\n", TMPL_INDENT, buf); + free(buf); + } + +} + +/* + * With as_value set to true, indent as appropriate for the value level. + * If false, indent to appropriate level for inclusion in constraint + * or choice printout. + */ +static void +print_template_value_details(scf_prop_tmpl_t *prt, const char *val_buf, + int as_value) +{ + char *buf; + + if (scf_tmpl_value_common_name(prt, NULL, val_buf, &buf) > 0) { + if (as_value == 0) + safe_printf("%s", TMPL_CHOICE_INDENT); + else + safe_printf("%s", TMPL_INDENT); + safe_printf("%s: %s\n", gettext("value common name"), buf); + free(buf); + } + + if (scf_tmpl_value_description(prt, NULL, val_buf, &buf) > 0) { + if (as_value == 0) + safe_printf("%s", TMPL_CHOICE_INDENT); + else + safe_printf("%s", TMPL_INDENT); + safe_printf("%s: %s\n", gettext("value description"), buf); + free(buf); + } +} + +static void +print_template_value(scf_prop_tmpl_t *prt, const char *val_buf) +{ + safe_printf("%s%s: ", TMPL_VALUE_INDENT, gettext("value")); + /* This is to be human-readable, so don't use CHARS_TO_QUOTE */ + safe_printf("%s\n", val_buf); + + print_template_value_details(prt, val_buf, 1); +} + +static void +print_template_constraints(scf_prop_tmpl_t *prt, int verbose) +{ + int i, printed = 0; + scf_values_t values; + scf_count_ranges_t c_ranges; + scf_int_ranges_t i_ranges; + + printed = 0; + i = 0; + if (scf_tmpl_value_name_constraints(prt, &values) == 0) { + safe_printf("%s%s:\n", TMPL_VALUE_INDENT, + gettext("value constraints")); + printed++; + for (i = 0; i < values.value_count; ++i) { + safe_printf("%s%s: %s\n", TMPL_INDENT, + gettext("value name"), values.values_as_strings[i]); + if (verbose == 1) + print_template_value_details(prt, + values.values_as_strings[i], 0); + } + + scf_values_destroy(&values); + } + + if (scf_tmpl_value_count_range_constraints(prt, &c_ranges) == 0) { + if (printed++ == 0) + safe_printf("%s%s:\n", TMPL_VALUE_INDENT, + gettext("value constraints")); + for (i = 0; i < c_ranges.scr_num_ranges; ++i) { + safe_printf("%s%s: %llu to %llu\n", TMPL_INDENT, + gettext("range"), c_ranges.scr_min[i], + c_ranges.scr_max[i]); + } + scf_count_ranges_destroy(&c_ranges); + } else if (scf_error() == SCF_ERROR_CONSTRAINT_VIOLATED && + scf_tmpl_value_int_range_constraints(prt, &i_ranges) == 0) { + if (printed++ == 0) + safe_printf("%s%s:\n", TMPL_VALUE_INDENT, + gettext("value constraints")); + for (i = 0; i < i_ranges.sir_num_ranges; ++i) { + safe_printf("%s%s: %lld to %lld\n", TMPL_INDENT, + gettext("range"), i_ranges.sir_min[i], + i_ranges.sir_max[i]); + } + scf_int_ranges_destroy(&i_ranges); + } +} + +static void +print_template_choices(scf_prop_tmpl_t *prt, int verbose) +{ + int i = 0, printed = 0; + scf_values_t values; + scf_count_ranges_t c_ranges; + scf_int_ranges_t i_ranges; + + printed = 0; + if (scf_tmpl_value_name_choices(prt, &values) == 0) { + safe_printf("%s%s:\n", TMPL_VALUE_INDENT, + gettext("value constraints")); + printed++; + for (i = 0; i < values.value_count; i++) { + safe_printf("%s%s: %s\n", TMPL_INDENT, + gettext("value name"), values.values_as_strings[i]); + if (verbose == 1) + print_template_value_details(prt, + values.values_as_strings[i], 0); + } + + scf_values_destroy(&values); + } + + if (scf_tmpl_value_count_range_choices(prt, &c_ranges) == 0) { + for (i = 0; i < c_ranges.scr_num_ranges; ++i) { + if (printed++ == 0) + safe_printf("%s%s:\n", TMPL_VALUE_INDENT, + gettext("value choices")); + safe_printf("%s%s: %llu to %llu\n", TMPL_INDENT, + gettext("range"), c_ranges.scr_min[i], + c_ranges.scr_max[i]); + } + scf_count_ranges_destroy(&c_ranges); + } else if (scf_error() == SCF_ERROR_CONSTRAINT_VIOLATED && + scf_tmpl_value_int_range_choices(prt, &i_ranges) == 0) { + for (i = 0; i < i_ranges.sir_num_ranges; ++i) { + if (printed++ == 0) + safe_printf("%s%s:\n", TMPL_VALUE_INDENT, + gettext("value choices")); + safe_printf("%s%s: %lld to %lld\n", TMPL_INDENT, + gettext("range"), i_ranges.sir_min[i], + i_ranges.sir_max[i]); + } + scf_int_ranges_destroy(&i_ranges); + } +} + +static void +list_values_by_template(scf_prop_tmpl_t *prt) +{ + print_template_constraints(prt, 1); + print_template_choices(prt, 1); +} + +static void +list_values_tmpl(scf_prop_tmpl_t *prt, scf_property_t *prop) +{ + char *val_buf; + scf_iter_t *iter; + scf_value_t *val; + int ret; + + if ((iter = scf_iter_create(g_hndl)) == NULL || + (val = scf_value_create(g_hndl)) == NULL) + scfdie(); + + if (scf_iter_property_values(iter, prop) != SCF_SUCCESS) + scfdie(); + + val_buf = safe_malloc(max_scf_value_len + 1); + + while ((ret = scf_iter_next_value(iter, val)) == 1) { + if (scf_value_get_as_string(val, val_buf, + max_scf_value_len + 1) < 0) + scfdie(); + + print_template_value(prt, val_buf); + } + if (ret != 0 && scf_error() != SCF_ERROR_PERMISSION_DENIED) + scfdie(); + free(val_buf); + + print_template_constraints(prt, 0); + print_template_choices(prt, 0); + +} + +/* + * Outputs property info for the describe subcommand + * Verbose output if templates == 2, -v option of svccfg describe + * Displays template data if prop is not NULL, -t option of svccfg describe + */ +static void +list_prop_tmpl(scf_prop_tmpl_t *prt, scf_property_t *prop, int templates) +{ + char *buf; + uint8_t u_buf; + int i; + uint64_t min, max; + scf_values_t values; + + if (prt == NULL || templates == 0) + return; + + if (prop == NULL) { + safe_printf("%s%s: ", TMPL_VALUE_INDENT, gettext("name")); + if (scf_tmpl_prop_name(prt, &buf) > 0) { + safe_printf("%s\n", buf); + free(buf); + } else + safe_printf("(%s)\n", gettext("any")); + } + + if (prop == NULL || templates == 2) { + if (prop != NULL) + safe_printf("%s", TMPL_INDENT); + else + safe_printf("%s", TMPL_VALUE_INDENT); + safe_printf("%s: ", gettext("type")); + if ((buf = _scf_read_tmpl_prop_type_as_string(prt)) != NULL) { + safe_printf("%s\n", buf); + free(buf); + } else + safe_printf("(%s)\n", gettext("any")); + } + + if (templates == 2 && scf_tmpl_prop_required(prt, &u_buf) == 0) + safe_printf("%s%s: %s\n", TMPL_INDENT, gettext("required"), + u_buf ? "true" : "false"); + + if (templates == 2 && scf_tmpl_prop_common_name(prt, NULL, &buf) > 0) { + safe_printf("%s%s: %s\n", TMPL_INDENT, gettext("common name"), + buf); + free(buf); + } + + if (templates == 2 && scf_tmpl_prop_units(prt, NULL, &buf) > 0) { + safe_printf("%s%s: %s\n", TMPL_INDENT, gettext("units"), + buf); + free(buf); + } + + if (scf_tmpl_prop_description(prt, NULL, &buf) > 0) { + safe_printf("%s%s\n", TMPL_INDENT, buf); + free(buf); + } + + if (templates == 2 && scf_tmpl_prop_visibility(prt, &u_buf) == 0) + safe_printf("%s%s: %s\n", TMPL_INDENT, gettext("visibility"), + scf_tmpl_visibility_to_string(u_buf)); + + if (templates == 2 && scf_tmpl_prop_cardinality(prt, &min, &max) == 0) { + safe_printf("%s%s: %" PRIu64 "\n", TMPL_INDENT, + gettext("minimum number of values"), min); + if (max == ULLONG_MAX) { + safe_printf("%s%s: %s\n", TMPL_INDENT, + gettext("maximum number of values"), + gettext("unlimited")); + } else { + safe_printf("%s%s: %" PRIu64 "\n", TMPL_INDENT, + gettext("maximum number of values"), max); + } + } + + if (templates == 2 && scf_tmpl_prop_internal_seps(prt, &values) == 0) { + for (i = 0; i < values.value_count; i++) { + if (i == 0) { + safe_printf("%s%s:", TMPL_INDENT, + gettext("internal separators")); + } + safe_printf(" \"%s\"", values.values_as_strings[i]); + } + safe_printf("\n"); + } + + if (templates != 2) + return; + + if (prop != NULL) + list_values_tmpl(prt, prop); + else + list_values_by_template(prt); +} + +static char * +read_astring(scf_propertygroup_t *pg, const char *prop_name) +{ + char *rv; + + rv = _scf_read_single_astring_from_pg(pg, prop_name); + if (rv == NULL) { + switch (scf_error()) { + case SCF_ERROR_NOT_FOUND: + break; + default: + scfdie(); + } + } + return (rv); +} + static void -listprop(const char *pattern, int only_pgs) +display_documentation(scf_iter_t *iter, scf_propertygroup_t *pg) +{ + size_t doc_len; + size_t man_len; + char *pg_name; + char *text = NULL; + int rv; + + doc_len = strlen(SCF_PG_TM_DOC_PREFIX); + man_len = strlen(SCF_PG_TM_MAN_PREFIX); + pg_name = safe_malloc(max_scf_name_len + 1); + while ((rv = scf_iter_next_pg(iter, pg)) == 1) { + if (scf_pg_get_name(pg, pg_name, max_scf_name_len + 1) == -1) { + scfdie(); + } + if (strncmp(pg_name, SCF_PG_TM_DOC_PREFIX, doc_len) == 0) { + /* Display doc_link and and uri */ + safe_printf("%s%s:\n", TMPL_INDENT, + gettext("doc_link")); + text = read_astring(pg, SCF_PROPERTY_TM_NAME); + if (text != NULL) { + safe_printf("%s%s%s: %s\n", TMPL_INDENT, + TMPL_INDENT, gettext("name"), text); + uu_free(text); + } + text = read_astring(pg, SCF_PROPERTY_TM_URI); + if (text != NULL) { + safe_printf("%s%s: %s\n", TMPL_INDENT_2X, + gettext("uri"), text); + uu_free(text); + } + } else if (strncmp(pg_name, SCF_PG_TM_MAN_PREFIX, + man_len) == 0) { + /* Display manpage title, section and path */ + safe_printf("%s%s:\n", TMPL_INDENT, + gettext("manpage")); + text = read_astring(pg, SCF_PROPERTY_TM_TITLE); + if (text != NULL) { + safe_printf("%s%s%s: %s\n", TMPL_INDENT, + TMPL_INDENT, gettext("title"), text); + uu_free(text); + } + text = read_astring(pg, SCF_PROPERTY_TM_SECTION); + if (text != NULL) { + safe_printf("%s%s%s: %s\n", TMPL_INDENT, + TMPL_INDENT, gettext("section"), text); + uu_free(text); + } + text = read_astring(pg, SCF_PROPERTY_TM_MANPATH); + if (text != NULL) { + safe_printf("%s%s%s: %s\n", TMPL_INDENT, + TMPL_INDENT, gettext("manpath"), text); + uu_free(text); + } + } + } + if (rv == -1) + scfdie(); + +done: + free(pg_name); +} + +static void +list_entity_tmpl(int templates) +{ + char *common_name = NULL; + char *description = NULL; + char *locale = NULL; + scf_iter_t *iter; + scf_propertygroup_t *pg; + scf_property_t *prop; + int r; + scf_value_t *val; + + if ((pg = scf_pg_create(g_hndl)) == NULL || + (prop = scf_property_create(g_hndl)) == NULL || + (val = scf_value_create(g_hndl)) == NULL || + (iter = scf_iter_create(g_hndl)) == NULL) + scfdie(); + + locale = setlocale(LC_MESSAGES, NULL); + + if (get_pg(SCF_PG_TM_COMMON_NAME, pg) == 0) { + common_name = safe_malloc(max_scf_value_len + 1); + + /* Try both the current locale and the "C" locale. */ + if (scf_pg_get_property(pg, locale, prop) == 0 || + (scf_error() == SCF_ERROR_NOT_FOUND && + scf_pg_get_property(pg, "C", prop) == 0)) { + if (prop_get_val(prop, val) == 0 && + scf_value_get_ustring(val, common_name, + max_scf_value_len + 1) != -1) { + safe_printf("%s%s: %s\n", TMPL_INDENT, + gettext("common name"), common_name); + } + } + } + + /* + * Do description, manpages, and doc links if templates == 2. + */ + if (templates == 2) { + /* Get the description. */ + if (get_pg(SCF_PG_TM_DESCRIPTION, pg) == 0) { + description = safe_malloc(max_scf_value_len + 1); + + /* Try both the current locale and the "C" locale. */ + if (scf_pg_get_property(pg, locale, prop) == 0 || + (scf_error() == SCF_ERROR_NOT_FOUND && + scf_pg_get_property(pg, "C", prop) == 0)) { + if (prop_get_val(prop, val) == 0 && + scf_value_get_ustring(val, description, + max_scf_value_len + 1) != -1) { + safe_printf("%s%s: %s\n", TMPL_INDENT, + gettext("description"), + description); + } + } + } + + /* Process doc_link & manpage elements. */ + if (cur_level != NULL) { + r = scf_iter_snaplevel_pgs_typed(iter, cur_level, + SCF_GROUP_TEMPLATE); + } else if (cur_inst != NULL) { + r = scf_iter_instance_pgs_typed(iter, cur_inst, + SCF_GROUP_TEMPLATE); + } else { + r = scf_iter_service_pgs_typed(iter, cur_svc, + SCF_GROUP_TEMPLATE); + } + if (r == 0) { + display_documentation(iter, pg); + } + } + + free(common_name); + free(description); + scf_pg_destroy(pg); + scf_property_destroy(prop); + scf_value_destroy(val); + scf_iter_destroy(iter); +} + +static void +listtmpl(const char *pattern, int templates) +{ + scf_pg_tmpl_t *pgt; + scf_prop_tmpl_t *prt; + char *snapbuf = NULL; + char *fmribuf; + char *pg_name = NULL, *prop_name = NULL; + ssize_t prop_name_size; + char *qual_prop_name; + char *search_name; + int listed = 0; + + if ((pgt = scf_tmpl_pg_create(g_hndl)) == NULL || + (prt = scf_tmpl_prop_create(g_hndl)) == NULL) + scfdie(); + + fmribuf = safe_malloc(max_scf_name_len + 1); + qual_prop_name = safe_malloc(max_scf_name_len + 1); + + if (cur_snap != NULL) { + snapbuf = safe_malloc(max_scf_name_len + 1); + if (scf_snapshot_get_name(cur_snap, snapbuf, + max_scf_name_len + 1) < 0) + scfdie(); + } + + if (cur_inst != NULL) { + if (scf_instance_to_fmri(cur_inst, fmribuf, + max_scf_name_len + 1) < 0) + scfdie(); + } else if (cur_svc != NULL) { + if (scf_service_to_fmri(cur_svc, fmribuf, + max_scf_name_len + 1) < 0) + scfdie(); + } else + abort(); + + /* If pattern is specified, we want to list only those items. */ + while (scf_tmpl_iter_pgs(pgt, fmribuf, snapbuf, NULL, NULL) == 1) { + listed = 0; + if (pattern == NULL || (scf_tmpl_pg_name(pgt, &pg_name) > 0 && + fnmatch(pattern, pg_name, 0) == 0)) { + list_pg_tmpl(pgt, NULL, templates); + listed++; + } + + scf_tmpl_prop_reset(prt); + + while (scf_tmpl_iter_props(pgt, prt, NULL) == 0) { + search_name = NULL; + prop_name_size = scf_tmpl_prop_name(prt, &prop_name); + if ((prop_name_size > 0) && (pg_name != NULL)) { + if (snprintf(qual_prop_name, + max_scf_name_len + 1, "%s/%s", + pg_name, prop_name) >= + max_scf_name_len + 1) { + prop_name_size = -1; + } else { + search_name = qual_prop_name; + } + } + if (listed > 0 || pattern == NULL || + (prop_name_size > 0 && + fnmatch(pattern, search_name, + FNM_PATHNAME) == 0)) + list_prop_tmpl(prt, NULL, templates); + if (prop_name != NULL) { + free(prop_name); + prop_name = NULL; + } + } + if (pg_name != NULL) { + free(pg_name); + pg_name = NULL; + } + } + + scf_tmpl_prop_destroy(prt); + scf_tmpl_pg_destroy(pgt); + free(snapbuf); + free(fmribuf); + free(qual_prop_name); +} + +static void +listprop(const char *pattern, int only_pgs, int templates) { scf_propertygroup_t *pg; scf_property_t *prop; scf_iter_t *iter, *piter; char *pgnbuf, *prnbuf, *ppnbuf; + scf_pg_tmpl_t *pgt, *pgtp; + scf_prop_tmpl_t *prt; void **objects; char **names; + void **tmpls; int allocd, i; int ret; @@ -10755,7 +11553,9 @@ listprop(const char *pattern, int only_pgs) if ((pg = scf_pg_create(g_hndl)) == NULL || (prop = scf_property_create(g_hndl)) == NULL || (iter = scf_iter_create(g_hndl)) == NULL || - (piter = scf_iter_create(g_hndl)) == NULL) + (piter = scf_iter_create(g_hndl)) == NULL || + (prt = scf_tmpl_prop_create(g_hndl)) == NULL || + (pgt = scf_tmpl_pg_create(g_hndl)) == NULL) scfdie(); prnbuf = safe_malloc(max_scf_name_len + 1); @@ -10767,16 +11567,15 @@ listprop(const char *pattern, int only_pgs) else ret = scf_iter_service_pgs(iter, cur_svc); if (ret != 0) { - if (scf_error() == SCF_ERROR_DELETED) - scfdie(); return; } /* * We want to only list items which match pattern, and we want the * second column to line up, so during the first pass we'll save - * matching items & their names in objects and names, computing the - * maximum name length as we go, and then we'll print them out. + * matching items, their names, and their templates in objects, + * names, and tmpls, computing the maximum name length as we go, + * and then we'll print them out. * * Note: We always keep an extra slot available so the array can be * NULL-terminated. @@ -10785,9 +11584,12 @@ listprop(const char *pattern, int only_pgs) allocd = 1; objects = safe_malloc(sizeof (*objects)); names = safe_malloc(sizeof (*names)); + tmpls = safe_malloc(sizeof (*tmpls)); while ((ret = scf_iter_next_pg(iter, pg)) == 1) { int new_pg = 0; + int print_props = 0; + pgtp = NULL; pgnlen = scf_pg_get_name(pg, NULL, 0); if (pgnlen < 0) @@ -10800,6 +11602,14 @@ listprop(const char *pattern, int only_pgs) scfdie(); assert(szret <= pgnlen); + if (scf_tmpl_get_by_pg(pg, pgt, NULL) == -1) { + if (scf_error() != SCF_ERROR_NOT_FOUND) + scfdie(); + pgtp = NULL; + } else { + pgtp = pgt; + } + if (pattern == NULL || fnmatch(pattern, pgnbuf, 0) == 0) { if (i+1 >= allocd) { @@ -10808,17 +11618,27 @@ listprop(const char *pattern, int only_pgs) sizeof (*objects) * allocd); names = realloc(names, sizeof (*names) * allocd); - if (objects == NULL || names == NULL) + tmpls = realloc(tmpls, + sizeof (*tmpls) * allocd); + if (objects == NULL || names == NULL || + tmpls == NULL) uu_die(gettext("Out of memory")); } objects[i] = pg; names[i] = pgnbuf; + + if (pgtp == NULL) + tmpls[i] = NULL; + else + tmpls[i] = pgt; + ++i; if (pgnlen > max_len) max_len = pgnlen; new_pg = 1; + print_props = 1; } if (only_pgs) { @@ -10826,6 +11646,9 @@ listprop(const char *pattern, int only_pgs) pg = scf_pg_create(g_hndl); if (pg == NULL) scfdie(); + pgt = scf_tmpl_pg_create(g_hndl); + if (pgt == NULL) + scfdie(); } else free(pgnbuf); @@ -10850,7 +11673,7 @@ listprop(const char *pattern, int only_pgs) prnbuf) < 0) uu_die("snprintf"); - if (pattern == NULL || + if (pattern == NULL || print_props == 1 || fnmatch(pattern, ppnbuf, 0) == 0) { if (i+1 >= allocd) { allocd *= 2; @@ -10858,19 +11681,38 @@ listprop(const char *pattern, int only_pgs) sizeof (*objects) * allocd); names = realloc(names, sizeof (*names) * allocd); - if (objects == NULL || names == NULL) - uu_die(gettext("Out of " - "memory")); + tmpls = realloc(tmpls, + sizeof (*tmpls) * allocd); + if (objects == NULL || names == NULL || + tmpls == NULL) + uu_die(gettext( + "Out of memory")); } objects[i] = prop; names[i] = ppnbuf; + + if (pgtp != NULL) { + if (scf_tmpl_get_by_prop(pgt, prnbuf, + prt, NULL) < 0) { + if (scf_error() != + SCF_ERROR_NOT_FOUND) + scfdie(); + tmpls[i] = NULL; + } else { + tmpls[i] = prt; + } + } else { + tmpls[i] = NULL; + } + ++i; if (prnlen > max_len) max_len = prnlen; prop = scf_property_create(g_hndl); + prt = scf_tmpl_prop_create(g_hndl); } else { free(ppnbuf); } @@ -10880,6 +11722,9 @@ listprop(const char *pattern, int only_pgs) pg = scf_pg_create(g_hndl); if (pg == NULL) scfdie(); + pgt = scf_tmpl_pg_create(g_hndl); + if (pgt == NULL) + scfdie(); } else free(pgnbuf); } @@ -10889,26 +11734,37 @@ listprop(const char *pattern, int only_pgs) objects[i] = NULL; scf_pg_destroy(pg); + scf_tmpl_pg_destroy(pgt); scf_property_destroy(prop); + scf_tmpl_prop_destroy(prt); for (i = 0; objects[i] != NULL; ++i) { if (strchr(names[i], '/') == NULL) { /* property group */ pg = (scf_propertygroup_t *)objects[i]; + pgt = (scf_pg_tmpl_t *)tmpls[i]; list_pg_info(pg, names[i], max_len); + list_pg_tmpl(pgt, pg, templates); free(names[i]); scf_pg_destroy(pg); + if (pgt != NULL) + scf_tmpl_pg_destroy(pgt); } else { /* property */ prop = (scf_property_t *)objects[i]; + prt = (scf_prop_tmpl_t *)tmpls[i]; list_prop_info(prop, names[i], max_len); + list_prop_tmpl(prt, prop, templates); free(names[i]); scf_property_destroy(prop); + if (prt != NULL) + scf_tmpl_prop_destroy(prt); } } free(names); free(objects); + free(tmpls); } void @@ -10916,7 +11772,7 @@ lscf_listpg(const char *pattern) { lscf_prep_hndl(); - listprop(pattern, 1); + listprop(pattern, 1, 0); } /* @@ -11061,7 +11917,7 @@ lscf_listprop(const char *pattern) { lscf_prep_hndl(); - listprop(pattern, 0); + listprop(pattern, 0, 0); } int @@ -12808,6 +13664,110 @@ lscf_refresh(void) free(fmribuf); } +/* + * describe [-v] [-t] [pg/prop] + */ +int +lscf_describe(uu_list_t *args, int hasargs) +{ + int ret = 0; + size_t i; + int argc; + char **argv = NULL; + string_list_t *slp; + int do_verbose = 0; + int do_templates = 0; + char *pattern = NULL; + + lscf_prep_hndl(); + + if (hasargs != 0) { + argc = uu_list_numnodes(args); + if (argc < 1) + goto usage; + + argv = calloc(argc + 1, sizeof (char *)); + if (argv == NULL) + uu_die(gettext("Out of memory.\n")); + + for (slp = uu_list_first(args), i = 0; + slp != NULL; + slp = uu_list_next(args, slp), ++i) + argv[i] = slp->str; + + argv[i] = NULL; + + /* + * We start optind = 0 because our list of arguments + * starts at argv[0] + */ + optind = 0; + opterr = 0; + for (;;) { + ret = getopt(argc, argv, "vt"); + if (ret == -1) + break; + + switch (ret) { + case 'v': + do_verbose = 1; + break; + + case 't': + do_templates = 1; + break; + + case '?': + goto usage; + + default: + bad_error("getopt", ret); + } + } + + pattern = argv[optind]; + } + + if (cur_inst == NULL && cur_svc == NULL) { + semerr(emsg_entity_not_selected); + ret = -1; + goto out; + } + + /* + * list_entity_tmpl(), listprop() and listtmpl() produce verbose + * output if their last parameter is set to 2. Less information is + * produced if the parameter is set to 1. + */ + if (pattern == NULL) { + if (do_verbose == 1) + list_entity_tmpl(2); + else + list_entity_tmpl(1); + } + + if (do_templates == 0) { + if (do_verbose == 1) + listprop(pattern, 0, 2); + else + listprop(pattern, 0, 1); + } else { + if (do_verbose == 1) + listtmpl(pattern, 2); + else + listtmpl(pattern, 1); + } + + ret = 0; +out: + if (argv != NULL) + free(argv); + return (ret); +usage: + ret = -2; + goto out; +} + #ifndef NATIVE_BUILD /* ARGSUSED */ CPL_MATCH_FN(complete_select) diff --git a/usr/src/cmd/svc/svccfg/svccfg_main.c b/usr/src/cmd/svc/svccfg/svccfg_main.c index 97893f86f4..aee16b5da2 100644 --- a/usr/src/cmd/svc/svccfg/svccfg_main.c +++ b/usr/src/cmd/svc/svccfg/svccfg_main.c @@ -19,12 +19,10 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" - /* * svccfg - modify service configuration repository */ @@ -180,6 +178,7 @@ initialize(int argc, char *argv[]) internal_init(); engine_init(); lscf_init(); /* must follow engine_init() */ + tmpl_init(); } int diff --git a/usr/src/cmd/svc/svccfg/svccfg_tmpl.c b/usr/src/cmd/svc/svccfg/svccfg_tmpl.c new file mode 100644 index 0000000000..215a9d79a3 --- /dev/null +++ b/usr/src/cmd/svc/svccfg/svccfg_tmpl.c @@ -0,0 +1,4036 @@ +/* + * 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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * This file provides the code that allows svccfg(1M) to validate a + * manifest against the template specifications. svccfg uses the + * validation facilities for the import and validate subcommands. + * + * There are three entry points -- tmpl_validate_bundle(), + * tmpl_errors_print() and tmpl_errors_destroy(). svccfg calls + * tmpl_validate_bundle() to validate a bundle. tmpl_validate_bundle() + * returns a pointer to a tmpl_errors_t. This is a pointer to information + * about any validation errors that were found. If an error was detected, + * svccfg calls tmpl_errors_print() to print the error information. Once + * the error information is printed, svccfg calls tmpl_errors_destroy() to + * free the memory associated with the tmpl_errors_t. + * + * libscf's scf_tmpl.c performs similar checks to the ones described in + * this paragraph. Any changes to the algorithms in this file should also + * be infcorporated into scf_tmpl.c. The reason that there are two bodies + * of code is that they work on different data structures. + * tmpl_validate_bundle() validates each instance of each service in the + * bundle. The following checks are performed on each instance: + * + * 1. Verify template consistency. + * A. No conflicting definitions of "pg_pattern" are allowed + * within a single instance. + * B. Templates at a narrow target (e.g. instance) which define + * property groups already templated at a broad target + * (e.g. delegate or all) are strongly discouraged. + * C. Developers may not define a template which specifies a + * single prop_pattern name with differing types on the same + * target entity. + * D. If a pg_pattern has a required attribute with a value of + * true, then its name and type attributes must be + * specified. + * E. If a prop_pattern has a required attribute with a value + * of true, then its type attribute must be specified. + * F. If a prop_pattern has an include_values element make sure + * that the appropriate constraints or values element has + * also been declared. + * 2. Validate that each property group in the instance is in + * conformance with the template specifications. + * A. Verify that the types of the PG and the pg_pattern are + * compatible. + * B. Verify properties of the PG against the prop_patterns in + * the template. + * o Verify property's type. + * o Verify cardinality. + * o Vefiy that property values satisfy the constraints + * imposed by the prop_pattern. + * C. Verify that required properties are present. + * 3. Verify that all required property groups are present in the + * insance. + * + * tmpl_validate_bundle() is called after svccfg has processed the manifest + * file. The manifest is represented in memory by a set of entity_t, + * pgroup_t, property_t and value_t structures. These structures are + * defined in svccfg.h. + * + * tmpl_validate_bundle() calls tmpl_validate_service() for each service in + * the bundle, and tmpl_validate_service() then calls + * tmpl_validate_instance() for each instance in the service. + * tmpl_validate_instance() is the function that does the real work of + * validation against the template specification. + * + * Subsystems: + * ========== + * + * General Templates: + * ----------------- + * In order to perform the validation specified by 1.B above, we need to + * load the templates specifications for the global service and the + * instance's restarter. This information is loaded from the repository + * and it is held in memory using the entity_t, pgroup_t, property_t and + * value_t hierarchy of structures. When a service is processed, + * load_general_templates() is called to load the information for the + * global service and restarter that is declared at the service level. The + * sc_service.sc_global and sc_service.sc_restarter members of the + * service's entity_t point to the information for the global and restarter + * services. + * + * The instance portion of a manifest can declare an instance specific + * restarter. If this is the case, load_instance_restarter() will load the + * information for that restarter, and it is saved in the + * sc_instance.sc_instance_restarter member of the entity_t that represents + * the instance. + * + * Composed Properties: + * ------------------- + * We need the ability to process the composed properties of an instance. + * That is to say if an instance defines a property, it overrides any + * definition in the service. Otherwise, the service's definition is + * inherited in the instance. + * + * In an entity_t, the sc_instance.sc_composed member points to a + * uu_avl tree of composed property groups (composed_pg_t) for the + * instance. The composed_pg_t has two members, cpg_instance_pg and + * cpg_service_pg, that point to the instance and service property group + * definitions respectively. Either of these may be NULL indicating that + * only an instance or service definition exists in the manifest. + * + * In the case where both the instance and the service define a property + * group, the properties must be composed. This is done by + * compose_props(). The compose_pg_t holds the composed properties in a + * uu_avl_tree at cpf_compose_props. This is a tree of property_t + * structures. If a property is defined in both the instance and service + * property group, the tree will hold the instance definition. If the + * property is defined at only one level, the tree will hold the property_t + * for that level. Thus, the tree is truly a set of composed properties of + * the property group. + * + * Property Group Iteration: + * ------------------------ + * A number of functions must iterate through an instance's property groups + * looking for the ones that define a pg_pattern or a prop_pattern. To be + * specific, the iteration starts with the composed view of the instance. + * It then proceeds through the restarter information and finally moves on + * to the global service. The pg_iter_t structure holds the information + * that is needed to implement this type of iteration. pg_iter_create() + * creates one of these iterators, and pg_iter_destroy() frees the memory + * associated with the pg_iter_t. next_pattern_pg(), is used to step + * through the iteration. + * + * Error Reporting: + * --------------- + * While performing the templates validation checks, svccfg collects + * information for all the errors that it finds. Because of this you will + * see many places in the code where a loop is not exited when an error is + * encountered. We note that fact that an error has occurred, but continue + * in the loop to see if there are more validation errors. The error code + * of the last error that is encountered is returned. This works, because + * the callers of tmpl_validate_bundle() only look to see whether or not + * the return code is TVS_SUCCESS. + * + * The information for reporting the errors is collected in a tmpl_errors_t + * structure, and tmpl_validate_bundle() returns the address of this + * structure. The caller of tmpl_validate_bundle() can then call + * tmpl_errors_print() to display the error information to the user. + * + * There are two categories of errors. Some errors are seen when + * processing the information in the manifest. This type of error is only + * seen by svccfg when it is importing or validating a manifest. The other + * type of error consists of template validation errors. These errors can + * be seen when processing a manifest or when performing a templates + * validation of the information associated with an FMRI in the the + * repository. tmpl_errors_add_im() is used to capture error information + * about the first type of error, and add_scf_error() is used to capture + * error information about the second type of error. + * + * The distinction is important when printing the error information. The + * fuctions for printing the first type of error reside in this file, since + * these errors will only be seen by the functions in this file. The + * functions for printing the template validation errors reside in libscf, + * because these errors are of a more general nature. + * + * Thus, tmpl_errors_t has two lists -- one for each type of error. + * te_list is a list of im_tmpl_error_t structures that represent the first + * type of error. te_scf is a list of tv_errors_t structures that hold + * information about general template validation errors. + * tmpl_errors_print() processes both lists to print information about all + * errors. In tmpl_errors_print() im_tmpl_error_print() is used to print + * the errors that are specific to this file. scf_tmpl_strerror() provides + * the errors messages for general templates errors. + * + * As was mentioned in the previous paragraph, im_tmpl_error_print() is + * responsible for printing the errors that are specific to this file. + * Based on the error code, it dispatches to one of + * im_perror_bad_conversion(), im_perror_bad_template(), + * im_perror_invalid_type(), im_perror_missing_pg_type() or + * im_perror_missing_type(). The rest of the im_perror_* functions provide + * services to these error specific functions by printing common + * information. + * + * im_perror_item() is the heart of this message printing subsystem. It is + * called directly or indirectly by all of the other im_perror_* functions. + * im_perror_item() prints a single item of error information. If svccfg + * is running in interactive mode, im_perror_item() prints each item on a + * single line, so that they are readable by a human. In non-interactive + * mode, all items are printed on a single line separated by semi-colons. + */ + +#include <assert.h> +#include <errno.h> +#include <inttypes.h> +#include <libintl.h> +#include <limits.h> +#include <libscf.h> +#include <libscf_priv.h> +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include "svccfg.h" + +/* + * Clear error_info_t structure. + */ +#define CLEAR_ERROR_INFO(ei) ((void) memset((ei), 0, sizeof (error_info_t))) + +/* + * Retrieve the property group pointer from the composed_pg structure. + */ +#define CPG2PG(cpg) (cpg->cpg_instance_pg ? cpg->cpg_instance_pg : \ + cpg->cpg_service_pg) + +/* + * Convert a pointer to an empty string into a NULL pointer. + */ +#define EMPTY_TO_NULL(p) (((p) && (*(p) == 0)) ? NULL : (p)) + +/* uu_avl and uu_list debugging bits. */ +#ifdef NDEBUG +#define TMPL_DEBUG_AVL_POOL UU_DEFAULT +#define TMPL_DEBUG_LIST UU_DEFAULT +#define TMPL_DEBUG_LIST_POOL UU_DEFAULT +#define TMPL_DEBUG_TREE UU_DEFAULT +#else +#define TMPL_DEBUG_AVL_POOL UU_AVL_POOL_DEBUG +#define TMPL_DEBUG_LIST UU_LIST_DEBUG +#define TMPL_DEBUG_LIST_POOL UU_LIST_POOL_DEBUG +#define TMPL_DEBUG_TREE UU_AVL_DEBUG +#endif /* NDEBUG */ + +/* + * Structures and enums that are used in producing error messages: + * + * error_info_t is used to pass information about an error to + * tmpl_errors_add_im() and add_scf_error(). tmpl_errors_add_im() collects + * the error information and stores it in an im_tmpl_error_t. The + * im_tmpl_error_t is linked into the tmpl_errors_t, so that the captured + * information can be used later when error messages are printed. + * + * tv_errors_t is used to keep track of error information for general + * template errors that are known by libscf. add_scf_error() captures the + * error information for use in this structure. + */ +/* + * enum to designate the type of data that is held in a err_info structure. + */ +typedef enum err_info_type { + EIT_NONE, /* No values in the structure */ + EIT_BAD_TEMPLATE, /* Reason that template is bad */ + EIT_CARDINALITY, /* Ranges for property cardinality */ + EIT_INCLUDE_VALUES, /* include_values type attribute */ + EIT_MISSING_PG, /* Name of missing pg */ + EIT_MISSING_PROP, /* Name of missing property */ + EIT_PATTERN_CONFLICT, /* Conflicting pattern definition */ + EIT_PROP_TYPE, /* Value with invalid type */ + EIT_RANGE /* Value that is out of range */ +} err_info_type_t; + +/* + * Structure to hold information that will be used in generating error + * messages. + */ +typedef struct error_info { + err_info_type_t ei_type; /* Type of information stored here */ + union { + /* EIT_BAD_TEMPLATE */ + struct { + const char *ei_reason; + } ei_bad_template; + /* EIT_CARDINALITY */ + struct { + uint64_t ei_min; + uint64_t ei_max; + uint64_t ei_count; /* Number of prop values */ + } ei_cardinality; + /* EIT_INCLUDE_VALUES */ + struct { + const char *ei_type; + } ei_inc_values; + /* EIT_MISSING_PG */ + struct { + const char *ei_pg_name; /* Name of missing pg */ + const char *ei_pg_type; /* Type of missing pg */ + } ei_missing_pg; + /* EIT_MISSING_PROP */ + struct { + const char *ei_prop_name; /* Name of prop */ + } ei_missing_prop; + /* EIT_PATTERN_CONFLICT */ + struct { + pgroup_t *ei_pattern; /* Conficting pattern */ + } ei_pattern_conflict; + /* EIT_PROP_TYPE */ + struct { + scf_type_t ei_actual; + scf_type_t ei_specified; + } ei_prop_type; + /* EIT_RANGE */ + struct { + scf_type_t ei_rtype; + int64_t ei_ivalue; + uint64_t ei_uvalue; + } ei_range; + } ei_u; +} error_info_t; + +/* + * Structure with information about a template violation. This structure + * is for use with in memory representations of the manifest and template. + * See scf_tmpl_error_t for use with repository representations. Some of + * the pointers may be NULL for some types of errors. + */ +typedef struct im_tmpl_error { + tmpl_validate_status_t ite_type; /* Type of error */ + entity_t *ite_entity; /* Instance or service */ + pgroup_t *ite_pg; /* Non-conforming prop. group */ + pgroup_t *ite_pg_pattern; /* Violated pg_pattern */ + property_t *ite_prop; /* Non-conforming property */ + pgroup_t *ite_prop_pattern; /* Violated prop_pattern */ + value_t *ite_value; /* Non-conforming value */ + error_info_t ite_einfo; /* Extra error information */ + uu_list_node_t ite_node; /* Node to link us in a list. */ +} im_tmpl_error_t; + +/* + * This structure holds the data that will be used by scf_tmpl_strerror() + * for printing template validation errors. + */ +typedef struct tv_errors { + scf_tmpl_errors_t *tve_errors; /* Errors for scf_tmpl_strerror() */ + uu_list_node_t tve_node; /* Linkage in a list. */ +} tv_errors_t; + +/* + * Structure to collect template validation errors. + */ +struct tmpl_errors { + uu_list_t *te_list; /* List of im_tmpl_error_t */ + im_tmpl_error_t *te_next; /* Next error to present */ + uu_list_t *te_scf; /* Errors for scf_tmpl_strerror() */ + tv_errors_t *te_cur_scf; /* Current member of te_scf */ +}; + +/* End of structures used in error processing. */ + +/* + * Property group types that are of interest to us. See pgroup_type(). + */ +typedef enum pg_type { + NORMAL_PG, + PG_PATTERN_PG, + PROP_PATTERN_PG +} pg_type_t; + +/* + * Structure to keep track of a set of ASTRING property values for a + * property. The consumer may wish to have the ASTRING property values + * converted to a numeric form which is the reason for the av_v union. + * This structure is returned by av_get_values() and is accessed by + * av_get_integer(), av_get_string() and av_get_unsigned(). + */ +typedef struct avalues { + uint_t av_count; /* Number of values */ + scf_type_t av_type; /* Type of value representation */ + union { + uint64_t *av_unsigned; /* Count & boolean values */ + int64_t *av_integer; /* Integer values */ + const char **av_string; /* String values */ + } av_v; /* Container for the values */ +} avalues_t; + +/* + * composed_pg_t contains the information that is needed to compose a + * property group. See the section on Composed Properties in the block + * comment at the beginning of this file. The composed_pg structures are + * linked into a uu_avl tree. The tree is at sc_instance.sc_composed in + * the entity_t. + */ +struct composed_pg { + /* + * Property group is uniquely identified by its name and type. + * These two elements point to the name and type in a pgroup_t + * (either service or instance), so they do not need to be + * allocated or freed. + */ + const char *cpg_name; + const char *cpg_type; + + /* References to the actual property group definitions. */ + pgroup_t *cpg_instance_pg; + pgroup_t *cpg_service_pg; + + /* Composed properties of the property group. */ + uu_avl_t *cpg_composed_props; + + uu_avl_node_t cpg_node; /* Linkage for AVL tree */ +}; + +/* + * Prefixes for standard property names. Used in + * include_values_support(). + */ +typedef struct prop_prefix { + const char *pp_prefix; + size_t pp_size; +} prop_prefix_t; + +/* + * Store a legal range for a property allowing for either signed or + * unsigned ranges. It is used to store a range from a template + * constraint element of a prop_pattern. The structure is returned by + * get_ranges() and is used by value_in_range() to validate the values of a + * property. + */ +typedef struct range { + union { + struct { + uint64_t rng_min; + uint64_t rng_max; + } rng_unsigned; + struct { + int64_t rng_min; + int64_t rng_max; + } rng_signed; + } rng_u; +} range_t; + +/* + * This enum defines the levels where templates can be defined. See the + * pg_iter structure below. + */ +typedef enum tmpl_level { + TL_NOLEVEL = 0, /* No level yet specified. */ + TL_INSTANCE, /* Instance templates. */ + TL_COMPOSED, /* Composed instance. */ + TL_SERVICE, /* Service wide templates. */ + TL_RESTARTER, /* Templates from restarter manifest. */ + TL_GLOBAL /* SMF wide templates. */ +} tmpl_level_t; + +/* + * pg_iter is a structure that allows us to iterate through property groups + * in an instance followed by the property groups of the instance's + * service, the instance's restarter and finally the global service. See + * the Property Group Iteration section of the block comment at the + * beginning of this file. + */ +typedef struct pg_iter { + entity_t *pgi_entity; /* Entity being searched */ + const char *pgi_restrict; /* Only return PGs of this type */ + tmpl_level_t pgi_level; /* Current level */ + entity_t *pgi_service; /* Service being processed. */ + union { + pgroup_t *pgi_pg; + composed_pg_t *pgi_cpg; + } pgi_current; /* Current property group. */ +} pg_iter_t; + +/* + * enum to distinguish between pg_patterns and prop_patterns. It is used + * in the ptrn_info_t structure. See below. + */ +typedef enum ptrn_type { + PG_PATTERN, + PROP_PATTERN +} ptrn_type_t; + +/* + * Structure of information about a pg_pattern or a prop_pattern. It is + * used for template consistency checks. gather_pattern() is used to + * gather information for all the pg_patterns or prop_patterns in an + * instance. It allocates a ptrn_info_t for each of these and adds them to + * an avl tree that is held by tmpl_consistency(). + */ +typedef struct ptrn_info { + ptrn_type_t pi_ptrn_type; + pgroup_t *pi_ptrnpg; /* pgroup_t defining the pattern. */ + const char *pi_name; /* Name attribute. */ + const char *pi_type; /* Type attribute. */ + const char *pi_target; /* Target attribute - only PG_PATTERN */ + const char *pi_pgp_name; /* Name of the pg pattern. Only */ + /* used for PROP_PATTERN. */ + pgroup_t *pi_enc_pgp; /* PG of the pg_pattern that holds */ + /* the prop_pattern defined by this */ + /* structure. Only used for */ + /* PROP_PATTERN. */ + uu_avl_node_t pi_link; /* Linkage into AVL tree */ +} ptrn_info_t; + +static const char *emesg_nomem; + +/* + * Pool for trees of composed property groups. + */ +static uu_avl_pool_t *composed_pg_pool; + +/* + * Pool for trees of composed properties. + */ +static uu_avl_pool_t *composed_prop_pool; + +/* + * Pool for lists of errors in the internal representation. + */ +static uu_list_pool_t *inmem_errors_pool; + +/* + * Pool for trees of pg_pattern info structures (ptrn_info_t). + */ +static uu_avl_pool_t *ptrn_info_pool; + +/* + * Pool for lists of template errors in the libscf representation. + */ +static uu_list_pool_t *tv_errors_pool; + +/* + * Property name prefixes for constraints and values. + */ +static const char *constraint_prefixes[] = { + SCF_PROPERTY_TM_CONSTRAINT_NAME, + SCF_PROPERTY_TM_CONSTRAINT_RANGE, + NULL +}; +static const char *value_prefixes[] = { + SCF_PROPERTY_TM_VALUE_PREFIX, + NULL +}; + +/* + * Function to compare two composed_pg structures. + */ +/* ARGSUSED2 */ +static int +composed_pg_compare(const void *left, const void *right, void *unused) +{ + composed_pg_t *l = (composed_pg_t *)left; + composed_pg_t *r = (composed_pg_t *)right; + int rc; + + if ((rc = strcmp(l->cpg_name, r->cpg_name)) == 0) { + rc = strcmp(l->cpg_type, r->cpg_type); + } + return (rc); +} + +/* ARGSUSED2 */ +static int +composed_prop_compare(const void *left, const void *right, void *unused) +{ + property_t *l = (property_t *)left; + property_t *r = (property_t *)right; + + return (strcmp(l->sc_property_name, r->sc_property_name)); +} + +static composed_pg_t * +composed_pg_create() +{ + composed_pg_t *cpg; + + cpg = safe_malloc(sizeof (*cpg)); + uu_avl_node_init(cpg, &cpg->cpg_node, composed_pg_pool); + return (cpg); +} + +static void +composed_pg_destroy(composed_pg_t *cpg) +{ + void *marker = NULL; + pgroup_t *pg; + + if (cpg == NULL) + return; + /* Tear down composed property tree if we have one. */ + if ((cpg->cpg_composed_props != NULL)) { + while (uu_avl_teardown(cpg->cpg_composed_props, &marker) != + NULL) { + /* + * Nothing to do other than getting the property + * out of the list. This cleans up the property's + * uu_avl_node. + */ + } + uu_avl_destroy(cpg->cpg_composed_props); + } + + /* Clean up any pgroup_t references to us. */ + if ((pg = cpg->cpg_instance_pg) != NULL) { + assert((pg->sc_pgroup_composed == NULL) || + (pg->sc_pgroup_composed == cpg)); + pg->sc_pgroup_composed = NULL; + } + + uu_avl_node_fini(cpg, &cpg->cpg_node, composed_pg_pool); + free(cpg); +} + +/* + * Walk the property group at pg, and add its properties to the AVL tree at + * tree. + */ +static void +grow_props_tree(pgroup_t *pg, uu_avl_t *tree) +{ + uu_avl_index_t marker; + property_t *prop; + + for (prop = uu_list_first(pg->sc_pgroup_props); + prop != NULL; + prop = uu_list_next(pg->sc_pgroup_props, prop)) { + if (uu_avl_find(tree, prop, NULL, &marker) == NULL) { + /* + * If there was no match, insert the property into + * the tree. If we do get a match, there is + * nothing to do. That is because we rely on our + * caller to process the instance properties first, + * and the instance properties override the service + * properties. + */ + uu_avl_insert(tree, prop, marker); + } + } +} + +/* + * The composed properties are stored in a uu_avl_tree. First we populate + * the tree with properties from the instance level property group. Then, + * we'll add the properties from the service level property group. + */ +static void +compose_props(composed_pg_t *cpg) +{ + uu_avl_t *tree; + + tree = uu_avl_create(composed_prop_pool, cpg, TMPL_DEBUG_TREE); + if (tree == NULL) { + uu_die(gettext("composed_pool tree creation failed: %s\n"), + uu_strerror(uu_error())); + } + cpg->cpg_composed_props = tree; + + /* + * compose_props() is only called when there is both an instance + * and a service definition of the property group. This implies + * that neither cpg->cpg_instance_pg nor cpg->cpg_service_pg can be + * NULL. + */ + /* + * First add instance properties to the tree. + */ + assert(cpg->cpg_instance_pg != NULL); + grow_props_tree(cpg->cpg_instance_pg, tree); + + /* + * Add service properties to the tree. + */ + assert(cpg->cpg_service_pg != NULL); + grow_props_tree(cpg->cpg_service_pg, tree); +} + +/* + * This function is a utility for build_composed_instance(). + */ +static void +build_composed_property_groups(entity_t *inst, uu_avl_t *tree) +{ + composed_pg_t *cpg; + uu_avl_index_t marker; + composed_pg_t *match; + pgroup_t *pg; + entity_t *svc; + + /* First capture the instance property groups. */ + for (pg = uu_list_first(inst->sc_pgroups); + pg != NULL; + pg = uu_list_next(inst->sc_pgroups, pg)) { + cpg = composed_pg_create(); + cpg->cpg_name = pg->sc_pgroup_name; + cpg->cpg_type = pg->sc_pgroup_type; + cpg->cpg_instance_pg = pg; + match = uu_avl_find(tree, cpg, NULL, &marker); + /* Since we do the instance first, there should be no match. */ + assert(match == NULL); + uu_avl_insert(tree, cpg, marker); + pg->sc_pgroup_composed = cpg; + } + + /* Now capture the service property groups. */ + svc = inst->sc_parent; + cpg = NULL; + for (pg = uu_list_first(svc->sc_pgroups); + pg != NULL; + pg = uu_list_next(svc->sc_pgroups, pg)) { + if (cpg == NULL) + cpg = composed_pg_create(); + cpg->cpg_name = pg->sc_pgroup_name; + cpg->cpg_type = pg->sc_pgroup_type; + cpg->cpg_service_pg = pg; + match = uu_avl_find(tree, cpg, NULL, &marker); + if (match == NULL) { + uu_avl_insert(tree, cpg, marker); + /* Get new composed_pg_t next at top of loop. */ + cpg = NULL; + } else { + /* + * Already have a composed_pg from instance + * processing. Just add the pointer to the service + * pg and compose the properties. + */ + match->cpg_service_pg = pg; + compose_props(match); + } + } + if (cpg != NULL) + composed_pg_destroy(cpg); +} + +static void +build_composed_instance(entity_t *inst) +{ + uu_avl_t *tree; + + assert(inst->sc_etype == SVCCFG_INSTANCE_OBJECT); + + if (inst->sc_u.sc_instance.sc_composed == NULL) { + tree = uu_avl_create(composed_pg_pool, inst, TMPL_DEBUG_TREE); + if (tree == NULL) { + uu_die(gettext("composed_instance tree creation " + "failed: %s\n"), uu_strerror(uu_error())); + } + inst->sc_u.sc_instance.sc_composed = tree; + } + build_composed_property_groups(inst, + inst->sc_u.sc_instance.sc_composed); +} + +static void +demolish_composed_instance(entity_t *inst) +{ + composed_pg_t *cpg; + void *marker = NULL; + uu_avl_t *tree; + + tree = inst->sc_u.sc_instance.sc_composed; + if (tree == NULL) + return; + + marker = NULL; + while ((cpg = uu_avl_teardown(tree, &marker)) != NULL) { + composed_pg_destroy(cpg); + } + uu_avl_destroy(tree); + + inst->sc_u.sc_instance.sc_composed = NULL; +} +/* + * Return the number of values in prop. + */ +static size_t +count_prop_values(property_t *prop) +{ + return (uu_list_numnodes(prop->sc_property_values)); +} + +static int +is_numeric_type(scf_type_t type) +{ + if (type == SCF_TYPE_BOOLEAN) + return (1); + if (type == SCF_TYPE_COUNT) + return (1); + if (type == SCF_TYPE_INTEGER) + return (1); + return (0); +} + +static pg_type_t +pgroup_type(pgroup_t *pg) +{ + if (strcmp(pg->sc_pgroup_type, SCF_GROUP_TEMPLATE_PG_PATTERN) == 0) + return (PG_PATTERN_PG); + if (strcmp(pg->sc_pgroup_type, SCF_GROUP_TEMPLATE_PROP_PATTERN) == 0) + return (PROP_PATTERN_PG); + return (NORMAL_PG); +} + +/* + * Search the property group at pg for a property named name. If the + * property group has a tree of composed properties, the tree will be + * searched for the property. Otherwise, the property group's linked list + * will be searched. + */ +static property_t * +property_find(pgroup_t *pg, const char *name) +{ + composed_pg_t *cpg; + property_t look; + + cpg = pg->sc_pgroup_composed; + + if ((cpg == NULL) || (cpg->cpg_composed_props == NULL)) { + /* This is not a composed property group. */ + return (internal_property_find(pg, name)); + } + + /* + * This is a composed property group, so look for the property in + * the AVL tree. + */ + look.sc_property_name = (char *)name; + return (uu_avl_find(cpg->cpg_composed_props, &look, NULL, NULL)); +} + +/* + * Functions for manipulating the avalues structure. + */ + +/* + * Free allocated memory referenced by the avalues structure. Then, free + * the structure itself. + */ +static void +av_destroy(avalues_t *av) +{ + if (av == NULL) + return; + switch (av->av_type) { + case SCF_TYPE_BOOLEAN: + case SCF_TYPE_COUNT: + uu_free(av->av_v.av_unsigned); + break; + case SCF_TYPE_INTEGER: + uu_free(av->av_v.av_integer); + break; + default: + /* + * We don't need to free the strings that are referenced by + * av_string. The strings are held in propery_t structures + * that will be freed at a later time. + */ + uu_free(av->av_v.av_string); + break; + } + uu_free(av); +} +/* + * Allocate and inialize an avalues structure. count represents the + * number of values the structure is expected to hold. type specifies how + * the consumer of the property values would like to see them represented. + * See comments for the av_get_values() more details on how type is used. + * + * The returned structure must be freed by calling av_destroy(). + * + * NULL is returned if memory allocation fails. + */ +static avalues_t * +av_create(size_t count, scf_type_t type) +{ + uint_t alloc_failed = 0; + avalues_t *av; + + av = uu_zalloc(sizeof (*av)); + if (av == NULL) + return (NULL); + av->av_count = count; + av->av_type = type; + switch (type) { + case SCF_TYPE_BOOLEAN: + case SCF_TYPE_COUNT: + av->av_v.av_unsigned = uu_zalloc(count * sizeof (uint64_t)); + if (av->av_v.av_unsigned == NULL) + alloc_failed = 1; + break; + case SCF_TYPE_INTEGER: + av->av_v.av_integer = uu_zalloc(count * sizeof (int64_t)); + if (av->av_v.av_integer == NULL) + alloc_failed = 1; + break; + default: + av->av_v.av_string = uu_zalloc(count * sizeof (char *)); + if (av->av_v.av_string == NULL) + alloc_failed = 1; + } + if (alloc_failed) { + av_destroy(av); + return (NULL); + } + return (av); +} + +/* + * Return the ith integer value in av. + */ +static int64_t +av_get_integer(avalues_t *av, uint_t i) +{ + assert(av->av_type == SCF_TYPE_INTEGER); + assert(i < av->av_count); + return (*(av->av_v.av_integer + i)); +} + +/* + * Return the ith string value in av. + */ +static const char * +av_get_string(avalues_t *av, uint_t i) +{ + assert(is_numeric_type(av->av_type) == 0); + assert(i < av->av_count); + return (*(av->av_v.av_string + i)); +} + +/* + * Return the ith unsigned value in av. + */ +static uint64_t +av_get_unsigned(avalues_t *av, uint_t i) +{ + assert((av->av_type == SCF_TYPE_BOOLEAN) || + (av->av_type == SCF_TYPE_COUNT)); + assert(i < av->av_count); + return (*(av->av_v.av_unsigned + i)); +} + +/* + * Store the value in the ith slot of the av structure. If av is being + * used to store numeric values, the string at value will be converted to + * the appropriate numeric form. + */ +static tmpl_validate_status_t +av_set_value(avalues_t *av, uint_t i, const char *value) +{ + char *endptr; + int64_t n; + uint64_t un; + + if (is_numeric_type(av->av_type)) { + switch (av->av_type) { + case SCF_TYPE_BOOLEAN: + case SCF_TYPE_COUNT: + un = strtoull(value, &endptr, 0); + if ((endptr == value) || (*endptr != 0)) { + return (TVS_BAD_CONVERSION); + } + *(av->av_v.av_unsigned + i) = un; + break; + case SCF_TYPE_INTEGER: + n = strtoll(value, &endptr, 0); + if ((endptr == value) || (*endptr != 0)) { + return (TVS_BAD_CONVERSION); + } + *(av->av_v.av_integer + i) = n; + } + } else { + *(av->av_v.av_string + i) = value; + } + + return (TVS_SUCCESS); +} + +/* + * Find the property whose name is prop_name in the property group at pg. + * Read all the values of this property and return them in an avalues + * structure placing the address of the structure in *values. The caller + * must free the structure by calling av_destroy(). + * + * The type parameter is used to indicate the type of information that the + * caller would like to consume. If it is one of the numeric types, the + * property value will be converted to the appropriate numeric type before + * placing it in the avalues struct. Decoding will be done before the + * conversion if necessary. + */ +static tmpl_validate_status_t +av_get_values(pgroup_t *pg, const char *prop_name, scf_type_t type, + avalues_t **values) +{ + avalues_t *av; + uint_t i; + property_t *prop; + tmpl_validate_status_t rc; + value_t *v; + + prop = property_find(pg, prop_name); + if (prop == NULL) { + return (TVS_NOMATCH); + } + assert(prop->sc_value_type == SCF_TYPE_ASTRING); + av = av_create(count_prop_values(prop), type); + if (av == NULL) + uu_die(emesg_nomem); + + /* Collect the values. */ + for ((v = uu_list_first(prop->sc_property_values)), i = 0; + v != NULL; + (v = uu_list_next(prop->sc_property_values, v)), i++) { + assert(i < av->av_count); + assert(v->sc_type == SCF_TYPE_ASTRING); + rc = av_set_value(av, i, v->sc_u.sc_string); + if (rc != TVS_SUCCESS) { + av_destroy(av); + return (rc); + } + } + *values = av; + return (TVS_SUCCESS); +} + +/* + * Find the property in pg whose name is prop_name. Return a pointer to + * the first astring value in that property. + * + * NULL is returned if there is no property named prop_name or if it does + * not have an astring value. + */ +static const char * +find_astring_value_in_pg(pgroup_t *pg, const char *prop_name) +{ + property_t *prop; + value_t *v; + + prop = property_find(pg, prop_name); + if (prop == NULL) + return (NULL); + if (prop->sc_value_type != SCF_TYPE_ASTRING) + return (NULL); + v = uu_list_first(prop->sc_property_values); + if (v == NULL) + return (NULL); + assert(v->sc_type == SCF_TYPE_ASTRING); + return (v->sc_u.sc_string); +} +/* + * Find the first property value of type SCF_TYPE_COUNT in the property at + * prop. Return the value to count. + */ +static tmpl_validate_status_t +find_count_value(property_t *prop, uint64_t *count) +{ + value_t *value; + + assert(prop->sc_value_type == SCF_TYPE_COUNT); + value = uu_list_first(prop->sc_property_values); + if (value == NULL) + return (TVS_NOMATCH); + *count = value->sc_u.sc_count; + return (TVS_SUCCESS); +} + +/* + * pattern is a property group representing a pg_pattern or a + * prop_pattern. This function returns the name specification from the + * pg_pattern or prop_pattern. + */ +static const char * +find_name_specification(pgroup_t *pattern) +{ + return (find_astring_value_in_pg(pattern, SCF_PROPERTY_TM_NAME)); +} + +/* + * pattern is a property group representing a pg_pattern or a prop_pattern. + * This function returns the type specification from the pg_pattern or + * prop_pattern. + */ +static const char * +find_type_specification(pgroup_t *pattern) +{ + return (find_astring_value_in_pg(pattern, SCF_PROPERTY_TM_TYPE)); +} + +/* + * Find the FMRI of the restarter for the entity, e. The restarter is the + * value of the "restarter" property in the "general" property group. + */ +static const char * +find_restarter(entity_t *e) +{ + pgroup_t *pg; + property_t *prop; + value_t *v; + + pg = internal_pgroup_find(e, scf_pg_general, scf_group_framework); + if (pg != NULL) { + prop = property_find(pg, SCF_PROPERTY_RESTARTER); + if ((prop != NULL) && (prop->sc_value_type == SCF_TYPE_FMRI)) { + v = uu_list_first(prop->sc_property_values); + if (v != NULL) + return (v->sc_u.sc_string); + } + } + + /* + * Didn't find the restarter. + */ + return (NULL); +} + +/* + * prop_pattern points to a prop_pattern. This function finds the + * cardinality specification in the prop_pattern and returns the minimum + * and maximum values of the cardinality. + * + * Returns TVS_NOMATCH if either the cardinality minimum or maximum are + * missing. + */ +static tmpl_validate_status_t +get_cardinality(pgroup_t *prop_pattern, uint64_t *min, uint64_t *max) +{ + property_t *prop; + tmpl_validate_status_t rc; + + assert(strcmp(prop_pattern->sc_pgroup_type, + SCF_GROUP_TEMPLATE_PROP_PATTERN) == 0); + + prop = property_find(prop_pattern, + SCF_PROPERTY_TM_CARDINALITY_MIN); + if (prop == NULL) + return (TVS_NOMATCH); + rc = find_count_value(prop, min); + if (rc != TVS_SUCCESS) + return (rc); + + prop = property_find(prop_pattern, + SCF_PROPERTY_TM_CARDINALITY_MAX); + if (prop == NULL) + return (TVS_NOMATCH); + rc = find_count_value(prop, max); + + return (rc); +} + +/* + * Ranges are represented as ASTRING values in the property at range_prop. + * The minimum and maximum of the range are separated by a comma. + * + * range_prop can contain multiple range values, so we return a pointer to + * an allocated array of range_t in ranges. This array must be freed by + * the caller using free(). count receives the number of range_t + * structures that are allocated. + * + * type tells us whether the range values should be treated as signed or + * unsigned. It must be SCF_TYPE_COUNT or SCF_TYPE_INTEGER. + */ +static tmpl_validate_status_t +get_ranges(property_t *range_prop, scf_type_t type, range_t **ranges, + uint_t *count) +{ + char *endptr; + char *endptr2; + range_t *r; + value_t *value; + + *count = uu_list_numnodes(range_prop->sc_property_values); + assert(*count != 0); + r = safe_malloc(*count * sizeof (*r)); + *ranges = r; + for (value = uu_list_first(range_prop->sc_property_values); + value != NULL; + value = uu_list_next(range_prop->sc_property_values, value)) { + assert(value->sc_type == SCF_TYPE_ASTRING); + + /* First get the minimum */ + errno = 0; + if (type == SCF_TYPE_INTEGER) { + r->rng_u.rng_signed.rng_min = + strtoll(value->sc_u.sc_string, &endptr, 0); + } else { + r->rng_u.rng_unsigned.rng_min = + strtoull(value->sc_u.sc_string, &endptr, 0); + } + if ((errno != 0) || (endptr == value->sc_u.sc_string)) + goto badtemplate; + if (*endptr != ',') + goto badtemplate; + + /* Now get the maximum */ + endptr++; + if (type == SCF_TYPE_INTEGER) { + r->rng_u.rng_signed.rng_max = + strtoll(endptr, &endptr2, 0); + } else { + r->rng_u.rng_unsigned.rng_max = + strtoull(endptr, &endptr2, 0); + } + if ((errno != 0) || (endptr2 == endptr) || + (*endptr2 != 0)) + goto badtemplate; + r++; + } + + return (TVS_SUCCESS); + +badtemplate: + free(*ranges); + *ranges = NULL; + return (TVS_BAD_TEMPLATE); +} + +static tv_errors_t * +tv_errors_create(const char *fmri) +{ + tv_errors_t *ste; + + ste = safe_malloc(sizeof (*ste)); + uu_list_node_init(ste, &ste->tve_node, tv_errors_pool); + ste->tve_errors = _scf_create_errors(fmri, 1); + if (ste->tve_errors == NULL) + uu_die(emesg_nomem); + + return (ste); +} + +static void +destroy_scf_errors(tv_errors_t *ste) +{ + scf_tmpl_errors_destroy(ste->tve_errors); + uu_list_node_fini(ste, &ste->tve_node, tv_errors_pool); + free(ste); +} + +/* + * Given a property group and the name of a property within that property + * group, generate the name of the property group that holds the + * prop_pattern information for the property. The address of the generated + * name is returned to prop_pattern_pg_name. The memory holding the + * generated name must be freed using uu_free(). + */ +static tmpl_validate_status_t +gen_prop_pattern_pg_name(pgroup_t *pg_pattern, const char *prop_name, + char **prop_pattern_pg_name) +{ + ssize_t limit; + char *name; + size_t prefix_size; + const char *unique; + + limit = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH) + 1; + assert(limit > 0); + + /* Get the unique part of the pg_pattern's property group name. */ + prefix_size = strlen(SCF_PG_TM_PG_PAT_BASE); + assert(strncmp(pg_pattern->sc_pgroup_name, SCF_PG_TM_PG_PAT_BASE, + prefix_size) == 0); + unique = pg_pattern->sc_pgroup_name + prefix_size; + + /* Construct the prop pattern property group name. */ + *prop_pattern_pg_name = NULL; + name = uu_zalloc(limit); + if (name == NULL) + uu_die(emesg_nomem); + if (snprintf(name, limit, "%s%s_%s", SCF_PG_TM_PROP_PATTERN_PREFIX, + unique, prop_name) >= limit) { + uu_free(name); + return (TVS_BAD_TEMPLATE); + } + *prop_pattern_pg_name = name; + return (TVS_SUCCESS); +} + +/* + * Error message printing functions: + */ + +/* + * Flags for use by im_perror_item. + */ +#define IPI_NOT_FIRST 0x1 /* Not first item to be displayed. */ + +/* + * Print a single item of information about a validation failure. This + * function takes care of printing the appropriate decoration before the + * first item and between subsequent items. + * + * Parameters: + * out Stream to receive the output. + * desc Text describing the items + * item Address of the item to be displayed + * type Type of the item + * flags Used by im_perror_item to keep track of where it + * is. Caller should set flags to 0 before calling + * this function with the first item. + */ +static void +im_perror_item(FILE *out, const char *desc, void *item, scf_type_t type, + int *flags) +{ + const char *cp; + const char *first_sep; + int64_t ival; + const char *subsequent_sep; + uint64_t uval; + + /* Nothing to print if item is NULL. */ + if (item == NULL) + return; + + assert(type != SCF_TYPE_INVALID); + + /* Establish separators for environment. */ + if (est->sc_cmd_flags & SC_CMD_IACTIVE) { + /* Interactive mode - make messages readable */ + first_sep = ":\n\t"; + subsequent_sep = "\n\t"; + } else { + /* Non-interactive - one line messages. */ + first_sep = ": "; + subsequent_sep = "; "; + } + + /* Print separator and description */ + if (*flags & IPI_NOT_FIRST) { + (void) fprintf(out, subsequent_sep); + } else { + (void) fprintf(out, first_sep); + *flags |= IPI_NOT_FIRST; + } + (void) fprintf(out, "%s=", desc); + + switch (type) { + case SCF_TYPE_BOOLEAN: + uval = *((uint64_t *)item); + if (uval) { + (void) fprintf(out, "\"%s\"", gettext("true")); + } else { + (void) fprintf(out, "\"%s\"", gettext("false")); + } + break; + case SCF_TYPE_COUNT: + uval = *((uint64_t *)item); + (void) fprintf(out, "%" PRIu64, uval); + break; + case SCF_TYPE_INTEGER: + ival = *((int64_t *)item); + (void) fprintf(out, "%" PRIi64, ival); + break; + default: + /* + * Treat everything else as a string, but escape any + * internal quotes. + */ + (void) fputc('\"', out); + cp = (const char *)item; + while (*cp != 0) { + if (*cp == '\"') { + (void) fprintf(out, "\\\""); + } else { + (void) fputc(*cp, out); + } + cp++; + } + (void) fputc('\"', out); + break; + } +} + +/* + * Print erroneous FMRI. + */ +static void +im_perror_fmri(FILE *out, im_tmpl_error_t *i, int *flags) +{ + if (i->ite_entity != NULL) { + im_perror_item(out, "FMRI", (void *)i->ite_entity->sc_fmri, + SCF_TYPE_FMRI, flags); + } +} + +/* + * Print erroneous property group name. + */ +static void +im_perror_pg_name(FILE *out, im_tmpl_error_t *i, int *flags) +{ + if (i->ite_pg != NULL) { + im_perror_item(out, gettext("Property group"), + (void *)i->ite_pg->sc_pgroup_name, SCF_TYPE_ASTRING, + flags); + } +} + +/* + * If srcflag is 1, print the template source of the pg_pattern or + * prop_pattern at pattern. Always print the name and type of the pattern. + */ +static void +im_perror_pattern_info(FILE *out, pgroup_t *pattern, int *flags, int srcflag) +{ + void *c; + const char *name_string; + const char *type_string; + + if (pattern == NULL) + return; + switch (pgroup_type(pattern)) { + case PG_PATTERN_PG: + name_string = gettext("pg_pattern name"); + type_string = gettext("pg_pattern type"); + break; + case PROP_PATTERN_PG: + name_string = gettext("prop_pattern name"); + type_string = gettext("prop_pattern type"); + break; + default: + assert(0); + abort(); + } + if (srcflag) { + im_perror_item(out, gettext("Template source"), + (void *)pattern->sc_parent->sc_fmri, SCF_TYPE_FMRI, flags); + } + c = (void *)find_name_specification(pattern); + im_perror_item(out, name_string, + (c == NULL) ? "" : c, SCF_TYPE_ASTRING, flags); + c = (void *)find_type_specification(pattern); + im_perror_item(out, type_string, + (c == NULL) ? "" : c, SCF_TYPE_ASTRING, flags); +} + +/* + * Print information about the template specifications that were violated, + * so that the user can find the specification. + */ +static void +im_perror_template_info(FILE *out, im_tmpl_error_t *i, int *flags) +{ + pgroup_t *pg_pattern = i->ite_pg_pattern; + pgroup_t *prop_pattern = i->ite_prop_pattern; + int srcflag = 1; + + if (pg_pattern != NULL) { + im_perror_pattern_info(out, pg_pattern, flags, srcflag); + srcflag = 0; + } + if (prop_pattern != NULL) { + im_perror_pattern_info(out, prop_pattern, flags, srcflag); + } +} + +/* Print error message for TVS_BAD_CONVERSION errors. */ +static void +im_perror_bad_conversion(FILE *out, im_tmpl_error_t *i, const char *prefix) +{ + int flags = 0; + + (void) fprintf(out, gettext("%sUnable to convert property value"), + prefix); + im_perror_fmri(out, i, &flags); + im_perror_pg_name(out, i, &flags); + im_perror_item(out, gettext("Property"), + (void *)i->ite_prop->sc_property_name, SCF_TYPE_ASTRING, &flags); + im_perror_template_info(out, i, &flags); + (void) fputc('\n', out); +} + +/* Print error message for TVS_BAD_TEMPLATE errors. */ +static void +im_perror_bad_template(FILE *out, im_tmpl_error_t *i, const char *prefix) +{ + int flags = 0; + + assert(i->ite_einfo.ei_type == EIT_BAD_TEMPLATE); + (void) fprintf(out, gettext("%sInvalid template - %s"), prefix, + i->ite_einfo.ei_u.ei_bad_template.ei_reason); + im_perror_fmri(out, i, &flags); + im_perror_template_info(out, i, &flags); + (void) fputc('\n', out); +} + +/* + * Print error message for TVS_INVALID_TYPE_SPECIFICATION errors. This + * error occurs if a prop_pattern has an invalid type specification. Thus, + * it is an indication of an invalid template rather than a violation of a + * template. + */ +static void +im_perror_invalid_type(FILE *out, im_tmpl_error_t *i, const char *prefix) +{ + int flags = 0; + const char *prop_pattern_name; + + (void) fprintf(out, gettext("%sInvalid type in prop_pattern"), prefix); + im_perror_pg_name(out, i, &flags); + if (i->ite_prop_pattern != NULL) { + prop_pattern_name = + find_name_specification(i->ite_prop_pattern); + im_perror_item(out, gettext("prop_pattern name"), + (void *)prop_pattern_name, SCF_TYPE_ASTRING, &flags); + } + im_perror_template_info(out, i, &flags); + (void) fputc('\n', out); +} + +/* + * Print error message for TVS_MISSING_PG_TYPE errors. In this case the + * template specifies a type, but the property group itself has no type. + */ +static void +im_perror_missing_pg_type(FILE *out, im_tmpl_error_t *i, const char *prefix) +{ + int flags = 0; + const char *type_spec; + + (void) fprintf(out, gettext("%sProperty group has no type"), prefix); + im_perror_fmri(out, i, &flags); + im_perror_pg_name(out, i, &flags); + if (i->ite_pg_pattern != NULL) { + type_spec = find_type_specification(i->ite_pg_pattern); + im_perror_item(out, gettext("Type specified in pg_pattern"), + (void *)type_spec, SCF_TYPE_ASTRING, &flags); + } + (void) fputc('\n', out); +} + +/* + * Print error message for TVS_MISSING_TYPE_SPECIFICATION errors. A + * property group has a "required" attribute of true, but it does not have + * a type specification. + */ +static void +im_perror_missing_type(FILE *out, im_tmpl_error_t *i, const char *prefix) +{ + int flags = 0; + const char *pg_pattern_name; + + (void) fprintf(out, gettext("%sPg_pattern with true required attribute " + "is missing the type attribute"), prefix); + im_perror_fmri(out, i, &flags); + if (i->ite_pg_pattern != NULL) { + pg_pattern_name = find_name_specification(i->ite_pg_pattern); + im_perror_item(out, gettext("Pg_pattern name"), + (void *)pg_pattern_name, SCF_TYPE_ASTRING, &flags); + } + im_perror_template_info(out, i, &flags); + (void) fputc('\n', out); +} + +static void +im_tmpl_error_print(FILE *out, im_tmpl_error_t *ite, const char *prefix) +{ + switch (ite->ite_type) { + case TVS_BAD_CONVERSION: + im_perror_bad_conversion(out, ite, prefix); + break; + case TVS_BAD_TEMPLATE: + im_perror_bad_template(out, ite, prefix); + break; + case TVS_INVALID_TYPE_SPECIFICATION: + im_perror_invalid_type(out, ite, prefix); + break; + case TVS_MISSING_PG_TYPE: + im_perror_missing_pg_type(out, ite, prefix); + break; + case TVS_MISSING_TYPE_SPECIFICATION: + im_perror_missing_type(out, ite, prefix); + break; + case TVS_NOMATCH: + /* + * TVS_NOMATCH should be handled where it occurs. Thus, + * there are no error messages associated with it. + */ + assert(0); + abort(); + break; + case TVS_SUCCESS: + break; + default: + assert(0); + abort(); + } +} + +static char * +int64_to_str(int64_t i) +{ + char *c; + const char *fmt; + int size; + + fmt = "%" PRIi64; + size = snprintf(NULL, 0, fmt, i) + 1; + c = safe_malloc(size); + (void) snprintf(c, size, fmt, i); + return (c); +} + +static char * +uint64_to_str(uint64_t u) +{ + char *c; + const char *fmt; + int size; + + fmt = "%" PRIu64; + size = snprintf(NULL, 0, fmt, u) + 1; + c = safe_malloc(size); + (void) snprintf(c, size, fmt, u); + return (c); +} + +/* + * Convert the value to a string. The returned value must be freed using + * free(3C). + */ +static const char * +value_to_string(value_t *v) +{ + char *c; + + if (is_numeric_type(v->sc_type)) { + switch (v->sc_type) { + case SCF_TYPE_BOOLEAN: + if (v->sc_u.sc_count == 0) { + c = gettext("false"); + } else { + c = gettext("true"); + } + break; + case SCF_TYPE_COUNT: + c = uint64_to_str(v->sc_u.sc_count); + return (c); + case SCF_TYPE_INTEGER: + c = int64_to_str(v->sc_u.sc_integer); + return (c); + } + } else { + c = v->sc_u.sc_string; + } + + return (safe_strdup(c)); +} + +/* + * Subscripts for common error data. + */ +#define ED_PG_NAME 0 +#define ED_PROP_NAME 1 +#define ED_TMPL_FMRI 2 +#define ED_TMPL_PG_NAME 3 +#define ED_TMPL_PG_TYPE 4 +#define ED_TMPL_PROP_NAME 5 +#define ED_TMPL_PROP_TYPE 6 +#define ED_COUNT 7 + +/* + * This function converts the error information specified by the function + * parameters. It converts it to form needed by _scf_tmpl_add_error(). + * _scf_tmpl_add_error() requires that the error information be in the form + * of allocated strings that can be freed when it is done with them. Thus, + * the bulk of this function is devoted to producing those allocated + * strings. + * + * Once the strings are ready, we call _scf_tmpl_add_error() to add an + * new error structure to errs. + */ +static int +add_scf_error(tmpl_errors_t *errs, scf_tmpl_error_type_t ec, + pgroup_t *pg_pattern, pgroup_t *pg, pgroup_t *prop_pattern, + property_t *prop, value_t *val, error_info_t *einfo) +{ + const char *actual = NULL; + char *c; + pgroup_t *conflict; + const char *ed[ED_COUNT]; + const char *ev1 = NULL; + const char *ev2 = NULL; + int i; + scf_type_t prop_type; + int rc; + + (void) memset(ed, 0, sizeof (ed)); + + /* Set values that are common to most error types. */ + if (pg != NULL) { + ed[ED_PG_NAME] = pg->sc_pgroup_name; + } + if (prop != NULL) { + ed[ED_PROP_NAME] = prop->sc_property_name; + } + if (pg_pattern == NULL) { + if (prop_pattern != NULL) { + ed[ED_TMPL_FMRI] = prop_pattern->sc_parent->sc_fmri; + } + } else { + ed[ED_TMPL_FMRI] = pg_pattern->sc_parent->sc_fmri; + ed[ED_TMPL_PG_NAME] = find_name_specification(pg_pattern); + ed[ED_TMPL_PG_TYPE] = find_type_specification(pg_pattern); + } + if (prop_pattern != NULL) { + ed[ED_TMPL_PROP_NAME] = find_name_specification(prop_pattern); + ed[ED_TMPL_PROP_TYPE] = find_type_specification(prop_pattern); + } + + /* + * All of the strings that we've found must be strduped. This is + * so that scf_tmpl_errors_destroy() can free them. We cannot use + * the flag argument of _scf_create_errors() to indicate that the + * strings should not be freed. The flag argument is an all or + * nothing thing. In the code below we need to convert integers to + * strings, and this requires memory allocation. Since we have to + * allocate memory for that data, we need to allocate it for every + * thing. + */ + for (i = 0; i < ED_COUNT; i++) { + if (ed[i] == NULL) + continue; + ed[i] = safe_strdup(ed[i]); + } + + /* actual, ev1 and ev2 are error code specific. */ + switch (ec) { + case SCF_TERR_CARDINALITY_VIOLATION: + assert(einfo != NULL); + assert(einfo->ei_type == EIT_CARDINALITY); + ev1 = uint64_to_str(einfo->ei_u.ei_cardinality.ei_min); + ev2 = uint64_to_str(einfo->ei_u.ei_cardinality.ei_max); + actual = uint64_to_str(einfo->ei_u.ei_cardinality.ei_count); + break; + case SCF_TERR_WRONG_PG_TYPE: + /* Specified type. */ + if (pg_pattern != NULL) { + ev1 = find_type_specification(pg_pattern); + if (ev1 != NULL) { + ev1 = safe_strdup(ev1); + } + } + /* Actual type. */ + if (pg != NULL) { + actual = pg->sc_pgroup_type; + if (actual != NULL) { + actual = safe_strdup(actual); + } + } + break; + case SCF_TERR_WRONG_PROP_TYPE: + assert(einfo->ei_type == EIT_PROP_TYPE); + prop_type = einfo->ei_u.ei_prop_type.ei_specified; + ev1 = safe_strdup(scf_type_to_string(prop_type)); + prop_type = einfo->ei_u.ei_prop_type.ei_actual; + actual = safe_strdup(scf_type_to_string(prop_type)); + break; + case SCF_TERR_VALUE_CONSTRAINT_VIOLATED: + actual = value_to_string(val); + break; + case SCF_TERR_MISSING_PG: + assert(einfo->ei_type == EIT_MISSING_PG); + ev1 = safe_strdup(einfo->ei_u.ei_missing_pg.ei_pg_name); + ev2 = safe_strdup(einfo->ei_u.ei_missing_pg.ei_pg_type); + break; + case SCF_TERR_MISSING_PROP: + assert(einfo->ei_type == EIT_MISSING_PROP); + ev1 = safe_strdup(einfo->ei_u.ei_missing_prop.ei_prop_name); + break; + case SCF_TERR_RANGE_VIOLATION: + assert(einfo->ei_type == EIT_RANGE); + if (einfo->ei_u.ei_range.ei_rtype == SCF_TYPE_COUNT) { + c = uint64_to_str(einfo->ei_u.ei_range.ei_uvalue); + } else { + c = int64_to_str(einfo->ei_u.ei_range.ei_ivalue); + } + actual = c; + break; + case SCF_TERR_PG_PATTERN_CONFLICT: + case SCF_TERR_PROP_PATTERN_CONFLICT: + case SCF_TERR_GENERAL_REDEFINE: + assert(einfo->ei_type == EIT_PATTERN_CONFLICT); + conflict = einfo->ei_u.ei_pattern_conflict.ei_pattern; + ev1 = safe_strdup(conflict->sc_parent->sc_fmri); + ev2 = find_name_specification(conflict); + if (ev2 != NULL) + ev2 = safe_strdup(ev2); + actual = find_type_specification(conflict); + if (actual != NULL) + actual = safe_strdup(actual); + break; + case SCF_TERR_INCLUDE_VALUES: + assert(einfo->ei_type == EIT_INCLUDE_VALUES); + ev1 = safe_strdup(einfo->ei_u.ei_inc_values.ei_type); + break; + case SCF_TERR_PG_PATTERN_INCOMPLETE: + case SCF_TERR_PROP_PATTERN_INCOMPLETE: + break; + default: + assert(0); + abort(); + }; + + rc = _scf_tmpl_add_error(errs->te_cur_scf->tve_errors, ec, + ed[ED_PG_NAME], ed[ED_PROP_NAME], ev1, ev2, actual, + ed[ED_TMPL_FMRI], ed[ED_TMPL_PG_NAME], ed[ED_TMPL_PG_TYPE], + ed[ED_TMPL_PROP_NAME], ed[ED_TMPL_PROP_TYPE]); + + return (rc); +} + +/* + * Create and initialize a new im_tmpl_error structure and add it to the + * list of errors in errs. The rest of the parameters are used to + * initialize the im_tmpl_error structure. + */ +static tmpl_validate_status_t +tmpl_errors_add_im(tmpl_errors_t *errs, tmpl_validate_status_t ec, entity_t *e, + pgroup_t *pg_pattern, pgroup_t *pg, pgroup_t *prop_pattern, + property_t *prop, value_t *val, error_info_t *einfo) +{ + im_tmpl_error_t *ite; + int result; + + ite = uu_zalloc(sizeof (*ite)); + if (ite == NULL) + uu_die(emesg_nomem); + uu_list_node_init(ite, &ite->ite_node, inmem_errors_pool); + ite->ite_type = ec; + ite->ite_entity = e; + ite->ite_pg = pg; + ite->ite_pg_pattern = pg_pattern; + ite->ite_prop = prop; + ite->ite_prop_pattern = prop_pattern; + ite->ite_value = val; + if (einfo != NULL) + ite->ite_einfo = *einfo; + + result = uu_list_insert_after(errs->te_list, NULL, ite); + assert(result == 0); + return (TVS_SUCCESS); +} + +/* + * pattern must point to a pg_pattern or a prop_pattern. This function + * finds the property named required and returns the property's value. If + * the property does not exist, false is return since it is the default. + */ +static int +is_required(pgroup_t *pattern) +{ + property_t *required; + value_t *value; + + assert((strcmp(pattern->sc_pgroup_type, + SCF_GROUP_TEMPLATE_PG_PATTERN) == 0) || + (strcmp(pattern->sc_pgroup_type, + SCF_GROUP_TEMPLATE_PROP_PATTERN) == 0)); + + required = property_find(pattern, SCF_PROPERTY_TM_REQUIRED); + + /* Default if there is no required property is false. */ + if (required == NULL) + return (0); + + /* Retrieve the value of the required property. */ + value = uu_list_first(required->sc_property_values); + if (value == NULL) + return (0); + if (value->sc_type == SCF_TYPE_BOOLEAN) + return (value->sc_u.sc_count == 0 ? 0 : 1); + + /* No boolean property values, so return false. */ + return (0); +} + +/* + * Load the service's restarter instance and the global instance from the + * repository. This will allow us to use their templates in validating the + * service. + * + * There is no function to unload the general templates. The memory that + * is allocated by load_general_templates() will be freed automatically in + * internal_service_free() which is called by internal_bundle_free(). + */ +static void +load_general_templates(entity_t *svc) +{ + const char *restarter; + int is_global = 0; + int r; + + assert(svc->sc_etype == SVCCFG_SERVICE_OBJECT); + + /* + * If e is the global service, we only need to load the restarter. + */ + if ((strcmp(svc->sc_fmri, SCF_INSTANCE_GLOBAL) == 0) || + (strcmp(svc->sc_fmri, SCF_SERVICE_GLOBAL) == 0)) { + is_global = 1; + } + + /* + * Load the templates for the service's restarter. + */ + restarter = find_restarter(svc); + if (restarter == NULL) + restarter = SCF_SERVICE_STARTD; + if ((r = load_instance(restarter, "restarter", + &svc->sc_u.sc_service.sc_restarter)) != 0) { + /* + * During initial manifest import, restarter may + * not be in the repository yet. In this case we + * continue on without it. + */ + if (r == ENOMEM) + uu_die(emesg_nomem); + else + svc->sc_u.sc_service.sc_restarter = NULL; + } + if (is_global == 0) { + if ((r = load_instance(SCF_INSTANCE_GLOBAL, "global", + &svc->sc_u.sc_service.sc_global)) != 0) { + /* + * During initial manifest import, global may not be in + * the repository yet. + */ + if (r == ENOMEM) + uu_die(emesg_nomem); + else + svc->sc_u.sc_service.sc_global = NULL; + } + } +} + +/* + * Load the instance specific restarter if one is declared. + * + * There is no corresponding unload_instance_restarter() function because + * it is not needed. The memory will be freed in internal_instance_free() + * when internal_bundle_free() is called. + */ +static void +load_instance_restarter(entity_t *i) +{ + const char *restarter; + int r; + + assert(i->sc_etype == SVCCFG_INSTANCE_OBJECT); + + restarter = find_restarter(i); + if (restarter == NULL) { + /* No instance specific restarter */ + return; + } + r = load_instance(restarter, "instance_restarter", + &i->sc_u.sc_instance.sc_instance_restarter); + if (r != 0) { + /* + * During initial manifest import, the restarter may not be + * in the repository yet. In this case we continue on + * without it. + */ + if (r == ENOMEM) + uu_die(emesg_nomem); + } +} + +/* + * Find the next property after current in the property group at pg. If + * the property group contains a tree of composed properties, that tree is + * walked. Otherwise, we walk through the uu_list at sc_pgroup_props. + */ +static property_t * +next_property(pgroup_t *pg, property_t *current) +{ + composed_pg_t *cpg; + property_t *prop; + + cpg = pg->sc_pgroup_composed; + if ((cpg != NULL) && (cpg->cpg_composed_props != NULL)) { + /* Walk through composed property list. */ + if (current) { + prop = uu_avl_next(cpg->cpg_composed_props, current); + } else { + prop = uu_avl_first(cpg->cpg_composed_props); + } + } else { + /* No composition available, so walk the list of properties */ + if (current) { + prop = uu_list_next(pg->sc_pgroup_props, current); + } else { + prop = uu_list_first(pg->sc_pgroup_props); + } + } + + return (prop); +} + +static ptrn_info_t * +ptrn_info_create(pgroup_t *pat) +{ + entity_t *e; + ptrn_info_t *info; + composed_pg_t *match; + composed_pg_t cpg; + + info = safe_malloc(sizeof (*info)); + + switch (pgroup_type(pat)) { + case PG_PATTERN_PG: + info->pi_ptrn_type = PG_PATTERN; + break; + case PROP_PATTERN_PG: + info->pi_ptrn_type = PROP_PATTERN; + break; + default: + assert(0); + abort(); + } + info->pi_ptrnpg = pat; + info->pi_name = find_name_specification(pat); + info->pi_name = EMPTY_TO_NULL(info->pi_name); + info->pi_type = find_type_specification(pat); + info->pi_type = EMPTY_TO_NULL(info->pi_type); + if (info->pi_ptrn_type == PG_PATTERN) { + info->pi_target = find_astring_value_in_pg(pat, + SCF_PROPERTY_TM_TARGET); + if (info->pi_target == NULL) + info->pi_target = SCF_TM_TARGET_THIS; + } + if (info->pi_ptrn_type == PROP_PATTERN) { + info->pi_pgp_name = find_astring_value_in_pg(pat, + SCF_PROPERTY_TM_PG_PATTERN); + assert((info->pi_pgp_name != NULL) && + (*(info->pi_pgp_name) != 0)); + + /* + * Find the property group that defines the pg_pattern that + * holds this prop_pattern. + */ + e = pat->sc_parent; + if (e->sc_etype == SVCCFG_INSTANCE_OBJECT) { + (void) memset(&cpg, 0, sizeof (cpg)); + cpg.cpg_name = info->pi_pgp_name; + cpg.cpg_type = SCF_GROUP_TEMPLATE_PG_PATTERN; + match = uu_avl_find(e->sc_u.sc_instance.sc_composed, + &cpg, NULL, NULL); + assert(match != NULL); + info->pi_enc_pgp = CPG2PG(match); + } else { + info->pi_enc_pgp = internal_pgroup_find(e, + info->pi_pgp_name, SCF_GROUP_TEMPLATE_PG_PATTERN); + } + assert(info->pi_enc_pgp != NULL); + } + uu_avl_node_init(info, &info->pi_link, ptrn_info_pool); + return (info); +} + +static void +ptrn_info_destroy(ptrn_info_t *info) +{ + if (info == NULL) + return; + uu_avl_node_fini(info, &info->pi_link, ptrn_info_pool); + free(info); +} + +/* + * Walk through the property groups of the instance or service at e looking + * for definitions of pg_patterns or prop_patterns as specified by type. + * For each property group that matches type create a ptrn_info_t and add + * it to the avl tree at tree. If duplicates are found add an error entry + * to errs. + */ +static tmpl_validate_status_t +gather_pattern(entity_t *e, ptrn_type_t type, uu_avl_t *tree, + tmpl_errors_t *errs) +{ + error_info_t einfo; + ptrn_info_t *info = NULL; + uu_avl_index_t marker; + ptrn_info_t *match; + pgroup_t *pg; + tmpl_validate_status_t rc = TVS_SUCCESS; + const char *selector; + + switch (type) { + case PG_PATTERN: + selector = SCF_GROUP_TEMPLATE_PG_PATTERN; + break; + case PROP_PATTERN: + selector = SCF_GROUP_TEMPLATE_PROP_PATTERN; + break; + default: + assert(0); + abort(); + } + + for (pg = uu_list_first(e->sc_pgroups); + pg != NULL; + pg = uu_list_next(e->sc_pgroups, pg)) { + if (strcmp(pg->sc_pgroup_type, selector) != 0) { + continue; + } + if (info != NULL) { + /* Get rid of old structure. */ + ptrn_info_destroy(info); + } + info = ptrn_info_create(pg); + match = uu_avl_find(tree, info, NULL, &marker); + if (match == NULL) { + /* No match. Insert the info. */ + uu_avl_insert(tree, info, marker); + info = NULL; + continue; + } + + /* Got a match. Determine if it is a conflict. */ + if ((info->pi_name == NULL) || + (info->pi_type == NULL) || + (match->pi_name == NULL) || + (match->pi_type == NULL)) { + /* No conflicts if any wild cards. */ + continue; + } + + /* + * Name already matches, or we wouldn't have gotten + * here. Make sure that the type also matches. + */ + if (strcmp(info->pi_type, match->pi_type) == 0) { + continue; + } + + /* + * If we get to this point we have a conflict, and + * we need to generate the correct type of error. + */ + CLEAR_ERROR_INFO(&einfo); + einfo.ei_type = EIT_PATTERN_CONFLICT; + einfo.ei_u.ei_pattern_conflict.ei_pattern = + match->pi_ptrnpg; + if (type == PG_PATTERN) { + rc = TVS_VALIDATION; + if (add_scf_error(errs, SCF_TERR_PG_PATTERN_CONFLICT, + info->pi_ptrnpg, NULL, NULL, NULL, NULL, + &einfo) != 0) { + /* + * If we can no longer accumulate + * errors, break out of the loop. + */ + break; + } + } else { + /* + * Possible conflicting prop_pattern. See if the + * prop_patterns are declared in the same + * pg_pattern. + */ + if ((info->pi_pgp_name == NULL) || + (match->pi_pgp_name == NULL)) { + continue; + } + if (strcmp(info->pi_pgp_name, match->pi_pgp_name) != 0) + continue; + + /* It is a real conflict. */ + rc = TVS_VALIDATION; + if (add_scf_error(errs, SCF_TERR_PROP_PATTERN_CONFLICT, + info->pi_enc_pgp, NULL, info->pi_ptrnpg, NULL, NULL, + &einfo) != 0) { + /* + * If we can no longer accumulate + * errors, break out of the loop. + */ + break; + } + } + } + + ptrn_info_destroy(info); + return (rc); +} + +/* + * Free the pg_iter structure. + */ +static void +pg_iter_destroy(pg_iter_t *i) +{ + if (i == NULL) + return; + + uu_free(i); +} + +/* + * Create a property group iterator for the instance at e. This iterator + * will walk through the composed property groups of the instance. It will + * then step through the property groups of the instance's restarter and + * finally the global service. If you wish to iterate over a specific type + * of property group, set restriction to point the the desired type. + * Otherwise set restriction to NULL. + * + * The returned interator must be freed by calling pg_iter_destroy(). NULL + * is returned if we are unable to allocate the necessary memory. + */ +static pg_iter_t * +pg_iter_create(entity_t *e, const char *restriction) +{ + pg_iter_t *i; + + assert(e->sc_etype == SVCCFG_INSTANCE_OBJECT); + + i = uu_zalloc(sizeof (*i)); + if (i == NULL) + return (NULL); + + i->pgi_entity = e; + i->pgi_restrict = restriction; + i->pgi_level = TL_COMPOSED; + i->pgi_service = e->sc_parent; + + return (i); +} + +/* + * Return the next property group in the iteration. NULL is returned if we + * reach the end of the list. The iterator will automatically proceed from + * most specific to most general levels. + */ +static pgroup_t * +next_pattern_pg(pg_iter_t *i) +{ + composed_pg_t *cpg; + entity_t *e; + pgroup_t *pg; + uu_avl_t *composed_tree; + + assert(i->pgi_level != TL_NOLEVEL); + + while (i->pgi_entity != NULL) { + if (i->pgi_level == TL_COMPOSED) { + composed_tree = + i->pgi_entity->sc_u.sc_instance.sc_composed; + cpg = i->pgi_current.pgi_cpg; + if (cpg == NULL) { + cpg = uu_avl_first(composed_tree); + } else { + cpg = uu_avl_next(composed_tree, cpg); + } + if (cpg == NULL) { + pg = NULL; + } else { + pg = CPG2PG(cpg); + i->pgi_current.pgi_cpg = cpg; + } + } else { + pg = i->pgi_current.pgi_pg; + if (pg == NULL) { + pg = uu_list_first(i->pgi_entity->sc_pgroups); + } else { + pg = uu_list_next(i->pgi_entity->sc_pgroups, + pg); + } + i->pgi_current.pgi_pg = pg; + } + + if (pg == NULL) { + /* + * End of the list. Reset current and break out of + * the loop. + */ + (void) memset(&i->pgi_current, 0, + sizeof (i->pgi_current)); + break; + } + + /* + * If this iteration is for a specific type, verify that + * this pg is of that type. + */ + if (i->pgi_restrict) { + if (strcmp(pg->sc_pgroup_type, i->pgi_restrict) != 0) { + continue; + } + } + + return (pg); + } + + /* + * End of the list in the current level. Move up to the next + * level. + */ + switch (i->pgi_level) { + case TL_COMPOSED: + /* Skip service if we've finished a composed instance. */ + e = i->pgi_entity; + if (e->sc_u.sc_instance.sc_instance_restarter == NULL) { + /* Use service restarter */ + i->pgi_entity = + i->pgi_service->sc_u.sc_service.sc_restarter; + } else { + /* Use instance restarter */ + i->pgi_entity = + e->sc_u.sc_instance.sc_instance_restarter; + } + i->pgi_level = TL_RESTARTER; + break; + case TL_RESTARTER: + i->pgi_entity = i->pgi_service->sc_u.sc_service.sc_global; + i->pgi_level = TL_GLOBAL; + break; + case TL_GLOBAL: + i->pgi_level = TL_NOLEVEL; + return (NULL); + default: + assert(0); + abort(); + } + + /* Go process the next level. */ + return (next_pattern_pg(i)); +} + +/* + * Compare two pattern info structures (ptrn_info_t). If both structures + * have names, the comparison is based on the name. If only one has a + * name, the structure with no name will be first. If neither structure + * has a name, the comparison is based on their types using similar wild + * card logic. + */ +/* ARGSUSED2 */ +static int +ptrn_info_compare(const void *left, const void *right, void *unused) +{ + ptrn_info_t *l = (ptrn_info_t *)left; + ptrn_info_t *r = (ptrn_info_t *)right; + + if ((l->pi_name != NULL) && (r->pi_name != NULL)) + return (strcmp(l->pi_name, r->pi_name)); + if ((l->pi_name == NULL) && (r->pi_name == NULL)) { + /* No names, so we need to compare types. */ + if ((l->pi_type != NULL) && (r->pi_type != NULL)) + return (strcmp(l->pi_type, r->pi_type)); + if ((l->pi_type == NULL) && (r->pi_type == NULL)) + return (0); + + /* If we get here, exactly one of the types is NULL */ + if (l->pi_type == NULL) + return (-1); + return (1); + } + + /* If we get here, exactly one of the names is NULL */ + if (l->pi_name == NULL) + return (-1); + return (1); +} + +/* + * The target of a pg_pattern in combination with the level at which the + * pg_pattern was defined determines whether or not it should be applied. + * The following combinations should be ignored, and all others should be + * applied. + * + * Target Level + * ------ ----- + * this TL_RESTARTER, TL_GLOBAL + * "this" only applies if the pg_pattern was defined + * at the instance or service level + * delegate TL_INSTANCE, TL_SERVICE + * Only restarters and the global service can + * delegate. + * instance TL_INSTANCE, TL_RESTARTER, TL_GLOBAL + * Only the service level can specify the "instance" + * target. + * all TL_INSTANCE, TL_SERVICE, TL_RESTARTER + * Only the global service can specify the "all" + * target. + * + * Return Values: + * 1 apply the pg_pattern + * 0 ignore the pg_pattern + */ +static int +target_check(const char *target, tmpl_level_t level) +{ + if ((target == NULL) || (*target == 0)) { + /* Default is this */ + target = SCF_TM_TARGET_THIS; + } + if (strcmp(target, SCF_TM_TARGET_THIS) == 0) { + if ((level == TL_RESTARTER) || + (level == TL_GLOBAL)) { + return (0); + } else { + return (1); + } + } + if (strcmp(target, SCF_TM_TARGET_DELEGATE) == 0) { + if ((level == TL_INSTANCE) || + (level == TL_SERVICE)) { + return (0); + } else { + return (1); + } + } + if (strcmp(target, SCF_TM_TARGET_INSTANCE) == 0) { + /* + * Note that the test is inverted from the other cases. + * This is because there is only one instance where apply + * is the correct thing to do. + */ + if (level == TL_SERVICE) { + return (1); + } else { + return (0); + } + } + if (strcmp(target, SCF_TM_TARGET_ALL) == 0) { + if ((level == TL_INSTANCE) || + (level == TL_SERVICE) || + (level == TL_RESTARTER)) { + return (0); + } + } + return (1); +} + +static int +pg_target_check(pgroup_t *pg_pattern, tmpl_level_t level) +{ + const char *target; + + target = find_astring_value_in_pg(pg_pattern, SCF_PROPERTY_TM_TARGET); + if (level == TL_COMPOSED) { + switch (pg_pattern->sc_parent->sc_etype) { + case SVCCFG_INSTANCE_OBJECT: + level = TL_INSTANCE; + break; + case SVCCFG_SERVICE_OBJECT: + level = TL_SERVICE; + break; + default: + assert(0); + abort(); + } + } + return (target_check(target, level)); +} + +/* + * Find the prop_pattern's type sepcification and convert it to the + * appropriate scf_type. + */ +static tmpl_validate_status_t +prop_pattern_type(pgroup_t *pattern, scf_type_t *type) +{ + const char *type_spec; + + assert(strcmp(pattern->sc_pgroup_type, + SCF_GROUP_TEMPLATE_PROP_PATTERN) == 0); + + type_spec = find_type_specification(pattern); + if ((type_spec == NULL) || (*type_spec == 0)) + return (TVS_MISSING_TYPE_SPECIFICATION); + *type = scf_string_to_type(type_spec); + return (TVS_SUCCESS); +} + +/* + * This function is analagous to scf_property_is_type(3SCF), but it works + * on the in memory representation of the property. + * + * RETURNS: + * 0 The property at prop does not have the specified + * type. + * non-zero The property at prop does have the specified type. + */ +static int +property_is_type(property_t *prop, scf_type_t type) +{ + return (prop->sc_value_type == type); +} + +/* + * This function generates a property group name for a template's + * pg_pattern. The name and type of the pg_pattern are used to construct + * the name, but either or both may be null. A pointer to the constructed + * name is returned, and the referenced memory must be freed using + * free(3c). NULL is returned if we are unable to allocate enough memory. + */ +static char * +gen_pg_pattern_pg_name(const char *name, const char *type) +{ + char *pg_name; + char *rv = NULL; + ssize_t name_size; + + name_size = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH); + pg_name = safe_malloc(name_size); + rv = pg_name; + + /* + * There are four cases -- name and type are both null, name and + * type are both non-null, only name is present or only type is + * present. + */ + if ((name == NULL) || (*name == 0)) { + if ((type == NULL) || (*type == 0)) { + /* + * Name and type are both null, so the PG name + * contains only the prefix. + */ + if (strlcpy(pg_name, SCF_PG_TM_PG_PATTERN_PREFIX, + name_size) >= name_size) { + rv = NULL; + } + } else { + /* + * If we have a type and no name, the type becomes + * part of the pg_pattern property group name. + */ + if (snprintf(pg_name, name_size, "%s%s", + SCF_PG_TM_PG_PATTERN_T_PREFIX, type) >= + name_size) { + rv = NULL; + } + } + } else { + /* + * As long as the pg_pattern has a name, it becomes part of + * the name of the pg_pattern property group name. We + * merely need to pick the appropriate prefix. + */ + const char *prefix; + if ((type == NULL) || (*type == 0)) { + prefix = SCF_PG_TM_PG_PATTERN_N_PREFIX; + } else { + prefix = SCF_PG_TM_PG_PATTERN_NT_PREFIX; + } + if (snprintf(pg_name, name_size, "%s%s", prefix, name) >= + name_size) { + rv = NULL; + } + } + + if (rv == NULL) { + /* Name was too big. */ + free(pg_name); + } + return (rv); +} + +/* + * pinfo contains information about a prop_pattern. An include_values + * element with a type of type has been included in the prop_pattern + * specification. We need to determine if the prop_pattern also contains + * constraints or values specifications as determined by type. Thus, we + * search the prop_pattern for properties whose names start with the + * correct prefix. + */ +static tmpl_validate_status_t +include_values_support(ptrn_info_t *pinfo, const char *type, + tmpl_errors_t *errs) +{ + error_info_t einfo; + int i; + const char **prefixes; + const char *pfx; + property_t *prop; + pgroup_t *ptrn; + + if (strcmp(type, "constraints") == 0) { + prefixes = constraint_prefixes; + } else if (strcmp(type, "values") == 0) { + prefixes = value_prefixes; + } else { + CLEAR_ERROR_INFO(&einfo); + einfo.ei_type = EIT_BAD_TEMPLATE; + einfo.ei_u.ei_bad_template.ei_reason = gettext("include_values " + "type must be \"constraints\" or \"values\""); + (void) tmpl_errors_add_im(errs, TVS_BAD_TEMPLATE, + pinfo->pi_ptrnpg->sc_parent, pinfo->pi_enc_pgp, + NULL, pinfo->pi_ptrnpg, NULL, NULL, &einfo); + return (TVS_BAD_TEMPLATE); + } + + /* + * Now see if the prop_pattern has a property whose name starts + * with one of these prefixes. + */ + ptrn = pinfo->pi_ptrnpg; + for (prop = uu_list_first(ptrn->sc_pgroup_props); + prop != NULL; + prop = uu_list_next(ptrn->sc_pgroup_props, prop)) { + for (pfx = prefixes[0], i = 0; + pfx != NULL; + ++i, pfx = prefixes[i]) { + if (strncmp(prop->sc_property_name, pfx, + strlen(pfx)) == 0) { + return (TVS_SUCCESS); + } + } + } + + /* No match found. Generate error */ + CLEAR_ERROR_INFO(&einfo); + einfo.ei_type = EIT_INCLUDE_VALUES; + einfo.ei_u.ei_inc_values.ei_type = type; + (void) add_scf_error(errs, SCF_TERR_INCLUDE_VALUES, pinfo->pi_enc_pgp, + NULL, ptrn, NULL, NULL, &einfo); + + return (TVS_VALIDATION); +} + +/* + * Walk through the prop_patterns in tree, looking for any that have an + * include_values, SCF_PROPERTY_TM_CHOICES_INCLUDE_VALUES, property. For + * the prop_patterns with the include values property, verify that the + * prop_pattern has constraint or values declarations as specified by the + * include_values property. + */ +static tmpl_validate_status_t +tmpl_include_values_check(uu_avl_t *tree, tmpl_errors_t *errs) +{ + ptrn_info_t *info; + property_t *iv; + tmpl_validate_status_t r; + tmpl_validate_status_t rc = TVS_SUCCESS; + value_t *v; + + for (info = uu_avl_first(tree); + info != NULL; + info = uu_avl_next(tree, info)) { + iv = internal_property_find(info->pi_ptrnpg, + SCF_PROPERTY_TM_CHOICES_INCLUDE_VALUES); + if (iv == NULL) + continue; + for (v = uu_list_first(iv->sc_property_values); + v != NULL; + v = uu_list_next(iv->sc_property_values, v)) { + assert(is_numeric_type(v->sc_type) == 0); + r = include_values_support(info, v->sc_u.sc_string, + errs); + if (r != TVS_SUCCESS) + rc = r; + } + } + return (rc); +} + +/* + * Verify that there are no conflicting definitions of pg_pattern or + * prop_pattern. Two patterns are said to be in conflict if they have the + * same name and differing types. There is a caveat, however. Empty + * pattern names or types are considered to be wild cards. There is no + * conflict if a pattern has a wild card. + */ +static tmpl_validate_status_t +tmpl_pattern_conflict(entity_t *inst, uu_avl_t *tree, ptrn_type_t type, + tmpl_errors_t *errs) +{ + tmpl_validate_status_t r; + tmpl_validate_status_t rc; + + /* First walk the instance. */ + rc = gather_pattern(inst, type, tree, errs); + + /* Now walk the service */ + r = gather_pattern(inst->sc_parent, type, tree, errs); + if (r != TVS_SUCCESS) + rc = r; + + return (rc); +} + +static tmpl_validate_status_t +tmpl_required_attr_present(uu_avl_t *tree, tmpl_errors_t *errs) +{ + ptrn_info_t *pinfo; + tmpl_validate_status_t rc = TVS_SUCCESS; + int reported_name; + int rv; + + for (pinfo = uu_avl_first(tree); + pinfo != NULL; + pinfo = uu_avl_next(tree, pinfo)) { + if (is_required(pinfo->pi_ptrnpg) == 0) { + /* Nothing to check if pattern is not required. */ + continue; + } + + /* + * For pg_pattern both name and type are optional unless + * the required attribute has a value of true. For + * prop_patterns only the type is optional, but it must be + * provided if the required attribute has a value of true. + */ + reported_name = 0; + if ((pinfo->pi_ptrn_type == PG_PATTERN) && + (pinfo->pi_name == NULL)) { + rc = TVS_VALIDATION; + if (add_scf_error(errs, SCF_TERR_PG_PATTERN_INCOMPLETE, + pinfo->pi_ptrnpg, + NULL, NULL, NULL, NULL, NULL) != 0) { + /* + * If we're unable to report errors, break + * out of the loop. + */ + break; + } + /* + * Don't report the error twice if both name and + * type are missing. One error message is + * adequate. + */ + reported_name = 1; + } + if ((pinfo->pi_type == NULL) && (reported_name == 0)) { + rc = TVS_VALIDATION; + if (pinfo->pi_ptrn_type == PG_PATTERN) { + rv = add_scf_error(errs, + SCF_TERR_PG_PATTERN_INCOMPLETE, + pinfo->pi_ptrnpg, + NULL, NULL, NULL, NULL, NULL); + } else { + rv = add_scf_error(errs, + SCF_TERR_PROP_PATTERN_INCOMPLETE, + pinfo->pi_enc_pgp, NULL, pinfo->pi_ptrnpg, + NULL, NULL, NULL); + } + /* If we're unable to log errors, break out of loop. */ + if (rv != 0) + break; + } + } + return (rc); +} + +/* + * Look for pg_pattern definitions in general. general is either the + * restarter serivce for inst or it is the global service. tree contains + * the ptrn_info_t structures describing the pg_patterns for an instance. + * For each general pg_pattern, see if the instance contains an overriding + * definition in tree. If it does generate an error entry. + * + * If a redefinition is found, TVS_WARN is returned. This is because a + * redefinition is not sufficient reason to inhibit the import operation. + */ +static tmpl_validate_status_t +tmpl_scan_general(entity_t *general, uu_avl_t *tree, + tmpl_level_t level, tmpl_errors_t *errs) +{ + tmpl_level_t cur_level; + error_info_t einfo; + pgroup_t *pg; + ptrn_info_t *ginfo = NULL; + ptrn_info_t *match; + tmpl_validate_status_t rc = TVS_SUCCESS; + + /* + * General services may not be in repository yet. It depends on + * the order that manifests are imported. + */ + if (general == NULL) + return (TVS_SUCCESS); + + for (pg = uu_list_first(general->sc_pgroups); + pg != NULL; + pg = uu_list_next(general->sc_pgroups, pg)) { + if (strcmp(pg->sc_pgroup_type, + SCF_GROUP_TEMPLATE_PG_PATTERN) != 0) { + /* Not a pg_pattern */ + continue; + } + if (ginfo != NULL) + ptrn_info_destroy(ginfo); + ginfo = ptrn_info_create(pg); + match = uu_avl_find(tree, ginfo, NULL, NULL); + if (match != NULL) { + /* See if global pg_pattern is targeted at us. */ + if (target_check(ginfo->pi_target, level) == 0) + continue; + + /* + * See if the match applies to us. If we happen to + * be a restarter, the pg_pattern could have a + * target of delegate. That wouldn't apply to this + * instance, it would only apply to our delegates. + * Cases such as this are not a redefinition. + */ + if (match->pi_ptrnpg->sc_parent->sc_etype == + SVCCFG_INSTANCE_OBJECT) { + cur_level = TL_INSTANCE; + } else { + cur_level = TL_SERVICE; + } + if (target_check(match->pi_target, cur_level) == 0) + continue; + + /* + * Instance or service overrides a general + * definition. We need to issue a warning message. + */ + rc = TVS_WARN; + CLEAR_ERROR_INFO(&einfo); + einfo.ei_type = EIT_PATTERN_CONFLICT; + einfo.ei_u.ei_pattern_conflict.ei_pattern = pg; + if (add_scf_error(errs, SCF_TERR_GENERAL_REDEFINE, + match->pi_ptrnpg, NULL, NULL, NULL, NULL, + &einfo) != 0) { + /* + * No need to continue the search if we + * cannot record errors. + */ + break; + } + } + } + + if (ginfo != NULL) + ptrn_info_destroy(ginfo); + return (rc); +} + +/* + * tree contains the pg_pattern definitions for the instance at inst. See + * if these pg_patterns redefine any pg_patterns in the instance's + * restarter or in the global service. TVS_WARN is returned if a + * redefinition is encountered. + */ +static tmpl_validate_status_t +tmpl_level_redefine(entity_t *inst, uu_avl_t *tree, tmpl_errors_t *errs) +{ + entity_t *restarter; + entity_t *svc = inst->sc_parent; + tmpl_validate_status_t r; + tmpl_validate_status_t rc; + + restarter = inst->sc_u.sc_instance.sc_instance_restarter; + if (restarter == NULL) { + /* No instance restarter. Use the service restarter */ + restarter = svc->sc_u.sc_service.sc_restarter; + } + rc = tmpl_scan_general(restarter, tree, TL_RESTARTER, errs); + r = tmpl_scan_general(svc->sc_u.sc_service.sc_global, tree, + TL_GLOBAL, errs); + if (r != TVS_SUCCESS) + rc = r; + return (rc); +} + +/* + * Perform the following consistency checks on the template specifications + * themselves: + * + * - No conflicting definitions of `pg_pattern` are allowed within a + * single instance. + * + * - Templates at a narrow target (e.g. instance) which define + * property groups already templated at a broad target + * (e.g. restarter or all) are strongly discouraged. + * + * - Developers may not define a template which specifies a single + * prop_pattern name with differing types on the same target + * entity. + * + * - If a pg_pattern has a required attribute with a value of true, + * then its name and type attributes must be specified. + * + * - If a prop_pattern has a required attribute with a value of true, + * then its type attribute must be specified. + * + * - If a prop_pattern has an include values make sure that the + * appropriate constraints or values element has also been + * declared. + */ +static tmpl_validate_status_t +tmpl_consistency(entity_t *inst, tmpl_errors_t *errs) +{ + void *marker = NULL; + ptrn_info_t *info; + uu_avl_t *tree; + tmpl_validate_status_t rc; + tmpl_validate_status_t r; + + /* Allocate the tree. */ + tree = uu_avl_create(ptrn_info_pool, NULL, TMPL_DEBUG_TREE); + if (tree == NULL) { + uu_die(gettext("pg_info tree creation failed: %s\n"), + uu_strerror(uu_error())); + } + + rc = tmpl_pattern_conflict(inst, tree, PG_PATTERN, errs); + + /* + * The tree now contains the instance and service pg_patterns. + * Check to see if they override any pg_pattern definitions in the + * restarter and global services. + */ + r = tmpl_level_redefine(inst, tree, errs); + if (r != TVS_SUCCESS) { + /* + * tmpl_level_redefine() can return a warning. Don't + * override a serious error with a warning. + */ + if (r == TVS_WARN) { + if (rc == TVS_SUCCESS) + rc = r; + } else { + rc = r; + } + } + + /* + * If the pg_pattern has a required attribute with a value of true, + * then it must also have name and type attributes. + */ + r = tmpl_required_attr_present(tree, errs); + if (r != TVS_SUCCESS) + rc = r; + + /* Empty the tree, so that we can reuse it for prop_patterns. */ + while ((info = uu_avl_teardown(tree, &marker)) != NULL) { + ptrn_info_destroy(info); + } + + r = tmpl_pattern_conflict(inst, tree, PROP_PATTERN, errs); + if (r != TVS_SUCCESS) + rc = r; + + /* + * If a prop_pattern has required attribute with a value of true, + * then it must also have a type attribute. + */ + r = tmpl_required_attr_present(tree, errs); + if (r != TVS_SUCCESS) + rc = r; + + /* + * Insure that include_values have the constraint for values + * elements that are needed. + */ + r = tmpl_include_values_check(tree, errs); + if (r != TVS_SUCCESS) + rc = r; + + /* Tear down the tree. */ + marker = NULL; + while ((info = uu_avl_teardown(tree, &marker)) != NULL) { + ptrn_info_destroy(info); + } + uu_avl_destroy(tree); + + return (rc); +} + +/* + * Release memory associated with the tmpl_errors structure and then free + * the structure itself. + */ +void +tmpl_errors_destroy(tmpl_errors_t *te) +{ + im_tmpl_error_t *ite; + tv_errors_t *ste; + void *marker = NULL; + + if (te == NULL) + return; + if (te->te_list) { + while ((ite = uu_list_teardown(te->te_list, &marker)) != NULL) { + uu_list_node_fini(ite, &ite->ite_node, + inmem_errors_pool); + uu_free(ite); + } + uu_list_destroy(te->te_list); + } + if (te->te_scf) { + marker = NULL; + while ((ste = uu_list_teardown(te->te_scf, &marker)) != NULL) { + destroy_scf_errors(ste); + } + uu_list_destroy(te->te_scf); + } + uu_free(te); +} + +/* + * Allocate and initialize a tmpl_errors structure. The address of the + * structure is returned, unless we are unable to allocate enough memory. + * In the case of memory allocation failures, NULL is returned. + * + * The allocated structure should be freed by calling + * tmpl_errors_destroy(). + */ +static tmpl_errors_t * +tmpl_errors_create() +{ + tmpl_errors_t *te; + + te = uu_zalloc(sizeof (*te)); + if (te == NULL) + return (NULL); + te->te_list = uu_list_create(inmem_errors_pool, NULL, TMPL_DEBUG_LIST); + if (te->te_list == NULL) { + uu_free(te); + return (NULL); + } + te->te_scf = uu_list_create(tv_errors_pool, NULL, TMPL_DEBUG_LIST); + if (te->te_scf == NULL) { + tmpl_errors_destroy(te); + return (NULL); + } + + return (te); +} + +void +tmpl_errors_print(FILE *out, tmpl_errors_t *errs, const char *prefix) +{ + scf_tmpl_error_t *cur; + size_t buf_size = 4096; + im_tmpl_error_t *ite; + char *s = NULL; + scf_tmpl_errors_t *scferrs; + tv_errors_t *scft; + int interactive = (est->sc_cmd_flags & SC_CMD_IACTIVE) ? + SCF_TMPL_STRERROR_HUMAN : 0; + + for (ite = uu_list_first(errs->te_list); + ite != NULL; + ite = uu_list_next(errs->te_list, ite)) { + im_tmpl_error_print(out, ite, prefix); + } + + /* Now handle the errors that can be printed via libscf. */ + s = safe_malloc(buf_size); + for (scft = uu_list_first(errs->te_scf); + scft != NULL; + scft = uu_list_next(errs->te_scf, scft)) { + scferrs = scft->tve_errors; + if (_scf_tmpl_error_set_prefix(scferrs, prefix) != 0) + uu_die(emesg_nomem); + while ((cur = scf_tmpl_next_error(scferrs)) != NULL) { + (void) scf_tmpl_strerror(cur, s, buf_size, interactive); + (void) fputs(s, out); + (void) fputc('\n', out); + } + } + + free(s); +} + +/* + * This function finds the prop_pattern for the property, prop. e is the + * instance where the search for the prop_pattern will start. pg_pattern + * is the address of the pg_pattern that holds the prop_pattern. + */ +static tmpl_validate_status_t +tmpl_find_prop_pattern(entity_t *inst, pgroup_t *pg_pattern, + property_t *prop, pgroup_t **prop_pattern) +{ + pgroup_t *candidate; + pg_iter_t *iter = NULL; + char *prop_pattern_name = NULL; + tmpl_validate_status_t rc; + + /* + * Get the name of the property group that holds the prop_pattern + * definition. + */ + rc = gen_prop_pattern_pg_name(pg_pattern, + prop->sc_property_name, &prop_pattern_name); + if (rc != TVS_SUCCESS) + goto out; + + /* Find the property group. */ + iter = pg_iter_create(inst, SCF_GROUP_TEMPLATE_PROP_PATTERN); + if (iter == NULL) + goto out; + while ((candidate = next_pattern_pg(iter)) != NULL) { + const char *c; + + if (strcmp(prop_pattern_name, candidate->sc_pgroup_name) != 0) + continue; + c = find_astring_value_in_pg(candidate, + SCF_PROPERTY_TM_PG_PATTERN); + if (c == NULL) + continue; + if (strcmp(pg_pattern->sc_pgroup_name, c) == 0) + break; + } + *prop_pattern = candidate; + if (candidate == NULL) + rc = TVS_NOMATCH; + +out: + pg_iter_destroy(iter); + uu_free((void *)prop_pattern_name); + return (rc); +} + +/* + * Indexes for pg_pattern property group names. Indexes are arranged + * from most specific to least specific. + */ +#define PGN_BOTH 0 /* both name and type */ +#define PGN_NAME 1 /* name only */ +#define PGN_TYPE 2 /* type only */ +#define PGN_NEITHER 3 /* neither name nor type */ +#define PGN_MAX 4 /* Size of array */ + +/* + * Given an instance entity, e, and a propety group, pg, within the + * instance; return the address of the pg_pattern for the property group. + * The address of the pg_pattern is placed at pgp. NULL indicates that no + * pg_pattern was specified. + */ +static tmpl_validate_status_t +tmpl_find_pg_pattern(entity_t *e, pgroup_t *pg, pgroup_t **pgp) +{ + pgroup_t *cpg; /* candidate property group */ + int i; + pg_iter_t *iter = NULL; + char *pg_names[PGN_MAX]; + pgroup_t *pg_patterns[PGN_MAX]; + tmpl_validate_status_t rv = TVS_SUCCESS; + + (void) memset(pg_patterns, 0, sizeof (pg_patterns)); + *pgp = NULL; + + /* Generate candidate names for pg_pattern property groups. */ + pg_names[PGN_BOTH] = gen_pg_pattern_pg_name(pg->sc_pgroup_name, + pg->sc_pgroup_type); + pg_names[PGN_NAME] = gen_pg_pattern_pg_name(pg->sc_pgroup_name, + NULL); + pg_names[PGN_TYPE] = gen_pg_pattern_pg_name(NULL, + pg->sc_pgroup_type); + pg_names[PGN_NEITHER] = gen_pg_pattern_pg_name(NULL, NULL); + for (i = 0; i < PGN_MAX; i++) { + if (pg_names[i] == NULL) { + rv = TVS_BAD_TEMPLATE; + goto errout; + } + } + + /* Search for property groups that match these names */ + iter = pg_iter_create(e, SCF_GROUP_TEMPLATE_PG_PATTERN); + if (iter == NULL) { + uu_die(emesg_nomem); + } + while ((cpg = next_pattern_pg(iter)) != NULL) { + if (pg_target_check(cpg, iter->pgi_level) == 0) + continue; + + /* See if we have a name match. */ + for (i = 0; i < PGN_MAX; i++) { + if (strcmp(cpg->sc_pgroup_name, pg_names[i]) == 0) { + /* + * If we already have a lower level + * pg_pattern, keep it. + */ + if (pg_patterns[i] == NULL) + pg_patterns[i] = cpg; + break; + } + } + } + + /* Find the most specific pg_pattern. */ + for (i = 0; i < PGN_MAX; i++) { + if (pg_patterns[i] != NULL) { + *pgp = pg_patterns[i]; + break; + } + } +errout: + for (i = 0; i < PGN_MAX; i++) { + free(pg_names[i]); + } + pg_iter_destroy(iter); + return (rv); +} + +/* + * Initialize structures that are required for validation using + * templates specifications. + */ +void +tmpl_init(void) +{ + emesg_nomem = gettext("Out of memory.\n"); + + composed_pg_pool = uu_avl_pool_create("composed_pg", + sizeof (composed_pg_t), offsetof(composed_pg_t, cpg_node), + composed_pg_compare, TMPL_DEBUG_AVL_POOL); + if (composed_pg_pool == NULL) { + uu_die(gettext("composed_pg pool creation failed: %s\n"), + uu_strerror(uu_error())); + } + composed_prop_pool = uu_avl_pool_create("composed_prop", + sizeof (property_t), offsetof(property_t, sc_composed_node), + composed_prop_compare, TMPL_DEBUG_AVL_POOL); + if (composed_prop_pool == NULL) { + uu_die(gettext("composed_prop pool creation failed. %s\n"), + uu_strerror(uu_error())); + } + ptrn_info_pool = uu_avl_pool_create("ptrn_info", sizeof (ptrn_info_t), + offsetof(ptrn_info_t, pi_link), ptrn_info_compare, + TMPL_DEBUG_AVL_POOL); + if (ptrn_info_pool == NULL) { + uu_die(gettext("pg_pattern info pool creation failed: %s\n"), + uu_strerror(uu_error())); + } + inmem_errors_pool = uu_list_pool_create("errors-internal", + sizeof (im_tmpl_error_t), offsetof(im_tmpl_error_t, + ite_node), NULL, TMPL_DEBUG_LIST_POOL); + if (inmem_errors_pool == NULL) { + uu_die(gettext("inmem_errors_pool pool creation failed: " + "%s\n"), uu_strerror(uu_error())); + } + tv_errors_pool = uu_list_pool_create("scf-terrors", + sizeof (tv_errors_t), offsetof(tv_errors_t, tve_node), + NULL, TMPL_DEBUG_LIST_POOL); + if (tv_errors_pool == NULL) { + uu_die(gettext("tv_errors_pool pool creation failed: %s\n"), + uu_strerror(uu_error())); + } +} + +/* + * Clean up the composed property node in the property. + */ +void +tmpl_property_fini(property_t *p) +{ + uu_avl_node_fini(p, &p->sc_composed_node, composed_prop_pool); +} + +/* + * Initialize the composed property node in the property. + */ +void +tmpl_property_init(property_t *p) +{ + uu_avl_node_init(p, &p->sc_composed_node, composed_prop_pool); +} + +/* + * Use the cardinality specification in the prop_pattern to verify the + * cardinality of the property at prop. The cardinality of the property is + * the number of values that it has. + * + * pg is the property group that holds prop, and pg_pattern is the + * pg_pattern for the property group. pg and pg_pattern are only used for + * error reporting. + */ +static tmpl_validate_status_t +tmpl_validate_cardinality(pgroup_t *prop_pattern, property_t *prop, + pgroup_t *pg, pgroup_t *pg_pattern, tmpl_errors_t *errs) +{ + size_t count; + uint64_t max; + uint64_t min; + tmpl_validate_status_t rc; + error_info_t einfo; + + assert(strcmp(prop_pattern->sc_pgroup_type, + SCF_GROUP_TEMPLATE_PROP_PATTERN) == 0); + + rc = get_cardinality(prop_pattern, &min, &max); + switch (rc) { + case TVS_NOMATCH: + /* Nothing to check. */ + return (TVS_SUCCESS); + case TVS_SUCCESS: + /* Process the limits. */ + break; + default: + return (rc); + } + + if ((min == 0) && (max == ULLONG_MAX)) { + /* Any number of values is permitted. No need to count. */ + return (TVS_SUCCESS); + } + + count = count_prop_values(prop); + if ((count < min) || (count > max)) { + CLEAR_ERROR_INFO(&einfo); + einfo.ei_type = EIT_CARDINALITY; + einfo.ei_u.ei_cardinality.ei_min = min; + einfo.ei_u.ei_cardinality.ei_max = max; + einfo.ei_u.ei_cardinality.ei_count = count; + (void) add_scf_error(errs, SCF_TERR_CARDINALITY_VIOLATION, + pg_pattern, pg, prop_pattern, prop, NULL, &einfo); + return (TVS_VALIDATION); + } + + return (TVS_SUCCESS); +} + +/* + * Iterate over pg_patterns in the entity, e. If the pg_pattern's required + * attribute is true, verify that the entity contains the corresponding + * property group. + */ +static tmpl_validate_status_t +tmpl_required_pg_present(entity_t *e, tmpl_errors_t *errs) +{ + composed_pg_t cpg; + composed_pg_t *match; + error_info_t einfo; + pg_iter_t *iter; + pgroup_t *pg; + const char *pg_name; + const char *pg_type; + tmpl_validate_status_t rc = TVS_SUCCESS; + uu_avl_t *tree; + + assert(e->sc_etype == SVCCFG_INSTANCE_OBJECT); + + iter = pg_iter_create(e, SCF_GROUP_TEMPLATE_PG_PATTERN); + if (iter == NULL) + uu_die(emesg_nomem); + + CLEAR_ERROR_INFO(&einfo); + einfo.ei_type = EIT_MISSING_PG; + + while ((pg = next_pattern_pg(iter)) != NULL) { + if (is_required(pg) == 0) { + /* If pg is not required, there is nothing to check. */ + continue; + } + pg_name = find_astring_value_in_pg(pg, SCF_PROPERTY_TM_NAME); + pg_type = find_astring_value_in_pg(pg, SCF_PROPERTY_TM_TYPE); + if (pg_target_check(pg, iter->pgi_level) == 0) + continue; + einfo.ei_u.ei_missing_pg.ei_pg_name = pg_name; + einfo.ei_u.ei_missing_pg.ei_pg_type = pg_type; + tree = e->sc_u.sc_instance.sc_composed; + (void) memset(&cpg, 0, sizeof (cpg)); + cpg.cpg_name = pg_name; + cpg.cpg_type = pg_type; + match = uu_avl_find(tree, &cpg, NULL, NULL); + if (match == NULL) { + rc = TVS_VALIDATION; + if (add_scf_error(errs, SCF_TERR_MISSING_PG, pg, + NULL, NULL, NULL, NULL, &einfo) != 0) { + break; + } + } + } + + pg_iter_destroy(iter); + return (rc); +} + +/* + * Verify that the property group, pg, contains property declarations for + * all required properties. Unfortunately, there is no direct way to find + * the prop_patterns for a given property group. Therefore, we need to + * scan the entity at e looking for property groups with a type of + * SCF_GROUP_TEMPLATE_PROP_PATTERN. That is, we scan the entity looking + * for all prop_patterns. When we find a prop_pattern, we look at the + * value of its pg_pattern property to see if it matches the name of the + * pg_pattern. If they match, this is a prop_pattern that is of interest + * to us. + * + * When we find an interesting prop_pattern, we see if it's required + * property is true. If it is, we verify that the property group at pg + * contains the specified property. + */ +static tmpl_validate_status_t +tmpl_required_props_present(entity_t *e, pgroup_t *pg, pgroup_t *pg_pattern, + tmpl_errors_t *errs) +{ + error_info_t einfo; + pg_iter_t *iter; + const char *prop_name; + const char *prop_pg_pattern_name; + pgroup_t *prop_pattern; + scf_tmpl_error_type_t ec; + tmpl_validate_status_t rc = TVS_SUCCESS; + + /* + * Scan the entity's property groups looking for ones with a type + * of SCF_GROUP_TEMPLATE_PROP_PATTERN. + */ + iter = pg_iter_create(e, SCF_GROUP_TEMPLATE_PROP_PATTERN); + if (iter == NULL) + uu_die(emesg_nomem); + CLEAR_ERROR_INFO(&einfo); + for (prop_pattern = next_pattern_pg(iter); + prop_pattern != NULL; + prop_pattern = next_pattern_pg(iter)) { + /* + * Find the pg_pattern property in this prop_pattern. + * Verify that its value matches the name of the + * pg_pattern. + */ + prop_pg_pattern_name = find_astring_value_in_pg(prop_pattern, + SCF_PROPERTY_TM_PG_PATTERN); + assert(prop_pg_pattern_name != NULL); + if (strcmp(pg_pattern->sc_pgroup_name, + prop_pg_pattern_name) != 0) { + continue; + } + + /* If the property is required, see if it is in the pg. */ + if (is_required(prop_pattern) == 0) + continue; + prop_name = find_astring_value_in_pg(prop_pattern, + SCF_PROPERTY_TM_NAME); + assert(prop_name != NULL); + if (property_find(pg, prop_name) == NULL) { + ec = SCF_TERR_MISSING_PROP; + rc = TVS_VALIDATION; + einfo.ei_type = EIT_MISSING_PROP; + einfo.ei_u.ei_missing_prop.ei_prop_name = prop_name; + if (add_scf_error(errs, ec, pg_pattern, pg, + prop_pattern, NULL, NULL, &einfo) != 0) { + /* + * If we can no longer accumulate errors, + * break out of the loop. + */ + break; + } + } + } + + pg_iter_destroy(iter); + return (rc); +} + +/* + * Check the value at v to see if it falls within any of the ranges at r. + * count is the number of ranges at r, and type tells whether to treat the + * value as signed or unsigned. + * + * Return 1 if the value falls within one of the ranges. Otherwise return + * 0. + */ +static int +value_in_range(value_t *v, scf_type_t type, range_t *r, size_t count) +{ + for (; count > 0; --count, r++) { + if (type == SCF_TYPE_COUNT) { + if ((v->sc_u.sc_count >= + r->rng_u.rng_unsigned.rng_min) && + (v->sc_u.sc_count <= + r->rng_u.rng_unsigned.rng_max)) + return (1); + } else { + if ((v->sc_u.sc_integer >= + r->rng_u.rng_signed.rng_min) && + (v->sc_u.sc_integer <= + r->rng_u.rng_signed.rng_max)) + return (1); + } + } + return (0); +} + +/* + * If the template prop_pattern at pattern contains a constraint_range + * property, use the specified range to validate all the numeric property + * values of the property at prop. + * + * pg is the property group that holds prop, and pg_pattern is the + * pg_pattern for the property group. pg and pg_pattern are only used for + * error reporting. + */ +static tmpl_validate_status_t +tmpl_validate_value_range(pgroup_t *pattern, property_t *prop, pgroup_t *pg, + pgroup_t *pg_pattern, tmpl_errors_t *errs) +{ + uint_t count; + error_info_t einfo; + property_t *range_prop; + range_t *ranges; + tmpl_validate_status_t rc; + scf_type_t type; + value_t *v; + + /* Get the range constraints if they exist. */ + if ((range_prop = property_find(pattern, + SCF_PROPERTY_TM_CONSTRAINT_RANGE)) == NULL) { + /* No range to check. */ + return (TVS_SUCCESS); + } + type = prop->sc_value_type; + if ((type != SCF_TYPE_COUNT) && (type != SCF_TYPE_INTEGER)) { + rc = TVS_BAD_TEMPLATE; + CLEAR_ERROR_INFO(&einfo); + einfo.ei_type = EIT_BAD_TEMPLATE; + einfo.ei_u.ei_bad_template.ei_reason = + gettext("Property does not have correct type for " + "a range specification"); + (void) tmpl_errors_add_im(errs, rc, pg_pattern->sc_parent, + pg_pattern, pg, pattern, prop, NULL, &einfo); + return (rc); + } + if ((rc = get_ranges(range_prop, prop->sc_value_type, &ranges, + &count)) != TVS_SUCCESS) { + rc = TVS_BAD_TEMPLATE; + CLEAR_ERROR_INFO(&einfo); + einfo.ei_type = EIT_BAD_TEMPLATE; + einfo.ei_u.ei_bad_template.ei_reason = gettext("Illegal range " + "value"); + (void) tmpl_errors_add_im(errs, rc, pg_pattern->sc_parent, + pg_pattern, pg, pattern, prop, NULL, &einfo); + return (rc); + } + + /* Set up error info before entering loop. */ + CLEAR_ERROR_INFO(&einfo); + einfo.ei_type = EIT_RANGE; + einfo.ei_u.ei_range.ei_rtype = type; + + /* Compare numeric values of the property to the range. */ + for (v = uu_list_first(prop->sc_property_values); + v != NULL; + v = uu_list_next(prop->sc_property_values, v)) { + if (value_in_range(v, type, ranges, count) == 1) + continue; + if (type == SCF_TYPE_COUNT) { + einfo.ei_u.ei_range.ei_uvalue = v->sc_u.sc_count; + } else { + einfo.ei_u.ei_range.ei_ivalue = v->sc_u.sc_integer; + } + rc = TVS_VALIDATION; + if (add_scf_error(errs, SCF_TERR_RANGE_VIOLATION, pg_pattern, + pg, pattern, prop, v, &einfo) != 0) { + return (rc); + } + } + + return (rc); +} + +/* + * If the prop_pattern has value constraints, verify that all the values + * for the property at prop are legal values. + * + * pg is the property group that holds prop, and pg_pattern is the + * pg_pattern for the property group. pg and pg_pattern are only used for + * error reporting. + */ +static tmpl_validate_status_t +tmpl_validate_values(pgroup_t *prop_pattern, property_t *prop, pgroup_t *pg, + pgroup_t *pg_pattern, tmpl_errors_t *errs) +{ + int found; + uint_t i; + avalues_t *legal; + tmpl_validate_status_t r; + tmpl_validate_status_t rc = TVS_SUCCESS; + value_t *v; + + /* Get list of legal values. */ + r = av_get_values(prop_pattern, SCF_PROPERTY_TM_CONSTRAINT_NAME, + prop->sc_value_type, &legal); + switch (r) { + case TVS_BAD_CONVERSION: + (void) tmpl_errors_add_im(errs, r, pg->sc_parent, pg_pattern, + pg, prop_pattern, prop, NULL, NULL); + return (r); + case TVS_NOMATCH: + /* No constraints in template. */ + return (TVS_SUCCESS); + case TVS_SUCCESS: + /* process the constraints. */ + break; + default: + assert(0); + abort(); + } + + /* Check the property values against the legal values. */ + for (v = uu_list_first(prop->sc_property_values); + v != NULL; + v = uu_list_next(prop->sc_property_values, v)) { + /* Check this property value against the legal values. */ + found = 0; + for (i = 0; (i < legal->av_count) && (found == 0); i++) { + switch (v->sc_type) { + case SCF_TYPE_BOOLEAN: + case SCF_TYPE_COUNT: + if (av_get_unsigned(legal, i) == + v->sc_u.sc_count) { + found = 1; + } + break; + case SCF_TYPE_INTEGER: + if (av_get_integer(legal, i) == + v->sc_u.sc_integer) { + found = 1; + } + break; + default: + if (strcmp(av_get_string(legal, i), + v->sc_u.sc_string) == 0) { + found = 1; + } + break; + } + } + if (found == 0) { + rc = TVS_VALIDATION; + if (add_scf_error(errs, + SCF_TERR_VALUE_CONSTRAINT_VIOLATED, pg_pattern, pg, + prop_pattern, prop, v, NULL) != 0) { + /* + * Exit loop if no longer able to report + * errors. + */ + break; + } + } + } + +out: + av_destroy(legal); + return (rc); +} + +/* + * Verify the following items about the values of property, prop. + * + * - The values all have the type specified by the prop_pattern at + * pattern. + * - Check numeric values against range constraints. + * - If the prop_pattern has one or more value constraints, validate + * the property's values against the constraints. + * + * pg is the property group that holds prop, and pg_pattern is the + * pg_pattern for the property group. pg and pg_pattern are only used for + * error reporting. + */ +static tmpl_validate_status_t +tmpl_validate_value_constraints(pgroup_t *pattern, property_t *prop, + pgroup_t *pg, pgroup_t *pg_pattern, tmpl_errors_t *errs) +{ + tmpl_validate_status_t r; + tmpl_validate_status_t rc; + + rc = tmpl_validate_value_range(pattern, prop, pg, pg_pattern, errs); + r = tmpl_validate_values(pattern, prop, pg, pg_pattern, errs); + if (r != TVS_SUCCESS) + rc = r; + + return (rc); +} + +/* + * Perform the following validations on the property, prop. + * + * - Verify that the property's type agrees with the type specified in + * the prop_pattern template, tmpl. + * - Verify the cardinality. + * - Verify that the property values satisfy the constraints specified + * by the template. + * + * pg is the property group that holds prop, and pg_pattern is the + * pg_pattern for the property group. pg and pg_pattern are only used for + * error reporting. + */ +static tmpl_validate_status_t +tmpl_validate_prop(property_t *prop, pgroup_t *tmpl, pgroup_t *pg, + pgroup_t *pg_pattern, tmpl_errors_t *errs) +{ + scf_tmpl_error_type_t ec; + error_info_t einfo; + tmpl_validate_status_t r; + tmpl_validate_status_t rc = TVS_SUCCESS; + int status; + scf_type_t type; + + r = prop_pattern_type(tmpl, &type); + switch (r) { + case TVS_SUCCESS: + if (type == SCF_TYPE_INVALID) { + rc = TVS_INVALID_TYPE_SPECIFICATION; + r = tmpl_errors_add_im(errs, rc, pg->sc_parent, NULL, + pg, tmpl, NULL, NULL, NULL); + if (r != TVS_SUCCESS) { + /* + * Give up if we can no longer accumulate + * errors. + */ + return (rc); + } + } else { + if (property_is_type(prop, type) == 0) { + CLEAR_ERROR_INFO(&einfo); + rc = TVS_VALIDATION; + ec = SCF_TERR_WRONG_PROP_TYPE; + einfo.ei_type = EIT_PROP_TYPE; + einfo.ei_u.ei_prop_type.ei_specified = type; + einfo.ei_u.ei_prop_type.ei_actual = + prop->sc_value_type; + status = add_scf_error(errs, ec, + pg_pattern, pg, tmpl, prop, NULL, &einfo); + if (status != 0) { + /* + * Give up if we can no longer + * accumulate errors. + */ + return (rc); + } + } + } + break; + case TVS_MISSING_TYPE_SPECIFICATION: + /* + * A null type specification means that we do not need to + * check the property's type. + */ + break; + default: + rc = r; + } + + /* Validate the cardinality */ + r = tmpl_validate_cardinality(tmpl, prop, pg, pg_pattern, errs); + if (r != TVS_SUCCESS) + rc = r; + + /* Validate that property values satisfy constraints. */ + r = tmpl_validate_value_constraints(tmpl, prop, pg, pg_pattern, errs); + if (r != TVS_SUCCESS) + rc = r; + + return (rc); +} + +/* + * Validate the property group at pg by performing the following checks: + * + * - Verify that the types of the pg and the pg_pattern are + * compatible. + * - Verify the properties in the pg. + * - Verify that required properties are present. + */ +static tmpl_validate_status_t +tmpl_validate_pg(entity_t *e, pgroup_t *pg, tmpl_errors_t *errs) +{ + error_info_t einfo; + const char *pg_pattern_type; /* Type declared by pg_pattern. */ + pgroup_t *pg_pattern; /* Prop. group for pg_pattern */ + property_t *prop; + pgroup_t *prop_pattern; + tmpl_validate_status_t r; + tmpl_validate_status_t rc = TVS_SUCCESS; + int stat; + + /* + * See if there is a pg_pattern for this property group. If it + * exists, use it to validate the property group. If there is no + * pg_pattern, then there is no validation to do. + */ + rc = tmpl_find_pg_pattern(e, pg, &pg_pattern); + switch (rc) { + case TVS_SUCCESS: + break; + case TVS_BAD_TEMPLATE: + CLEAR_ERROR_INFO(&einfo); + einfo.ei_type = EIT_BAD_TEMPLATE; + einfo.ei_u.ei_bad_template.ei_reason = gettext("Property " + "group name too long"); + (void) tmpl_errors_add_im(errs, rc, e, NULL, pg, NULL, NULL, + NULL, &einfo); + return (rc); + default: + assert(0); + abort(); + } + if (pg_pattern == NULL) + return (TVS_SUCCESS); + + /* + * If the pg_pattern declares a type, verify that the PG has the + * correct type. + */ + pg_pattern_type = find_type_specification(pg_pattern); + if ((pg_pattern_type != NULL) && + (*pg_pattern_type != 0)) { + if ((pg->sc_pgroup_type != NULL) && + (*(pg->sc_pgroup_type) != 0)) { + if (strcmp(pg_pattern_type, + pg->sc_pgroup_type) != 0) { + rc = TVS_VALIDATION; + stat = add_scf_error(errs, + SCF_TERR_WRONG_PG_TYPE, pg_pattern, pg, + NULL, NULL, NULL, NULL); + if (stat != 0) { + /* + * If we can no longer accumulate + * errors, return without trying to + * do further validation. + */ + return (rc); + } + } + } else { + rc = TVS_MISSING_PG_TYPE; + r = tmpl_errors_add_im(errs, rc, e, pg_pattern, pg, + NULL, NULL, NULL, NULL); + if (r != TVS_SUCCESS) { + /* + * If we can no longer accumulate errors, + * return without trying to do further + * validation. + */ + return (rc); + } + } + } + + /* Verify the properties in the property group. */ + prop = NULL; + while ((prop = next_property(pg, prop)) != NULL) { + r = tmpl_find_prop_pattern(e, pg_pattern, prop, &prop_pattern); + switch (r) { + case TVS_SUCCESS: + /* Found match. Validate property. */ + break; + case TVS_NOMATCH: + /* No prop_patern. Go on to next property. */ + continue; + case TVS_BAD_TEMPLATE: + CLEAR_ERROR_INFO(&einfo); + einfo.ei_type = EIT_BAD_TEMPLATE; + einfo.ei_u.ei_bad_template.ei_reason = + gettext("prop_pattern name too long"); + (void) tmpl_errors_add_im(errs, r, e, NULL, pg, NULL, + NULL, NULL, &einfo); + continue; + default: + assert(0); + abort(); + } + r = tmpl_validate_prop(prop, prop_pattern, pg, pg_pattern, + errs); + if (r != TVS_SUCCESS) + rc = r; + } + + /* + * Confirm required properties are present. + */ + r = tmpl_required_props_present(e, pg, pg_pattern, errs); + if (r != TVS_SUCCESS) + rc = r; + + return (rc); +} + +/* + * Validate that the property groups in the entity conform to the template + * specifications. Specifically, this means do the following: + * + * - Loop through the property groups in the entity skipping the ones + * that are of type "template". + * + * - For the PG search for the corresponding template_pg_pattern + * property group. It is possible that one may not exist. + * + * - Verify that the PG is in conformance with the pg_pattern + * specification if it exists. + */ +static tmpl_validate_status_t +tmpl_validate_entity_pgs(entity_t *e, tmpl_errors_t *errs) +{ + composed_pg_t *cpg; + uu_avl_t *pgroups; + pgroup_t *pg; + tmpl_validate_status_t r; + tmpl_validate_status_t rc = TVS_SUCCESS; + + assert(e->sc_etype == SVCCFG_INSTANCE_OBJECT); + + pgroups = e->sc_u.sc_instance.sc_composed; + for (cpg = uu_avl_first(pgroups); + cpg != NULL; + cpg = uu_avl_next(pgroups, cpg)) { + if (strcmp(cpg->cpg_type, SCF_GROUP_TEMPLATE) == 0) + continue; + pg = CPG2PG(cpg); + if ((r = tmpl_validate_pg(e, pg, errs)) != TVS_SUCCESS) + rc = r; + } + + return (rc); +} + +/* + * Validate the instance, e, by performing the following checks: + * + * - Verify template consistency. + * + * - Validate each property group in the entity is in conformance + * with the template specifications. + * + * - Verify that all required property groups are present in the + * entity. + */ +static tmpl_validate_status_t +tmpl_validate_instance(entity_t *e, tmpl_errors_t *errs) +{ + tmpl_validate_status_t r; + tmpl_validate_status_t rc = TVS_SUCCESS; + int status; + tv_errors_t *ste; + + /* Prepare to collect errors for this instance. */ + ste = tv_errors_create(e->sc_fmri); + status = uu_list_insert_after(errs->te_scf, errs->te_cur_scf, ste); + assert(status == 0); + errs->te_cur_scf = ste; + + /* Verify template consistency */ + rc = tmpl_consistency(e, errs); + + /* Validate the property groups in the entity. */ + r = tmpl_validate_entity_pgs(e, errs); + if (r != TVS_SUCCESS) + rc = r; + + /* Verify that all required property groups are present. */ + r = tmpl_required_pg_present(e, errs); + if (r != TVS_SUCCESS) + rc = r; + + return (rc); +} + +/* + * First validate the instances of the service. + */ +static tmpl_validate_status_t +tmpl_validate_service(entity_t *svc, tmpl_errors_t *errs) +{ + entity_t *inst; + tmpl_validate_status_t r; + tmpl_validate_status_t rc = TVS_SUCCESS; + + assert(svc->sc_etype == SVCCFG_SERVICE_OBJECT); + + load_general_templates(svc); + + /* Validate the service's instances. */ + for (inst = uu_list_first(svc->sc_u.sc_service.sc_service_instances); + inst != NULL; + inst = uu_list_next(svc->sc_u.sc_service.sc_service_instances, + inst)) { + load_instance_restarter(inst); + build_composed_instance(inst); + r = tmpl_validate_instance(inst, errs); + if (r != TVS_SUCCESS) + rc = r; + demolish_composed_instance(inst); + } + + return (rc); +} + +/* + * Validate all services and instances in the bundle against their + * templates. If err_list is not NULL, a tmpl_errors structure will be + * allocated and its address will be returned to err_list. This structure + * can be used to generate error messages. + */ +tmpl_validate_status_t +tmpl_validate_bundle(bundle_t *bndl, tmpl_errors_t **err_list) +{ + tmpl_errors_t *errs = NULL; + entity_t *svc; + tmpl_validate_status_t r; + tmpl_validate_status_t rc = TVS_SUCCESS; + + if (err_list != NULL) + *err_list = NULL; + if (bndl->sc_bundle_type != SVCCFG_MANIFEST) { + semerr(gettext("Bundle is not a manifest. Unable to validate " + "against templates.\n")); + return (TVS_FATAL); + } + + errs = tmpl_errors_create(); + if (errs == NULL) + uu_die(emesg_nomem); + + lscf_prep_hndl(); /* Initialize g_hndl */ + if (load_init() != 0) + uu_die(emesg_nomem); + + /* + * We will process all services in the bundle, unless we get a + * fatal error. That way we can report all errors on all services + * on a single run of svccfg. + */ + for (svc = uu_list_first(bndl->sc_bundle_services); + svc != NULL; + svc = uu_list_next(bndl->sc_bundle_services, svc)) { + if (svc->sc_etype != SVCCFG_SERVICE_OBJECT) { + semerr(gettext("Manifest for %s contains an object " + "named \"%s\" that is not a service.\n"), + bndl->sc_bundle_name, svc->sc_name); + tmpl_errors_destroy(errs); + load_fini(); + return (TVS_FATAL); + } + if ((r = tmpl_validate_service(svc, errs)) != TVS_SUCCESS) + rc = r; + if (r == TVS_FATAL) + break; + } + + if (err_list == NULL) { + tmpl_errors_destroy(errs); + } else { + *err_list = errs; + } + + load_fini(); + + return (rc); +} diff --git a/usr/src/cmd/svc/svccfg/svccfg_xml.c b/usr/src/cmd/svc/svccfg/svccfg_xml.c index 930a61bdd4..2b04a0a1df 100644 --- a/usr/src/cmd/svc/svccfg/svccfg_xml.c +++ b/usr/src/cmd/svc/svccfg/svccfg_xml.c @@ -19,11 +19,18 @@ * CDDL HEADER END */ /* - * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" +/* + * XML document manipulation routines + * + * These routines provide translation to and from the internal representation to + * XML. Directionally-oriented verbs are with respect to the external source, + * so lxml_get_service() fetches a service from the XML file into the + * internal representation. + */ #include <libxml/parser.h> #include <libxml/xinclude.h> @@ -32,7 +39,10 @@ #include <ctype.h> #include <errno.h> #include <libintl.h> +#include <libscf.h> +#include <libscf_priv.h> #include <libuutil.h> +#include <sasl/saslutil.h> #include <stdlib.h> #include <string.h> @@ -43,20 +53,46 @@ #include "svccfg.h" /* - * XML document manipulation routines + * snprintf(3C) format strings for constructing property names that include + * the locale designation. Use %s to indicate where the locale should go. * - * These routines provide translation to and from the internal representation to - * XML. Directionally-oriented verbs are with respect to the external source, - * so lxml_get_service() fetches a service from the XML file into the - * internal representation. + * The VALUE_* symbols are an exception. The firs %s will be replaced with + * "value_". The second %s will be replaced by the name of the value and + * %%s will be replaced by the locale designation. These formats are + * processed twice by snprintf(3C). The first time captures the value name + * and the second time captures the locale. */ - +#define LOCALE_ONLY_FMT ("%s") +#define COMMON_NAME_FMT ("common_name_%s") +#define DESCRIPTION_FMT ("description_%s") +#define UNITS_FMT ("units_%s") +#define VALUE_COMMON_NAME_FMT ("%s%s_common_name_%%s") +#define VALUE_DESCRIPTION_FMT ("%s%s_description_%%s") + +/* Attribute names */ const char * const delete_attr = "delete"; const char * const enabled_attr = "enabled"; +const char * const lang_attr = "lang"; +const char * const manpath_attr = "manpath"; +const char * const max_attr = "max"; +const char * const min_attr = "min"; const char * const name_attr = "name"; const char * const override_attr = "override"; +const char * const required_attr = "required"; +const char * const section_attr = "section"; +const char * const set_attr = "set"; +const char * const target_attr = "target"; +const char * const timeout_seconds_attr = "timeout_seconds"; +const char * const title_attr = "title"; const char * const type_attr = "type"; +const char * const uri_attr = "uri"; const char * const value_attr = "value"; +const char * const version_attr = "version"; +const char * const xml_lang_attr = "xml:lang"; + +/* Attribute values */ +const char * const all_value = "all"; + const char * const true = "true"; const char * const false = "false"; @@ -67,7 +103,10 @@ const char * const false = "false"; static const char *lxml_elements[] = { "astring_list", /* SC_ASTRING */ "boolean_list", /* SC_BOOLEAN */ + "cardinality", /* SC_CARDINALITY */ + "choices", /* SC_CHOICES */ "common_name", /* SC_COMMON_NAME */ + "constraints", /* SC_CONSTRAINTS */ "count_list", /* SC_COUNT */ "create_default_instance", /* SC_INSTANCE_CREATE_DEFAULT */ "dependency", /* SC_DEPENDENCY */ @@ -80,8 +119,10 @@ static const char *lxml_elements[] = { "fmri_list", /* SC_FMRI */ "host_list", /* SC_HOST */ "hostname_list", /* SC_HOSTNAME */ + "include_values", /* SC_INCLUDE_VALUES */ "instance", /* SC_INSTANCE */ "integer_list", /* SC_INTEGER */ + "internal_separators", /* SC_INTERNAL_SEPARATORS */ "loctext", /* SC_LOCTEXT */ "manpage", /* SC_MANPAGE */ "method_context", /* SC_METHOD_CONTEXT */ @@ -92,9 +133,12 @@ static const char *lxml_elements[] = { "net_address_v4_list", /* SC_NET_ADDR_V4 */ "net_address_v6_list", /* SC_NET_ADDR_V6 */ "opaque_list", /* SC_OPAQUE */ + "pg_pattern", /* SC_PG_PATTERN */ + "prop_pattern", /* SC_PROP_PATTERN */ "property", /* SC_PROPERTY */ "property_group", /* SC_PROPERTY_GROUP */ "propval", /* SC_PROPVAL */ + "range", /* SC_RANGE */ "restarter", /* SC_RESTARTER */ "service", /* SC_SERVICE */ "service_bundle", /* SC_SERVICE_BUNDLE */ @@ -103,9 +147,13 @@ static const char *lxml_elements[] = { "stability", /* SC_STABILITY */ "template", /* SC_TEMPLATE */ "time_list", /* SC_TIME */ + "units", /* SC_UNITS */ "uri_list", /* SC_URI */ "ustring_list", /* SC_USTRING */ + "value", /* SC_VALUE */ "value_node", /* SC_VALUE_NODE */ + "values", /* SC_VALUES */ + "visibility", /* SC_VISIBILITY */ "xi:fallback", /* SC_XI_FALLBACK */ "xi:include" /* SC_XI_INCLUDE */ }; @@ -117,7 +165,10 @@ static const char *lxml_elements[] = { static const char *lxml_prop_types[] = { "astring", /* SC_ASTRING */ "boolean", /* SC_BOOLEAN */ + "", /* SC_CARDINALITY */ + "", /* SC_CHOICES */ "", /* SC_COMMON_NAME */ + "", /* SC_CONSTRAINTS */ "count", /* SC_COUNT */ "", /* SC_INSTANCE_CREATE_DEFAULT */ "", /* SC_DEPENDENCY */ @@ -130,8 +181,10 @@ static const char *lxml_prop_types[] = { "fmri", /* SC_FMRI */ "host", /* SC_HOST */ "hostname", /* SC_HOSTNAME */ + "", /* SC_INCLUDE_VALUES */ "", /* SC_INSTANCE */ "integer", /* SC_INTEGER */ + "", /* SC_INTERNAL_SEPARATORS */ "", /* SC_LOCTEXT */ "", /* SC_MANPAGE */ "", /* SC_METHOD_CONTEXT */ @@ -142,9 +195,12 @@ static const char *lxml_prop_types[] = { "net_address_v4", /* SC_NET_ADDR_V4 */ "net_address_v6", /* SC_NET_ADDR_V6 */ "opaque", /* SC_OPAQUE */ + "", /* SC_PG_PATTERN */ + "", /* SC_PROP_PATTERN */ "", /* SC_PROPERTY */ "", /* SC_PROPERTY_GROUP */ "", /* SC_PROPVAL */ + "", /* SC_RANGE */ "", /* SC_RESTARTER */ "", /* SC_SERVICE */ "", /* SC_SERVICE_BUNDLE */ @@ -153,10 +209,14 @@ static const char *lxml_prop_types[] = { "", /* SC_STABILITY */ "", /* SC_TEMPLATE */ "time", /* SC_TIME */ + "", /* SC_UNITS */ "uri", /* SC_URI */ "ustring", /* SC_USTRING */ - "" /* SC_VALUE_NODE */ - "" /* SC_XI_FALLBACK */ + "", /* SC_VALUE */ + "", /* SC_VALUE_NODE */ + "", /* SC_VALUES */ + "", /* SC_VISIBILITY */ + "", /* SC_XI_FALLBACK */ "" /* SC_XI_INCLUDE */ }; @@ -280,6 +340,49 @@ lxml_element_to_scf_type(element_t type) /* NOTREACHED */ } +/* + * Create a SCF_TYPE_BOOLEAN property name pname and attach it to the + * property group at pgrp. The value of the property will be set from the + * attribute named attr. attr must have a value of 0, 1, true or false. + * + * Zero is returned on success. An error is indicated by -1. It indicates + * that either the attribute had an invalid value or that we could not + * attach the property to pgrp. The attribute should not have an invalid + * value if the DTD is correctly written. + */ +static int +new_bool_prop_from_attr(pgroup_t *pgrp, const char *pname, xmlNodePtr n, + const char *attr) +{ + uint64_t bool; + xmlChar *val; + property_t *p; + int r; + + val = xmlGetProp(n, (xmlChar *)attr); + if (val == NULL) + return (0); + + if ((xmlStrcmp(val, (xmlChar *)"0") == 0) || + (xmlStrcmp(val, (xmlChar *)"false") == 0)) { + bool = 0; + } else if ((xmlStrcmp(val, (xmlChar *)"1") == 0) || + (xmlStrcmp(val, (xmlChar *)"true") == 0)) { + bool = 1; + } else { + xmlFree(val); + return (-1); + } + xmlFree(val); + p = internal_property_create(pname, SCF_TYPE_BOOLEAN, 1, bool); + r = internal_attach_property(pgrp, p); + + if (r != 0) + internal_property_free(p); + + return (r); +} + static int new_str_prop_from_attr(pgroup_t *pgrp, const char *pname, scf_type_t ty, xmlNodePtr n, const char *attr) @@ -300,6 +403,39 @@ new_str_prop_from_attr(pgroup_t *pgrp, const char *pname, scf_type_t ty, } static int +new_opt_str_prop_from_attr(pgroup_t *pgrp, const char *pname, scf_type_t ty, + xmlNodePtr n, const char *attr, const char *dflt) +{ + xmlChar *val; + property_t *p; + int r; + + val = xmlGetProp(n, (xmlChar *)attr); + if (val == NULL) { + if (dflt == NULL) { + /* + * A missing attribute is considered to be a + * success in this function, because many of the + * attributes are optional. Missing non-optional + * attributes will be detected later when template + * validation is done. + */ + return (0); + } else { + val = (xmlChar *)dflt; + } + } + + p = internal_property_create(pname, ty, 1, val); + r = internal_attach_property(pgrp, p); + + if (r != 0) + internal_property_free(p); + + return (r); +} + +static int lxml_ignorable_block(xmlNodePtr n) { return ((xmlStrcmp(n->name, (xmlChar *)"text") == 0 || @@ -446,12 +582,12 @@ lxml_get_propval(pgroup_t *pgrp, xmlNodePtr propval) p = internal_property_new(); p->sc_property_name = (char *)xmlGetProp(propval, (xmlChar *)name_attr); - if (p->sc_property_name == NULL) + if ((p->sc_property_name == NULL) || (*p->sc_property_name == 0)) uu_die(gettext("property name missing in group '%s'\n"), pgrp->sc_pgroup_name); type = xmlGetProp(propval, (xmlChar *)type_attr); - if (type == NULL) + if ((type == NULL) || (*type == 0)) uu_die(gettext("property type missing for property '%s/%s'\n"), pgrp->sc_pgroup_name, p->sc_property_name); @@ -459,6 +595,7 @@ lxml_get_propval(pgroup_t *pgrp, xmlNodePtr propval) if (xmlStrcmp(type, (const xmlChar *)lxml_prop_types[r]) == 0) break; } + xmlFree(type); if (r >= sizeof (lxml_prop_types) / sizeof (char *)) uu_die(gettext("property type invalid for property '%s/%s'\n"), pgrp->sc_pgroup_name, p->sc_property_name); @@ -471,6 +608,7 @@ lxml_get_propval(pgroup_t *pgrp, xmlNodePtr propval) pgrp->sc_pgroup_name, p->sc_property_name); v = lxml_make_value(r, val); + xmlFree(val); internal_attach_value(p, v); override = xmlGetProp(propval, (xmlChar *)override_attr); @@ -490,15 +628,17 @@ lxml_get_property(pgroup_t *pgrp, xmlNodePtr property) p = internal_property_new(); - if ((p->sc_property_name = (char *)xmlGetProp(property, - (xmlChar *)name_attr)) == NULL) + if (((p->sc_property_name = (char *)xmlGetProp(property, + (xmlChar *)name_attr)) == NULL) || (*p->sc_property_name == 0)) uu_die(gettext("property name missing in group \'%s\'\n"), pgrp->sc_pgroup_name); - if ((type = xmlGetProp(property, (xmlChar *)type_attr)) == NULL) + if (((type = xmlGetProp(property, (xmlChar *)type_attr)) == NULL) || + (*type == 0)) { uu_die(gettext("property type missing for " "property \'%s/%s\'\n"), pgrp->sc_pgroup_name, p->sc_property_name); + } for (r = 0; r < sizeof (lxml_prop_types) / sizeof (char *); r++) { if (xmlStrcmp(type, (const xmlChar *)lxml_prop_types[r]) == 0) @@ -674,8 +814,8 @@ lxml_get_envvar(xmlNodePtr envvar) char *value; char *ret; - name = (char *)xmlGetProp(envvar, (xmlChar *)"name"); - value = (char *)xmlGetProp(envvar, (xmlChar *)"value"); + name = (char *)xmlGetProp(envvar, (xmlChar *)name_attr); + value = (char *)xmlGetProp(envvar, (xmlChar *)value_attr); if (strlen(name) == 0 || strchr(name, '=') != NULL) uu_die(gettext("Invalid environment variable " @@ -803,7 +943,7 @@ lxml_get_exec_method(entity_t *entity, xmlNodePtr emeth) emeth, "exec") != 0) return (-1); - timeout = xmlGetProp(emeth, (xmlChar *)"timeout_seconds"); + timeout = xmlGetProp(emeth, (xmlChar *)timeout_seconds_attr); if (timeout != NULL) { uint64_t u_timeout; char *endptr; @@ -1093,7 +1233,8 @@ lxml_get_entity_stability(entity_t *entity, xmlNodePtr rstr) property_t *p; xmlChar *stabval; - if ((stabval = xmlGetProp(rstr, (xmlChar *)value_attr)) == NULL) { + if (((stabval = xmlGetProp(rstr, (xmlChar *)value_attr)) == NULL) || + (*stabval == 0)) { uu_warn(gettext("no stability value found\n")); stabval = (xmlChar *)strdup("External"); } @@ -1156,28 +1297,45 @@ lxml_get_restarter(entity_t *entity, xmlNodePtr rstr) return (0); } -static void -sanitize_locale(uchar_t *locale) -{ - for (; *locale != '\0'; locale++) - if (!isalnum(*locale) && *locale != '_') - *locale = '_'; -} - +/* + * Add a property containing the localized text from the manifest. The + * property is added to the property group at pg. The name of the created + * property is based on the format at pn_format. This is an snprintf(3C) + * format containing a single %s conversion specification. At conversion + * time, the %s is replaced by the locale designation. + * + * source is the source element and it is only used for error messages. + */ static int -lxml_get_loctext(entity_t *service, pgroup_t *pg, xmlNodePtr loctext) +lxml_get_loctext(entity_t *service, pgroup_t *pg, xmlNodePtr loctext, + const char *pn_format, const char *source) { + int extra; xmlNodePtr cursor; xmlChar *val; char *stripped, *cp; property_t *p; + char *prop_name; int r; - if ((val = xmlGetProp(loctext, (xmlChar *)"xml:lang")) == NULL) - if ((val = xmlGetProp(loctext, (xmlChar *)"lang")) == NULL) + if (((val = xmlGetProp(loctext, (xmlChar *)xml_lang_attr)) == NULL) || + (*val == 0)) { + if (((val = xmlGetProp(loctext, + (xmlChar *)lang_attr)) == NULL) || (*val == 0)) { val = (xmlChar *)"unknown"; + } + } - sanitize_locale(val); + _scf_sanitize_locale((char *)val); + prop_name = safe_malloc(max_scf_name_len + 1); + if ((extra = snprintf(prop_name, max_scf_name_len + 1, pn_format, + val)) >= max_scf_name_len + 1) { + extra -= max_scf_name_len; + uu_die(gettext("%s attribute is %d characters too long for " + "%s in %s\n"), + xml_lang_attr, extra, source, service->sc_name); + } + xmlFree(val); for (cursor = loctext->xmlChildrenNode; cursor != NULL; cursor = cursor->next) { @@ -1207,45 +1365,54 @@ lxml_get_loctext(entity_t *service, pgroup_t *pg, xmlNodePtr loctext) ; *(cp + 1) = '\0'; - p = internal_property_create((const char *)val, SCF_TYPE_USTRING, 1, + p = internal_property_create(prop_name, SCF_TYPE_USTRING, 1, stripped); r = internal_attach_property(pg, p); - if (r != 0) + if (r != 0) { internal_property_free(p); + free(prop_name); + } return (r); } +/* + * This function processes all loctext elements in the current XML element + * designated by container. A property is created for each loctext element + * and added to the property group at pg. The name of the property is + * derived from the loctext language designation using the format at + * pn_format. pn_format should be an snprintf format string containing one + * %s which is replaced by the language designation. + * + * The function returns 0 on success and -1 if it is unable to attach the + * newly created property to pg. + */ static int -lxml_get_tm_common_name(entity_t *service, xmlNodePtr common_name) +lxml_get_all_loctext(entity_t *service, pgroup_t *pg, xmlNodePtr container, + const char *pn_format, const char *source) { xmlNodePtr cursor; - pgroup_t *pg; - - /* - * Create the property group, if absent. - */ - pg = internal_pgroup_find_or_create(service, - (char *)SCF_PG_TM_COMMON_NAME, (char *)SCF_GROUP_TEMPLATE); /* - * Iterate through one or more loctext elements. The locale is the - * property name; the contents are the ustring value for the property. + * Iterate through one or more loctext elements. The locale is + * used to generate the property name; the contents are the ustring + * value for the property. */ - for (cursor = common_name->xmlChildrenNode; cursor != NULL; + for (cursor = container->xmlChildrenNode; cursor != NULL; cursor = cursor->next) { if (lxml_ignorable_block(cursor)) continue; switch (lxml_xlate_element(cursor->name)) { case SC_LOCTEXT: - if (lxml_get_loctext(service, pg, cursor)) + if (lxml_get_loctext(service, pg, cursor, pn_format, + source)) return (-1); break; default: - uu_die(gettext("illegal element \"%s\" on common_name " - "element for \"%s\"\n"), cursor->name, + uu_die(gettext("illegal element \"%s\" on %s element " + "for \"%s\"\n"), cursor->name, container->name, service->sc_name); break; } @@ -1254,41 +1421,170 @@ lxml_get_tm_common_name(entity_t *service, xmlNodePtr common_name) return (0); } +/* + * Obtain the specified cardinality attribute and place it in a property + * named prop_name. The converted attribute is placed at *value, and the + * newly created property is returned to propp. NULL is returned to propp + * if the attribute is not provided in the manifest. + * + * 0 is returned upon success, and -1 indicates that the manifest contained + * an invalid cardinality value. + */ static int -lxml_get_tm_description(entity_t *service, xmlNodePtr description) +lxml_get_cardinality_attribute(entity_t *service, xmlNodePtr cursor, + const char *attr_name, const char *prop_name, uint64_t *value, + property_t **propp) { - xmlNodePtr cursor; - pgroup_t *pg; + char *c; + property_t *p; + xmlChar *val; + uint64_t count; + char *endptr; - /* - * Create the property group, if absent. - */ - pg = internal_pgroup_find_or_create(service, - (char *)SCF_PG_TM_DESCRIPTION, (char *)SCF_GROUP_TEMPLATE); + *propp = NULL; + val = xmlGetProp(cursor, (xmlChar *)attr_name); + if (val == NULL) + return (0); + if (*val == 0) { + xmlFree(val); + return (0); + } /* - * Iterate through one or more loctext elements. The locale is the - * property name; the contents are the ustring value for the property. + * Make sure that the string at val doesn't have a leading minus + * sign. The strtoull() call below does not catch this problem. */ - for (cursor = description->xmlChildrenNode; cursor != NULL; - cursor = cursor->next) { - if (lxml_ignorable_block(cursor)) + for (c = (char *)val; *c != 0; c++) { + if (isspace(*c)) continue; - - switch (lxml_xlate_element(cursor->name)) { - case SC_LOCTEXT: - if (lxml_get_loctext(service, pg, cursor)) - return (-1); - break; - default: - uu_die(gettext("illegal element \"%s\" on description " - "element for \"%s\"\n"), cursor->name, - service->sc_name); + if (isdigit(*c)) break; + semerr(gettext("\"%c\" is not a legal character in the %s " + "attribute of the %s element in %s.\n"), *c, + attr_name, prop_name, service->sc_name); + xmlFree(val); + return (-1); + } + errno = 0; + count = strtoull((char *)val, &endptr, 10); + if (errno != 0 || endptr == (char *)val || *endptr) { + semerr(gettext("\"%s\" is not a legal number for the %s " + "attribute of the %s element in %s.\n"), (char *)val, + attr_name, prop_name, service->sc_name); + xmlFree(val); + return (-1); + } + + xmlFree(val); + + /* Value is valid. Create the property. */ + p = internal_property_create(prop_name, SCF_TYPE_COUNT, 1, count); + *value = count; + *propp = p; + return (0); +} + +/* + * The cardinality is specified by two attributes max and min at cursor. + * Both are optional, but if present they must be unsigned integers. + */ +static int +lxml_get_tm_cardinality(entity_t *service, pgroup_t *pg, xmlNodePtr cursor) +{ + int min_attached = 0; + int compare = 1; + property_t *min_prop; + property_t *max_prop; + uint64_t max; + uint64_t min; + int r; + + r = lxml_get_cardinality_attribute(service, cursor, min_attr, + SCF_PROPERTY_TM_CARDINALITY_MIN, &min, &min_prop); + if (r != 0) + return (r); + if (min_prop == NULL) + compare = 0; + r = lxml_get_cardinality_attribute(service, cursor, max_attr, + SCF_PROPERTY_TM_CARDINALITY_MAX, &max, &max_prop); + if (r != 0) + goto errout; + if ((max_prop != NULL) && (compare == 1)) { + if (max < min) { + semerr(gettext("Cardinality max is less than min for " + "the %s element in %s.\n"), pg->sc_pgroup_name, + service->sc_fmri); + goto errout; } } + /* Attach the properties to the property group. */ + if (min_prop) { + if (internal_attach_property(pg, min_prop) == 0) { + min_attached = 1; + } else { + goto errout; + } + } + if (max_prop) { + if (internal_attach_property(pg, max_prop) != 0) { + if (min_attached) + internal_detach_property(pg, min_prop); + goto errout; + } + } return (0); + +errout: + if (min_prop) + internal_property_free(min_prop); + if (max_prop) + internal_property_free(max_prop); + return (-1); +} + +/* + * Get the common_name which is present as localized text at common_name in + * the manifest. The common_name is stored as the value of a property in + * the property group whose name is SCF_PG_TM_COMMON_NAME and type is + * SCF_GROUP_TEMPLATE. This property group will be created in service if + * it is not already there. + */ +static int +lxml_get_tm_common_name(entity_t *service, xmlNodePtr common_name) +{ + pgroup_t *pg; + + /* + * Create the property group, if absent. + */ + pg = internal_pgroup_find_or_create(service, SCF_PG_TM_COMMON_NAME, + SCF_GROUP_TEMPLATE); + + return (lxml_get_all_loctext(service, pg, common_name, LOCALE_ONLY_FMT, + "common_name")); +} + +/* + * Get the description which is present as localized text at description in + * the manifest. The description is stored as the value of a property in + * the property group whose name is SCF_PG_TM_DESCRIPTION and type is + * SCF_GROUP_TEMPLATE. This property group will be created in service if + * it is not already there. + */ +static int +lxml_get_tm_description(entity_t *service, xmlNodePtr description) +{ + pgroup_t *pg; + + /* + * Create the property group, if absent. + */ + pg = internal_pgroup_find_or_create(service, SCF_PG_TM_DESCRIPTION, + SCF_GROUP_TEMPLATE); + + return (lxml_get_all_loctext(service, pg, description, + LOCALE_ONLY_FMT, "description")); } static char * @@ -1329,6 +1625,108 @@ lxml_label_to_groupname(const char *prefix, const char *in) return (out); } +/* + * If *p is NULL, astring_prop_value() first creates a property with the + * name specified in prop_name. The address of the newly created property + * is placed in *p. + * + * In either case, newly created property or existing property, a new + * SCF_TYPE_ASTRING value will created and attached to the property at *p. + * The value of the newly created property is prop_value. + * + * free_flag is used to indicate whether or not the memory at prop_value + * should be freed when the property is freed by a call to + * internal_property_free(). + */ +static void +astring_prop_value(property_t **p, const char *prop_name, char *prop_value, + boolean_t free_flag) +{ + value_t *v; + + if (*p == NULL) { + /* Create the property */ + *p = internal_property_new(); + (*p)->sc_property_name = (char *)prop_name; + (*p)->sc_value_type = SCF_TYPE_ASTRING; + } + + /* Add the property value to the property's list of values. */ + v = internal_value_new(); + v->sc_type = SCF_TYPE_ASTRING; + if (free_flag == B_TRUE) + v->sc_free = lxml_free_str; + v->sc_u.sc_string = prop_value; + internal_attach_value(*p, v); +} + +/* + * If p points to a null pointer, create an internal_separators property + * saving the address at p. For each character at seps create a property + * value and attach it to the property at p. + */ +static void +seps_to_prop_values(property_t **p, xmlChar *seps) +{ + value_t *v; + char val_str[2]; + + if (*p == NULL) { + *p = internal_property_new(); + (*p)->sc_property_name = + (char *)SCF_PROPERTY_INTERNAL_SEPARATORS; + (*p)->sc_value_type = SCF_TYPE_ASTRING; + } + + /* Add the values to the property's list. */ + val_str[1] = 0; /* Terminate the string. */ + for (; *seps != 0; seps++) { + v = internal_value_new(); + v->sc_type = (*p)->sc_value_type; + v->sc_free = lxml_free_str; + val_str[0] = *seps; + v->sc_u.sc_string = strdup(val_str); + if (v->sc_u.sc_string == NULL) + uu_die(gettext("Out of memory\n")); + internal_attach_value(*p, v); + } +} + +/* + * Create an internal_separators property and attach it to the property + * group at pg. The separator characters are provided in the text nodes + * that are the children of seps. Each separator character is stored as a + * property value in the internal_separators property. + */ +static int +lxml_get_tm_internal_seps(entity_t *service, pgroup_t *pg, xmlNodePtr seps) +{ + xmlNodePtr cursor; + property_t *prop = NULL; + int r; + + for (cursor = seps->xmlChildrenNode; cursor != NULL; + cursor = cursor->next) { + if (strcmp("text", (const char *)cursor->name) == 0) { + seps_to_prop_values(&prop, cursor->content); + } else if (strcmp("comment", (const char *)cursor->name) != 0) { + uu_die(gettext("illegal element \"%s\" on %s element " + "for \"%s\"\n"), cursor->name, seps->name, + service->sc_name); + } + } + if (prop == NULL) { + semerr(gettext("The %s element in %s had an empty list of " + "separators.\n"), (const char *)seps->name, + service->sc_name); + return (-1); + } + r = internal_attach_property(pg, prop); + if (r != 0) + internal_property_free(prop); + return (r); +} + static int lxml_get_tm_manpage(entity_t *service, xmlNodePtr manpage) { @@ -1340,9 +1738,10 @@ lxml_get_tm_manpage(entity_t *service, xmlNodePtr manpage) * Fetch title attribute, convert to something sanitized, and create * property group. */ - title = xmlGetProp(manpage, (xmlChar *)"title"); + title = xmlGetProp(manpage, (xmlChar *)title_attr); pgname = (char *)lxml_label_to_groupname(SCF_PG_TM_MAN_PREFIX, (const char *)title); + xmlFree(title); pg = internal_pgroup_find_or_create(service, pgname, (char *)SCF_GROUP_TEMPLATE); @@ -1350,12 +1749,12 @@ lxml_get_tm_manpage(entity_t *service, xmlNodePtr manpage) /* * Each attribute is an astring property within the group. */ - if (new_str_prop_from_attr(pg, "title", SCF_TYPE_ASTRING, manpage, - "title") != 0 || - new_str_prop_from_attr(pg, "section", SCF_TYPE_ASTRING, manpage, - "section") != 0 || - new_str_prop_from_attr(pg, "manpath", SCF_TYPE_ASTRING, manpage, - "manpath") != 0) + if (new_str_prop_from_attr(pg, SCF_PROPERTY_TM_TITLE, + SCF_TYPE_ASTRING, manpage, title_attr) != 0 || + new_str_prop_from_attr(pg, SCF_PROPERTY_TM_SECTION, + SCF_TYPE_ASTRING, manpage, section_attr) != 0 || + new_str_prop_from_attr(pg, SCF_PROPERTY_TM_MANPATH, + SCF_TYPE_ASTRING, manpage, manpath_attr) != 0) return (-1); return (0); @@ -1372,21 +1771,22 @@ lxml_get_tm_doclink(entity_t *service, xmlNodePtr doc_link) * Fetch name attribute, convert name to something sanitized, and create * property group. */ - name = xmlGetProp(doc_link, (xmlChar *)"name"); + name = xmlGetProp(doc_link, (xmlChar *)name_attr); pgname = (char *)lxml_label_to_groupname(SCF_PG_TM_DOC_PREFIX, (const char *)name); pg = internal_pgroup_find_or_create(service, pgname, (char *)SCF_GROUP_TEMPLATE); + xmlFree(name); /* * Each attribute is an astring property within the group. */ - if (new_str_prop_from_attr(pg, "name", SCF_TYPE_ASTRING, doc_link, - "name") != 0 || - new_str_prop_from_attr(pg, "uri", SCF_TYPE_ASTRING, doc_link, - "uri") != 0) + if (new_str_prop_from_attr(pg, SCF_PROPERTY_TM_NAME, SCF_TYPE_ASTRING, + doc_link, name_attr) != 0 || + new_str_prop_from_attr(pg, SCF_PROPERTY_TM_URI, SCF_TYPE_ASTRING, + doc_link, uri_attr) != 0) return (-1); return (0); @@ -1420,6 +1820,919 @@ lxml_get_tm_documentation(entity_t *service, xmlNodePtr documentation) } static int +lxml_get_prop_pattern_attributes(pgroup_t *pg, xmlNodePtr cursor) +{ + if (new_opt_str_prop_from_attr(pg, SCF_PROPERTY_TM_NAME, + SCF_TYPE_ASTRING, cursor, name_attr, NULL) != 0) { + return (-1); + } + if (new_opt_str_prop_from_attr(pg, SCF_PROPERTY_TM_TYPE, + SCF_TYPE_ASTRING, cursor, type_attr, "") != 0) { + return (-1); + } + if (new_bool_prop_from_attr(pg, SCF_PROPERTY_TM_REQUIRED, cursor, + required_attr) != 0) + return (-1); + return (0); +} + +static int +lxml_get_tm_include_values(entity_t *service, pgroup_t *pg, + xmlNodePtr include_values, const char *prop_name) +{ + boolean_t attach_to_pg = B_FALSE; + property_t *p; + int r = 0; + char *type; + + /* Get the type attribute of the include_values element. */ + type = (char *)xmlGetProp(include_values, (const xmlChar *)type_attr); + if ((type == NULL) || (*type == 0)) { + uu_die(gettext("%s element requires a %s attribute in the %s " + "service.\n"), include_values->name, type_attr, + service->sc_name); + } + + /* Add the type to the values of the prop_name property. */ + p = internal_property_find(pg, prop_name); + if (p == NULL) + attach_to_pg = B_TRUE; + astring_prop_value(&p, prop_name, type, B_FALSE); + if (attach_to_pg == B_TRUE) { + r = internal_attach_property(pg, p); + if (r != 0) + internal_property_free(p); + } + return (r); +} + +#define RC_MIN 0 +#define RC_MAX 1 +#define RC_COUNT 2 + +/* + * Verify that the strings at min and max are valid numeric strings. Also + * verify that max is numerically >= min. + * + * 0 is returned if the range is valid, and -1 is returned if it is not. + */ +static int +verify_range(entity_t *service, xmlNodePtr range, char *min, char *max) +{ + char *c; + int i; + int is_signed = 0; + int inverted = 0; + const char *limit[RC_COUNT]; + char *strings[RC_COUNT]; + uint64_t urange[RC_COUNT]; /* unsigned range. */ + int64_t srange[RC_COUNT]; /* signed range. */ + + strings[RC_MIN] = min; + strings[RC_MAX] = max; + limit[RC_MIN] = min_attr; + limit[RC_MAX] = max_attr; + + /* See if the range is signed. */ + for (i = 0; (i < RC_COUNT) && (is_signed == 0); i++) { + c = strings[i]; + while (isspace(*c)) { + c++; + } + if (*c == '-') + is_signed = 1; + } + + /* Attempt to convert the strings. */ + for (i = 0; i < RC_COUNT; i++) { + errno = 0; + if (is_signed) { + srange[i] = strtoll(strings[i], &c, 0); + } else { + urange[i] = strtoull(strings[i], &c, 0); + } + if ((errno != 0) || (c == strings[i]) || (*c != 0)) { + /* Conversion failed. */ + uu_die(gettext("Unable to convert %s for the %s " + "element in service %s.\n"), limit[i], + (char *)range->name, service->sc_name); + } + } + + /* Make sure that min is <= max */ + if (is_signed) { + if (srange[RC_MAX] < srange[RC_MIN]) + inverted = 1; + } else { + if (urange[RC_MAX] < urange[RC_MIN]) + inverted = 1; + } + if (inverted != 0) { + semerr(gettext("Maximum less than minimum for the %s element " + "in service %s.\n"), (char *)range->name, + service->sc_name); + return (-1); + } + + return (0); +} + +/* + * This, function creates a property named prop_name. The range element + * should have two attributes -- min and max. The property value then + * becomes the concatenation of their value separated by a comma. The + * property is then attached to the property group at pg. + * + * If pg already contains a property with a name of prop_name, it is only + * necessary to create a new value and attach it to the existing property. + */ +static int +lxml_get_tm_range(entity_t *service, pgroup_t *pg, xmlNodePtr range, + const char *prop_name) +{ + boolean_t attach_to_pg = B_FALSE; + char *max; + char *min; + property_t *p; + char *prop_value; + int r = 0; + + /* Get max and min from the XML description. */ + max = (char *)xmlGetProp(range, (xmlChar *)max_attr); + if ((max == NULL) || (*max == 0)) { + uu_die(gettext("%s element is missing the %s attribute in " + "service %s.\n"), (char *)range->name, max_attr, + service->sc_name); + } + min = (char *)xmlGetProp(range, (xmlChar *)min_attr); + if ((min == NULL) || (*min == 0)) { + uu_die(gettext("%s element is missing the %s attribute in " + "service %s.\n"), (char *)range->name, min_attr, + service->sc_name); + } + if (verify_range(service, range, min, max) != 0) { + xmlFree(min); + xmlFree(max); + return (-1); + } + + /* Property value is concatenation of min and max. */ + prop_value = safe_malloc(max_scf_value_len + 1); + if (snprintf(prop_value, max_scf_value_len + 1, "%s,%s", min, max) >= + max_scf_value_len + 1) { + uu_die(gettext("min and max are too long for the %s element " + "of %s.\n"), (char *)range->name, service->sc_name); + } + xmlFree(min); + xmlFree(max); + + /* + * If necessary create the property and attach it to the property + * group. + */ + p = internal_property_find(pg, prop_name); + if (p == NULL) + attach_to_pg = B_TRUE; + astring_prop_value(&p, prop_name, prop_value, B_TRUE); + if (attach_to_pg == B_TRUE) { + r = internal_attach_property(pg, p); + if (r != 0) { + internal_property_free(p); + } + } + return (r); +} + +/* + * Determine how many plain characters are represented by count Base32 + * encoded characters. 5 plain text characters are converted to 8 Base32 + * characters. + */ +static size_t +encoded_count_to_plain(size_t count) +{ + return (5 * ((count + 7) / 8)); +} + +/* + * The value element contains 0 or 1 common_name element followed by 0 or 1 + * description element. It also has a required attribute called "name". + * The common_name and description are stored as property values in pg. + * The property names are: + * value_<name>_common_name_<lang> + * value_<name>_description_<lang> + * + * The <name> portion of the preceeding proper names requires more + * explanation. Ideally it would just the name attribute of this value + * element. Unfortunately, the name attribute can contain characters that + * are not legal in a property name. Thus, we base 32 encode the name + * attribute and use that for <name>. + * + * There are cases where the caller needs to know the name, so it is + * returned through the name_value pointer if it is not NULL. + * + * Parameters: + * service - Information about the service that is being + * processed. This function only uses this parameter + * for producing error messages. + * + * pg - The property group to receive the newly created + * properties. + * + * value - Pointer to the value element in the XML tree. + * + * name_value - Address to receive the value of the name attribute. + * The caller must free the memory. + */ +static int +lxml_get_tm_value_element(entity_t *service, pgroup_t *pg, xmlNodePtr value, + char **name_value) +{ + char *common_name_fmt; + xmlNodePtr cursor; + char *description_fmt; + char *encoded_value = NULL; + size_t extra; + char *value_name; + int r = 0; + + common_name_fmt = safe_malloc(max_scf_name_len + 1); + description_fmt = safe_malloc(max_scf_name_len + 1); + + /* + * Get the value of our name attribute, so that we can use it to + * construct property names. + */ + value_name = (char *)xmlGetProp(value, (xmlChar *)name_attr); + /* The value name must be present, but it can be empty. */ + if (value_name == NULL) { + uu_die(gettext("%s element requires a %s attribute in the %s " + "service.\n"), (char *)value->name, name_attr, + service->sc_name); + } + + /* + * The value_name may contain characters that are not valid in in a + * property name. So we will encode value_name and then use the + * encoded value in the property name. + */ + encoded_value = safe_malloc(max_scf_name_len + 1); + if (scf_encode32(value_name, strlen(value_name), encoded_value, + max_scf_name_len + 1, &extra, SCF_ENCODE32_PAD) != 0) { + extra = encoded_count_to_plain(extra - max_scf_name_len); + uu_die(gettext("Constructed property name is %u characters " + "too long for value \"%s\" in the %s service.\n"), + extra, value_name, service->sc_name); + } + if ((extra = snprintf(common_name_fmt, max_scf_name_len + 1, + VALUE_COMMON_NAME_FMT, SCF_PROPERTY_TM_VALUE_PREFIX, + encoded_value)) >= max_scf_name_len + 1) { + extra = encoded_count_to_plain(extra - max_scf_name_len); + uu_die(gettext("Name attribute is " + "%u characters too long for %s in service %s\n"), + extra, (char *)value->name, service->sc_name); + } + if ((extra = snprintf(description_fmt, max_scf_name_len + 1, + VALUE_DESCRIPTION_FMT, SCF_PROPERTY_TM_VALUE_PREFIX, + encoded_value)) >= max_scf_name_len + 1) { + extra = encoded_count_to_plain(extra - max_scf_name_len); + uu_die(gettext("Name attribute is " + "%u characters too long for %s in service %s\n"), + extra, (char *)value->name, service->sc_name); + } + + for (cursor = value->xmlChildrenNode; + cursor != NULL; + cursor = cursor->next) { + if (lxml_ignorable_block(cursor)) + continue; + switch (lxml_xlate_element(cursor->name)) { + case SC_COMMON_NAME: + r = lxml_get_all_loctext(service, pg, cursor, + common_name_fmt, (const char *)cursor->name); + break; + case SC_DESCRIPTION: + r = lxml_get_all_loctext(service, pg, cursor, + description_fmt, (const char *)cursor->name); + break; + default: + uu_die(gettext("\"%s\" is an illegal element in %s " + "of service %s\n"), (char *)cursor->name, + (char *)value->name, service->sc_name); + } + if (r != 0) + break; + } + + free(description_fmt); + free(common_name_fmt); + if (r == 0) { + *name_value = safe_strdup(value_name); + } + xmlFree(value_name); + free(encoded_value); + return (r); +} + +static int +lxml_get_tm_choices(entity_t *service, pgroup_t *pg, xmlNodePtr choices) +{ + xmlNodePtr cursor; + char *name_value; + property_t *name_prop = NULL; + int r = 0; + + for (cursor = choices->xmlChildrenNode; + (cursor != NULL) && (r == 0); + cursor = cursor->next) { + if (lxml_ignorable_block(cursor)) + continue; + switch (lxml_xlate_element(cursor->name)) { + case SC_INCLUDE_VALUES: + (void) lxml_get_tm_include_values(service, pg, cursor, + SCF_PROPERTY_TM_CHOICES_INCLUDE_VALUES); + break; + case SC_RANGE: + r = lxml_get_tm_range(service, pg, cursor, + SCF_PROPERTY_TM_CHOICES_RANGE); + if (r != 0) + goto out; + break; + case SC_VALUE: + r = lxml_get_tm_value_element(service, pg, cursor, + &name_value); + if (r == 0) { + /* + * There is no need to free the memory + * associated with name_value, because the + * property value will end up pointing to + * the memory. + */ + astring_prop_value(&name_prop, + SCF_PROPERTY_TM_CHOICES_NAME, name_value, + B_TRUE); + } else { + goto out; + } + break; + default: + uu_die(gettext("%s is an invalid element of " + "choices for service %s.\n"), cursor->name, + service->sc_name); + } + } + +out: + /* Attach the name property if we created one. */ + if ((r == 0) && (name_prop != NULL)) { + r = internal_attach_property(pg, name_prop); + } + if ((r != 0) && (name_prop != NULL)) { + internal_property_free(name_prop); + } + + return (r); +} + +static int +lxml_get_tm_constraints(entity_t *service, pgroup_t *pg, xmlNodePtr constraints) +{ + xmlNodePtr cursor; + char *name_value; + property_t *name_prop = NULL; + int r = 0; + + for (cursor = constraints->xmlChildrenNode; + (cursor != NULL) && (r == 0); + cursor = cursor->next) { + if (lxml_ignorable_block(cursor)) + continue; + switch (lxml_xlate_element(cursor->name)) { + case SC_RANGE: + r = lxml_get_tm_range(service, pg, cursor, + SCF_PROPERTY_TM_CONSTRAINT_RANGE); + if (r != 0) + goto out; + break; + case SC_VALUE: + r = lxml_get_tm_value_element(service, pg, cursor, + &name_value); + if (r == 0) { + /* + * There is no need to free the memory + * associated with name_value, because the + * property value will end up pointing to + * the memory. + */ + astring_prop_value(&name_prop, + SCF_PROPERTY_TM_CONSTRAINT_NAME, name_value, + B_TRUE); + } else { + goto out; + } + break; + default: + uu_die(gettext("%s is an invalid element of " + "constraints for service %s.\n"), cursor->name, + service->sc_name); + } + } + +out: + /* Attach the name property if we created one. */ + if ((r == 0) && (name_prop != NULL)) { + r = internal_attach_property(pg, name_prop); + } + if ((r != 0) && (name_prop != NULL)) { + internal_property_free(name_prop); + } + + return (r); +} + +/* + * The values element contains one or more value elements. + */ +static int +lxml_get_tm_values(entity_t *service, pgroup_t *pg, xmlNodePtr values) +{ + xmlNodePtr cursor; + char *name_value; + property_t *name_prop = NULL; + int r = 0; + + for (cursor = values->xmlChildrenNode; + (cursor != NULL) && (r == 0); + cursor = cursor->next) { + if (lxml_ignorable_block(cursor)) + continue; + if (lxml_xlate_element(cursor->name) != SC_VALUE) { + uu_die(gettext("\"%s\" is an illegal element in the " + "%s element of %s\n"), (char *)cursor->name, + (char *)values->name, service->sc_name); + } + r = lxml_get_tm_value_element(service, pg, cursor, &name_value); + if (r == 0) { + /* + * There is no need to free the memory + * associated with name_value, because the + * property value will end up pointing to + * the memory. + */ + astring_prop_value(&name_prop, + SCF_PROPERTY_TM_VALUES_NAME, name_value, + B_TRUE); + } + } + + /* Attach the name property if we created one. */ + if ((r == 0) && (name_prop != NULL)) { + r = internal_attach_property(pg, name_prop); + } + if ((r != 0) && (name_prop != NULL)) { + internal_property_free(name_prop); + } + + return (r); +} + +/* + * This function processes a prop_pattern element within a pg_pattern XML + * element. First it creates a property group to hold the prop_pattern + * information. The name of this property group is the concatenation of: + * - SCF_PG_TM_PROP_PATTERN_PREFIX + * - The unique part of the property group name of the enclosing + * pg_pattern. The property group name of the enclosing pg_pattern + * is passed to us in pgpat_name. The unique part, is the part + * following SCF_PG_TM_PG_PATTERN_PREFIX. + * - The name of this prop_pattern element. + * + * After creating the property group, the prop_pattern attributes are saved + * as properties in the PG. Finally, the prop_pattern elements are + * processed and added to the PG. + */ +static int +lxml_get_tm_prop_pattern(entity_t *service, xmlNodePtr prop_pattern, + const char *pgpat_name) +{ + xmlNodePtr cursor; + int extra; + pgroup_t *pg; + property_t *p; + char *pg_name; + size_t prefix_len; + xmlChar *prop_pattern_name; + int r; + const char *unique; + value_t *v; + + /* Find the unique part of the pg_pattern property group name. */ + prefix_len = strlen(SCF_PG_TM_PG_PAT_BASE); + assert(strncmp(pgpat_name, SCF_PG_TM_PG_PAT_BASE, prefix_len) == 0); + unique = pgpat_name + prefix_len; + + /* + * We need to get the value of the name attribute first. The + * prop_pattern name as well as the name of the enclosing + * pg_pattern both constitute part of the name of the property + * group that we will create. + */ + prop_pattern_name = xmlGetProp(prop_pattern, (xmlChar *)name_attr); + if ((prop_pattern_name == NULL) || (*prop_pattern_name == 0)) { + semerr(gettext("prop_pattern name is missing for %s\n"), + service->sc_name); + return (-1); + } + if (uu_check_name((const char *)prop_pattern_name, + UU_NAME_DOMAIN) != 0) { + semerr(gettext("prop_pattern name, \"%s\", for %s is not " + "valid.\n"), prop_pattern_name, service->sc_name); + xmlFree(prop_pattern_name); + return (-1); + } + pg_name = safe_malloc(max_scf_name_len + 1); + if ((extra = snprintf(pg_name, max_scf_name_len + 1, "%s%s_%s", + SCF_PG_TM_PROP_PATTERN_PREFIX, unique, + (char *)prop_pattern_name)) >= max_scf_name_len + 1) { + uu_die(gettext("prop_pattern name, \"%s\", for %s is %d " + "characters too long\n"), (char *)prop_pattern_name, + service->sc_name, extra - max_scf_name_len); + } + + /* + * Create the property group, the property referencing the pg_pattern + * name, and add the prop_pattern attributes to the property group. + */ + pg = internal_pgroup_create_strict(service, pg_name, + SCF_GROUP_TEMPLATE_PROP_PATTERN); + if (pg == NULL) { + uu_die(gettext("Property group for prop_pattern, \"%s\", " + "already exists in %s\n"), prop_pattern_name, + service->sc_name); + } + + p = internal_property_create(SCF_PROPERTY_TM_PG_PATTERN, + SCF_TYPE_ASTRING, 1, safe_strdup(pgpat_name)); + /* + * Unfortunately, internal_property_create() does not set the free + * function for the value, so we'll set it now. + */ + v = uu_list_first(p->sc_property_values); + v->sc_free = lxml_free_str; + if (internal_attach_property(pg, p) != 0) + internal_property_free(p); + + + r = lxml_get_prop_pattern_attributes(pg, prop_pattern); + if (r != 0) + goto out; + + /* + * Now process the elements of prop_pattern + */ + for (cursor = prop_pattern->xmlChildrenNode; + cursor != NULL; + cursor = cursor->next) { + if (lxml_ignorable_block(cursor)) + continue; + + switch (lxml_xlate_element(cursor->name)) { + case SC_CARDINALITY: + r = lxml_get_tm_cardinality(service, pg, cursor); + if (r != 0) + goto out; + break; + case SC_CHOICES: + r = lxml_get_tm_choices(service, pg, cursor); + if (r != 0) + goto out; + break; + case SC_COMMON_NAME: + (void) lxml_get_all_loctext(service, pg, cursor, + COMMON_NAME_FMT, (const char *)cursor->name); + break; + case SC_CONSTRAINTS: + r = lxml_get_tm_constraints(service, pg, cursor); + if (r != 0) + goto out; + break; + case SC_DESCRIPTION: + (void) lxml_get_all_loctext(service, pg, cursor, + DESCRIPTION_FMT, (const char *)cursor->name); + break; + case SC_INTERNAL_SEPARATORS: + r = lxml_get_tm_internal_seps(service, pg, cursor); + if (r != 0) + goto out; + break; + case SC_UNITS: + (void) lxml_get_all_loctext(service, pg, cursor, + UNITS_FMT, "units"); + break; + case SC_VALUES: + (void) lxml_get_tm_values(service, pg, cursor); + break; + case SC_VISIBILITY: + /* + * The visibility element is empty, so we only need + * to proccess the value attribute. + */ + (void) new_str_prop_from_attr(pg, + SCF_PROPERTY_TM_VISIBILITY, SCF_TYPE_ASTRING, + cursor, value_attr); + break; + default: + uu_die(gettext("illegal element \"%s\" in prop_pattern " + "for service \"%s\"\n"), cursor->name, + service->sc_name); + } + } + +out: + xmlFree(prop_pattern_name); + free(pg_name); + return (r); +} + +/* + * Get the pg_pattern attributes and save them as properties in the + * property group at pg. The pg_pattern element accepts four attributes -- + * name, type, required and target. + */ +static int +lxml_get_pg_pattern_attributes(pgroup_t *pg, xmlNodePtr cursor) +{ + if (new_opt_str_prop_from_attr(pg, SCF_PROPERTY_TM_NAME, + SCF_TYPE_ASTRING, cursor, name_attr, NULL) != 0) { + return (-1); + } + if (new_opt_str_prop_from_attr(pg, SCF_PROPERTY_TM_TYPE, + SCF_TYPE_ASTRING, cursor, type_attr, NULL) != 0) { + return (-1); + } + if (new_opt_str_prop_from_attr(pg, SCF_PROPERTY_TM_TARGET, + SCF_TYPE_ASTRING, cursor, target_attr, NULL) != 0) { + return (-1); + } + if (new_bool_prop_from_attr(pg, SCF_PROPERTY_TM_REQUIRED, cursor, + required_attr) != 0) + return (-1); + return (0); +} + +/* + * There are several restrictions on the pg_pattern attributes that cannot + * be specifed in the service bundle DTD. This function verifies that + * those restrictions have been satisfied. The restrictions are: + * + * - The target attribute may have a value of "instance" only when the + * template block is in a service declaration. + * + * - The target attribute may have a value of "delegate" only when the + * template block applies to a restarter. + * + * - The target attribute may have a value of "all" only when the + * template block applies to the master restarter. + * + * The function returns 0 on success and -1 on failure. + */ +static int +verify_pg_pattern_attributes(entity_t *s, pgroup_t *pg) +{ + int is_restarter; + property_t *target; + value_t *v; + + /* Find the value of the target property. */ + target = internal_property_find(pg, SCF_PROPERTY_TM_TARGET); + if (target == NULL) { + uu_die(gettext("pg_pattern is missing the %s attribute " + "in %s\n"), target_attr, s->sc_name); + return (-1); + } + v = uu_list_first(target->sc_property_values); + assert(v != NULL); + assert(v->sc_type == SCF_TYPE_ASTRING); + + /* + * If target has a value of instance, the template must be in a + * service object. + */ + if (strcmp(v->sc_u.sc_string, "instance") == 0) { + if (s->sc_etype != SVCCFG_SERVICE_OBJECT) { + uu_warn(gettext("pg_pattern %s attribute may only " + "have a value of \"instance\" when it is in a " + "service declaration.\n"), target_attr); + return (-1); + } + } + + /* + * If target has a value of "delegate", the template must be in a + * restarter. + */ + if (strcmp(v->sc_u.sc_string, "delegate") == 0) { + is_restarter = 0; + if ((s->sc_etype == SVCCFG_SERVICE_OBJECT) && + (s->sc_u.sc_service.sc_service_type == SVCCFG_RESTARTER)) { + is_restarter = 1; + } + if ((s->sc_etype == SVCCFG_INSTANCE_OBJECT) && + (s->sc_parent->sc_u.sc_service.sc_service_type == + SVCCFG_RESTARTER)) { + is_restarter = 1; + } + if (is_restarter == 0) { + uu_warn(gettext("pg_pattern %s attribute has a " + "value of \"delegate\" but is not in a " + "restarter service\n"), target_attr); + return (-1); + } + } + + /* + * If target has a value of "all", the template must be in the + * global (SCF_SERVICE_GLOBAL) service. + */ + if (strcmp(v->sc_u.sc_string, all_value) == 0) { + if (s->sc_etype != SVCCFG_SERVICE_OBJECT) { + uu_warn(gettext("pg_pattern %s attribute has a " + "value of \"%s\" but is not in a " + "service entity.\n"), target_attr, all_value); + return (-1); + } + if (strcmp(s->sc_fmri, SCF_SERVICE_GLOBAL) != 0) { + uu_warn(gettext("pg_pattern %s attribute has a " + "value of \"%s\" but is in the \"%s\" service. " + "pg_patterns with target \"%s\" are only allowed " + "in the global service.\n"), + target_attr, all_value, s->sc_fmri, all_value); + return (-1); + } + } + + return (0); +} + +static int +lxml_get_tm_pg_pattern(entity_t *service, xmlNodePtr pg_pattern) +{ + xmlNodePtr cursor; + int out_len; + xmlChar *name; + pgroup_t *pg = NULL; + char *pg_name; + int r = -1; + xmlChar *type; + + pg_name = safe_malloc(max_scf_name_len + 1); + + /* + * Get the name and type attributes. Their presence or absence + * determines whcih prefix we will use for the property group name. + * There are four cases -- neither attribute is present, both are + * present, only name is present or only type is present. + */ + name = xmlGetProp(pg_pattern, (xmlChar *)name_attr); + type = xmlGetProp(pg_pattern, (xmlChar *)type_attr); + if ((name == NULL) || (*name == 0)) { + if ((type == NULL) || (*type == 0)) { + /* PG name contains only the prefix in this case */ + if (strlcpy(pg_name, SCF_PG_TM_PG_PATTERN_PREFIX, + max_scf_name_len + 1) >= max_scf_name_len + 1) { + uu_die(gettext("Unable to create pg_pattern " + "property for %s\n"), service->sc_name); + } + } else { + /* + * If we have a type and no name, the type becomes + * part of the pg_pattern property group name. + */ + if ((out_len = snprintf(pg_name, max_scf_name_len + 1, + "%s%s", SCF_PG_TM_PG_PATTERN_T_PREFIX, type)) >= + max_scf_name_len + 1) { + uu_die(gettext("pg_pattern type is for %s is " + "%d bytes too long\n"), service->sc_name, + out_len - max_scf_name_len); + } + } + } else { + const char *prefix; + + /* Make sure that the name is valid. */ + if (uu_check_name((const char *)name, UU_NAME_DOMAIN) != 0) { + semerr(gettext("pg_pattern name attribute, \"%s\", " + "for %s is invalid\n"), name, service->sc_name); + goto out; + } + + /* + * As long as the pg_pattern has a name, it becomes part of + * the name of the pg_pattern property group name. We + * merely need to pick the appropriate prefix. + */ + if ((type == NULL) || (*type == 0)) { + prefix = SCF_PG_TM_PG_PATTERN_N_PREFIX; + } else { + prefix = SCF_PG_TM_PG_PATTERN_NT_PREFIX; + } + if ((out_len = snprintf(pg_name, max_scf_name_len + 1, "%s%s", + prefix, name)) >= max_scf_name_len + 1) { + uu_die(gettext("pg_pattern property group name " + "for %s is %d bytes too long\n"), service->sc_name, + out_len - max_scf_name_len); + } + } + + /* + * Create the property group for holding this pg_pattern + * information, and capture the pg_pattern attributes. + */ + pg = internal_pgroup_create_strict(service, pg_name, + SCF_GROUP_TEMPLATE_PG_PATTERN); + if (pg == NULL) { + if ((name == NULL) || (*name == 0)) { + if ((type == NULL) ||(*type == 0)) { + semerr(gettext("pg_pattern with empty name and " + "type is not unique in %s\n"), + service->sc_name); + } else { + semerr(gettext("pg_pattern with empty name and " + "type \"%s\" is not unique in %s\n"), + type, service->sc_name); + } + } else { + if ((type == NULL) || (*type == 0)) { + semerr(gettext("pg_pattern with name \"%s\" " + "and empty type is not unique in %s\n"), + name, service->sc_name); + } else { + semerr(gettext("pg_pattern with name \"%s\" " + "and type \"%s\" is not unique in %s\n"), + name, type, service->sc_name); + } + } + goto out; + } + + /* + * Get the pg_pattern attributes from the manifest and verify + * that they satisfy our restrictions. + */ + r = lxml_get_pg_pattern_attributes(pg, pg_pattern); + if (r != 0) + goto out; + if (verify_pg_pattern_attributes(service, pg) != 0) { + semerr(gettext("Invalid pg_pattern attributes in %s\n"), + service->sc_name); + r = -1; + goto out; + } + + /* + * Now process all of the elements of pg_pattern. + */ + for (cursor = pg_pattern->xmlChildrenNode; + cursor != NULL; + cursor = cursor->next) { + if (lxml_ignorable_block(cursor)) + continue; + + switch (lxml_xlate_element(cursor->name)) { + case SC_COMMON_NAME: + (void) lxml_get_all_loctext(service, pg, cursor, + COMMON_NAME_FMT, (const char *)cursor->name); + break; + case SC_DESCRIPTION: + (void) lxml_get_all_loctext(service, pg, cursor, + DESCRIPTION_FMT, (const char *)cursor->name); + break; + case SC_PROP_PATTERN: + r = lxml_get_tm_prop_pattern(service, cursor, + pg_name); + if (r != 0) + goto out; + break; + default: + uu_die(gettext("illegal element \"%s\" in pg_pattern " + "for service \"%s\"\n"), cursor->name, + service->sc_name); + } + } + +out: + if ((r != 0) && (pg != NULL)) { + internal_detach_pgroup(service, pg); + internal_pgroup_free(pg); + } + free(pg_name); + xmlFree(name); + xmlFree(type); + + return (r); +} + +static int lxml_get_template(entity_t *service, xmlNodePtr templ) { xmlNodePtr cursor; @@ -1439,6 +2752,10 @@ lxml_get_template(entity_t *service, xmlNodePtr templ) case SC_DOCUMENTATION: (void) lxml_get_tm_documentation(service, cursor); break; + case SC_PG_PATTERN: + if (lxml_get_tm_pg_pattern(service, cursor) != 0) + return (-1); + break; default: uu_die(gettext("illegal element \"%s\" on template " "for service \"%s\"\n"), @@ -1581,7 +2898,8 @@ lxml_get_instance(entity_t *service, xmlNodePtr inst, svccfg_op_t op) (void) lxml_get_pgroup(i, cursor); break; case SC_TEMPLATE: - (void) lxml_get_template(i, cursor); + if (lxml_get_template(i, cursor) != 0) + return (-1); break; default: uu_die(gettext( @@ -1636,7 +2954,7 @@ lxml_get_service(bundle_t *bundle, xmlNodePtr svc, svccfg_op_t op) s = internal_service_new((char *)xmlGetProp(svc, (xmlChar *)name_attr)); - version = xmlGetProp(svc, (xmlChar *)"version"); + version = xmlGetProp(svc, (xmlChar *)version_attr); s->sc_u.sc_service.sc_service_version = atol((const char *)version); xmlFree(version); @@ -1664,10 +2982,12 @@ lxml_get_service(bundle_t *bundle, xmlNodePtr svc, svccfg_op_t op) switch (e) { case SC_INSTANCE: - (void) lxml_get_instance(s, cursor, op); + if (lxml_get_instance(s, cursor, op) != 0) + return (-1); break; case SC_TEMPLATE: - (void) lxml_get_template(s, cursor); + if (lxml_get_template(s, cursor) != 0) + return (-1); break; case SC_STABILITY: (void) lxml_get_entity_stability(s, cursor); @@ -1741,7 +3061,7 @@ lxml_get_bundle(bundle_t *bundle, bundle_type_t bundle_type, /* * 1. Get bundle attributes. */ - type = xmlGetProp(subbundle, (xmlChar *)"type"); + type = xmlGetProp(subbundle, (xmlChar *)type_attr); bundle->sc_bundle_type = lxml_xlate_bundle_type(type); if (bundle->sc_bundle_type != bundle_type && bundle_type != SVCCFG_UNKNOWN_BUNDLE) { @@ -1772,8 +3092,8 @@ lxml_get_bundle(bundle_t *bundle, bundle_type_t bundle_type, break; } - if ((bundle->sc_bundle_name = xmlGetProp(subbundle, - (xmlChar *)"name")) == NULL) { + if (((bundle->sc_bundle_name = xmlGetProp(subbundle, + (xmlChar *)name_attr)) == NULL) || (*bundle->sc_bundle_name == 0)) { semerr(gettext("service bundle lacks name attribute\n")); return (-1); } @@ -1797,7 +3117,8 @@ lxml_get_bundle(bundle_t *bundle, bundle_type_t bundle_type, return (-1); break; case SC_SERVICE: - (void) lxml_get_service(bundle, cursor, op); + if (lxml_get_service(bundle, cursor, op) != 0) + return (-1); break; } } diff --git a/usr/src/cmd/svc/svcs/svcs.c b/usr/src/cmd/svc/svcs/svcs.c index d36999fec9..9cdcc49ad3 100644 --- a/usr/src/cmd/svc/svcs/svcs.c +++ b/usr/src/cmd/svc/svcs/svcs.c @@ -24,8 +24,6 @@ * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" - /* * svcs - display attributes of service instances * @@ -61,7 +59,6 @@ #include <sys/stat.h> #include <assert.h> -#include <ctype.h> #include <errno.h> #include <fcntl.h> #include <fnmatch.h> @@ -127,6 +124,7 @@ int exit_status; ssize_t max_scf_name_length; ssize_t max_scf_value_length; ssize_t max_scf_fmri_length; +static ssize_t max_scf_type_length; static time_t now; static struct pfmri_list *restarters = NULL; static int first_paragraph = 1; /* For -l mode. */ @@ -202,14 +200,6 @@ safe_strdup(const char *str) return (cp); } -static void -sanitize_locale(char *locale) -{ - for (; *locale != '\0'; locale++) - if (!isalnum(*locale)) - *locale = '_'; -} - /* * FMRI hashtable. For uniquifing listings. */ @@ -2123,6 +2113,143 @@ out: } static void +print_application_properties(scf_walkinfo_t *wip, scf_snapshot_t *snap) +{ + scf_iter_t *pg_iter, *prop_iter, *val_iter; + scf_propertygroup_t *pg; + scf_property_t *prop; + scf_value_t *val; + scf_pg_tmpl_t *pt; + scf_prop_tmpl_t *prt; + char *pg_name_buf = safe_malloc(max_scf_name_length + 1); + char *prop_name_buf = safe_malloc(max_scf_name_length + 1); + char *snap_name = safe_malloc(max_scf_name_length + 1); + char *val_buf = safe_malloc(max_scf_value_length + 1); + char *desc, *cp; + scf_type_t type; + int i, j, k; + uint8_t vis; + + if ((pg_iter = scf_iter_create(h)) == NULL || + (prop_iter = scf_iter_create(h)) == NULL || + (val_iter = scf_iter_create(h)) == NULL || + (val = scf_value_create(h)) == NULL || + (prop = scf_property_create(h)) == NULL || + (pt = scf_tmpl_pg_create(h)) == NULL || + (prt = scf_tmpl_prop_create(h)) == NULL || + (pg = scf_pg_create(h)) == NULL) + scfdie(); + + if (scf_iter_instance_pgs_typed_composed(pg_iter, wip->inst, snap, + SCF_PG_APP_DEFAULT) == -1) + scfdie(); + + /* + * Format for output: + * pg (pgtype) + * description + * pg/prop (proptype) = <value> <value> + * description + */ + while ((i = scf_iter_next_pg(pg_iter, pg)) == 1) { + int tmpl = 0; + + if (scf_pg_get_name(pg, pg_name_buf, max_scf_name_length) < 0) + scfdie(); + if (scf_snapshot_get_name(snap, snap_name, + max_scf_name_length) < 0) + scfdie(); + + if (scf_tmpl_get_by_pg_name(wip->fmri, snap_name, pg_name_buf, + SCF_PG_APP_DEFAULT, pt, 0) == 0) + tmpl = 1; + else + tmpl = 0; + + (void) printf("%s (%s)\n", pg_name_buf, SCF_PG_APP_DEFAULT); + + if (tmpl == 1 && scf_tmpl_pg_description(pt, NULL, &desc) > 0) { + (void) printf(" %s\n", desc); + free(desc); + } + + if (scf_iter_pg_properties(prop_iter, pg) == -1) + scfdie(); + while ((j = scf_iter_next_property(prop_iter, prop)) == 1) { + if (scf_property_get_name(prop, prop_name_buf, + max_scf_name_length) < 0) + scfdie(); + if (scf_property_type(prop, &type) == -1) + scfdie(); + + if ((tmpl == 1) && + (scf_tmpl_get_by_prop(pt, prop_name_buf, prt, + 0) != 0)) + tmpl = 0; + + if (tmpl == 1 && + scf_tmpl_prop_visibility(prt, &vis) != -1 && + vis == SCF_TMPL_VISIBILITY_HIDDEN) + continue; + + (void) printf("%s/%s (%s) = ", pg_name_buf, + prop_name_buf, scf_type_to_string(type)); + + if (scf_iter_property_values(val_iter, prop) == -1) + scfdie(); + + while ((k = scf_iter_next_value(val_iter, val)) == 1) { + if (scf_value_get_as_string(val, val_buf, + max_scf_value_length + 1) < 0) + scfdie(); + if (strpbrk(val_buf, " \t\n\"()") != NULL) { + (void) printf("\""); + for (cp = val_buf; *cp != '\0'; ++cp) { + if (*cp == '"' || *cp == '\\') + (void) putc('\\', + stdout); + + (void) putc(*cp, stdout); + } + (void) printf("\""); + } else { + (void) printf("%s ", val_buf); + } + } + + (void) printf("\n"); + + if (k == -1) + scfdie(); + + if (tmpl == 1 && scf_tmpl_prop_description(prt, NULL, + &desc) > 0) { + (void) printf(" %s\n", desc); + free(desc); + } + } + if (j == -1) + scfdie(); + } + if (i == -1) + scfdie(); + + + scf_iter_destroy(pg_iter); + scf_iter_destroy(prop_iter); + scf_iter_destroy(val_iter); + scf_value_destroy(val); + scf_property_destroy(prop); + scf_tmpl_pg_destroy(pt); + scf_tmpl_prop_destroy(prt); + scf_pg_destroy(pg); + free(pg_name_buf); + free(prop_name_buf); + free(snap_name); + free(val_buf); +} + +static void print_detailed_dependency(scf_propertygroup_t *pg) { scf_property_t *eprop; @@ -2404,12 +2531,17 @@ restarter_common: if (ret == -1) scfdie(); - scf_snapshot_destroy(snap); scf_iter_destroy(pg_iter); if (opt_processes) detailed_list_processes(wip); + /* "application" type property groups */ + if (opt_verbose == 1) + print_application_properties(wip, snap); + + scf_snapshot_destroy(snap); + return (0); } @@ -2973,7 +3105,7 @@ main(int argc, char **argv) locale = setlocale(LC_MESSAGES, ""); if (locale) { locale = safe_strdup(locale); - sanitize_locale(locale); + _scf_sanitize_locale(locale); } (void) textdomain(TEXT_DOMAIN); @@ -2984,9 +3116,10 @@ main(int argc, char **argv) max_scf_name_length = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH); max_scf_value_length = scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH); max_scf_fmri_length = scf_limit(SCF_LIMIT_MAX_FMRI_LENGTH); + max_scf_type_length = scf_limit(SCF_LIMIT_MAX_PG_TYPE_LENGTH); if (max_scf_name_length == -1 || max_scf_value_length == -1 || - max_scf_fmri_length == -1) + max_scf_fmri_length == -1 || max_scf_type_length == -1) scfdie(); now = time(NULL); @@ -3054,8 +3187,6 @@ main(int argc, char **argv) break; case 'v': - if (opt_mode == 'l') - argserr(progname); opt_verbose = 1; break; diff --git a/usr/src/lib/libscf/Makefile b/usr/src/lib/libscf/Makefile index 9925a9ed2e..41ce640a2d 100644 --- a/usr/src/lib/libscf/Makefile +++ b/usr/src/lib/libscf/Makefile @@ -19,11 +19,9 @@ # CDDL HEADER END # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # -# ident "%Z%%M% %I% %E% SMI" -# # # We build a native version of libscf.so.1 in the "native" subdirectory. @@ -45,10 +43,11 @@ HDRDIR = inc HDRS = libscf.h libscf_priv.h # -# only error descriptions are internationalized, but they aren't directly -# surrounded by gettext(), so we use '-a' to grab all strings. +# only error descriptions and templates messages are internationalized, +# but they aren't directly surrounded by gettext(), so we use '-a' to grab +# all strings. # -MSGFILES = common/error.c +MSGFILES = common/error.c common/scf_tmpl.c POFILE = libscf.po XGETFLAGS += -a diff --git a/usr/src/lib/libscf/Makefile.com b/usr/src/lib/libscf/Makefile.com index 230b768690..b138032d66 100644 --- a/usr/src/lib/libscf/Makefile.com +++ b/usr/src/lib/libscf/Makefile.com @@ -19,11 +19,9 @@ # CDDL HEADER END # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # -# ident "%Z%%M% %I% %E% SMI" -# LIBRARY = libscf.a VERS = .1 @@ -32,6 +30,7 @@ OBJECTS = \ error.o \ lowlevel.o \ midlevel.o \ + scf_tmpl.o \ scf_type.o include ../../Makefile.lib diff --git a/usr/src/lib/libscf/common/error.c b/usr/src/lib/libscf/common/error.c index 0bbfe84992..70b62f9686 100644 --- a/usr/src/lib/libscf/common/error.c +++ b/usr/src/lib/libscf/common/error.c @@ -24,8 +24,6 @@ * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" - #include "libscf_impl.h" #include <assert.h> @@ -67,6 +65,7 @@ static struct scf_error_info { {SCF_ERROR_HANDLE_DESTROYED, "object bound to destroyed handle"}, {SCF_ERROR_VERSION_MISMATCH, "incompatible SCF version"}, {SCF_ERROR_DELETED, "object has been deleted"}, + {SCF_ERROR_TEMPLATE_INVALID, "template data is invalid"}, {SCF_ERROR_CALLBACK_FAILED, "user callback function failed"}, diff --git a/usr/src/lib/libscf/common/lowlevel.c b/usr/src/lib/libscf/common/lowlevel.c index 6374590467..beda73a279 100644 --- a/usr/src/lib/libscf/common/lowlevel.c +++ b/usr/src/lib/libscf/common/lowlevel.c @@ -24,8 +24,6 @@ * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" - /* * This is the main implementation file for the low-level repository * interface. @@ -71,6 +69,31 @@ static uu_list_pool_t *datael_pool; static uu_list_pool_t *iter_pool; /* + * base32[] index32[] are used in base32 encoding and decoding. + */ +static char base32[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; +static char index32[128] = { + -1, -1, -1, -1, -1, -1, -1, -1, /* 0-7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 8-15 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 16-23 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 24-31 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 32-39 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 40-47 */ + -1, -1, 26, 27, 28, 29, 30, 31, /* 48-55 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 56-63 */ + -1, 0, 1, 2, 3, 4, 5, 6, /* 64-71 */ + 7, 8, 9, 10, 11, 12, 13, 14, /* 72-79 */ + 15, 16, 17, 18, 19, 20, 21, 22, /* 80-87 */ + 23, 24, 25, -1, -1, -1, -1, -1, /* 88-95 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 96-103 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 104-111 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 112-119 */ + -1, -1, -1, -1, -1, -1, -1, -1 /* 120-127 */ +}; + +#define DECODE32_GS (8) /* scf_decode32 group size */ + +/* * We want MUTEX_HELD, but we also want pthreads. */ struct _lwp_mutex; @@ -2036,6 +2059,7 @@ scf_iter_create(scf_handle_t *h) (void) pthread_mutex_unlock(&h->rh_lock); uu_list_node_fini(iter, &iter->iter_node, iter_pool); (void) scf_set_error(SCF_ERROR_NO_MEMORY); + uu_free(iter); return (NULL); } if (iter_attach(iter) == -1) { @@ -4209,7 +4233,7 @@ scf_value_create(scf_handle_t *h) if (h->rh_flags & HANDLE_DEAD) { (void) pthread_mutex_unlock(&h->rh_lock); uu_free(ret); - (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + (void) scf_set_error(SCF_ERROR_HANDLE_DESTROYED); return (NULL); } h->rh_values++; @@ -6165,9 +6189,10 @@ scf_walk_fmri(scf_handle_t *h, int argc, char **argv, int flags, /* * Setup initial variables */ - if ((max_fmri_length = scf_limit(SCF_LIMIT_MAX_FMRI_LENGTH)) == -1 || - (max_name_length = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH)) == -1) - return (SCF_ERROR_INTERNAL); + max_fmri_length = scf_limit(SCF_LIMIT_MAX_FMRI_LENGTH); + assert(max_fmri_length != -1); + max_name_length = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH); + assert(max_name_length != -1); if ((fmri = malloc(max_fmri_length + 1)) == NULL || (pgname = malloc(max_name_length + 1)) == NULL) { @@ -6837,6 +6862,318 @@ error: } /* + * scf_encode32() is an implementation of Base32 encoding as described in + * section 6 of RFC 4648 - "The Base16, Base32, and Base64 Data + * Encodings". See http://www.ietf.org/rfc/rfc4648.txt?number=4648. The + * input stream is divided into groups of 5 characters (40 bits). Each + * group is encoded into 8 output characters where each output character + * represents 5 bits of input. + * + * If the input is not an even multiple of 5 characters, the output will be + * padded so that the output is an even multiple of 8 characters. The + * standard specifies that the pad character is '='. Unfortunately, '=' is + * not a legal character in SMF property names. Thus, the caller can + * specify an alternate pad character with the pad argument. If pad is 0, + * scf_encode32() will use '='. Note that use of anything other than '=' + * produces output that is not in conformance with RFC 4648. It is + * suitable, however, for internal use of SMF software. When the encoded + * data is used as part of an SMF property name, SCF_ENCODE32_PAD should be + * used as the pad character. + * + * Arguments: + * input - Address of the buffer to be encoded. + * inlen - Number of characters at input. + * output - Address of the buffer to receive the encoded data. + * outmax - Size of the buffer at output. + * outlen - If it is not NULL, outlen receives the number of + * bytes placed in output. + * pad - Alternate padding character. + * + * Returns: + * 0 Buffer was successfully encoded. + * -1 Indicates output buffer too small, or pad is one of the + * standard encoding characters. + */ +int +scf_encode32(const char *input, size_t inlen, char *output, size_t outmax, + size_t *outlen, char pad) +{ + uint_t group_size = 5; + uint_t i; + const unsigned char *in = (const unsigned char *)input; + size_t olen; + uchar_t *out = (uchar_t *)output; + uint_t oval; + uint_t pad_count; + + /* Verify that there is enough room for the output. */ + olen = ((inlen + (group_size - 1)) / group_size) * 8; + if (outlen) + *outlen = olen; + if (olen > outmax) + return (-1); + + /* If caller did not provide pad character, use the default. */ + if (pad == 0) { + pad = '='; + } else { + /* + * Make sure that caller's pad is not one of the encoding + * characters. + */ + for (i = 0; i < sizeof (base32) - 1; i++) { + if (pad == base32[i]) + return (-1); + } + } + + /* Process full groups capturing 5 bits per output character. */ + for (; inlen >= group_size; in += group_size, inlen -= group_size) { + /* + * The comments in this section number the bits in an + * 8 bit byte 0 to 7. The high order bit is bit 7 and + * the low order bit is bit 0. + */ + + /* top 5 bits (7-3) from in[0] */ + *out++ = base32[in[0] >> 3]; + /* bits 2-0 from in[0] and top 2 (7-6) from in[1] */ + *out++ = base32[((in[0] << 2) & 0x1c) | (in[1] >> 6)]; + /* 5 bits (5-1) from in[1] */ + *out++ = base32[(in[1] >> 1) & 0x1f]; + /* low bit (0) from in[1] and top 4 (7-4) from in[2] */ + *out++ = base32[((in[1] << 4) & 0x10) | ((in[2] >> 4) & 0xf)]; + /* low 4 (3-0) from in[2] and top bit (7) from in[3] */ + *out++ = base32[((in[2] << 1) & 0x1e) | (in[3] >> 7)]; + /* 5 bits (6-2) from in[3] */ + *out++ = base32[(in[3] >> 2) & 0x1f]; + /* low 2 (1-0) from in[3] and top 3 (7-5) from in[4] */ + *out++ = base32[((in[3] << 3) & 0x18) | (in[4] >> 5)]; + /* low 5 (4-0) from in[4] */ + *out++ = base32[in[4] & 0x1f]; + } + + /* Take care of final input bytes. */ + pad_count = 0; + if (inlen) { + /* top 5 bits (7-3) from in[0] */ + *out++ = base32[in[0] >> 3]; + /* + * low 3 (2-0) from in[0] and top 2 (7-6) from in[1] if + * available. + */ + oval = (in[0] << 2) & 0x1c; + if (inlen == 1) { + *out++ = base32[oval]; + pad_count = 6; + goto padout; + } + oval |= in[1] >> 6; + *out++ = base32[oval]; + /* 5 bits (5-1) from in[1] */ + *out++ = base32[(in[1] >> 1) & 0x1f]; + /* + * low bit (0) from in[1] and top 4 (7-4) from in[2] if + * available. + */ + oval = (in[1] << 4) & 0x10; + if (inlen == 2) { + *out++ = base32[oval]; + pad_count = 4; + goto padout; + } + oval |= in[2] >> 4; + *out++ = base32[oval]; + /* + * low 4 (3-0) from in[2] and top 1 (7) from in[3] if + * available. + */ + oval = (in[2] << 1) & 0x1e; + if (inlen == 3) { + *out++ = base32[oval]; + pad_count = 3; + goto padout; + } + oval |= in[3] >> 7; + *out++ = base32[oval]; + /* 5 bits (6-2) from in[3] */ + *out++ = base32[(in[3] >> 2) & 0x1f]; + /* low 2 bits (1-0) from in[3] */ + *out++ = base32[(in[3] << 3) & 0x18]; + pad_count = 1; + } +padout: + /* + * Pad the output so that it is a multiple of 8 bytes. + */ + for (; pad_count > 0; pad_count--) { + *out++ = pad; + } + + /* + * Null terminate the output if there is enough room. + */ + if (olen < outmax) + *out = 0; + + return (0); +} + +/* + * scf_decode32() is an implementation of Base32 decoding as described in + * section 6 of RFC 4648 - "The Base16, Base32, and Base64 Data + * Encodings". See http://www.ietf.org/rfc/rfc4648.txt?number=4648. The + * input stream is divided into groups of 8 encoded characters. Each + * encoded character represents 5 bits of data. Thus, the 8 encoded + * characters are used to produce 40 bits or 5 bytes of unencoded data in + * outbuf. + * + * If the encoder did not have enough data to generate a mulitple of 8 + * characters of encoded data, it used a pad character to get to the 8 + * character boundry. The standard specifies that the pad character is '='. + * Unfortunately, '=' is not a legal character in SMF property names. + * Thus, the caller can specify an alternate pad character with the pad + * argument. If pad is 0, scf_decode32() will use '='. Note that use of + * anything other than '=' is not in conformance with RFC 4648. It is + * suitable, however, for internal use of SMF software. When the encoded + * data is used in SMF property names, SCF_ENCODE32_PAD should be used as + * the pad character. + * + * Arguments: + * in - Buffer of encoded characters. + * inlen - Number of characters at in. + * outbuf - Buffer to receive the decoded bytes. It can be the + * same buffer as in. + * outmax - Size of the buffer at outbuf. + * outlen - If it is not NULL, outlen receives the number of + * bytes placed in output. + * pad - Alternate padding character. + * + * Returns: + * 0 Buffer was successfully decoded. + * -1 Indicates an invalid input character, output buffer too + * small, or pad is one of the standard encoding characters. + */ +int +scf_decode32(const char *in, size_t inlen, char *outbuf, size_t outmax, + size_t *outlen, char pad) +{ + char *bufend = outbuf + outmax; + char c; + uint_t count; + uint32_t g[DECODE32_GS]; + size_t i; + uint_t j; + char *out = outbuf; + boolean_t pad_seen = B_FALSE; + + /* If caller did not provide pad character, use the default. */ + if (pad == 0) { + pad = '='; + } else { + /* + * Make sure that caller's pad is not one of the encoding + * characters. + */ + for (i = 0; i < sizeof (base32) - 1; i++) { + if (pad == base32[i]) + return (-1); + } + } + + i = 0; + while ((i < inlen) && (out < bufend)) { + /* Get a group of input characters. */ + for (j = 0, count = 0; + (j < DECODE32_GS) && (i < inlen); + i++) { + c = in[i]; + /* + * RFC 4648 allows for the encoded data to be split + * into multiple lines, so skip carriage returns + * and new lines. + */ + if ((c == '\r') || (c == '\n')) + continue; + if ((pad_seen == B_TRUE) && (c != pad)) { + /* Group not completed by pads */ + return (-1); + } + if ((c < 0) || (c >= sizeof (index32))) { + /* Illegal character. */ + return (-1); + } + if (c == pad) { + pad_seen = B_TRUE; + continue; + } + if ((g[j++] = index32[c]) == 0xff) { + /* Illegal character */ + return (-1); + } + count++; + } + + /* Pack the group into five 8 bit bytes. */ + if ((count >= 2) && (out < bufend)) { + /* + * Output byte 0: + * 5 bits (7-3) from g[0] + * 3 bits (2-0) from g[1] (4-2) + */ + *out++ = (g[0] << 3) | ((g[1] >> 2) & 0x7); + } + if ((count >= 4) && (out < bufend)) { + /* + * Output byte 1: + * 2 bits (7-6) from g[1] (1-0) + * 5 bits (5-1) from g[2] (4-0) + * 1 bit (0) from g[3] (4) + */ + *out++ = (g[1] << 6) | (g[2] << 1) | \ + ((g[3] >> 4) & 0x1); + } + if ((count >= 5) && (out < bufend)) { + /* + * Output byte 2: + * 4 bits (7-4) from g[3] (3-0) + * 4 bits (3-0) from g[4] (4-1) + */ + *out++ = (g[3] << 4) | ((g[4] >> 1) & 0xf); + } + if ((count >= 7) && (out < bufend)) { + /* + * Output byte 3: + * 1 bit (7) from g[4] (0) + * 5 bits (6-2) from g[5] (4-0) + * 2 bits (0-1) from g[6] (4-3) + */ + *out++ = (g[4] << 7) | (g[5] << 2) | + ((g[6] >> 3) & 0x3); + } + if ((count == 8) && (out < bufend)) { + /* + * Output byte 4; + * 3 bits (7-5) from g[6] (2-0) + * 5 bits (4-0) from g[7] (4-0) + */ + *out++ = (g[6] << 5) | g[7]; + } + } + if (i < inlen) { + /* Did not process all input characters. */ + return (-1); + } + if (outlen) + *outlen = out - outbuf; + /* Null terminate the output if there is room. */ + if (out < bufend) + *out = 0; + return (0); +} + + +/* * _scf_request_backup: a simple wrapper routine */ int diff --git a/usr/src/lib/libscf/common/mapfile-vers b/usr/src/lib/libscf/common/mapfile-vers index 8c420aade1..9d92919960 100644 --- a/usr/src/lib/libscf/common/mapfile-vers +++ b/usr/src/lib/libscf/common/mapfile-vers @@ -23,6 +23,62 @@ # Use is subject to license terms. # +SUNW_1.2 { + global: + scf_count_ranges_destroy; + scf_int_ranges_destroy; + scf_string_to_type; + scf_tmpl_error_pg; + scf_tmpl_error_pg_tmpl; + scf_tmpl_error_prop; + scf_tmpl_error_prop_tmpl; + scf_tmpl_error_type; + scf_tmpl_error_value; + scf_tmpl_errors_destroy; + scf_tmpl_get_by_pg; + scf_tmpl_get_by_prop; + scf_tmpl_get_by_pg_name; + scf_tmpl_iter_pgs; + scf_tmpl_iter_props; + scf_tmpl_next_error; + scf_tmpl_pg_common_name; + scf_tmpl_pg_create; + scf_tmpl_pg_description; + scf_tmpl_pg_destroy; + scf_tmpl_pg_name; + scf_tmpl_pg_required; + scf_tmpl_pg_reset; + scf_tmpl_pg_target; + scf_tmpl_pg_type; + scf_tmpl_prop_cardinality; + scf_tmpl_prop_common_name; + scf_tmpl_prop_create; + scf_tmpl_prop_description; + scf_tmpl_prop_destroy; + scf_tmpl_prop_internal_seps; + scf_tmpl_prop_name; + scf_tmpl_prop_required; + scf_tmpl_prop_reset; + scf_tmpl_prop_type; + scf_tmpl_prop_units; + scf_tmpl_prop_visibility; + scf_tmpl_reset_errors; + scf_tmpl_strerror; + scf_tmpl_validate_fmri; + scf_tmpl_value_common_name; + scf_tmpl_value_count_range_choices; + scf_tmpl_value_count_range_constraints; + scf_tmpl_value_description; + scf_tmpl_value_in_constraint; + scf_tmpl_value_int_range_constraints; + scf_tmpl_value_name_choices; + scf_tmpl_value_name_constraints; + scf_tmpl_value_int_range_choices; + scf_tmpl_visibility_to_string; + scf_type_to_string; + scf_values_destroy; +}; + SUNW_1.1 { global: scf_entry_add_value; @@ -203,8 +259,13 @@ SUNW_1.1 { SUNWprivate_1.1 { global: + gen_filenms_from_fmri; scf_canonify_fmri; scf_cmp_pattern; + _scf_create_errors; + scf_decode32; + scf_encode32; + scf_general_pg_setup; _scf_handle_decorations; _scf_notify_add_pgname; _scf_notify_add_pgtype; @@ -212,27 +273,28 @@ SUNWprivate_1.1 { scf_parse_file_fmri; scf_parse_fmri; scf_parse_svc_fmri; + _scf_pg_is_read_protected; _scf_pg_wait; + scf_read_count_property; + _scf_read_single_astring_from_pg; + _scf_read_tmpl_prop_type_as_string; + _scf_repository_switch; _scf_request_backup; + _scf_sanitize_locale; _scf_set_annotation; + scf_set_count_property; + scf_simple_handle_destroy; _scf_snapshot_attach; _scf_snapshot_delete; _scf_snapshot_take_attach; _scf_snapshot_take_new; _scf_snapshot_take_new_named; - scf_string_to_type; - scf_type_to_string; - scf_walk_fmri; - _smf_refresh_instance_i; - scf_general_pg_setup; + _scf_tmpl_add_error; + _scf_tmpl_error_set_prefix; scf_transaction_setup; scf_transaction_restart; - scf_read_count_property; - scf_set_count_property; - scf_simple_handle_destroy; - gen_filenms_from_fmri; - _scf_pg_is_read_protected; - _scf_repository_switch; + scf_walk_fmri; + _smf_refresh_instance_i; scf_read_propvec; scf_write_propvec; scf_clean_propvec; diff --git a/usr/src/lib/libscf/common/midlevel.c b/usr/src/lib/libscf/common/midlevel.c index c12cab4027..f6ad278eda 100644 --- a/usr/src/lib/libscf/common/midlevel.c +++ b/usr/src/lib/libscf/common/midlevel.c @@ -26,6 +26,7 @@ #include "libscf_impl.h" +#include <assert.h> #include <libuutil.h> #include <stdio.h> #include <strings.h> @@ -1125,12 +1126,8 @@ get_instance_pg(scf_simple_handle_t *simple_h) return (NULL); } - if ((namelen = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH)) == -1) { - if (scf_error() == SCF_ERROR_NOT_SET) { - (void) scf_set_error(SCF_ERROR_INTERNAL); - } - return (NULL); - } + namelen = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH) + 1; + assert(namelen > 0); if ((pg_name = malloc(namelen)) == NULL) { if (scf_error() == SCF_ERROR_NOT_SET) { @@ -1668,11 +1665,10 @@ scf_simple_prop_get(scf_handle_t *hin, const char *instance, const char *pgname, if (scf_handle_decode_fmri(h, fmri_buf, NULL, svc, NULL, NULL, NULL, SCF_DECODE_FMRI_TRUNCATE) == -1) goto error1; - if ((fmri_sz = scf_limit(SCF_LIMIT_MAX_FMRI_LENGTH)) == - -1) { - (void) scf_set_error(SCF_ERROR_INTERNAL); - goto error1; - } + + fmri_sz = scf_limit(SCF_LIMIT_MAX_FMRI_LENGTH) + 1; + assert(fmri_sz > 0); + if (scf_service_to_fmri(svc, fmri_buf, fmri_sz) == -1) goto error1; if ((svcfmri = assemble_fmri(h, fmri_buf, pgname, @@ -1814,10 +1810,8 @@ scf_simple_app_props_get(scf_handle_t *hin, const char *inst_fmri) } } - if ((namelen = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH)) == -1) { - (void) scf_set_error(SCF_ERROR_INTERNAL); - return (NULL); - } + namelen = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH) + 1; + assert(namelen > 0); if ((inst = scf_instance_create(h)) == NULL || (svc = scf_service_create(h)) == NULL || @@ -2646,8 +2640,8 @@ scf_read_propvec(const char *fmri, const char *pgname, boolean_t running, case SCF_TYPE_TIME: { scf_time_t *time = prop->pv_ptr; - ret = scf_value_get_time(v, &time->st_sec, - &time->st_nanosec); + ret = scf_value_get_time(v, &time->t_seconds, + &time->t_ns); break; } case SCF_TYPE_OPAQUE: { @@ -2812,8 +2806,8 @@ top: case SCF_TYPE_TIME: { scf_time_t *time = prop->pv_ptr; - ret = scf_value_set_time(v[i], time->st_sec, - time->st_nanosec); + ret = scf_value_set_time(v[i], time->t_seconds, + time->t_ns); break; } case SCF_TYPE_OPAQUE: { diff --git a/usr/src/lib/libscf/common/scf_tmpl.c b/usr/src/lib/libscf/common/scf_tmpl.c new file mode 100644 index 0000000000..caa35b2a11 --- /dev/null +++ b/usr/src/lib/libscf/common/scf_tmpl.c @@ -0,0 +1,7002 @@ +/* + * 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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * scf_tmpl.c + * + * This file implements the bulk of the libscf templates interfaces. + * Templates describe metadata about a service or instance in general, + * and individual configuration properties on those services and instances. + * Human-consumable descriptions can be provided, along with definitions + * of valid configuration. See service_bundle.dtd.1 for XML definitions + * of templates, and the svccfg code for information on how those definitions + * are translated into the repository. + * + * The main data structures are scf_pg_tmpl and scf_prop_tmpl. These + * are allocated by the callers through scf_tmpl_[pg|prop]_create(), and + * destroyed with scf_tmpl_[pg|prop]_destroy(). They are populated by + * scf_tmpl_get_by_pg_name(), scf_tmpl_get_by_pg(), and + * scf_tmpl_get_by_prop(). They also store the iterator state for + * scf_tmpl_iter_pgs() and scf_tmpl_iter_props(). + * + * These data structures are then consumed by other functions to + * gather information about the template (e.g. name, description, + * choices, constraints, etc.). + * + * scf_tmpl_validate_fmri() does instance validation against template + * data, and populates a set of template errors which can be explored using + * the scf_tmpl_next_error() and the scf_tmpl_error*() suite of functions. + * + * The main data structures for template errors are scf_tmpl_errors, + * defined in this file, and scf_tmpl_error, defined in libscf_priv.h. + * scf_tmpl_error is shared with svccfg to offer common printing + * of error messages between libscf and svccfg. + * + * General convenience functions are towards the top of this file, + * followed by pg and prop template discovery functions, followed + * by functions which gather information about the discovered + * template. Validation and error functions are at the end of this file. + */ + +#include "lowlevel_impl.h" +#include "libscf_impl.h" +#include <assert.h> +#include <errno.h> +#include <libintl.h> +#include <stdlib.h> +#include <stdio.h> +#include <strings.h> +#include <locale.h> +#include <ctype.h> +#include <inttypes.h> + +#define SCF_TMPL_PG_COMMON_NAME_C "common_name_C" + +#define SCF__TMPL_ITER_NONE 0 +#define SCF__TMPL_ITER_INST 1 +#define SCF__TMPL_ITER_RESTARTER 2 +#define SCF__TMPL_ITER_GLOBAL 3 + +#define SCF_TMPL_PG_NT 0 +#define SCF_TMPL_PG_N 1 +#define SCF_TMPL_PG_T 2 +#define SCF_TMPL_PG_WILD 3 + +struct scf_pg_tmpl { + int pt_populated; + scf_handle_t *pt_h; + scf_propertygroup_t *pt_pg; + scf_service_t *pt_orig_svc; + scf_service_t *pt_svc; + scf_instance_t *pt_orig_inst; + scf_instance_t *pt_inst; + scf_snapshot_t *pt_snap; + int pt_is_iter; + scf_iter_t *pt_iter; + int pt_iter_last; +}; + +#define SCF_WALK_ERROR -1 +#define SCF_WALK_NEXT 0 +#define SCF_WALK_DONE 1 + +struct pg_tmpl_walk { + const char *pw_snapname; + const char *pw_pgname; + const char *pw_pgtype; + scf_instance_t *pw_inst; + scf_service_t *pw_svc; + scf_snapshot_t *pw_snap; + scf_propertygroup_t *pw_pg; + const char *pw_target; + char *pw_tmpl_pgname; +}; + +typedef struct pg_tmpl_walk pg_tmpl_walk_t; + +typedef int walk_template_inst_func_t(scf_service_t *_svc, + scf_instance_t *_inst, pg_tmpl_walk_t *p); + +struct scf_prop_tmpl { + int prt_populated; + scf_handle_t *prt_h; + scf_pg_tmpl_t *prt_t; + scf_propertygroup_t *prt_pg; + char *prt_pg_name; + scf_iter_t *prt_iter; +}; + +/* + * Common server errors are usually passed back to the caller. This + * array defines them centrally so that they don't need to be enumerated + * in every libscf call. + */ +static const scf_error_t errors_server[] = { + SCF_ERROR_BACKEND_ACCESS, + SCF_ERROR_CONNECTION_BROKEN, + SCF_ERROR_DELETED, + SCF_ERROR_HANDLE_DESTROYED, + SCF_ERROR_INTERNAL, + SCF_ERROR_NO_MEMORY, + SCF_ERROR_NO_RESOURCES, + SCF_ERROR_NOT_BOUND, + SCF_ERROR_PERMISSION_DENIED, + 0 + }; + +/* + * int ismember() + * + * Returns 1 if the supplied error is a member of the error array, 0 + * if it is not. + */ +static scf_error_t +ismember(const int error, const scf_error_t error_array[]) +{ + int i; + + for (i = 0; error_array[i] != 0; ++i) { + if (error == error_array[i]) + return (1); + } + + return (0); +} + +/* + * char *_scf_tmpl_get_fmri() + * + * Given a pg_tmpl, returns the FMRI of the service or instance that + * template describes. The allocated string must be freed with free(). + * + * On failure, returns NULL and sets scf_error() to _CONNECTION_BROKEN, + * _DELETED, or _NO_MEMORY. + */ +static char * +_scf_tmpl_get_fmri(const scf_pg_tmpl_t *t) +{ + ssize_t sz = scf_limit(SCF_LIMIT_MAX_FMRI_LENGTH) + 1; + int r; + char *buf = malloc(sz); + + assert(t->pt_svc != NULL || t->pt_inst != NULL); + assert(t->pt_svc == NULL || t->pt_inst == NULL); + + if (buf == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + return (buf); + } + + if (t->pt_inst != NULL) + r = scf_instance_to_fmri(t->pt_inst, buf, sz); + else + r = scf_service_to_fmri(t->pt_svc, buf, sz); + + if (r == -1) { + if (ismember(scf_error(), errors_server)) { + free(buf); + buf = NULL; + } else { + assert(0); + abort(); + } + } + + return (buf); +} + +/* + * char *_scf_get_pg_type() + * + * Given a propertygroup, returns an allocated string containing the + * type. The string must be freed with free(). + * + * On failure, returns NULL and sets scf_error() to: _CONNECTION_BROKEN, + * _DELETED, or _NO_MEMORY. + */ +static char * +_scf_get_pg_type(scf_propertygroup_t *pg) +{ + ssize_t sz = scf_limit(SCF_LIMIT_MAX_PG_TYPE_LENGTH) + 1; + char *buf = malloc(sz); + + if (buf == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + } else if (scf_pg_get_type(pg, buf, sz) == -1) { + if (ismember(scf_error(), errors_server)) { + free(buf); + buf = NULL; + } else { + assert(0); + abort(); + } + } + + return (buf); +} + +/* + * char *_scf_get_prop_name() + * + * Given a property, returns the name in an allocated string. The string must + * be freed with free(). + * + * On error, returns NULL and sets scf_error() to _CONNECTION_BROKEN, + * _DELETED, or _NO_MEMORY. + */ +static char * +_scf_get_prop_name(scf_property_t *prop) +{ + ssize_t sz = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH) + 1; + char *buf = malloc(sz); + + if (buf == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + } else if (scf_property_get_name(prop, buf, sz) == -1) { + if (ismember(scf_error(), errors_server)) { + free(buf); + buf = NULL; + } else { + assert(0); + abort(); + } + } + + return (buf); +} + +/* + * char *_scf_get_prop_type() + * + * Given a property, returns the type in an allocated string. The string must + * be freed with free(). + * + * On error, returns NULL and sets scf_error() to _CONNECTION_BROKEN, + * _DELETED, or _NO_MEMORY. + */ +static char * +_scf_get_prop_type(scf_property_t *prop) +{ + scf_type_t type; + char *ret; + + if (scf_property_type(prop, &type) == -1) { + if (ismember(scf_error(), errors_server)) { + return (NULL); + } else { + assert(0); + abort(); + } + } + + ret = strdup(scf_type_to_string(type)); + if (ret == NULL) + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + + return (ret); +} + +/* + * int _read_single_value_from_pg() + * + * Reads a single value from the pg and property name specified. On success, + * returns an allocated value that must be freed. + * + * Returns -1 on failure, sets scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_CONSTRAINT_VIOLATED + * Property has more than one value associated with it. + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * prop_name not a valid property name. + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * Property doesn't exist or exists and has no value. + * SCF_ERROR_NOT_SET + * Property group specified by pg is not set. + * SCF_ERROR_PERMISSION_DENIED + */ +static int +_read_single_value_from_pg(scf_propertygroup_t *pg, const char *prop_name, + scf_value_t **val) +{ + scf_handle_t *h; + scf_property_t *prop; + int ret = 0; + + assert(val != NULL); + if ((h = scf_pg_handle(pg)) == NULL) { + assert(scf_error() == SCF_ERROR_HANDLE_DESTROYED); + return (-1); + } + + prop = scf_property_create(h); + *val = scf_value_create(h); + + if (prop == NULL || *val == NULL) { + assert(scf_error() != SCF_ERROR_INVALID_ARGUMENT); + goto read_single_value_from_pg_fail; + } + + if (scf_pg_get_property(pg, prop_name, prop) != 0) { + assert(scf_error() != SCF_ERROR_HANDLE_MISMATCH); + goto read_single_value_from_pg_fail; + } + + if (scf_property_get_value(prop, *val) == -1) { + assert(scf_error() != SCF_ERROR_NOT_SET); + assert(scf_error() != SCF_ERROR_HANDLE_MISMATCH); + goto read_single_value_from_pg_fail; + } + + goto read_single_value_from_pg_done; + +read_single_value_from_pg_fail: + scf_value_destroy(*val); + *val = NULL; + ret = -1; + +read_single_value_from_pg_done: + scf_property_destroy(prop); + return (ret); +} + +/* + * char *_scf_read_single_astring_from_pg() + * + * Reads an astring from the pg and property name specified. On success, + * returns an allocated string. The string must be freed with free(). + * + * Returns NULL on failure, sets scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_CONSTRAINT_VIOLATED + * Property has more than one value associated with it. + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * prop_name not a valid property name. + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * Property doesn't exist or exists and has no value. + * SCF_ERROR_NOT_SET + * The property group specified by pg is not set. + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TYPE_MISMATCH + */ +char * +_scf_read_single_astring_from_pg(scf_propertygroup_t *pg, const char *prop_name) +{ + scf_value_t *val; + char *ret = NULL; + ssize_t rsize = scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH) + 1; + + assert(rsize != 0); + if (_read_single_value_from_pg(pg, prop_name, &val) == -1) + return (NULL); + + ret = malloc(rsize); + if (ret == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + + if (scf_value_get_astring(val, ret, rsize) < 0) { + assert(scf_error() != SCF_ERROR_NOT_SET); + free(ret); + ret = NULL; + } + +cleanup: + scf_value_destroy(val); + return (ret); +} + +/* + * char *_scf_read_tmpl_prop_type_as_string() + * + * Reads the property type and returns it as an allocated string. The string + * must be freed with free(). + * + * Returns NULL on failure, sets scf_error() to _BACKEND_ACCESS, + * _CONNECTION_BROKEN, _DELETED, _HANDLE_DESTROYED, _INTERNAL, _NO_MEMORY, + * _NO_RESOURCES, _NOT_BOUND, _PERMISSION_DENIED, or _TEMPLATE_INVALID. + */ +char * +_scf_read_tmpl_prop_type_as_string(const scf_prop_tmpl_t *pt) +{ + char *type; + + type = _scf_read_single_astring_from_pg(pt->prt_pg, + SCF_PROPERTY_TM_TYPE); + if (type == NULL) { + if (ismember(scf_error(), errors_server)) { + return (NULL); + } else switch (scf_error()) { + case SCF_ERROR_CONSTRAINT_VIOLATED: + case SCF_ERROR_NOT_FOUND: + case SCF_ERROR_TYPE_MISMATCH: + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + return (NULL); + + case SCF_ERROR_INVALID_ARGUMENT: + case SCF_ERROR_NOT_SET: + default: + assert(0); + abort(); + } + } + + return (type); +} + +/* + * int _read_single_boolean_from_pg() + * + * Reads a boolean from the pg and property name specified. + * + * Returns -1 on failure, sets scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_CONSTRAINT_VIOLATED + * Property has more than one value associated with it. + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * prop_name is not a valid property name. + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * Property doesn't exist or exists and has no value. + * SCF_ERROR_NOT_SET + * The property group specified by pg is not set. + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TYPE_MISMATCH + */ +static int +_read_single_boolean_from_pg(scf_propertygroup_t *pg, const char *prop_name, + uint8_t *bool) +{ + scf_value_t *val; + int ret = 0; + + if (_read_single_value_from_pg(pg, prop_name, &val) == -1) + return (-1); + + if (scf_value_get_boolean(val, bool) < 0) { + assert(scf_error() != SCF_ERROR_NOT_SET); + ret = -1; + } + + scf_value_destroy(val); + return (ret); +} + +/* + * char **_append_astrings_values() + * + * This function reads the values from the property prop_name in pg and + * appends to an existing scf_values_t *vals. vals may be empty, but + * must exist. The function skips over zero-length and duplicate values. + * + * Returns NULL on failure, sets scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * prop_name is not a valid property name. + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * SCF_ERROR_NOT_SET + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TYPE_MISMATCH + */ +static char ** +_append_astrings_values(scf_propertygroup_t *pg, const char *prop_name, + scf_values_t *vals) +{ + scf_handle_t *h; + scf_property_t *prop; + scf_value_t *val; + scf_iter_t *iter; + ssize_t rsize = scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH) + 1; + int err, count, cursz, i; + + assert(vals != NULL); + assert(vals->value_type == SCF_TYPE_ASTRING); + assert(vals->reserved == NULL); + count = vals->value_count; + if (count == 0) { + cursz = 8; + vals->values.v_astring = calloc(cursz, sizeof (char *)); + if (vals->values.v_astring == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + return (NULL); + } + } else { + /* + * The array may be bigger, but it is irrelevant since + * we will always re-allocate a new one. + */ + cursz = count; + } + + if ((h = scf_pg_handle(pg)) == NULL) { + assert(scf_error() == SCF_ERROR_HANDLE_DESTROYED); + return (NULL); + } + + prop = scf_property_create(h); + val = scf_value_create(h); + iter = scf_iter_create(h); + + if (prop == NULL || val == NULL || iter == NULL) { + assert(scf_error() != SCF_ERROR_INVALID_ARGUMENT); + goto append_single_astring_from_pg_fail; + } + + if (scf_pg_get_property(pg, prop_name, prop) != 0) { + assert(scf_error() != SCF_ERROR_HANDLE_MISMATCH); + goto append_single_astring_from_pg_fail; + } + + if (scf_iter_property_values(iter, prop) != 0) { + assert(scf_error() != SCF_ERROR_NOT_SET); + assert(scf_error() != SCF_ERROR_HANDLE_MISMATCH); + goto append_single_astring_from_pg_fail; + } + + while ((err = scf_iter_next_value(iter, val)) == 1) { + int flag; + int r; + + if (count + 1 >= cursz) { + void *aux; + + cursz *= 2; + if ((aux = calloc(cursz, sizeof (char *))) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto append_single_astring_from_pg_fail; + } + (void) memcpy(aux, vals->values.v_astring, + count * sizeof (char *)); + free(vals->values.v_astring); + vals->values.v_astring = aux; + } + + vals->values.v_astring[count] = malloc(rsize); + if (vals->values.v_astring[count] == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto append_single_astring_from_pg_fail; + } + + if ((r = scf_value_get_astring(val, + vals->values.v_astring[count], rsize)) <= 0) { + /* discard zero length strings */ + if (r == 0) { + free(vals->values.v_astring[count]); + continue; + } + assert(scf_error() != SCF_ERROR_NOT_SET); + goto append_single_astring_from_pg_fail; + } + for (i = 0, flag = 0; i < count; ++i) { + /* find and discard duplicates */ + if (strncmp(vals->values.v_astring[i], + vals->values.v_astring[count], rsize) == 0) { + free(vals->values.v_astring[count]); + flag = 1; + break; + } + } + if (flag == 1) + continue; + + count++; + } + + vals->value_count = count; + + if (err != 0) { + assert(scf_error() != SCF_ERROR_NOT_SET); + assert(scf_error() != SCF_ERROR_INVALID_ARGUMENT); + assert(scf_error() != SCF_ERROR_HANDLE_MISMATCH); + goto append_single_astring_from_pg_fail; + } else { + vals->values_as_strings = vals->values.v_astring; + } + + goto append_single_astring_from_pg_done; + +append_single_astring_from_pg_fail: + for (i = 0; i <= count; ++i) { + if (vals->values.v_astring[i] != NULL) + free(vals->values.v_astring[i]); + vals->values.v_astring[i] = NULL; + } + free(vals->values.v_astring); + vals->values.v_astring = NULL; + vals->value_count = 0; + +append_single_astring_from_pg_done: + scf_iter_destroy(iter); + scf_property_destroy(prop); + scf_value_destroy(val); + return (vals->values.v_astring); +} + +/* + * Returns NULL on failure, sets scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * prop_name is not a valid property name. + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * SCF_ERROR_NOT_SET + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TYPE_MISMATCH + */ +static char ** +_read_astrings_values(scf_propertygroup_t *pg, const char *prop_name, + scf_values_t *vals) +{ + assert(vals != NULL); + vals->value_count = 0; + vals->value_type = SCF_TYPE_ASTRING; + vals->reserved = NULL; + return (_append_astrings_values(pg, prop_name, vals)); +} + +void +_scf_sanitize_locale(char *locale) +{ + for (; *locale != '\0'; locale++) + if (!isalnum(*locale) && *locale != '_') + *locale = '_'; +} + +/* + * The returned string needs to be freed by the caller + * Returns NULL on failure. Sets scf_error() to: + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_INVALID_ARGUMENT + * Name isn't short enough to add the locale to. + */ +static char * +_add_locale_to_name(const char *name, const char *locale) +{ + char *lname = NULL; + ssize_t lsz; + char *loc; + + if (locale == NULL) + locale = setlocale(LC_MESSAGES, NULL); + loc = strdup(locale); + if (loc == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + return (NULL); + } else { + _scf_sanitize_locale(loc); + } + + lsz = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH) + 1; + lname = malloc(lsz); + if (lname == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + + (void) strlcpy(lname, name, lsz); + if (strlcat(lname, loc, lsz) >= lsz) { + (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + free(lname); + lname = NULL; + } +cleanup: + free(loc); + + return (lname); +} + +/* + * char *_tmpl_pg_name(pg, type, use_type) + * + * pg and type can both be NULL. Returns the name of the most specific + * template property group name based on the inputs. + * If use_type is set and pg is not NULL, a property group name for a + * property group template that has type defined is returned, even if no + * type is provided. + * + * Returns NULL on failure and sets scf_error() to: + * SCF_ERROR_INVALID_ARGUMENT + * can't combine the arguments and get a reasonable length name + * SCF_ERROR_NO_MEMORY + * + */ +static char * +_tmpl_pg_name(const char *pg, const char *type, int use_type) +{ + char *name; + ssize_t limit, size = 0; + + limit = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH) + 1; + name = malloc(limit); + if (name == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + return (NULL); + } + + if (pg == NULL && type == NULL) { + if (strlcpy(name, SCF_PG_TM_PG_PATTERN_PREFIX, limit) >= + limit) { + assert(0); + abort(); + } + return (name); + } else if (pg != NULL && type != NULL) { + size = snprintf(name, limit, "%s%s", + SCF_PG_TM_PG_PATTERN_NT_PREFIX, pg); + } else if (pg != NULL && type == NULL && use_type == 1) { + size = snprintf(name, limit, "%s%s", + SCF_PG_TM_PG_PATTERN_NT_PREFIX, pg); + } else if (pg != NULL && type == NULL) { + size = snprintf(name, limit, "%s%s", + SCF_PG_TM_PG_PATTERN_N_PREFIX, pg); + } else if (type != NULL && pg == NULL) { + size = snprintf(name, limit, "%s%s", + SCF_PG_TM_PG_PATTERN_T_PREFIX, type); + } else { + assert(0); + abort(); + } + + if (size >= limit) { + (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + free(name); + return (NULL); + } else { + return (name); + } +} + +/* + * _scf_get_pg_name() + * Gets the name of the supplied property group. On success, returns an + * allocated string. The string must be freed by free(). + * + * Returns NULL on failure and sets scf_error() to _CONNECTION_BROKEN, + * _DELETED, or _NO_MEMORY. + */ +static char * +_scf_get_pg_name(scf_propertygroup_t *pg) +{ + ssize_t sz = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH) + 1; + char *buf = malloc(sz); + + if (buf == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + } else if (scf_pg_get_name(pg, buf, sz) == -1) { + if (ismember(scf_error(), errors_server)) { + free(buf); + buf = NULL; + } else { + assert(0); + abort(); + } + } + + return (buf); +} + +/* + * char *_tmpl_prop_name() + * + * Returns the name of the property template prop (which is the name of + * the property template property group) in the property group + * template t. Returns NULL on failure and sets scf_error() to: + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_INVALID_ARGUMENT + * can't combine the arguments and get a reasonable length name + * SCF_ERROR_NO_MEMORY + */ +static char * +_tmpl_prop_name(const char *prop, scf_pg_tmpl_t *t) +{ + char *name = NULL, *pg_name = NULL; + size_t prefix_size; + ssize_t limit, size = 0; + + assert(prop != NULL); + assert(t->pt_pg != NULL); + + limit = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH) + 1; + name = malloc(limit); + if (name == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + return (NULL); + } + + if ((pg_name = _scf_get_pg_name(t->pt_pg)) == NULL) { + free(name); + return (NULL); + } + + prefix_size = strlen(SCF_PG_TM_PG_PAT_BASE); + if (strncmp(pg_name, SCF_PG_TM_PG_PAT_BASE, prefix_size) != 0) { + (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + free(name); + free(pg_name); + return (NULL); + } + + size = snprintf(name, limit, "%s%s_%s", SCF_PG_TM_PROP_PATTERN_PREFIX, + pg_name + prefix_size, prop); + + if (size >= limit) { + (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + free(name); + free(pg_name); + return (NULL); + } else { + free(pg_name); + return (name); + } +} + +/* + * int _get_snapshot() + * + * Gets the specified snapshot. If "snapshot" isn't defined, use the + * running snapshot. If the snapshot isn't found, that may or may + * not be an error depending on the caller. Return 0 in that case, + * but leave scf_error() set to SCF_ERROR_NOT_FOUND. On all other + * errors, set scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * The handle argument is NULL, or snaphot is not a valid snapshot name + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + */ +static int +_get_snapshot(scf_instance_t *inst, const char *snapshot, + scf_snapshot_t **snap) +{ + int err; + scf_handle_t *h; + + h = scf_instance_handle(inst); + if (h == NULL) + return (-1); + + if ((*snap = scf_snapshot_create(h)) == NULL) { + return (-1); + } + + /* Use running snapshot by default. */ + if (snapshot == NULL) + err = scf_instance_get_snapshot(inst, "running", *snap); + else + err = scf_instance_get_snapshot(inst, snapshot, *snap); + + if (err != 0) { + if (ismember(scf_error(), errors_server)) { + scf_snapshot_destroy(*snap); + *snap = NULL; + return (-1); + } else switch (scf_error()) { + case SCF_ERROR_INVALID_ARGUMENT: + scf_snapshot_destroy(*snap); + *snap = NULL; + return (-1); + + case SCF_ERROR_NOT_FOUND: + scf_snapshot_destroy(*snap); + *snap = NULL; + return (0); + + case SCF_ERROR_NOT_SET: + case SCF_ERROR_HANDLE_MISMATCH: + default: + assert(0); + abort(); + } + } + + /* + * Explicitly set SCF_ERROR_NONE so that the SCF_ERROR_NOT_FOUND + * return above is explicitly guaranteed to be from + * scf_instance_get_snapshot(). + */ + (void) scf_set_error(SCF_ERROR_NONE); + return (0); +} + +/* + * Returns NULL on error, sets scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_CONSTRAINT_VIOLATED + * The restarter's FMRI does not match an existing instance. + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * The restarter's FMRI is not a valid FMRI. + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * Property doesn't exist or exists and has no value. + * SCF_ERROR_TEMPLATE_INVALID + * restarter property is not SCF_TYPE_ASTRING or has more than one value + */ +static scf_instance_t * +_get_restarter_inst(scf_handle_t *h, scf_service_t *svc, + scf_instance_t *inst, scf_snapshot_t *s) +{ + char *restarter = NULL; + scf_instance_t *ri = NULL; + scf_propertygroup_t *pg = NULL; + int ret = 0; + + assert(svc != NULL || inst != NULL); + assert(svc == NULL || inst == NULL); + + if ((ri = scf_instance_create(h)) == NULL || + (pg = scf_pg_create(h)) == NULL) { + assert(scf_error() != SCF_ERROR_INVALID_ARGUMENT); + scf_instance_destroy(ri); + return (NULL); + } + + if (inst != NULL) + ret = scf_instance_get_pg_composed(inst, s, SCF_PG_GENERAL, + pg); + else + ret = scf_service_get_pg(svc, SCF_PG_GENERAL, pg); + + if (ret != 0) { + if (ismember(scf_error(), errors_server)) { + goto _get_restarter_inst_fail; + } else switch (scf_error()) { + case SCF_ERROR_NOT_FOUND: + /* Assume default restarter. */ + break; + + case SCF_ERROR_NOT_SET: + case SCF_ERROR_HANDLE_MISMATCH: + /* + * If the arguments to the above functions + * aren't derived from the same handle, there's + * something wrong with the internal implementation, + * not the public caller further up the chain. + */ + case SCF_ERROR_INVALID_ARGUMENT: + default: + assert(0); + abort(); + } + } else { + restarter = _scf_read_single_astring_from_pg(pg, + SCF_PROPERTY_RESTARTER); + /* zero length string is NOT a valid restarter */ + if (restarter != NULL && restarter[0] == '\0') { + free(restarter); + restarter = NULL; + } else if (restarter == NULL) { + if (ismember(scf_error(), errors_server)) { + goto _get_restarter_inst_fail; + } else switch (scf_error()) { + case SCF_ERROR_NOT_FOUND: + break; + + case SCF_ERROR_CONSTRAINT_VIOLATED: + case SCF_ERROR_TYPE_MISMATCH: + (void) scf_set_error( + SCF_ERROR_TEMPLATE_INVALID); + goto _get_restarter_inst_fail; + + case SCF_ERROR_NOT_SET: + case SCF_ERROR_INVALID_ARGUMENT: + default: + assert(0); + abort(); + } + } + } + + if (restarter == NULL) { + /* Use default restarter */ + restarter = strdup(SCF_SERVICE_STARTD); + if (restarter == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto _get_restarter_inst_fail; + } + } + + if (scf_handle_decode_fmri(h, restarter, NULL, NULL, ri, NULL, NULL, + SCF_DECODE_FMRI_EXACT|SCF_DECODE_FMRI_REQUIRE_INSTANCE) != 0) { + if (ismember(scf_error(), errors_server)) { + uu_free(restarter); + goto _get_restarter_inst_fail; + } else switch (scf_error()) { + case SCF_ERROR_CONSTRAINT_VIOLATED: + case SCF_ERROR_INVALID_ARGUMENT: + case SCF_ERROR_NOT_FOUND: + free(restarter); + goto _get_restarter_inst_fail; + + case SCF_ERROR_HANDLE_MISMATCH: + case SCF_ERROR_NOT_SET: + default: + assert(0); + abort(); + } + } + free(restarter); + scf_pg_destroy(pg); + + return (ri); + +_get_restarter_inst_fail: + scf_instance_destroy(ri); + scf_pg_destroy(pg); + return (NULL); +} + +/* + * Returns NULL on error, sets scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_CONSTRAINT_VIOLATED + * Restarter property has more than one value associated with it, + * or FMRI does not meet restrictions in scf_handle_decode_fmri() flags. + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * The fmri argument in scf_handle_decode_fmri() is not a valid FMRI. + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * Property doesn't exist or exists and has no value. + */ +static scf_instance_t * +_get_global_inst(scf_handle_t *h) +{ + scf_instance_t *ri; + + if ((ri = scf_instance_create(h)) == NULL) { + assert(scf_error() != SCF_ERROR_INVALID_ARGUMENT); + (void) scf_set_error(SCF_ERROR_NO_RESOURCES); + return (NULL); + } + + if (scf_handle_decode_fmri(h, SCF_INSTANCE_GLOBAL, NULL, NULL, ri, + NULL, NULL, + SCF_DECODE_FMRI_EXACT|SCF_DECODE_FMRI_REQUIRE_INSTANCE) != 0) { + if (ismember(scf_error(), errors_server)) { + scf_instance_destroy(ri); + return (NULL); + } else switch (scf_error()) { + case SCF_ERROR_CONSTRAINT_VIOLATED: + case SCF_ERROR_INVALID_ARGUMENT: + case SCF_ERROR_NOT_FOUND: + scf_instance_destroy(ri); + return (NULL); + + case SCF_ERROR_HANDLE_MISMATCH: + case SCF_ERROR_NOT_SET: + default: + assert(0); + abort(); + } + } + + return (ri); +} + +/* + * Call the supplied function for each of the service or instance, the + * service's restarter, and the globally defined template instance. + * If the function returns SCF_WALK_ERROR, the walk is ended. If + * the function returns SCF_WALK_NEXT, the next entity is tried. + * + * The function is only expected to return SCF_WALK_DONE if it has + * found a property group match in the current entity, and has + * populated p->pw_pg with the matching property group. + */ +static void +_walk_template_instances(scf_service_t *svc, scf_instance_t *inst, + scf_snapshot_t *snap, walk_template_inst_func_t *func, + pg_tmpl_walk_t *p, int flag) +{ + scf_instance_t *tmpl_inst = NULL; + scf_handle_t *h; + int ret; + char *tg = NULL; + + assert(svc != NULL || inst != NULL); + assert(svc == NULL || inst == NULL); + + if (inst != NULL) + h = scf_instance_handle(inst); + else + h = scf_service_handle(svc); + if (h == NULL) + goto done; + + /* First, use supplied service or instance */ + p->pw_target = SCF_TM_TARGET_THIS; + ret = func(svc, inst, p); + switch (ret) { + case SCF_WALK_NEXT: + break; + case SCF_WALK_DONE: + /* + * Check that the template scoping matches and if not, + * continue. + */ + assert(p->pw_pg != NULL); + tg = _scf_read_single_astring_from_pg(p->pw_pg, + SCF_PROPERTY_TM_TARGET); + if (tg == NULL || /* scf_error() was set */ + (strcmp(tg, SCF_TM_TARGET_INSTANCE) != 0 && + strcmp(tg, SCF_TM_TARGET_THIS) != 0 && + (flag & SCF_PG_TMPL_FLAG_EXACT) != + SCF_PG_TMPL_FLAG_EXACT)) { + scf_pg_destroy(p->pw_pg); + p->pw_pg = NULL; + if (tg != NULL) { + free(tg); + tg = NULL; + break; + } + } + /*FALLTHROUGH*/ + case SCF_WALK_ERROR: + goto done; + /*NOTREACHED*/ + default: + assert(0); + abort(); + } + + /* Next the restarter. */ + p->pw_target = SCF_TM_TARGET_DELEGATE; + tmpl_inst = _get_restarter_inst(h, svc, inst, snap); + if (tmpl_inst != NULL) { + ret = func(NULL, tmpl_inst, p); + switch (ret) { + case SCF_WALK_NEXT: + break; + case SCF_WALK_DONE: + /* + * Check that the template scoping matches and if not, + * continue. + */ + assert(p->pw_pg != NULL); + tg = _scf_read_single_astring_from_pg(p->pw_pg, + SCF_PROPERTY_TM_TARGET); + if (tg == NULL || /* scf_error() was set */ + strcmp(tg, SCF_TM_TARGET_DELEGATE) != 0) { + scf_pg_destroy(p->pw_pg); + p->pw_pg = NULL; + if (tg != NULL) { + free(tg); + tg = NULL; + break; + } + } + /*FALLTHROUGH*/ + case SCF_WALK_ERROR: + goto done; + /*NOTREACHED*/ + default: + assert(0); + abort(); + } + } + + p->pw_target = SCF_TM_TARGET_ALL; + scf_instance_destroy(tmpl_inst); + tmpl_inst = _get_global_inst(h); + if (tmpl_inst != NULL) { + ret = func(NULL, tmpl_inst, p); + switch (ret) { + case SCF_WALK_NEXT: + break; + case SCF_WALK_DONE: + /* + * Check that the template scoping matches and if not, + * continue. + */ + assert(p->pw_pg != NULL); + tg = _scf_read_single_astring_from_pg(p->pw_pg, + SCF_PROPERTY_TM_TARGET); + if (tg == NULL || /* scf_error() was set */ + strcmp(tg, SCF_TM_TARGET_ALL) != 0) { + scf_pg_destroy(p->pw_pg); + p->pw_pg = NULL; + if (tg != NULL) { + free(tg); + tg = NULL; + break; + } + } + /*FALLTHROUGH*/ + case SCF_WALK_ERROR: + goto done; + /*NOTREACHED*/ + default: + assert(0); + abort(); + } + } + +done: + free(tg); + if (ret != SCF_WALK_DONE) + scf_instance_destroy(tmpl_inst); + p->pw_target = NULL; +} + +/* + * _get_pg() returns 0 on success and -1 on failure. Sets scf_error() + * on failure. + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_MISMATCH + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * name is not a valid property group. + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * SCF_ERROR_NOT_SET + */ +static int +_get_pg(scf_service_t *svc, scf_instance_t *inst, + const scf_snapshot_t *snap, const char *name, scf_propertygroup_t *pg) +{ + int ret; + + assert(svc != NULL || inst != NULL); + assert(svc == NULL || inst == NULL); + assert(pg != NULL); + + if (inst != NULL) + ret = scf_instance_get_pg_composed(inst, snap, name, pg); + else + ret = scf_service_get_pg(svc, name, pg); + + return (ret); +} + +/* + * Returns SCF_WALK_NEXT for not found, SCF_WALK_ERROR for error, + * and SCF_WALK_DONE for found. + * On error, destroy pg and set it to NULL. + * + * Sets scf_error() if SCF_WALK_ERROR is returned to _BACKEND_ACCESS, + * _CONNECTION_BROKEN, _INTERNAL, _INVALID_ARGUMENT (name is not a + * valid property group), _NO_RESOURCES, or _NOT_BOUND. + */ +static int +_lookup_pg(scf_service_t *svc, scf_instance_t *inst, + const scf_snapshot_t *snap, const char *name, scf_propertygroup_t *pg) +{ + int ret; + + ret = _get_pg(svc, inst, snap, name, pg); + + if (ret == 0) { + return (SCF_WALK_DONE); + } else { + switch (scf_error()) { + case SCF_ERROR_NOT_FOUND: + case SCF_ERROR_DELETED: + return (SCF_WALK_NEXT); + + case SCF_ERROR_BACKEND_ACCESS: + case SCF_ERROR_CONNECTION_BROKEN: + case SCF_ERROR_INTERNAL: + case SCF_ERROR_INVALID_ARGUMENT: + case SCF_ERROR_NOT_BOUND: + case SCF_ERROR_NO_RESOURCES: + scf_pg_destroy(pg); + pg = NULL; + return (SCF_WALK_ERROR); + + case SCF_ERROR_NOT_SET: + case SCF_ERROR_HANDLE_MISMATCH: + default: + assert(0); + abort(); + } + } + + /*NOTREACHED*/ +} + +/* + * If match, return 0. If no match, return 1. If error, return -1. + * On error set scf_error() to _BACKEND_ACCESS, _CONNECTION_BROKEN, + * _HANDLE_DESTROYED, _INTERNAL, _NO_MEMORY, _NO_RESOURCES, _NOT_BOUND, + * _NOT_SET (property group specified by pg is not set), _PERMISSION_DENIED, + * or _TEMPLATE_INVALID (target property is not SCF_TYPE_ASTRING or has + * more than one value). + */ +static int +check_target_match(scf_propertygroup_t *pg, const char *target) +{ + char *pg_target; + int ret = 0; + + pg_target = _scf_read_single_astring_from_pg(pg, + SCF_PROPERTY_TM_TARGET); + if (pg_target == NULL) { + switch (scf_error()) { + case SCF_ERROR_DELETED: + case SCF_ERROR_NOT_FOUND: + return (1); + + case SCF_ERROR_CONSTRAINT_VIOLATED: + case SCF_ERROR_TYPE_MISMATCH: + (void) scf_set_error( + SCF_ERROR_TEMPLATE_INVALID); + /*FALLTHROUGH*/ + + case SCF_ERROR_BACKEND_ACCESS: + case SCF_ERROR_CONNECTION_BROKEN: + case SCF_ERROR_HANDLE_DESTROYED: + case SCF_ERROR_INTERNAL: + case SCF_ERROR_NO_RESOURCES: + case SCF_ERROR_NOT_BOUND: + case SCF_ERROR_PERMISSION_DENIED: + return (-1); + + case SCF_ERROR_NOT_SET: + case SCF_ERROR_INVALID_ARGUMENT: + default: + assert(0); + abort(); + } + /*NOTREACHED*/ + } + + /* For a desired target of 'this', check for 'this' and 'instance'. */ + if ((strcmp(target, SCF_TM_TARGET_INSTANCE) == 0 || + strcmp(target, SCF_TM_TARGET_THIS) == 0) && + (strcmp(pg_target, SCF_TM_TARGET_INSTANCE) == 0 || + strcmp(pg_target, SCF_TM_TARGET_THIS) == 0)) { + goto cleanup; + } + + if (strcmp(target, SCF_TM_TARGET_DELEGATE) == 0 && + strcmp(pg_target, SCF_TM_TARGET_DELEGATE) == 0) { + goto cleanup; + } + + if (strcmp(target, SCF_TM_TARGET_ALL) == 0 && + strcmp(pg_target, SCF_TM_TARGET_ALL) == 0) { + goto cleanup; + } + + ret = 1; +cleanup: + free(pg_target); + return (ret); +} + +/* + * Check if a matching template property group exists for each of: + * name and type, name only, type only, and completely wildcarded + * template. + * + * Both pg_name and pg_type are optional. + * + * Returns NULL on failure, sets scf_error(): + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * can't combine the _tmpl_pg_name arguments and get a reasonable + * length name, or pg_name is not a valid property group. + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * Property doesn't exist or exists and has no value. + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + * target property is not SCF_TYPE_ASTRING or has more than one value. + */ +static scf_propertygroup_t * +_find_template_pg_match(scf_service_t *svc, scf_instance_t *inst, + const scf_snapshot_t *snap, const char *pg_name, const char *pg_type, + const char *target, char **tmpl_pg_name) +{ + int ret, r; + scf_propertygroup_t *pg = NULL; + scf_handle_t *h; + scf_iter_t *iter; + char *name, *type; + + assert(inst != NULL || svc != NULL); + assert(inst == NULL || svc == NULL); + + if (inst != NULL) + h = scf_instance_handle(inst); + else + h = scf_service_handle(svc); + if (h == NULL) { + return (NULL); + } + + if ((pg = scf_pg_create(h)) == NULL || + (iter = scf_iter_create(h)) == NULL) { + assert(scf_error() != SCF_ERROR_INVALID_ARGUMENT); + scf_pg_destroy(pg); + return (NULL); + } + + /* + * We're going to walk through the possible pg templates that + * could match the supplied name and type. We do this + * by explicit name lookups when possible to avoid having to + * keep track of a most-explicit-match during iteration. + */ + + /* First look for a template with name and type set and matching. */ + *tmpl_pg_name = _tmpl_pg_name(pg_name, pg_type, 1); + if (*tmpl_pg_name == NULL) + goto fail; + ret = _lookup_pg(svc, inst, snap, *tmpl_pg_name, pg); + if (ret != SCF_WALK_NEXT) { + if (pg != NULL) { + if ((r = check_target_match(pg, target)) == 0) + goto done; + else if (r == -1) + goto fail; + } else { + goto done; + } + } + free(*tmpl_pg_name); + + /* + * Need to search on a name-only match before searching on + * type matches. + */ + + *tmpl_pg_name = _tmpl_pg_name(pg_name, NULL, 0); + if (*tmpl_pg_name == NULL) + goto fail; + ret = _lookup_pg(svc, inst, snap, *tmpl_pg_name, pg); + if (ret != SCF_WALK_NEXT) { + if (pg != NULL) { + if ((r = check_target_match(pg, target)) == 0) + goto done; + else if (r == -1) + goto fail; + } else { + goto done; + } + } + free(*tmpl_pg_name); + + /* Next, see if there's an "nt" template where the type matches. */ + if (pg_type != NULL && pg_name == NULL) { + if (inst != NULL) + ret = scf_iter_instance_pgs_typed_composed(iter, inst, + snap, SCF_GROUP_TEMPLATE_PG_PATTERN); + else + ret = scf_iter_service_pgs_typed(iter, svc, + SCF_GROUP_TEMPLATE_PG_PATTERN); + + if (ret != 0) { + if (ismember(scf_error(), errors_server)) { + goto fail; + } else { + assert(0); + abort(); + } + } + + while ((ret = scf_iter_next_pg(iter, pg)) == 1) { + /* Make sure this is a name and type specified pg. */ + name = _scf_read_single_astring_from_pg(pg, + SCF_PROPERTY_TM_NAME); + if (name == NULL) + continue; + type = _scf_read_single_astring_from_pg(pg, + SCF_PROPERTY_TM_TYPE); + if (type == NULL) { + free(name); + continue; + } + if (strcmp(pg_type, type) == 0 && + check_target_match(pg, target) == 0) { + *tmpl_pg_name = name; + free(type); + goto done; + } + free(type); + free(name); + } + if (ret == -1) { + if (ismember(scf_error(), errors_server)) { + goto fail; + } else { + assert(0); + abort(); + } + } + } + + *tmpl_pg_name = _tmpl_pg_name(NULL, pg_type, 0); + if (*tmpl_pg_name == NULL) + goto fail; + ret = _lookup_pg(svc, inst, snap, *tmpl_pg_name, pg); + if (ret != SCF_WALK_NEXT) { + if (pg != NULL) { + if ((r = check_target_match(pg, target)) == 0) + goto done; + else if (r == -1) + goto fail; + } else { + goto done; + } + } + free(*tmpl_pg_name); + + *tmpl_pg_name = _tmpl_pg_name(NULL, NULL, 0); + if (*tmpl_pg_name == NULL) + goto fail; + ret = _lookup_pg(svc, inst, snap, *tmpl_pg_name, pg); + if (ret != SCF_WALK_NEXT) { + if (pg != NULL) { + if ((r = check_target_match(pg, target)) == 0) + goto done; + else if (r == -1) + goto fail; + } else { + goto done; + } + } + + (void) scf_set_error(SCF_ERROR_NOT_FOUND); +fail: + scf_pg_destroy(pg); + if (*tmpl_pg_name != NULL) + free(*tmpl_pg_name); + *tmpl_pg_name = NULL; + pg = NULL; +done: + if (ret == SCF_WALK_ERROR) + free(*tmpl_pg_name); + scf_iter_destroy(iter); + return (pg); +} + +/* + * Finds the pg match in either the supplied service or instance. + * Returns SCF_WALK_ERROR, SCF_WALK_NEXT, or SCF_WALK_DONE. + * If returning SCF_WALK_ERROR, sets scf_error(): + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * The snaphot is not a valid snapshot name, + * or can't create a reasonable property group template name. + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * Property doesn't exist or exists and has no value. + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + * target property is not SCF_TYPE_ASTRING or has more than one value. + */ +static int +find_pg_match(scf_service_t *svc, scf_instance_t *inst, pg_tmpl_walk_t *p) +{ + scf_snapshot_t *tmpl_snap = NULL; + scf_propertygroup_t *pg; + scf_handle_t *h; + char *tmpl_pg_name; + + assert(svc != NULL || inst != NULL); + assert(svc == NULL || inst == NULL); + + if (inst != NULL) + h = scf_instance_handle(inst); + else + h = scf_service_handle(svc); + if (h == NULL) + return (SCF_WALK_ERROR); + + if (p->pw_snapname != NULL) { + if (_get_snapshot(inst, p->pw_snapname, &tmpl_snap) == -1) + return (SCF_WALK_ERROR); + } + pg = _find_template_pg_match(svc, inst, tmpl_snap, p->pw_pgname, + p->pw_pgtype, p->pw_target, &tmpl_pg_name); + + if (pg != NULL) { + p->pw_snap = tmpl_snap; + p->pw_pg = pg; + p->pw_tmpl_pgname = tmpl_pg_name; + p->pw_inst = inst; + p->pw_svc = svc; + return (SCF_WALK_DONE); + } + + scf_snapshot_destroy(tmpl_snap); + return (SCF_WALK_NEXT); +} + +/* + * return 0 on success and -1 on failure. + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_HANDLE_MISMATCH + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * FMRI argument, snapshot name, pg_name, or pg is invalid. + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * SCF_ERROR_NOT_SET + */ +int +scf_tmpl_get_by_pg(scf_propertygroup_t *pg, scf_pg_tmpl_t *pg_tmpl, int flags) +{ + char *fmribuf = NULL, *snapbuf = NULL, *pg_name = NULL, *pg_type = NULL; + int ret = 0; + ssize_t fbufsz = scf_limit(SCF_LIMIT_MAX_FMRI_LENGTH) + 1; + ssize_t nbufsz = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH) + 1; + ssize_t tbufsz = scf_limit(SCF_LIMIT_MAX_PG_TYPE_LENGTH) + 1; + scf_instance_t *inst = NULL; + scf_snaplevel_t *snaplvl = NULL; + scf_service_t *svc = NULL; + scf_handle_t *h; + scf_snapshot_t *snap = NULL; + pg_tmpl_walk_t *p; + + assert(fbufsz != 0 && nbufsz != 0 && tbufsz != 0); + + scf_tmpl_pg_reset(pg_tmpl); + + if ((h = scf_pg_handle(pg)) == NULL) + return (-1); + + if ((inst = scf_instance_create(h)) == NULL || + (svc = scf_service_create(h)) == NULL || + (snaplvl = scf_snaplevel_create(h)) == NULL) { + scf_instance_destroy(inst); + scf_service_destroy(svc); + return (-1); + } + + if ((fmribuf = malloc(fbufsz)) == NULL || + (pg_name = malloc(nbufsz)) == NULL || + (pg_type = malloc(tbufsz)) == NULL || + (p = calloc(1, sizeof (pg_tmpl_walk_t))) == NULL) { + free(fmribuf); + free(pg_name); + free(pg_type); + scf_instance_destroy(inst); + scf_service_destroy(svc); + scf_snaplevel_destroy(snaplvl); + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + return (-1); + } + + if (scf_pg_get_name(pg, pg_name, nbufsz) < 0) { + ret = -1; + goto fail; + } + + if (scf_pg_get_type(pg, pg_type, tbufsz) < 0) { + ret = -1; + goto fail; + } + p->pw_pgname = pg_name; + p->pw_pgtype = pg_type; + + ret = scf_pg_get_parent_snaplevel(pg, snaplvl); + if (ret == -1) { + switch (scf_error()) { + case SCF_ERROR_CONSTRAINT_VIOLATED: + /* Parent type doesn't match. Keep looking. */ + break; + + case SCF_ERROR_DELETED: + case SCF_ERROR_NOT_BOUND: + case SCF_ERROR_NOT_SET: + /* Pass these back to the caller. */ + goto fail; + + case SCF_ERROR_HANDLE_MISMATCH: + default: + assert(0); + abort(); + } + + /* + * No snapshot. We'll use 'editing' by default since + * snap and snapbuf are NULL. + */ + p->pw_snapname = NULL; + + } else { + if ((snap = scf_snapshot_create(h)) == NULL) { + ret = -1; + goto fail; + } + + ret = scf_snaplevel_get_parent(snaplvl, snap); + if (ret == -1) { + if (ismember(scf_error(), errors_server)) { + ret = -1; + goto fail; + } else { + assert(0); + abort(); + } + } + + /* Grab snapshot name while we're here. */ + if ((snapbuf = malloc(nbufsz)) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + ret = -1; + goto fail; + } + if (scf_snapshot_get_name(snap, snapbuf, nbufsz) < 0) { + if (ismember(scf_error(), errors_server)) { + ret = -1; + goto fail; + } else { + assert(0); + abort(); + } + } + p->pw_snapname = snapbuf; + + ret = scf_snapshot_get_parent(snap, inst); + if (ret == -1) { + if (ismember(scf_error(), errors_server)) { + ret = -1; + goto fail; + } else { + assert(0); + abort(); + } + } + + _walk_template_instances(NULL, inst, snap, + (walk_template_inst_func_t *)find_pg_match, p, flags); + } + + /* No snapshot parent. Go looking for instance parent. */ + if (snapbuf == NULL) { + /* First look for instance parent. */ + ret = scf_pg_get_parent_instance(pg, inst); + if (ret == 0) { + _walk_template_instances(NULL, inst, snap, + (walk_template_inst_func_t *)find_pg_match, + p, flags); + /* OK, check for service parent */ + } else if (ret == -1 && + scf_error() == SCF_ERROR_CONSTRAINT_VIOLATED) { + ret = scf_pg_get_parent_service(pg, svc); + if (ret == 0) { + _walk_template_instances(svc, NULL, snap, + (walk_template_inst_func_t *)find_pg_match, + p, flags); + } else { + switch (scf_error()) { + case SCF_ERROR_CONSTRAINT_VIOLATED: + (void) scf_set_error( + SCF_ERROR_NOT_FOUND); + /*FALLTHROUGH*/ + + case SCF_ERROR_CONNECTION_BROKEN: + case SCF_ERROR_DELETED: + case SCF_ERROR_HANDLE_MISMATCH: + case SCF_ERROR_NOT_BOUND: + case SCF_ERROR_NOT_SET: + ret = -1; + goto fail; + + default: + assert(0); + abort(); + } + } + } else { + ret = -1; + goto fail; + } + } + + if (p->pw_pg != NULL) { + pg_tmpl->pt_h = h; + pg_tmpl->pt_pg = p->pw_pg; + pg_tmpl->pt_inst = p->pw_inst; + pg_tmpl->pt_snap = p->pw_snap; + pg_tmpl->pt_svc = p->pw_svc; + pg_tmpl->pt_populated = 1; + free(p->pw_tmpl_pgname); + ret = 0; + goto done; + } + + ret = -1; + (void) scf_set_error(SCF_ERROR_NOT_FOUND); + +fail: + scf_instance_destroy(inst); + scf_service_destroy(svc); + scf_snapshot_destroy(snap); +done: + free(snapbuf); + free(fmribuf); + free(pg_name); + free(pg_type); + free(p); + scf_snaplevel_destroy(snaplvl); + return (ret); +} + +/* + * int scf_tmpl_get_by_pg_name() + * + * Get a template by a combination of the name and type. Either name + * or type can be null, which indicates a wildcard. flags may be + * SCF_PG_TMPL_FLAG_CURRENT (use current properties rather than + * the defined or running snapshot), and SCF_PG_TMPL_FLAG_EXACT (match + * only templates defined by the FMRI in question, not by its restarter + * or globally). Returns 0 on success and -1 on error, and sets + * scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * The connection to the repository was lost. + * SCF_ERROR_DELETED + * The instance has been deleted. + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * FMRI isn't valid, pg_name is too long to look for a template, or + * snapshot specified isn't a valid name + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * The server does not have adequate resources to complete the request. + * SCF_ERROR_NOT_BOUND + * The handle is not currently bound. + * SCF_ERROR_NOT_FOUND + * Object matching FMRI doesn't exist in the repository, or snapshot + * doesn't exist. + */ +int +scf_tmpl_get_by_pg_name(const char *fmri, const char *snapshot, + const char *pg_name, const char *pg_type, scf_pg_tmpl_t *pg_tmpl, int flags) +{ + scf_instance_t *inst = NULL; + scf_service_t *svc = NULL; + scf_snapshot_t *snap = NULL; + pg_tmpl_walk_t *p; + scf_handle_t *h; + int ret; + + assert(pg_tmpl != NULL); + h = pg_tmpl->pt_h; + assert(h != NULL); + + scf_tmpl_pg_reset(pg_tmpl); + + if ((inst = scf_instance_create(h)) == NULL || + (svc = scf_service_create(h)) == NULL) { + scf_instance_destroy(inst); + return (-1); + } + + p = calloc(1, sizeof (pg_tmpl_walk_t)); + if (p == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto fail_zalloc; + } + + ret = scf_handle_decode_fmri(h, fmri, NULL, NULL, inst, NULL, + NULL, SCF_DECODE_FMRI_EXACT); + if (ret == 0) { + scf_service_destroy(svc); + svc = NULL; + } else if (ret != 0 && + scf_error() == SCF_ERROR_CONSTRAINT_VIOLATED) { + ret = scf_handle_decode_fmri(h, fmri, NULL, svc, + NULL, NULL, NULL, SCF_DECODE_FMRI_EXACT); + if (ret == 0) { + scf_instance_destroy(inst); + inst = NULL; + } + } + if (ret != 0) { + if (ismember(scf_error(), errors_server)) { + goto fail; + } else switch (scf_error()) { + case SCF_ERROR_CONSTRAINT_VIOLATED: + (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + goto fail; + + case SCF_ERROR_INVALID_ARGUMENT: + case SCF_ERROR_NOT_FOUND: + goto fail; + + case SCF_ERROR_HANDLE_MISMATCH: + case SCF_ERROR_NOT_SET: + default: + assert(0); + abort(); + } + } + + assert(svc == NULL || inst == NULL); + assert(svc != NULL || inst != NULL); + + if (inst != NULL) { + if (snapshot == NULL || strcmp(snapshot, "running") == 0 || + (flags & SCF_PG_TMPL_FLAG_CURRENT) == + SCF_PG_TMPL_FLAG_CURRENT) { + if (_get_snapshot(inst, NULL, &snap) == -1) + goto fail; + } else { + if (_get_snapshot(inst, snapshot, &snap) == -1) { + goto fail; + } else if (scf_error() == SCF_ERROR_NOT_FOUND) { + goto fail; + } + } + } else { + /* If we have a service fmri, snapshot is ignored. */ + scf_snapshot_destroy(snap); + snap = NULL; + } + + p->pw_snapname = snapshot; + p->pw_pgname = pg_name; + p->pw_pgtype = pg_type; + + /* + * For each of instance, restarter, global + * - check for a tm_pg_pattern_nt_<name> matching type + * - check for a tm_pg_pattern_t_<type> matching type + * - check for any tm_pg_pattern_ + * Currently plan to return the most specific match only. + */ + _walk_template_instances(svc, inst, snap, + (walk_template_inst_func_t *)find_pg_match, p, flags); + + if (p->pw_pg != NULL) { + pg_tmpl->pt_h = h; + pg_tmpl->pt_pg = p->pw_pg; + pg_tmpl->pt_inst = p->pw_inst; + pg_tmpl->pt_snap = p->pw_snap; + pg_tmpl->pt_svc = p->pw_svc; + pg_tmpl->pt_populated = 1; + free(p->pw_tmpl_pgname); + free(p); + return (0); + } + + (void) scf_set_error(SCF_ERROR_NOT_FOUND); +fail: + free(p); +fail_zalloc: + scf_instance_destroy(inst); + scf_service_destroy(svc); + scf_snapshot_destroy(snap); + return (-1); +} + +/* + * Returns NULL on failure, sets scf_error() to _CONNECTION_BROKEN, + * _DELETED, _NO_RESOURCES, or _NOT_BOUND. + */ +static scf_iter_t * +_get_svc_or_inst_iter(scf_handle_t *h, scf_pg_tmpl_t *t) +{ + scf_iter_t *iter; + int ret; + + assert(t->pt_svc != NULL || t->pt_inst != NULL); + assert(t->pt_svc == NULL || t->pt_inst == NULL); + + if ((iter = scf_iter_create(h)) == NULL) { + return (NULL); + } + + /* Iterate on property groups of type template_pg_pattern */ + + if (t->pt_inst != NULL) + ret = scf_iter_instance_pgs_typed_composed(iter, + t->pt_inst, t->pt_snap, + SCF_GROUP_TEMPLATE_PG_PATTERN); + if (t->pt_svc != NULL) + ret = scf_iter_service_pgs_typed(iter, t->pt_svc, + SCF_GROUP_TEMPLATE_PG_PATTERN); + + if (ret != 0) { + if (ismember(scf_error(), errors_server)) { + scf_iter_destroy(iter); + return (NULL); + } else { + assert(0); + abort(); + } + } + + return (iter); +} + +/* + * Returns NULL on failure, sets scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * Handle argument is NULL, or snaphot is not a valid snapshot name. + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + */ +static scf_iter_t * +_get_next_iterator(scf_handle_t *h, scf_pg_tmpl_t *t, const char *snapshot, + int exact) +{ + scf_iter_t *iter = NULL; + ssize_t limit; + + limit = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH) + 1; + assert(limit != 0); + + /* + * Check what level we last iterated on: none, service, + * restarter, or global. Make sure that if one in the middle + * doesn't exist, we move on to the next entity. + */ + do { + switch (t->pt_iter_last) { + case SCF__TMPL_ITER_NONE: + t->pt_iter_last = SCF__TMPL_ITER_INST; + t->pt_inst = t->pt_orig_inst; + t->pt_svc = t->pt_orig_svc; + break; + + case SCF__TMPL_ITER_INST: + /* + * Don't go any further than the specified instance + * if exact was set. + */ + if (exact == 1) { + (void) scf_set_error(SCF_ERROR_NOT_FOUND); + goto fail; + } + t->pt_iter_last = SCF__TMPL_ITER_RESTARTER; + t->pt_inst = _get_restarter_inst(h, t->pt_orig_svc, + t->pt_orig_inst, t->pt_snap); + t->pt_svc = NULL; + break; + + case SCF__TMPL_ITER_RESTARTER: + t->pt_iter_last = SCF__TMPL_ITER_GLOBAL; + t->pt_inst = _get_global_inst(h); + t->pt_svc = NULL; + break; + + case SCF__TMPL_ITER_GLOBAL: + (void) scf_set_error(SCF_ERROR_NOT_FOUND); + return (NULL); + + default: + assert(0); + abort(); + } + } while (t->pt_inst == NULL && t->pt_svc == NULL); + + /* Set pt_snap to the snapshot for this instance */ + if (t->pt_inst != NULL) { + scf_snapshot_destroy(t->pt_snap); + if (_get_snapshot(t->pt_inst, snapshot, + &t->pt_snap) == -1) + goto fail; + } + + + iter = _get_svc_or_inst_iter(h, t); +fail: + return (iter); +} + +/* + * scf_pg_tmpl_t *scf_tmpl_pg_create(scf_handle_t *) + * + * Returns NULL on failure, sets scf_error() to _INVALID_ARGUMENT + * or _NO_MEMORY. + */ +scf_pg_tmpl_t * +scf_tmpl_pg_create(scf_handle_t *handle) +{ + scf_pg_tmpl_t *pg_tmpl = NULL; + + if (handle == NULL) { + (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + return (NULL); + } + pg_tmpl = calloc(1, sizeof (scf_pg_tmpl_t)); + if (pg_tmpl == NULL) + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + else + pg_tmpl->pt_h = handle; + + return (pg_tmpl); +} + +/* + * Retrieves name or type of a template pg. + * Returns -1 on failure. Sets scf_error(): + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + * pname property is not SCF_TYPE_ASTRING or has more than one value. + */ +static ssize_t +_scf_tmpl_prop_value(scf_propertygroup_t *pg, const char *pname, char **out) +{ + assert(strcmp(pname, SCF_PROPERTY_TM_NAME) == 0 || + strcmp(pname, SCF_PROPERTY_TM_TYPE) == 0); + + *out = _scf_read_single_astring_from_pg(pg, pname); + + if (*out != NULL && *out[0] == '\0') { + (void) scf_set_error(SCF_ERROR_NONE); + free(*out); + *out = strdup(SCF_TMPL_WILDCARD); + if (*out == NULL) + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + } + if (*out == NULL) { + if (ismember(scf_error(), errors_server)) { + return (-1); + } else switch (scf_error()) { + case SCF_ERROR_CONSTRAINT_VIOLATED: + case SCF_ERROR_NOT_FOUND: + case SCF_ERROR_TYPE_MISMATCH: + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + return (-1); + + case SCF_ERROR_INVALID_ARGUMENT: + case SCF_ERROR_NOT_SET: + default: + assert(0); + abort(); + } + } + + return (strlen(*out)); +} + +/* + * int scf_tmpl_iter_pgs() + * + * Iterates through the property group templates for the fmri given. + * When t is uninitialized or reset, sets t to the first property group + * template in fmri. On subsequent calls, sets t to the next property group + * template in frmi. + * Returns 1 on success, 0 when no property group templates are left to + * iterate, -1 on error. + * The flags argument may include SCF_PG_TMPL_FLAG_REQUIRED, + * SCF_PG_TMPL_FLAG_CURRENT, and/or SCF_PG_TMPL_FLAG_EXACT. + * + * Returns -1 on error and sets scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * The handle argument is NULL, fmri is invalid, or snapshot is invalid. + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * SCF_ERROR_PERMISSION_DENIED + */ +int +scf_tmpl_iter_pgs(scf_pg_tmpl_t *t, const char *fmri, const char *snapshot, + const char *type, int flags) +{ + scf_handle_t *h; + scf_service_t *svc = NULL; + scf_instance_t *inst = NULL; + scf_propertygroup_t *pg = NULL; + scf_snapshot_t *snap = NULL; + scf_pg_tmpl_t *pg_tmpl = NULL; + int err; + int found = 0; + char *tmpl_type; + uint8_t required; + int ret; + + if (t == NULL) { + (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + return (-1); + } + + h = t->pt_h; + + if (t->pt_populated == 0) { + if ((svc = scf_service_create(h)) == NULL || + (inst = scf_instance_create(h)) == NULL || + (pg = scf_pg_create(h)) == NULL) { + goto fail_non_populated; + } + + ret = scf_handle_decode_fmri(h, fmri, NULL, NULL, inst, NULL, + NULL, SCF_DECODE_FMRI_EXACT); + if (ret == 0) { + scf_service_destroy(svc); + svc = NULL; + } else if (ret != 0 && + scf_error() == SCF_ERROR_CONSTRAINT_VIOLATED) { + ret = scf_handle_decode_fmri(h, fmri, NULL, svc, + NULL, NULL, NULL, SCF_DECODE_FMRI_EXACT); + if (ret == 0) { + scf_instance_destroy(inst); + inst = NULL; + } + } + + if (ret != 0) { + if (ismember(scf_error(), errors_server)) { + goto fail_non_populated; + } else switch (scf_error()) { + case SCF_ERROR_CONSTRAINT_VIOLATED: + (void) scf_set_error( + SCF_ERROR_INVALID_ARGUMENT); + goto fail_non_populated; + + case SCF_ERROR_INVALID_ARGUMENT: + case SCF_ERROR_NOT_FOUND: + goto fail_non_populated; + + case SCF_ERROR_HANDLE_MISMATCH: + case SCF_ERROR_NOT_SET: + default: + assert(0); + abort(); + } + } + + assert(svc == NULL || inst == NULL); + assert(svc != NULL || inst != NULL); + + if (inst != NULL) { + if (snapshot == NULL || + strcmp(snapshot, "running") == 0 || + (flags & SCF_PG_TMPL_FLAG_CURRENT) == + SCF_PG_TMPL_FLAG_CURRENT) { + if (_get_snapshot(inst, NULL, &snap) == -1) + goto fail_non_populated; + } else { + (void) scf_set_error(SCF_ERROR_NONE); + if (_get_snapshot(inst, snapshot, + &snap) == -1) { + goto fail_non_populated; + } else if (scf_error() == SCF_ERROR_NOT_FOUND) { + goto fail_non_populated; + } + } + } else { + scf_snapshot_destroy(snap); + snap = NULL; + } + + pg_tmpl = t; + pg_tmpl->pt_orig_inst = inst; + pg_tmpl->pt_orig_svc = svc; + pg_tmpl->pt_snap = snap; + pg_tmpl->pt_is_iter = 1; + pg_tmpl->pt_iter_last = SCF__TMPL_ITER_NONE; + pg_tmpl->pt_pg = pg; + pg_tmpl->pt_populated = 1; + } else { + if (t->pt_is_iter != 1) { + (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + return (-1); + } + pg_tmpl = t; + assert(pg_tmpl->pt_pg != NULL); + } + + if (pg_tmpl->pt_iter == NULL) { + pg_tmpl->pt_iter = _get_next_iterator(h, pg_tmpl, snapshot, + (flags & SCF_PG_TMPL_FLAG_EXACT) ? 1 : 0); + if (pg_tmpl->pt_iter == NULL) { + if (scf_error() == SCF_ERROR_NOT_FOUND) + return (0); + else + return (-1); + } + } + + while (found == 0) { + while ((err = scf_iter_next_pg(pg_tmpl->pt_iter, + pg_tmpl->pt_pg)) != 1) { + if (err == -1) { + if (ismember(scf_error(), errors_server)) { + return (-1); + } else switch (scf_error()) { + case SCF_ERROR_HANDLE_MISMATCH: + return (-1); + + case SCF_ERROR_NOT_SET: + case SCF_ERROR_INVALID_ARGUMENT: + default: + assert(0); + abort(); + } + } else if (err == 0) { + /* This iteration is done. Get the next one */ + scf_iter_destroy(pg_tmpl->pt_iter); + pg_tmpl->pt_iter = _get_next_iterator(h, + pg_tmpl, snapshot, + (flags & SCF_PG_TMPL_FLAG_EXACT) ? 1 : 0); + if (pg_tmpl->pt_iter == NULL) { + if (scf_error() == SCF_ERROR_NOT_FOUND) + return (0); + else + return (-1); + } + continue; + } else { + assert(0); + abort(); + } + } + + /* + * Discard pgs which don't exist at the right scoping. This + * check also makes sure that if we're looking at, for + * example, svc:/system/svc/restarter:default, that we + * don't hand back the same property groups twice. + */ + switch (t->pt_iter_last) { + case SCF__TMPL_ITER_INST: + ret = check_target_match(pg_tmpl->pt_pg, + SCF_TM_TARGET_THIS); + break; + case SCF__TMPL_ITER_RESTARTER: + ret = check_target_match(pg_tmpl->pt_pg, + SCF_TM_TARGET_DELEGATE); + break; + case SCF__TMPL_ITER_GLOBAL: + ret = check_target_match(pg_tmpl->pt_pg, + SCF_TM_TARGET_ALL); + break; + case SCF__TMPL_ITER_NONE: + default: + assert(0); + abort(); + } + + if (ret != 0) + continue; + + /* + * If walking only required property groups, check if + * the retrieved group is required. + */ + if (flags & SCF_PG_TMPL_FLAG_REQUIRED) { + if (scf_tmpl_pg_required(pg_tmpl, &required) == 0) { + if (required == 0) + continue; + } else { + return (-1); + } + } + + /* + * If type != NULL, check if type property matches. If no + * type property exists, consider it a match. + */ + if (type != NULL) { + if (scf_tmpl_pg_type(pg_tmpl, &tmpl_type) != -1) { + if (strcmp(tmpl_type, SCF_TMPL_WILDCARD) + == 0 || strcmp(type, tmpl_type) == 0) { + free(tmpl_type); + break; + } + free(tmpl_type); + } else if (scf_error() == SCF_ERROR_NOT_FOUND || + scf_error() == SCF_ERROR_CONSTRAINT_VIOLATED || + scf_error() == SCF_ERROR_TYPE_MISMATCH) { + break; + } else { + return (-1); + } + } else { + break; + } + } + + return (1); + +fail_non_populated: + scf_service_destroy(svc); + scf_instance_destroy(inst); + scf_pg_destroy(pg); + scf_snapshot_destroy(snap); + return (-1); +} + +void +scf_tmpl_pg_destroy(scf_pg_tmpl_t *t) +{ + if (t == NULL) + return; + + scf_pg_destroy(t->pt_pg); + scf_instance_destroy(t->pt_inst); + if (t->pt_inst != t->pt_orig_inst) + scf_instance_destroy(t->pt_orig_inst); + scf_snapshot_destroy(t->pt_snap); + scf_service_destroy(t->pt_orig_svc); + if (t->pt_svc != t->pt_orig_svc) + scf_service_destroy(t->pt_svc); + scf_iter_destroy(t->pt_iter); + free(t); +} + +void +scf_tmpl_pg_reset(scf_pg_tmpl_t *t) +{ + scf_pg_destroy(t->pt_pg); + t->pt_pg = NULL; + + scf_instance_destroy(t->pt_inst); + if (t->pt_inst != t->pt_orig_inst) + scf_instance_destroy(t->pt_orig_inst); + t->pt_inst = NULL; + t->pt_orig_inst = NULL; + + scf_snapshot_destroy(t->pt_snap); + t->pt_snap = NULL; + + scf_service_destroy(t->pt_orig_svc); + if (t->pt_svc != t->pt_orig_svc) + scf_service_destroy(t->pt_svc); + t->pt_orig_svc = NULL; + t->pt_svc = NULL; + + scf_iter_destroy(t->pt_iter); + t->pt_iter = NULL; + + t->pt_populated = 0; + t->pt_is_iter = 0; + t->pt_iter_last = 0; + + /* Do not reset t->pt_h. */ +} + +/* + * int scf_tmpl_get_by_prop() + * + * Get the property template given a property group template and property + * name. No flags are currently defined for this function. + * + * Returns NULL on failure, and sets scf_error(): + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * Template object matching property doesn't exist in the repository. + * SCF_ERROR_TYPE_MISMATCH + * Matching template object is the wrong type in the repository. + */ +int +scf_tmpl_get_by_prop(scf_pg_tmpl_t *t, const char *prop, + scf_prop_tmpl_t *prop_tmpl, int flags) +{ + char *tmpl_prop_name; + scf_propertygroup_t *pg = NULL; + char *pg_type; + int found = 0; + + if (flags != 0) { + (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + return (-1); + } + + scf_tmpl_prop_reset(prop_tmpl); + if ((pg = scf_pg_create(scf_pg_handle(t->pt_pg))) == NULL) + return (-1); + + tmpl_prop_name = _tmpl_prop_name(prop, t); + if (tmpl_prop_name == NULL) { + assert(scf_error() != SCF_ERROR_NOT_SET); + return (-1); + } + + if (_get_pg(t->pt_svc, t->pt_inst, t->pt_snap, + tmpl_prop_name, pg) != 0) { + if (!ismember(scf_error(), errors_server)) { + switch (scf_error()) { + case SCF_ERROR_NOT_FOUND: + case SCF_ERROR_INVALID_ARGUMENT: + break; + + case SCF_ERROR_NOT_SET: + case SCF_ERROR_HANDLE_MISMATCH: + default: + assert(0); + abort(); + } + } + } else { + /* + * We've only found a template property group if the type + * is correct. + */ + if ((pg_type = _scf_get_pg_type(pg)) != NULL && + strcmp(pg_type, SCF_GROUP_TEMPLATE_PROP_PATTERN) == 0) + found++; + else + (void) scf_set_error(SCF_ERROR_TYPE_MISMATCH); + + + free(pg_type); + } + + if (found == 0) { + scf_pg_destroy(pg); + free(tmpl_prop_name); + return (-1); + } + + prop_tmpl->prt_h = scf_pg_handle(t->pt_pg); + prop_tmpl->prt_t = t; + prop_tmpl->prt_pg = pg; + prop_tmpl->prt_pg_name = tmpl_prop_name; + prop_tmpl->prt_populated = 1; + + return (0); +} + +/* + * scf_prop_tmpl_t *scf_tmpl_prop_create(scf_handle_t *); + * + * Returns NULL on failure, sets scf_error() to _INVALID_ARGUMENT, or + * _NO_MEMORY. + */ +scf_prop_tmpl_t * +scf_tmpl_prop_create(scf_handle_t *handle) +{ + scf_prop_tmpl_t *pt; + + if (handle == NULL) { + (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + return (NULL); + } + pt = calloc(1, sizeof (scf_prop_tmpl_t)); + if (pt == NULL) + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + else + pt->prt_h = handle; + + return (pt); +} + +/* + * int scf_tmpl_iter_props() + * + * Iterates over all property templates defined in the specified property + * group template. The iterator state is stored on the property template + * data structure, and the data structure should be allocated with + * scf_tmpl_prop_create(). To continue the iteration, the previously + * returned structure should be passed in as an argument to this function. + * flags can include SCF_PROP_TMPL_FLAG_REQUIRED. The function returns + * 1 when a result was found, and 0 when the iteration is complete. + * + * Returns -1 on failure, and sets scf_error(): + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + * Template data is invalid. One of the property templates in this + * pg_tmpl is malformed. + */ +int +scf_tmpl_iter_props(scf_pg_tmpl_t *t, scf_prop_tmpl_t *pt, int flags) +{ + scf_prop_tmpl_t *prop_tmpl; + char *pg_pat; + char *pg_name = NULL; + int err; + int ret; + ssize_t size = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH) + 1; + uint8_t required; + scf_handle_t *h; + scf_propertygroup_t *pg = NULL; + scf_iter_t *iter = NULL; + + assert(size != 0); + if (t == NULL || pt == NULL) { + (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + return (-1); + } + + assert(t->pt_inst == NULL || t->pt_svc == NULL); + assert(t->pt_inst != NULL || t->pt_svc != NULL); + + if ((pg_name = malloc(size)) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + return (-1); + } + + if (pt->prt_populated == 0) { + if ((h = scf_pg_handle(t->pt_pg)) == NULL) + goto fail_non_populated; + + if ((pg = scf_pg_create(h)) == NULL || + (iter = scf_iter_create(h)) == NULL) + goto fail_non_populated; + + if (t->pt_inst != NULL) + err = scf_iter_instance_pgs_typed_composed(iter, + t->pt_inst, t->pt_snap, + SCF_GROUP_TEMPLATE_PROP_PATTERN); + else if (t->pt_svc != NULL) + err = scf_iter_service_pgs_typed(iter, t->pt_svc, + SCF_GROUP_TEMPLATE_PROP_PATTERN); + + if (err != 0) { + if (ismember(scf_error(), errors_server)) { + goto fail_non_populated; + } else switch (scf_error()) { + case SCF_ERROR_INVALID_ARGUMENT: + goto fail_non_populated; + + case SCF_ERROR_NOT_SET: + case SCF_ERROR_HANDLE_MISMATCH: + default: + assert(0); + abort(); + } + + } + prop_tmpl = pt; + prop_tmpl->prt_t = t; + prop_tmpl->prt_populated = 1; + prop_tmpl->prt_pg = pg; + prop_tmpl->prt_iter = iter; + } else { + prop_tmpl = pt; + } + + while ((err = scf_iter_next_pg(prop_tmpl->prt_iter, + prop_tmpl->prt_pg)) > 0) { + /* + * Check if the name matches the appropriate property + * group template name. + */ + pg_pat = _scf_read_single_astring_from_pg(prop_tmpl->prt_pg, + SCF_PROPERTY_TM_PG_PATTERN); + if (pg_pat == NULL) { + if (ismember(scf_error(), errors_server)) { + uu_free(pg_name); + return (-1); + } else switch (scf_error()) { + case SCF_ERROR_NOT_FOUND: + continue; + + case SCF_ERROR_CONSTRAINT_VIOLATED: + case SCF_ERROR_TYPE_MISMATCH: + (void) scf_set_error( + SCF_ERROR_TEMPLATE_INVALID); + free(pg_name); + return (-1); + + case SCF_ERROR_INVALID_ARGUMENT: + case SCF_ERROR_NOT_SET: + default: + assert(0); + abort(); + } + } + if ((ret = scf_pg_get_name(t->pt_pg, pg_name, size)) <= 0) { + free(pg_pat); + if (ret == 0) + continue; + + if (ismember(scf_error(), errors_server)) { + free(pg_name); + return (-1); + } else { + assert(0); + abort(); + } + } + if (strcmp(pg_pat, pg_name) != 0) { + free(pg_pat); + continue; + } + free(pg_pat); + + /* + * If walking only required properties, check if + * the retrieved property is required. + */ + if (flags & SCF_PROP_TMPL_FLAG_REQUIRED) { + if (scf_tmpl_prop_required(prop_tmpl, &required) == 0) { + if (required == 0) + continue; + } else { + free(pg_name); + return (-1); + } + } + free(pg_name); + return (0); + } + + if (err == -1) { + if (ismember(scf_error(), errors_server)) { + free(pg_name); + return (-1); + } else { + assert(0); + abort(); + } + } else if (err == 0) { + scf_iter_destroy(prop_tmpl->prt_iter); + prop_tmpl->prt_iter = NULL; + prop_tmpl->prt_populated = 0; + } + free(pg_name); + + return (1); + +fail_non_populated: + free(pg_name); + scf_pg_destroy(pg); + scf_iter_destroy(iter); + return (-1); +} + +void +scf_tmpl_prop_destroy(scf_prop_tmpl_t *t) +{ + if (t == NULL) + return; + + scf_pg_destroy(t->prt_pg); + free(t->prt_pg_name); + free(t->prt_iter); + free(t); +} + +void +scf_tmpl_prop_reset(scf_prop_tmpl_t *t) +{ + scf_pg_destroy(t->prt_pg); + t->prt_pg = NULL; + + free(t->prt_pg_name); + t->prt_pg_name = NULL; + + free(t->prt_iter); + t->prt_iter = NULL; + + t->prt_populated = 0; + t->prt_h = NULL; + t->prt_t = NULL; +} + +/* + * Returns -1 on failure. Sets scf_error(): + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + * The name of the template property group (the pname property) has + * an improper repository format and is not type astring or has + * more than one value. + */ +ssize_t +scf_tmpl_pg_name(const scf_pg_tmpl_t *t, char **out) +{ + return (_scf_tmpl_prop_value(t->pt_pg, SCF_PROPERTY_TM_NAME, out)); +} + +/* + * returns an allocated string that must be freed + * + * Returns NULL on failure, sets scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * name not a valid property name + * name and locale are too long to make a property name + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * Property doesn't exist or exists and has no value. + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +static char * +_read_localized_astring_from_pg(scf_propertygroup_t *pg, const char *name, + const char *locale) +{ + char *str; + char *lname_prop; + + str = _add_locale_to_name(name, locale); + if (str == NULL) + return (NULL); + lname_prop = _scf_read_single_astring_from_pg(pg, str); + if (lname_prop == NULL) { + free(str); + if (scf_error() != SCF_ERROR_NOT_FOUND) + return (NULL); + str = _add_locale_to_name(name, "C"); + if (str == NULL) + return (NULL); + lname_prop = _scf_read_single_astring_from_pg(pg, str); + } + free(str); + if (lname_prop == NULL) { + if (scf_error() == SCF_ERROR_TYPE_MISMATCH || + scf_error() == SCF_ERROR_CONSTRAINT_VIOLATED) + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + } + return (lname_prop); +} + +/* + * returns an allocated string that must be freed + * + * Returns -1 on failure, sets scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * locale is too long to make a valid property name + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * Property doesn't exist or exists and has no value. + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +ssize_t +scf_tmpl_pg_common_name(const scf_pg_tmpl_t *t, const char *locale, char **out) +{ + assert(out != NULL); + if ((*out = _read_localized_astring_from_pg(t->pt_pg, + SCF_PROPERTY_TM_COMMON_NAME_PREFIX, locale)) == NULL) + return (-1); + + return (strlen(*out)); +} + +/* + * returns an allocated string that must be freed + * + * Returns -1 on failure, sets scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * locale is too long to make a valid property name + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * Property doesn't exist or exists and has no value. + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +ssize_t +scf_tmpl_pg_description(const scf_pg_tmpl_t *t, const char *locale, char **out) +{ + assert(out != NULL); + if ((*out = _read_localized_astring_from_pg(t->pt_pg, + SCF_PROPERTY_TM_DESCRIPTION_PREFIX, locale)) == NULL) + return (-1); + + return (strlen(*out)); +} + +/* + * Returns -1 on failure. Sets scf_error(): + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * 'type' property doesn't exist or exists and has no value. + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + * 'type' property is not SCF_TYPE_ASTRING or has more than one value. + */ +ssize_t +scf_tmpl_pg_type(const scf_pg_tmpl_t *t, char **out) +{ + return (_scf_tmpl_prop_value(t->pt_pg, SCF_PROPERTY_TM_TYPE, out)); +} + +/* + * Returns -1 on failure, sets scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + * required property is not SCF_TYPE_BOOLEAN or has more than one value. + */ +int +scf_tmpl_pg_required(const scf_pg_tmpl_t *t, uint8_t *out) +{ + + if (_read_single_boolean_from_pg(t->pt_pg, SCF_PROPERTY_TM_REQUIRED, + out) == -1) { + if (ismember(scf_error(), errors_server)) { + return (-1); + } else switch (scf_error()) { + case SCF_ERROR_CONSTRAINT_VIOLATED: + case SCF_ERROR_TYPE_MISMATCH: + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + return (-1); + + case SCF_ERROR_NOT_FOUND: + *out = 0; + return (0); + + case SCF_ERROR_INVALID_ARGUMENT: + default: + assert(0); + abort(); + } + } + + return (0); +} + +/* + * Returns -1 on failure. Sets scf_error(): + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + * target property is not SCF_TYPE_ASTRING or has more than one value. + */ +ssize_t +scf_tmpl_pg_target(const scf_pg_tmpl_t *t, char **out) +{ + *out = _scf_read_single_astring_from_pg(t->pt_pg, + SCF_PROPERTY_TM_TARGET); + + if (*out == NULL) { + if (ismember(scf_error(), errors_server)) { + return (-1); + } else switch (scf_error()) { + case SCF_ERROR_CONSTRAINT_VIOLATED: + case SCF_ERROR_NOT_FOUND: + case SCF_ERROR_TYPE_MISMATCH: + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + return (-1); + + case SCF_ERROR_INVALID_ARGUMENT: + case SCF_ERROR_NOT_SET: + default: + assert(0); + abort(); + } + } + + return (strlen(*out)); +} + +/* + * Returns -1 on failure. Sets scf_error(): + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +ssize_t +scf_tmpl_prop_name(const scf_prop_tmpl_t *t, char **out) +{ + *out = _scf_read_single_astring_from_pg(t->prt_pg, + SCF_PROPERTY_TM_NAME); + + if (*out != NULL && *out[0] == '\0') { + free(*out); + *out = NULL; + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + } + if (*out == NULL) { + if (ismember(scf_error(), errors_server)) { + return (-1); + } else switch (scf_error()) { + case SCF_ERROR_CONSTRAINT_VIOLATED: + case SCF_ERROR_NOT_FOUND: + case SCF_ERROR_TEMPLATE_INVALID: + case SCF_ERROR_TYPE_MISMATCH: + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + return (-1); + + case SCF_ERROR_INVALID_ARGUMENT: + case SCF_ERROR_NOT_SET: + default: + assert(0); + abort(); + } + } + + return (strlen(*out)); +} + +/* + * Returns -1 on failure. Sets scf_error(): + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * 'type' property doesn't exist or exists and has no value. + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + * 'type' property is not SCF_TYPE_ASTRING, has more than one value, + * is SCF_TYPE_INVALID, or is the empty string. + */ +int +scf_tmpl_prop_type(const scf_prop_tmpl_t *t, scf_type_t *out) +{ + char *type; + + type = _scf_read_single_astring_from_pg(t->prt_pg, + SCF_PROPERTY_TM_TYPE); + if (type != NULL && type[0] == '\0') { + free(type); + (void) scf_set_error(SCF_ERROR_NOT_FOUND); + return (-1); + } + if (type == NULL) { + if (ismember(scf_error(), errors_server)) { + return (-1); + } else switch (scf_error()) { + case SCF_ERROR_CONSTRAINT_VIOLATED: + case SCF_ERROR_TYPE_MISMATCH: + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + /*FALLTHROUGH*/ + + case SCF_ERROR_NOT_FOUND: + return (-1); + + case SCF_ERROR_INVALID_ARGUMENT: + case SCF_ERROR_NOT_SET: + default: + assert(0); + abort(); + } + } + + *out = scf_string_to_type(type); + free(type); + + if (*out == SCF_TYPE_INVALID) { + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + return (-1); + } + + return (0); +} + +/* + * Returns -1 on failure, sets scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * Property group which represents t was deleted. + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + * required property is not SCF_TYPE_ASTRING has more than one value. + */ +int +scf_tmpl_prop_required(const scf_prop_tmpl_t *t, uint8_t *out) +{ + if (_read_single_boolean_from_pg(t->prt_pg, SCF_PROPERTY_TM_REQUIRED, + out) == -1) { + if (ismember(scf_error(), errors_server)) { + return (-1); + } else switch (scf_error()) { + case SCF_ERROR_CONSTRAINT_VIOLATED: + case SCF_ERROR_TYPE_MISMATCH: + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + return (-1); + + case SCF_ERROR_NOT_FOUND: + *out = 0; + return (0); + + case SCF_ERROR_INVALID_ARGUMENT: + case SCF_ERROR_NOT_SET: + default: + assert(0); + abort(); + } + } + + return (0); +} + +/* + * Returns -1 on failure. Sets scf_error(): + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * Property doesn't exist or exists and has no value. + * SCF_ERROR_INVALID_ARGUMENT + * locale is too long to make a property name + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + * common_name property is not SCF_TYPE_ASTRING has more than one value. + */ +ssize_t +scf_tmpl_prop_common_name(const scf_prop_tmpl_t *t, const char *locale, + char **out) +{ + assert(out != NULL); + if ((*out = _read_localized_astring_from_pg(t->prt_pg, + SCF_PROPERTY_TM_COMMON_NAME_PREFIX, locale)) == NULL) + return (-1); + + return (strlen(*out)); +} + +/* + * Returns -1 on failure. Sets scf_error(): + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * Property doesn't exist or exists and has no value. + * SCF_ERROR_INVALID_ARGUMENT + * locale is too long to make a property name + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + * description property is not SCF_TYPE_ASTRING has more than one value. + */ +ssize_t +scf_tmpl_prop_description(const scf_prop_tmpl_t *t, const char *locale, + char **out) +{ + assert(out != NULL); + if ((*out = _read_localized_astring_from_pg(t->prt_pg, + SCF_PROPERTY_TM_DESCRIPTION_PREFIX, locale)) == NULL) + return (-1); + + return (strlen(*out)); +} + +/* + * Returns -1 on failure. Sets scf_error(): + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * Property doesn't exist or exists and has no value. + * SCF_ERROR_INVALID_ARGUMENT + * locale is too long to make a property name + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + * units property is not SCF_TYPE_ASTRING has more than one value. + */ +ssize_t +scf_tmpl_prop_units(const scf_prop_tmpl_t *t, const char *locale, char **out) +{ + assert(out != NULL); + if ((*out = _read_localized_astring_from_pg(t->prt_pg, + SCF_PROPERTY_TM_UNITS_PREFIX, locale)) == NULL) + return (-1); + + return (strlen(*out)); +} + +/* + * Returns -1 on failure. Sets scf_error(): + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + * visibility property is not SCF_TYPE_ASTRING has more than one value. + */ +int +scf_tmpl_prop_visibility(const scf_prop_tmpl_t *t, uint8_t *out) +{ + char *visibility; + + visibility = _scf_read_single_astring_from_pg(t->prt_pg, + SCF_PROPERTY_TM_VISIBILITY); + if (visibility == NULL) { + if (ismember(scf_error(), errors_server)) { + return (-1); + } else switch (scf_error()) { + /* prop doesn't exist we take the default value */ + case SCF_ERROR_NOT_FOUND: + *out = SCF_TMPL_VISIBILITY_READWRITE; + return (0); + + case SCF_ERROR_CONSTRAINT_VIOLATED: + case SCF_ERROR_TYPE_MISMATCH: + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + return (-1); + + case SCF_ERROR_INVALID_ARGUMENT: + case SCF_ERROR_NOT_SET: + default: + assert(0); + abort(); + } + } else if (strcmp(visibility, SCF_TM_VISIBILITY_READWRITE) == 0) { + *out = SCF_TMPL_VISIBILITY_READWRITE; + } else if (strcmp(visibility, SCF_TM_VISIBILITY_HIDDEN) == 0) { + *out = SCF_TMPL_VISIBILITY_HIDDEN; + } else if (strcmp(visibility, SCF_TM_VISIBILITY_READONLY) == 0) { + *out = SCF_TMPL_VISIBILITY_READONLY; + } else { + free(visibility); + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + return (-1); + } + + free(visibility); + return (0); +} + +/* + * Return an allocated string containing the value that must be freed + * with free(). + * + * On error set scf_error() _NO_MEMORY, or _NOT_SET (val has not been set + * to a value). + */ +static char * +_scf_value_get_as_string(scf_value_t *val) +{ + ssize_t sz = scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH) + 1; + char *buf = malloc(sz); + + if (buf == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + } else if (scf_value_get_as_string(val, buf, sz) == -1) { + free(buf); + buf = NULL; + } + + return (buf); +} + +/* + * Returns -1 on failure, sets scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +int +scf_tmpl_prop_cardinality(const scf_prop_tmpl_t *t, uint64_t *min, + uint64_t *max) +{ + scf_value_t *val_min = NULL; + scf_value_t *val_max = NULL; + int ret = 0; + + if (_read_single_value_from_pg(t->prt_pg, + SCF_PROPERTY_TM_CARDINALITY_MIN, &val_min) == 0) { + if (scf_value_get_count(val_min, min) < 0) + goto error; + } else { + if (scf_error() == SCF_ERROR_NOT_FOUND) + *min = 0; + else + goto error; + } + + if (_read_single_value_from_pg(t->prt_pg, + SCF_PROPERTY_TM_CARDINALITY_MAX, &val_max) == 0) { + if (scf_value_get_count(val_max, max) < 0) + goto error; + } else { + if (scf_error() == SCF_ERROR_NOT_FOUND) + *max = UINT64_MAX; + else + goto error; + } + goto cleanup; + +error: + if (ismember(scf_error(), errors_server)) { + ret = -1; + } else switch (scf_error()) { + case SCF_ERROR_TYPE_MISMATCH: + case SCF_ERROR_CONSTRAINT_VIOLATED: + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + /*FALLTHROUGH*/ + + case SCF_ERROR_NOT_FOUND: + case SCF_ERROR_TEMPLATE_INVALID: + ret = -1; + break; + + case SCF_ERROR_NOT_SET: + case SCF_ERROR_INVALID_ARGUMENT: + default: + assert(0); + abort(); + } + +cleanup: + scf_value_destroy(val_min); + scf_value_destroy(val_max); + + return (ret); +} + +/* + * Returns -1 on failure. Sets scf_error(): + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * Property doesn't exist or exists and has no value. + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +int +scf_tmpl_prop_internal_seps(const scf_prop_tmpl_t *t, scf_values_t *vals) +{ + if (_read_astrings_values(t->prt_pg, + SCF_PROPERTY_INTERNAL_SEPARATORS, vals) == NULL) { + if (ismember(scf_error(), errors_server)) { + return (-1); + } else switch (scf_error()) { + case SCF_ERROR_CONSTRAINT_VIOLATED: + case SCF_ERROR_TYPE_MISMATCH: + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + /*FALLTHROUGH*/ + + case SCF_ERROR_NOT_FOUND: + return (-1); + + case SCF_ERROR_INVALID_ARGUMENT: + case SCF_ERROR_NOT_SET: + default: + assert(0); + abort(); + } + } else if (vals->value_count == 0) { + /* property has no value */ + (void) scf_set_error(SCF_ERROR_NOT_FOUND); + scf_values_destroy(vals); + return (-1); + } + + return (0); +} + +/* + * Returns -1 on failure. Sets scf_error(): + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * Property doesn't exist or exists and has no value. + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +int +scf_tmpl_value_name_constraints(const scf_prop_tmpl_t *t, + scf_values_t *vals) +{ + char **ret; + + ret = _read_astrings_values(t->prt_pg, + SCF_PROPERTY_TM_CONSTRAINT_NAME, vals); + + if (ret == NULL) { + if (ismember(scf_error(), errors_server)) { + return (-1); + } else switch (scf_error()) { + case SCF_ERROR_CONSTRAINT_VIOLATED: + case SCF_ERROR_TYPE_MISMATCH: + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + /*FALLTHROUGH*/ + + case SCF_ERROR_NOT_FOUND: + return (-1); + + case SCF_ERROR_INVALID_ARGUMENT: + case SCF_ERROR_NOT_SET: + default: + assert(0); + abort(); + } + } else if (vals->value_count == 0) { + /* property has no value */ + (void) scf_set_error(SCF_ERROR_NOT_FOUND); + scf_values_destroy(vals); + return (-1); + } + + return (0); +} + +/* + * Returns NULL on failure. Sets scf_error(): + * Caller is responsible for freeing returned pointer after use. + * SCF_ERROR_CONSTRAINT_VIOLATED + * More tokens than the array size supplied. + * SCF_ERROR_NO_MEMORY + */ +static void * +_separate_by_separator(char *string, const char *sep, char **array, int size) +{ + char *str, *token; + char *lasts; + int n = 0; + + assert(array != NULL); + assert(string != NULL); + assert(sep != NULL); + assert(size > 0); + + str = strdup(string); + if (str == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + return (NULL); + } + + if ((array[n] = strtok_r(str, sep, &lasts)) == NULL) { + assert(0); + abort(); + } + + n++; + while ((token = strtok_r(NULL, sep, &lasts)) != NULL) { + if (n >= size) { + goto error; + } + array[n] = token; + n++; + } + if (n < size) { + goto error; + } + + return (str); +error: + free(str); + (void) scf_set_error(SCF_ERROR_CONSTRAINT_VIOLATED); + return (NULL); +} + +/* + * check if name is among values of CHOICES_INCLUDE_VALUES + * return 0 if name is present, 1 name is not present, -1 on failure + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +static int +_check_choices_include_values(scf_propertygroup_t *pg, const char *name) +{ + int n = 0, r = 1; + char **ret; + scf_values_t vals; + + if ((ret = _read_astrings_values(pg, + SCF_PROPERTY_TM_CHOICES_INCLUDE_VALUES, &vals)) == NULL) { + if (ismember(scf_error(), errors_server)) { + return (-1); + } else switch (scf_error()) { + case SCF_ERROR_NOT_FOUND: + return (1); + + case SCF_ERROR_TYPE_MISMATCH: + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + return (-1); + + case SCF_ERROR_INVALID_ARGUMENT: + case SCF_ERROR_NOT_SET: + default: + assert(0); + abort(); + } + } + + for (n = 0; n < vals.value_count; ++n) { + if (strcmp(name, ret[n]) == 0) { + r = 0; + break; + } + } + scf_values_destroy(&vals); + return (r); +} + +void +scf_count_ranges_destroy(scf_count_ranges_t *ranges) +{ + if (ranges == NULL) + return; + + ranges->scr_num_ranges = 0; + free(ranges->scr_min); + free(ranges->scr_max); + ranges->scr_min = NULL; + ranges->scr_max = NULL; +} + +void +scf_int_ranges_destroy(scf_int_ranges_t *ranges) +{ + if (ranges == NULL) + return; + + ranges->sir_num_ranges = 0; + free(ranges->sir_min); + free(ranges->sir_max); + ranges->sir_min = NULL; + ranges->sir_max = NULL; +} + +/* + * Returns -1 on failure. Sets scf_error(): + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_CONSTRAINT_VIOLATED + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * Property doesn't exist or exists and has no value. + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +static int +_scf_tmpl_get_count_ranges(const scf_prop_tmpl_t *t, const char *prop, + scf_count_ranges_t *ranges) +{ + scf_values_t vals; + int i = 0; + char **ret; + char *one_range[2]; + char *endptr; + char *str = NULL; + uint64_t *min = NULL; + uint64_t *max = NULL; + + assert(ranges != NULL); + if ((ret = _read_astrings_values(t->prt_pg, prop, &vals)) == NULL) + goto error; + if (vals.value_count == 0) { + /* range values are empty */ + (void) scf_set_error(SCF_ERROR_NOT_FOUND); + goto cleanup; + } + + min = malloc(vals.value_count * sizeof (uint64_t)); + max = malloc(vals.value_count * sizeof (uint64_t)); + if (min == NULL || max == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + for (i = 0; i < vals.value_count; ++i) { + /* min and max should be separated by a "," */ + if ((str = _separate_by_separator(ret[i], ",", one_range, + 2)) == NULL) + goto cleanup; + errno = 0; + min[i] = strtoull(one_range[0], &endptr, 10); + if (errno != 0 || endptr == one_range[0] || *endptr) { + (void) scf_set_error(SCF_ERROR_CONSTRAINT_VIOLATED); + goto cleanup; + } + errno = 0; + max[i] = strtoull(one_range[1], &endptr, 10); + if (errno != 0 || endptr == one_range[1] || *endptr) { + (void) scf_set_error(SCF_ERROR_CONSTRAINT_VIOLATED); + goto cleanup; + } + if (min[i] > max[i]) { + (void) scf_set_error(SCF_ERROR_CONSTRAINT_VIOLATED); + goto cleanup; + } + free(str); + str = NULL; + } + ranges->scr_num_ranges = vals.value_count; + ranges->scr_min = min; + ranges->scr_max = max; + scf_values_destroy(&vals); + return (0); +cleanup: + free(str); + free(min); + free(max); + scf_values_destroy(&vals); +error: + if (ismember(scf_error(), errors_server)) { + return (-1); + } else switch (scf_error()) { + case SCF_ERROR_TYPE_MISMATCH: + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + /*FALLTHROUGH*/ + + case SCF_ERROR_CONSTRAINT_VIOLATED: + case SCF_ERROR_NOT_FOUND: + return (-1); + + case SCF_ERROR_INVALID_ARGUMENT: + case SCF_ERROR_NOT_SET: + default: + assert(0); + abort(); + } + /*NOTREACHED*/ +} + +/* + * Returns -1 on failure. Sets scf_error(): + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_CONSTRAINT_VIOLATED + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * Property doesn't exist or exists and has no value. + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +static int +_scf_tmpl_get_int_ranges(const scf_prop_tmpl_t *t, const char *prop, + scf_int_ranges_t *ranges) +{ + scf_values_t vals; + int n = 0; + char **ret; + char *one_range[2]; + char *endptr; + char *str = NULL; + int64_t *min = NULL; + int64_t *max = NULL; + + assert(ranges != NULL); + if ((ret = _read_astrings_values(t->prt_pg, prop, &vals)) == NULL) + goto error; + if (vals.value_count == 0) { + /* range values are empty */ + (void) scf_set_error(SCF_ERROR_NOT_FOUND); + goto cleanup; + } + + min = malloc(vals.value_count * sizeof (int64_t)); + max = malloc(vals.value_count * sizeof (int64_t)); + if (min == NULL || max == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + while (n < vals.value_count) { + /* min and max should be separated by a "," */ + if ((str = _separate_by_separator(ret[n], ",", one_range, 2)) + == NULL) + goto cleanup; + errno = 0; + min[n] = strtoll(one_range[0], &endptr, 10); + if (errno != 0 || endptr == one_range[0] || *endptr) { + (void) scf_set_error(SCF_ERROR_CONSTRAINT_VIOLATED); + goto cleanup; + } + errno = 0; + max[n] = strtoll(one_range[1], &endptr, 10); + if (errno != 0 || endptr == one_range[1] || *endptr) { + (void) scf_set_error(SCF_ERROR_CONSTRAINT_VIOLATED); + goto cleanup; + } + if (min[n] > max[n]) { + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + goto cleanup; + } + ++n; + free(str); + str = NULL; + } + ranges->sir_num_ranges = vals.value_count; + ranges->sir_min = min; + ranges->sir_max = max; + scf_values_destroy(&vals); + return (0); +cleanup: + free(str); + free(min); + free(max); + scf_values_destroy(&vals); +error: + if (ismember(scf_error(), errors_server)) { + return (-1); + } else switch (scf_error()) { + case SCF_ERROR_TYPE_MISMATCH: + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + /*FALLTHROUGH*/ + + case SCF_ERROR_CONSTRAINT_VIOLATED: + case SCF_ERROR_NOT_FOUND: + case SCF_ERROR_TEMPLATE_INVALID: + return (-1); + + case SCF_ERROR_INVALID_ARGUMENT: + case SCF_ERROR_NOT_SET: + default: + assert(0); + abort(); + } + /*NOTREACHED*/ +} + +/* + * Returns -1 on failure. Sets scf_error(): + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_CONSTRAINT_VIOLATED + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * Property doesn't exist or exists and has no value. + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +int +scf_tmpl_value_count_range_constraints(const scf_prop_tmpl_t *t, + scf_count_ranges_t *ranges) +{ + return (_scf_tmpl_get_count_ranges(t, SCF_PROPERTY_TM_CONSTRAINT_RANGE, + ranges)); +} + +int +scf_tmpl_value_int_range_constraints(const scf_prop_tmpl_t *t, + scf_int_ranges_t *ranges) +{ + return (_scf_tmpl_get_int_ranges(t, SCF_PROPERTY_TM_CONSTRAINT_RANGE, + ranges)); +} + +int +scf_tmpl_value_count_range_choices(const scf_prop_tmpl_t *t, + scf_count_ranges_t *ranges) +{ + return (_scf_tmpl_get_count_ranges(t, SCF_PROPERTY_TM_CHOICES_RANGE, + ranges)); +} + +int +scf_tmpl_value_int_range_choices(const scf_prop_tmpl_t *t, + scf_int_ranges_t *ranges) +{ + return (_scf_tmpl_get_int_ranges(t, SCF_PROPERTY_TM_CHOICES_RANGE, + ranges)); +} + +/* + * Returns -1 on failure. Sets scf_error(): + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * Property doesn't exist or exists and has no value. + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +int +scf_tmpl_value_name_choices(const scf_prop_tmpl_t *t, scf_values_t *vals) +{ + int c_flag = 0; /* have not read any value yet */ + int r; + char **ret; + + /* First, look for explicitly declared choices. */ + if ((ret = _read_astrings_values(t->prt_pg, + SCF_PROPERTY_TM_CHOICES_NAME, vals)) != NULL) { + c_flag = 1; + } else if (scf_error() != SCF_ERROR_NOT_FOUND) { + goto error; + } + + /* Next, check for choices included by 'values'. */ + if ((r = _check_choices_include_values(t->prt_pg, "values")) == 0) { + /* read values_name */ + if (c_flag == 1) + /* append values */ + ret = _append_astrings_values(t->prt_pg, + SCF_PROPERTY_TM_VALUES_NAME, vals); + else + /* read values */ + ret = _read_astrings_values(t->prt_pg, + SCF_PROPERTY_TM_VALUES_NAME, vals); + if (ret != NULL) { + c_flag = 1; + } else if (scf_error() != SCF_ERROR_NOT_FOUND) { + goto error; + } + } else if (r == -1) { + goto error; + } + + /* Finally check for choices included by 'constraints'. */ + if ((r = _check_choices_include_values(t->prt_pg, "constraints")) == + 0) { + /* read constraint_name */ + if (c_flag == 1) + /* append values */ + ret = _append_astrings_values(t->prt_pg, + SCF_PROPERTY_TM_CONSTRAINT_NAME, vals); + else + /* read values */ + ret = _read_astrings_values(t->prt_pg, + SCF_PROPERTY_TM_CONSTRAINT_NAME, vals); + if (ret != NULL) { + c_flag = 1; + } else if (scf_error() != SCF_ERROR_NOT_FOUND) { + goto error; + } + } else if (r == -1) { + goto error; + } + + if (c_flag == 0 || vals->value_count == 0) { + (void) scf_set_error(SCF_ERROR_NOT_FOUND); + return (-1); + } + + return (0); + +error: + if (ismember(scf_error(), errors_server)) { + return (-1); + } else switch (scf_error()) { + case SCF_ERROR_TYPE_MISMATCH: + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + return (-1); + + case SCF_ERROR_NOT_SET: + case SCF_ERROR_INVALID_ARGUMENT: + default: + assert(0); + abort(); + } + /*NOTREACHED*/ +} + +void +scf_values_destroy(scf_values_t *vals) +{ + int i; + char **items = NULL; + char **str = vals->values_as_strings; + + if (vals == NULL) + return; + + /* free values */ + switch (vals->value_type) { + case SCF_TYPE_BOOLEAN: + free(vals->values.v_boolean); + break; + case SCF_TYPE_COUNT: + free(vals->values.v_count); + break; + case SCF_TYPE_INTEGER: + free(vals->values.v_integer); + break; + case SCF_TYPE_ASTRING: + items = vals->values.v_astring; + str = NULL; + break; + case SCF_TYPE_USTRING: + items = vals->values.v_ustring; + str = NULL; + break; + case SCF_TYPE_OPAQUE: + items = vals->values.v_opaque; + str = NULL; + break; + case SCF_TYPE_TIME: + free(vals->values.v_time); + break; + default: + assert(0); + abort(); + } + for (i = 0; i < vals->value_count; ++i) { + if (items != NULL) + free(items[i]); + if (str != NULL) + free(str[i]); + } + vals->value_count = 0; + free(items); + free(str); +} + +/* + * char *_make_value_name() + * + * Construct the prefix for a value common name or value description property. + * It takes the form: + * value_<BASE32 name>_<common_name|description>_ + * This is then combined with a localized suffix by the caller to look + * up the property in the repository: + * value_<BASE32 name>_<common_name|description>_<lang> + * + * Returns NULL on failure. Sets scf_error(): + * SCF_ERROR_INVALID_ARGUMENT + * Name isn't short enough make a value name with. + * SCF_ERROR_NO_MEMORY + */ +static char * +_make_value_name(char *desc_name, const char *value) +{ + char *name = NULL; + char *encoded = NULL; + ssize_t sz = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH) + 1; + + name = malloc(sz); + encoded = malloc(sz); + if (name == NULL || encoded == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + free(name); + free(encoded); + return (NULL); + } + + if (scf_encode32(value, strlen(value), encoded, sz, NULL, + SCF_ENCODE32_PAD) != 0) { + /* Shouldn't happen. */ + assert(0); + } + + (void) strlcpy(name, SCF_PROPERTY_TM_VALUE_PREFIX, sz); + + if (strlcat(name, encoded, sz) >= sz) { + (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + free(name); + free(encoded); + return (NULL); + } + + if (strlcat(name, "_", sz) >= sz) { + (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + free(name); + free(encoded); + return (NULL); + } + + if (strlcat(name, desc_name, sz) >= sz) { + (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + free(name); + free(encoded); + return (NULL); + } + + if (strlcat(name, "_", sz) >= sz) { + (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + free(name); + free(encoded); + return (NULL); + } + + free(encoded); + return (name); +} + +/* + * ssize_t scf_tmpl_value_common_name() + * + * Populates "out" with an allocated string containing the value's + * common name. Returns the size of the string on successful return. + * out must be freed with free() on successful return. + * + * Returns -1 on failure, sets scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * Property group was deleted. + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * name not a valid property name + * name and locale are too long to make a property name + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * Property doesn't exist or exists and has no value. + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + * property is not SCF_TYPE_ASTRING has more than one value. + */ +ssize_t +scf_tmpl_value_common_name(const scf_prop_tmpl_t *t, const char *locale, + const char *value, char **out) +{ + char *value_name = NULL; + + value_name = _make_value_name("common_name", value); + if (value_name == NULL) + return (-1); + + *out = _read_localized_astring_from_pg(t->prt_pg, value_name, locale); + + free(value_name); + + if (*out == NULL) + return (-1); + + return (strlen(*out)); +} + +/* + * ssize_t scf_tmpl_value_description() + * + * Populates "out" with an allocated string containing the value's + * description. Returns the size of the string on successful return. + * out must be freed with free() on successful return. + * + * Returns -1 on failure, sets scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * Property group was deleted. + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * name not a valid property name + * name and locale are too long to make a property name + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * Property doesn't exist or exists and has no value. + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + * property is not SCF_TYPE_ASTRING has more than one value. + */ +ssize_t +scf_tmpl_value_description(const scf_prop_tmpl_t *t, const char *locale, + const char *value, char **out) +{ + char *value_name = NULL; + + value_name = _make_value_name("description", value); + if (value_name == NULL) + return (-1); + + + *out = _read_localized_astring_from_pg(t->prt_pg, value_name, locale); + + free(value_name); + + if (*out == NULL) + return (-1); + + return (strlen(*out)); +} + +/* + * Templates error messages format, in human readable form. + * Each line is one error item: + * + * prefix error message + * FMRI="err->te_errs->tes_fmri" + * Property group="err->te_pg_name" + * Property name="err->te_prop_name" + * expected value 1="err->te_ev1" + * expected value 2="err->te_ev2" + * actual value="err->te_actual" + * Tempalte source="err->te_tmpl_fmri" + * pg_pattern name="err->tmpl_pg_name" + * pg_pattern type="err->tmpl_pg_type" + * prop_pattern name="err->tmpl_prop_name" + * prop_pattern type="err->tmpl_prop_type" + * + * To add a new error type, include scf_tmpl_error_type_t in libscf.h + * add one entry in em_desc[], and update the functions pointed by the + * _tmpl_error_access array with the new error code. Also, update the + * scf_tmpl_error_* functions to provide access to desired + * scf_tmpl_error_t fields. + * + * To add a new error item, add a new field to scf_tmpl_error_t, a new field + * in _scf_tmpl_error_desc or a new non-error-dependent string, add a new entry + * in _tmpl_error_access array and create the appropriate get_val, get_desc + * functions. + * + * Changes to both the validation logic and the error types and items must + * be coordinated with the code in svccfg to ensure both libscf and svccfg's + * manifest validation validate the same things. + */ + +/* + * Container for all template errors on a validated object. + */ +struct scf_tmpl_errors { + int tes_index; + int tes_num_errs; + scf_tmpl_error_t **tes_errs; + int tes_errs_size; + const char *tes_fmri; + const char *tes_prefix; + int tes_flag; /* if set, scf_tmpl_error_destroy */ + /* will free strings in tes_errs */ +}; + +/* + * Templates error-dependent labels + */ +struct _scf_tmpl_error_desc { + const char *em_msg; + const char *em_ev1; + const char *em_ev2; + const char *em_actual; +}; + +/* + * This array MUST be kept in synch with the template error definition of + * scf_tmpl_error_type_t in libscf.h + */ +static struct _scf_tmpl_error_desc em_desc[] = { + /* SCF_TERR_MISSING_PG */ + { "Required property group missing", "Name of missing property group", + "Type of missing property group", NULL }, + /* SCF_TERR_WRONG_PG_TYPE */ + { "Property group has bad type", "Specified type", NULL, + "Actual type" }, + /* SCF_TERR_MISSING_PROP */ + { "Required property missing", "Name of missing property", NULL, NULL }, + /* SCF_TERR_WRONG_PROP_TYPE */ + { "Property has bad type", "Specified property type", NULL, + "Actual property type" }, + /* SCF_TERR_CARDINALITY_VIOLATION */ + { "Number of property values violates cardinality restriction", + "Cardinality minimum", "Cardinality maximum", + "Actual number of values" }, + /* SCF_TERR_VALUE_CONSTRAINT_VIOLATED */ + { "Property has illegal value", NULL, NULL, "Illegal value" }, + /* SCF_TERR_RANGE_VIOLATION */ + { "Property value is out of range", NULL, NULL, "Actual value" }, + /* SCF_TERR_PG_REDEFINE */ + { "Instance redefines pg_pattern", "Instance pg_pattern name", + "Instance pg_pattern type", NULL }, + /* SCF_TERR_PROP_TYPE_MISMATCH */ + { "Property type and value type mismatch", NULL, NULL, "Value type" }, + /* SCF_TERR_VALUE_OUT_OF_RANGE */ + { "Value is out of range", NULL, NULL, "Value" }, + /* SCF_TERR_INVALID_VALUE */ + { "Value is not valid", NULL, NULL, "Value" }, + /* SCF_TERR_PG_PATTERN_CONFLICT */ + { "Conflicting pg_pattern specifications", "Template source", + "pg_pattern name", "pg_pattern type" }, + /* SCF_TERR_PROP_PATTERN_CONFLICT */ + { "Conflicting prop_pattern specifications", "Template source", + "prop_pattern name", "prop_pattern type" }, + /* SCF_TERR_GENERAL_REDEFINE */ + { "Service or instance pg_pattern redefines a global or restarter " + "pg_pattern", "Template source", "pg_pattern name", + "pg_pattern type" }, + /* SCF_TERR_INCLUDE_VALUES */ + { "Missing constraints or values for include_values element", + "include_values type", NULL, NULL }, + /* SCF_TERR_PG_PATTERN_INCOMPLETE */ + { "Required pg_pattern is missing a name or type attribute", + NULL, NULL, NULL }, + /* SCF_TERR_PROP_PATTERN_INCOMPLETE */ + { "Required prop_pattern is missing a type attribute", + NULL, NULL, NULL } +}; + +/* + * Templates non error-dependent labels + */ +static const char *em_fmri = "FMRI"; +static const char *em_pg_name = "Property group"; +static const char *em_prop_name = "Property name"; +static const char *em_tmpl_fmri = "Template source"; +static const char *em_tmpl_pg_name = "pg_pattern name"; +static const char *em_tmpl_pg_type = "pg_pattern type"; +static const char *em_tmpl_prop_name = "prop_pattern name"; +static const char *em_tmpl_prop_type = "prop_pattern type"; + +static const char * +_get_fmri_desc(scf_tmpl_error_t *err) +{ + switch (err->te_type) { + case SCF_TERR_MISSING_PG: + case SCF_TERR_WRONG_PG_TYPE: + case SCF_TERR_MISSING_PROP: + case SCF_TERR_WRONG_PROP_TYPE: + case SCF_TERR_CARDINALITY_VIOLATION: + case SCF_TERR_VALUE_CONSTRAINT_VIOLATED: + case SCF_TERR_RANGE_VIOLATION: + case SCF_TERR_PG_REDEFINE: + case SCF_TERR_PG_PATTERN_INCOMPLETE: + case SCF_TERR_PROP_PATTERN_INCOMPLETE: + case SCF_TERR_INCLUDE_VALUES: + return (dgettext(TEXT_DOMAIN, em_fmri)); + case SCF_TERR_PROP_TYPE_MISMATCH: + case SCF_TERR_VALUE_OUT_OF_RANGE: + case SCF_TERR_INVALID_VALUE: + case SCF_TERR_PG_PATTERN_CONFLICT: + case SCF_TERR_PROP_PATTERN_CONFLICT: + case SCF_TERR_GENERAL_REDEFINE: + default: + return (NULL); + } +} + +static const char * +_get_pg_name_desc(scf_tmpl_error_t *err) +{ + switch (err->te_type) { + case SCF_TERR_WRONG_PG_TYPE: + case SCF_TERR_MISSING_PROP: + case SCF_TERR_WRONG_PROP_TYPE: + case SCF_TERR_CARDINALITY_VIOLATION: + case SCF_TERR_VALUE_CONSTRAINT_VIOLATED: + case SCF_TERR_RANGE_VIOLATION: + return (dgettext(TEXT_DOMAIN, em_pg_name)); + case SCF_TERR_MISSING_PG: + case SCF_TERR_PG_REDEFINE: + case SCF_TERR_PROP_TYPE_MISMATCH: + case SCF_TERR_VALUE_OUT_OF_RANGE: + case SCF_TERR_INVALID_VALUE: + case SCF_TERR_PG_PATTERN_CONFLICT: + case SCF_TERR_PROP_PATTERN_CONFLICT: + case SCF_TERR_GENERAL_REDEFINE: + case SCF_TERR_INCLUDE_VALUES: + case SCF_TERR_PG_PATTERN_INCOMPLETE: + case SCF_TERR_PROP_PATTERN_INCOMPLETE: + default: + return (NULL); + } +} + +static const char * +_get_prop_name_desc(scf_tmpl_error_t *err) +{ + switch (err->te_type) { + case SCF_TERR_WRONG_PROP_TYPE: + case SCF_TERR_CARDINALITY_VIOLATION: + case SCF_TERR_VALUE_CONSTRAINT_VIOLATED: + case SCF_TERR_RANGE_VIOLATION: + return (dgettext(TEXT_DOMAIN, em_prop_name)); + case SCF_TERR_MISSING_PG: + case SCF_TERR_WRONG_PG_TYPE: + case SCF_TERR_MISSING_PROP: + case SCF_TERR_PG_REDEFINE: + case SCF_TERR_PROP_TYPE_MISMATCH: + case SCF_TERR_VALUE_OUT_OF_RANGE: + case SCF_TERR_INVALID_VALUE: + case SCF_TERR_PG_PATTERN_CONFLICT: + case SCF_TERR_PROP_PATTERN_CONFLICT: + case SCF_TERR_GENERAL_REDEFINE: + case SCF_TERR_INCLUDE_VALUES: + case SCF_TERR_PG_PATTERN_INCOMPLETE: + case SCF_TERR_PROP_PATTERN_INCOMPLETE: + default: + return (NULL); + } +} + +static const char * +_get_ev1_desc(scf_tmpl_error_t *err) +{ + switch (err->te_type) { + case SCF_TERR_MISSING_PG: + case SCF_TERR_WRONG_PG_TYPE: + case SCF_TERR_MISSING_PROP: + case SCF_TERR_WRONG_PROP_TYPE: + case SCF_TERR_CARDINALITY_VIOLATION: + case SCF_TERR_RANGE_VIOLATION: + case SCF_TERR_PG_REDEFINE: + case SCF_TERR_PG_PATTERN_CONFLICT: + case SCF_TERR_PROP_PATTERN_CONFLICT: + case SCF_TERR_GENERAL_REDEFINE: + case SCF_TERR_INCLUDE_VALUES: + return (dgettext(TEXT_DOMAIN, em_desc[err->te_type].em_ev1)); + case SCF_TERR_VALUE_CONSTRAINT_VIOLATED: + case SCF_TERR_PROP_TYPE_MISMATCH: + case SCF_TERR_VALUE_OUT_OF_RANGE: + case SCF_TERR_INVALID_VALUE: + case SCF_TERR_PG_PATTERN_INCOMPLETE: + case SCF_TERR_PROP_PATTERN_INCOMPLETE: + default: + return (NULL); + } +} + +static const char * +_get_ev2_desc(scf_tmpl_error_t *err) +{ + switch (err->te_type) { + case SCF_TERR_MISSING_PG: + case SCF_TERR_CARDINALITY_VIOLATION: + case SCF_TERR_RANGE_VIOLATION: + case SCF_TERR_PG_REDEFINE: + case SCF_TERR_PG_PATTERN_CONFLICT: + case SCF_TERR_PROP_PATTERN_CONFLICT: + case SCF_TERR_GENERAL_REDEFINE: + return (dgettext(TEXT_DOMAIN, em_desc[err->te_type].em_ev2)); + case SCF_TERR_WRONG_PG_TYPE: + case SCF_TERR_MISSING_PROP: + case SCF_TERR_WRONG_PROP_TYPE: + case SCF_TERR_VALUE_CONSTRAINT_VIOLATED: + case SCF_TERR_PROP_TYPE_MISMATCH: + case SCF_TERR_VALUE_OUT_OF_RANGE: + case SCF_TERR_INVALID_VALUE: + case SCF_TERR_INCLUDE_VALUES: + case SCF_TERR_PG_PATTERN_INCOMPLETE: + case SCF_TERR_PROP_PATTERN_INCOMPLETE: + default: + return (NULL); + } +} + +static const char * +_get_actual_desc(scf_tmpl_error_t *err) +{ + switch (err->te_type) { + case SCF_TERR_MISSING_PG: + case SCF_TERR_WRONG_PG_TYPE: + case SCF_TERR_WRONG_PROP_TYPE: + case SCF_TERR_CARDINALITY_VIOLATION: + case SCF_TERR_VALUE_CONSTRAINT_VIOLATED: + case SCF_TERR_RANGE_VIOLATION: + case SCF_TERR_PROP_TYPE_MISMATCH: + case SCF_TERR_VALUE_OUT_OF_RANGE: + case SCF_TERR_INVALID_VALUE: + case SCF_TERR_PG_PATTERN_CONFLICT: + case SCF_TERR_PROP_PATTERN_CONFLICT: + case SCF_TERR_GENERAL_REDEFINE: + case SCF_TERR_INCLUDE_VALUES: + return (dgettext(TEXT_DOMAIN, + em_desc[err->te_type].em_actual)); + case SCF_TERR_MISSING_PROP: + case SCF_TERR_PG_REDEFINE: + case SCF_TERR_PG_PATTERN_INCOMPLETE: + case SCF_TERR_PROP_PATTERN_INCOMPLETE: + default: + return (NULL); + } +} + +static const char * +_get_tmpl_fmri_desc(scf_tmpl_error_t *err) +{ + switch (err->te_type) { + case SCF_TERR_MISSING_PG: + case SCF_TERR_WRONG_PG_TYPE: + case SCF_TERR_MISSING_PROP: + case SCF_TERR_WRONG_PROP_TYPE: + case SCF_TERR_CARDINALITY_VIOLATION: + case SCF_TERR_VALUE_CONSTRAINT_VIOLATED: + case SCF_TERR_RANGE_VIOLATION: + case SCF_TERR_PG_REDEFINE: + case SCF_TERR_PROP_TYPE_MISMATCH: + case SCF_TERR_VALUE_OUT_OF_RANGE: + case SCF_TERR_INVALID_VALUE: + case SCF_TERR_PG_PATTERN_CONFLICT: + case SCF_TERR_PROP_PATTERN_CONFLICT: + case SCF_TERR_GENERAL_REDEFINE: + case SCF_TERR_INCLUDE_VALUES: + case SCF_TERR_PG_PATTERN_INCOMPLETE: + case SCF_TERR_PROP_PATTERN_INCOMPLETE: + return (dgettext(TEXT_DOMAIN, em_tmpl_fmri)); + default: + return (NULL); + } +} + +static const char * +_get_tmpl_pg_name_desc(scf_tmpl_error_t *err) +{ + switch (err->te_type) { + case SCF_TERR_MISSING_PG: + case SCF_TERR_WRONG_PG_TYPE: + case SCF_TERR_MISSING_PROP: + case SCF_TERR_WRONG_PROP_TYPE: + case SCF_TERR_CARDINALITY_VIOLATION: + case SCF_TERR_VALUE_CONSTRAINT_VIOLATED: + case SCF_TERR_RANGE_VIOLATION: + case SCF_TERR_PG_REDEFINE: + case SCF_TERR_PROP_TYPE_MISMATCH: + case SCF_TERR_VALUE_OUT_OF_RANGE: + case SCF_TERR_INVALID_VALUE: + case SCF_TERR_PG_PATTERN_CONFLICT: + case SCF_TERR_PROP_PATTERN_CONFLICT: + case SCF_TERR_GENERAL_REDEFINE: + case SCF_TERR_INCLUDE_VALUES: + case SCF_TERR_PG_PATTERN_INCOMPLETE: + case SCF_TERR_PROP_PATTERN_INCOMPLETE: + return (dgettext(TEXT_DOMAIN, em_tmpl_pg_name)); + default: + return (NULL); + } +} + +static const char * +_get_tmpl_pg_type_desc(scf_tmpl_error_t *err) +{ + switch (err->te_type) { + case SCF_TERR_MISSING_PG: + case SCF_TERR_WRONG_PG_TYPE: + case SCF_TERR_MISSING_PROP: + case SCF_TERR_WRONG_PROP_TYPE: + case SCF_TERR_CARDINALITY_VIOLATION: + case SCF_TERR_VALUE_CONSTRAINT_VIOLATED: + case SCF_TERR_RANGE_VIOLATION: + case SCF_TERR_PG_REDEFINE: + case SCF_TERR_PROP_TYPE_MISMATCH: + case SCF_TERR_VALUE_OUT_OF_RANGE: + case SCF_TERR_INVALID_VALUE: + case SCF_TERR_PG_PATTERN_CONFLICT: + case SCF_TERR_PROP_PATTERN_CONFLICT: + case SCF_TERR_GENERAL_REDEFINE: + case SCF_TERR_INCLUDE_VALUES: + case SCF_TERR_PROP_PATTERN_INCOMPLETE: + case SCF_TERR_PG_PATTERN_INCOMPLETE: + return (dgettext(TEXT_DOMAIN, em_tmpl_pg_type)); + default: + return (NULL); + } +} + +static const char * +_get_tmpl_prop_name_desc(scf_tmpl_error_t *err) +{ + switch (err->te_type) { + case SCF_TERR_MISSING_PROP: + case SCF_TERR_WRONG_PROP_TYPE: + case SCF_TERR_CARDINALITY_VIOLATION: + case SCF_TERR_VALUE_CONSTRAINT_VIOLATED: + case SCF_TERR_RANGE_VIOLATION: + case SCF_TERR_PROP_TYPE_MISMATCH: + case SCF_TERR_VALUE_OUT_OF_RANGE: + case SCF_TERR_INVALID_VALUE: + case SCF_TERR_PROP_PATTERN_CONFLICT: + case SCF_TERR_INCLUDE_VALUES: + case SCF_TERR_PROP_PATTERN_INCOMPLETE: + return (dgettext(TEXT_DOMAIN, em_tmpl_prop_name)); + case SCF_TERR_MISSING_PG: + case SCF_TERR_WRONG_PG_TYPE: + case SCF_TERR_PG_REDEFINE: + case SCF_TERR_PG_PATTERN_CONFLICT: + case SCF_TERR_GENERAL_REDEFINE: + case SCF_TERR_PG_PATTERN_INCOMPLETE: + default: + return (NULL); + } +} + +static const char * +_get_tmpl_prop_type_desc(scf_tmpl_error_t *err) +{ + switch (err->te_type) { + case SCF_TERR_MISSING_PROP: + case SCF_TERR_WRONG_PROP_TYPE: + case SCF_TERR_CARDINALITY_VIOLATION: + case SCF_TERR_VALUE_CONSTRAINT_VIOLATED: + case SCF_TERR_RANGE_VIOLATION: + case SCF_TERR_PROP_TYPE_MISMATCH: + case SCF_TERR_VALUE_OUT_OF_RANGE: + case SCF_TERR_INVALID_VALUE: + case SCF_TERR_PROP_PATTERN_CONFLICT: + case SCF_TERR_INCLUDE_VALUES: + return (dgettext(TEXT_DOMAIN, em_tmpl_prop_type)); + case SCF_TERR_MISSING_PG: + case SCF_TERR_WRONG_PG_TYPE: + case SCF_TERR_PG_REDEFINE: + case SCF_TERR_PG_PATTERN_CONFLICT: + case SCF_TERR_GENERAL_REDEFINE: + case SCF_TERR_PG_PATTERN_INCOMPLETE: + case SCF_TERR_PROP_PATTERN_INCOMPLETE: + default: + return (NULL); + } +} + +static const char * +_get_fmri_val(scf_tmpl_error_t *err) +{ + assert(err != NULL && err->te_errs != NULL && + err->te_errs->tes_fmri != NULL); + return (err->te_errs->tes_fmri); +} + +static const char * +_get_pg_name_val(scf_tmpl_error_t *err) +{ + assert(err != NULL); + return (err->te_pg_name); +} + +static const char * +_get_prop_name_val(scf_tmpl_error_t *err) +{ + assert(err != NULL); + return (err->te_prop_name); +} + +static const char * +_get_ev1_val(scf_tmpl_error_t *err) +{ + assert(err != NULL); + return (err->te_ev1); +} + +static const char * +_get_ev2_val(scf_tmpl_error_t *err) +{ + assert(err != NULL); + return (err->te_ev2); +} + +static const char * +_get_actual_val(scf_tmpl_error_t *err) +{ + assert(err != NULL); + return (err->te_actual); +} + +static const char * +_get_tmpl_fmri_val(scf_tmpl_error_t *err) +{ + assert(err != NULL); + return (err->te_tmpl_fmri); +} + +static const char * +_get_tmpl_pg_name_val(scf_tmpl_error_t *err) +{ + assert(err != NULL); + return (err->te_tmpl_pg_name); +} + +static const char * +_get_tmpl_pg_type_val(scf_tmpl_error_t *err) +{ + assert(err != NULL); + return (err->te_tmpl_pg_type); +} + +static const char * +_get_tmpl_prop_name_val(scf_tmpl_error_t *err) +{ + assert(err != NULL); + return (err->te_tmpl_prop_name); +} + +static const char * +_get_tmpl_prop_type_val(scf_tmpl_error_t *err) +{ + assert(err != NULL); + return (err->te_tmpl_prop_type); +} + +/* + * Templates error item retrival functions + */ +typedef const char *(*get_em)(scf_tmpl_error_t *); + +/* + * if new items (lines) are added to the templates error messages, + * new entries in this array (and new fuctions) will be required. + */ +static struct _tmpl_error_access { + get_em get_desc; + get_em get_val; +} _tmpl_error_items[] = { + { (get_em)_get_fmri_desc, (get_em)_get_fmri_val }, + { (get_em)_get_pg_name_desc, (get_em)_get_pg_name_val }, + { (get_em)_get_prop_name_desc, (get_em)_get_prop_name_val }, + { (get_em)_get_ev1_desc, (get_em)_get_ev1_val }, + { (get_em)_get_ev2_desc, (get_em)_get_ev2_val }, + { (get_em)_get_actual_desc, (get_em)_get_actual_val }, + { (get_em)_get_tmpl_fmri_desc, (get_em)_get_tmpl_fmri_val }, + { (get_em)_get_tmpl_pg_name_desc, (get_em)_get_tmpl_pg_name_val }, + { (get_em)_get_tmpl_pg_type_desc, (get_em)_get_tmpl_pg_type_val }, + { (get_em)_get_tmpl_prop_name_desc, (get_em)_get_tmpl_prop_name_val }, + { (get_em)_get_tmpl_prop_type_desc, (get_em)_get_tmpl_prop_type_val }, + { NULL } +}; + +/* + * Allocate a new scf_tmpl_error_t and add it to the errs list provided. + * Returns NULL on failure. Sets scf_error(): + * SCF_ERROR_NO_MEMORY + */ +static scf_tmpl_error_t * +_create_error(scf_tmpl_errors_t *errs) +{ + scf_tmpl_error_t *ret; + scf_tmpl_error_t **saved_errs; + + assert(errs != NULL); + ret = calloc(1, sizeof (scf_tmpl_error_t)); + if (ret == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + return (NULL); + } + + ret->te_errs = errs; + + assert(errs->tes_num_errs <= errs->tes_errs_size); + if (errs->tes_num_errs == errs->tes_errs_size) { + /* Time to grow the pointer array. */ + saved_errs = errs->tes_errs; + errs->tes_errs = calloc(2 * errs->tes_errs_size, + sizeof (scf_tmpl_error_t *)); + if (errs->tes_errs == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + errs->tes_errs = saved_errs; + free(ret); + return (NULL); + } + (void) memcpy(errs->tes_errs, saved_errs, errs->tes_errs_size * + sizeof (scf_tmpl_error_t *)); + errs->tes_errs_size = 2 * errs->tes_errs_size; + free(saved_errs); + } + + errs->tes_errs[errs->tes_num_errs] = ret; + errs->tes_num_errs++; + + return (ret); +} + +/* + * + * If destroy_strings is set, scf_tmpl_errors_destroy will free the + * strings in scf_tmpl_error_t entries. + * + * Returns NULL on failure. Sets scf_error(): + * SCF_ERROR_NO_MEMORY + */ +scf_tmpl_errors_t * +_scf_create_errors(const char *fmri, int destroy_strings) +{ + scf_tmpl_errors_t *ret; + int errs_size = 20; + + assert(fmri != NULL); + + ret = calloc(1, sizeof (scf_tmpl_errors_t)); + if (ret == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + return (NULL); + } + + ret->tes_index = 0; + ret->tes_num_errs = 0; + if ((ret->tes_fmri = strdup(fmri)) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + free(ret); + return (NULL); + } + + ret->tes_prefix = strdup(""); + if (ret->tes_prefix == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + free((char *)ret->tes_fmri); + free(ret); + return (NULL); + } + ret->tes_flag = destroy_strings; + + /* Make space for a few errors. */ + ret->tes_errs = calloc(errs_size, sizeof (scf_tmpl_error_t *)); + if (ret->tes_errs == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + free((char *)ret->tes_fmri); + free((char *)ret->tes_prefix); + free(ret); + return (NULL); + } + ret->tes_errs_size = errs_size; + + return (ret); +} + +/* + * return 0 on success, if fails set scf_error() to: + * + * SCF_ERROR_NO_MEMORY + */ +int +_scf_tmpl_error_set_prefix(scf_tmpl_errors_t *errs, const char *prefix) +{ + free((void *) errs->tes_prefix); + if (prefix == NULL) + errs->tes_prefix = strdup(""); + else + errs->tes_prefix = strdup(prefix); + if (errs->tes_prefix == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + return (-1); + } + return (0); +} + +/* + * + * Returns -1 on failure. Sets scf_error(): + * SCF_ERROR_NO_MEMORY + */ +int +_scf_tmpl_add_error(scf_tmpl_errors_t *errs, scf_tmpl_error_type_t type, + const char *pg_name, const char *prop_name, + const char *ev1, const char *ev2, const char *actual, + const char *tmpl_fmri, const char *tmpl_pg_name, const char *tmpl_pg_type, + const char *tmpl_prop_name, const char *tmpl_prop_type) +{ + scf_tmpl_error_t *err; + + assert(errs != NULL); + assert(tmpl_fmri != NULL); + + err = _create_error(errs); + if (err == NULL) + return (-1); + + err->te_type = type; + err->te_pg_name = pg_name; + err->te_prop_name = prop_name; + err->te_ev1 = ev1; + err->te_ev2 = ev2; + err->te_actual = actual; + err->te_tmpl_fmri = tmpl_fmri; + err->te_tmpl_pg_name = tmpl_pg_name; + err->te_tmpl_pg_type = tmpl_pg_type; + err->te_tmpl_prop_name = tmpl_prop_name; + err->te_tmpl_prop_type = tmpl_prop_type; + + return (0); +} + +/* + * returns an allocated string that must be freed with free() + * string contains converted 64-bit integer value + * flag set for signed values + * if fails return NULL and set scf_error() to: + * SCF_ERROR_NO_MEMORY + */ +static char * +_val_to_string(uint64_t val, int flag) +{ + ssize_t sz = scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH) + 1; + char *buf; + + buf = malloc(sz); + if (buf == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + return (NULL); + } + + if (flag == 0) + (void) snprintf(buf, sz, "%" PRIu64, val); + else + (void) snprintf(buf, sz, "%" PRIi64, (int64_t)val); + + return (buf); +} + +/* + * return 0 on success, -1 on failure. + * set scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +static int +_add_tmpl_missing_pg_error(scf_tmpl_errors_t *errs, scf_pg_tmpl_t *t) +{ + char *ev1 = NULL; + char *ev2 = NULL; + char *t_fmri = NULL; + char *t_pg_name = NULL; + char *t_pg_type = NULL; + + if ((t_fmri = _scf_tmpl_get_fmri(t)) == NULL) + return (-1); + if (scf_tmpl_pg_name(t, &t_pg_name) == -1) { + goto cleanup; + } + if (scf_tmpl_pg_type(t, &t_pg_type) == -1) { + goto cleanup; + } + if ((ev1 = strdup(t_pg_name)) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + if ((ev2 = strdup(t_pg_type)) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + + return (_scf_tmpl_add_error(errs, SCF_TERR_MISSING_PG, NULL, NULL, ev1, + ev2, NULL, t_fmri, t_pg_name, t_pg_type, NULL, NULL)); +cleanup: + free(ev1); + free(ev2); + free(t_fmri); + free(t_pg_name); + free(t_pg_type); + return (-1); +} + +/* + * return 0 on success, -1 on failure. + * set scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +static int +_add_tmpl_wrong_pg_type_error(scf_tmpl_errors_t *errs, scf_pg_tmpl_t *t, + scf_propertygroup_t *pg) +{ + char *pg_name = NULL; + char *ev1 = NULL; + char *actual = NULL; + char *t_fmri = NULL; + char *t_pg_name = NULL; + char *t_pg_type = NULL; + + if ((t_fmri = _scf_tmpl_get_fmri(t)) == NULL) + return (-1); + if ((pg_name = _scf_get_pg_name(pg)) == NULL) + goto cleanup; + if ((actual = _scf_get_pg_type(pg)) == NULL) + goto cleanup; + if (scf_tmpl_pg_name(t, &t_pg_name) == -1) { + goto cleanup; + } + if (scf_tmpl_pg_type(t, &t_pg_type) == -1) { + goto cleanup; + } + if ((ev1 = strdup(t_pg_type)) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + + return (_scf_tmpl_add_error(errs, SCF_TERR_WRONG_PG_TYPE, pg_name, NULL, + ev1, NULL, actual, t_fmri, t_pg_name, t_pg_type, NULL, NULL)); +cleanup: + free(pg_name); + free(ev1); + free(actual); + free(t_fmri); + free(t_pg_name); + free(t_pg_type); + return (-1); +} + +/* + * return 0 on success, -1 on failure. + * set scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +static int +_add_tmpl_missing_prop_error(scf_tmpl_errors_t *errs, scf_pg_tmpl_t *t, + scf_propertygroup_t *pg, const scf_prop_tmpl_t *pt) +{ + char *pg_name = NULL; + char *ev1 = NULL; + char *t_fmri = NULL; + char *t_pg_name = NULL; + char *t_pg_type = NULL; + char *t_prop_name = NULL; + char *t_prop_type = NULL; + + if ((t_fmri = _scf_tmpl_get_fmri(t)) == NULL) + return (-1); + if ((pg_name = _scf_get_pg_name(pg)) == NULL) + goto cleanup; + if (scf_tmpl_pg_name(t, &t_pg_name) == -1) { + goto cleanup; + } + if (scf_tmpl_pg_type(t, &t_pg_type) == -1) { + goto cleanup; + } + if (scf_tmpl_prop_name(pt, &t_prop_name) == -1) { + goto cleanup; + } + t_prop_type = _scf_read_tmpl_prop_type_as_string(pt); + if (t_prop_type != NULL && t_prop_type[0] == '\0') { + free(t_prop_type); + t_prop_type = NULL; + } else if (t_prop_type == NULL) { + goto cleanup; + } + if (t_prop_type == NULL) + if ((t_prop_type = strdup(SCF_TMPL_WILDCARD)) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + if ((ev1 = strdup(t_prop_name)) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + + return (_scf_tmpl_add_error(errs, SCF_TERR_MISSING_PROP, pg_name, NULL, + ev1, NULL, NULL, t_fmri, t_pg_name, t_pg_type, t_prop_name, + t_prop_type)); +cleanup: + free(pg_name); + free(ev1); + free(t_fmri); + free(t_pg_name); + free(t_pg_type); + free(t_prop_name); + free(t_prop_type); + return (-1); +} + +/* + * return 0 on success, -1 on failure. + * set scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +static int +_add_tmpl_wrong_prop_type_error(scf_tmpl_errors_t *errs, + scf_propertygroup_t *pg, const scf_prop_tmpl_t *pt, scf_property_t *prop) +{ + char *pg_name = NULL; + char *prop_name = NULL; + char *ev1 = NULL; + char *actual = NULL; + char *t_fmri = NULL; + char *t_pg_name = NULL; + char *t_pg_type = NULL; + char *t_prop_name = NULL; + char *t_prop_type = NULL; + + if ((t_fmri = _scf_tmpl_get_fmri(pt->prt_t)) == NULL) + return (-1); + if ((pg_name = _scf_get_pg_name(pg)) == NULL) + goto cleanup; + if ((prop_name = _scf_get_prop_name(prop)) == NULL) + goto cleanup; + if ((actual = _scf_get_prop_type(prop)) == NULL) + goto cleanup; + if (scf_tmpl_pg_name(pt->prt_t, &t_pg_name) == -1) { + goto cleanup; + } + if (scf_tmpl_pg_type(pt->prt_t, &t_pg_type) == -1) { + goto cleanup; + } + if (scf_tmpl_prop_name(pt, &t_prop_name) == -1) { + goto cleanup; + } + t_prop_type = _scf_read_tmpl_prop_type_as_string(pt); + if (t_prop_type != NULL && t_prop_type[0] == '\0') { + free(t_prop_type); + t_prop_type = NULL; + } else if (t_prop_type == NULL) { + goto cleanup; + } + if (t_prop_type == NULL) + if ((t_prop_type = strdup(SCF_TMPL_WILDCARD)) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + if ((ev1 = strdup(t_prop_type)) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + + return (_scf_tmpl_add_error(errs, SCF_TERR_WRONG_PROP_TYPE, pg_name, + prop_name, ev1, NULL, actual, t_fmri, t_pg_name, t_pg_type, + t_prop_name, t_prop_type)); +cleanup: + free(pg_name); + free(prop_name); + free(ev1); + free(actual); + free(t_fmri); + free(t_pg_name); + free(t_pg_type); + free(t_prop_name); + free(t_prop_type); + return (-1); +} + +/* + * return 0 on success, -1 on failure. + * set scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +static int +_add_tmpl_count_error(scf_tmpl_errors_t *errs, scf_tmpl_error_type_t type, + scf_propertygroup_t *pg, const scf_prop_tmpl_t *pt, scf_property_t *prop, + uint64_t count, uint64_t *min, uint64_t *max) +{ + char *pg_name = NULL; + char *prop_name = NULL; + char *s_min = NULL; + char *s_max = NULL; + char *num = NULL; + char *t_fmri = NULL; + char *t_pg_name = NULL; + char *t_pg_type = NULL; + char *t_prop_name = NULL; + char *t_prop_type = NULL; + + if ((t_fmri = _scf_tmpl_get_fmri(pt->prt_t)) == NULL) + return (-1); + switch (type) { + case SCF_TERR_RANGE_VIOLATION: + case SCF_TERR_CARDINALITY_VIOLATION: + if ((pg_name = _scf_get_pg_name(pg)) == NULL) + goto cleanup; + if ((prop_name = _scf_get_prop_name(prop)) == NULL) + goto cleanup; + break; + case SCF_TERR_VALUE_OUT_OF_RANGE: + /* keep pg_name = NULL and prop_name = NULL */ + break; + } + if (scf_tmpl_pg_name(pt->prt_t, &t_pg_name) == -1) { + goto cleanup; + } + if (scf_tmpl_pg_type(pt->prt_t, &t_pg_type) == -1) { + goto cleanup; + } + if (scf_tmpl_prop_name(pt, &t_prop_name) == -1) { + goto cleanup; + } + t_prop_type = _scf_read_tmpl_prop_type_as_string(pt); + if (t_prop_type != NULL && t_prop_type[0] == '\0') { + free(t_prop_type); + t_prop_type = NULL; + } else if (t_prop_type == NULL) { + goto cleanup; + } + if (t_prop_type == NULL) + if ((t_prop_type = strdup(SCF_TMPL_WILDCARD)) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + if (min == NULL) { + if ((s_min = strdup("")) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + } else { + if ((s_min = _val_to_string(*min, 0)) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + } + if (max == NULL) { + if ((s_max = strdup("")) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + } else { + if ((s_max = _val_to_string(*max, 0)) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + } + if ((num = _val_to_string(count, 0)) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + + return (_scf_tmpl_add_error(errs, type, pg_name, prop_name, s_min, + s_max, num, t_fmri, t_pg_name, t_pg_type, t_prop_name, + t_prop_type)); +cleanup: + free(pg_name); + free(prop_name); + free(s_min); + free(s_max); + free(num); + free(t_fmri); + free(t_pg_name); + free(t_pg_type); + free(t_prop_name); + free(t_prop_type); + return (-1); +} + +/* + * return 0 on success, -1 on failure. + * set scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +static int +_add_tmpl_constraint_error(scf_tmpl_errors_t *errs, scf_tmpl_error_type_t type, + scf_propertygroup_t *pg, const scf_prop_tmpl_t *pt, scf_property_t *prop, + scf_value_t *val) +{ + scf_type_t val_type; + char *pg_name = NULL; + char *prop_name = NULL; + char *value = NULL; + char *t_fmri = NULL; + char *t_pg_name = NULL; + char *t_pg_type = NULL; + char *t_prop_name = NULL; + char *t_prop_type = NULL; + + if ((t_fmri = _scf_tmpl_get_fmri(pt->prt_t)) == NULL) + return (-1); + switch (type) { + case SCF_TERR_VALUE_CONSTRAINT_VIOLATED: + if ((pg_name = _scf_get_pg_name(pg)) == NULL) + goto cleanup; + if ((prop_name = _scf_get_prop_name(prop)) == NULL) + goto cleanup; + /*FALLTHROUGH*/ + case SCF_TERR_INVALID_VALUE: + /* keep pg_name = NULL and prop_name = NULL */ + if ((value = _scf_value_get_as_string(val)) == NULL) + goto cleanup; + break; + case SCF_TERR_PROP_TYPE_MISMATCH: + /* keep pg_name = NULL and prop_name = NULL */ + /* use value for value type */ + val_type = scf_value_type(val); + if ((value = strdup(scf_type_to_string(val_type))) == + NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + break; + } + if (scf_tmpl_pg_name(pt->prt_t, &t_pg_name) == -1) { + goto cleanup; + } + if (scf_tmpl_pg_type(pt->prt_t, &t_pg_type) == -1) { + goto cleanup; + } + if (scf_tmpl_prop_name(pt, &t_prop_name) == -1) { + goto cleanup; + } + t_prop_type = _scf_read_tmpl_prop_type_as_string(pt); + if (t_prop_type != NULL && t_prop_type[0] == '\0') { + free(t_prop_type); + t_prop_type = NULL; + } else if (t_prop_type == NULL) { + goto cleanup; + } + if (t_prop_type == NULL) + if ((t_prop_type = strdup(SCF_TMPL_WILDCARD)) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + + return (_scf_tmpl_add_error(errs, type, pg_name, prop_name, NULL, NULL, + value, t_fmri, t_pg_name, t_pg_type, t_prop_name, t_prop_type)); +cleanup: + assert(scf_error() != SCF_ERROR_NOT_SET); + free(pg_name); + free(prop_name); + free(value); + free(t_fmri); + free(t_pg_name); + free(t_pg_type); + free(t_prop_name); + free(t_prop_type); + return (-1); +} + +/* + * return 0 on success, -1 on failure. + * set scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +static int +_add_tmpl_int_error(scf_tmpl_errors_t *errs, scf_tmpl_error_type_t type, + scf_propertygroup_t *pg, const scf_prop_tmpl_t *pt, scf_property_t *prop, + int64_t val, int64_t *min, int64_t *max) +{ + char *pg_name = NULL; + char *prop_name = NULL; + char *s_min = NULL; + char *s_max = NULL; + char *value = NULL; + char *t_fmri = NULL; + char *t_pg_name = NULL; + char *t_pg_type = NULL; + char *t_prop_name = NULL; + char *t_prop_type = NULL; + + if ((t_fmri = _scf_tmpl_get_fmri(pt->prt_t)) == NULL) + return (-1); + + switch (type) { + case SCF_TERR_RANGE_VIOLATION: + if ((pg_name = _scf_get_pg_name(pg)) == NULL) + goto cleanup; + if ((prop_name = _scf_get_prop_name(prop)) == NULL) + goto cleanup; + break; + case SCF_TERR_VALUE_OUT_OF_RANGE: + /* keep pg_name = NULL and prop_name = NULL */ + break; + } + if (scf_tmpl_pg_name(pt->prt_t, &t_pg_name) == -1) { + goto cleanup; + } + if (scf_tmpl_pg_type(pt->prt_t, &t_pg_type) == -1) { + goto cleanup; + } + if (scf_tmpl_prop_name(pt, &t_prop_name) == -1) { + goto cleanup; + } + t_prop_type = _scf_read_tmpl_prop_type_as_string(pt); + if (t_prop_type != NULL && t_prop_type[0] == '\0') { + free(t_prop_type); + t_prop_type = NULL; + } else if (t_prop_type == NULL) { + goto cleanup; + } + if (t_prop_type == NULL) + if ((t_prop_type = strdup(SCF_TMPL_WILDCARD)) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + if (min == NULL) { + if ((s_min = strdup("")) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + } else { + if ((s_min = _val_to_string(*((uint64_t *)min), 1)) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + } + if (max == NULL) { + if ((s_max = strdup("")) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + } else { + if ((s_max = _val_to_string(*((uint64_t *)max), 1)) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + } + if ((value = _val_to_string((uint64_t)val, 1)) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + + return (_scf_tmpl_add_error(errs, type, pg_name, prop_name, s_min, + s_max, value, t_fmri, t_pg_name, t_pg_type, t_prop_name, + t_prop_type)); +cleanup: + free(pg_name); + free(prop_name); + free(s_min); + free(s_max); + free(value); + free(t_fmri); + free(t_pg_name); + free(t_pg_type); + free(t_prop_name); + free(t_prop_type); + return (-1); +} + +/* + * return 0 on success, -1 on failure. + * set scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +static int +_add_tmpl_pg_redefine_error(scf_tmpl_errors_t *errs, scf_pg_tmpl_t *t, + scf_pg_tmpl_t *r) +{ + char *ev1 = NULL; + char *ev2 = NULL; + char *t_fmri = NULL; + char *t_pg_name = NULL; + char *t_pg_type = NULL; + + if ((t_fmri = _scf_tmpl_get_fmri(r)) == NULL) + return (-1); + if (scf_tmpl_pg_name(r, &t_pg_name) == -1) { + goto cleanup; + } + if (scf_tmpl_pg_type(r, &t_pg_type) == -1) { + goto cleanup; + } + if (scf_tmpl_pg_name(t, &ev1) == -1) { + goto cleanup; + } + if (scf_tmpl_pg_type(t, &ev2) == -1) { + goto cleanup; + } + + return (_scf_tmpl_add_error(errs, SCF_TERR_PG_REDEFINE, NULL, NULL, + ev1, ev2, NULL, t_fmri, t_pg_name, t_pg_type, NULL, NULL)); +cleanup: + free(ev1); + free(ev2); + free(t_fmri); + free(t_pg_name); + free(t_pg_type); + return (-1); +} + +/* + * return 0 if value is within count ranges constraint. + * return -1 otherwise + */ +static int +_check_count_ranges(scf_count_ranges_t *cr, uint64_t v) +{ + int i; + + for (i = 0; i < cr->scr_num_ranges; ++i) { + if (v >= cr->scr_min[i] && + v <= cr->scr_max[i]) { + /* value is within ranges constraint */ + return (0); + } + } + return (-1); +} + +/* + * return 0 if value is within count ranges constraint. + * return -1 otherwise + */ +static int +_check_int_ranges(scf_int_ranges_t *ir, int64_t v) +{ + int i; + + for (i = 0; i < ir->sir_num_ranges; ++i) { + if (v >= ir->sir_min[i] && + v <= ir->sir_max[i]) { + /* value is within integer ranges constraint */ + return (0); + } + } + return (-1); +} + +/* + * int _value_in_constraint() + * + * Checks whether the supplied value violates any of the constraints + * specified in the supplied property template. If it does, an appropriate + * error is appended to "errs". pg and prop, if supplied, are used to + * augment the information in the error. Returns 0 on success. + * + * Returns -1 on failure. Sets scf_error(): + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +static int +_value_in_constraint(scf_propertygroup_t *pg, scf_property_t *prop, + const scf_prop_tmpl_t *pt, scf_value_t *value, scf_tmpl_errors_t *errs) +{ + scf_type_t type, tmpl_type; + scf_values_t vals; + scf_tmpl_error_type_t terr_type; + uint64_t v_count; + int64_t v_int; + char *vstr; + ssize_t sz = scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH) + 1; + ssize_t ret = 0; + char **constraints; + int n = 0; + int r; + int err_flag = 0; + scf_count_ranges_t cr; + scf_int_ranges_t ir; + + type = scf_value_type(value); + if (type == SCF_TYPE_INVALID) { + (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + return (-1); + } + + /* Check if template type matches value type. */ + if (scf_tmpl_prop_type(pt, &tmpl_type) == -1) { + if (scf_error() != SCF_ERROR_NOT_FOUND) + /* type is not wildcarded */ + return (-1); + } else if (tmpl_type != type) { + if (errs != NULL) { + if (pg == NULL && prop == NULL) { + if (_add_tmpl_constraint_error(errs, + SCF_TERR_PROP_TYPE_MISMATCH, NULL, pt, + NULL, value) == -1) + return (-1); + } else { + if (_add_tmpl_wrong_prop_type_error(errs, pg, + pt, prop) == -1) + return (-1); + } + } + return (1); + } + + /* Numeric values should be checked against any range constraints. */ + switch (type) { + case SCF_TYPE_COUNT: + r = scf_value_get_count(value, &v_count); + assert(r == 0); + + if (scf_tmpl_value_count_range_constraints(pt, &cr) != 0) { + if (scf_error() == SCF_ERROR_NOT_FOUND) + break; + if (scf_error() == SCF_ERROR_CONSTRAINT_VIOLATED) + (void) scf_set_error( + SCF_ERROR_TEMPLATE_INVALID); + return (-1); + } else { + if (_check_count_ranges(&cr, v_count) == 0) { + /* value is within ranges constraint */ + scf_count_ranges_destroy(&cr); + return (0); + } + scf_count_ranges_destroy(&cr); + } + + /* + * If we get here, we have a possible constraint + * violation. + */ + err_flag |= 0x1; /* RANGE_VIOLATION, count */ + break; + case SCF_TYPE_INTEGER: + if (scf_value_get_integer(value, &v_int) != 0) + assert(0); + if (scf_tmpl_value_int_range_constraints(pt, &ir) != 0) { + if (scf_error() == SCF_ERROR_NOT_FOUND) + break; + if (scf_error() != SCF_ERROR_CONSTRAINT_VIOLATED) + (void) scf_set_error( + SCF_ERROR_TEMPLATE_INVALID); + return (-1); + } else { + if (_check_int_ranges(&ir, v_int) == 0) { + /* value is within ranges constraint */ + scf_int_ranges_destroy(&ir); + return (0); + } + scf_int_ranges_destroy(&ir); + } + /* + * If we get here, we have a possible constraint + * violation. + */ + err_flag |= 0x2; /* RANGE_VIOLATION, integer */ + break; + default: + break; + } + + vstr = malloc(sz); + if (vstr == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + return (-1); + } + + /* + * If a set of names is provided, confirm value has one of + * those names. + */ + if (scf_tmpl_value_name_constraints(pt, &vals) != 0) { + free(vstr); + if (scf_error() != SCF_ERROR_NOT_FOUND) { + return (-1); + } + } else { + r = scf_value_get_as_string_typed(value, type, vstr, sz); + + /* + * All errors (INVALID_ARGUMENT, NOT_SET, TYPE_MISMATCH) + * should be impossible or already caught above. + */ + assert(r > 0); + + constraints = vals.values.v_astring; + for (n = 0; constraints[n] != NULL; ++n) { + if (strcmp(constraints[n], vstr) == 0) { + /* value is within constraint */ + scf_values_destroy(&vals); + free(vstr); + return (0); + } + } + /* if we get here, we have a constraint violation */ + err_flag |= 0x4; /* CONSTRAINT_VIOLATED */ + scf_values_destroy(&vals); + free(vstr); + } + if (err_flag != 0) + ret = 1; + /* register the errors found */ + if (ret == 1 && errs != NULL) { + if ((err_flag & 0x1) == 0x1) { + /* + * Help make the error more human-friendly. If + * pg and prop are provided, we know we're + * validating repository data. If they're not, + * we're validating a potentially hypothetical + * value. + */ + if (pg == NULL && prop == NULL) + terr_type = SCF_TERR_VALUE_OUT_OF_RANGE; + else + terr_type = SCF_TERR_RANGE_VIOLATION; + if (_add_tmpl_count_error(errs, terr_type, pg, pt, + prop, v_count, 0, 0) == -1) + ret = -1; + } + if ((err_flag & 0x2) == 0x2) { + if (pg == NULL && prop == NULL) + terr_type = SCF_TERR_VALUE_OUT_OF_RANGE; + else + terr_type = SCF_TERR_RANGE_VIOLATION; + if (_add_tmpl_int_error(errs, terr_type, pg, pt, prop, + v_int, 0, 0) == -1) + ret = -1; + } + if ((err_flag & 0x4) == 0x4) { + if (pg == NULL && prop == NULL) + terr_type = SCF_TERR_INVALID_VALUE; + else + terr_type = SCF_TERR_VALUE_CONSTRAINT_VIOLATED; + if (_add_tmpl_constraint_error(errs, terr_type, pg, + pt, prop, value) == -1) + ret = -1; + } + } + return (ret); +} + +/* + * Returns -1 on failure. Sets scf_error(): + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +int +scf_tmpl_value_in_constraint(const scf_prop_tmpl_t *pt, scf_value_t *value, + scf_tmpl_errors_t **errs) +{ + scf_tmpl_errors_t *e = NULL; + + if (errs != NULL) { + char *fmri; + + if ((fmri = _scf_tmpl_get_fmri(pt->prt_t)) == NULL) + return (-1); + *errs = _scf_create_errors(fmri, 1); + free(fmri); + if (*errs == NULL) + return (-1); + e = *errs; + } + + return (_value_in_constraint(NULL, NULL, pt, value, e)); +} + +scf_tmpl_error_t * +scf_tmpl_next_error(scf_tmpl_errors_t *errs) +{ + if (errs->tes_index < errs->tes_num_errs) { + assert(errs->tes_errs[errs->tes_index] != NULL); + return (errs->tes_errs[errs->tes_index++]); + } else { + return (NULL); + } +} + +void +scf_tmpl_reset_errors(scf_tmpl_errors_t *errs) +{ + errs->tes_index = 0; +} + +int +scf_tmpl_strerror(scf_tmpl_error_t *err, char *s, size_t n, int flag) +{ + const char *str; + int i; + int ret = -1; + int nsz = 0; /* err msg length */ + int sz = n; /* available buffer size */ + char *buf = s; /* where to append in buffer */ + char *s0 = (flag == SCF_TMPL_STRERROR_HUMAN) ? ":\n\t" : ": "; + char *s1 = (flag == SCF_TMPL_STRERROR_HUMAN) ? "\n\t" : "; "; + char *sep = s0; + const char *val; + + /* prefix */ + if (err->te_errs->tes_prefix != NULL) { + ret = snprintf(buf, sz, "%s", dgettext(TEXT_DOMAIN, + err->te_errs->tes_prefix)); + nsz += ret; + sz = (sz - ret) > 0 ? sz - ret : 0; + buf = (sz > 0) ? s + nsz : NULL; + } + /* error message */ + ret = snprintf(buf, sz, "%s", dgettext(TEXT_DOMAIN, + em_desc[err->te_type].em_msg)); + nsz += ret; + sz = (sz - ret) > 0 ? sz - ret : 0; + buf = (sz > 0) ? s + nsz : NULL; + + for (i = 0; _tmpl_error_items[i].get_desc != NULL; ++i) { + if ((str = _tmpl_error_items[i].get_desc(err)) == NULL) + /* no item to print */ + continue; + val = _tmpl_error_items[i].get_val(err); + ret = snprintf(buf, sz, "%s%s=\"%s\"", sep, str, + (val == NULL) ? "" : val); + nsz += ret; + sz = (sz - ret) > 0 ? sz - ret : 0; + buf = (sz > 0) ? s + nsz : NULL; + sep = s1; + } + return (nsz); +} + +/* + * return 0 on success, -1 on failure. + * set scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +static int +_validate_cardinality(scf_propertygroup_t *pg, scf_prop_tmpl_t *pt, + scf_property_t *prop, scf_tmpl_errors_t *errs) +{ + uint64_t min, max; + scf_handle_t *h; + scf_iter_t *iter = NULL; + scf_value_t *val = NULL; + int count = 0; + int ret = -1; + int r; + + if (scf_tmpl_prop_cardinality(pt, &min, &max) != 0) { + if (scf_error() == SCF_ERROR_NOT_FOUND) + return (0); + else + return (-1); + } + + /* Any number of values permitted. Just return success. */ + if (min == 0 && max == UINT64_MAX) { + return (0); + } + + h = scf_property_handle(prop); + if (h == NULL) { + assert(scf_error() == SCF_ERROR_HANDLE_DESTROYED); + goto cleanup; + } + + iter = scf_iter_create(h); + val = scf_value_create(h); + if (iter == NULL || val == NULL) { + if (ismember(scf_error(), errors_server)) { + goto cleanup; + } else { + assert(0); + abort(); + } + } + + if (scf_iter_property_values(iter, prop) != 0) { + if (ismember(scf_error(), errors_server)) { + goto cleanup; + } else { + assert(0); + abort(); + } + } + + while ((r = scf_iter_next_value(iter, val)) == 1) + count++; + + if (r < 0) { + if (ismember(scf_error(), errors_server)) { + goto cleanup; + } else { + assert(0); + abort(); + } + } + + if (count < min || count > max) + if (_add_tmpl_count_error(errs, SCF_TERR_CARDINALITY_VIOLATION, + pg, pt, prop, (uint64_t)count, &min, &max) == -1) + goto cleanup; + + ret = 0; + +cleanup: + scf_iter_destroy(iter); + scf_value_destroy(val); + return (ret); +} + +/* + * Returns -1 on error. Sets scf_error(): + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +static int +_check_property(scf_prop_tmpl_t *pt, scf_propertygroup_t *pg, + scf_property_t *prop, scf_tmpl_errors_t *errs) +{ + scf_type_t tmpl_type; + uint8_t required; + scf_handle_t *h; + scf_iter_t *iter = NULL; + scf_value_t *val = NULL; + int r; + int ret = -1; + + h = scf_pg_handle(pg); + if (h == NULL) { + assert(scf_error() == SCF_ERROR_HANDLE_DESTROYED); + return (-1); + } + + iter = scf_iter_create(h); + val = scf_value_create(h); + if (iter == NULL || val == NULL) { + if (ismember(scf_error(), errors_server)) { + scf_iter_destroy(iter); + scf_value_destroy(val); + return (-1); + } else { + assert(0); + abort(); + } + } + + if (scf_tmpl_prop_required(pt, &required) != 0) + goto cleanup; + + /* Check type */ + if (scf_tmpl_prop_type(pt, &tmpl_type) == -1) { + if (scf_error() != SCF_ERROR_NOT_FOUND) { + goto cleanup; + } else if (required) { + /* If required, type must be specified. */ + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + goto cleanup; + } + } else if (scf_property_is_type(prop, tmpl_type) != 0) { + if (ismember(scf_error(), errors_server)) { + goto cleanup; + } else switch (scf_error()) { + case SCF_ERROR_TYPE_MISMATCH: + if (_add_tmpl_wrong_prop_type_error(errs, pg, pt, + prop) == -1) + goto cleanup; + break; + + case SCF_ERROR_INVALID_ARGUMENT: + /* + * tmpl_prop_type shouldn't have handed back + * an invalid property type. + */ + case SCF_ERROR_NOT_SET: + default: + assert(0); + abort(); + } + } + + + /* Cardinality */ + if (_validate_cardinality(pg, pt, prop, errs) == -1) + goto cleanup; + + /* Value constraints */ + /* + * Iterate through each value, and confirm it is defined as + * constrained. + */ + if (scf_iter_property_values(iter, prop) != 0) { + assert(scf_error() != SCF_ERROR_NOT_SET && + scf_error() != SCF_ERROR_HANDLE_MISMATCH); + goto cleanup; + } + + while ((r = scf_iter_next_value(iter, val)) == 1) { + if (_value_in_constraint(pg, prop, pt, val, errs) == -1) { + if (ismember(scf_error(), errors_server)) { + goto cleanup; + } else switch (scf_error()) { + case SCF_ERROR_TEMPLATE_INVALID: + goto cleanup; + + case SCF_ERROR_INVALID_ARGUMENT: + default: + assert(0); + abort(); + } + } + } + + if (r < 0) { + if (ismember(scf_error(), errors_server)) { + goto cleanup; + } else { + assert(0); + abort(); + } + } + + ret = 0; + +cleanup: + scf_iter_destroy(iter); + scf_value_destroy(val); + return (ret); +} + +/* + * Returns -1 on failure, sets scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +static int +_check_pg(scf_pg_tmpl_t *t, scf_propertygroup_t *pg, char *pg_name, + char *type, scf_tmpl_errors_t *errs) +{ + scf_prop_tmpl_t *pt = NULL; + char *pg_type = NULL; + scf_iter_t *iter = NULL; + uint8_t pg_required; + scf_property_t *prop = NULL; + scf_handle_t *h; + int r; + char *prop_name = NULL; + ssize_t nsize = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH) + 1; + int ret = -1; + + assert(pg_name != NULL); + assert(t != NULL); + assert(pg != NULL); + assert(type != NULL); + assert(nsize != 0); + + if ((h = scf_pg_handle(pg)) == NULL) { + assert(scf_error() == SCF_ERROR_HANDLE_DESTROYED); + return (-1); + } + if ((pt = scf_tmpl_prop_create(h)) == NULL) { + assert(scf_error() != SCF_ERROR_INVALID_ARGUMENT); + return (-1); + } + + if ((prop = scf_property_create(h)) == NULL) { + assert(scf_error() != SCF_ERROR_INVALID_ARGUMENT); + goto cleanup; + } + + if ((iter = scf_iter_create(h)) == NULL) { + assert(scf_error() != SCF_ERROR_INVALID_ARGUMENT); + goto cleanup; + } + if ((prop_name = malloc(nsize)) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + + if (scf_tmpl_pg_required(t, &pg_required) != 0) + goto cleanup; + + if (scf_tmpl_pg_type(t, &pg_type) == -1) { + goto cleanup; + } else if (pg_required != 0 && + strcmp(SCF_TMPL_WILDCARD, pg_type) == 0) { + /* Type must be specified for required pgs. */ + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + goto cleanup; + } + + if (pg_type != NULL) { + if (strcmp(pg_type, type) != 0 && + strcmp(pg_type, SCF_TMPL_WILDCARD) != 0) { + if (_add_tmpl_wrong_pg_type_error(errs, t, pg) == -1) + goto cleanup; + } + } + + + /* Iterate through properties in the repository and check them. */ + if (scf_iter_pg_properties(iter, pg) != 0) { + if (ismember(scf_error(), errors_server)) { + goto cleanup; + } else { + assert(0); + abort(); + } + } + + while ((r = scf_iter_next_property(iter, prop)) == 1) { + if (scf_property_get_name(prop, prop_name, nsize) == -1) { + assert(scf_error() != SCF_ERROR_NOT_SET); + goto cleanup; + } + if (scf_tmpl_get_by_prop(t, prop_name, pt, 0) != 0) { + if (ismember(scf_error(), errors_server)) { + goto cleanup; + } else switch (scf_error()) { + case SCF_ERROR_NOT_FOUND: + /* No template. Continue. */ + continue; + + case SCF_ERROR_INVALID_ARGUMENT: + default: + assert(0); + abort(); + } + } + + if (_check_property(pt, pg, prop, errs) != 0) + goto cleanup; + } + + if (r < 0) { + if (ismember(scf_error(), errors_server)) { + goto cleanup; + } else { + assert(0); + abort(); + } + } + + scf_tmpl_prop_reset(pt); + free(prop_name); + prop_name = NULL; + /* + * Confirm required properties are present. + */ + while ((r = scf_tmpl_iter_props(t, pt, + SCF_PROP_TMPL_FLAG_REQUIRED)) == 0) { + scf_type_t prop_type; + + if (scf_tmpl_prop_name(pt, &prop_name) == -1) + goto cleanup; + + /* required properties cannot have type wildcarded */ + if (scf_tmpl_prop_type(pt, &prop_type) == -1) { + if (scf_error() == SCF_ERROR_NOT_FOUND) + (void) scf_set_error( + SCF_ERROR_TEMPLATE_INVALID); + goto cleanup; + } + + if (scf_pg_get_property(pg, prop_name, prop) != 0) { + if (ismember(scf_error(), errors_server)) { + goto cleanup; + } else switch (scf_error()) { + case SCF_ERROR_NOT_FOUND: + if (_add_tmpl_missing_prop_error(errs, t, pg, + pt) == -1) + goto cleanup; + break; + + case SCF_ERROR_INVALID_ARGUMENT: + (void) scf_set_error( + SCF_ERROR_TEMPLATE_INVALID); + goto cleanup; + + case SCF_ERROR_HANDLE_MISMATCH: + case SCF_ERROR_NOT_SET: + default: + assert(0); + abort(); + } + } + free(prop_name); + prop_name = NULL; + } + if (r < 0) { + if (ismember(scf_error(), errors_server)) { + goto cleanup; + } else switch (scf_error()) { + case SCF_ERROR_NOT_FOUND: + break; + + case SCF_ERROR_TEMPLATE_INVALID: + goto cleanup; + + case SCF_ERROR_INVALID_ARGUMENT: + default: + assert(0); + abort(); + } + } + + ret = 0; +cleanup: + scf_tmpl_prop_destroy(pt); + scf_iter_destroy(iter); + scf_property_destroy(prop); + free(prop_name); + free(pg_type); + return (ret); +} + +/* + * Checks if instance fmri redefines any pgs defined in restarter or global + * Return -1 on failure, sets scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +static int +_scf_tmpl_check_pg_redef(scf_handle_t *h, const char *fmri, + const char *snapname, scf_tmpl_errors_t *errs) +{ + scf_pg_tmpl_t *t = NULL; + scf_pg_tmpl_t *r = NULL; + char *pg_name = NULL; + char *pg_name_r = NULL; + char *pg_type = NULL; + char *pg_type_r = NULL; + char *target = NULL; + int ret_val = -1; + int ret; + + t = scf_tmpl_pg_create(h); + r = scf_tmpl_pg_create(h); + if (t == NULL || r == NULL) + goto cleanup; + + while ((ret = scf_tmpl_iter_pgs(t, fmri, snapname, NULL, + SCF_PG_TMPL_FLAG_EXACT)) == 1) { + if (scf_tmpl_pg_name(t, &pg_name) == -1) { + goto cleanup; + } + if (scf_tmpl_pg_type(t, &pg_type) == -1) { + goto cleanup; + } + /* look for a redefinition of a global/restarter pg_pattern */ + while ((ret = scf_tmpl_iter_pgs(r, fmri, snapname, pg_type, + 0)) == 1) { + if (scf_tmpl_pg_name(r, &pg_name_r) == -1) { + goto cleanup; + } else if (strcmp(pg_name_r, SCF_TMPL_WILDCARD) != 0 && + strcmp(pg_name, SCF_TMPL_WILDCARD) != 0 && + strcmp(pg_name, pg_name_r) != 0) { + /* not a match */ + free(pg_name_r); + pg_name_r = NULL; + continue; + } + if (scf_tmpl_pg_type(r, &pg_type_r) == -1) { + goto cleanup; + } else if (strcmp(pg_type_r, SCF_TMPL_WILDCARD) != 0 && + strcmp(pg_type, SCF_TMPL_WILDCARD) != 0 && + strcmp(pg_type, pg_type_r) != 0) { + /* not a match */ + free(pg_name_r); + pg_name_r = NULL; + free(pg_type_r); + pg_type_r = NULL; + continue; + } + if (scf_tmpl_pg_target(r, &target) == -1) { + target = NULL; + goto cleanup; + } + if (strcmp(target, SCF_TM_TARGET_ALL) == 0 || + strcmp(target, SCF_TM_TARGET_DELEGATE) == 0) { + /* found a pg_pattern redefinition */ + if (_add_tmpl_pg_redefine_error(errs, t, + r) == -1) + goto cleanup; + free(pg_name_r); + pg_name_r = NULL; + free(target); + target = NULL; + break; + } + free(pg_name_r); + pg_name_r = NULL; + free(target); + target = NULL; + } + if (ret == -1) + goto cleanup; + scf_tmpl_pg_reset(r); + + free(pg_name); + free(pg_type); + pg_name = NULL; + pg_type = NULL; + } + if (ret == -1) + goto cleanup; + + ret_val = 0; + +cleanup: + scf_tmpl_pg_destroy(t); + scf_tmpl_pg_destroy(r); + free(pg_name); + free(pg_type); + free(pg_name_r); + free(pg_type_r); + free(target); + + if (ret_val == -1) { + if (!ismember(scf_error(), errors_server)) { + switch (scf_error()) { + case SCF_ERROR_TYPE_MISMATCH: + (void) scf_set_error( + SCF_ERROR_TEMPLATE_INVALID); + /*FALLTHROUGH*/ + + case SCF_ERROR_CONSTRAINT_VIOLATED: + case SCF_ERROR_INVALID_ARGUMENT: + case SCF_ERROR_NOT_FOUND: + case SCF_ERROR_TEMPLATE_INVALID: + break; + + case SCF_ERROR_HANDLE_MISMATCH: + case SCF_ERROR_NOT_SET: + default: + assert(0); + abort(); + } + } + } + return (ret_val); +} + +/* + * Returns -1 on failure, sets scf_error() to: + * SCF_ERROR_BACKEND_ACCESS + * SCF_ERROR_CONNECTION_BROKEN + * SCF_ERROR_DELETED + * SCF_ERROR_HANDLE_DESTROYED + * SCF_ERROR_INTERNAL + * SCF_ERROR_INVALID_ARGUMENT + * SCF_ERROR_NO_MEMORY + * SCF_ERROR_NO_RESOURCES + * SCF_ERROR_NOT_BOUND + * SCF_ERROR_NOT_FOUND + * SCF_ERROR_PERMISSION_DENIED + * SCF_ERROR_TEMPLATE_INVALID + */ +int +scf_tmpl_validate_fmri(scf_handle_t *h, const char *fmri, const char *snapshot, + scf_tmpl_errors_t **errs, int flags) +{ + scf_pg_tmpl_t *t = NULL; + scf_iter_t *iter = NULL; + scf_propertygroup_t *pg = NULL; + scf_instance_t *inst = NULL; + scf_snapshot_t *snap = NULL; + char *type = NULL; + char *pg_name = NULL; + ssize_t rsize = scf_limit(SCF_LIMIT_MAX_PG_TYPE_LENGTH) + 1; + ssize_t nsize = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH) + 1; + int ret = -1; + int r; + + assert(errs != NULL); + + if ((*errs = _scf_create_errors(fmri, 1)) == NULL) + return (-1); + + if ((pg = scf_pg_create(h)) == NULL || + (iter = scf_iter_create(h)) == NULL || + (inst = scf_instance_create(h)) == NULL || + (t = scf_tmpl_pg_create(h)) == NULL) { + /* + * Sets SCF_ERROR_INVALID_ARGUMENT, SCF_ERROR_NO_MEMORY, + * SCF_ERROR_NO_RESOURCES, SCF_ERROR_INTERNAL or + * SCF_ERROR_HANDLE_DESTROYED. + */ + goto cleanup; + } + + if ((type = malloc(rsize)) == NULL || + (pg_name = malloc(nsize)) == NULL) { + (void) scf_set_error(SCF_ERROR_NO_MEMORY); + goto cleanup; + } + + if (scf_handle_decode_fmri(h, fmri, NULL, NULL, inst, NULL, NULL, + SCF_DECODE_FMRI_EXACT|SCF_DECODE_FMRI_REQUIRE_INSTANCE) != 0) { + if (ismember(scf_error(), errors_server)) { + goto cleanup; + } else switch (scf_error()) { + case SCF_ERROR_CONSTRAINT_VIOLATED: + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + /*FALLTHROUGH*/ + + case SCF_ERROR_INVALID_ARGUMENT: + case SCF_ERROR_NOT_FOUND: + goto cleanup; + + case SCF_ERROR_HANDLE_MISMATCH: + case SCF_ERROR_NOT_SET: + default: + assert(0); + abort(); + } + } + + if (snapshot == NULL || strcmp(snapshot, "running") == 0 || + (flags & SCF_TMPL_VALIDATE_FLAG_CURRENT)) { + if (_get_snapshot(inst, NULL, &snap) == -1) + goto cleanup; + } else { + (void) scf_set_error(SCF_ERROR_NONE); + if (_get_snapshot(inst, snapshot, &snap) == -1) { + goto cleanup; + } else if (scf_error() == SCF_ERROR_NOT_FOUND) { + goto cleanup; + } + } + if (_scf_tmpl_check_pg_redef(h, fmri, snapshot, *errs) != 0) { + goto cleanup; + } + + /* + * Check that property groups on this instance conform to the template. + */ + if (scf_iter_instance_pgs_composed(iter, inst, snap) != 0) { + if (ismember(scf_error(), errors_server)) { + goto cleanup; + } else { + assert(0); + abort(); + } + } + + while ((r = scf_iter_next_pg(iter, pg)) == 1) { + if (scf_pg_get_name(pg, pg_name, nsize) == -1) { + if (ismember(scf_error(), errors_server)) { + goto cleanup; + } else { + assert(0); + abort(); + } + } + + if (scf_pg_get_type(pg, type, rsize) == -1) { + if (ismember(scf_error(), errors_server)) { + goto cleanup; + } else { + assert(0); + abort(); + } + } + + if (scf_tmpl_get_by_pg_name(fmri, snapshot, pg_name, type, t, + 0) != 0) { + if (ismember(scf_error(), errors_server)) { + goto cleanup; + } else switch (scf_error()) { + case SCF_ERROR_NOT_FOUND: + continue; + + case SCF_ERROR_INVALID_ARGUMENT: + goto cleanup; + + default: + assert(0); + abort(); + } + } + + if (_check_pg(t, pg, pg_name, type, *errs) != 0) + goto cleanup; + } + if (r < 0) { + if (ismember(scf_error(), errors_server)) { + goto cleanup; + } else { + assert(0); + abort(); + } + } + + scf_tmpl_pg_reset(t); + + /* + * Confirm required property groups are present. + */ + while ((r = scf_tmpl_iter_pgs(t, fmri, snapshot, NULL, + SCF_PG_TMPL_FLAG_REQUIRED)) == 1) { + free(pg_name); + free(type); + + if (scf_tmpl_pg_name(t, &pg_name) == -1) + goto cleanup; + if (scf_tmpl_pg_type(t, &type) == -1) + goto cleanup; + /* + * required property group templates should not have + * wildcarded name or type + */ + if (strcmp(pg_name, SCF_TMPL_WILDCARD) == 0 || + strcmp(type, SCF_TMPL_WILDCARD) == 0) { + (void) scf_set_error(SCF_ERROR_TEMPLATE_INVALID); + goto cleanup; + } + + if (_get_pg(NULL, inst, snap, pg_name, pg) != 0) { + if (ismember(scf_error(), errors_server)) { + goto cleanup; + } else switch (scf_error()) { + case SCF_ERROR_NOT_FOUND: + if (_add_tmpl_missing_pg_error(*errs, t) == -1) + goto cleanup; + continue; + + case SCF_ERROR_INVALID_ARGUMENT: + case SCF_ERROR_HANDLE_MISMATCH: + case SCF_ERROR_NOT_SET: + default: + assert(0); + abort(); + } + } + } + if (r < 0) { + if (ismember(scf_error(), errors_server)) { + goto cleanup; + } else switch (scf_error()) { + case SCF_ERROR_NOT_FOUND: + break; + + case SCF_ERROR_INVALID_ARGUMENT: + goto cleanup; + + default: + assert(0); + abort(); + } + } + + ret = 0; + if ((*errs)->tes_num_errs > 0) + ret = 1; +cleanup: + if (ret != 1) { + /* there are no errors to report */ + scf_tmpl_errors_destroy(*errs); + *errs = NULL; + } + scf_tmpl_pg_destroy(t); + free(type); + free(pg_name); + + scf_iter_destroy(iter); + scf_pg_destroy(pg); + scf_instance_destroy(inst); + scf_snapshot_destroy(snap); + + return (ret); +} + +void +scf_tmpl_errors_destroy(scf_tmpl_errors_t *errs) +{ + int i; + scf_tmpl_error_t *e; + + if (errs == NULL) + return; + + for (i = 0; i < errs->tes_num_errs; ++i) { + e = errs->tes_errs[i]; + if (errs->tes_flag != 0) { + free((char *)e->te_pg_name); + free((char *)e->te_prop_name); + free((char *)e->te_ev1); + free((char *)e->te_ev2); + free((char *)e->te_actual); + free((char *)e->te_tmpl_fmri); + free((char *)e->te_tmpl_pg_name); + free((char *)e->te_tmpl_pg_type); + free((char *)e->te_tmpl_prop_name); + free((char *)e->te_tmpl_prop_type); + } + free(e); + } + free((char *)errs->tes_fmri); + free((char *)errs->tes_prefix); + free(errs->tes_errs); + free(errs); +} + +int +scf_tmpl_error_source_fmri(const scf_tmpl_error_t *err, char **fmri) +{ + assert(err != NULL); + switch (err->te_type) { + case SCF_TERR_MISSING_PG: + case SCF_TERR_WRONG_PG_TYPE: + case SCF_TERR_MISSING_PROP: + case SCF_TERR_WRONG_PROP_TYPE: + case SCF_TERR_CARDINALITY_VIOLATION: + case SCF_TERR_VALUE_CONSTRAINT_VIOLATED: + case SCF_TERR_RANGE_VIOLATION: + case SCF_TERR_PROP_TYPE_MISMATCH: + case SCF_TERR_VALUE_OUT_OF_RANGE: + case SCF_TERR_INVALID_VALUE: + case SCF_TERR_PG_REDEFINE: + *fmri = (char *)err->te_tmpl_fmri; + return (0); + /*NOTREACHED*/ + default: + (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + } + return (-1); +} + +int +scf_tmpl_error_type(const scf_tmpl_error_t *err, scf_tmpl_error_type_t *type) +{ + assert(err != NULL); + switch (err->te_type) { + case SCF_TERR_MISSING_PG: + case SCF_TERR_WRONG_PG_TYPE: + case SCF_TERR_MISSING_PROP: + case SCF_TERR_WRONG_PROP_TYPE: + case SCF_TERR_CARDINALITY_VIOLATION: + case SCF_TERR_VALUE_CONSTRAINT_VIOLATED: + case SCF_TERR_RANGE_VIOLATION: + case SCF_TERR_PROP_TYPE_MISMATCH: + case SCF_TERR_VALUE_OUT_OF_RANGE: + case SCF_TERR_INVALID_VALUE: + case SCF_TERR_PG_REDEFINE: + *type = err->te_type; + return (0); + /*NOTREACHED*/ + default: + (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + } + return (-1); +} + +int +scf_tmpl_error_pg_tmpl(const scf_tmpl_error_t *err, char **name, char **type) +{ + assert(err != NULL); + switch (err->te_type) { + case SCF_TERR_MISSING_PG: + case SCF_TERR_WRONG_PG_TYPE: + case SCF_TERR_MISSING_PROP: + case SCF_TERR_WRONG_PROP_TYPE: + case SCF_TERR_CARDINALITY_VIOLATION: + case SCF_TERR_VALUE_CONSTRAINT_VIOLATED: + case SCF_TERR_RANGE_VIOLATION: + case SCF_TERR_PROP_TYPE_MISMATCH: + case SCF_TERR_VALUE_OUT_OF_RANGE: + case SCF_TERR_INVALID_VALUE: + case SCF_TERR_PG_REDEFINE: + if (err->te_tmpl_pg_name != NULL && + err->te_tmpl_pg_type != NULL) { + if (name != NULL) + *name = (char *)err->te_tmpl_pg_name; + if (type != NULL) + *type = (char *)err->te_tmpl_pg_type; + return (0); + } + (void) scf_set_error(SCF_ERROR_NOT_FOUND); + break; + default: + (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + } + return (-1); +} + +int +scf_tmpl_error_pg(const scf_tmpl_error_t *err, char **name, char **type) +{ + assert(err != NULL); + switch (err->te_type) { + case SCF_TERR_WRONG_PG_TYPE: + if (err->te_pg_name != NULL && + err->te_actual != NULL) { + if (name != NULL) + *name = (char *)err->te_pg_name; + if (type != NULL) + *type = (char *)err->te_actual; + return (0); + } + (void) scf_set_error(SCF_ERROR_NOT_FOUND); + break; + case SCF_TERR_WRONG_PROP_TYPE: + case SCF_TERR_CARDINALITY_VIOLATION: + case SCF_TERR_VALUE_CONSTRAINT_VIOLATED: + case SCF_TERR_RANGE_VIOLATION: + if (err->te_pg_name != NULL && + err->te_tmpl_pg_type != NULL) { + if (name != NULL) + *name = (char *)err->te_pg_name; + if (type != NULL) + *type = (char *)err->te_tmpl_pg_type; + return (0); + } + /*FALLTHROUGH*/ + case SCF_TERR_MISSING_PROP: + case SCF_TERR_MISSING_PG: + case SCF_TERR_PROP_TYPE_MISMATCH: + case SCF_TERR_VALUE_OUT_OF_RANGE: + case SCF_TERR_INVALID_VALUE: + (void) scf_set_error(SCF_ERROR_NOT_FOUND); + break; + case SCF_TERR_PG_REDEFINE: + if (err->te_ev1 != NULL && err->te_ev2 != NULL) { + if (name != NULL) + *name = (char *)err->te_ev1; + if (type != NULL) + *type = (char *)err->te_ev2; + return (0); + } + (void) scf_set_error(SCF_ERROR_NOT_FOUND); + break; + default: + (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + } + return (-1); +} + +int +scf_tmpl_error_prop_tmpl(const scf_tmpl_error_t *err, char **name, char **type) +{ + assert(err != NULL); + switch (err->te_type) { + case SCF_TERR_MISSING_PROP: + case SCF_TERR_WRONG_PROP_TYPE: + case SCF_TERR_CARDINALITY_VIOLATION: + case SCF_TERR_VALUE_CONSTRAINT_VIOLATED: + case SCF_TERR_RANGE_VIOLATION: + case SCF_TERR_PROP_TYPE_MISMATCH: + case SCF_TERR_VALUE_OUT_OF_RANGE: + case SCF_TERR_INVALID_VALUE: + if (err->te_tmpl_prop_name != NULL && + err->te_tmpl_prop_type != NULL) { + if (name != NULL) + *name = (char *)err->te_tmpl_prop_name; + if (type != NULL) + *type = (char *)err->te_tmpl_prop_type; + return (0); + } + /*FALLTHROUGH*/ + case SCF_TERR_MISSING_PG: + case SCF_TERR_WRONG_PG_TYPE: + case SCF_TERR_PG_REDEFINE: + (void) scf_set_error(SCF_ERROR_NOT_FOUND); + break; + default: + (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + } + return (-1); +} + +int +scf_tmpl_error_prop(const scf_tmpl_error_t *err, char **name, char **type) +{ + assert(err != NULL); + switch (err->te_type) { + case SCF_TERR_WRONG_PROP_TYPE: + case SCF_TERR_CARDINALITY_VIOLATION: + case SCF_TERR_VALUE_CONSTRAINT_VIOLATED: + case SCF_TERR_RANGE_VIOLATION: + if (err->te_prop_name != NULL && + err->te_tmpl_prop_type != NULL) { + if (name != NULL) + *name = (char *)err->te_prop_name; + if (type != NULL) + *type = (char *)err->te_tmpl_prop_type; + return (0); + } + /*FALLTHROUGH*/ + case SCF_TERR_MISSING_PG: + case SCF_TERR_WRONG_PG_TYPE: + case SCF_TERR_MISSING_PROP: + case SCF_TERR_PROP_TYPE_MISMATCH: + case SCF_TERR_VALUE_OUT_OF_RANGE: + case SCF_TERR_INVALID_VALUE: + case SCF_TERR_PG_REDEFINE: + (void) scf_set_error(SCF_ERROR_NOT_FOUND); + break; + default: + (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + } + return (-1); +} + +int +scf_tmpl_error_value(const scf_tmpl_error_t *err, char **val) +{ + assert(err != NULL); + switch (err->te_type) { + case SCF_TERR_VALUE_CONSTRAINT_VIOLATED: + case SCF_TERR_RANGE_VIOLATION: + case SCF_TERR_VALUE_OUT_OF_RANGE: + case SCF_TERR_INVALID_VALUE: + if (err->te_actual != NULL) { + if (val != NULL) + *val = (char *)err->te_actual; + return (0); + } + /*FALLTHROUGH*/ + case SCF_TERR_MISSING_PG: + case SCF_TERR_WRONG_PG_TYPE: + case SCF_TERR_MISSING_PROP: + case SCF_TERR_WRONG_PROP_TYPE: + case SCF_TERR_CARDINALITY_VIOLATION: + case SCF_TERR_PROP_TYPE_MISMATCH: + case SCF_TERR_PG_REDEFINE: + (void) scf_set_error(SCF_ERROR_NOT_FOUND); + break; + default: + (void) scf_set_error(SCF_ERROR_INVALID_ARGUMENT); + } + return (-1); +} + +const char * +scf_tmpl_visibility_to_string(uint8_t vis) +{ + if (vis == SCF_TMPL_VISIBILITY_READONLY) + return (SCF_TM_VISIBILITY_READONLY); + else if (vis == SCF_TMPL_VISIBILITY_HIDDEN) + return (SCF_TM_VISIBILITY_HIDDEN); + else if (vis == SCF_TMPL_VISIBILITY_READWRITE) + return (SCF_TM_VISIBILITY_READWRITE); + else + return ("unknown"); +} diff --git a/usr/src/lib/libscf/inc/libscf.h b/usr/src/lib/libscf/inc/libscf.h index 3bdd12f7b9..eb303f04df 100644 --- a/usr/src/lib/libscf/inc/libscf.h +++ b/usr/src/lib/libscf/inc/libscf.h @@ -18,6 +18,7 @@ * * CDDL HEADER END */ + /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. @@ -56,6 +57,10 @@ typedef struct scf_value scf_value_t; typedef struct scf_iter scf_iter_t; +typedef struct scf_pg_tmpl scf_pg_tmpl_t; +typedef struct scf_prop_tmpl scf_prop_tmpl_t; +typedef struct scf_tmpl_errors scf_tmpl_errors_t; + typedef struct scf_simple_app_props scf_simple_app_props_t; typedef struct scf_simple_prop scf_simple_prop_t; @@ -83,6 +88,44 @@ typedef enum { SCF_TYPE_NET_ADDR_V6 } scf_type_t; +typedef struct scf_time { + int64_t t_seconds; + int32_t t_ns; +} scf_time_t; + +/* + * There is no explicit initializer for this structure. Functions + * which set or populate this structure assume that it is either + * uninitialized or destroyed. + */ +typedef struct scf_values { + scf_type_t value_type; + void *reserved; /* reserved for future use */ + int value_count; + char **values_as_strings; + union { + uint64_t *v_count; + uint8_t *v_boolean; + int64_t *v_integer; + char **v_astring; + char **v_ustring; + char **v_opaque; + scf_time_t *v_time; + } values; +} scf_values_t; + +typedef struct scf_count_ranges { + int scr_num_ranges; + uint64_t *scr_min; + uint64_t *scr_max; +} scf_count_ranges_t; + +typedef struct scf_int_ranges { + int sir_num_ranges; + int64_t *sir_min; + int64_t *sir_max; +} scf_int_ranges_t; + /* * Return codes */ @@ -111,6 +154,7 @@ typedef enum scf_error { SCF_ERROR_VERSION_MISMATCH, /* incompatible SCF version */ SCF_ERROR_BACKEND_READONLY, /* backend is read-only */ SCF_ERROR_DELETED, /* object has been deleted */ + SCF_ERROR_TEMPLATE_INVALID, /* template data is invalid */ SCF_ERROR_CALLBACK_FAILED = 1080, /* user callback function failed */ @@ -118,12 +162,55 @@ typedef enum scf_error { } scf_error_t; /* + * This enum MUST be kept in sync with + * struct _scf_tmpl_error_desc em_desc() in scf_tmpl.c + */ +typedef enum scf_tmpl_error_type { + SCF_TERR_MISSING_PG, /* property group missing */ + SCF_TERR_WRONG_PG_TYPE, /* property group type incorrect */ + SCF_TERR_MISSING_PROP, /* missing required property */ + SCF_TERR_WRONG_PROP_TYPE, /* property type incorrect */ + SCF_TERR_CARDINALITY_VIOLATION, /* wrong number of values */ + SCF_TERR_VALUE_CONSTRAINT_VIOLATED, /* constraint violated for value */ + SCF_TERR_RANGE_VIOLATION, /* value violated specified range */ + SCF_TERR_PG_REDEFINE, /* global or restarter pg_pattern */ + /* redefined by the instance */ + SCF_TERR_PROP_TYPE_MISMATCH, /* property and value type mismatch */ + SCF_TERR_VALUE_OUT_OF_RANGE, /* value is out of range in template */ + SCF_TERR_INVALID_VALUE, /* value is not valid for the */ + /* template */ + SCF_TERR_PG_PATTERN_CONFLICT, /* pg_pattern conflicts with higher */ + /* level definition */ + SCF_TERR_PROP_PATTERN_CONFLICT, /* prop_pattern conflicts with higher */ + /* level definition */ + SCF_TERR_GENERAL_REDEFINE, /* global or restarter template */ + /* redefined */ + SCF_TERR_INCLUDE_VALUES, /* No supporting constraints or */ + /* values for include_values */ + SCF_TERR_PG_PATTERN_INCOMPLETE, /* Required pg_pattern is missing */ + /* name or type attribute. */ + SCF_TERR_PROP_PATTERN_INCOMPLETE /* Required prop_pattern is */ + /* missing a type attribute. */ +} scf_tmpl_error_type_t; + +typedef struct scf_tmpl_error scf_tmpl_error_t; + +/* + * scf_tmpl_strerror() human readable flag + */ +#define SCF_TMPL_STRERROR_HUMAN 0x1 + +/* * Standard services */ -#define SCF_SERVICE_STARTD ((const char *) \ - "svc:/system/svc/restarter:default") #define SCF_SERVICE_CONFIGD ((const char *) \ "svc:/system/svc/repository:default") +#define SCF_INSTANCE_GLOBAL ((const char *) \ + "svc:/system/svc/global:default") +#define SCF_SERVICE_GLOBAL ((const char *) \ + "svc:/system/svc/global") +#define SCF_SERVICE_STARTD ((const char *) \ + "svc:/system/svc/restarter:default") /* * Major milestones @@ -148,6 +235,8 @@ typedef enum scf_error { #define SCF_GROUP_DEPENDENCY ((const char *)"dependency") #define SCF_GROUP_METHOD ((const char *)"method") #define SCF_GROUP_TEMPLATE ((const char *)"template") +#define SCF_GROUP_TEMPLATE_PG_PATTERN ((const char *)"template_pg_pattern") +#define SCF_GROUP_TEMPLATE_PROP_PATTERN ((const char *)"template_prop_pattern") /* * Dependency types @@ -179,7 +268,7 @@ typedef enum scf_error { #define SCF_PG_DEATHROW ((const char *)"deathrow") /* - * Template property group names and prefix + * Template property group names and prefixes */ #define SCF_PG_TM_COMMON_NAME ((const char *)"tm_common_name") #define SCF_PG_TM_DESCRIPTION ((const char *)"tm_description") @@ -204,6 +293,7 @@ typedef enum scf_error { #define SCF_PROPERTY_GROUP ((const char *)"group") #define SCF_PROPERTY_GROUPING ((const char *)"grouping") #define SCF_PROPERTY_IGNORE ((const char *)"ignore_error") +#define SCF_PROPERTY_INTERNAL_SEPARATORS ((const char *)"internal_separators") #define SCF_PROPERTY_LIMIT_PRIVILEGES ((const char *)"limit_privileges") #define SCF_PROPERTY_MAINT_OFF ((const char *)"maint_off") #define SCF_PROPERTY_MAINT_ON ((const char *)"maint_on") @@ -248,11 +338,34 @@ typedef enum scf_error { /* * Template property names */ +#define SCF_PROPERTY_TM_CARDINALITY_MIN ((const char *)"cardinality_min") +#define SCF_PROPERTY_TM_CARDINALITY_MAX ((const char *)"cardinality_max") +#define SCF_PROPERTY_TM_CHOICES_INCLUDE_VALUES ((const char *) \ + "choices_include_values") +#define SCF_PROPERTY_TM_CHOICES_NAME ((const char *)"choices_name") +#define SCF_PROPERTY_TM_CHOICES_RANGE ((const char *)"choices_range") +#define SCF_PROPERTY_TM_CONSTRAINT_NAME ((const char *)"constraint_name") +#define SCF_PROPERTY_TM_CONSTRAINT_RANGE ((const char *)"constraint_range") #define SCF_PROPERTY_TM_MANPATH ((const char *)"manpath") +#define SCF_PROPERTY_TM_NAME ((const char *)"name") +#define SCF_PROPERTY_TM_PG_PATTERN ((const char *)"pg_pattern") +#define SCF_PROPERTY_TM_REQUIRED ((const char *)"required") #define SCF_PROPERTY_TM_SECTION ((const char *)"section") +#define SCF_PROPERTY_TM_TARGET ((const char *)"target") #define SCF_PROPERTY_TM_TITLE ((const char *)"title") -#define SCF_PROPERTY_TM_NAME ((const char *)"name") +#define SCF_PROPERTY_TM_TYPE ((const char *)"type") #define SCF_PROPERTY_TM_URI ((const char *)"uri") +#define SCF_PROPERTY_TM_VALUE_PREFIX ((const char *)"value_") +#define SCF_PROPERTY_TM_VALUES_NAME ((const char *)"values_name") +#define SCF_PROPERTY_TM_VISIBILITY ((const char *)"visibility") +#define SCF_PROPERTY_TM_COMMON_NAME_PREFIX ((const char *)"common_name_") +#define SCF_PROPERTY_TM_DESCRIPTION_PREFIX ((const char *)"description_") +#define SCF_PROPERTY_TM_UNITS_PREFIX ((const char *)"units_") + +/* + * Templates wildcard string + */ +#define SCF_TMPL_WILDCARD ((const char *)"*") /* * Strings used by restarters for state and next_state properties. @@ -307,6 +420,8 @@ int scf_handle_unbind(scf_handle_t *); void scf_handle_destroy(scf_handle_t *); int scf_type_base_type(scf_type_t type, scf_type_t *out); +const char *scf_type_to_string(scf_type_t); +scf_type_t scf_string_to_type(const char *); /* values */ scf_value_t *scf_value_create(scf_handle_t *); @@ -509,9 +624,113 @@ int scf_handle_decode_fmri(scf_handle_t *, const char *, scf_scope_t *, ssize_t scf_myname(scf_handle_t *, char *, size_t); /* - * Simplified calls + * Property group template interfaces. */ +scf_pg_tmpl_t *scf_tmpl_pg_create(scf_handle_t *); +void scf_tmpl_pg_destroy(scf_pg_tmpl_t *); +void scf_tmpl_pg_reset(scf_pg_tmpl_t *); +int scf_tmpl_get_by_pg(scf_propertygroup_t *, scf_pg_tmpl_t *, int); +int scf_tmpl_get_by_pg_name(const char *, const char *, + const char *, const char *, scf_pg_tmpl_t *, int); +int scf_tmpl_iter_pgs(scf_pg_tmpl_t *, const char *, const char *, + const char *, int); +#define SCF_PG_TMPL_FLAG_REQUIRED 0x1 +#define SCF_PG_TMPL_FLAG_EXACT 0x2 +#define SCF_PG_TMPL_FLAG_CURRENT 0x4 + +ssize_t scf_tmpl_pg_name(const scf_pg_tmpl_t *, char **); +ssize_t scf_tmpl_pg_common_name(const scf_pg_tmpl_t *, const char *, char **); +ssize_t scf_tmpl_pg_description(const scf_pg_tmpl_t *, const char *, char **); +ssize_t scf_tmpl_pg_type(const scf_pg_tmpl_t *, char **); + +ssize_t scf_tmpl_pg_target(const scf_pg_tmpl_t *, char **); +#define SCF_TM_TARGET_ALL ((const char *)"all") +#define SCF_TM_TARGET_DELEGATE ((const char *)"delegate") +#define SCF_TM_TARGET_INSTANCE ((const char *)"instance") +#define SCF_TM_TARGET_THIS ((const char *)"this") + +int scf_tmpl_pg_required(const scf_pg_tmpl_t *, uint8_t *); +/* + * Property template interfaces. + */ +scf_prop_tmpl_t *scf_tmpl_prop_create(scf_handle_t *); +void scf_tmpl_prop_destroy(scf_prop_tmpl_t *); +void scf_tmpl_prop_reset(scf_prop_tmpl_t *); +int scf_tmpl_get_by_prop(scf_pg_tmpl_t *, const char *, + scf_prop_tmpl_t *, int); +int scf_tmpl_iter_props(scf_pg_tmpl_t *, scf_prop_tmpl_t *, int); +#define SCF_PROP_TMPL_FLAG_REQUIRED 0x1 + +ssize_t scf_tmpl_prop_name(const scf_prop_tmpl_t *, char **); +int scf_tmpl_prop_type(const scf_prop_tmpl_t *, scf_type_t *); +int scf_tmpl_prop_required(const scf_prop_tmpl_t *, uint8_t *); +ssize_t scf_tmpl_prop_common_name(const scf_prop_tmpl_t *, const char *, + char **); +ssize_t scf_tmpl_prop_description(const scf_prop_tmpl_t *, const char *, + char **); +ssize_t scf_tmpl_prop_units(const scf_prop_tmpl_t *, const char *, char **); +int scf_tmpl_prop_cardinality(const scf_prop_tmpl_t *prop, uint64_t *, + uint64_t *); +int scf_tmpl_prop_internal_seps(const scf_prop_tmpl_t *, scf_values_t *); + +int scf_tmpl_prop_visibility(const scf_prop_tmpl_t *, uint8_t *); +#define SCF_TMPL_VISIBILITY_HIDDEN 1 +#define SCF_TMPL_VISIBILITY_READONLY 2 +#define SCF_TMPL_VISIBILITY_READWRITE 3 + +const char *scf_tmpl_visibility_to_string(uint8_t); +#define SCF_TM_VISIBILITY_HIDDEN ((const char *)"hidden") +#define SCF_TM_VISIBILITY_READONLY ((const char *)"readonly") +#define SCF_TM_VISIBILITY_READWRITE ((const char *)"readwrite") + +int scf_tmpl_value_name_constraints(const scf_prop_tmpl_t *prop, + scf_values_t *vals); +void scf_count_ranges_destroy(scf_count_ranges_t *); +void scf_int_ranges_destroy(scf_int_ranges_t *); +int scf_tmpl_value_count_range_constraints(const scf_prop_tmpl_t *, + scf_count_ranges_t *); +int scf_tmpl_value_int_range_constraints(const scf_prop_tmpl_t *, + scf_int_ranges_t *); +int scf_tmpl_value_count_range_choices(const scf_prop_tmpl_t *, + scf_count_ranges_t *); +int scf_tmpl_value_int_range_choices(const scf_prop_tmpl_t *, + scf_int_ranges_t *); +int scf_tmpl_value_name_choices(const scf_prop_tmpl_t *prop, + scf_values_t *vals); + +void scf_values_destroy(scf_values_t *); + +ssize_t scf_tmpl_value_common_name(const scf_prop_tmpl_t *, const char *, + const char *, char **); +ssize_t scf_tmpl_value_description(const scf_prop_tmpl_t *, const char *, + const char *, char **); + +int scf_tmpl_value_in_constraint(const scf_prop_tmpl_t *pt, scf_value_t *value, + scf_tmpl_errors_t **errs); + +/* + * Template validation interfaces + */ +int scf_tmpl_validate_fmri(scf_handle_t *, const char *, + const char *, scf_tmpl_errors_t **, int); +#define SCF_TMPL_VALIDATE_FLAG_CURRENT 0x1 + +void scf_tmpl_errors_destroy(scf_tmpl_errors_t *errs); +scf_tmpl_error_t *scf_tmpl_next_error(scf_tmpl_errors_t *); +void scf_tmpl_reset_errors(scf_tmpl_errors_t *errs); +int scf_tmpl_strerror(scf_tmpl_error_t *err, char *s, size_t n, int flag); +int scf_tmpl_error_source_fmri(const scf_tmpl_error_t *, char **); +int scf_tmpl_error_type(const scf_tmpl_error_t *, scf_tmpl_error_type_t *); +int scf_tmpl_error_pg_tmpl(const scf_tmpl_error_t *, char **, char **); +int scf_tmpl_error_pg(const scf_tmpl_error_t *, char **, char **); +int scf_tmpl_error_prop_tmpl(const scf_tmpl_error_t *, char **, char **); +int scf_tmpl_error_prop(const scf_tmpl_error_t *, char **, char **); +int scf_tmpl_error_value(const scf_tmpl_error_t *, char **); + +/* + * Simplified calls + */ int smf_enable_instance(const char *, int); int smf_disable_instance(const char *, int); int smf_refresh_instance(const char *); diff --git a/usr/src/lib/libscf/inc/libscf_priv.h b/usr/src/lib/libscf/inc/libscf_priv.h index 4c6ad80597..559f647a8e 100644 --- a/usr/src/lib/libscf/inc/libscf_priv.h +++ b/usr/src/lib/libscf/inc/libscf_priv.h @@ -212,9 +212,6 @@ int scf_parse_file_fmri(char *fmri, const char **scope, const char **path); ssize_t scf_canonify_fmri(const char *, char *, size_t); -const char *scf_type_to_string(scf_type_t); -scf_type_t scf_string_to_type(const char *); - int _smf_refresh_instance_i(scf_instance_t *); typedef struct scf_simple_handle { @@ -398,11 +395,6 @@ int gen_filenms_from_fmri(const char *, const char *, char *, char *); * (both) pv_aux is unused */ typedef struct { - int64_t st_sec; - int32_t st_nanosec; -} scf_time_t; - -typedef struct { void *so_addr; size_t so_size; } scf_opaque_t; @@ -421,6 +413,112 @@ int scf_read_propvec(const char *, const char *, boolean_t, scf_propvec_t *, int scf_write_propvec(const char *, const char *, scf_propvec_t *, scf_propvec_t **); +scf_tmpl_errors_t *_scf_create_errors(const char *, int); +int _scf_tmpl_add_error(scf_tmpl_errors_t *errs, scf_tmpl_error_type_t type, + const char *pg_name, const char *prop_name, + const char *ev1, const char *ev2, const char *actual, + const char *tmpl_fmri, const char *tmpl_pg_name, const char *tmpl_pg_type, + const char *tmpl_prop_name, const char *tmpl_prop_type); +int _scf_tmpl_error_set_prefix(scf_tmpl_errors_t *, const char *); + +/* + * Templates definitions + */ + +/* + * For CARDINALITY_VIOLATION and RANGE_VIOLATION, te_ev1 holds + * the min value and te_ev2 holds the max value + * + * For MISSING_PG te_ev1 should hold the expected pg_name and + * expected2 holds the expected pg_type. + * + * For SCF_TERR_PG_PATTERN_CONFLICT and SCF_TERR_GENERAL_REDEFINE te_ev1 is + * the FMRI holding the conflicting pg_pattern. te_ev2 is the name of the + * conflicting pg_pattern, and actual is the type of the conflicting + * pg_pattern. + * + * SCF_TERR_PROP_PATTERN_CONFLICT te_ev1 is the FMRI holding the + * conflicting prop_pattern. te_ev2 is the name of the conflicting + * prop_pattern, and actual is the type of the conflicting prop_pattern. + * + * For SCF_TERR_INCLUDE_VALUES te_ev1 is the type specified for the + * include_values element. + * + * For all other errors, te_ev1 should hold the expected value and + * te_ev2 is ignored + * + * te_actual holds the current value of the property + */ + +struct scf_tmpl_error { + scf_tmpl_errors_t *te_errs; + scf_tmpl_error_type_t te_type; + const char *te_pg_name; + const char *te_prop_name; + const char *te_ev1; + const char *te_ev2; + const char *te_actual; + const char *te_tmpl_fmri; + const char *te_tmpl_pg_name; + const char *te_tmpl_pg_type; + const char *te_tmpl_prop_name; + const char *te_tmpl_prop_type; +}; + +/* + * The pg_pattern element has two optional attributes that play a part in + * selecting the appropriate prefix for the name of the pg_pattern property + * group. The two attributes are name and type. The appropriate prefix + * encodes the presence are absence of these attributes. + * + * SCF_PG_TM_PG_PATTERN_PREFIX neither attribute + * SCF_PG_TM_PG_PATTERN_N_PREFIX name only + * SCF_PG_TM_PG_PATTERN_T_PREFIX type only + * SCF_PG_TM_PG_PATTERN_NT_PREFIX both name and type + */ +#define SCF_PG_TM_PG_PAT_BASE "tm_pgpat" +#define SCF_PG_TM_PG_PATTERN_PREFIX ((const char *)SCF_PG_TM_PG_PAT_BASE \ + "_") +#define SCF_PG_TM_PG_PATTERN_N_PREFIX ((const char *)SCF_PG_TM_PG_PAT_BASE \ + "n_") +#define SCF_PG_TM_PG_PATTERN_T_PREFIX ((const char *)SCF_PG_TM_PG_PAT_BASE \ + "t_") +#define SCF_PG_TM_PG_PATTERN_NT_PREFIX ((const char *)SCF_PG_TM_PG_PAT_BASE \ + "nt_") +#define SCF_PG_TM_PROP_PATTERN_PREFIX ((const char *)"tm_proppat_") + +/* + * Pad character to use when encoding strings for property names. + */ +#define SCF_ENCODE32_PAD ('-') + +/* + * Functions for base 32 encoding/decoding + */ +int scf_decode32(const char *, size_t, char *, size_t, size_t *, char); +int scf_encode32(const char *, size_t, char *, size_t, size_t *, char); + +/* + * handy functions + */ +/* + * _scf_sanitize_locale + * Make sure a locale string has only alpha-numeric or '_' characters + */ +void _scf_sanitize_locale(char *); + +/* + * _scf_read_tmpl_prop_type_as_string() + * Handy function to get template property type as a string + */ +char *_scf_read_tmpl_prop_type_as_string(const scf_prop_tmpl_t *); +/* + * _scf_read_single_astring_from_pg() + * Given a property group (pg) and a property name (pn), this function + * retrives an astring value from pg/pn. + */ +char *_scf_read_single_astring_from_pg(scf_propertygroup_t *, const char *); + #ifdef __cplusplus } #endif diff --git a/usr/src/lib/libuutil/common/libuutil.h b/usr/src/lib/libuutil/common/libuutil.h index ab6b012eef..ccd46b9774 100644 --- a/usr/src/lib/libuutil/common/libuutil.h +++ b/usr/src/lib/libuutil/common/libuutil.h @@ -2,9 +2,8 @@ * 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. + * 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. @@ -20,15 +19,13 @@ * CDDL HEADER END */ /* - * Copyright 2005 Sun Microsystems, Inc. All rights reserved. + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _LIBUUTIL_H #define _LIBUUTIL_H -#pragma ident "%Z%%M% %I% %E% SMI" - #include <sys/types.h> #include <stdarg.h> @@ -148,6 +145,7 @@ extern int uu_open_tmp(const char *dir, uint_t uflags); /*PRINTFLIKE1*/ extern char *uu_msprintf(const char *format, ...); extern void *uu_zalloc(size_t); +extern char *uu_strdup(const char *); extern void uu_free(void *); /* diff --git a/usr/src/lib/libuutil/common/mapfile-vers b/usr/src/lib/libuutil/common/mapfile-vers index 9794b33328..7d3fdb4a65 100644 --- a/usr/src/lib/libuutil/common/mapfile-vers +++ b/usr/src/lib/libuutil/common/mapfile-vers @@ -19,11 +19,9 @@ # CDDL HEADER END # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # -# ident "%Z%%M% %I% %E% SMI" -# SUNWprivate_1.1 { global: @@ -87,6 +85,7 @@ SUNWprivate_1.1 { uu_msprintf; uu_open_tmp; uu_setpname; + uu_strdup; uu_strerror; uu_strtoint; uu_strtouint; diff --git a/usr/src/lib/libuutil/common/uu_alloc.c b/usr/src/lib/libuutil/common/uu_alloc.c index 7cdbf01750..05d8622871 100644 --- a/usr/src/lib/libuutil/common/uu_alloc.c +++ b/usr/src/lib/libuutil/common/uu_alloc.c @@ -2,9 +2,8 @@ * 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. + * 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. @@ -20,12 +19,10 @@ * CDDL HEADER END */ /* - * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" - #include "libuutil_common.h" #include <stdarg.h> @@ -55,6 +52,22 @@ uu_free(void *p) } char * +uu_strdup(const char *str) +{ + char *buf = NULL; + + if (str != NULL) { + size_t sz; + + sz = strlen(str) + 1; + buf = uu_zalloc(sz); + if (buf != NULL) + (void) memcpy(buf, str, sz); + } + return (buf); +} + +char * uu_msprintf(const char *format, ...) { va_list args; diff --git a/usr/src/pkgdefs/SUNWcsr/prototype_com b/usr/src/pkgdefs/SUNWcsr/prototype_com index 0d3d4c6ec9..6682b68886 100644 --- a/usr/src/pkgdefs/SUNWcsr/prototype_com +++ b/usr/src/pkgdefs/SUNWcsr/prototype_com @@ -551,6 +551,7 @@ f manifest var/svc/manifest/system/system-log.xml 0444 root sys f manifest var/svc/manifest/system/utmp.xml 0444 root sys f manifest var/svc/manifest/system/vtdaemon.xml 0444 root sys d none var/svc/manifest/system/svc 755 root sys +f manifest var/svc/manifest/system/svc/global.xml 0444 root sys f manifest var/svc/manifest/system/svc/restarter.xml 0444 root sys d none var/svc/profile 755 root sys f none var/svc/profile/generic_open.xml 0444 root sys diff --git a/usr/src/tools/scripts/bfu.sh b/usr/src/tools/scripts/bfu.sh index 643e83fa9e..da02e5924b 100644 --- a/usr/src/tools/scripts/bfu.sh +++ b/usr/src/tools/scripts/bfu.sh @@ -1826,6 +1826,22 @@ smf_fix_i86pc_profile () { [[ -n "$rootprefix" ]] && unset SVCCFG_REPOSITORY } +# +# If the new system doesn't support the templates DTD extensions +# (due to backwards bfu), the global.xml manifest should be deleted. +# +smf_bkbfu_templates() { + mfst="var/svc/manifest/system/svc/global.xml" + + grep "pg_pattern" \ + $rootprefix/usr/share/lib/xml/dtd/service_bundle.dtd.1> \ + /dev/null 2>&1 + if [ $? -eq 1 ]; then + rm -f $rootprefix/$mfst + fi + +} + smf_apply_conf () { # # Go thru the original manifests and move any that were unchanged @@ -2252,6 +2268,8 @@ EOF EOF smf_fix_i86pc_profile + + smf_bkbfu_templates } tx_check_update() { |