startd.c revision 2ba6d2b94a398caab9e751c277f0acbd1cc22c77
/*
* 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 2015, Joyent, Inc.
*/
/*
* 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.
*
* Overview of the SMF Architecture
*
* There are a few different components that make up SMF and are responsible
* for different pieces of functionality that are used:
*
* svc.startd(1M): A daemon that is in charge of starting, stopping, and
* restarting services and instances.
* svc.configd(1M): A daemon that manages the repository that stores
* information, property groups, and state of the different services and
* instances.
* libscf(3LIB): A C library that provides the glue for communicating,
* accessing, and updating information about services and instances.
* svccfg(1M): A utility to add and remove services as well as change the
* properties associated with different services and instances.
* svcadm(1M): A utility to control the different instance of a service. You
* can use this to enable and disable them among some other useful things.
* svcs(1): A utility that reports on the status of various services on the
* system.
*
* The following block diagram explains how these components communicate:
*
* The SMF Block Diagram
* Repository
* This attempts to show +---------+ +--------+
* the relations between | | SQL | |
* the different pieces | configd |<----------->| SQLite |
* that make SMF work and | | Transaction | |
* users/administrators +---------+ +--------+
* call into. ^ ^
* | |
* door_call(3C)| | door_call(3C)
* | |
* v v
* +----------+ +--------+ +--------+ +----------+
* | | | | | | | svccfg |
* | startd |<--->| libscf | | libscf |<---->| svcadm |
* | | | (3LIB) | | (3LIB) | | svcs |
* +----------+ +--------+ +--------+ +----------+
* ^ ^
* | | fork(2)/exec(2)
* | | libcontract(3LIB)
* +-------------------------------------------------------------------+
* | system/filesystem/local:default system/coreadm:default |
* | milestone/multi-user:default system/cron:default |
* | system/console-login:default network/ssh:default |
* +-------------------------------------------------------------------+
*
* Chatting with Configd and Sharing Repository Information
*
* As you run commands with svcs, svccfg, and svcadm, they are all creating a
* libscf handle to communicate with configd. As calls are made via libscf they
* ultimately go and talk to configd to get information. However, how we
* actually are talking to configd is not as straightforward as it appears.
*
* When configd starts up it creates a door located at
* /etc/svc/volatile/repository_door. This door runs the routine called
* invoke svc(cfg|s|adm), one of the first things that occurs is creating a
* scf_handle_t and binding it to configd by calling scf_handle_bind(). This
* function makes a door call to configd and gets returned a new file
* descriptor. This file descriptor is itself another door which calls into
* configd's client_switcher(). This is the door that is actually used when
* getting and fetching properties, and many other useful things.
*
* svc.startd needs a way to notice the changes that occur to the repository.
* For example, if you enabled a service that was not previously running, it's
* up to startd to notice that this has happened, check dependencies, and
* eventually start up the service. The way it gets these notifications is via
* a thread who's sole purpose in life is to call _scf_notify_wait(). This
* function acts like poll(2) but for changes that occur in the repository.
* Once this thread gets the event, it dispatches the event appropriately.
*
* The Events of svc.startd
*
* svc.startd has to handle a lot of complexity. Understanding how you go from
* getting the notification that a service was enabled to actually enabling it
* is not obvious from a cursory glance. The first thing to keep in mind is
* that startd maintains a graph of all the related services and instances so
* it can keep track of what is enabled, what dependencies exist, etc. all so
* that it can answer the question of what is affected by a change. Internally
* there are a lot of different queues for events, threads to process these
* queues, and different paths to have events enter these queues. What follows
* is a diagram that attempts to explain some of those paths, though it's
* important to note that for some of these pieces, such as the graph and
* vertex events, there are many additional ways and code paths these threads
* and functions can take. And yes, restarter_event_enqueue() is not the same
* thing as restarter_queue_event().
*
*
* called by various
* +----------------+ +-------+ +-------------+
* --->| graph_protocol | graph_event | graph | graph_event_ | graph_event |
* --->| _send_event() |------------>| event |----------------->| _thread |
* +----------------+ _enqueue() | queue | dequeue() +-------------+
* +-------+ |
* _scf_notify_wait() vertex_send_event()|
* | v
* | +------------------+ +--------------------+
* +->| repository_event | vertex_send_event() | restarter_protocol |
* | _thread |----------------------------->| _send_event() |
* +------------------+ +--------------------+
* | | out to other
* restarter_ restarter_ | | restarters
* event_dequeue() +-----------+ event_ | | not startd
* +----------------| restarter |<------------+ +------------->
* v | event | enqueue()
* +-----------------+ | queue | +------------------>
* | restarter_event | +-----------+ |+----------------->
* | _thread | ||+---------------->
* | +--------------+ +--------------------+
* | | instance | | restarter_process_ |
* +-------------->| event |------>| events |
* restarter_ | queue | | per-instance lwp |
* queue_event() +--------------+ +--------------------+
* ||| various funcs
* ||| controlling
* ||| instance state
* ||+--------------->
* |+---------------->
* +----------------->
*
* What's important to take away is that there is a queue for each instance on
* the system that handles events related to dealing directly with that
* instance and that events can be added to it because of changes to properties
* that are made to configd and acted upon asynchronously by startd.
*
* 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);
/*
* Call utmpx_init() before creating the fork_configd() thread.
*/
utmpx_init();
/*
* Await, if necessary, configd's initial arrival.
*/
while (!st->st_configd_lives) {
"configd_live_cv\n");
}
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);
}