/* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define walkcontext _walkcontext #include #define min(a, b) ((a) > (b) ? (b) : (a)) #define MKW_TRUE ":true" #define MKW_KILL ":kill" #define MKW_KILL_PROC ":kill_process" #define ALLOCFAIL ((char *)"Allocation failure.") #define RCBROKEN ((char *)"Repository connection broken.") #define MAX_COMMIT_RETRIES 20 #define MAX_COMMIT_RETRY_INT (5 * 1000000) /* 5 seconds */ #define INITIAL_COMMIT_RETRY_INT (10000) /* 1/100th second */ /* * bad_fail() catches bugs in this and lower layers by reporting supposedly * impossible function failures. The NDEBUG case keeps the strings out of the * library but still calls abort() so we can root-cause from the coredump. */ #ifndef NDEBUG #define bad_fail(func, err) { \ (void) fprintf(stderr, \ "At %s:%d, %s() failed with unexpected error %d. Aborting.\n", \ __FILE__, __LINE__, (func), (err)); \ abort(); \ } #else #define bad_fail(func, err) abort() #endif struct restarter_event_handle { char *reh_restarter_name; char *reh_delegate_channel_name; evchan_t *reh_delegate_channel; char *reh_delegate_subscriber_id; char *reh_master_channel_name; evchan_t *reh_master_channel; char *reh_master_subscriber_id; int (*reh_handler)(restarter_event_t *); }; struct restarter_event { sysevent_t *re_sysevent; restarter_event_type_t re_type; char *re_instance_name; restarter_event_handle_t *re_event_handle; restarter_instance_state_t re_state; restarter_instance_state_t re_next_state; }; static const char * const allocfail = "Allocation failure.\n"; static const char * const rcbroken = "Repository connection broken.\n"; static int method_context_safety = 0; /* Can safely call pools/projects. */ int ndebug = 1; static void free_restarter_event_handle(struct restarter_event_handle *h) { if (h == NULL) return; /* * Just free the memory -- don't unbind the sysevent handle, * as otherwise events may be lost if this is just a restarter * restart. */ if (h->reh_restarter_name != NULL) free(h->reh_restarter_name); if (h->reh_delegate_channel_name != NULL) free(h->reh_delegate_channel_name); if (h->reh_delegate_subscriber_id != NULL) free(h->reh_delegate_subscriber_id); if (h->reh_master_channel_name != NULL) free(h->reh_master_channel_name); if (h->reh_master_subscriber_id != NULL) free(h->reh_master_subscriber_id); free(h); } static const char * last_part(const char *fmri) { char *last_part; last_part = strrchr(fmri, '/'); last_part++; assert(last_part != NULL); return (last_part); } char * _restarter_get_channel_name(const char *fmri, int type) { const char *name; char *chan_name = malloc(MAX_CHNAME_LEN); char prefix_name[3]; if (chan_name == NULL) return (NULL); if (type == RESTARTER_CHANNEL_DELEGATE) (void) strcpy(prefix_name, "d_"); else if (type == RESTARTER_CHANNEL_MASTER) (void) strcpy(prefix_name, "m_"); else { free(chan_name); return (NULL); } name = last_part(fmri); /* * Should check for [a-z],[A-Z],[0-9],.,_,-,: */ if (snprintf(chan_name, MAX_CHNAME_LEN, "com.sun:scf:%s%s", prefix_name, name) > MAX_CHNAME_LEN) { free(chan_name); return (NULL); } return (chan_name); } int cb(sysevent_t *syse, void *cookie) { restarter_event_handle_t *h = (restarter_event_handle_t *)cookie; restarter_event_t *e; nvlist_t *attr_list = NULL; int ret = 0; e = uu_zalloc(sizeof (restarter_event_t)); if (e == NULL) uu_die(allocfail); e->re_event_handle = h; e->re_sysevent = syse; if (sysevent_get_attr_list(syse, &attr_list) != 0) uu_die(allocfail); if ((nvlist_lookup_uint32(attr_list, RESTARTER_NAME_TYPE, &(e->re_type)) != 0) || (nvlist_lookup_string(attr_list, RESTARTER_NAME_INSTANCE, &(e->re_instance_name)) != 0)) { uu_warn("%s: Can't decode nvlist for event %p\n", h->reh_restarter_name, (void *)syse); ret = 0; } else { ret = h->reh_handler(e); } uu_free(e); nvlist_free(attr_list); return (ret); } /* * restarter_bind_handle(uint32_t, char *, int (*)(restarter_event_t *), int, * restarter_event_handle_t **) * * Bind to a delegated restarter event channel. * Each delegated restarter gets its own channel for resource management. * * Returns 0 on success or * ENOTSUP version mismatch * EINVAL restarter_name or event_handle is NULL * ENOMEM out of memory, too many channels, or too many subscriptions * EBUSY sysevent_evc_bind() could not establish binding * EFAULT internal sysevent_evc_bind()/sysevent_evc_subscribe() error * EMFILE out of file descriptors * EPERM insufficient privilege for sysevent_evc_bind() * EEXIST already subscribed */ int restarter_bind_handle(uint32_t version, const char *restarter_name, int (*event_handler)(restarter_event_t *), int flags, restarter_event_handle_t **rehp) { restarter_event_handle_t *h; size_t sz; int err; if (version != RESTARTER_EVENT_VERSION) return (ENOTSUP); if (restarter_name == NULL || event_handler == NULL) return (EINVAL); if (flags & RESTARTER_FLAG_DEBUG) ndebug++; if ((h = uu_zalloc(sizeof (restarter_event_handle_t))) == NULL) return (ENOMEM); h->reh_delegate_subscriber_id = malloc(MAX_SUBID_LEN); h->reh_master_subscriber_id = malloc(MAX_SUBID_LEN); h->reh_restarter_name = strdup(restarter_name); if (h->reh_delegate_subscriber_id == NULL || h->reh_master_subscriber_id == NULL || h->reh_restarter_name == NULL) { free_restarter_event_handle(h); return (ENOMEM); } sz = strlcpy(h->reh_delegate_subscriber_id, "del", MAX_SUBID_LEN); assert(sz < MAX_SUBID_LEN); sz = strlcpy(h->reh_master_subscriber_id, "master", MAX_SUBID_LEN); assert(sz < MAX_SUBID_LEN); h->reh_delegate_channel_name = _restarter_get_channel_name(restarter_name, RESTARTER_CHANNEL_DELEGATE); h->reh_master_channel_name = _restarter_get_channel_name(restarter_name, RESTARTER_CHANNEL_MASTER); if (h->reh_delegate_channel_name == NULL || h->reh_master_channel_name == NULL) { free_restarter_event_handle(h); return (ENOMEM); } if (sysevent_evc_bind(h->reh_delegate_channel_name, &h->reh_delegate_channel, EVCH_CREAT|EVCH_HOLD_PEND) != 0) { err = errno; assert(err != EINVAL); assert(err != ENOENT); free_restarter_event_handle(h); return (err); } if (sysevent_evc_bind(h->reh_master_channel_name, &h->reh_master_channel, EVCH_CREAT|EVCH_HOLD_PEND) != 0) { err = errno; assert(err != EINVAL); assert(err != ENOENT); free_restarter_event_handle(h); return (err); } h->reh_handler = event_handler; assert(strlen(restarter_name) <= MAX_CLASS_LEN - 1); assert(strlen(h->reh_delegate_subscriber_id) <= MAX_SUBID_LEN - 1); assert(strlen(h->reh_master_subscriber_id) <= MAX_SUBID_LEN - 1); if (sysevent_evc_subscribe(h->reh_delegate_channel, h->reh_delegate_subscriber_id, EC_ALL, cb, h, EVCH_SUB_KEEP) != 0) { err = errno; assert(err != EINVAL); free_restarter_event_handle(h); return (err); } *rehp = h; return (0); } restarter_event_handle_t * restarter_event_get_handle(restarter_event_t *e) { assert(e != NULL && e->re_event_handle != NULL); return (e->re_event_handle); } restarter_event_type_t restarter_event_get_type(restarter_event_t *e) { assert(e != NULL); return (e->re_type); } ssize_t restarter_event_get_instance(restarter_event_t *e, char *inst, size_t sz) { assert(e != NULL && inst != NULL); return ((ssize_t)strlcpy(inst, e->re_instance_name, sz)); } int restarter_event_get_current_states(restarter_event_t *e, restarter_instance_state_t *state, restarter_instance_state_t *next_state) { if (e == NULL) return (-1); *state = e->re_state; *next_state = e->re_next_state; return (0); } /* * Commit the state, next state, and auxiliary state into the repository. * Let the graph engine know about the state change and error. On success, * return 0. On error, return * EINVAL - aux has spaces * - inst is invalid or not an instance FMRI * EPROTO - librestart compiled against different libscf * ENOMEM - out of memory * - repository server out of resources * ENOTACTIVE - repository server not running * ECONNABORTED - repository connection established, but then broken * - unknown libscf error * ENOENT - inst does not exist in the repository * EPERM - insufficient permissions * EACCESS - backend access denied * EROFS - backend is readonly * EFAULT - internal sysevent_evc_publish() error * EBADF - h is invalid (sysevent_evc_publish() returned EINVAL) */ int restarter_set_states(restarter_event_handle_t *h, const char *inst, restarter_instance_state_t cur_state, restarter_instance_state_t new_cur_state, restarter_instance_state_t next_state, restarter_instance_state_t new_next_state, restarter_error_t e, const char *aux) { nvlist_t *attr; scf_handle_t *scf_h; instance_data_t id; useconds_t retry_int = INITIAL_COMMIT_RETRY_INT; int retries; int ret = 0; char *p = (char *)aux; assert(h->reh_master_channel != NULL); assert(h->reh_master_channel_name != NULL); assert(h->reh_master_subscriber_id != NULL); /* Validate format of auxiliary state: no spaces allowed */ if (p != NULL) { while (*p != '\0') { if (isspace(*p)) return (EINVAL); p++; } } if ((scf_h = scf_handle_create(SCF_VERSION)) == NULL) { switch (scf_error()) { case SCF_ERROR_VERSION_MISMATCH: return (EPROTO); case SCF_ERROR_NO_MEMORY: return (ENOMEM); default: bad_fail("scf_handle_create", scf_error()); } } if (scf_handle_bind(scf_h) == -1) { scf_handle_destroy(scf_h); switch (scf_error()) { case SCF_ERROR_NO_SERVER: return (ENOTACTIVE); case SCF_ERROR_NO_RESOURCES: return (ENOMEM); case SCF_ERROR_INVALID_ARGUMENT: case SCF_ERROR_IN_USE: default: bad_fail("scf_handle_bind", scf_error()); } } if (nvlist_alloc(&attr, NV_UNIQUE_NAME, 0) != 0 || nvlist_add_int32(attr, RESTARTER_NAME_STATE, new_cur_state) != 0 || nvlist_add_int32(attr, RESTARTER_NAME_NEXT_STATE, new_next_state) != 0 || nvlist_add_int32(attr, RESTARTER_NAME_ERROR, e) != 0 || nvlist_add_string(attr, RESTARTER_NAME_INSTANCE, inst) != 0) { ret = ENOMEM; goto errout; } id.i_fmri = inst; id.i_state = cur_state; id.i_next_state = next_state; ret = _restarter_commit_states(scf_h, &id, new_cur_state, new_next_state, aux); if (ret != 0) goto errout; for (retries = 0; retries < MAX_COMMIT_RETRIES; retries++) { ret = sysevent_evc_publish(h->reh_master_channel, "master", "state_change", "com.sun", "librestart", attr, EVCH_NOSLEEP); if (ret == 0) break; switch (ret) { case EAGAIN: /* Queue is full */ (void) usleep(retry_int); retry_int = min(retry_int * 2, MAX_COMMIT_RETRY_INT); break; case EFAULT: case ENOMEM: goto errout; case EINVAL: ret = EBADF; goto errout; case EOVERFLOW: default: bad_fail("sysevent_evc_publish", ret); } } errout: nvlist_free(attr); (void) scf_handle_unbind(scf_h); scf_handle_destroy(scf_h); return (ret); } restarter_instance_state_t restarter_string_to_state(char *string) { assert(string != NULL); if (strcmp(string, SCF_STATE_STRING_NONE) == 0) return (RESTARTER_STATE_NONE); else if (strcmp(string, SCF_STATE_STRING_UNINIT) == 0) return (RESTARTER_STATE_UNINIT); else if (strcmp(string, SCF_STATE_STRING_MAINT) == 0) return (RESTARTER_STATE_MAINT); else if (strcmp(string, SCF_STATE_STRING_OFFLINE) == 0) return (RESTARTER_STATE_OFFLINE); else if (strcmp(string, SCF_STATE_STRING_DISABLED) == 0) return (RESTARTER_STATE_DISABLED); else if (strcmp(string, SCF_STATE_STRING_ONLINE) == 0) return (RESTARTER_STATE_ONLINE); else if (strcmp(string, SCF_STATE_STRING_DEGRADED) == 0) return (RESTARTER_STATE_DEGRADED); else { return (RESTARTER_STATE_NONE); } } ssize_t restarter_state_to_string(restarter_instance_state_t state, char *string, size_t len) { assert(string != NULL); if (state == RESTARTER_STATE_NONE) return ((ssize_t)strlcpy(string, SCF_STATE_STRING_NONE, len)); else if (state == RESTARTER_STATE_UNINIT) return ((ssize_t)strlcpy(string, SCF_STATE_STRING_UNINIT, len)); else if (state == RESTARTER_STATE_MAINT) return ((ssize_t)strlcpy(string, SCF_STATE_STRING_MAINT, len)); else if (state == RESTARTER_STATE_OFFLINE) return ((ssize_t)strlcpy(string, SCF_STATE_STRING_OFFLINE, len)); else if (state == RESTARTER_STATE_DISABLED) return ((ssize_t)strlcpy(string, SCF_STATE_STRING_DISABLED, len)); else if (state == RESTARTER_STATE_ONLINE) return ((ssize_t)strlcpy(string, SCF_STATE_STRING_ONLINE, len)); else if (state == RESTARTER_STATE_DEGRADED) return ((ssize_t)strlcpy(string, SCF_STATE_STRING_DEGRADED, len)); else return ((ssize_t)strlcpy(string, "unknown", len)); } /* * Sets pg to the name property group of s_inst. If it doesn't exist, it is * added. * * Fails with * ECONNABORTED - repository disconnection or unknown libscf error * EBADF - inst is not set * ECANCELED - inst is deleted * EPERM - permission is denied * EACCES - backend denied access * EROFS - backend readonly */ static int instance_get_or_add_pg(scf_instance_t *inst, const char *name, const char *type, uint32_t flags, scf_propertygroup_t *pg) { again: if (scf_instance_get_pg(inst, name, pg) == 0) return (0); switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: return (ECONNABORTED); case SCF_ERROR_NOT_SET: return (EBADF); case SCF_ERROR_DELETED: return (ECANCELED); case SCF_ERROR_NOT_FOUND: break; case SCF_ERROR_HANDLE_MISMATCH: case SCF_ERROR_INVALID_ARGUMENT: bad_fail("scf_instance_get_pg", scf_error()); } if (scf_instance_add_pg(inst, name, type, flags, pg) == 0) return (0); switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: return (ECONNABORTED); case SCF_ERROR_DELETED: return (ECANCELED); case SCF_ERROR_EXISTS: goto again; case SCF_ERROR_PERMISSION_DENIED: return (EPERM); case SCF_ERROR_BACKEND_ACCESS: return (EACCES); case SCF_ERROR_BACKEND_READONLY: return (EROFS); case SCF_ERROR_HANDLE_MISMATCH: case SCF_ERROR_INVALID_ARGUMENT: case SCF_ERROR_NOT_SET: /* should be caught above */ bad_fail("scf_instance_add_pg", scf_error()); } return (0); } /* * Fails with * ECONNABORTED * ECANCELED - pg was deleted */ static int tx_set_value(scf_transaction_t *tx, scf_transaction_entry_t *ent, const char *pname, scf_type_t ty, scf_value_t *val) { int r; for (;;) { if (scf_transaction_property_change_type(tx, ent, pname, ty) == 0) break; switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: return (ECONNABORTED); case SCF_ERROR_DELETED: return (ECANCELED); case SCF_ERROR_NOT_FOUND: break; case SCF_ERROR_HANDLE_MISMATCH: case SCF_ERROR_INVALID_ARGUMENT: case SCF_ERROR_IN_USE: case SCF_ERROR_NOT_SET: bad_fail("scf_transaction_property_change_type", scf_error()); } if (scf_transaction_property_new(tx, ent, pname, ty) == 0) break; switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: return (ECONNABORTED); case SCF_ERROR_DELETED: return (ECANCELED); case SCF_ERROR_EXISTS: break; case SCF_ERROR_HANDLE_MISMATCH: case SCF_ERROR_INVALID_ARGUMENT: case SCF_ERROR_IN_USE: case SCF_ERROR_NOT_SET: bad_fail("scf_transaction_property_new", scf_error()); } } r = scf_entry_add_value(ent, val); assert(r == 0); return (0); } /* * Commit new_state, new_next_state, and aux to the repository for id. If * successful, also set id's state and next-state as given, and return 0. * Fails with * ENOMEM - out of memory * ECONNABORTED - repository connection broken * - unknown libscf error * EINVAL - id->i_fmri is invalid or not an instance FMRI * ENOENT - id->i_fmri does not exist * EPERM - insufficient permissions * EACCES - backend access denied * EROFS - backend is readonly */ int _restarter_commit_states(scf_handle_t *h, instance_data_t *id, restarter_instance_state_t new_state, restarter_instance_state_t new_state_next, const char *aux) { char str_state[MAX_SCF_STATE_STRING_SZ]; char str_new_state[MAX_SCF_STATE_STRING_SZ]; char str_state_next[MAX_SCF_STATE_STRING_SZ]; char str_new_state_next[MAX_SCF_STATE_STRING_SZ]; int ret = 0, r; struct timeval now; ssize_t sz; char *default_aux = "none"; scf_transaction_t *t = NULL; scf_transaction_entry_t *t_state = NULL, *t_state_next = NULL; scf_transaction_entry_t *t_stime = NULL, *t_aux = NULL; scf_value_t *v_state = NULL, *v_state_next = NULL, *v_stime = NULL; scf_value_t *v_aux = NULL; scf_instance_t *s_inst = NULL; scf_propertygroup_t *pg = NULL; assert(new_state != RESTARTER_STATE_NONE); /* If aux state is unset, set aux to a default string. */ if (aux == NULL) aux = default_aux; if ((s_inst = scf_instance_create(h)) == NULL || (pg = scf_pg_create(h)) == NULL || (t = scf_transaction_create(h)) == NULL || (t_state = scf_entry_create(h)) == NULL || (t_state_next = scf_entry_create(h)) == NULL || (t_stime = scf_entry_create(h)) == NULL || (t_aux = scf_entry_create(h)) == NULL || (v_state = scf_value_create(h)) == NULL || (v_state_next = scf_value_create(h)) == NULL || (v_stime = scf_value_create(h)) == NULL || (v_aux = scf_value_create(h)) == NULL) { ret = ENOMEM; goto out; } sz = restarter_state_to_string(new_state, str_new_state, sizeof (str_new_state)); assert(sz < sizeof (str_new_state)); sz = restarter_state_to_string(new_state_next, str_new_state_next, sizeof (str_new_state_next)); assert(sz < sizeof (str_new_state_next)); sz = restarter_state_to_string(id->i_state, str_state, sizeof (str_state)); assert(sz < sizeof (str_state)); sz = restarter_state_to_string(id->i_next_state, str_state_next, sizeof (str_state_next)); assert(sz < sizeof (str_state_next)); ret = gettimeofday(&now, NULL); assert(ret != -1); if (scf_handle_decode_fmri(h, id->i_fmri, NULL, NULL, s_inst, NULL, NULL, SCF_DECODE_FMRI_EXACT) == -1) { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: ret = ECONNABORTED; break; case SCF_ERROR_INVALID_ARGUMENT: case SCF_ERROR_CONSTRAINT_VIOLATED: ret = EINVAL; break; case SCF_ERROR_NOT_FOUND: ret = ENOENT; break; case SCF_ERROR_HANDLE_MISMATCH: bad_fail("scf_handle_decode_fmri", scf_error()); } goto out; } if (scf_value_set_astring(v_state, str_new_state) != 0 || scf_value_set_astring(v_state_next, str_new_state_next) != 0 || scf_value_set_astring(v_aux, aux) != 0) bad_fail("scf_value_set_astring", scf_error()); if (scf_value_set_time(v_stime, now.tv_sec, now.tv_usec * 1000) != 0) bad_fail("scf_value_set_time", scf_error()); add_pg: switch (r = instance_get_or_add_pg(s_inst, SCF_PG_RESTARTER, SCF_PG_RESTARTER_TYPE, SCF_PG_RESTARTER_FLAGS, pg)) { case 0: break; case ECONNABORTED: case EPERM: case EACCES: case EROFS: ret = r; goto out; case ECANCELED: ret = ENOENT; goto out; case EBADF: default: bad_fail("instance_get_or_add_pg", r); } for (;;) { if (scf_transaction_start(t, pg) != 0) { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: ret = ECONNABORTED; goto out; case SCF_ERROR_NOT_SET: goto add_pg; case SCF_ERROR_PERMISSION_DENIED: ret = EPERM; goto out; case SCF_ERROR_BACKEND_ACCESS: ret = EACCES; goto out; case SCF_ERROR_BACKEND_READONLY: ret = EROFS; goto out; case SCF_ERROR_HANDLE_MISMATCH: case SCF_ERROR_IN_USE: bad_fail("scf_transaction_start", scf_error()); } } if ((r = tx_set_value(t, t_state, SCF_PROPERTY_STATE, SCF_TYPE_ASTRING, v_state)) != 0 || (r = tx_set_value(t, t_state_next, SCF_PROPERTY_NEXT_STATE, SCF_TYPE_ASTRING, v_state_next)) != 0 || (r = tx_set_value(t, t_aux, SCF_PROPERTY_AUX_STATE, SCF_TYPE_ASTRING, v_aux)) != 0 || (r = tx_set_value(t, t_stime, SCF_PROPERTY_STATE_TIMESTAMP, SCF_TYPE_TIME, v_stime)) != 0) { switch (r) { case ECONNABORTED: ret = ECONNABORTED; goto out; case ECANCELED: scf_transaction_reset(t); goto add_pg; default: bad_fail("tx_set_value", r); } } ret = scf_transaction_commit(t); if (ret == 1) break; if (ret == -1) { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: ret = ECONNABORTED; goto out; case SCF_ERROR_PERMISSION_DENIED: ret = EPERM; goto out; case SCF_ERROR_BACKEND_ACCESS: ret = EACCES; goto out; case SCF_ERROR_BACKEND_READONLY: ret = EROFS; goto out; case SCF_ERROR_NOT_SET: bad_fail("scf_transaction_commit", scf_error()); } } scf_transaction_reset(t); if (scf_pg_update(pg) == -1) { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: ret = ECONNABORTED; goto out; case SCF_ERROR_NOT_SET: goto add_pg; } } } id->i_state = new_state; id->i_next_state = new_state_next; ret = 0; out: scf_transaction_destroy(t); scf_entry_destroy(t_state); scf_entry_destroy(t_state_next); scf_entry_destroy(t_stime); scf_entry_destroy(t_aux); scf_value_destroy(v_state); scf_value_destroy(v_state_next); scf_value_destroy(v_stime); scf_value_destroy(v_aux); scf_pg_destroy(pg); scf_instance_destroy(s_inst); return (ret); } /* * Fails with * EINVAL - type is invalid * ENOMEM * ECONNABORTED - repository connection broken * EBADF - s_inst is not set * ECANCELED - s_inst is deleted * EPERM - permission denied * EACCES - backend access denied * EROFS - backend readonly */ int restarter_remove_contract(scf_instance_t *s_inst, ctid_t contract_id, restarter_contract_type_t type) { scf_handle_t *h; scf_transaction_t *t = NULL; scf_transaction_entry_t *t_cid = NULL; scf_propertygroup_t *pg = NULL; scf_property_t *prop = NULL; scf_value_t *val; scf_iter_t *iter = NULL; const char *pname; int ret = 0, primary; uint64_t c; switch (type) { case RESTARTER_CONTRACT_PRIMARY: primary = 1; break; case RESTARTER_CONTRACT_TRANSIENT: primary = 0; break; default: return (EINVAL); } h = scf_instance_handle(s_inst); pg = scf_pg_create(h); prop = scf_property_create(h); iter = scf_iter_create(h); t = scf_transaction_create(h); if (pg == NULL || prop == NULL || iter == NULL || t == NULL) { ret = ENOMEM; goto remove_contract_cleanup; } add: scf_transaction_destroy_children(t); ret = instance_get_or_add_pg(s_inst, SCF_PG_RESTARTER, SCF_PG_RESTARTER_TYPE, SCF_PG_RESTARTER_FLAGS, pg); if (ret != 0) goto remove_contract_cleanup; pname = primary? SCF_PROPERTY_CONTRACT : SCF_PROPERTY_TRANSIENT_CONTRACT; for (;;) { if (scf_transaction_start(t, pg) != 0) { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: ret = ECONNABORTED; goto remove_contract_cleanup; case SCF_ERROR_DELETED: goto add; case SCF_ERROR_PERMISSION_DENIED: ret = EPERM; goto remove_contract_cleanup; case SCF_ERROR_BACKEND_ACCESS: ret = EACCES; goto remove_contract_cleanup; case SCF_ERROR_BACKEND_READONLY: ret = EROFS; goto remove_contract_cleanup; case SCF_ERROR_HANDLE_MISMATCH: case SCF_ERROR_IN_USE: case SCF_ERROR_NOT_SET: bad_fail("scf_transaction_start", scf_error()); } } t_cid = scf_entry_create(h); if (scf_pg_get_property(pg, pname, prop) == 0) { replace: if (scf_transaction_property_change_type(t, t_cid, pname, SCF_TYPE_COUNT) != 0) { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: ret = ECONNABORTED; goto remove_contract_cleanup; case SCF_ERROR_DELETED: scf_entry_destroy(t_cid); goto add; case SCF_ERROR_NOT_FOUND: goto new; case SCF_ERROR_HANDLE_MISMATCH: case SCF_ERROR_INVALID_ARGUMENT: case SCF_ERROR_IN_USE: case SCF_ERROR_NOT_SET: bad_fail( "scf_transaction_property_changetype", scf_error()); } } if (scf_property_is_type(prop, SCF_TYPE_COUNT) == 0) { if (scf_iter_property_values(iter, prop) != 0) { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: ret = ECONNABORTED; goto remove_contract_cleanup; case SCF_ERROR_NOT_SET: case SCF_ERROR_HANDLE_MISMATCH: bad_fail( "scf_iter_property_values", scf_error()); } } next_val: val = scf_value_create(h); if (val == NULL) { assert(scf_error() == SCF_ERROR_NO_MEMORY); ret = ENOMEM; goto remove_contract_cleanup; } ret = scf_iter_next_value(iter, val); if (ret == -1) { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: ret = ECONNABORTED; goto remove_contract_cleanup; case SCF_ERROR_DELETED: scf_value_destroy(val); goto add; case SCF_ERROR_HANDLE_MISMATCH: case SCF_ERROR_INVALID_ARGUMENT: default: bad_fail("scf_iter_next_value", scf_error()); } } if (ret == 1) { ret = scf_value_get_count(val, &c); assert(ret == 0); if (c != contract_id) { ret = scf_entry_add_value(t_cid, val); assert(ret == 0); } else { scf_value_destroy(val); } goto next_val; } scf_value_destroy(val); } else { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: ret = ECONNABORTED; goto remove_contract_cleanup; case SCF_ERROR_TYPE_MISMATCH: break; case SCF_ERROR_INVALID_ARGUMENT: case SCF_ERROR_NOT_SET: bad_fail("scf_property_is_type", scf_error()); } } } else { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: ret = ECONNABORTED; goto remove_contract_cleanup; case SCF_ERROR_DELETED: scf_entry_destroy(t_cid); goto add; case SCF_ERROR_NOT_FOUND: break; case SCF_ERROR_HANDLE_MISMATCH: case SCF_ERROR_INVALID_ARGUMENT: case SCF_ERROR_NOT_SET: bad_fail("scf_pg_get_property", scf_error()); } new: if (scf_transaction_property_new(t, t_cid, pname, SCF_TYPE_COUNT) != 0) { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: ret = ECONNABORTED; goto remove_contract_cleanup; case SCF_ERROR_DELETED: scf_entry_destroy(t_cid); goto add; case SCF_ERROR_EXISTS: goto replace; case SCF_ERROR_HANDLE_MISMATCH: case SCF_ERROR_INVALID_ARGUMENT: case SCF_ERROR_NOT_SET: bad_fail("scf_transaction_property_new", scf_error()); } } } ret = scf_transaction_commit(t); if (ret == -1) { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: ret = ECONNABORTED; goto remove_contract_cleanup; case SCF_ERROR_DELETED: goto add; case SCF_ERROR_PERMISSION_DENIED: ret = EPERM; goto remove_contract_cleanup; case SCF_ERROR_BACKEND_ACCESS: ret = EACCES; goto remove_contract_cleanup; case SCF_ERROR_BACKEND_READONLY: ret = EROFS; goto remove_contract_cleanup; case SCF_ERROR_NOT_SET: bad_fail("scf_transaction_commit", scf_error()); } } if (ret == 1) { ret = 0; break; } scf_transaction_destroy_children(t); if (scf_pg_update(pg) == -1) { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: ret = ECONNABORTED; goto remove_contract_cleanup; case SCF_ERROR_DELETED: goto add; case SCF_ERROR_NOT_SET: bad_fail("scf_pg_update", scf_error()); } } } remove_contract_cleanup: scf_transaction_destroy_children(t); scf_transaction_destroy(t); scf_iter_destroy(iter); scf_property_destroy(prop); scf_pg_destroy(pg); return (ret); } /* * Fails with * EINVAL - type is invalid * ENOMEM * ECONNABORTED - repository disconnection * EBADF - s_inst is not set * ECANCELED - s_inst is deleted * EPERM * EACCES * EROFS */ int restarter_store_contract(scf_instance_t *s_inst, ctid_t contract_id, restarter_contract_type_t type) { scf_handle_t *h; scf_transaction_t *t = NULL; scf_transaction_entry_t *t_cid = NULL; scf_value_t *val; scf_propertygroup_t *pg = NULL; scf_property_t *prop = NULL; scf_iter_t *iter = NULL; const char *pname; int ret = 0, primary; if (type == RESTARTER_CONTRACT_PRIMARY) primary = 1; else if (type == RESTARTER_CONTRACT_TRANSIENT) primary = 0; else return (EINVAL); h = scf_instance_handle(s_inst); pg = scf_pg_create(h); prop = scf_property_create(h); iter = scf_iter_create(h); t = scf_transaction_create(h); if (pg == NULL || prop == NULL || iter == NULL || t == NULL) { ret = ENOMEM; goto out; } add: scf_transaction_destroy_children(t); ret = instance_get_or_add_pg(s_inst, SCF_PG_RESTARTER, SCF_PG_RESTARTER_TYPE, SCF_PG_RESTARTER_FLAGS, pg); if (ret != 0) goto out; pname = primary ? SCF_PROPERTY_CONTRACT : SCF_PROPERTY_TRANSIENT_CONTRACT; for (;;) { if (scf_transaction_start(t, pg) != 0) { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: ret = ECONNABORTED; goto out; case SCF_ERROR_DELETED: goto add; case SCF_ERROR_PERMISSION_DENIED: ret = EPERM; goto out; case SCF_ERROR_BACKEND_ACCESS: ret = EACCES; goto out; case SCF_ERROR_BACKEND_READONLY: ret = EROFS; goto out; case SCF_ERROR_HANDLE_MISMATCH: case SCF_ERROR_IN_USE: case SCF_ERROR_NOT_SET: bad_fail("scf_transaction_start", scf_error()); } } t_cid = scf_entry_create(h); if (t_cid == NULL) { ret = ENOMEM; goto out; } if (scf_pg_get_property(pg, pname, prop) == 0) { replace: if (scf_transaction_property_change_type(t, t_cid, pname, SCF_TYPE_COUNT) != 0) { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: ret = ECONNABORTED; goto out; case SCF_ERROR_DELETED: scf_entry_destroy(t_cid); goto add; case SCF_ERROR_NOT_FOUND: goto new; case SCF_ERROR_HANDLE_MISMATCH: case SCF_ERROR_INVALID_ARGUMENT: case SCF_ERROR_IN_USE: case SCF_ERROR_NOT_SET: bad_fail( "scf_transaction_propert_change_type", scf_error()); } } if (scf_property_is_type(prop, SCF_TYPE_COUNT) == 0) { if (scf_iter_property_values(iter, prop) != 0) { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: ret = ECONNABORTED; goto out; case SCF_ERROR_NOT_SET: case SCF_ERROR_HANDLE_MISMATCH: bad_fail( "scf_iter_property_values", scf_error()); } } next_val: val = scf_value_create(h); if (val == NULL) { assert(scf_error() == SCF_ERROR_NO_MEMORY); ret = ENOMEM; goto out; } ret = scf_iter_next_value(iter, val); if (ret == -1) { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: ret = ECONNABORTED; goto out; case SCF_ERROR_DELETED: scf_value_destroy(val); goto add; case SCF_ERROR_HANDLE_MISMATCH: case SCF_ERROR_INVALID_ARGUMENT: bad_fail( "scf_iter_next_value", scf_error()); } } if (ret == 1) { ret = scf_entry_add_value(t_cid, val); assert(ret == 0); goto next_val; } scf_value_destroy(val); } else { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: ret = ECONNABORTED; goto out; case SCF_ERROR_TYPE_MISMATCH: break; case SCF_ERROR_INVALID_ARGUMENT: case SCF_ERROR_NOT_SET: bad_fail("scf_property_is_type", scf_error()); } } } else { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: ret = ECONNABORTED; goto out; case SCF_ERROR_DELETED: scf_entry_destroy(t_cid); goto add; case SCF_ERROR_NOT_FOUND: break; case SCF_ERROR_HANDLE_MISMATCH: case SCF_ERROR_INVALID_ARGUMENT: case SCF_ERROR_NOT_SET: bad_fail("scf_pg_get_property", scf_error()); } new: if (scf_transaction_property_new(t, t_cid, pname, SCF_TYPE_COUNT) != 0) { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: ret = ECONNABORTED; goto out; case SCF_ERROR_DELETED: scf_entry_destroy(t_cid); goto add; case SCF_ERROR_EXISTS: goto replace; case SCF_ERROR_HANDLE_MISMATCH: case SCF_ERROR_INVALID_ARGUMENT: case SCF_ERROR_NOT_SET: bad_fail("scf_transaction_property_new", scf_error()); } } } val = scf_value_create(h); if (val == NULL) { assert(scf_error() == SCF_ERROR_NO_MEMORY); ret = ENOMEM; goto out; } scf_value_set_count(val, contract_id); ret = scf_entry_add_value(t_cid, val); assert(ret == 0); ret = scf_transaction_commit(t); if (ret == -1) { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: ret = ECONNABORTED; goto out; case SCF_ERROR_DELETED: goto add; case SCF_ERROR_PERMISSION_DENIED: ret = EPERM; goto out; case SCF_ERROR_BACKEND_ACCESS: ret = EACCES; goto out; case SCF_ERROR_BACKEND_READONLY: ret = EROFS; goto out; case SCF_ERROR_NOT_SET: bad_fail("scf_transaction_commit", scf_error()); } } if (ret == 1) { ret = 0; break; } scf_transaction_destroy_children(t); if (scf_pg_update(pg) == -1) { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: default: ret = ECONNABORTED; goto out; case SCF_ERROR_DELETED: goto add; case SCF_ERROR_NOT_SET: bad_fail("scf_pg_update", scf_error()); } } } out: scf_transaction_destroy_children(t); scf_transaction_destroy(t); scf_iter_destroy(iter); scf_property_destroy(prop); scf_pg_destroy(pg); return (ret); } int restarter_rm_libs_loadable() { void *libhndl; if (method_context_safety) return (1); if ((libhndl = dlopen("libpool.so", RTLD_LAZY | RTLD_LOCAL)) == NULL) return (0); (void) dlclose(libhndl); if ((libhndl = dlopen("libproject.so", RTLD_LAZY | RTLD_LOCAL)) == NULL) return (0); (void) dlclose(libhndl); method_context_safety = 1; return (1); } static int get_astring_val(scf_propertygroup_t *pg, const char *name, char *buf, size_t bufsz, scf_property_t *prop, scf_value_t *val) { ssize_t szret; if (scf_pg_get_property(pg, name, prop) != SCF_SUCCESS) { if (scf_error() == SCF_ERROR_CONNECTION_BROKEN) uu_die(rcbroken); return (-1); } if (scf_property_get_value(prop, val) != SCF_SUCCESS) { if (scf_error() == SCF_ERROR_CONNECTION_BROKEN) uu_die(rcbroken); return (-1); } szret = scf_value_get_astring(val, buf, bufsz); return (szret >= 0 ? 0 : -1); } /* * Try to load mcp->pwd, if it isn't already. * Fails with * ENOMEM - malloc() failed * ENOENT - no entry found * EIO - I/O error * EMFILE - process out of file descriptors * ENFILE - system out of file handles */ static int lookup_pwd(struct method_context *mcp) { struct passwd *pwdp; if (mcp->pwbuf != NULL && mcp->pwd.pw_uid == mcp->uid) return (0); if (mcp->pwbuf == NULL) { mcp->pwbufsz = sysconf(_SC_GETPW_R_SIZE_MAX); assert(mcp->pwbufsz >= 0); mcp->pwbuf = malloc(mcp->pwbufsz); if (mcp->pwbuf == NULL) return (ENOMEM); } do { errno = 0; pwdp = getpwuid_r(mcp->uid, &mcp->pwd, mcp->pwbuf, mcp->pwbufsz); } while (pwdp == NULL && errno == EINTR); if (pwdp != NULL) return (0); free(mcp->pwbuf); mcp->pwbuf = NULL; switch (errno) { case 0: default: /* * Until bug 5065780 is fixed, getpwuid_r() can fail with * ENOENT, particularly on the miniroot. Since the * documentation is inaccurate, we'll return ENOENT for unknown * errors. */ return (ENOENT); case EIO: case EMFILE: case ENFILE: return (errno); case ERANGE: bad_fail("getpwuid_r", errno); /* NOTREACHED */ } } /* * Get the user id for str. Returns 0 on success or * ERANGE the uid is too big * EINVAL the string starts with a digit, but is not a valid uid * ENOMEM out of memory * ENOENT no passwd entry for str * EIO an I/O error has occurred * EMFILE/ENFILE out of file descriptors */ int get_uid(const char *str, struct method_context *ci, uid_t *uidp) { if (isdigit(str[0])) { uid_t uid; char *cp; errno = 0; uid = strtol(str, &cp, 10); if (uid == 0 && errno != 0) { assert(errno != EINVAL); return (errno); } for (; *cp != '\0'; ++cp) if (*cp != ' ' || *cp != '\t') return (EINVAL); if (uid > UID_MAX) return (EINVAL); *uidp = uid; return (0); } else { struct passwd *pwdp; if (ci->pwbuf == NULL) { ci->pwbufsz = sysconf(_SC_GETPW_R_SIZE_MAX); ci->pwbuf = malloc(ci->pwbufsz); if (ci->pwbuf == NULL) return (ENOMEM); } do { errno = 0; pwdp = getpwnam_r(str, &ci->pwd, ci->pwbuf, ci->pwbufsz); } while (pwdp == NULL && errno == EINTR); if (pwdp != NULL) { *uidp = ci->pwd.pw_uid; return (0); } else { free(ci->pwbuf); ci->pwbuf = NULL; switch (errno) { case 0: return (ENOENT); case ENOENT: case EIO: case EMFILE: case ENFILE: return (errno); case ERANGE: default: bad_fail("getpwnam_r", errno); /* NOTREACHED */ } } } } gid_t get_gid(const char *str) { if (isdigit(str[0])) { gid_t gid; char *cp; errno = 0; gid = strtol(str, &cp, 10); if (gid == 0 && errno != 0) return ((gid_t)-1); for (; *cp != '\0'; ++cp) if (*cp != ' ' || *cp != '\t') return ((gid_t)-1); return (gid); } else { struct group grp, *ret; char *buffer; size_t buflen; buflen = sysconf(_SC_GETGR_R_SIZE_MAX); buffer = malloc(buflen); if (buffer == NULL) uu_die(allocfail); errno = 0; ret = getgrnam_r(str, &grp, buffer, buflen); free(buffer); return (ret == NULL ? (gid_t)-1 : grp.gr_gid); } } /* * Fails with * ENOMEM - out of memory * ENOENT - no passwd entry * no project entry * EIO - an I/O error occurred * EMFILE - the process is out of file descriptors * ENFILE - the system is out of file handles * ERANGE - the project id is out of range * EINVAL - str is invalid * E2BIG - the project entry was too big * -1 - the name service switch is misconfigured */ int get_projid(const char *str, struct method_context *cip) { int ret; void *buf; const size_t bufsz = PROJECT_BUFSZ; struct project proj, *pp; if (strcmp(str, ":default") == 0) { if (cip->uid == 0) { /* Don't change project for root services */ cip->project = NULL; return (0); } switch (ret = lookup_pwd(cip)) { case 0: break; case ENOMEM: case ENOENT: case EIO: case EMFILE: case ENFILE: return (ret); default: bad_fail("lookup_pwd", ret); } buf = malloc(bufsz); if (buf == NULL) return (ENOMEM); do { errno = 0; pp = getdefaultproj(cip->pwd.pw_name, &proj, buf, bufsz); } while (pp == NULL && errno == EINTR); /* to be continued ... */ } else { projid_t projid; char *cp; if (!isdigit(str[0])) { cip->project = strdup(str); return (cip->project != NULL ? 0 : ENOMEM); } errno = 0; projid = strtol(str, &cp, 10); if (projid == 0 && errno != 0) { assert(errno == ERANGE); return (errno); } for (; *cp != '\0'; ++cp) if (*cp != ' ' || *cp != '\t') return (EINVAL); if (projid > MAXPROJID) return (ERANGE); buf = malloc(bufsz); if (buf == NULL) return (ENOMEM); do { errno = 0; pp = getprojbyid(projid, &proj, buf, bufsz); } while (pp == NULL && errno == EINTR); } if (pp) { cip->project = strdup(pp->pj_name); free(buf); return (cip->project != NULL ? 0 : ENOMEM); } free(buf); switch (errno) { case 0: return (ENOENT); case EIO: case EMFILE: case ENFILE: return (errno); case ERANGE: return (E2BIG); default: return (-1); } } /* * Parse the supp_groups property value and populate ci->groups. Returns * EINVAL (get_gid() failed for one of the components), E2BIG (the property has * more than NGROUPS_MAX-1 groups), or 0 on success. */ int get_groups(char *str, struct method_context *ci) { char *cp, *end, *next; uint_t i; const char * const whitespace = " \t"; const char * const illegal = ", \t"; if (str[0] == '\0') { ci->ngroups = 0; return (0); } for (cp = str, i = 0; *cp != '\0'; ) { /* skip whitespace */ cp += strspn(cp, whitespace); /* find the end */ end = cp + strcspn(cp, illegal); /* skip whitespace after end */ next = end + strspn(end, whitespace); /* if there's a comma, it separates the fields */ if (*next == ',') ++next; *end = '\0'; if ((ci->groups[i] = get_gid(cp)) == (gid_t)-1) { ci->ngroups = 0; return (EINVAL); } ++i; if (i > NGROUPS_MAX - 1) { ci->ngroups = 0; return (E2BIG); } cp = next; } ci->ngroups = i; return (0); } /* * Eventually, we will return a structured error in the case of * retryable or abortable failures such as memory allocation errors and * repository connection failures. For now, these failures are just * encoded in the failure string. */ static const char * get_profile(scf_propertygroup_t *pg, scf_property_t *prop, scf_value_t *val, const char *cmdline, struct method_context *ci) { char *buf = ci->vbuf; ssize_t buf_sz = ci->vbuf_sz; char cmd[PATH_MAX]; char *cp, *value; const char *cmdp; execattr_t *eap; char *errstr = NULL; if (get_astring_val(pg, SCF_PROPERTY_PROFILE, buf, buf_sz, prop, val) != 0) return ("Could not get profile property."); /* Extract the command from the command line. */ cp = strpbrk(cmdline, " \t"); if (cp == NULL) { cmdp = cmdline; } else { (void) strncpy(cmd, cmdline, cp - cmdline); cmd[cp - cmdline] = '\0'; cmdp = cmd; } /* Require that cmdp[0] == '/'? */ eap = getexecprof(buf, KV_COMMAND, cmdp, GET_ONE); if (eap == NULL) return ("Could not find profile."); /* Based on pfexec.c */ /* Get the euid first so we don't override ci->pwd for the uid. */ if ((value = kva_match(eap->attr, EXECATTR_EUID_KW)) != NULL) { if (get_uid(value, ci, &ci->euid) != 0) { ci->euid = (uid_t)-1; errstr = "Could not interpret profile euid."; goto out; } } if ((value = kva_match(eap->attr, EXECATTR_UID_KW)) != NULL) { if (get_uid(value, ci, &ci->uid) != 0) { ci->euid = ci->uid = (uid_t)-1; errstr = "Could not interpret profile uid."; goto out; } ci->euid = ci->uid; } if ((value = kva_match(eap->attr, EXECATTR_GID_KW)) != NULL) { ci->egid = ci->gid = get_gid(value); if (ci->gid == (gid_t)-1) { errstr = "Could not interpret profile gid."; goto out; } } if ((value = kva_match(eap->attr, EXECATTR_EGID_KW)) != NULL) { ci->egid = get_gid(value); if (ci->egid == (gid_t)-1) { errstr = "Could not interpret profile egid."; goto out; } } if ((value = kva_match(eap->attr, EXECATTR_LPRIV_KW)) != NULL) { ci->lpriv_set = priv_str_to_set(value, ",", NULL); if (ci->lpriv_set == NULL) { if (errno != EINVAL) errstr = ALLOCFAIL; else errstr = "Could not interpret profile " "limitprivs."; goto out; } } if ((value = kva_match(eap->attr, EXECATTR_IPRIV_KW)) != NULL) { ci->priv_set = priv_str_to_set(value, ",", NULL); if (ci->priv_set == NULL) { if (errno != EINVAL) errstr = ALLOCFAIL; else errstr = "Could not interpret profile privs."; goto out; } } out: free_execattr(eap); return (errstr); } /* * Eventually, we will return a structured error in the case of * retryable or abortable failures such as memory allocation errors and * repository connection failures. For now, these failures are just * encoded in the failure string. */ static const char * get_ids(scf_propertygroup_t *pg, scf_property_t *prop, scf_value_t *val, struct method_context *ci) { const char *errstr = NULL; char *vbuf = ci->vbuf; ssize_t vbuf_sz = ci->vbuf_sz; int r; if (get_astring_val(pg, SCF_PROPERTY_USER, vbuf, vbuf_sz, prop, val) != 0) { errstr = "Could not get user property."; goto out; } if (get_uid(vbuf, ci, &ci->uid) != 0) { ci->uid = (uid_t)-1; errstr = "Could not interpret user property."; goto out; } if (get_astring_val(pg, SCF_PROPERTY_GROUP, vbuf, vbuf_sz, prop, val) != 0) { errstr = "Could not get group property."; goto out; } if (strcmp(vbuf, ":default") != 0) { ci->gid = get_gid(vbuf); if (ci->gid == (gid_t)-1) { errstr = "Could not interpret group property."; goto out; } } else { switch (r = lookup_pwd(ci)) { case 0: ci->gid = ci->pwd.pw_gid; break; case ENOENT: ci->gid = (gid_t)-1; errstr = "No passwd entry."; goto out; case ENOMEM: errstr = "Out of memory."; goto out; case EIO: case EMFILE: case ENFILE: errstr = "getpwuid_r() failed."; goto out; default: bad_fail("lookup_pwd", r); } } if (get_astring_val(pg, SCF_PROPERTY_SUPP_GROUPS, vbuf, vbuf_sz, prop, val) != 0) { errstr = "Could not get supplemental groups property."; goto out; } if (strcmp(vbuf, ":default") != 0) { switch (r = get_groups(vbuf, ci)) { case 0: break; case EINVAL: errstr = "Could not interpret supplemental groups property."; goto out; case E2BIG: errstr = "Too many supplemental groups."; goto out; default: bad_fail("get_groups", r); } } else { ci->ngroups = -1; } if (get_astring_val(pg, SCF_PROPERTY_PRIVILEGES, vbuf, vbuf_sz, prop, val) != 0) { errstr = "Could not get privileges property."; goto out; } /* * For default privs, we need to keep priv_set == NULL, as * we use this test elsewhere. */ if (strcmp(vbuf, ":default") != 0) { ci->priv_set = priv_str_to_set(vbuf, ",", NULL); if (ci->priv_set == NULL) { if (errno != EINVAL) { errstr = ALLOCFAIL; } else { errstr = "Could not interpret privileges " "property."; } goto out; } } if (get_astring_val(pg, SCF_PROPERTY_LIMIT_PRIVILEGES, vbuf, vbuf_sz, prop, val) != 0) { errstr = "Could not get limit_privileges property."; goto out; } if (strcmp(vbuf, ":default") == 0) /* * L must default to all privileges so root NPA services see * iE = all. "zone" is all privileges available in the current * zone, equivalent to "all" in the global zone. */ (void) strcpy(vbuf, "zone"); ci->lpriv_set = priv_str_to_set(vbuf, ",", NULL); if (ci->lpriv_set == NULL) { if (errno != EINVAL) errstr = ALLOCFAIL; else { errstr = "Could not interpret limit_privileges " "property."; } goto out; } out: return (errstr); } static int get_environment(scf_handle_t *h, scf_propertygroup_t *pg, struct method_context *mcp, scf_property_t *prop, scf_value_t *val) { scf_iter_t *iter; scf_type_t type; size_t i = 0; int ret; if (scf_pg_get_property(pg, SCF_PROPERTY_ENVIRONMENT, prop) != 0) { if (scf_error() == SCF_ERROR_NOT_FOUND) return (ENOENT); return (scf_error()); } if (scf_property_type(prop, &type) != 0) return (scf_error()); if (type != SCF_TYPE_ASTRING) return (EINVAL); if ((iter = scf_iter_create(h)) == NULL) return (scf_error()); if (scf_iter_property_values(iter, prop) != 0) { ret = scf_error(); scf_iter_destroy(iter); return (ret); } mcp->env_sz = 10; if ((mcp->env = uu_zalloc(sizeof (*mcp->env) * mcp->env_sz)) == NULL) { ret = ENOMEM; goto out; } while ((ret = scf_iter_next_value(iter, val)) == 1) { ret = scf_value_get_as_string(val, mcp->vbuf, mcp->vbuf_sz); if (ret == -1) { ret = scf_error(); goto out; } if ((mcp->env[i] = strdup(mcp->vbuf)) == NULL) { ret = ENOMEM; goto out; } if (++i == mcp->env_sz) { char **env; mcp->env_sz *= 2; env = uu_zalloc(sizeof (*mcp->env) * mcp->env_sz); if (env == NULL) { ret = ENOMEM; goto out; } (void) memcpy(env, mcp->env, sizeof (*mcp->env) * (mcp->env_sz / 2)); free(mcp->env); mcp->env = env; } } if (ret == -1) ret = scf_error(); out: scf_iter_destroy(iter); return (ret); } /* * Fetch method context information from the repository, allocate and fill * a method_context structure, return it in *mcpp, and return NULL. On error, * return a human-readable string which indicates the error. * * Eventually, we will return a structured error in the case of * retryable or abortable failures such as memory allocation errors and * repository connection failures. For now, these failures are just * encoded in the failure string. */ const char * restarter_get_method_context(uint_t version, scf_instance_t *inst, scf_snapshot_t *snap, const char *mname, const char *cmdline, struct method_context **mcpp) { scf_handle_t *h; scf_propertygroup_t *methpg = NULL; scf_propertygroup_t *instpg = NULL; scf_propertygroup_t *pg = NULL; scf_property_t *prop = NULL; scf_value_t *val = NULL; scf_type_t ty; uint8_t use_profile; int ret; const char *errstr = NULL; struct method_context *cip; if (version != RESTARTER_METHOD_CONTEXT_VERSION) return ("Unknown method_context version."); /* Get the handle before we allocate anything. */ h = scf_instance_handle(inst); if (h == NULL) return (scf_strerror(scf_error())); cip = malloc(sizeof (*cip)); if (cip == NULL) return (ALLOCFAIL); (void) memset(cip, 0, sizeof (*cip)); cip->uid = (uid_t)-1; cip->euid = (uid_t)-1; cip->gid = (gid_t)-1; cip->egid = (gid_t)-1; cip->vbuf_sz = scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH); assert(cip->vbuf_sz >= 0); cip->vbuf = malloc(cip->vbuf_sz); if (cip->vbuf == NULL) { free(cip); return (ALLOCFAIL); } if ((instpg = scf_pg_create(h)) == NULL || (methpg = scf_pg_create(h)) == NULL || (prop = scf_property_create(h)) == NULL || (val = scf_value_create(h)) == NULL) { errstr = ALLOCFAIL; goto out; } /* * The method environment, and the credentials/profile data, * may be found either in the pg for the method (methpg), * or in the instance/service SCF_PG_METHOD_CONTEXT pg (named * instpg below). */ if (scf_instance_get_pg_composed(inst, snap, mname, methpg) != SCF_SUCCESS) { errstr = scf_strerror(scf_error()); goto out; } if (scf_instance_get_pg_composed(inst, snap, SCF_PG_METHOD_CONTEXT, instpg) != SCF_SUCCESS) { if (scf_error() != SCF_ERROR_NOT_FOUND) { errstr = scf_strerror(scf_error()); goto out; } scf_pg_destroy(instpg); instpg = NULL; } ret = get_environment(h, methpg, cip, prop, val); if (ret == ENOENT && instpg != NULL) { ret = get_environment(h, instpg, cip, prop, val); } switch (ret) { case 0: case ENOENT: break; case ENOMEM: errstr = "Out of memory."; goto out; case EINVAL: errstr = "Invalid method environment."; goto out; default: errstr = scf_strerror(ret); goto out; } pg = methpg; ret = scf_pg_get_property(pg, SCF_PROPERTY_USE_PROFILE, prop); if (ret && scf_error() == SCF_ERROR_NOT_FOUND && instpg != NULL) { pg = instpg; ret = scf_pg_get_property(pg, SCF_PROPERTY_USE_PROFILE, prop); } if (ret) { switch (scf_error()) { case SCF_ERROR_NOT_FOUND: /* No context: use defaults */ cip->uid = 0; cip->gid = 0; *mcpp = cip; goto out; case SCF_ERROR_CONNECTION_BROKEN: errstr = RCBROKEN; goto out; case SCF_ERROR_DELETED: errstr = "\"use_profile\" property deleted."; goto out; case SCF_ERROR_HANDLE_MISMATCH: case SCF_ERROR_INVALID_ARGUMENT: case SCF_ERROR_NOT_SET: default: bad_fail("scf_pg_get_property", scf_error()); } } if (scf_property_type(prop, &ty) != SCF_SUCCESS) { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: errstr = RCBROKEN; break; case SCF_ERROR_DELETED: errstr = "\"use profile\" property deleted."; break; case SCF_ERROR_NOT_SET: default: bad_fail("scf_property_type", scf_error()); } goto out; } if (ty != SCF_TYPE_BOOLEAN) { errstr = "\"use profile\" property is not boolean."; goto out; } if (scf_property_get_value(prop, val) != SCF_SUCCESS) { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: errstr = RCBROKEN; break; case SCF_ERROR_CONSTRAINT_VIOLATED: errstr = "\"use profile\" property has multiple values."; break; case SCF_ERROR_NOT_FOUND: errstr = "\"use profile\" property has no values."; break; default: bad_fail("scf_property_get_value", scf_error()); } goto out; } ret = scf_value_get_boolean(val, &use_profile); assert(ret == SCF_SUCCESS); /* get ids & privileges */ if (use_profile) errstr = get_profile(pg, prop, val, cmdline, cip); else errstr = get_ids(pg, prop, val, cip); if (errstr != NULL) goto out; /* get working directory */ if (get_astring_val(pg, SCF_PROPERTY_WORKING_DIRECTORY, cip->vbuf, cip->vbuf_sz, prop, val) != 0) { errstr = "Could not get value for working directory."; goto out; } if (strcmp(cip->vbuf, ":default") == 0 || strcmp(cip->vbuf, ":home") == 0) { switch (ret = lookup_pwd(cip)) { case 0: break; case ENOMEM: errstr = "Out of memory."; goto out; case ENOENT: case EIO: case EMFILE: case ENFILE: errstr = "Could not get passwd entry."; goto out; default: bad_fail("lookup_pwd", ret); } cip->working_dir = strdup(cip->pwd.pw_dir); if (cip->working_dir == NULL) { errstr = ALLOCFAIL; goto out; } } else { cip->working_dir = strdup(cip->vbuf); if (cip->working_dir == NULL) { errstr = ALLOCFAIL; goto out; } } /* get (optional) corefile pattern */ if (scf_pg_get_property(pg, SCF_PROPERTY_COREFILE_PATTERN, prop) == SCF_SUCCESS) { if (get_astring_val(pg, SCF_PROPERTY_COREFILE_PATTERN, cip->vbuf, cip->vbuf_sz, prop, val) != 0) { errstr = "Could not get value for corefile pattern."; goto out; } cip->corefile_pattern = strdup(cip->vbuf); if (cip->corefile_pattern == NULL) { errstr = ALLOCFAIL; goto out; } } else { switch (scf_error()) { case SCF_ERROR_NOT_FOUND: /* okay if missing. */ break; case SCF_ERROR_CONNECTION_BROKEN: errstr = RCBROKEN; goto out; case SCF_ERROR_DELETED: errstr = "\"corefile_pattern\" property deleted."; goto out; case SCF_ERROR_HANDLE_MISMATCH: case SCF_ERROR_INVALID_ARGUMENT: case SCF_ERROR_NOT_SET: default: bad_fail("scf_pg_get_property", scf_error()); } } if (restarter_rm_libs_loadable()) { /* get project */ if (get_astring_val(pg, SCF_PROPERTY_PROJECT, cip->vbuf, cip->vbuf_sz, prop, val) != 0) { errstr = "Could not get project."; goto out; } switch (ret = get_projid(cip->vbuf, cip)) { case 0: break; case ENOMEM: errstr = "Out of memory."; goto out; case ENOENT: errstr = "Missing passwd or project entry."; goto out; case EIO: errstr = "I/O error."; goto out; case EMFILE: case ENFILE: errstr = "Out of file descriptors."; goto out; case -1: errstr = "Name service switch is misconfigured."; goto out; case ERANGE: errstr = "Project ID too big."; goto out; case EINVAL: errstr = "Project ID is invalid."; goto out; case E2BIG: errstr = "Project entry is too big."; goto out; default: bad_fail("get_projid", ret); } /* get resource pool */ if (get_astring_val(pg, SCF_PROPERTY_RESOURCE_POOL, cip->vbuf, cip->vbuf_sz, prop, val) != 0) { errstr = "Could not get value of resource pool."; goto out; } if (strcmp(cip->vbuf, ":default") != 0) { cip->resource_pool = strdup(cip->vbuf); if (cip->resource_pool == NULL) { errstr = ALLOCFAIL; goto out; } } } *mcpp = cip; out: (void) scf_value_destroy(val); scf_property_destroy(prop); scf_pg_destroy(instpg); scf_pg_destroy(methpg); if (cip->pwbuf != NULL) free(cip->pwbuf); free(cip->vbuf); if (errstr != NULL) restarter_free_method_context(cip); return (errstr); } /* * Modify the current process per the given method_context. On success, returns * 0. Note that the environment is not modified by this function to include the * environment variables in cip->env. * * On failure, sets *fp to NULL or the name of the function which failed, * and returns one of the following error codes. The words in parentheses are * the values to which *fp may be set for the error case. * ENOMEM - malloc() failed * EIO - an I/O error occurred (getpwuid_r, chdir) * EMFILE - process is out of file descriptors (getpwuid_r) * ENFILE - system is out of file handles (getpwuid_r) * EINVAL - gid or egid is out of range (setregid) * ngroups is too big (setgroups) * project's project id is bad (setproject) * uid or euid is out of range (setreuid) * poolname is invalid (pool_set_binding) * EPERM - insufficient privilege (setregid, initgroups, setgroups, setppriv, * setproject, setreuid, settaskid) * ENOENT - uid has a passwd entry but no shadow entry * working_dir does not exist (chdir) * uid has no passwd entry * the pool could not be found (pool_set_binding) * EFAULT - lpriv_set or priv_set has a bad address (setppriv) * working_dir has a bad address (chdir) * EACCES - could not access working_dir (chdir) * in a TASK_FINAL task (setproject, settaskid) * no resource pool accepting default binding exists (setproject) * ELOOP - too many symbolic links in working_dir (chdir) * ENAMETOOLONG - working_dir is too long (chdir) * ENOLINK - working_dir is on an inaccessible remote machine (chdir) * ENOTDIR - working_dir is not a directory (chdir) * ESRCH - uid is not a user of project (setproject) * project is invalid (setproject) * the resource pool specified for project is unknown (setproject) * EBADF - the configuration for the pool is invalid (pool_set_binding) * -1 - core_set_process_path() failed (core_set_process_path) * a resource control assignment failed (setproject) * a system error occurred during pool_set_binding (pool_set_binding) */ int restarter_set_method_context(struct method_context *cip, const char **fp) { pid_t mypid = -1; int r, ret; cip->pwbuf = NULL; *fp = NULL; if (cip->gid != (gid_t)-1) { if (setregid(cip->gid, cip->egid != (gid_t)-1 ? cip->egid : cip->gid) != 0) { *fp = "setregid"; ret = errno; assert(ret == EINVAL || ret == EPERM); goto out; } } else { if (cip->pwbuf == NULL) { switch (ret = lookup_pwd(cip)) { case 0: break; case ENOMEM: case ENOENT: *fp = NULL; goto out; case EIO: case EMFILE: case ENFILE: *fp = "getpwuid_r"; goto out; default: bad_fail("lookup_pwd", ret); } } if (setregid(cip->pwd.pw_gid, cip->egid != (gid_t)-1 ? cip->egid : cip->pwd.pw_gid) != 0) { *fp = "setregid"; ret = errno; assert(ret == EINVAL || ret == EPERM); goto out; } } if (cip->ngroups == -1) { if (cip->pwbuf == NULL) { switch (ret = lookup_pwd(cip)) { case 0: break; case ENOMEM: case ENOENT: *fp = NULL; goto out; case EIO: case EMFILE: case ENFILE: *fp = "getpwuid_r"; goto out; default: bad_fail("lookup_pwd", ret); } } /* Ok if cip->gid == -1 */ if (initgroups(cip->pwd.pw_name, cip->gid) != 0) { *fp = "initgroups"; ret = errno; assert(ret == EPERM); goto out; } } else if (cip->ngroups > 0 && setgroups(cip->ngroups, cip->groups) != 0) { *fp = "setgroups"; ret = errno; assert(ret == EINVAL || ret == EPERM); goto out; } *fp = "setppriv"; if (cip->lpriv_set != NULL) { if (setppriv(PRIV_SET, PRIV_LIMIT, cip->lpriv_set) != 0) { ret = errno; assert(ret == EFAULT || ret == EPERM); goto out; } } if (cip->priv_set != NULL) { if (setppriv(PRIV_SET, PRIV_INHERITABLE, cip->priv_set) != 0) { ret = errno; assert(ret == EFAULT || ret == EPERM); goto out; } } if (cip->corefile_pattern != NULL) { mypid = getpid(); if (core_set_process_path(cip->corefile_pattern, strlen(cip->corefile_pattern) + 1, mypid) != 0) { *fp = "core_set_process_path"; ret = -1; goto out; } } if (restarter_rm_libs_loadable()) { if (cip->project == NULL) { if (settaskid(getprojid(), TASK_NORMAL) == -1) { switch (errno) { case EACCES: case EPERM: *fp = "settaskid"; ret = errno; goto out; case EINVAL: default: bad_fail("settaskid", errno); } } } else { switch (ret = lookup_pwd(cip)) { case 0: break; case ENOMEM: case ENOENT: *fp = NULL; goto out; case EIO: case EMFILE: case ENFILE: *fp = "getpwuid_r"; goto out; default: bad_fail("lookup_pwd", ret); } *fp = "setproject"; switch (setproject(cip->project, cip->pwd.pw_name, TASK_NORMAL)) { case 0: break; case SETPROJ_ERR_TASK: case SETPROJ_ERR_POOL: ret = errno; goto out; default: ret = -1; goto out; } } if (cip->resource_pool != NULL) { if (mypid == -1) mypid = getpid(); *fp = "pool_set_binding"; if (pool_set_binding(cip->resource_pool, P_PID, mypid) != PO_SUCCESS) { switch (pool_error()) { case POE_INVALID_SEARCH: ret = ENOENT; break; case POE_BADPARAM: ret = EINVAL; break; case POE_INVALID_CONF: ret = EBADF; break; case POE_SYSTEM: ret = -1; break; default: bad_fail("pool_set_binding", pool_error()); } goto out; } } } /* * Now, we have to assume our ID. If the UID is 0, we want it to be * privilege-aware, otherwise the limit set gets used instead of E/P. * We can do this by setting P as well, which keeps * PA status (see priv_can_clear_PA()). */ *fp = "setreuid"; if (setreuid(cip->uid, cip->euid != (uid_t)-1 ? cip->euid : cip->uid) != 0) { ret = errno; assert(ret == EINVAL || ret == EPERM); goto out; } *fp = "setppriv"; if (cip->priv_set != NULL) { if (setppriv(PRIV_SET, PRIV_PERMITTED, cip->priv_set) != 0) { ret = errno; assert(ret == EFAULT || ret == EPERM); goto out; } } /* * The last thing to do is chdir to the specified working directory. * This should come after the uid switching as only the user might * have access to the specified directory. */ if (cip->working_dir != NULL) { do r = chdir(cip->working_dir); while (r != 0 && errno == EINTR); if (r != 0) { *fp = "chdir"; ret = errno; goto out; } } ret = 0; out: free(cip->pwbuf); cip->pwbuf = NULL; return (ret); } void restarter_free_method_context(struct method_context *mcp) { size_t i; if (mcp->lpriv_set != NULL) priv_freeset(mcp->lpriv_set); if (mcp->priv_set != NULL) priv_freeset(mcp->priv_set); if (mcp->env != NULL) { for (i = 0; i < mcp->env_sz; i++) free(mcp->env[i]); free(mcp->env); } free(mcp->working_dir); free(mcp->corefile_pattern); free(mcp->project); free(mcp->resource_pool); free(mcp); } /* * Method keyword functions */ int restarter_is_null_method(const char *meth) { return (strcmp(meth, MKW_TRUE) == 0); } static int is_kill_method(const char *method, const char *kill_str, size_t kill_str_len) { const char *cp; int sig; if (strncmp(method, kill_str, kill_str_len) != 0 || (method[kill_str_len] != '\0' && !isspace(method[kill_str_len]))) return (-1); cp = method + kill_str_len; while (*cp != '\0' && isspace(*cp)) ++cp; if (*cp == '\0') return (SIGTERM); if (*cp != '-') return (-1); return (str2sig(cp + 1, &sig) == 0 ? sig : -1); } int restarter_is_kill_proc_method(const char *method) { return (is_kill_method(method, MKW_KILL_PROC, sizeof (MKW_KILL_PROC) - 1)); } int restarter_is_kill_method(const char *method) { return (is_kill_method(method, MKW_KILL, sizeof (MKW_KILL) - 1)); } /* * Stubs for now. */ /* ARGSUSED */ int restarter_event_get_enabled(restarter_event_t *e) { return (-1); } /* ARGSUSED */ uint64_t restarter_event_get_seq(restarter_event_t *e) { return (-1); } /* ARGSUSED */ void restarter_event_get_time(restarter_event_t *e, hrtime_t *time) { }