diff options
Diffstat (limited to 'usr/src/cmd/svc/svccfg')
-rw-r--r-- | usr/src/cmd/svc/svccfg/Makefile | 2 | ||||
-rw-r--r-- | usr/src/cmd/svc/svccfg/svccfg.h | 99 | ||||
-rw-r--r-- | usr/src/cmd/svc/svccfg/svccfg.l | 1 | ||||
-rw-r--r-- | usr/src/cmd/svc/svccfg/svccfg.y | 28 | ||||
-rw-r--r-- | usr/src/cmd/svc/svccfg/svccfg_engine.c | 44 | ||||
-rw-r--r-- | usr/src/cmd/svc/svccfg/svccfg_help.c | 14 | ||||
-rw-r--r-- | usr/src/cmd/svc/svccfg/svccfg_internal.c | 303 | ||||
-rw-r--r-- | usr/src/cmd/svc/svccfg/svccfg_libscf.c | 990 | ||||
-rw-r--r-- | usr/src/cmd/svc/svccfg/svccfg_main.c | 5 | ||||
-rw-r--r-- | usr/src/cmd/svc/svccfg/svccfg_tmpl.c | 4036 | ||||
-rw-r--r-- | usr/src/cmd/svc/svccfg/svccfg_xml.c | 1501 |
11 files changed, 6842 insertions, 181 deletions
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; } } |