startd.c revision 3dd94f79268fa1debdd48a44e49c9958fcbad2eb
/*
* 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
* 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) 2012, Joyent, Inc. All rights reserved.
*/
/*
* startd.c - the master restarter
*
* maintains the service dependency graph based on the information in the
* repository. For each service it also tracks the current state and the
* restarter responsible for the service. Based on the graph, events from the
* repository (mostly administrative requests from svcadm), and messages from
* the restarters, the graph engine makes decisions about how the services
* should be manipulated and sends commands to the appropriate restarters.
* Communication between the graph engine and the restarters is embodied in
* protocol.c.
*
* The second half of svc.startd is the restarter for services managed by
* svc.startd and is primarily contained in restarter.c. It responds to graph
* engine commands by executing methods, updating the repository, and sending
* feedback (mostly state updates) to the graph engine.
*
* Error handling
*
* In general, when svc.startd runs out of memory it reattempts a few times,
* sleeping inbetween, before giving up and exiting (see startd_alloc_retry()).
* When a repository connection is broken (libscf calls fail with
* SCF_ERROR_CONNECTION_BROKEN, librestart and internal functions return
* ECONNABORTED), svc.startd calls libscf_rebind_handle(), which coordinates
* with the svc.configd-restarting thread, fork_configd_thread(), via
* st->st_configd_live_cv, and rebinds the repository handle. Doing so resets
* all libscf state associated with that handle, so functions which do this
* should communicate the event to their callers (usually by returning
* ECONNRESET) so they may reset their state appropriately.
*
* External references
*
* svc.configd generates special security audit events for changes to some
* restarter related properties. See the special_props_list array in
* events. If you change the semantics of these propereties within startd, you
* will probably need to update rc_node.c
*/
#include <stdio.h>
#include <stdio_ext.h>
#include <alloca.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <ftw.h>
#include <libintl.h>
#include <libscf.h>
#include <libscf_priv.h>
#include <libuutil.h>
#include <locale.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include "startd.h"
#include "protocol.h"
const char * const admin_actions[] = {
};
const int admin_events[NACTIONS] = {
};
const char * const instance_state_str[] = {
"none",
"uninitialized",
"maintenance",
"offline",
"disabled",
"online",
"degraded"
};
static int finished = 0;
static int opt_reconfig = 0;
static uint8_t prop_reconfig = 0;
#define INITIAL_REBIND_ATTEMPTS 5
#define INITIAL_REBIND_DELAY 3
#ifdef DEBUG
const char *
_umem_debug_init(void)
{
return ("default,verbose"); /* UMEM_DEBUG setting */
}
const char *
_umem_logging_init(void)
{
return ("fail,contents"); /* UMEM_LOGGING setting */
}
#endif
const char *
_umem_options_init(void)
{
/*
* To reduce our memory footprint, we set our UMEM_OPTIONS to indicate
* that we do not wish to have per-CPU magazines -- if svc.startd is so
* hot on CPU such that this becomes a scalability problem, there are
* likely deeper things amiss...
*/
return ("nomagazines"); /* UMEM_OPTIONS setting */
}
/*
* startd_alloc_retry()
* Wrapper for allocation functions. Retries with a decaying time
* value on failure to allocate, and aborts startd if failure is
* persistent.
*/
void *
{
void *p;
p = f(sz, UMEM_DEFAULT);
return (p);
msecs = ALLOC_DELAY;
p = f(sz, UMEM_DEFAULT);
if (p != NULL)
return (p);
}
uu_die("Insufficient memory.\n");
/* NOTREACHED */
}
void *
{
return (p);
msecs = ALLOC_DELAY;
if (p != NULL)
return (p);
}
uu_die("Insufficient memory.\n");
/* NOTREACHED */
}
char *
safe_strdup(const char *s)
{
char *d;
d = strdup(s);
if (d != NULL)
return (d);
msecs = ALLOC_DELAY;
for (try = 0;
++try) {
d = strdup(s);
if (d != NULL)
return (d);
}
uu_die("Insufficient memory.\n");
/* NOTREACHED */
}
void
{
}
/*
* Creates a uu_list_pool_t with the same retry policy as startd_alloc().
* Only returns NULL for UU_ERROR_UNKNOWN_FLAG and UU_ERROR_NOT_SUPPORTED.
*/
{
return (pool);
msecs = ALLOC_DELAY;
++try) {
return (pool);
}
if (try < ALLOC_RETRY)
return (NULL);
uu_die("Insufficient memory.\n");
/* NOTREACHED */
}
/*
* Creates a uu_list_t with the same retry policy as startd_alloc(). Only
* returns NULL for UU_ERROR_UNKNOWN_FLAG and UU_ERROR_NOT_SUPPORTED.
*/
{
return (list);
msecs = ALLOC_DELAY;
++try) {
return (list);
}
if (try < ALLOC_RETRY)
return (NULL);
uu_die("Insufficient memory.\n");
/* NOTREACHED */
}
{
int err;
if (err != 0) {
uu_die("Could not create thread.\n");
}
return (tid);
}
extern int info_events_all;
static int
read_startd_config(void)
{
char *startd_reconfigure_fmri = uu_msprintf(
"%s/:properties/system/reconfigure", SCF_SERVICE_STARTD);
int bind_fails = 0;
int ret = 0, r;
uu_die("Allocation failure\n");
}
/*
* Read "options" property group.
*/
(void) sleep(INITIAL_REBIND_DELAY);
if (bind_fails > INITIAL_REBIND_ATTEMPTS) {
/*
* In the case that we can't bind to the repository
* (which should have been started), we need to allow
* the user into maintenance mode to determine what's
* failed.
*/
"default settings: %s\n",
scf_strerror(scf_error()));
ret = -1;
goto noscfout;
}
}
case 0:
break;
case ENOMEM:
++count;
if (count < ALLOC_RETRY) {
goto timestamp;
}
uu_die("Insufficient memory.\n");
/* NOTREACHED */
case ECONNABORTED:
goto timestamp;
case ENOENT:
case EPERM:
case EACCES:
case EROFS:
break;
case EINVAL:
default:
bad_error("_restarter_commit_states", r);
}
/* set startd's restarter properties */
ctid = proc_get_ctid();
if (ctid != -1) {
(void) libscf_inst_set_count_prop(inst,
uint64);
}
}
/* Read reconfigure property for recovery. */
/*
* No configuration options defined.
*/
if (scf_error() != SCF_ERROR_NOT_FOUND)
uu_warn("Couldn't read configuration from 'options' "
goto scfout;
}
/*
* If there is no "options" group defined, then our defaults are fine.
*/
goto scfout;
/* get info_events_all */
/* Iterate through. */
continue;
continue;
switch (scf_error()) {
default:
continue;
case SCF_ERROR_DELETED:
continue;
case SCF_ERROR_NOT_BOUND:
case SCF_ERROR_NOT_SET:
}
}
if (ty != SCF_TYPE_ASTRING) {
uu_warn("property \"options/%s\" is not of type "
"astring; ignored.\n", buf);
continue;
}
switch (scf_error()) {
default:
return (ECONNABORTED);
case SCF_ERROR_DELETED:
case SCF_ERROR_NOT_FOUND:
return (0);
uu_warn("property \"options/%s\" has multiple "
"values; ignored.\n", buf);
continue;
uu_warn("property \"options/%s\" cannot be "
"read because startd has insufficient "
"permission; ignored.\n", buf);
continue;
case SCF_ERROR_NOT_BOUND:
case SCF_ERROR_NOT_SET:
bad_error("scf_property_get_value",
scf_error());
}
}
} else {
"value '%s' ignored\n", vbuf);
}
} else {
"options/boot_messages value '%s' "
"ignored\n", vbuf);
}
}
}
(void) scf_handle_unbind(hndl);
if (booting_to_single_user) {
}
/*
* Options passed in as boot arguments override repository defaults.
*/
return (ret);
/* -m debug should send messages to console */
st->st_log_flags =
sizeof ("milestone=") - 1) == 0) {
continue;
st->st_subgraph =
}
"milestone/single-user:default");
"milestone/multi-user:default");
"milestone/multi-user-server:default");
} else {
"invalid milestone option value "
"'%s' ignored\n", mp);
}
} else {
}
}
return (ret);
}
/*
* void set_boot_env()
*
* If -r was passed or /reconfigure exists, this is a reconfig
* reboot. We need to make sure that this information is given
* to the appropriate services the first time they're started
* by setting the system/reconfigure repository property,
* as well as pass the _INIT_RECONFIG variable on to the rcS
* start method so that legacy services can continue to use it.
*
* This function must never be called before contract_init(), as
* it sets st_initial. get_startd_config() sets prop_reconfig from
* pre-existing repository state.
*/
static void
{
int r;
/*
* Check if property still is set -- indicates we didn't get
* far enough previously to unset it. Otherwise, if this isn't
* the first startup, don't re-process /reconfigure or the
* boot flag.
*/
return;
/* If /reconfigure exists, also set opt_reconfig. */
opt_reconfig = 1;
/* Nothing to do. Just return. */
if (opt_reconfig == 0 && prop_reconfig == 0)
return;
/*
* Set startd's reconfigure property. This property is
* then cleared by successful completion of the single-user
* milestone.
*/
if (prop_reconfig != 1) {
r = libscf_set_reconfig(1);
switch (r) {
case 0:
break;
case ENOENT:
case EPERM:
case EACCES:
case EROFS:
"property: %s\n", strerror(r));
break;
default:
bad_error("libscf_set_reconfig", r);
}
}
}
static void
startup(void)
{
int err;
/*
* Initialize data structures.
*/
if (configd_ctid != -1)
"starting svc.configd\n", configd_ctid);
/*
* Await, if necessary, configd's initial arrival.
*/
while (!st->st_configd_lives) {
"configd_live_cv\n");
}
utmpx_init();
wait_init();
if (read_startd_config())
"optional settings\n");
log_init();
dict_init();
timeout_init();
/*
* svc.configd is started by fork_configd_thread so repository access is
* available, run early manifest import before continuing with starting
* graph engine and the rest of startd.
*/
fork_emi();
graph_init();
init_env();
set_boot_env();
}
static void
{
}
static int
daemonize_start(void)
{
int fd;
return (-1);
if (pid != 0)
exit(0);
(void) close(STDIN_FILENO);
} else if (fd != STDIN_FILENO) {
}
closefrom(3);
(void) setsid();
(void) chdir("/");
/* Use default umask that init handed us, but 022 to create files. */
return (0);
}
/*ARGSUSED*/
static void
{
finished = 1;
}
int
{
int opt;
int daemonize = 1;
(void) uu_setpname(argv[0]);
(void) pthread_mutexattr_init(&mutex_attrs);
#ifndef NDEBUG
(void) pthread_mutexattr_settype(&mutex_attrs,
#endif
max_scf_value_size == -1)
uu_die("Can't determine repository maximum lengths.\n");
switch (opt) {
case 'n':
daemonize = 0;
break;
case 'r': /* reconfiguration boot */
opt_reconfig = 1;
break;
case 's': /* single-user mode */
break;
default:
}
}
if (daemonize)
if (daemonize_start() < 0)
uu_die("Can't daemonize\n");
log_init();
for (;;)
(void) pause();
}
startup();
(void) sigemptyset(&nullset);
while (!finished) {
(void) sigsuspend(&nullset);
}
return (0);
}