/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2004, 2012, Oracle and/or its affiliates. All rights reserved.
*/
#include <libintl.h>
#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 <stdarg.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>
#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 10
#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;
};
/*
* Long reasons must all parse/read correctly in the following contexts:
*
* "A service instance transitioned state: %s."
* "A service failed: %s."
* "Reason: %s."
* "The service transitioned state (%s) and ..."
*
* With the exception of restart_str_none they must also fit the following
* moulds:
*
* "An instance transitioned because %s, and ..."
* "An instance transitioned to <new-state> because %s, and ..."
*
* Note that whoever is rendering the long message must provide the
* terminal punctuation - don't include it here. Similarly, do not
* provide an initial capital letter in reason-long.
*
* The long reason strings are Volatile - within the grammatical constraints
* above we may improve them as need be. The intention is that a consumer
* may blindly render the string along the lines of the above examples,
* but has no other guarantees as to the exact wording. Long reasons
* are localized.
*
* We define revisions of the set of short reason strings in use. Within
* a given revision, all short reasons are Committed. Consumers must check
* the revision in use before relying on the semantics of the short reason
* codes - if the version exceeds that which they are familiar with they should
* fail gracefully. Having checked for version compatibility, a consumer
* is assured that
*
* "short_reason_A iff semantic_A", provided:
*
* . the restarter uses this short reason code at all,
* . the short reason is not "none" (which a restarter could
* specifiy for any transition semantics)
*
* To split/refine such a Committed semantic_A into further cases,
* we are required to bump the revision number. This should be an
* infrequent occurence. If you bump the revision number you may
* need to make corresponding changes in any source that calls
* restarter_str_version (e.g., FMA event generation).
*
* To add additional reasons to the set you must also bump the version
* number.
*/
/*
* The following describes revision 1 of the set of transition reasons.
* Read the preceding block comment before making any changes.
*/
static const struct restarter_state_transition_reason restarter_str[] = {
/*
* Any transition for which the restarter has not provided a reason.
*/
{
restarter_str_none,
"none",
"the restarter gave no reason"
},
/*
* A transition to maintenance state due to a
* 'svcadm mark maintenance <fmri>'. *Not* used if the libscf
* interface smf_maintain_instance(3SCF) is used to request maintenance.
*/
{
restarter_str_administrative_request,
"administrative_request",
"maintenance was requested by an administrator"
},
/*
* A transition to maintenance state if a repository inconsistency
* exists when the service/instance state is first read by startd
* into the graph engine (this can also happen during startd restart).
*/
{
restarter_str_bad_repo_state,
"bad_repo_state",
"an SMF repository inconsistecy exists"
},
/*
* A transition 'maintenance -> uninitialized' resulting always
* from 'svcadm clear <fmri>'. *Not* used if the libscf interface
* smf_restore_instance(3SCF) is used.
*/
{
restarter_str_clear_request,
"clear_request",
"maintenance clear was requested by an administrator"
},
/*
* A transition 'online -> offline' due to a process core dump.
*/
{
restarter_str_ct_ev_core,
"ct_ev_core",
"a process dumped core"
},
/*
* A transition 'online -> offline' due to an empty process contract,
* i.e., the last process in a contract type service has exited.
*/
{
restarter_str_ct_ev_exit,
"ct_ev_exit",
"all processes in the service have exited"
},
/*
* A transition 'online -> offline' due to a hardware error.
*/
{
restarter_str_ct_ev_hwerr,
"ct_ev_hwerr",
"a process was killed due to uncorrectable hardware error"
},
/*
* A transition 'online -> offline' due to a process in the service
* having received a fatal signal originating from outside the
* service process contract.
*/
{
restarter_str_ct_ev_signal,
"ct_ev_signal",
"a process received a fatal signal from outside the service"
},
/*
* A transition 'offline -> online' when all dependencies for the
* service have been met.
*/
{
restarter_str_dependencies_satisfied,
"dependencies_satisfied",
"all dependencies have been satisfied"
},
/*
* A transition 'online -> offline' because some dependency for the
* service is no-longer met.
*/
{
restarter_str_dependency_activity,
"dependency_activity",
"a dependency activity required a stop"
},
/*
* A transition to maintenance state due to a cycle in the
* service dependencies.
*/
{
restarter_str_dependency_cycle,
"dependency_cycle",
"a dependency cycle exists"
},
/*
* A transition 'online -> offline -> disabled' due to a
* 'svcadm disable [-t] <fmri>' or smf_disable_instance(3SCF) call.
*/
{
restarter_str_disable_request,
"disable_request",
"a disable was requested"
},
/*
* A transition 'disabled -> offline' due to a
* 'svcadm enable [-t] <fmri>' or smf_enable_instance(3SCF) call.
*/
{
restarter_str_enable_request,
"enable_request",
"an enable was requested"
},
/*
* A transition to maintenance state when a method fails
* repeatedly for a retryable reason.
*/
{
restarter_str_fault_threshold_reached,
"fault_threshold_reached",
"a method is failing in a retryable manner but too often"
},
/*
* A transition to uninitialized state when startd reads the service
* configuration and inserts it into the graph engine.
*/
{
restarter_str_insert_in_graph,
"insert_in_graph",
"the instance was inserted in the graph"
},
/*
* A transition to maintenance state due to an invalid dependency
* declared for the service.
*/
{
restarter_str_invalid_dependency,
"invalid_dependency",
"a service has an invalid dependency"
},
/*
* A transition to maintenance state because the service-declared
* restarter is invalid.
*/
{
restarter_str_invalid_restarter,
"invalid_restarter",
"the service restarter is invalid"
},
/*
* A transition to maintenance state because a restarter method
* exited with one of SMF_EXIT_ERR_CONFIG, SMF_EXIT_ERR_NOSMF,
* SMF_EXIT_ERR_PERM, or SMF_EXIT_ERR_FATAL.
*/
{
restarter_str_method_failed,
"method_failed",
"a start, stop or refresh method failed"
},
/*
* A transition 'uninitialized -> {disabled|offline}' after
* "insert_in_graph" to match the state configured in the
* repository.
*/
{
restarter_str_per_configuration,
"per_configuration",
"the SMF repository configuration specifies this state"
},
/*
* Refresh requested - no state change.
*/
{
restarter_str_refresh,
NULL,
"a refresh was requested (no change of state)"
},
/*
* A transition 'online -> offline -> online' due to a
* 'svcadm restart <fmri> or equivlaent libscf API call.
* Both the 'online -> offline' and 'offline -> online' transtions
* specify this reason.
*/
{
restarter_str_restart_request,
"restart_request",
"a restart was requested"
},
/*
* A transition to maintenance state because the start method is
* being executed successfully but too frequently.
*/
{
restarter_str_restarting_too_quickly,
"restarting_too_quickly",
"the instance is restarting too quickly"
},
/*
* A transition to maintenance state due a service requesting
* 'svcadm mark maintenance <fmri>' or equivalent libscf API call.
* A command line 'svcadm mark maintenance <fmri>' does not produce
* this reason - it produces administrative_request instead.
*/
{
restarter_str_service_request,
"service_request",
"maintenance was requested by another service"
},
/*
* An instanced inserted into the graph at its existing state
* during a startd restart - no state change.
*/
{
restarter_str_startd_restart,
NULL,
"the instance was inserted in the graph due to startd restart"
},
/*
* An instance is found to be in conflict either at the instance
* level or at the instance parent's level.
*/
{
restarter_str_instance_conflict,
"instance_conflict",
"the instance is in conflict"
},
/*
* A method defined its own, custom messages
*/
{
restarter_str_custom,
"custom",
"the instance method supplied its own reasons"
}
};
uint32_t
restarter_str_version(void)
{
return (RESTARTER_STRING_VERSION);
}
const char *
restarter_get_str_short(restarter_str_t key)
{
int i;
for (i = 0; i < sizeof (restarter_str) /
sizeof (struct restarter_state_transition_reason); i++)
if (key == restarter_str[i].str_key)
return (restarter_str[i].str_short);
return (NULL);
}
const char *
restarter_get_str_long(restarter_str_t key)
{
int i;
for (i = 0; i < sizeof (restarter_str) /
sizeof (struct restarter_state_transition_reason); i++)
if (key == restarter_str[i].str_key)
return (dgettext(TEXT_DOMAIN,
restarter_str[i].str_long));
return (NULL);
}
/*
* A static no memory error message mc_error_t structure
* to be used in cases when memory errors are to be returned
* This avoids the need to attempt to allocate memory for the
* message, therefore getting into a cycle of no memory failures.
*/
mc_error_t mc_nomem_err = {
0, ENOMEM, sizeof ("Out of memory") - 1, "Out of memory"
};
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;
/* PRINTFLIKE3 */
static mc_error_t *
mc_error_create(mc_error_t *e, int type, const char *format, ...)
{
mc_error_t *le;
va_list args;
int size;
/*
* If the type is ENOMEM and format is NULL, then
* go ahead and return the default nomem error.
* Otherwise, attempt to allocate the memory and if
* that fails then there is no reason to continue.
*/
if (type == ENOMEM && format == NULL)
return (&mc_nomem_err);
if (e == NULL && (le = malloc(sizeof (mc_error_t))) == NULL)
return (&mc_nomem_err);
else
le = e;
le->type = type;
le->destroy = 1;
va_start(args, format);
size = vsnprintf(NULL, 0, format, args) + 1;
if (size >= RESTARTER_ERRMSGSZ) {
if ((le = realloc(e, sizeof (mc_error_t) +
(size - RESTARTER_ERRMSGSZ))) == NULL) {
size = RESTARTER_ERRMSGSZ - 1;
le = e;
}
}
le->size = size;
(void) vsnprintf(le->msg, le->size, format, args);
va_end(args);
return (le);
}
void
restarter_mc_error_destroy(mc_error_t *mc_err)
{
if (mc_err == NULL)
return;
/*
* If the error messages was allocated then free.
*/
if (mc_err->destroy) {
free(mc_err);
}
}
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);
}
char *
_restarter_get_channel_name(const char *fmri, int type)
{
char *name;
char *chan_name = malloc(MAX_CHNAME_LEN);
char prefix_name[3];
int i;
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);
}
/*
* Create a unique name
*
* Use the entire name, using a replacement of the /
* characters to get a better name.
*
* Remove the svc:/ from the beginning as this really
* isn't going to provide any uniqueness...
*
* An fmri name greater than MAX_CHNAME_LEN is going
* to be rejected as too long for the chan_name below
* in the snprintf call.
*/
if ((name = strdup(strchr(fmri, '/') + 1)) == NULL) {
free(chan_name);
return (NULL);
}
i = 0;
while (name[i]) {
if (name[i] == '/') {
name[i] = '_';
}
i++;
}
/*
* 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);
chan_name = NULL;
}
free(name);
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 state, next_state;
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) ||
(nvlist_lookup_int32(attr_list, RESTARTER_NAME_STATE,
&state) != 0) ||
(nvlist_lookup_int32(attr_list, RESTARTER_NAME_NEXT_STATE,
&next_state) != 0)) {
uu_warn("%s: Can't decode nvlist for event %p\n",
h->reh_restarter_name, (void *)syse);
ret = 0;
} else {
e->re_state = state;
e->re_next_state = next_state;
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);
}
/*
* restarter_event_publish_retry() is a wrapper around sysevent_evc_publish().
* In case, the event cannot be sent at the first attempt (sysevent_evc_publish
* returned EAGAIN - sysevent queue full), this function retries a few time
* and return ENOSPC if it reaches the retry limit.
*
* The arguments to this function map the arguments of sysevent_evc_publish().
*
* On success, return 0. On error, return
*
* EFAULT - internal sysevent_evc_publish() error
* ENOMEM - internal sysevent_evc_publish() error
* EBADF - scp is invalid (sysevent_evc_publish() returned EINVAL)
* ENOSPC - sysevent queue full (sysevent_evc_publish() returned EAGAIN)
*/
int
restarter_event_publish_retry(evchan_t *scp, const char *class,
const char *subclass, const char *vendor, const char *pub_name,
nvlist_t *attr_list, uint32_t flags)
{
int retries, ret;
useconds_t retry_int = INITIAL_COMMIT_RETRY_INT;
for (retries = 0; retries < MAX_COMMIT_RETRIES; retries++) {
ret = sysevent_evc_publish(scp, class, subclass, vendor,
pub_name, attr_list, flags);
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 EINVAL:
ret = EBADF;
/* FALLTHROUGH */
case EFAULT:
case ENOMEM:
return (ret);
case EOVERFLOW:
default:
/* internal error - abort */
bad_fail("sysevent_evc_publish", ret);
}
}
if (retries == MAX_COMMIT_RETRIES)
ret = ENOSPC;
return (ret);
}
/*
* 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
* 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)
* ENOSPC - sysevent queue full (sysevent_evc_publish() returned EAGAIN)
*/
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,
restarter_str_t aux)
{
nvlist_t *attr;
scf_handle_t *scf_h;
instance_data_t id;
int ret = 0;
const char *p = restarter_get_str_short(aux);
assert(h->reh_master_channel != NULL);
assert(h->reh_master_channel_name != NULL);
assert(h->reh_master_subscriber_id != NULL);
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 ||
nvlist_add_int32(attr, RESTARTER_NAME_REASON, aux) != 0) {
ret = ENOMEM;
} else {
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, p);
if (ret == 0) {
ret = restarter_event_publish_retry(
h->reh_master_channel, "master", "state_change",
"com.sun", "librestart", attr, EVCH_NOSLEEP);
}
}
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());
}
}
/*
* 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);
}
/*
* Fails with
* ECONNABORTED
* ECANCELED - pg was deleted
*/
static int
tx_delete_prop(scf_transaction_t *tx, scf_transaction_entry_t *ent,
const char *pname)
{
if (scf_transaction_property_delete(tx, ent, pname) != 0) {
switch (scf_error()) {
case SCF_ERROR_CONNECTION_BROKEN:
default:
return (ECONNABORTED);
case SCF_ERROR_DELETED:
return (ECANCELED);
case SCF_ERROR_NOT_FOUND:
return (0);
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_delete",
scf_error());
}
}
return (0);
}
/*
* Commit new_state, new_next_state, and aux to the repository for id.
*
* If aux == "custom":
* - process three additional, optional args
* - set aux_state_custom and aux_reason in repository
* - if non-NULL, set aux_textdomain in repository
*
* If aux != "custom":
* - clear aux_state_custom, aux_reason, and aux_textdomain from repository
*
* All property updates (additions, value changes, or deletions) are done in a
* single transaction on the restarter property group.
*
* 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
*/
static int
_restarter_commit_states_internal(scf_handle_t *h, instance_data_t *id,
restarter_instance_state_t new_state,
restarter_instance_state_t new_state_next, const char *aux, int aux_custom,
const char *aux_short, const char *aux_long, const char *aux_textdomain)
{
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;
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_transaction_entry_t *t_aux_state_custom = NULL;
scf_transaction_entry_t *t_aux_reason = NULL;
scf_transaction_entry_t *t_aux_textdomain = NULL;
scf_value_t *v_state = NULL, *v_state_next = NULL, *v_stime = NULL;
scf_value_t *v_aux = NULL, *v_aux_state_custom = NULL;
scf_value_t *v_aux_reason = NULL, *v_aux_textdomain = NULL;
scf_instance_t *s_inst = NULL;
scf_propertygroup_t *pg = NULL;
assert(new_state != RESTARTER_STATE_NONE);
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 ||
(t_aux_state_custom = scf_entry_create(h)) == NULL ||
(t_aux_reason = scf_entry_create(h)) == NULL ||
(t_aux_textdomain = 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 ||
(v_aux_state_custom = scf_value_create(h)) == NULL ||
(v_aux_reason = scf_value_create(h)) == NULL ||
(v_aux_textdomain = 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 ||
(aux != NULL && scf_value_set_astring(v_aux, aux) != 0))
bad_fail("scf_value_set_astring", scf_error());
if (aux_custom) {
if (scf_value_set_astring(v_aux_state_custom, aux_short) != 0 ||
scf_value_set_astring(v_aux_reason, aux_long) != 0 ||
(aux_textdomain != NULL &&
scf_value_set_astring(v_aux_textdomain, aux_textdomain)
!= 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_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);
}
}
if (aux) {
if ((r = tx_set_value(t, t_aux, SCF_PROPERTY_AUX_STATE,
SCF_TYPE_ASTRING, v_aux)) != 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);
}
}
}
if ((aux_custom == 1) &&
((r = tx_set_value(t, t_aux_state_custom,
SCF_PROPERTY_AUX_STATE_CUSTOM, SCF_TYPE_ASTRING,
v_aux_state_custom)) != 0 ||
(r = tx_set_value(t, t_aux_reason, SCF_PROPERTY_AUX_REASON,
SCF_TYPE_ASTRING, v_aux_reason)) != 0 ||
(aux_textdomain != NULL && (r = tx_set_value(t,
t_aux_textdomain, SCF_PROPERTY_AUX_TEXTDOMAIN,
SCF_TYPE_ASTRING, v_aux_textdomain)) != 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);
}
}
if ((aux_custom == 0) &&
((r = tx_delete_prop(t, t_aux_state_custom,
SCF_PROPERTY_AUX_STATE_CUSTOM)) != 0 ||
(r = tx_delete_prop(t, t_aux_reason,
SCF_PROPERTY_AUX_REASON)) != 0 ||
(r = tx_delete_prop(t, t_aux_textdomain,
SCF_PROPERTY_AUX_TEXTDOMAIN)) != 0)) {
switch (r) {
case ECONNABORTED:
ret = ECONNABORTED;
goto out;
case ECANCELED:
scf_transaction_reset(t);
goto add_pg;
default:
bad_fail("tx_delete_prop", 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_entry_destroy(t_aux_state_custom);
scf_entry_destroy(t_aux_reason);
scf_entry_destroy(t_aux_textdomain);
scf_value_destroy(v_state);
scf_value_destroy(v_state_next);
scf_value_destroy(v_stime);
scf_value_destroy(v_aux);
scf_value_destroy(v_aux_state_custom);
scf_value_destroy(v_aux_reason);
scf_value_destroy(v_aux_textdomain);
scf_pg_destroy(pg);
scf_instance_destroy(s_inst);
return (ret);
}
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)
{
if (aux != NULL &&
strcmp(aux, restarter_get_str_short(restarter_str_custom)) == 0) {
uu_warn("%s: restarter set auxiliary state to \"%s\" without "
"setting custom reason strings\n", id->i_fmri, aux);
aux = NULL;
}
return (_restarter_commit_states_internal(h, id, new_state,
new_state_next, aux, 0, NULL, NULL, NULL));
}
int
_restarter_commit_states_custom(scf_handle_t *h, instance_data_t *id,
restarter_instance_state_t new_state,
restarter_instance_state_t new_state_next,
const char *aux_short, const char *aux_long, const char *aux_textdomain)
{
const char *s = (aux_short == NULL) ? "" : aux_short;
const char *l = (aux_long == NULL) ? "" : aux_long;
if (s != aux_short || l != aux_long) {
uu_warn("%s: restarter supplied NULL custom state "
"(\"%s\", \"%s\")\n", id->i_fmri, s, l);
}
return (_restarter_commit_states_internal(h, id, new_state,
new_state_next, restarter_get_str_short(restarter_str_custom),
1, s, l, aux_textdomain));
}
/*
* 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:
case SCF_ERROR_PERMISSION_DENIED:
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:
case SCF_ERROR_PERMISSION_DENIED:
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 (pg == NULL)
return (-1);
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);
}
static int
get_boolean_val(scf_propertygroup_t *pg, const char *name, uint8_t *b,
scf_property_t *prop, scf_value_t *val)
{
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);
}
if (scf_value_get_boolean(val, b))
return (-1);
return (0);
}
/*
* 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);
}
/*
* Return an error message structure containing the error message
* with context, and the error so the caller can make a decision
* on what to do next.
*
* Because get_ids uses the mc_error_create() function which can
* reallocate the merr, this function must return the merr pointer
* in case it was reallocated.
*/
static mc_error_t *
get_profile(scf_propertygroup_t *methpg, scf_propertygroup_t *instpg,
scf_property_t *prop, scf_value_t *val, const char *cmdline,
struct method_context *ci, mc_error_t *merr)
{
char *buf = ci->vbuf;
ssize_t buf_sz = ci->vbuf_sz;
char cmd[PATH_MAX];
char *cp, *value;
const char *cmdp;
execattr_t *eap;
mc_error_t *err = merr;
int r;
if (!(get_astring_val(methpg, SCF_PROPERTY_PROFILE, buf, buf_sz, prop,
val) == 0 || get_astring_val(instpg, SCF_PROPERTY_PROFILE, buf,
buf_sz, prop, val) == 0))
return (mc_error_create(merr, scf_error(),
"Method context requires a profile, but the \"%s\" "
"property could not be read. scf_error is %s",
SCF_PROPERTY_PROFILE, scf_strerror(scf_error())));
/* 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 (mc_error_create(merr, ENOENT,
"Could not find the execution profile \"%s\", "
"command %s.", buf, cmdp));
/* 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 ((r = get_uid(value, ci, &ci->euid)) != 0) {
ci->euid = (uid_t)-1;
err = mc_error_create(merr, r,
"Could not interpret profile euid value \"%s\", "
"from the execution profile \"%s\", error %d.",
value, buf, r);
goto out;
}
}
if ((value = kva_match(eap->attr, EXECATTR_UID_KW)) != NULL) {
if ((r = get_uid(value, ci, &ci->uid)) != 0) {
ci->euid = ci->uid = (uid_t)-1;
err = mc_error_create(merr, r,
"Could not interpret profile uid value \"%s\", "
"from the execution profile \"%s\", error %d.",
value, buf, r);
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) {
err = mc_error_create(merr, EINVAL,
"Could not interpret profile gid value \"%s\", "
"from the execution profile \"%s\".", value, buf);
goto out;
}
}
if ((value = kva_match(eap->attr, EXECATTR_EGID_KW)) != NULL) {
ci->egid = get_gid(value);
if (ci->egid == (gid_t)-1) {
err = mc_error_create(merr, EINVAL,
"Could not interpret profile egid value \"%s\", "
"from the execution profile \"%s\".", value, buf);
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)
err = mc_error_create(merr, ENOMEM,
ALLOCFAIL);
else
err = mc_error_create(merr, EINVAL,
"Could not interpret profile "
"limitprivs value \"%s\", from "
"the execution profile \"%s\".",
value, buf);
goto out;
}
}
if ((value = kva_match(eap->attr, EXECATTR_IPRIV_KW)) != NULL) {
ci->priv_set = NULL;
ci->xpctxt = NULL;
if (xpol_parse_ruleset(value, &ci->xpctxt, &ci->priv_set) < 0) {
if (errno != EINVAL)
err = mc_error_create(merr, ENOMEM,
ALLOCFAIL);
else
err = mc_error_create(merr, EINVAL,
"Could not interpret profile privs value "
"\"%s\", from the execution profile "
"\"%s\".", value, buf);
goto out;
}
/*
* The exec_attr privs keyword only lists additional
* privileges; we need to add the inheritable set.
*/
if (ci->priv_set != NULL) {
priv_set_t *iset = priv_allocset();
if (iset == NULL) {
err = mc_error_create(merr, ENOMEM, ALLOCFAIL);
goto out;
}
if (getppriv(PRIV_INHERITABLE, iset) != 0) {
err = mc_error_create(merr, errno,
"Could not get the inheritable privset");
priv_freeset(iset);
goto out;
}
priv_union(iset, ci->priv_set);
priv_freeset(iset);
}
}
out:
free_execattr(eap);
return (err);
}
/*
* Return an error message structure containing the error message
* with context, and the error so the caller can make a decision
* on what to do next.
*
* Because get_ids uses the mc_error_create() function which can
* reallocate the merr, this function must return the merr pointer
* in case it was reallocated.
*/
static mc_error_t *
get_ids(scf_propertygroup_t *methpg, scf_propertygroup_t *instpg,
scf_property_t *prop, scf_value_t *val, struct method_context *ci,
mc_error_t *merr)
{
char *vbuf = ci->vbuf;
ssize_t vbuf_sz = ci->vbuf_sz;
int r;
/*
* This should never happen because the caller should fall through
* another path of just setting the ids to defaults, instead of
* attempting to get the ids here.
*/
if (methpg == NULL && instpg == NULL)
return (mc_error_create(merr, ENOENT,
"No property groups to get ids from."));
if (!(get_astring_val(methpg, SCF_PROPERTY_USER,
vbuf, vbuf_sz, prop, val) == 0 || get_astring_val(instpg,
SCF_PROPERTY_USER, vbuf, vbuf_sz, prop,
val) == 0))
return (mc_error_create(merr, ENOENT,
"Could not get \"%s\" property.", SCF_PROPERTY_USER));
if ((r = get_uid(vbuf, ci, &ci->uid)) != 0) {
ci->uid = (uid_t)-1;
return (mc_error_create(merr, r,
"Could not interpret \"%s\" property value \"%s\", "
"error %d.", SCF_PROPERTY_USER, vbuf, r));
}
if (!(get_astring_val(methpg, SCF_PROPERTY_GROUP, vbuf, vbuf_sz, prop,
val) == 0 || get_astring_val(instpg, SCF_PROPERTY_GROUP, vbuf,
vbuf_sz, prop, val) == 0)) {
if (scf_error() == SCF_ERROR_NOT_FOUND) {
(void) strcpy(vbuf, ":default");
} else {
return (mc_error_create(merr, ENOENT,
"Could not get \"%s\" property.",
SCF_PROPERTY_GROUP));
}
}
if (strcmp(vbuf, ":default") != 0) {
ci->gid = get_gid(vbuf);
if (ci->gid == (gid_t)-1) {
return (mc_error_create(merr, ENOENT,
"Could not interpret \"%s\" property value \"%s\".",
SCF_PROPERTY_GROUP, vbuf));
}
} else {
switch (r = lookup_pwd(ci)) {
case 0:
ci->gid = ci->pwd.pw_gid;
break;
case ENOENT:
ci->gid = (gid_t)-1;
return (mc_error_create(merr, ENOENT,
"No passwd entry for uid \"%d\".", ci->uid));
case ENOMEM:
return (mc_error_create(merr, ENOMEM,
"Out of memory."));
case EIO:
case EMFILE:
case ENFILE:
return (mc_error_create(merr, ENFILE,
"getpwuid_r() failed, error %d.", r));
default:
bad_fail("lookup_pwd", r);
}
}
if (!(get_astring_val(methpg, SCF_PROPERTY_SUPP_GROUPS, vbuf, vbuf_sz,
prop, val) == 0 || get_astring_val(instpg,
SCF_PROPERTY_SUPP_GROUPS, vbuf, vbuf_sz, prop, val) == 0)) {
if (scf_error() == SCF_ERROR_NOT_FOUND) {
(void) strcpy(vbuf, ":default");
} else {
return (mc_error_create(merr, ENOENT,
"Could not get supplemental groups (\"%s\") "
"property.", SCF_PROPERTY_SUPP_GROUPS));
}
}
if (strcmp(vbuf, ":default") != 0) {
switch (r = get_groups(vbuf, ci)) {
case 0:
break;
case EINVAL:
return (mc_error_create(merr, EINVAL,
"Could not interpret supplemental groups (\"%s\") "
"property value \"%s\".", SCF_PROPERTY_SUPP_GROUPS,
vbuf));
case E2BIG:
return (mc_error_create(merr, E2BIG,
"Too many supplemental groups values in \"%s\".",
vbuf));
default:
bad_fail("get_groups", r);
}
} else {
ci->ngroups = -1;
}
if (!(get_astring_val(methpg, SCF_PROPERTY_PRIVILEGES, vbuf, vbuf_sz,
prop, val) == 0 || get_astring_val(instpg, SCF_PROPERTY_PRIVILEGES,
vbuf, vbuf_sz, prop, val) == 0)) {
if (scf_error() == SCF_ERROR_NOT_FOUND) {
(void) strcpy(vbuf, ":default");
} else {
return (mc_error_create(merr, ENOENT,
"Could not get \"%s\" property.",
SCF_PROPERTY_PRIVILEGES));
}
}
/*
* For default privs, we need to keep priv_set == NULL, as
* we use this test elsewhere.
*/
if (strcmp(vbuf, ":default") != 0) {
ci->priv_set = NULL;
ci->xpctxt = NULL;
if (xpol_parse_ruleset(vbuf, &ci->xpctxt, &ci->priv_set) < 0) {
if (errno != EINVAL) {
return (mc_error_create(merr, ENOMEM,
ALLOCFAIL));
} else {
return (mc_error_create(merr, EINVAL,
"Could not interpret \"%s\" "
"property value \"%s\".",
SCF_PROPERTY_PRIVILEGES, vbuf));
}
}
}
if (!(get_astring_val(methpg, SCF_PROPERTY_LIMIT_PRIVILEGES, vbuf,
vbuf_sz, prop, val) == 0 || get_astring_val(instpg,
SCF_PROPERTY_LIMIT_PRIVILEGES, vbuf, vbuf_sz, prop, val) == 0)) {
if (scf_error() == SCF_ERROR_NOT_FOUND) {
(void) strcpy(vbuf, ":default");
} else {
return (mc_error_create(merr, ENOENT,
"Could not get \"%s\" property.",
SCF_PROPERTY_LIMIT_PRIVILEGES));
}
}
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) {
return (mc_error_create(merr, ENOMEM, ALLOCFAIL));
} else {
return (mc_error_create(merr, EINVAL,
"Could not interpret \"%s\" property value \"%s\".",
SCF_PROPERTY_LIMIT_PRIVILEGES, vbuf));
}
}
return (merr);
}
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.
*
* If no method_context is defined, original init context is provided, where
* the working directory is '/', and uid/gid are 0/0. But if a method_context
* is defined at any level the smf_method(5) method_context defaults are used.
*
* Return an error message structure containing the error message
* with context, and the error so the caller can make a decision
* on what to do next.
*
* Error Types :
* E2BIG Too many values or entry is too big
* EINVAL Invalid value
* EIO an I/O error has occured
* ENOENT no entry for value
* ENOMEM out of memory
* ENOTSUP Version mismatch
* ERANGE value is out of range
* EMFILE/ENFILE out of file descriptors
*
* SCF_ERROR_BACKEND_ACCESS
* SCF_ERROR_CONNECTION_BROKEN
* SCF_ERROR_DELETED
* SCF_ERROR_CONSTRAINT_VIOLATED
* SCF_ERROR_HANDLE_DESTROYED
* SCF_ERROR_INTERNAL
* SCF_ERROR_INVALID_ARGUMENT
* SCF_ERROR_NO_MEMORY
* SCF_ERROR_NO_RESOURCES
* SCF_ERROR_NOT_BOUND
* SCF_ERROR_NOT_FOUND
* SCF_ERROR_NOT_SET
* SCF_ERROR_TYPE_MISMATCH
*
*/
mc_error_t *
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 = 0;
int mc_used = 0;
mc_error_t *err = NULL;
struct method_context *cip;
if ((err = malloc(sizeof (mc_error_t))) == NULL)
return (mc_error_create(NULL, ENOMEM, NULL));
/* Set the type to zero to track if an error occured. */
err->type = 0;
if (version != RESTARTER_METHOD_CONTEXT_VERSION)
return (mc_error_create(err, ENOTSUP,
"Invalid client version %d. (Expected %d)",
version, RESTARTER_METHOD_CONTEXT_VERSION));
/* Get the handle before we allocate anything. */
h = scf_instance_handle(inst);
if (h == NULL)
return (mc_error_create(err, scf_error(),
scf_strerror(scf_error())));
cip = malloc(sizeof (*cip));
if (cip == NULL)
return (mc_error_create(err, ENOMEM, 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 (mc_error_create(err, ENOMEM, 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) {
err = mc_error_create(err, scf_error(),
"Failed to create repository object: %s\n",
scf_strerror(scf_error()));
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) {
err = mc_error_create(err, scf_error(), "Unable to get the "
"\"%s\" method, %s", mname, 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) {
err = mc_error_create(err, scf_error(),
"Unable to retrieve the \"%s\" property group, %s",
SCF_PG_METHOD_CONTEXT, scf_strerror(scf_error()));
goto out;
}
scf_pg_destroy(instpg);
instpg = NULL;
} else {
mc_used++;
}
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:
mc_used++;
break;
case ENOENT:
break;
case ENOMEM:
err = mc_error_create(err, ret, "Out of memory.");
goto out;
case EINVAL:
err = mc_error_create(err, ret, "Invalid method environment.");
goto out;
default:
err = mc_error_create(err, ret,
"Get method environment failed : %s\n", 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 = NULL;
ret = scf_pg_get_property(instpg, SCF_PROPERTY_USE_PROFILE,
prop);
}
if (ret) {
switch (scf_error()) {
case SCF_ERROR_NOT_FOUND:
/* No profile context: use default credentials */
cip->uid = 0;
cip->gid = 0;
break;
case SCF_ERROR_CONNECTION_BROKEN:
err = mc_error_create(err, SCF_ERROR_CONNECTION_BROKEN,
RCBROKEN);
goto out;
case SCF_ERROR_DELETED:
err = mc_error_create(err, SCF_ERROR_NOT_FOUND,
"Could not find property group \"%s\"",
pg == NULL ? SCF_PG_METHOD_CONTEXT : mname);
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());
}
} else {
if (scf_property_type(prop, &ty) != SCF_SUCCESS) {
ret = scf_error();
switch (ret) {
case SCF_ERROR_CONNECTION_BROKEN:
err = mc_error_create(err,
SCF_ERROR_CONNECTION_BROKEN, RCBROKEN);
break;
case SCF_ERROR_DELETED:
err = mc_error_create(err,
SCF_ERROR_NOT_FOUND,
"Could not find property group \"%s\"",
pg == NULL ? SCF_PG_METHOD_CONTEXT : mname);
break;
case SCF_ERROR_NOT_SET:
default:
bad_fail("scf_property_type", ret);
}
goto out;
}
if (ty != SCF_TYPE_BOOLEAN) {
err = mc_error_create(err,
SCF_ERROR_TYPE_MISMATCH,
"\"%s\" property is not boolean in property group "
"\"%s\".", SCF_PROPERTY_USE_PROFILE,
pg == NULL ? SCF_PG_METHOD_CONTEXT : mname);
goto out;
}
if (scf_property_get_value(prop, val) != SCF_SUCCESS) {
ret = scf_error();
switch (ret) {
case SCF_ERROR_CONNECTION_BROKEN:
err = mc_error_create(err,
SCF_ERROR_CONNECTION_BROKEN, RCBROKEN);
break;
case SCF_ERROR_CONSTRAINT_VIOLATED:
err = mc_error_create(err,
SCF_ERROR_CONSTRAINT_VIOLATED,
"\"%s\" property has multiple values.",
SCF_PROPERTY_USE_PROFILE);
break;
case SCF_ERROR_NOT_FOUND:
err = mc_error_create(err,
SCF_ERROR_NOT_FOUND,
"\"%s\" property has no values.",
SCF_PROPERTY_USE_PROFILE);
break;
default:
bad_fail("scf_property_get_value", ret);
}
goto out;
}
mc_used++;
ret = scf_value_get_boolean(val, &use_profile);
assert(ret == SCF_SUCCESS);
/* get ids & privileges */
if (use_profile)
err = get_profile(pg, instpg, prop, val, cmdline,
cip, err);
else
err = get_ids(pg, instpg, prop, val, cip, err);
if (err->type != 0)
goto out;
}
/* get working directory */
if ((methpg != NULL && scf_pg_get_property(methpg,
SCF_PROPERTY_WORKING_DIRECTORY, prop) == SCF_SUCCESS) ||
(instpg != NULL && scf_pg_get_property(instpg,
SCF_PROPERTY_WORKING_DIRECTORY, prop) == SCF_SUCCESS)) {
if (scf_property_get_value(prop, val) != SCF_SUCCESS) {
ret = scf_error();
switch (ret) {
case SCF_ERROR_CONNECTION_BROKEN:
err = mc_error_create(err, ret, RCBROKEN);
break;
case SCF_ERROR_CONSTRAINT_VIOLATED:
err = mc_error_create(err, ret,
"\"%s\" property has multiple values.",
SCF_PROPERTY_WORKING_DIRECTORY);
break;
case SCF_ERROR_NOT_FOUND:
err = mc_error_create(err, ret,
"\"%s\" property has no values.",
SCF_PROPERTY_WORKING_DIRECTORY);
break;
default:
bad_fail("scf_property_get_value", ret);
}
goto out;
}
mc_used++;
ret = scf_value_get_astring(val, cip->vbuf, cip->vbuf_sz);
assert(ret != -1);
} else {
ret = scf_error();
switch (ret) {
case SCF_ERROR_NOT_FOUND:
/* okay if missing. */
(void) strcpy(cip->vbuf, ":default");
break;
case SCF_ERROR_CONNECTION_BROKEN:
err = mc_error_create(err, ret, RCBROKEN);
goto out;
case SCF_ERROR_DELETED:
err = mc_error_create(err, ret,
"Property group could not be found");
goto out;
case SCF_ERROR_HANDLE_MISMATCH:
case SCF_ERROR_INVALID_ARGUMENT:
case SCF_ERROR_NOT_SET:
default:
bad_fail("scf_pg_get_property", ret);
}
}
if (strcmp(cip->vbuf, ":default") == 0 ||
strcmp(cip->vbuf, ":home") == 0) {
switch (ret = lookup_pwd(cip)) {
case 0:
break;
case ENOMEM:
err = mc_error_create(err, ret, "Out of memory.");
goto out;
case ENOENT:
case EIO:
case EMFILE:
case ENFILE:
err = mc_error_create(err, ret,
"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) {
err = mc_error_create(err, ENOMEM, ALLOCFAIL);
goto out;
}
} else {
cip->working_dir = strdup(cip->vbuf);
if (cip->working_dir == NULL) {
err = mc_error_create(err, ENOMEM, ALLOCFAIL);
goto out;
}
}
/* get (optional) corefile pattern */
if ((methpg != NULL && scf_pg_get_property(methpg,
SCF_PROPERTY_COREFILE_PATTERN, prop) == SCF_SUCCESS) ||
(instpg != NULL && scf_pg_get_property(instpg,
SCF_PROPERTY_COREFILE_PATTERN, prop) == SCF_SUCCESS)) {
if (scf_property_get_value(prop, val) != SCF_SUCCESS) {
ret = scf_error();
switch (ret) {
case SCF_ERROR_CONNECTION_BROKEN:
err = mc_error_create(err, ret, RCBROKEN);
break;
case SCF_ERROR_CONSTRAINT_VIOLATED:
err = mc_error_create(err, ret,
"\"%s\" property has multiple values.",
SCF_PROPERTY_COREFILE_PATTERN);
break;
case SCF_ERROR_NOT_FOUND:
err = mc_error_create(err, ret,
"\"%s\" property has no values.",
SCF_PROPERTY_COREFILE_PATTERN);
break;
default:
bad_fail("scf_property_get_value", ret);
}
} else {
ret = scf_value_get_astring(val, cip->vbuf,
cip->vbuf_sz);
assert(ret != -1);
cip->corefile_pattern = strdup(cip->vbuf);
if (cip->corefile_pattern == NULL) {
err = mc_error_create(err, ENOMEM, ALLOCFAIL);
goto out;
}
}
mc_used++;
} else {
ret = scf_error();
switch (ret) {
case SCF_ERROR_NOT_FOUND:
/* okay if missing. */
break;
case SCF_ERROR_CONNECTION_BROKEN:
err = mc_error_create(err, ret, RCBROKEN);
goto out;
case SCF_ERROR_DELETED:
err = mc_error_create(err, ret,
"Property group could not be found");
goto out;
case SCF_ERROR_HANDLE_MISMATCH:
case SCF_ERROR_INVALID_ARGUMENT:
case SCF_ERROR_NOT_SET:
default:
bad_fail("scf_pg_get_property", ret);
}
}
if (restarter_rm_libs_loadable()) {
/* get project */
if ((methpg != NULL && scf_pg_get_property(methpg,
SCF_PROPERTY_PROJECT, prop) == SCF_SUCCESS) ||
(instpg != NULL && scf_pg_get_property(instpg,
SCF_PROPERTY_PROJECT, prop) == SCF_SUCCESS)) {
if (scf_property_get_value(prop, val) != SCF_SUCCESS) {
ret = scf_error();
switch (ret) {
case SCF_ERROR_CONNECTION_BROKEN:
err = mc_error_create(err, ret,
RCBROKEN);
break;
case SCF_ERROR_CONSTRAINT_VIOLATED:
err = mc_error_create(err, ret,
"\"%s\" property has multiple "
"values.", SCF_PROPERTY_PROJECT);
break;
case SCF_ERROR_NOT_FOUND:
err = mc_error_create(err, ret,
"\"%s\" property has no values.",
SCF_PROPERTY_PROJECT);
break;
default:
bad_fail("scf_property_get_value", ret);
}
(void) strcpy(cip->vbuf, ":default");
} else {
ret = scf_value_get_astring(val, cip->vbuf,
cip->vbuf_sz);
assert(ret != -1);
}
mc_used++;
} else {
(void) strcpy(cip->vbuf, ":default");
}
switch (ret = get_projid(cip->vbuf, cip)) {
case 0:
break;
case ENOMEM:
err = mc_error_create(err, ret, "Out of memory.");
goto out;
case ENOENT:
err = mc_error_create(err, ret,
"Missing passwd or project entry for \"%s\".",
cip->vbuf);
goto out;
case EIO:
err = mc_error_create(err, ret, "I/O error.");
goto out;
case EMFILE:
case ENFILE:
err = mc_error_create(err, ret,
"Out of file descriptors.");
goto out;
case -1:
err = mc_error_create(err, ret,
"Name service switch is misconfigured.");
goto out;
case ERANGE:
case E2BIG:
err = mc_error_create(err, ret,
"Project ID \"%s\" too big.", cip->vbuf);
goto out;
case EINVAL:
err = mc_error_create(err, ret,
"Project ID \"%s\" is invalid.", cip->vbuf);
goto out;
default:
bad_fail("get_projid", ret);
}
/* get resource pool */
if ((methpg != NULL && scf_pg_get_property(methpg,
SCF_PROPERTY_RESOURCE_POOL, prop) == SCF_SUCCESS) ||
(instpg != NULL && scf_pg_get_property(instpg,
SCF_PROPERTY_RESOURCE_POOL, prop) == SCF_SUCCESS)) {
if (scf_property_get_value(prop, val) != SCF_SUCCESS) {
ret = scf_error();
switch (ret) {
case SCF_ERROR_CONNECTION_BROKEN:
err = mc_error_create(err, ret,
RCBROKEN);
break;
case SCF_ERROR_CONSTRAINT_VIOLATED:
err = mc_error_create(err, ret,
"\"%s\" property has multiple "
"values.",
SCF_PROPERTY_RESOURCE_POOL);
break;
case SCF_ERROR_NOT_FOUND:
err = mc_error_create(err, ret,
"\"%s\" property has no "
"values.",
SCF_PROPERTY_RESOURCE_POOL);
break;
default:
bad_fail("scf_property_get_value", ret);
}
(void) strcpy(cip->vbuf, ":default");
} else {
ret = scf_value_get_astring(val, cip->vbuf,
cip->vbuf_sz);
assert(ret != -1);
}
mc_used++;
} else {
ret = scf_error();
switch (ret) {
case SCF_ERROR_NOT_FOUND:
/* okay if missing. */
(void) strcpy(cip->vbuf, ":default");
break;
case SCF_ERROR_CONNECTION_BROKEN:
err = mc_error_create(err, ret, RCBROKEN);
goto out;
case SCF_ERROR_DELETED:
err = mc_error_create(err, ret,
"property group could not be found.");
goto out;
case SCF_ERROR_HANDLE_MISMATCH:
case SCF_ERROR_INVALID_ARGUMENT:
case SCF_ERROR_NOT_SET:
default:
bad_fail("scf_pg_get_property", ret);
}
}
if (strcmp(cip->vbuf, ":default") != 0) {
cip->resource_pool = strdup(cip->vbuf);
if (cip->resource_pool == NULL) {
err = mc_error_create(err, ENOMEM, ALLOCFAIL);
goto out;
}
}
}
/*
* A method_context was not used for any configurable
* elements or attributes, so reset and use the simple
* defaults that provide historic init behavior.
*/
if (mc_used == 0) {
free(cip->pwbuf);
free(cip->vbuf);
free(cip->working_dir);
(void) memset(cip, 0, sizeof (*cip));
cip->uid = 0;
cip->gid = 0;
cip->euid = (uid_t)-1;
cip->egid = (gid_t)-1;
}
*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);
cip->pwbuf = NULL;
cip->vbuf = NULL;
if (err->type != 0) {
restarter_free_method_context(cip);
} else {
restarter_mc_error_destroy(err);
err = NULL;
}
return (err);
}
/*
* 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;
}
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 = "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->xpctxt != NULL) {
priv_set_t *rset = xpol_restricted_privs(cip->xpctxt);
if (rset == NULL)
goto out;
if (setppriv(PRIV_OFF, PRIV_INHERITABLE, rset) != 0) {
ret = errno;
assert(ret == EFAULT || ret == EPERM);
priv_freeset(rset);
goto out;
}
priv_freeset(rset);
if (xpol_context_install(cip->xpctxt) != 0) {
ret = errno;
assert(ret == EFAULT);
goto out;
}
}
/*
* If the limit privset is already set, then must be privilege
* aware. Otherwise, don't assume anything, and force privilege
* aware status.
*/
if (cip->lpriv_set == NULL && cip->priv_set != NULL) {
ret = setpflags(PRIV_AWARE, 1);
assert(ret == 0);
}
*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->xpctxt != NULL)
xpol_context_free(mcp->xpctxt);
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)
{
}
/*
* Check for and validate fmri specified in restarter_actions/auxiliary_fmri
* 0 - Success
* 1 - Failure
*/
int
restarter_inst_validate_ractions_aux_fmri(scf_instance_t *inst)
{
scf_handle_t *h;
scf_propertygroup_t *pg;
scf_property_t *prop;
scf_value_t *val;
char *aux_fmri;
size_t size = scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH);
int ret = 1;
if ((aux_fmri = malloc(size)) == NULL)
return (1);
h = scf_instance_handle(inst);
pg = scf_pg_create(h);
prop = scf_property_create(h);
val = scf_value_create(h);
if (pg == NULL || prop == NULL || val == NULL)
goto out;
if (instance_get_or_add_pg(inst, SCF_PG_RESTARTER_ACTIONS,
SCF_PG_RESTARTER_ACTIONS_TYPE, SCF_PG_RESTARTER_ACTIONS_FLAGS,
pg) != SCF_SUCCESS)
goto out;
if (get_astring_val(pg, SCF_PROPERTY_AUX_FMRI, aux_fmri, size,
prop, val) != SCF_SUCCESS)
goto out;
if (scf_parse_fmri(aux_fmri, NULL, NULL, NULL, NULL, NULL,
NULL) != SCF_SUCCESS)
goto out;
ret = 0;
out:
free(aux_fmri);
scf_value_destroy(val);
scf_property_destroy(prop);
scf_pg_destroy(pg);
return (ret);
}
/*
* Get instance's boolean value in restarter_actions/auxiliary_tty
* Return -1 on failure
*/
int
restarter_inst_ractions_from_tty(scf_instance_t *inst)
{
scf_handle_t *h;
scf_propertygroup_t *pg;
scf_property_t *prop;
scf_value_t *val;
uint8_t has_tty;
int ret = -1;
h = scf_instance_handle(inst);
pg = scf_pg_create(h);
prop = scf_property_create(h);
val = scf_value_create(h);
if (pg == NULL || prop == NULL || val == NULL)
goto out;
if (instance_get_or_add_pg(inst, SCF_PG_RESTARTER_ACTIONS,
SCF_PG_RESTARTER_ACTIONS_TYPE, SCF_PG_RESTARTER_ACTIONS_FLAGS,
pg) != SCF_SUCCESS)
goto out;
if (get_boolean_val(pg, SCF_PROPERTY_AUX_TTY, &has_tty, prop,
val) != SCF_SUCCESS)
goto out;
ret = has_tty;
out:
scf_value_destroy(val);
scf_property_destroy(prop);
scf_pg_destroy(pg);
return (ret);
}
static int
restarter_inst_set_astring_prop(scf_instance_t *inst, const char *pgname,
const char *pgtype, uint32_t pgflags, const char *pname, const char *str)
{
scf_handle_t *h;
scf_propertygroup_t *pg;
scf_transaction_t *t;
scf_transaction_entry_t *e;
scf_value_t *v;
int ret = 1, r;
h = scf_instance_handle(inst);
pg = scf_pg_create(h);
t = scf_transaction_create(h);
e = scf_entry_create(h);
v = scf_value_create(h);
if (pg == NULL || t == NULL || e == NULL || v == NULL)
goto out;
if (instance_get_or_add_pg(inst, pgname, pgtype, pgflags, pg))
goto out;
if (scf_value_set_astring(v, str) != SCF_SUCCESS)
goto out;
for (;;) {
if (scf_transaction_start(t, pg) != 0)
goto out;
if (tx_set_value(t, e, pname, SCF_TYPE_ASTRING, v) != 0)
goto out;
if ((r = scf_transaction_commit(t)) == 1)
break;
if (r == -1)
goto out;
scf_transaction_reset(t);
if (scf_pg_update(pg) == -1)
goto out;
}
ret = 0;
out:
scf_transaction_destroy(t);
scf_entry_destroy(e);
scf_value_destroy(v);
scf_pg_destroy(pg);
return (ret);
}
int
restarter_inst_set_aux_fmri(scf_instance_t *inst)
{
scf_handle_t *h;
scf_propertygroup_t *pg;
scf_property_t *prop;
scf_value_t *val;
char *aux_fmri;
size_t size = scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH);
int ret = 1;
if ((aux_fmri = malloc(size)) == NULL)
return (1);
h = scf_instance_handle(inst);
pg = scf_pg_create(h);
prop = scf_property_create(h);
val = scf_value_create(h);
if (pg == NULL || prop == NULL || val == NULL)
goto out;
/*
* Get auxiliary_fmri value from restarter_actions pg
*/
if (instance_get_or_add_pg(inst, SCF_PG_RESTARTER_ACTIONS,
SCF_PG_RESTARTER_ACTIONS_TYPE, SCF_PG_RESTARTER_ACTIONS_FLAGS,
pg) != SCF_SUCCESS)
goto out;
if (get_astring_val(pg, SCF_PROPERTY_AUX_FMRI, aux_fmri, size,
prop, val) != SCF_SUCCESS)
goto out;
/*
* Populate restarter/auxiliary_fmri with the obtained fmri.
*/
ret = restarter_inst_set_astring_prop(inst, SCF_PG_RESTARTER,
SCF_PG_RESTARTER_TYPE, SCF_PG_RESTARTER_FLAGS,
SCF_PROPERTY_AUX_FMRI, aux_fmri);
out:
free(aux_fmri);
scf_value_destroy(val);
scf_property_destroy(prop);
scf_pg_destroy(pg);
return (ret);
}
int
restarter_inst_reset_aux_fmri(scf_instance_t *inst)
{
return (scf_instance_delete_prop(inst,
SCF_PG_RESTARTER, SCF_PROPERTY_AUX_FMRI));
}
int
restarter_inst_reset_ractions_aux_fmri(scf_instance_t *inst)
{
return (scf_instance_delete_prop(inst,
SCF_PG_RESTARTER_ACTIONS, SCF_PROPERTY_AUX_FMRI));
}