| /* |
| * CDDL HEADER START |
| * |
| * The contents of this file are subject to the terms of the |
| * Common Development and Distribution License (the "License"). |
| * You may not use this file except in compliance with the License. |
| * |
| * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
| * or http://www.opensolaris.org/os/licensing. |
| * See the License for the specific language governing permissions |
| * and limitations under the License. |
| * |
| * When distributing Covered Code, include this CDDL HEADER in each |
| * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
| * If applicable, add the following below this CDDL HEADER, with the |
| * fields enclosed by brackets "[]" replaced with your own identifying |
| * information: Portions Copyright [yyyy] [name of copyright owner] |
| * |
| * CDDL HEADER END |
| */ |
| |
| /* |
| * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. |
| * Copyright 2015 Joyent, Inc. |
| * Copyright 2012 Milan Jurik. All rights reserved. |
| * Copyright 2017 RackTop Systems. |
| * Copyright 2018 OmniOS Community Edition (OmniOSce) Association. |
| */ |
| |
| |
| #include <alloca.h> |
| #include <assert.h> |
| #include <ctype.h> |
| #include <door.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <fnmatch.h> |
| #include <inttypes.h> |
| #include <libintl.h> |
| #include <libnvpair.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> |
| #include <time.h> |
| #include <unistd.h> |
| #include <wait.h> |
| #include <poll.h> |
| |
| #include <libxml/tree.h> |
| |
| #include <sys/param.h> |
| |
| #include <sys/stat.h> |
| #include <sys/mman.h> |
| |
| #include "svccfg.h" |
| #include "notify_params.h" |
| #include "manifest_hash.h" |
| #include "manifest_find.h" |
| |
| /* The colon namespaces in each entity (each followed by a newline). */ |
| #define COLON_NAMESPACES ":properties\n" |
| |
| #define TEMP_FILE_PATTERN "/tmp/svccfg-XXXXXX" |
| |
| /* These are characters which the lexer requires to be in double-quotes. */ |
| #define CHARS_TO_QUOTE " \t\n\\>=\"()" |
| |
| #define HASH_SIZE 16 |
| #define HASH_PG_TYPE "framework" |
| #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 " " |
| |
| /* |
| * Directory locations for manifests |
| */ |
| #define VARSVC_DIR "/var/svc/manifest" |
| #define LIBSVC_DIR "/lib/svc/manifest" |
| #define VARSVC_PR "var_svc_manifest" |
| #define LIBSVC_PR "lib_svc_manifest" |
| #define MFSTFILEPR "manifestfile" |
| |
| #define SUPPORTPROP "support" |
| |
| #define MFSTHISTFILE "/lib/svc/share/mfsthistory" |
| |
| #define MFSTFILE_MAX 16 |
| |
| /* |
| * These are the classes of elements which may appear as children of service |
| * or instance elements in XML manifests. |
| */ |
| struct entity_elts { |
| xmlNodePtr create_default_instance; |
| xmlNodePtr single_instance; |
| xmlNodePtr restarter; |
| xmlNodePtr dependencies; |
| xmlNodePtr dependents; |
| xmlNodePtr method_context; |
| xmlNodePtr exec_methods; |
| xmlNodePtr notify_params; |
| xmlNodePtr property_groups; |
| xmlNodePtr instances; |
| xmlNodePtr stability; |
| xmlNodePtr template; |
| }; |
| |
| /* |
| * Likewise for property_group elements. |
| */ |
| struct pg_elts { |
| xmlNodePtr stability; |
| xmlNodePtr propvals; |
| xmlNodePtr properties; |
| }; |
| |
| /* |
| * Likewise for template elements. |
| */ |
| struct template_elts { |
| xmlNodePtr common_name; |
| xmlNodePtr description; |
| xmlNodePtr documentation; |
| }; |
| |
| /* |
| * Likewise for type (for notification parameters) elements. |
| */ |
| struct params_elts { |
| xmlNodePtr paramval; |
| xmlNodePtr parameter; |
| }; |
| |
| /* |
| * This structure is for snaplevel lists. They are convenient because libscf |
| * only allows traversing snaplevels in one direction. |
| */ |
| struct snaplevel { |
| uu_list_node_t list_node; |
| scf_snaplevel_t *sl; |
| }; |
| |
| /* |
| * This is used for communication between lscf_service_export and |
| * export_callback. |
| */ |
| struct export_args { |
| const char *filename; |
| int flags; |
| }; |
| |
| /* |
| * The service_manifest structure is used by the upgrade process |
| * to create a list of service to manifest linkages from the manifests |
| * in a set of given directories. |
| */ |
| typedef struct service_manifest { |
| const char *servicename; |
| uu_list_t *mfstlist; |
| size_t mfstlist_sz; |
| |
| uu_avl_node_t svcmfst_node; |
| } service_manifest_t; |
| |
| /* |
| * Structure to track the manifest file property group |
| * and the manifest file associated with that property |
| * group. Also, a flag to keep the access once it has |
| * been checked. |
| */ |
| struct mpg_mfile { |
| char *mpg; |
| char *mfile; |
| int access; |
| }; |
| |
| const char * const scf_pg_general = SCF_PG_GENERAL; |
| const char * const scf_group_framework = SCF_GROUP_FRAMEWORK; |
| const char * const scf_property_enabled = SCF_PROPERTY_ENABLED; |
| const char * const scf_property_external = "external"; |
| |
| const char * const snap_initial = "initial"; |
| 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; |
| ssize_t max_scf_name_len; |
| ssize_t max_scf_pg_type_len; |
| ssize_t max_scf_value_len; |
| static size_t max_scf_len; |
| |
| static scf_scope_t *cur_scope; |
| static scf_service_t *cur_svc = NULL; |
| static scf_instance_t *cur_inst = NULL; |
| static scf_snapshot_t *cur_snap = NULL; |
| static scf_snaplevel_t *cur_level = NULL; |
| |
| static uu_list_pool_t *snaplevel_pool; |
| /* cur_levels is the snaplevels of cur_snap, from least specific to most. */ |
| static uu_list_t *cur_levels; |
| static struct snaplevel *cur_elt; /* cur_elt->sl == cur_level */ |
| |
| static FILE *tempfile = NULL; |
| static char tempfilename[sizeof (TEMP_FILE_PATTERN)] = ""; |
| |
| static const char *emsg_entity_not_selected; |
| static const char *emsg_permission_denied; |
| static const char *emsg_create_xml; |
| static const char *emsg_cant_modify_snapshots; |
| static const char *emsg_invalid_for_snapshot; |
| static const char *emsg_read_only; |
| static const char *emsg_deleted; |
| static const char *emsg_invalid_pg_name; |
| static const char *emsg_invalid_prop_name; |
| static const char *emsg_no_such_pg; |
| static const char *emsg_fmri_invalid_pg_name; |
| static const char *emsg_fmri_invalid_pg_name_type; |
| static const char *emsg_pg_added; |
| static const char *emsg_pg_changed; |
| static const char *emsg_pg_deleted; |
| static const char *emsg_pg_mod_perm; |
| static const char *emsg_pg_add_perm; |
| static const char *emsg_pg_del_perm; |
| static const char *emsg_snap_perm; |
| static const char *emsg_dpt_dangling; |
| static const char *emsg_dpt_no_dep; |
| |
| static int li_only = 0; |
| static int no_refresh = 0; |
| |
| /* how long in ns we should wait between checks for a pg */ |
| static uint64_t pg_timeout = 100 * (NANOSEC / MILLISEC); |
| |
| /* import globals, to minimize allocations */ |
| static scf_scope_t *imp_scope = NULL; |
| static scf_service_t *imp_svc = NULL, *imp_tsvc = NULL; |
| static scf_instance_t *imp_inst = NULL, *imp_tinst = NULL; |
| static scf_snapshot_t *imp_snap = NULL, *imp_lisnap = NULL, *imp_tlisnap = NULL; |
| static scf_snapshot_t *imp_rsnap = NULL; |
| static scf_snaplevel_t *imp_snpl = NULL, *imp_rsnpl = NULL; |
| static scf_propertygroup_t *imp_pg = NULL, *imp_pg2 = NULL; |
| static scf_property_t *imp_prop = NULL; |
| static scf_iter_t *imp_iter = NULL; |
| static scf_iter_t *imp_rpg_iter = NULL; |
| static scf_iter_t *imp_up_iter = NULL; |
| static scf_transaction_t *imp_tx = NULL; /* always reset this */ |
| static char *imp_str = NULL; |
| static size_t imp_str_sz; |
| static char *imp_tsname = NULL; |
| static char *imp_fe1 = NULL; /* for fmri_equal() */ |
| static char *imp_fe2 = NULL; |
| static uu_list_t *imp_deleted_dpts = NULL; /* pgroup_t's to refresh */ |
| |
| /* upgrade_dependents() globals */ |
| static scf_instance_t *ud_inst = NULL; |
| static scf_snaplevel_t *ud_snpl = NULL; |
| static scf_propertygroup_t *ud_pg = NULL; |
| static scf_propertygroup_t *ud_cur_depts_pg = NULL; |
| static scf_propertygroup_t *ud_run_dpts_pg = NULL; |
| static int ud_run_dpts_pg_set = 0; |
| static scf_property_t *ud_prop = NULL; |
| static scf_property_t *ud_dpt_prop = NULL; |
| static scf_value_t *ud_val = NULL; |
| static scf_iter_t *ud_iter = NULL, *ud_iter2 = NULL; |
| static scf_transaction_t *ud_tx = NULL; |
| static char *ud_ctarg = NULL; |
| static char *ud_oldtarg = NULL; |
| static char *ud_name = NULL; |
| |
| /* export globals */ |
| static scf_instance_t *exp_inst; |
| static scf_propertygroup_t *exp_pg; |
| static scf_property_t *exp_prop; |
| static scf_value_t *exp_val; |
| static scf_iter_t *exp_inst_iter, *exp_pg_iter, *exp_prop_iter, *exp_val_iter; |
| static char *exp_str; |
| static size_t exp_str_sz; |
| |
| /* cleanup globals */ |
| static uu_avl_pool_t *service_manifest_pool = NULL; |
| static uu_avl_t *service_manifest_tree = NULL; |
| |
| static void scfdie_lineno(int lineno) __NORETURN; |
| |
| static char *start_method_names[] = { |
| "start", |
| "inetd_start", |
| NULL |
| }; |
| |
| static struct uri_scheme { |
| const char *scheme; |
| const char *protocol; |
| } uri_scheme[] = { |
| { "mailto", "smtp" }, |
| { "snmp", "snmp" }, |
| { "syslog", "syslog" }, |
| { NULL, NULL } |
| }; |
| #define URI_SCHEME_NUM ((sizeof (uri_scheme) / \ |
| sizeof (struct uri_scheme)) - 1) |
| |
| static int |
| check_uri_scheme(const char *scheme) |
| { |
| int i; |
| |
| for (i = 0; uri_scheme[i].scheme != NULL; ++i) { |
| if (strcmp(scheme, uri_scheme[i].scheme) == 0) |
| return (i); |
| } |
| |
| return (-1); |
| } |
| |
| static int |
| check_uri_protocol(const char *p) |
| { |
| int i; |
| |
| for (i = 0; uri_scheme[i].protocol != NULL; ++i) { |
| if (strcmp(p, uri_scheme[i].protocol) == 0) |
| return (i); |
| } |
| |
| return (-1); |
| } |
| |
| /* |
| * For unexpected libscf errors. |
| */ |
| #ifdef NDEBUG |
| |
| static void scfdie(void) __NORETURN; |
| |
| static void |
| scfdie(void) |
| { |
| scf_error_t err = scf_error(); |
| |
| if (err == SCF_ERROR_CONNECTION_BROKEN) |
| uu_die(gettext("Repository connection broken. Exiting.\n")); |
| |
| uu_die(gettext("Unexpected fatal libscf error: %s. Exiting.\n"), |
| scf_strerror(err)); |
| } |
| |
| #else |
| |
| #define scfdie() scfdie_lineno(__LINE__) |
| |
| static void |
| scfdie_lineno(int lineno) |
| { |
| scf_error_t err = scf_error(); |
| |
| if (err == SCF_ERROR_CONNECTION_BROKEN) |
| uu_die(gettext("Repository connection broken. Exiting.\n")); |
| |
| uu_die(gettext("Unexpected libscf error on line %d of " __FILE__ |
| ": %s.\n"), lineno, scf_strerror(err)); |
| } |
| |
| #endif |
| |
| static void |
| scfwarn(void) |
| { |
| warn(gettext("Unexpected libscf error: %s.\n"), |
| scf_strerror(scf_error())); |
| } |
| |
| /* |
| * Clear a field of a structure. |
| */ |
| static int |
| clear_int(void *a, void *b) |
| { |
| /* LINTED */ |
| *(int *)((char *)a + (size_t)b) = 0; |
| |
| return (UU_WALK_NEXT); |
| } |
| |
| static int |
| scferror2errno(scf_error_t err) |
| { |
| switch (err) { |
| case SCF_ERROR_BACKEND_ACCESS: |
| return (EACCES); |
| |
| case SCF_ERROR_BACKEND_READONLY: |
| return (EROFS); |
| |
| case SCF_ERROR_CONNECTION_BROKEN: |
| return (ECONNABORTED); |
| |
| case SCF_ERROR_CONSTRAINT_VIOLATED: |
| case SCF_ERROR_INVALID_ARGUMENT: |
| return (EINVAL); |
| |
| case SCF_ERROR_DELETED: |
| return (ECANCELED); |
| |
| case SCF_ERROR_EXISTS: |
| return (EEXIST); |
| |
| case SCF_ERROR_NO_MEMORY: |
| return (ENOMEM); |
| |
| case SCF_ERROR_NO_RESOURCES: |
| return (ENOSPC); |
| |
| case SCF_ERROR_NOT_FOUND: |
| return (ENOENT); |
| |
| case SCF_ERROR_PERMISSION_DENIED: |
| return (EPERM); |
| |
| default: |
| #ifndef NDEBUG |
| (void) fprintf(stderr, "%s:%d: Unknown libscf error %d.\n", |
| __FILE__, __LINE__, err); |
| #else |
| (void) fprintf(stderr, "Unknown libscf error %d.\n", err); |
| #endif |
| abort(); |
| /* NOTREACHED */ |
| } |
| } |
| |
| static int |
| entity_get_pg(void *ent, int issvc, const char *name, |
| scf_propertygroup_t *pg) |
| { |
| if (issvc) |
| return (scf_service_get_pg(ent, name, pg)); |
| else |
| return (scf_instance_get_pg(ent, name, pg)); |
| } |
| |
| static void |
| entity_destroy(void *ent, int issvc) |
| { |
| if (issvc) |
| scf_service_destroy(ent); |
| else |
| 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. |
| * |
| * Returns |
| * 0 - success |
| * ECONNABORTED - repository connection broken |
| * ECANCELED - instance containing snap was deleted |
| * ENOENT - snap has no snaplevels |
| * - requested snaplevel not found |
| */ |
| static int |
| get_snaplevel(scf_snapshot_t *snap, int get_svc, scf_snaplevel_t *snpl) |
| { |
| if (scf_snapshot_get_base_snaplevel(snap, snpl) != 0) { |
| switch (scf_error()) { |
| case SCF_ERROR_CONNECTION_BROKEN: |
| case SCF_ERROR_DELETED: |
| case SCF_ERROR_NOT_FOUND: |
| return (scferror2errno(scf_error())); |
| |
| case SCF_ERROR_HANDLE_MISMATCH: |
| case SCF_ERROR_NOT_BOUND: |
| case SCF_ERROR_NOT_SET: |
| default: |
| bad_error("scf_snapshot_get_base_snaplevel", |
| scf_error()); |
| } |
| } |
| |
| for (;;) { |
| ssize_t ssz; |
| |
| ssz = scf_snaplevel_get_instance_name(snpl, NULL, 0); |
| if (ssz >= 0) { |
| if (!get_svc) |
| return (0); |
| } else { |
| switch (scf_error()) { |
| case SCF_ERROR_CONSTRAINT_VIOLATED: |
| if (get_svc) |
| return (0); |
| break; |
| |
| case SCF_ERROR_DELETED: |
| case SCF_ERROR_CONNECTION_BROKEN: |
| return (scferror2errno(scf_error())); |
| |
| case SCF_ERROR_NOT_SET: |
| case SCF_ERROR_NOT_BOUND: |
| default: |
| bad_error("scf_snaplevel_get_instance_name", |
| scf_error()); |
| } |
| } |
| |
| if (scf_snaplevel_get_next_snaplevel(snpl, snpl) != 0) { |
| switch (scf_error()) { |
| case SCF_ERROR_NOT_FOUND: |
| case SCF_ERROR_CONNECTION_BROKEN: |
| case SCF_ERROR_DELETED: |
| return (scferror2errno(scf_error())); |
| |
| case SCF_ERROR_HANDLE_MISMATCH: |
| case SCF_ERROR_NOT_BOUND: |
| case SCF_ERROR_NOT_SET: |
| case SCF_ERROR_INVALID_ARGUMENT: |
| default: |
| bad_error("scf_snaplevel_get_next_snaplevel", |
| scf_error()); |
| } |
| } |
| } |
| } |
| |
| /* |
| * If issvc is 0, take ent to be a pointer to an scf_instance_t. If it has |
| * a running snapshot, and that snapshot has an instance snaplevel, set pg to |
| * the property group named name in it. If it doesn't have a running |
| * snapshot, set pg to the instance's current property group named name. |
| * |
| * If issvc is nonzero, take ent to be a pointer to an scf_service_t, and walk |
| * its instances. If one has a running snapshot with a service snaplevel, set |
| * pg to the property group named name in it. If no such snaplevel could be |
| * found, set pg to the service's current property group named name. |
| * |
| * iter, inst, snap, and snpl are required scratch objects. |
| * |
| * Returns |
| * 0 - success |
| * ECONNABORTED - repository connection broken |
| * ECANCELED - ent was deleted |
| * ENOENT - no such property group |
| * EINVAL - name is an invalid property group name |
| * EBADF - found running snapshot is missing a snaplevel |
| */ |
| static int |
| entity_get_running_pg(void *ent, int issvc, const char *name, |
| scf_propertygroup_t *pg, scf_iter_t *iter, scf_instance_t *inst, |
| scf_snapshot_t *snap, scf_snaplevel_t *snpl) |
| { |
| int r; |
| |
| if (issvc) { |
| /* Search for an instance with a running snapshot. */ |
| if (scf_iter_service_instances(iter, ent) != 0) { |
| switch (scf_error()) { |
| case SCF_ERROR_DELETED: |
| case SCF_ERROR_CONNECTION_BROKEN: |
| return (scferror2errno(scf_error())); |
| |
| case SCF_ERROR_NOT_SET: |
| case SCF_ERROR_NOT_BOUND: |
| case SCF_ERROR_HANDLE_MISMATCH: |
| default: |
| bad_error("scf_iter_service_instances", |
| scf_error()); |
| } |
| } |
| |
| for (;;) { |
| r = scf_iter_next_instance(iter, inst); |
| if (r == 0) { |
| if (scf_service_get_pg(ent, name, pg) == 0) |
| return (0); |
| |
| switch (scf_error()) { |
| case SCF_ERROR_DELETED: |
| case SCF_ERROR_NOT_FOUND: |
| case SCF_ERROR_INVALID_ARGUMENT: |
| case SCF_ERROR_CONNECTION_BROKEN: |
| return (scferror2errno(scf_error())); |
| |
| case SCF_ERROR_NOT_BOUND: |
| case SCF_ERROR_HANDLE_MISMATCH: |
| case SCF_ERROR_NOT_SET: |
| default: |
| bad_error("scf_service_get_pg", |
| scf_error()); |
| } |
| } |
| if (r != 1) { |
| switch (scf_error()) { |
| case SCF_ERROR_DELETED: |
| case SCF_ERROR_CONNECTION_BROKEN: |
| return (scferror2errno(scf_error())); |
| |
| case SCF_ERROR_INVALID_ARGUMENT: |
| case SCF_ERROR_NOT_SET: |
| case SCF_ERROR_NOT_BOUND: |
| case SCF_ERROR_HANDLE_MISMATCH: |
| default: |
| bad_error("scf_iter_next_instance", |
| scf_error()); |
| } |
| } |
| |
| if (scf_instance_get_snapshot(inst, snap_running, |
| snap) == 0) |
| break; |
| |
| switch (scf_error()) { |
| case SCF_ERROR_NOT_FOUND: |
| case SCF_ERROR_DELETED: |
| continue; |
| |
| case SCF_ERROR_CONNECTION_BROKEN: |
| return (ECONNABORTED); |
| |
| case SCF_ERROR_HANDLE_MISMATCH: |
| case SCF_ERROR_INVALID_ARGUMENT: |
| case SCF_ERROR_NOT_SET: |
| case SCF_ERROR_NOT_BOUND: |
| default: |
| bad_error("scf_instance_get_snapshot", |
| scf_error()); |
| } |
| } |
| } else { |
| if (scf_instance_get_snapshot(ent, snap_running, snap) != 0) { |
| switch (scf_error()) { |
| case SCF_ERROR_NOT_FOUND: |
| break; |
| |
| case SCF_ERROR_DELETED: |
| case SCF_ERROR_CONNECTION_BROKEN: |
| return (scferror2errno(scf_error())); |
| |
| case SCF_ERROR_NOT_BOUND: |
| case SCF_ERROR_HANDLE_MISMATCH: |
| case SCF_ERROR_INVALID_ARGUMENT: |
| case SCF_ERROR_NOT_SET: |
| default: |
| bad_error("scf_instance_get_snapshot", |
| scf_error()); |
| } |
| |
| if (scf_instance_get_pg(ent, name, pg) == 0) |
| return (0); |
| |
| switch (scf_error()) { |
| case SCF_ERROR_DELETED: |
| case SCF_ERROR_NOT_FOUND: |
| case SCF_ERROR_INVALID_ARGUMENT: |
| case SCF_ERROR_CONNECTION_BROKEN: |
| return (scferror2errno(scf_error())); |
| |
| case SCF_ERROR_NOT_BOUND: |
| case SCF_ERROR_HANDLE_MISMATCH: |
| case SCF_ERROR_NOT_SET: |
| default: |
| bad_error("scf_instance_get_pg", scf_error()); |
| } |
| } |
| } |
| |
| r = get_snaplevel(snap, issvc, snpl); |
| switch (r) { |
| case 0: |
| break; |
| |
| case ECONNABORTED: |
| case ECANCELED: |
| return (r); |
| |
| case ENOENT: |
| return (EBADF); |
| |
| default: |
| bad_error("get_snaplevel", r); |
| } |
| |
| if (scf_snaplevel_get_pg(snpl, name, pg) == 0) |
| return (0); |
| |
| switch (scf_error()) { |
| case SCF_ERROR_DELETED: |
| case SCF_ERROR_INVALID_ARGUMENT: |
| case SCF_ERROR_CONNECTION_BROKEN: |
| case SCF_ERROR_NOT_FOUND: |
| return (scferror2errno(scf_error())); |
| |
| case SCF_ERROR_NOT_BOUND: |
| case SCF_ERROR_HANDLE_MISMATCH: |
| case SCF_ERROR_NOT_SET: |
| default: |
| bad_error("scf_snaplevel_get_pg", scf_error()); |
| /* NOTREACHED */ |
| } |
| } |
| |
| /* |
| * To be registered with atexit(). |
| */ |
| static void |
| remove_tempfile(void) |
| { |
| int ret; |
| |
| if (tempfile != NULL) { |
| if (fclose(tempfile) == EOF) |
| (void) warn(gettext("Could not close temporary file")); |
| tempfile = NULL; |
| } |
| |
| if (tempfilename[0] != '\0') { |
| do { |
| ret = remove(tempfilename); |
| } while (ret == -1 && errno == EINTR); |
| if (ret == -1) |
| warn(gettext("Could not remove temporary file")); |
| tempfilename[0] = '\0'; |
| } |
| } |
| |
| /* |
| * Launch private svc.configd(1M) for manipulating alternate repositories. |
| */ |
| static void |
| start_private_repository(engine_state_t *est) |
| { |
| int fd, stat; |
| struct door_info info; |
| pid_t pid; |
| |
| /* |
| * 1. Create a temporary file for the door. |
| */ |
| if (est->sc_repo_doorname != NULL) |
| free((void *)est->sc_repo_doorname); |
| |
| est->sc_repo_doorname = tempnam(est->sc_repo_doordir, "scfdr"); |
| if (est->sc_repo_doorname == NULL) |
| uu_die(gettext("Could not acquire temporary filename")); |
| |
| fd = open(est->sc_repo_doorname, O_CREAT | O_EXCL | O_RDWR, 0600); |
| if (fd < 0) |
| uu_die(gettext("Could not create temporary file for " |
| "repository server")); |
| |
| (void) close(fd); |
| |
| /* |
| * 2. Launch a configd with that door, using the specified |
| * repository. |
| */ |
| if ((est->sc_repo_pid = fork()) == 0) { |
| (void) execlp(est->sc_repo_server, est->sc_repo_server, "-p", |
| "-d", est->sc_repo_doorname, "-r", est->sc_repo_filename, |
| NULL); |
| uu_die(gettext("Could not execute %s"), est->sc_repo_server); |
| } else if (est->sc_repo_pid == -1) |
| uu_die(gettext("Attempt to fork failed")); |
| |
| do { |
| pid = waitpid(est->sc_repo_pid, &stat, 0); |
| } while (pid == -1 && errno == EINTR); |
| |
| if (pid == -1) |
| uu_die(gettext("Could not waitpid() for repository server")); |
| |
| if (!WIFEXITED(stat)) { |
| uu_die(gettext("Repository server failed (status %d).\n"), |
| stat); |
| } else if (WEXITSTATUS(stat) != 0) { |
| uu_die(gettext("Repository server failed (exit %d).\n"), |
| WEXITSTATUS(stat)); |
| } |
| |
| /* |
| * See if it was successful by checking if the door is a door. |
| */ |
| |
| fd = open(est->sc_repo_doorname, O_RDWR); |
| if (fd < 0) |
| uu_die(gettext("Could not open door \"%s\""), |
| est->sc_repo_doorname); |
| |
| if (door_info(fd, &info) < 0) |
| uu_die(gettext("Unexpected door_info() error")); |
| |
| if (close(fd) == -1) |
| warn(gettext("Could not close repository door"), |
| strerror(errno)); |
| |
| est->sc_repo_pid = info.di_target; |
| } |
| |
| void |
| lscf_cleanup(void) |
| { |
| /* |
| * In the case where we've launched a private svc.configd(1M) |
| * instance, we must terminate our child and remove the temporary |
| * rendezvous point. |
| */ |
| if (est->sc_repo_pid > 0) { |
| (void) kill(est->sc_repo_pid, SIGTERM); |
| (void) waitpid(est->sc_repo_pid, NULL, 0); |
| (void) unlink(est->sc_repo_doorname); |
| |
| est->sc_repo_pid = 0; |
| } |
| } |
| |
| void |
| unselect_cursnap(void) |
| { |
| void *cookie; |
| |
| cur_level = NULL; |
| |
| cookie = NULL; |
| while ((cur_elt = uu_list_teardown(cur_levels, &cookie)) != NULL) { |
| scf_snaplevel_destroy(cur_elt->sl); |
| free(cur_elt); |
| } |
| |
| scf_snapshot_destroy(cur_snap); |
| cur_snap = NULL; |
| } |
| |
| void |
| lscf_prep_hndl(void) |
| { |
| if (g_hndl != NULL) |
| return; |
| |
| g_hndl = scf_handle_create(SCF_VERSION); |
| if (g_hndl == NULL) |
| scfdie(); |
| |
| if (est->sc_repo_filename != NULL) |
| start_private_repository(est); |
| |
| if (est->sc_repo_doorname != NULL) { |
| scf_value_t *repo_value; |
| int ret; |
| |
| repo_value = scf_value_create(g_hndl); |
| if (repo_value == NULL) |
| scfdie(); |
| |
| ret = scf_value_set_astring(repo_value, est->sc_repo_doorname); |
| assert(ret == SCF_SUCCESS); |
| |
| if (scf_handle_decorate(g_hndl, "door_path", repo_value) != |
| SCF_SUCCESS) |
| scfdie(); |
| |
| scf_value_destroy(repo_value); |
| } |
| |
| if (scf_handle_bind(g_hndl) != 0) |
| uu_die(gettext("Could not connect to repository server: %s.\n"), |
| scf_strerror(scf_error())); |
| |
| cur_scope = scf_scope_create(g_hndl); |
| if (cur_scope == NULL) |
| scfdie(); |
| |
| if (scf_handle_get_local_scope(g_hndl, cur_scope) != 0) |
| scfdie(); |
| } |
| |
| static void |
| repository_teardown(void) |
| { |
| if (g_hndl != NULL) { |
| if (cur_snap != NULL) |
| unselect_cursnap(); |
| scf_instance_destroy(cur_inst); |
| scf_service_destroy(cur_svc); |
| scf_scope_destroy(cur_scope); |
| scf_handle_destroy(g_hndl); |
| cur_inst = NULL; |
| cur_svc = NULL; |
| cur_scope = NULL; |
| g_hndl = NULL; |
| lscf_cleanup(); |
| } |
| } |
| |
| void |
| lscf_set_repository(const char *repfile, int force) |
| { |
| repository_teardown(); |
| |
| if (est->sc_repo_filename != NULL) { |
| free((void *)est->sc_repo_filename); |
| est->sc_repo_filename = NULL; |
| } |
| |
| if ((force == 0) && (access(repfile, R_OK) != 0)) { |
| /* |
| * Repository file does not exist |
| * or has no read permission. |
| */ |
| warn(gettext("Cannot access \"%s\": %s\n"), |
| repfile, strerror(errno)); |
| } else { |
| est->sc_repo_filename = safe_strdup(repfile); |
| } |
| |
| lscf_prep_hndl(); |
| } |
| |
| void |
| lscf_init() |
| { |
| if ((max_scf_fmri_len = scf_limit(SCF_LIMIT_MAX_FMRI_LENGTH)) < 0 || |
| (max_scf_name_len = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH)) < 0 || |
| (max_scf_pg_type_len = scf_limit(SCF_LIMIT_MAX_PG_TYPE_LENGTH)) < |
| 0 || |
| (max_scf_value_len = scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH)) < 0) |
| scfdie(); |
| |
| max_scf_len = max_scf_fmri_len; |
| if (max_scf_name_len > max_scf_len) |
| max_scf_len = max_scf_name_len; |
| if (max_scf_pg_type_len > max_scf_len) |
| max_scf_len = max_scf_pg_type_len; |
| /* |
| * When a value of type opaque is represented as a string, the |
| * string contains 2 characters for every byte of data. That is |
| * because the string contains the hex representation of the opaque |
| * value. |
| */ |
| if (2 * max_scf_value_len > max_scf_len) |
| max_scf_len = 2 * max_scf_value_len; |
| |
| if (atexit(remove_tempfile) != 0) |
| uu_die(gettext("Could not register atexit() function")); |
| |
| emsg_entity_not_selected = gettext("An entity is not selected.\n"); |
| emsg_permission_denied = gettext("Permission denied.\n"); |
| emsg_create_xml = gettext("Could not create XML node.\n"); |
| emsg_cant_modify_snapshots = gettext("Cannot modify snapshots.\n"); |
| emsg_invalid_for_snapshot = |
| gettext("Invalid operation on a snapshot.\n"); |
| emsg_read_only = gettext("Backend read-only.\n"); |
| emsg_deleted = gettext("Current selection has been deleted.\n"); |
| emsg_invalid_pg_name = |
| gettext("Invalid property group name \"%s\".\n"); |
| emsg_invalid_prop_name = gettext("Invalid property name \"%s\".\n"); |
| emsg_no_such_pg = gettext("No such property group \"%s\".\n"); |
| emsg_fmri_invalid_pg_name = gettext("Service %s has property group " |
| "with invalid name \"%s\".\n"); |
| emsg_fmri_invalid_pg_name_type = gettext("Service %s has property " |
| "group with invalid name \"%s\" or type \"%s\".\n"); |
| emsg_pg_added = gettext("%s changed unexpectedly " |
| "(property group \"%s\" added).\n"); |
| emsg_pg_changed = gettext("%s changed unexpectedly " |
| "(property group \"%s\" changed).\n"); |
| emsg_pg_deleted = gettext("%s changed unexpectedly " |
| "(property group \"%s\" or an ancestor was deleted).\n"); |
| emsg_pg_mod_perm = gettext("Could not modify property group \"%s\" " |
| "in %s (permission denied).\n"); |
| emsg_pg_add_perm = gettext("Could not create property group \"%s\" " |
| "in %s (permission denied).\n"); |
| emsg_pg_del_perm = gettext("Could not delete property group \"%s\" " |
| "in %s (permission denied).\n"); |
| emsg_snap_perm = gettext("Could not take \"%s\" snapshot of %s " |
| "(permission denied).\n"); |
| emsg_dpt_dangling = gettext("Conflict upgrading %s (not importing " |
| "new dependent \"%s\" because it already exists). Warning: The " |
| "current dependent's target (%s) does not exist.\n"); |
| emsg_dpt_no_dep = gettext("Conflict upgrading %s (not importing new " |
| "dependent \"%s\" because it already exists). Warning: The " |
| "current dependent's target (%s) does not have a dependency named " |
| "\"%s\" as expected.\n"); |
| |
| string_pool = uu_list_pool_create("strings", sizeof (string_list_t), |
| offsetof(string_list_t, node), NULL, 0); |
| snaplevel_pool = uu_list_pool_create("snaplevels", |
| sizeof (struct snaplevel), offsetof(struct snaplevel, list_node), |
| NULL, 0); |
| } |
| |
| |
| static const char * |
| prop_to_typestr(const scf_property_t *prop) |
| { |
| scf_type_t ty; |
| |
| if (scf_property_type(prop, &ty) != SCF_SUCCESS) |
| scfdie(); |
| |
| return (scf_type_to_string(ty)); |
| } |
| |
| static scf_type_t |
| string_to_type(const char *type) |
| { |
| size_t len = strlen(type); |
| char *buf; |
| |
| if (len == 0 || type[len - 1] != ':') |
| return (SCF_TYPE_INVALID); |
| |
| buf = (char *)alloca(len + 1); |
| (void) strlcpy(buf, type, len + 1); |
| buf[len - 1] = 0; |
| |
| return (scf_string_to_type(buf)); |
| } |
| |
| static scf_value_t * |
| string_to_value(const char *str, scf_type_t ty, boolean_t require_quotes) |
| { |
| scf_value_t *v; |
| char *dup, *nstr; |
| size_t len; |
| |
| v = scf_value_create(g_hndl); |
| if (v == NULL) |
| scfdie(); |
| |
| len = strlen(str); |
| if (require_quotes && |
| (len < 2 || str[0] != '\"' || str[len - 1] != '\"')) { |
| semerr(gettext("Multiple string values or string values " |
| "with spaces must be quoted with '\"'.\n")); |
| scf_value_destroy(v); |
| return (NULL); |
| } |
| |
| nstr = dup = safe_strdup(str); |
| if (dup[0] == '\"') { |
| /* |
| * Strip out the first and the last quote. |
| */ |
| dup[len - 1] = '\0'; |
| nstr = dup + 1; |
| } |
| |
| if (scf_value_set_from_string(v, ty, (const char *)nstr) != 0) { |
| assert(scf_error() == SCF_ERROR_INVALID_ARGUMENT); |
| semerr(gettext("Invalid \"%s\" value \"%s\".\n"), |
| scf_type_to_string(ty), nstr); |
| scf_value_destroy(v); |
| v = NULL; |
| } |
| free(dup); |
| return (v); |
| } |
| |
| /* |
| * Print str to strm, quoting double-quotes and backslashes with backslashes. |
| * Optionally append a comment prefix ('#') to newlines ('\n'). |
| */ |
| static int |
| quote_and_print(const char *str, FILE *strm, int commentnl) |
| { |
| const char *cp; |
| |
| for (cp = str; *cp != '\0'; ++cp) { |
| if (*cp == '"' || *cp == '\\') |
| (void) putc('\\', strm); |
| |
| (void) putc(*cp, strm); |
| |
| if (commentnl && *cp == '\n') { |
| (void) putc('#', strm); |
| } |
| } |
| |
| return (ferror(strm)); |
| } |
| |
| /* |
| * These wrappers around lowlevel functions provide consistent error checking |
| * and warnings. |
| */ |
| static int |
| pg_get_prop(scf_propertygroup_t *pg, const char *propname, scf_property_t *prop) |
| { |
| if (scf_pg_get_property(pg, propname, prop) == SCF_SUCCESS) |
| return (0); |
| |
| if (scf_error() != SCF_ERROR_NOT_FOUND) |
| scfdie(); |
| |
| if (g_verbose) { |
| ssize_t len; |
| char *fmri; |
| |
| len = scf_pg_to_fmri(pg, NULL, 0); |
| if (len < 0) |
| scfdie(); |
| |
| fmri = safe_malloc(len + 1); |
| |
| if (scf_pg_to_fmri(pg, fmri, len + 1) < 0) |
| scfdie(); |
| |
| warn(gettext("Expected property %s of property group %s is " |
| "missing.\n"), propname, fmri); |
| |
| free(fmri); |
| } |
| |
| return (-1); |
| } |
| |
| static int |
| prop_check_type(scf_property_t *prop, scf_type_t ty) |
| { |
| scf_type_t pty; |
| |
| if (scf_property_type(prop, &pty) != SCF_SUCCESS) |
| scfdie(); |
| |
| if (ty == pty) |
| return (0); |
| |
| if (g_verbose) { |
| ssize_t len; |
| char *fmri; |
| const char *tystr; |
| |
| len = scf_property_to_fmri(prop, NULL, 0); |
| if (len < 0) |
| scfdie(); |
| |
| fmri = safe_malloc(len + 1); |
| |
| if (scf_property_to_fmri(prop, fmri, len + 1) < 0) |
| scfdie(); |
| |
| tystr = scf_type_to_string(ty); |
| if (tystr == NULL) |
| tystr = "?"; |
| |
| warn(gettext("Property %s is not of expected type %s.\n"), |
| fmri, tystr); |
| |
| free(fmri); |
| } |
| |
| return (-1); |
| } |
| |
| static int |
| prop_get_val(scf_property_t *prop, scf_value_t *val) |
| { |
| scf_error_t err; |
| |
| if (scf_property_get_value(prop, val) == SCF_SUCCESS) |
| return (0); |
| |
| err = scf_error(); |
| |
| if (err != SCF_ERROR_NOT_FOUND && |
| err != SCF_ERROR_CONSTRAINT_VIOLATED && |
| err != SCF_ERROR_PERMISSION_DENIED) |
| scfdie(); |
| |
| if (g_verbose) { |
| ssize_t len; |
| char *fmri, *emsg; |
| |
| len = scf_property_to_fmri(prop, NULL, 0); |
| if (len < 0) |
| scfdie(); |
| |
| fmri = safe_malloc(len + 1); |
| |
| if (scf_property_to_fmri(prop, fmri, len + 1) < 0) |
| scfdie(); |
| |
| if (err == SCF_ERROR_NOT_FOUND) |
| emsg = gettext("Property %s has no values; expected " |
| "one.\n"); |
| else if (err == SCF_ERROR_CONSTRAINT_VIOLATED) |
| emsg = gettext("Property %s has multiple values; " |
| "expected one.\n"); |
| else |
| emsg = gettext("No permission to read property %s.\n"); |
| |
| warn(emsg, fmri); |
| |
| free(fmri); |
| } |
| |
| return (-1); |
| } |
| |
| |
| static boolean_t |
| snaplevel_is_instance(const scf_snaplevel_t *level) |
| { |
| if (scf_snaplevel_get_instance_name(level, NULL, 0) < 0) { |
| if (scf_error() != SCF_ERROR_CONSTRAINT_VIOLATED) |
| scfdie(); |
| return (0); |
| } else { |
| return (1); |
| } |
| } |
| |
| /* |
| * Decode FMRI into a service or instance, and put the result in *ep. If |
| * memory cannot be allocated, return SCF_ERROR_NO_MEMORY. If the FMRI is |
| * invalid, return SCF_ERROR_INVALID_ARGUMENT. If the FMRI does not specify |
| * an entity, return SCF_ERROR_CONSTRAINT_VIOLATED. If the entity cannot be |
| * found, return SCF_ERROR_NOT_FOUND. Otherwise return SCF_ERROR_NONE, point |
| * *ep to a valid scf_service_t or scf_instance_t, and set *isservice to |
| * whether *ep is a service. |
| */ |
| static scf_error_t |
| fmri_to_entity(scf_handle_t *h, const char *fmri, void **ep, int *isservice) |
| { |
| char *fmri_copy; |
| const char *sstr, *istr, *pgstr; |
| scf_service_t *svc; |
| scf_instance_t *inst; |
| |
| fmri_copy = strdup(fmri); |
| if (fmri_copy == NULL) |
| return (SCF_ERROR_NO_MEMORY); |
| |
| if (scf_parse_svc_fmri(fmri_copy, NULL, &sstr, &istr, &pgstr, NULL) != |
| SCF_SUCCESS) { |
| free(fmri_copy); |
| return (SCF_ERROR_INVALID_ARGUMENT); |
| } |
| |
| free(fmri_copy); |
| |
| if (sstr == NULL || pgstr != NULL) |
| return (SCF_ERROR_CONSTRAINT_VIOLATED); |
| |
| if (istr == NULL) { |
| svc = scf_service_create(h); |
| if (svc == NULL) |
| return (SCF_ERROR_NO_MEMORY); |
| |
| if (scf_handle_decode_fmri(h, fmri, NULL, svc, NULL, NULL, NULL, |
| SCF_DECODE_FMRI_EXACT) != SCF_SUCCESS) { |
| if (scf_error() != SCF_ERROR_NOT_FOUND) |
| scfdie(); |
| |
| return (SCF_ERROR_NOT_FOUND); |
| } |
| |
| *ep = svc; |
| *isservice = 1; |
| } else { |
| inst = scf_instance_create(h); |
| if (inst == NULL) |
| return (SCF_ERROR_NO_MEMORY); |
| |
| if (scf_handle_decode_fmri(h, fmri, NULL, NULL, inst, NULL, |
| NULL, SCF_DECODE_FMRI_EXACT) != SCF_SUCCESS) { |
| if (scf_error() != SCF_ERROR_NOT_FOUND) |
| scfdie(); |
| |
| return (SCF_ERROR_NOT_FOUND); |
| } |
| |
| *ep = inst; |
| *isservice = 0; |
| } |
| |
| return (SCF_ERROR_NONE); |
| } |
| |
| /* |
| * Create the entity named by fmri. Place a pointer to its libscf handle in |
| * *ep, and set or clear *isservicep if it is a service or an instance. |
| * Returns |
| * SCF_ERROR_NONE - success |
| * SCF_ERROR_NO_MEMORY - scf_*_create() failed |
| * SCF_ERROR_INVALID_ARGUMENT - fmri is invalid |
| * SCF_ERROR_CONSTRAINT_VIOLATED - fmri is not a service or instance |
| * SCF_ERROR_NOT_FOUND - no such scope |
| * SCF_ERROR_PERMISSION_DENIED |
| * SCF_ERROR_BACKEND_READONLY |
| * SCF_ERROR_BACKEND_ACCESS |
| */ |
| static scf_error_t |
| create_entity(scf_handle_t *h, const char *fmri, void **ep, int *isservicep) |
| { |
| char *fmri_copy; |
| const char *scstr, *sstr, *istr, *pgstr; |
| scf_scope_t *scope = NULL; |
| scf_service_t *svc = NULL; |
| scf_instance_t *inst = NULL; |
| scf_error_t scfe; |
| |
| fmri_copy = safe_strdup(fmri); |
| |
| if (scf_parse_svc_fmri(fmri_copy, &scstr, &sstr, &istr, &pgstr, NULL) != |
| 0) { |
| free(fmri_copy); |
| return (SCF_ERROR_INVALID_ARGUMENT); |
| } |
| |
| if (scstr == NULL || sstr == NULL || pgstr != NULL) { |
| free(fmri_copy); |
| return (SCF_ERROR_CONSTRAINT_VIOLATED); |
| } |
| |
| *ep = NULL; |
| |
| if ((scope = scf_scope_create(h)) == NULL || |
| (svc = scf_service_create(h)) == NULL || |
| (inst = scf_instance_create(h)) == NULL) { |
| scfe = SCF_ERROR_NO_MEMORY; |
| goto out; |
| } |
| |
| get_scope: |
| if (scf_handle_get_scope(h, scstr, scope) != 0) { |
| switch (scf_error()) { |
| case SCF_ERROR_CONNECTION_BROKEN: |
| scfdie(); |
| /* NOTREACHED */ |
| |
| case SCF_ERROR_NOT_FOUND: |
| scfe = SCF_ERROR_NOT_FOUND; |
| goto out; |
| |
| case SCF_ERROR_HANDLE_MISMATCH: |
| case SCF_ERROR_NOT_BOUND: |
| case SCF_ERROR_INVALID_ARGUMENT: |
| default: |
| bad_error("scf_handle_get_scope", scf_error()); |
| } |
| } |
| |
| get_svc: |
| if (scf_scope_get_service(scope, sstr, svc) != 0) { |
| switch (scf_error()) { |
| case SCF_ERROR_CONNECTION_BROKEN: |
| scfdie(); |
| /* NOTREACHED */ |
| |
| case SCF_ERROR_DELETED: |
| goto get_scope; |
| |
| case SCF_ERROR_NOT_FOUND: |
| break; |
| |
| case SCF_ERROR_HANDLE_MISMATCH: |
| case SCF_ERROR_INVALID_ARGUMENT: |
| case SCF_ERROR_NOT_BOUND: |
| case SCF_ERROR_NOT_SET: |
| default: |
| bad_error("scf_scope_get_service", scf_error()); |
| } |
| |
| if (scf_scope_add_service(scope, sstr, svc) != 0) { |
| switch (scf_error()) { |
| case SCF_ERROR_CONNECTION_BROKEN: |
| scfdie(); |
| /* NOTREACHED */ |
| |
| case SCF_ERROR_DELETED: |
| goto get_scope; |
| |
| case SCF_ERROR_PERMISSION_DENIED: |
| case SCF_ERROR_BACKEND_READONLY: |
| case SCF_ERROR_BACKEND_ACCESS: |
| scfe = scf_error(); |
| goto out; |
| |
| case SCF_ERROR_HANDLE_MISMATCH: |
| case SCF_ERROR_INVALID_ARGUMENT: |
| case SCF_ERROR_NOT_BOUND: |
| case SCF_ERROR_NOT_SET: |
| default: |
| bad_error("scf_scope_get_service", scf_error()); |
| } |
| } |
| } |
| |
| if (istr == NULL) { |
| scfe = SCF_ERROR_NONE; |
| *ep = svc; |
| *isservicep = 1; |
| goto out; |
| } |
| |
| get_inst: |
| if (scf_service_get_instance(svc, istr, inst) != 0) { |
| switch (scf_error()) { |
| case SCF_ERROR_CONNECTION_BROKEN: |
| scfdie(); |
| /* NOTREACHED */ |
| |
| case SCF_ERROR_DELETED: |
| goto get_svc; |
| |
| case SCF_ERROR_NOT_FOUND: |
| break; |
| |
| case SCF_ERROR_HANDLE_MISMATCH: |
| case SCF_ERROR_INVALID_ARGUMENT: |
| case SCF_ERROR_NOT_BOUND: |
| case SCF_ERROR_NOT_SET: |
| default: |
| bad_error("scf_service_get_instance", scf_error()); |
| } |
| |
| if (scf_service_add_instance(svc, istr, inst) != 0) { |
| switch (scf_error()) { |
| case SCF_ERROR_CONNECTION_BROKEN: |
| scfdie(); |
| /* NOTREACHED */ |
| |
| case SCF_ERROR_DELETED: |
| goto get_svc; |
| |
| case SCF_ERROR_PERMISSION_DENIED: |
| case SCF_ERROR_BACKEND_READONLY: |
| case SCF_ERROR_BACKEND_ACCESS: |
| scfe = scf_error(); |
| goto out; |
| |
| case SCF_ERROR_HANDLE_MISMATCH: |
| case SCF_ERROR_INVALID_ARGUMENT: |
| case SCF_ERROR_NOT_BOUND: |
| case SCF_ERROR_NOT_SET: |
| default: |
| bad_error("scf_service_add_instance", |
| scf_error()); |
| } |
| } |
| } |
| |
| scfe = SCF_ERROR_NONE; |
| *ep = inst; |
| *isservicep = 0; |
| |
| out: |
| if (*ep != inst) |
| scf_instance_destroy(inst); |
| if (*ep != svc) |
| scf_service_destroy(svc); |
| scf_scope_destroy(scope); |
| free(fmri_copy); |
| return (scfe); |
| } |
| |
| /* |
| * Create or update a snapshot of inst. snap is a required scratch object. |
| * |
| * Returns |
| * 0 - success |
| * ECONNABORTED - repository connection broken |
| * EPERM - permission denied |
| * ENOSPC - configd is out of resources |
| * ECANCELED - inst was deleted |
| * -1 - unknown libscf error (message printed) |
| */ |
| static int |
| take_snap(scf_instance_t *inst, const char *name, scf_snapshot_t *snap) |
| { |
| again: |
| if (scf_instance_get_snapshot(inst, name, snap) == 0) { |
| if (_scf_snapshot_take_attach(inst, snap) != 0) { |
| switch (scf_error()) { |
| case SCF_ERROR_CONNECTION_BROKEN: |
| case SCF_ERROR_PERMISSION_DENIED: |
| case SCF_ERROR_NO_RESOURCES: |
| return (scferror2errno(scf_error())); |
| |
| case SCF_ERROR_NOT_SET: |
| case SCF_ERROR_INVALID_ARGUMENT: |
| default: |
| bad_error("_scf_snapshot_take_attach", |
| scf_error()); |
| } |
| } |
| } else { |
| switch (scf_error()) { |
| case SCF_ERROR_NOT_FOUND: |
| break; |
| |
| case SCF_ERROR_DELETED: |
| case SCF_ERROR_CONNECTION_BROKEN: |
| return (scferror2errno(scf_error())); |
| |
| case SCF_ERROR_HANDLE_MISMATCH: |
| case SCF_ERROR_NOT_BOUND: |
| case SCF_ERROR_INVALID_ARGUMENT: |
| case SCF_ERROR_NOT_SET: |
| default: |
| bad_error("scf_instance_get_snapshot", scf_error()); |
| } |
| |
| if (_scf_snapshot_take_new(inst, name, snap) != 0) { |
| switch (scf_error()) { |
| case SCF_ERROR_EXISTS: |
| goto again; |
| |
| case SCF_ERROR_CONNECTION_BROKEN: |
| case SCF_ERROR_NO_RESOURCES: |
| case SCF_ERROR_PERMISSION_DENIED: |
| return (scferror2errno(scf_error())); |
| |
| default: |
| scfwarn(); |
| return (-1); |
| |
| case SCF_ERROR_NOT_SET: |
| case SCF_ERROR_INTERNAL: |
| case SCF_ERROR_INVALID_ARGUMENT: |
| case SCF_ERROR_HANDLE_MISMATCH: |
| bad_error("_scf_snapshot_take_new", |
| scf_error()); |
| } |
| } |
| } |
| |
| return (0); |
| } |
| |
| static int |
| refresh_running_snapshot(void *entity) |
| { |
| scf_snapshot_t *snap; |
| int r; |
| |
| if ((snap = scf_snapshot_create(g_hndl)) == NULL) |
| scfdie(); |
| r = take_snap(entity, snap_running, snap); |
| scf_snapshot_destroy(snap); |
| |
| return (r); |
| } |
| |
| /* |
| * Refresh entity. If isservice is zero, take entity to be an scf_instance_t *. |
| * Otherwise take entity to be an scf_service_t * and refresh all of its child |
| * instances. fmri is used for messages. inst, iter, and name_buf are used |
| * for scratch space. Returns |
| * 0 - success |
| * ECONNABORTED - repository connection broken |
| * ECANCELED - entity was deleted |
| * EACCES - backend denied access |
| * EPERM - permission denied |
| * ENOSPC - repository server out of resources |
| * -1 - _smf_refresh_instance_i() failed. scf_error() should be set. |
| */ |
| static int |
| refresh_entity(int isservice, void *entity, const char *fmri, |
| scf_instance_t *inst, scf_iter_t *iter, char *name_buf) |
| { |
| scf_error_t scfe; |
| int r; |
| |
| if (!isservice) { |
| /* |
| * Let restarter handles refreshing and making new running |
| * snapshot only if operating on a live repository and not |
| * running in early import. |
| */ |
| if (est->sc_repo_filename == NULL && |
| est->sc_repo_doorname == NULL && |
| est->sc_in_emi == 0) { |
| if (_smf_refresh_instance_i(entity) == 0) { |
| if (g_verbose) |
| warn(gettext("Refreshed %s.\n"), fmri); |
| return (0); |
| } |
| |
| switch (scf_error()) { |
| case SCF_ERROR_BACKEND_ACCESS: |
| return (EACCES); |
| |
| case SCF_ERROR_PERMISSION_DENIED: |
| return (EPERM); |
| |
| default: |
| return (-1); |
| } |
| } else { |
| r = refresh_running_snapshot(entity); |
| switch (r) { |
| case 0: |
| break; |
| |
| case ECONNABORTED: |
| case ECANCELED: |
| case EPERM: |
| case ENOSPC: |
| break; |
| |
| default: |
| bad_error("refresh_running_snapshot", |
| scf_error()); |
| } |
| |
| return (r); |
| } |
| } |
| |
| if (scf_iter_service_instances(iter, entity) != 0) { |
| switch (scf_error()) { |
| case SCF_ERROR_CONNECTION_BROKEN: |
| return (ECONNABORTED); |
| |
| case SCF_ERROR_DELETED: |
| return (ECANCELED); |
| |
| case SCF_ERROR_HANDLE_MISMATCH: |
| case SCF_ERROR_NOT_BOUND: |
| case SCF_ERROR_NOT_SET: |
| default: |
| bad_error("scf_iter_service_instances", scf_error()); |
| } |
| } |
| |
| for (;;) { |
| r = scf_iter_next_instance(iter, inst); |
| if (r == 0) |
| break; |
| if (r != 1) { |
| switch (scf_error()) { |
| case SCF_ERROR_CONNECTION_BROKEN: |
| return (ECONNABORTED); |
| |
| case SCF_ERROR_DELETED: |
| return (ECANCELED); |
| |
| case SCF_ERROR_HANDLE_MISMATCH: |
| case SCF_ERROR_NOT_BOUND: |
| case SCF_ERROR_NOT_SET: |
| case SCF_ERROR_INVALID_ARGUMENT: |
| default: |
| bad_error("scf_iter_next_instance", |
| scf_error()); |
| } |
| } |
| |
| /* |
| * Similarly, just take a new running snapshot if operating on |
| * a non-live repository or running during early import. |
| */ |
| if (est->sc_repo_filename != NULL || |
| est->sc_repo_doorname != NULL || |
| est->sc_in_emi == 1) { |
| r = refresh_running_snapshot(inst); |
| switch (r) { |
| case 0: |
| continue; |
| |
| case ECONNABORTED: |
| case ECANCELED: |
| case EPERM: |
| case ENOSPC: |
| break; |
| default: |
| bad_error("refresh_running_snapshot", |
| scf_error()); |
| } |
| |
| return (r); |
| |
| } |
| |
| if (_smf_refresh_instance_i(inst) == 0) { |
| if (g_verbose) { |
| if (scf_instance_get_name(inst, name_buf, |
| max_scf_name_len + 1) < 0) |
| (void) strcpy(name_buf, "?"); |
| |
| warn(gettext("Refreshed %s:%s.\n"), |
| fmri, name_buf); |
| } |
| } else { |
| if (scf_error() != SCF_ERROR_BACKEND_ACCESS || |
| g_verbose) { |
| scfe = scf_error(); |
| |
| if (scf_instance_to_fmri(inst, name_buf, |
| max_scf_name_len + 1) < 0) |
| (void) strcpy(name_buf, "?"); |
| |
| warn(gettext( |
| "Refresh of %s:%s failed: %s.\n"), fmri, |
| name_buf, scf_strerror(scfe)); |
| } |
| } |
| } |
| |
| return (0); |
| } |
| |
| static void |
| private_refresh(void) |
| { |
| scf_instance_t *pinst = NULL; |
| scf_iter_t *piter = NULL; |
| ssize_t fmrilen; |
| size_t bufsz; |
| char *fmribuf; |
| void *ent; |
| int issvc; |
| int r; |
| |
| if (est->sc_repo_filename == NULL && est->sc_repo_doorname == NULL) |
| return; |
| |
| assert(cur_svc != NULL); |
| |
| bufsz = max_scf_fmri_len + 1; |
| fmribuf = safe_malloc(bufsz); |
| if (cur_inst) { |
| issvc = 0; |
| ent = cur_inst; |
| fmrilen = scf_instance_to_fmri(ent, fmribuf, bufsz); |
| } else { |
| issvc = 1; |
| ent = cur_svc; |
| fmrilen = scf_service_to_fmri(ent, fmribuf, bufsz); |
| if ((pinst = scf_instance_create(g_hndl)) == NULL) |
| scfdie(); |
| |
| if ((piter = scf_iter_create(g_hndl)) == NULL) |
| scfdie(); |
| } |
| if (fmrilen < 0) { |
| free(fmribuf); |
| if (scf_error() != SCF_ERROR_DELETED) |
| scfdie(); |
| |
| warn(emsg_deleted); |
| return; |
| } |
| assert(fmrilen < bufsz); |
| |
| r = refresh_entity(issvc, ent, fmribuf, pinst, piter, NULL); |
| switch (r) { |
| case 0: |
| break; |
| |
| case ECONNABORTED: |
| warn(gettext("Could not refresh %s " |
| "(repository connection broken).\n"), fmribuf); |
| break; |
| |
| case ECANCELED: |
| warn(emsg_deleted); |
| break; |
| |
| case EPERM: |
| warn(gettext("Could not refresh %s " |
| "(permission denied).\n"), fmribuf); |
| break; |
| |
| case ENOSPC: |
| warn(gettext("Could not refresh %s " |
| "(repository server out of resources).\n"), |
| fmribuf); |
| break; |
| |
| case EACCES: |
| default: |
| bad_error("refresh_entity", scf_error()); |
| } |
| |
| if (issvc) { |
| scf_instance_destroy(pinst); |
| scf_iter_destroy(piter); |
| } |
| |
| free(fmribuf); |
| } |
| |
| |
| static int |
| stash_scferror_err(scf_callback_t *cbp, scf_error_t err) |
| { |
| cbp->sc_err = scferror2errno(err); |
| return (UU_WALK_ERROR); |
| } |
| |
| static int |
| stash_scferror(scf_callback_t *cbp) |
| { |
| return (stash_scferror_err(cbp, scf_error())); |
| } |
| |
| static int select_inst(const char *); |
| static int select_svc(const char *); |
| |
| /* |
| * Take a property that does not have a type and check to see if a type |
| * exists or can be gleened from the current data. Set the type. |
| * |
| * Check the current level (instance) and then check the higher level |
| * (service). This could be the case for adding a new property to |
| * the instance that's going to "override" a service level property. |
| * |
| * For a property : |
| * 1. Take the type from an existing property |
| * 2. Take the type from a template entry |
| * |
| * If the type can not be found, then leave the type as is, and let the import |
| * report the problem of the missing type. |
| */ |
| static int |
| find_current_prop_type(void *p, void *g) |
| { |
| property_t *prop = p; |
| scf_callback_t *lcb = g; |
| pgroup_t *pg = NULL; |
| |
| const char *fmri = NULL; |
| char *lfmri = NULL; |
| char *cur_selection = NULL; |
| |
| scf_propertygroup_t *sc_pg = NULL; |
| scf_property_t *sc_prop = NULL; |
| scf_pg_tmpl_t *t_pg = NULL; |
| scf_prop_tmpl_t *t_prop = NULL; |
| scf_type_t prop_type; |
| |
| value_t *vp; |
| int issvc = lcb->sc_service; |
| int r = UU_WALK_ERROR; |
| |
| if (prop->sc_value_type != SCF_TYPE_INVALID) |
| return (UU_WALK_NEXT); |
| |
| t_prop = scf_tmpl_prop_create(g_hndl); |
| sc_prop = scf_property_create(g_hndl); |
| if (sc_prop == NULL || t_prop == NULL) { |
| warn(gettext("Unable to create the property to attempt and " |
| "find a missing type.\n")); |
| |
| scf_property_destroy(sc_prop); |
| scf_tmpl_prop_destroy(t_prop); |
| |
| return (UU_WALK_ERROR); |
| } |
| |
| if (lcb->sc_flags == 1) { |
| pg = lcb->sc_parent; |
| issvc = (pg->sc_parent->sc_etype == SVCCFG_SERVICE_OBJECT); |
| fmri = pg->sc_parent->sc_fmri; |
| retry_pg: |
| if (cur_svc && cur_selection == NULL) { |
| cur_selection = safe_malloc(max_scf_fmri_len + 1); |
| lscf_get_selection_str(cur_selection, |
| max_scf_fmri_len + 1); |
| |
| if (strcmp(cur_selection, fmri) != 0) { |
| lscf_select(fmri); |
| } else { |
| free(cur_selection); |
| cur_selection = NULL; |
| } |
| } else { |
| lscf_select(fmri); |
| } |
| |
| if (sc_pg == NULL && (sc_pg = scf_pg_create(g_hndl)) == NULL) { |
| warn(gettext("Unable to create property group to " |
| "find a missing property type.\n")); |
| |
| goto out; |
| } |
| |
| if (get_pg(pg->sc_pgroup_name, sc_pg) != SCF_SUCCESS) { |
| /* |
| * If this is the sc_pg from the parent |
| * let the caller clean up the sc_pg, |
| * and just throw it away in this case. |
| */ |
| if (sc_pg != lcb->sc_parent) |
| scf_pg_destroy(sc_pg); |
| |
| sc_pg = NULL; |
| if ((t_pg = scf_tmpl_pg_create(g_hndl)) == NULL) { |
| warn(gettext("Unable to create template " |
| "property group to find a property " |
| "type.\n")); |
| |
| goto out; |
| } |
| |
| if (scf_tmpl_get_by_pg_name(fmri, NULL, |
| pg->sc_pgroup_name, NULL, t_pg, |
| SCF_PG_TMPL_FLAG_EXACT) != SCF_SUCCESS) { |
| /* |
| * if instance get service and jump back |
| */ |
| scf_tmpl_pg_destroy(t_pg); |
| t_pg = NULL; |
| if (issvc == 0) { |
| entity_t *e = pg->sc_parent->sc_parent; |
| |
| fmri = e->sc_fmri; |
| issvc = 1; |
| goto retry_pg; |
| } else { |
| goto out; |
| } |
| } |
| } |
| } else { |
| sc_pg = lcb->sc_parent; |
| } |
| |
| /* |
| * Attempt to get the type from an existing property. If the property |
| * cannot be found then attempt to get the type from a template entry |
| * for the property. |
| * |
| * Finally, if at the instance level look at the service level. |
| */ |
| if (sc_pg != NULL && |
| pg_get_prop(sc_pg, prop->sc_property_name, |
| sc_prop) == SCF_SUCCESS && |
| scf_property_type(sc_prop, &prop_type) == SCF_SUCCESS) { |
| prop->sc_value_type = prop_type; |
| |
| /* |
| * Found a type, update the value types and validate |
| * the actual value against this type. |
| */ |
| for (vp = uu_list_first(prop->sc_property_values); |
| vp != NULL; |
| vp = uu_list_next(prop->sc_property_values, vp)) { |
| vp->sc_type = prop->sc_value_type; |
| lxml_store_value(vp, 0, NULL); |
| } |
| |
| r = UU_WALK_NEXT; |
| goto out; |
| } |
| |
| /* |
| * If we get here with t_pg set to NULL then we had to have |
| * gotten an sc_pg but that sc_pg did not have the property |
| * we are looking for. So if the t_pg is not null look up |
| * the template entry for the property. |
| * |
| * If the t_pg is null then need to attempt to get a matching |
| * template entry for the sc_pg, and see if there is a property |
| * entry for that template entry. |
| */ |
| do_tmpl : |
| if (t_pg != NULL && |
| scf_tmpl_get_by_prop(t_pg, prop->sc_property_name, |
| t_prop, 0) == SCF_SUCCESS) { |
| if (scf_tmpl_prop_type(t_prop, &prop_type) == SCF_SUCCESS) { |
| prop->sc_value_type = prop_type; |
| |
| /* |
| * Found a type, update the value types and validate |
| * the actual value against this type. |
| */ |
| for (vp = uu_list_first(prop->sc_property_values); |
| vp != NULL; |
| vp = uu_list_next(prop->sc_property_values, vp)) { |
| vp->sc_type = prop->sc_value_type; |
| lxml_store_value(vp, 0, NULL); |
| } |
| |
| r = UU_WALK_NEXT; |
| goto out; |
| } |
| } else { |
| if (t_pg == NULL && sc_pg) { |
| if ((t_pg = scf_tmpl_pg_create(g_hndl)) == NULL) { |
| warn(gettext("Unable to create template " |
| "property group to find a property " |
| "type.\n")); |
| |
| goto out; |
| } |
| |
| if (scf_tmpl_get_by_pg(sc_pg, t_pg, 0) != SCF_SUCCESS) { |
| scf_tmpl_pg_destroy(t_pg); |
| t_pg = NULL; |
| } else { |
| goto do_tmpl; |
| } |
| } |
| } |
| |
| if (issvc == 0) { |
| scf_instance_t *i; |
| scf_service_t *s; |
| |
| issvc = 1; |
| if (lcb->sc_flags == 1) { |
| entity_t *e = pg->sc_parent->sc_parent; |
| |
| fmri = e->sc_fmri; |
| goto retry_pg; |
| } |
| |
| /* |
| * because lcb->sc_flags was not set then this means |
| * the pg was not used and can be used here. |
| */ |
| if ((pg = internal_pgroup_new()) == NULL) { |
| warn(gettext("Could not create internal property group " |
| "to find a missing type.")); |
| |
| goto out; |
| } |
| |
| pg->sc_pgroup_name = safe_malloc(max_scf_name_len + 1); |
| if (scf_pg_get_name(sc_pg, (char *)pg->sc_pgroup_name, |
| max_scf_name_len + 1) < 0) |
| goto out; |
| |
| i = scf_instance_create(g_hndl); |
| s = scf_service_create(g_hndl); |
| if (i == NULL || s == NULL || |
| scf_pg_get_parent_instance(sc_pg, i) != SCF_SUCCESS) { |
| warn(gettext("Could not get a service for the instance " |
| "to find a missing type.")); |
| |
| goto out; |
| } |
| |
| /* |
| * Check to see truly at the instance level. |
| */ |
| lfmri = safe_malloc(max_scf_fmri_len + 1); |
| if (scf_instance_get_parent(i, s) == SCF_SUCCESS && |
| scf_service_to_fmri(s, lfmri, max_scf_fmri_len + 1) < 0) |
| goto out; |
| else |
| fmri = (const char *)lfmri; |
| |
| goto retry_pg; |
| } |
| |
| out : |
| if (sc_pg != lcb->sc_parent) { |
| scf_pg_destroy(sc_pg); |
| } |
| |
| /* |
| * If this is true then the pg was allocated |
| * here, and the name was set so need to free |
| * the name and the pg. |
| */ |
| if (pg != NULL && pg != lcb->sc_parent) { |
| free((char *)pg->sc_pgroup_name); |
| internal_pgroup_free(pg); |
| } |
| |
| if (cur_selection) { |
| lscf_select(cur_selection); |
| free(cur_selection); |
| } |
| |
| scf_tmpl_pg_destroy(t_pg); |
| scf_tmpl_prop_destroy(t_prop); |
| scf_property_destroy(sc_prop); |
| |
| if (r != UU_WALK_NEXT) |
| warn(gettext("Could not find property type for \"%s\" " |
| "from \"%s\"\n"), prop->sc_property_name, |
| fmri != NULL ? fmri : lcb->sc_source_fmri); |
| |
| free(lfmri); |
| |
| return (r); |
| } |
| |
| /* |
| * Take a property group that does not have a type and check to see if a type |
| * exists or can be gleened from the current data. Set the type. |
| * |
| * Check the current level (instance) and then check the higher level |
| * (service). This could be the case for adding a new property to |
| * the instance that's going to "override" a service level property. |
| * |
| * For a property group |
| * 1. Take the type from an existing property group |
| * 2. Take the type from a template entry |
| * |
| * If the type can not be found, then leave the type as is, and let the import |
| * report the problem of the missing type. |
| */ |
| static int |
| find_current_pg_type(void *p, void *sori) |
| { |
| entity_t *si = sori; |
| pgroup_t *pg = p; |
| |
| const char *ofmri, *fmri; |
| char *cur_selection = NULL; |
| char *pg_type = NULL; |
| |
| scf_propertygroup_t *sc_pg = NULL; |
| scf_pg_tmpl_t *t_pg = NULL; |
| |
| int issvc = (si->sc_etype == SVCCFG_SERVICE_OBJECT); |
| int r = UU_WALK_ERROR; |
| |
| ofmri = fmri = si->sc_fmri; |
| if (pg->sc_pgroup_type != NULL) { |
| r = UU_WALK_NEXT; |
| |
| goto out; |
| } |
| |
| sc_pg = scf_pg_create(g_hndl); |
| if (sc_pg == NULL) { |
| warn(gettext("Unable to create property group to attempt " |
| "and find a missing type.\n")); |
| |
| return (UU_WALK_ERROR); |
| } |
| |
| /* |
| * Using get_pg() requires that the cur_svc/cur_inst be |
| * via lscf_select. Need to preserve the current selection |
| * if going to use lscf_select() to set up the cur_svc/cur_inst |
| */ |
| if (cur_svc) { |
| cur_selection = safe_malloc(max_scf_fmri_len + 1); |
| lscf_get_selection_str(cur_selection, max_scf_fmri_len + 1); |
| } |
| |
| /* |
| * If the property group exists get the type, and set |
| * the pgroup_t type of that type. |
| * |
| * If not the check for a template pg_pattern entry |
| * and take the type from that. |
| */ |
| retry_svc: |
| lscf_select(fmri); |
| |
| if (get_pg(pg->sc_pgroup_name, sc_pg) == SCF_SUCCESS) { |
| pg_type = safe_malloc(max_scf_pg_type_len + 1); |
| if (pg_type != NULL && scf_pg_get_type(sc_pg, pg_type, |
| max_scf_pg_type_len + 1) != -1) { |
| pg->sc_pgroup_type = pg_type; |
| |
| r = UU_WALK_NEXT; |
| goto out; |
| } else { |
| free(pg_type); |
| } |
| } else { |
| if ((t_pg == NULL) && |
| (t_pg = scf_tmpl_pg_create(g_hndl)) == NULL) |
| goto out; |
| |
| if (scf_tmpl_get_by_pg_name(fmri, NULL, pg->sc_pgroup_name, |
| NULL, t_pg, SCF_PG_TMPL_FLAG_EXACT) == SCF_SUCCESS && |
| scf_tmpl_pg_type(t_pg, &pg_type) != -1) { |
| pg->sc_pgroup_type = pg_type; |
| |
| r = UU_WALK_NEXT; |
| goto out; |
| } |
| } |
| |
| /* |
| * If type is not found at the instance level then attempt to |
| * find the type at the service level. |
| */ |
| if (!issvc) { |
| si = si->sc_parent; |
| fmri = si->sc_fmri; |
| issvc = (si->sc_etype == SVCCFG_SERVICE_OBJECT); |
| goto retry_svc; |
| } |
| |
| out : |
| if (cur_selection) { |
| lscf_select(cur_selection); |
| free(cur_selection); |
| } |
| |
| /* |
| * Now walk the properties of the property group to make sure that |
| * all properties have the correct type and values are valid for |
| * those types. |
| */ |
| if (r == UU_WALK_NEXT) { |
| scf_callback_t cb; |
| |
| cb.sc_service = issvc; |
| cb.sc_source_fmri = ofmri; |
| if (sc_pg != NULL) { |
| cb.sc_parent = sc_pg; |
| cb.sc_flags = 0; |
| } else { |
| cb.sc_parent = pg; |
| cb.sc_flags = 1; |
| } |
| |
| if (uu_list_walk(pg->sc_pgroup_props, find_current_prop_type, |
| &cb, UU_DEFAULT) != 0) { |
| if (uu_error() != UU_ERROR_CALLBACK_FAILED) |
| bad_error("uu_list_walk", uu_error()); |
| |
| r = UU_WALK_ERROR; |
| } |
| } else { |
| warn(gettext("Could not find property group type for " |
| "\"%s\" from \"%s\"\n"), pg->sc_pgroup_name, fmri); |
| } |
| |
| scf_tmpl_pg_destroy(t_pg); |
| scf_pg_destroy(sc_pg); |
| |
| return (r); |
| } |
| |
| /* |
| * Import. These functions import a bundle into the repository. |
| */ |
| |
| /* |
| * Add a transaction entry to lcbdata->sc_trans for this property_t. Uses |
| * sc_handle, sc_trans, and sc_flags (SCI_NOENABLED) in lcbdata. On success, |
| * returns UU_WALK_NEXT. On error returns UU_WALK_ERROR and sets |
| * lcbdata->sc_err to |
| * ENOMEM - out of memory |
| * ECONNABORTED - repository connection broken |
| * ECANCELED - sc_trans's property group was deleted |
| * EINVAL - p's name is invalid (error printed) |
| * - p has an invalid value (error printed) |
| */ |
| static int |
| lscf_property_import(void *v, void *pvt) |
| { |
| property_t *p = v; |
| scf_callback_t *lcbdata = pvt; |
| value_t *vp; |
| scf_transaction_t *trans = lcbdata->sc_trans; |
| scf_transaction_entry_t *entr; |
| scf_value_t *val; |
| scf_type_t tp; |
| |
| if ((lcbdata->sc_flags & SCI_NOENABLED || |
| lcbdata->sc_flags & SCI_DELAYENABLE) && |
| strcmp(p->sc_property_name, SCF_PROPERTY_ENABLED) == 0) { |
| lcbdata->sc_enable = p; |
| return (UU_WALK_NEXT); |
| } |
| |
| entr = scf_entry_create(lcbdata->sc_handle); |
| if (entr == NULL) { |
| switch (scf_error()) { |
| case SCF_ERROR_NO_MEMORY: |
| return (stash_scferror(lcbdata)); |
| |
| case SCF_ERROR_INVALID_ARGUMENT: |
| default: |
| bad_error("scf_entry_create", scf_error()); |
| } |
| } |
| |
| tp = p->sc_value_type; |
| |
| if (scf_transaction_property_new(trans, entr, |
| p->sc_property_name, tp) != 0) { |
| switch (scf_error()) { |
| case SCF_ERROR_INVALID_ARGUMENT: |
| semerr(emsg_invalid_prop_name, p->sc_property_name); |
| scf_entry_destroy(entr); |
| return (stash_scferror(lcbdata)); |
| |
| case SCF_ERROR_EXISTS: |
| break; |
| |
| case SCF_ERROR_DELETED: |
| case SCF_ERROR_CONNECTION_BROKEN: |
| scf_entry_destroy(entr); |
| return (stash_scferror(lcbdata)); |
| |
| case SCF_ERROR_NOT_BOUND: |
| case SCF_ERROR_HANDLE_MISMATCH: |
| case SCF_ERROR_NOT_SET: |
| default: |
| bad_error("scf_transaction_property_new", scf_error()); |
| } |
| |
| if (scf_transaction_property_change_type(trans, entr, |
| p->sc_property_name, tp) != 0) { |
| switch (scf_error()) { |
| case SCF_ERROR_DELETED: |
| case SCF_ERROR_CONNECTION_BROKEN: |
| scf_entry_destroy(entr); |
| return (stash_scferror(lcbdata)); |
| |
| case SCF_ERROR_INVALID_ARGUMENT: |
| semerr(emsg_invalid_prop_name, |
| p->sc_property_name); |
| scf_entry_destroy(entr); |
| return (stash_scferror(lcbdata)); |
| |
| case SCF_ERROR_NOT_FOUND: |
| case SCF_ERROR_NOT_SET: |
| case SCF_ERROR_HANDLE_MISMATCH: |
| case SCF_ERROR_NOT_BOUND: |
| default: |
| bad_error( |
| "scf_transaction_property_change_type", |
| scf_error()); |
| } |
| } |
| } |
| |
| for (vp = uu_list_first(p->sc_property_values); |
| vp != NULL; |
| vp = uu_list_next(p->sc_property_values, vp)) { |
| val = scf_value_create(g_hndl); |
| if (val == NULL) { |
| switch (scf_error()) { |
| case SCF_ERROR_NO_MEMORY: |
| return (stash_scferror(lcbdata)); |
| |
| case SCF_ERROR_INVALID_ARGUMENT: |
| default: |
| bad_error("scf_value_create", scf_error()); |
| } |
| } |
| |
| switch (tp) { |
| case SCF_TYPE_BOOLEAN: |
| scf_value_set_boolean(val, vp->sc_u.sc_count); |
| break; |
| case SCF_TYPE_COUNT: |
| scf_value_set_count(val, vp->sc_u.sc_count); |
| break; |
| case SCF_TYPE_INTEGER: |
| scf_value_set_integer(val, vp->sc_u.sc_integer); |
| break; |
| default: |
| assert(vp->sc_u.sc_string != NULL); |
| if (scf_value_set_from_string(val, tp, |
| vp->sc_u.sc_string) != 0) { |
| if (scf_error() != SCF_ERROR_INVALID_ARGUMENT) |
| bad_error("scf_value_set_from_string", |
| scf_error()); |
| |
| warn(gettext("Value \"%s\" is not a valid " |
| "%s.\n"), vp->sc_u.sc_string, |
| scf_type_to_string(tp)); |
| scf_value_destroy(val); |
| return (stash_scferror(lcbdata)); |
| } |
| break; |
| } |
| |
| if (scf_entry_add_value(entr, val) != 0) |
| bad_error("scf_entry_add_value", scf_error()); |
| } |
| |
| return (UU_WALK_NEXT); |
| } |
| |
| /* |
| * Import a pgroup_t into the repository. Uses sc_handle, sc_parent, |
| * sc_service, sc_flags (SCI_GENERALLAST, SCI_FORCE, & SCI_KEEP), |
| * sc_source_fmri, and sc_target_fmri in lcbdata, and uses imp_pg and imp_tx. |
| * On success, returns UU_WALK_NEXT. On error returns UU_WALK_ERROR and sets |
| * lcbdata->sc_err to |
| * ECONNABORTED - repository connection broken |
| * ENOMEM - out of memory |
| * ENOSPC - svc.configd is out of resources |
| * ECANCELED - sc_parent was deleted |
| * EPERM - could not create property group (permission denied) (error printed) |
| * - could not modify property group (permission denied) (error printed) |
| * - could not delete property group (permission denied) (error printed) |
| * EROFS - could not create property group (repository is read-only) |
| * - could not delete property group (repository is read-only) |
| * EACCES - could not create property group (backend access denied) |
| * - could not delete property group (backend access denied) |
| * EEXIST - could not create property group (already exists) |
| * EINVAL - invalid property group name (error printed) |
| * - invalid property name (error printed) |
| * - invalid value (error printed) |
| * EBUSY - new property group deleted (error printed) |
| * - new property group changed (error printed) |
| * - property group added (error printed) |
| * - property group deleted (error printed) |
| */ |
| static int |
| entity_pgroup_import(void *v, void *pvt) |
| { |
| pgroup_t *p = v; |
| scf_callback_t cbdata; |
| scf_callback_t *lcbdata = pvt; |
| void *ent = lcbdata->sc_parent; |
| int issvc = lcbdata->sc_service; |
| int r; |
| |
| const char * const pg_changed = gettext("%s changed unexpectedly " |
| "(new property group \"%s\" changed).\n"); |
| |
| /* Never import deleted property groups. */ |
| if (p->sc_pgroup_delete) { |
| if ((lcbdata->sc_flags & SCI_OP_APPLY) == SCI_OP_APPLY && |
| entity_get_pg(ent, issvc, p->sc_pgroup_name, imp_pg) == 0) { |
| goto delete_pg; |
| } |
| return (UU_WALK_NEXT); |
| } |
| |
| if (!issvc && (lcbdata->sc_flags & SCI_GENERALLAST) && |
| strcmp(p->sc_pgroup_name, SCF_PG_GENERAL) == 0) { |
| lcbdata->sc_general = p; |
| return (UU_WALK_NEXT); |
| } |
| |
| add_pg: |
| if (issvc) |
| r = scf_service_add_pg(ent, p->sc_pgroup_name, |
| p->sc_pgroup_type, p->sc_pgroup_flags, imp_pg); |
| else |
| r = scf_instance_add_pg(ent, p->sc_pgroup_name, |
| p->sc_pgroup_type, p->sc_pgroup_flags, imp_pg); |
| if (r != 0) { |
| switch (scf_error()) { |
| case SCF_ERROR_DELETED: |
| case SCF_ERROR_CONNECTION_BROKEN: |
| case SCF_ERROR_BACKEND_READONLY: |
| case SCF_ERROR_BACKEND_ACCESS: |
| case SCF_ERROR_NO_RESOURCES: |
| return (stash_scferror(lcbdata)); |
| |
| case SCF_ERROR_EXISTS: |
| if (lcbdata->sc_flags & SCI_FORCE) |
| break; |
| return (stash_scferror(lcbdata)); |
| |
| case SCF_ERROR_INVALID_ARGUMENT: |
| warn(emsg_fmri_invalid_pg_name_type, |
| lcbdata->sc_source_fmri, |
| p->sc_pgroup_name, p->sc_pgroup_type); |
| return (stash_scferror(lcbdata)); |
| |
| case SCF_ERROR_PERMISSION_DENIED: |
| warn(emsg_pg_add_perm, p->sc_pgroup_name, |
| lcbdata->sc_target_fmri); |
| return (stash_scferror(lcbdata)); |
| |
| case SCF_ERROR_NOT_BOUND: |
| case SCF_ERROR_HANDLE_MISMATCH: |
| case SCF_ERROR_NOT_SET: |
| default: |
| bad_error("scf_service_add_pg", scf_error()); |
| } |
| |
| if (entity_get_pg(ent, issvc, p->sc_pgroup_name, imp_pg) != 0) { |
| switch (scf_error()) { |
| case SCF_ERROR_CONNECTION_BROKEN: |
| case SCF_ERROR_DELETED: |
| return (stash_scferror(lcbdata)); |
| |
| case SCF_ERROR_INVALID_ARGUMENT: |
| warn(emsg_fmri_invalid_pg_name, |
| lcbdata->sc_source_fmri, |
| p->sc_pgroup_name); |
| return (stash_scferror(lcbdata)); |
| |
| case SCF_ERROR_NOT_FOUND: |
| warn(emsg_pg_deleted, lcbdata->sc_target_fmri, |
| p->sc_pgroup_name); |
| lcbdata->sc_err = EBUSY; |
| return (UU_WALK_ERROR); |
| |
| case SCF_ERROR_NOT_BOUND: |
| case SCF_ERROR_HANDLE_MISMATCH: |
| case SCF_ERROR_NOT_SET: |
| default: |
| bad_error("entity_get_pg", scf_error()); |
| } |
| } |
| |
| if (lcbdata->sc_flags & SCI_KEEP) |
| goto props; |
| |
| delete_pg: |
| if (scf_pg_delete(imp_pg) != 0) { |
| switch (scf_error()) { |
| case SCF_ERROR_DELETED: |
| warn(emsg_pg_deleted, lcbdata->sc_target_fmri, |
| p->sc_pgroup_name); |
| lcbdata->sc_err = EBUSY; |
| return (UU_WALK_ERROR); |
| |
| case SCF_ERROR_PERMISSION_DENIED: |
| warn(emsg_pg_del_perm, p->sc_pgroup_name, |
| lcbdata->sc_target_fmri); |
| return (stash_scferror(lcbdata)); |
| |
| case SCF_ERROR_BACKEND_READONLY: |
| case SCF_ERROR_BACKEND_ACCESS: |
| case SCF_ERROR_CONNECTION_BROKEN: |
| return (stash_scferror(lcbdata)); |
| |
| case SCF_ERROR_NOT_SET: |
| default: |
| bad_error("scf_pg_delete", scf_error()); |
| } |
| } |
| |
| if (p->sc_pgroup_delete) |
| return (UU_WALK_NEXT); |
| |
| goto add_pg; |
| } |
| |
| props: |
| |
| /* |
| * Add properties to property group, if any. |
| */ |
| cbdata.sc_handle = lcbdata->sc_handle; |
| cbdata.sc_parent = imp_pg; |
| cbdata.sc_flags = lcbdata->sc_flags; |
| cbdata.sc_trans = imp_tx; |
| cbdata.sc_enable = NULL; |
| |
| if (scf_transaction_start(imp_tx, imp_pg) != 0) { |
| switch (scf_error()) { |
| case SCF_ERROR_BACKEND_ACCESS: |
| case SCF_ERROR_BACKEND_READONLY: |
| case SCF_ERROR_CONNECTION_BROKEN: |
| return (stash_scferror(lcbdata)); |
| |
| case SCF_ERROR_DELETED: |
| warn(pg_changed, lcbdata->sc_target_fmri, |
| p->sc_pgroup_name); |
| lcbdata->sc_err = EBUSY; |
| return (UU_WALK_ERROR); |
| |
| case SCF_ERROR_PERMISSION_DENIED: |
| warn(emsg_pg_mod_perm, p->sc_pgroup_name, |
| lcbdata->sc_target_fmri); |
| return (stash_scferror(lcbdata)); |
| |
| case SCF_ERROR_NOT_BOUND: |
| case SCF_ERROR_NOT_SET: |
| case SCF_ERROR_IN_USE: |
| case SCF_ERROR_HANDLE_MISMATCH: |
| default: |
| bad_error("scf_transaction_start", scf_error()); |
| } |
| } |
| |
| if (uu_list_walk(p->sc_pgroup_props, lscf_property_import, &cbdata, |
| UU_DEFAULT) != 0) { |
| if (uu_error() != UU_ERROR_CALLBACK_FAILED) |
| bad_error("uu_list_walk", uu_error()); |
| scf_transaction_reset(imp_tx); |
| |
| lcbdata->sc_err = cbdata.sc_err; |
| if (cbdata.sc_err == ECANCELED) { |
| warn(pg_changed, lcbdata->sc_target_fmri, |
| p->sc_pgroup_name); |
| lcbdata->sc_err = EBUSY; |
| } |
| return (UU_WALK_ERROR); |
| } |
| |
| if ((lcbdata->sc_flags & SCI_DELAYENABLE) && cbdata.sc_enable) { |
| cbdata.sc_flags = cbdata.sc_flags & (~SCI_DELAYENABLE); |
| |
| /* |
| * take the snapshot running snapshot then |
| * import the stored general/enable property |
| */ |
| r = take_snap(ent, snap_running, imp_rsnap); |
| switch (r) { |
| case 0: |
| break; |
| |
| case ECONNABORTED: |
| warn(gettext("Could not take %s snapshot on import " |
| "(repository connection broken).\n"), |
| snap_running); |
| lcbdata->sc_err = r; |
| return (UU_WALK_ERROR); |
| case ECANCELED: |
| warn(emsg_deleted); |
| lcbdata->sc_err = r; |
| return (UU_WALK_ERROR); |
| |
| case EPERM: |
| warn(gettext("Could not take %s snapshot " |
| "(permission denied).\n"), snap_running); |
| lcbdata->sc_err = r; |
| return (UU_WALK_ERROR); |
| |
| case ENOSPC: |
| warn(gettext("Could not take %s snapshot" |
| "(repository server out of resources).\n"), |
| snap_running); |
| lcbdata->sc_err = r; |
| return (UU_WALK_ERROR); |
| |
| default: |
| bad_error("take_snap", r); |
| } |
| |
| r = lscf_property_import(cbdata.sc_enable, &cbdata); |
| if (r != UU_WALK_NEXT) { |
| if (r != UU_WALK_ERROR) |
| bad_error("lscf_property_import", r); |
| return (EINVAL); |
| } |
| } |
| |
| r = scf_transaction_commit(imp_tx); |
| switch (r) { |
| case 1: |
| r = UU_WALK_NEXT; |
| break; |
| |
| case 0: |
| warn(pg_changed, lcbdata->sc_target_fmri, p->sc_pgroup_name); |
| lcbdata->sc_err = EBUSY; |
| r = UU_WALK_ERROR; |
| break; |
| |
| case -1: |
| switch (scf_error()) { |
| case SCF_ERROR_BACKEND_READONLY: |
| case SCF_ERROR_BACKEND_ACCESS: |
| case SCF_ERROR_CONNECTION_BROKEN: |
| case SCF_ERROR_NO_RESOURCES: |
| r = stash_scferror(lcbdata); |
| break; |
| |
| case SCF_ERROR_DELETED: |
| warn(emsg_pg_deleted, lcbdata->sc_target_fmri, |
| p->sc_pgroup_name); |
| lcbdata->sc_err = EBUSY; |
| r = UU_WALK_ERROR; |
| break; |
| |
| case SCF_ERROR_PERMISSION_DENIED: |
| warn(emsg_pg_mod_perm, p->sc_pgroup_name, |
| lcbdata->sc_target_fmri); |
| r = stash_scferror(lcbdata); |
| break; |
| |
| case SCF_ERROR_NOT_SET: |
| case SCF_ERROR_INVALID_ARGUMENT: |
| case SCF_ERROR_NOT_BOUND: |
| default: |
| bad_error("scf_transaction_commit", scf_error()); |
| } |
| break; |
| |
| default: |
| bad_error("scf_transaction_commit", r); |
| } |
| |
| scf_transaction_destroy_children(imp_tx); |
| |
| return (r); |
| } |
| |
| /* |
| * Returns |
| * 0 - success |
| * ECONNABORTED - repository connection broken |
| * ENOMEM - out of memory |
| * ENOSPC - svc.configd is out of resources |
| * ECANCELED - inst was deleted |
| * EPERM - could not create property group (permission denied) (error printed) |
| * - could not modify property group (permission denied) (error printed) |
| * EROFS - could not create property group (repository is read-only) |
| * EACCES - could not create property group (backend access denied) |
| * EEXIST - could not create property group (already exists) |
| * EINVAL - invalid property group name (error printed) |
| * - invalid property name (error printed) |
| * - invalid value (error printed) |
| * EBUSY - new property group changed (error printed) |
| */ |
| static int |
| lscf_import_service_pgs(scf_service_t *svc, const char *target_fmri, |
| const entity_t *isvc, int flags) |
| { |
| scf_callback_t cbdata; |
| |
| cbdata.sc_handle = scf_service_handle(svc); |
| cbdata.sc_parent = svc; |
| cbdata.sc_service = 1; |
| cbdata.sc_general = 0; |
| cbdata.sc_enable = 0; |
| cbdata.sc_flags = flags; |
| cbdata.sc_source_fmri = isvc->sc_fmri; |
| cbdata.sc_target_fmri = target_fmri; |
| |
| /* |
| * If the op is set, then add the flag to the callback |
| * flags for later use. |
| */ |
| if (isvc->sc_op != SVCCFG_OP_NONE) { |
| switch (isvc->sc_op) { |
| case SVCCFG_OP_IMPORT : |
| cbdata.sc_flags |= SCI_OP_IMPORT; |
| break; |
| case SVCCFG_OP_APPLY : |
| cbdata.sc_flags |= SCI_OP_APPLY; |
| break; |
| case SVCCFG_OP_RESTORE : |
| cbdata.sc_flags |= SCI_OP_RESTORE; |
| break; |
| default : |
| uu_die(gettext("lscf_import_service_pgs : " |
| "Unknown op stored in the service entity\n")); |
| |
| } |
| } |
| |
| if (uu_list_walk(isvc->sc_pgroups, entity_pgroup_import, &cbdata, |
| UU_DEFAULT) != 0) { |
| if (uu_error() != UU_ERROR_CALLBACK_FAILED) |
| bad_error("uu_list_walk", uu_error()); |
| |
| return (cbdata.sc_err); |
| } |
| |
| return (0); |
| } |
| |
| /* |
| * Returns |
| * 0 - success |
| * ECONNABORTED - repository connection broken |
| * ENOMEM - out of memory |
| * ENOSPC - svc.configd is out of resources |
| * ECANCELED - inst was deleted |
| * EPERM - could not create property group (permission denied) (error printed) |
| * - could not modify property group (permission denied) (error printed) |
| * EROFS - could not create property group (repository is read-only) |
| * EACCES - could not create property group (backend access denied) |
| * EEXIST - could not create property group (already exists) |
| * EINVAL - invalid property group name (error printed) |
| * - invalid property name (error printed) |
| * - invalid value (error printed) |
| * EBUSY - new property group changed (error printed) |
| */ |
| static int |
| lscf_import_instance_pgs(scf_instance_t *inst, const char *target_fmri, |
| const entity_t *iinst, int flags) |
| { |
| scf_callback_t cbdata; |
| |
| cbdata.sc_handle = scf_instance_handle(inst); |
| cbdata.sc_parent = inst; |
| cbdata.sc_service = 0; |
| cbdata.sc_general = NULL; |
| cbdata.sc_enable = NULL; |
| cbdata.sc_flags = flags; |
| cbdata.sc_source_fmri = iinst->sc_fmri; |
| cbdata.sc_target_fmri = target_fmri; |
| |
| /* |
| * If the op is set, then add the flag to the callback |
| * flags for later use. |
| */ |
| if (iinst->sc_op != SVCCFG_OP_NONE) { |
| switch (iinst->sc_op) { |
| case SVCCFG_OP_IMPORT : |
| cbdata.sc_flags |= SCI_OP_IMPORT; |
| break; |
| case SVCCFG_OP_APPLY : |
| cbdata.sc_flags |= SCI_OP_APPLY; |
| break; |
| case SVCCFG_OP_RESTORE : |
| cbdata.sc_flags |= SCI_OP_RESTORE; |
| break; |
| default : |
| uu_die(gettext("lscf_import_instance_pgs : " |
| "Unknown op stored in the instance entity\n")); |
| } |
| } |
| |
| if (uu_list_walk(iinst->sc_pgroups, entity_pgroup_import, &cbdata, |
| UU_DEFAULT) != 0) { |
| if (uu_error() != UU_ERROR_CALLBACK_FAILED) |
| bad_error("uu_list_walk", uu_error()); |
| |
| return (cbdata.sc_err); |
| } |
| |
| if ((flags & SCI_GENERALLAST) && cbdata.sc_general) { |
| cbdata.sc_flags = flags & (~SCI_GENERALLAST); |
| /* |
| * If importing with the SCI_NOENABLED flag then |
| * skip the delay, but if not then add the delay |
| * of the enable property. |
| */ |
| if (!(cbdata.sc_flags & SCI_NOENABLED)) { |
| cbdata.sc_flags |= SCI_DELAYENABLE; |
| } |
| |
| if (entity_pgroup_import(cbdata.sc_general, &cbdata) |
| != UU_WALK_NEXT) |
| return (cbdata.sc_err); |
| } |
| |
| return (0); |
| } |
| |
| /* |
| * Report the reasons why we can't upgrade pg2 to pg1. |
| */ |
| static void |
| report_pg_diffs(const pgroup_t *pg1, const pgroup_t *pg2, const char *fmri, |
| int new) |
| { |
| property_t *p1, *p2; |
| |
| assert(strcmp(pg1->sc_pgroup_name, pg2->sc_pgroup_name) == 0); |
| |
| if (!pg_attrs_equal(pg1, pg2, fmri, new)) |
| return; |
| |
| for (p1 = uu_list_first(pg1->sc_pgroup_props); |
| p1 != NULL; |
| p1 = uu_list_next(pg1->sc_pgroup_props, p1)) { |
| p2 = uu_list_find(pg2->sc_pgroup_props, p1, NULL, NULL); |
| if (p2 != NULL) { |
| (void) prop_equal(p1, p2, fmri, pg1->sc_pgroup_name, |
| new); |
| continue; |
| } |
| |
| if (new) |
| warn(gettext("Conflict upgrading %s (new property " |
| "group \"%s\" is missing property \"%s\").\n"), |
| fmri, pg1->sc_pgroup_name, p1->sc_property_name); |
| else |
| warn(gettext("Conflict upgrading %s (property " |
| "\"%s/%s\" is missing).\n"), fmri, |
| pg1->sc_pgroup_name, p1->sc_property_name); |
| } |
| |
| /* |
| * Since pg1 should be from the manifest, any properties in pg2 which |
| * aren't in pg1 shouldn't be reported as conflicts. |
| */ |
| } |
| |
| /* |
| * Add transaction entries to tx which will upgrade cur's pg according to old |
| * & new. |
| * |
| * Returns |
| * 0 - success |
| * EINVAL - new has a property with an invalid name or value (message emitted) |
| * ENOMEM - out of memory |
| |