diff options
author | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
---|---|---|
committer | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
commit | 7c478bd95313f5f23a4c958a745db2134aa03244 (patch) | |
tree | c871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/lib/librestart/common/librestart.c | |
download | illumos-joyent-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz |
OpenSolaris Launch
Diffstat (limited to 'usr/src/lib/librestart/common/librestart.c')
-rw-r--r-- | usr/src/lib/librestart/common/librestart.c | 2946 |
1 files changed, 2946 insertions, 0 deletions
diff --git a/usr/src/lib/librestart/common/librestart.c b/usr/src/lib/librestart/common/librestart.c new file mode 100644 index 0000000000..d5b96a9c8b --- /dev/null +++ b/usr/src/lib/librestart/common/librestart.c @@ -0,0 +1,2946 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (the "License"). You may not use this file except in compliance + * with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <librestart.h> +#include <librestart_priv.h> +#include <libscf.h> +#include <libscf_priv.h> + +#include <assert.h> +#include <ctype.h> +#include <dlfcn.h> +#include <errno.h> +#include <exec_attr.h> +#include <grp.h> +#include <libsysevent.h> +#include <libuutil.h> +#include <limits.h> +#include <link.h> +#include <malloc.h> +#include <pool.h> +#include <priv.h> +#include <project.h> +#include <pthread.h> +#include <pwd.h> +#include <secdb.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <sys/corectl.h> +#include <sys/machelf.h> +#include <sys/task.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#define walkcontext _walkcontext +#include <ucontext.h> + +#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); +} + +void +restarter_unbind_handle(restarter_event_handle_t *h) +{ + free_restarter_event_handle(h); +} + +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 */ + while (p != NULL) { + 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 (-1); + + for (; *cp != '\0'; ++cp) + if (*cp != ' ' || *cp != '\t') + return (-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 ? -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)) == -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 = -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 = -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 == -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 == -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 = -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 == -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 = -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 = -1; + cip->euid = -1; + cip->gid = -1; + cip->egid = -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) + * 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 != -1) { + if (setregid(cip->gid, + cip->egid != -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 != -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->working_dir != NULL) { + do + r = chdir(cip->working_dir); + while (r != 0 && errno == EINTR); + if (r != 0) { + *fp = "chdir"; + ret = errno; + 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_BADPARAM: + ret = ENOENT; + break; + + case POE_INVALID_CONF: + ret = EBADF; + break; + + case POE_SYSTEM: + ret = -1; + break; + + default: + bad_fail("pool_set_binding", + pool_error()); + } + + goto out; + } + } + } + + /* + * The last thing we must do is 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 != -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; + } + } + + 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) +{ +} |