/*
* 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 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
* Copyright 2012 Milan Jurik. All rights reserved.
*/
/*
* Copyright (c) 2013, Joyent, Inc. All rights reserved.
*/
/*
*
* fork_configd() and fork_sulogin() are related, special cases that handle the
* spawning of specific client processes for svc.startd.
*/
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <libcontract.h>
#include <libcontract_priv.h>
#include <libscf_priv.h>
#include <limits.h>
#include <poll.h>
#include <port.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <utmpx.h>
#include <spawn.h>
#include "manifest_hash.h"
#include "configd_exit.h"
#include "protocol.h"
#include "startd.h"
{
pid_t p;
/*
* prefork stack
*/
wait_prefork();
p = fork1();
/*
* postfork stack
*/
wait_postfork(p);
return (p);
}
/*
* void fork_mount(char *, char *)
* Run mount(1M) with the given options and mount point. (mount(1M) has much
* hidden knowledge; it's much less correct to reimplement that logic here to
* save a fork(2)/exec(2) invocation.)
*/
int
{
int status;
if (++tries > MAX_MOUNT_RETRIES)
return (-1);
}
if (pid != 0) {
/*
* If our mount(1M) invocation exited by peculiar means, or with
* a non-zero status, our mount likelihood is low.
*/
WEXITSTATUS(status) != 0)
return (-1);
return (0);
}
return (-1);
}
/*
* pid_t fork_common(...)
* Common routine used by fork_sulogin, fork_emi, and fork_configd to
* fork a process in a contract with the provided terms. Invokes
* fork_sulogin (with its no-fork argument set) on errors.
*/
static pid_t
{
/*
* Establish process contract terms.
*/
/* NOTREACHED */
}
if (err) {
"terms\n", name);
/* NOTREACHED */
}
/* NOTREACHED */
}
/*
* Attempt to fork "retries" times.
*/
/*
* When we exit the sulogin session, init(1M)
* will restart svc.startd(1M).
*/
(void) ct_tmpl_clear(ctfd);
/* NOTREACHED */
}
}
/*
* Clean up, return pid and ctid.
*/
(void) ct_tmpl_clear(ctfd);
return (pid);
}
/*
* void fork_sulogin(boolean_t, const char *, ...)
* When we are invoked with the -s flag from boot (or run into an unfixable
* situation), we run a private copy of sulogin. When the sulogin session
* is ended, we continue. This is the last fallback action for system
* maintenance.
*
* If immediate is true, fork_sulogin() executes sulogin(1M) directly, without
* forking.
*
* Because fork_sulogin() is needed potentially before we daemonize, we leave
* it outside the wait_register() framework.
*/
/*PRINTFLIKE2*/
void
{
int fd_console;
(void) printf("Requesting System Maintenance Mode\n");
if (!booting_to_single_user)
"information.)\n");
if (!immediate) {
if (pid != 0) {
return;
}
/* close all inherited fds */
closefrom(0);
} else {
(void) printf("Directly executing sulogin.\n");
/*
* Can't call closefrom() in this MT section
* so safely close a minimum set of fds.
*/
(void) close(STDIN_FILENO);
(void) close(STDOUT_FILENO);
(void) close(STDERR_FILENO);
}
(void) setpgrp();
/* open the console for sulogin */
if (fd_console != STDIN_FILENO)
;
if (fd_console != STDOUT_FILENO)
;
if (fd_console != STDERR_FILENO)
;
if (fd_console > STDERR_FILENO)
(void) close(fd_console);
}
setutxent();
break;
}
}
}
uu_warn("Could not exec() sulogin");
exit(1);
}
/*
* void fork_configd(int status)
* We are interested in exit events (since the parent's exiting means configd
* is ready to run and since the child's exiting indicates an error case) and
* in empty events. This means we have a unique template for initiating
* configd.
*/
void
{
int err;
/*
* Checking the existatus for the potential failure of the
* daemonized svc.configd. If this is not the first time
* through, but a call from the svc.configd monitoring thread
* after a failure this is the status that is expected. Other
* failures are exposed during initialization or are fixed
* by a restart (e.g door closings).
*
* If this is on-disk database corruption it will also be
* caught by a restart but could be cleared before the restart.
*
* Or this could be internal database corruption due to a
* rogue service that needs to be cleared before restart.
*/
"corrupt error after initialization of the repository\n");
}
/*
* If we're retrying, we will have an old contract lying around
* from the failure. Since we're going to be creating a new
* contract shortly, we abandon the old one now.
*/
if (ctid != -1)
ctid = -1;
if (pid != 0) {
int exitstatus;
} else if (WIFEXITED(exitstatus)) {
char *errstr;
/*
* Examine exitstatus. This will eventually get more
* complicated, as we will want to teach startd how to
* invoke configd with alternate repositories, etc.
*
* Note that exec(2) failure results in an exit status
* of 1, resulting in the default clause below.
*/
/*
* Assign readable strings to cases we don't handle, or
* have error outcomes that cannot be eliminated.
*/
switch (WEXITSTATUS(exitstatus)) {
case CONFIGD_EXIT_BAD_ARGS:
errstr = "bad arguments";
break;
errstr = "database corrupt";
break;
errstr = "database locked";
break;
case CONFIGD_EXIT_INIT_FAILED:
errstr = "initialization failure";
break;
errstr = "door initialization failure";
break;
errstr = "database initialization failure";
break;
case CONFIGD_EXIT_NO_THREADS:
errstr = "no threads available";
break;
errstr = "lost door server attachment";
break;
case 1:
errstr = "execution failure";
break;
default:
errstr = "unknown error";
break;
}
/*
* Remedial actions for various configd failures.
*/
switch (WEXITSTATUS(exitstatus)) {
case CONFIGD_EXIT_OKAY:
break;
/* attempt remount of / read-write */
"remount of root "
"filesystem failed\n");
goto retry;
}
break;
default:
"with status %d (%s)\n",
goto retry;
}
} else if (WIFSIGNALED(exitstatus)) {
" %s\n", signame);
goto retry;
} else {
"condition: 0x%x\n", exitstatus);
goto retry;
}
/*
* Announce that we have a valid svc.configd status.
*/
"live\n");
return;
}
/*
* Set our per-process core file path to leave core files in
*/
"/etc/svc/volatile/core.configd.%%p");
/*
* Status code is used above to identify configd exec failure.
*/
exit(1);
}
void *
{
if (configd_ctid == -1) {
"fork_configd_thread starting svc.configd\n");
fork_configd(0);
} else {
/*
* configd_ctid is known: we broadcast and continue.
* test contract for appropriate state by verifying that
* there is one or more processes within it?
*/
"fork_configd_thread accepting svc.configd with CTID %ld\n",
}
if (fd == -1)
uu_die("process bundle open failed");
/*
* Make sure we get all events (including those generated by configd
* before this thread was started).
*/
for (;;) {
"Error reading next contract event: %s",
continue;
}
/* Fetch cookie. */
if (sfd < 0) {
continue;
}
continue;
}
/*
* Don't process events from contracts we aren't interested in.
*/
if (cookie != CONFIGD_COOKIE) {
continue;
}
if (type == CT_PR_EV_EXIT) {
int exitstatus;
(void) ct_pr_event_get_exitstatus(ev,
&exitstatus);
/*
* This is the child exiting, so we
* abandon the contract and restart
* configd.
*/
}
}
if (efd != -1) {
}
}
/*NOTREACHED*/
return (NULL);
}
void
{
char *pathenv;
char **nenv;
if (tmpl >= 0) {
} else {
}
if (pid < 0) {
return;
} else if (pid != 0) {
/* parent */
if (wait) {
do
;
"%s terminated with waitpid() status %d.\n",
} else if (WEXITSTATUS(stat) != 0) {
"%s failed with status %d.\n", path,
WEXITSTATUS(stat));
}
}
return;
}
/* child */
if (rl == 'S')
else
perror("exec");
exit(0);
}
/*
* Set Early Manifest Import service's state and log file.
*/
static int
{
/*
* 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.
*/
goto retry;
}
switch (scf_error()) {
case SCF_ERROR_NOT_FOUND:
goto out;
case SCF_ERROR_NOT_BOUND:
goto retry;
default:
"%s\n", SCF_INSTANCE_EMI,
scf_strerror(scf_error()));
goto retry;
}
}
if (setlog) {
"Set logfile property for %s\n", SCF_INSTANCE_EMI);
}
RESTARTER_STATE_NONE, NULL)) {
case 0:
break;
case ECONNABORTED:
goto retry;
case ENOMEM:
case ENOENT:
case EPERM:
case EACCES:
case EROFS:
goto retry;
case EINVAL:
default:
bad_error("_restarter_commit_states", r);
}
ret = 0;
out:
return (ret);
}
/*
* It is possible that the early-manifest-import service is disabled. This
* would not be the normal case for Solaris, but it may happen on dedicated
* property for Early Manifest Import.
*
* It is also possible that the early-manifest-import service does not yet
* have a repository representation when this function runs. This happens
* if non-Early Manifest Import system is upgraded to an Early Manifest
* an error.
*
* Returns 1 if Early Manifest Import is disabled and 0 otherwise.
*/
static int
{
int disabled = 0;
int enabled;
char *pname;
int hashash, r;
/*
* 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.
*/
}
}
while (disconnected) {
if (r != 0) {
switch (r) {
case ECONNABORTED:
continue;
case ENOENT:
/*
* Early Manifest Import service is not in
* the repository. Check the manifest file
* figure out whether Early Manifest Import
* service was deleted. If Early Manifest Import
* service was deleted, treat that as a disable
* and don't run early import.
*/
/*
* Manifest isn't found, so service is
* properly removed.
*/
disabled = 1;
} else {
/*
* If manifest exists and we have the
* hash, the service was improperly
* deleted, generate a warning and treat
* this as a disable.
*/
if ((pname = mhash_filename_to_propname(
/*
* Treat failure to get propname
* as a disable.
*/
disabled = 1;
uu_warn("Failed to get propname"
" for %s.\n",
} else {
NULL) == 0;
if (hashash) {
disabled = 1;
uu_warn("%s service is "
"deleted \n",
}
}
}
disconnected = 0;
continue;
default:
bad_error("libscf_fmri_get_instance",
scf_error());
}
}
if (r == 0) {
/*
* enabled can be returned as -1, which indicates
* that the enabled property was not found. To us
* that means that the service was not disabled.
*/
if (enabled == 0)
disabled = 1;
} else {
switch (r) {
case ECONNABORTED:
continue;
case ECANCELED:
case ENOENT:
break;
default:
bad_error("libscf_get_basic_instance_data", r);
}
}
disconnected = 0;
}
out:
return (disabled);
}
void
fork_emi()
{
char *emipath;
char *svc_state;
int setemilog;
int sz;
if (emi_is_disabled()) {
"not be run.\n", SCF_INSTANCE_EMI);
return;
}
/*
* Early Manifest Import should run only once, at boot. If svc.startd
* is some how restarted, Early Manifest Import should not run again.
* Use the Early Manifest Import service's state to figure out whether
* Early Manifest Import has successfully completed earlier and bail
* out if it did.
*/
return;
}
}
/*
* Attempt to set Early Manifest Import service's state and log file.
* If emi_set_state fails, set log file again in the next call to
* emi_set_state.
*/
/* Don't go further if /usr isn't available */
"supported on systems with a separate /usr filesystem.\n");
return;
}
/*
* If we're retrying, we will have an old contract lying around
* from the failure. Since we're going to be creating a new
* contract shortly, we abandon the old one now.
*/
if (ctid != -1)
ctid = -1;
if (pid != 0) {
int exitstatus;
} else if (WIFEXITED(exitstatus)) {
if (WEXITSTATUS(exitstatus)) {
"%d \n", SCF_INSTANCE_EMI,
goto fork_retry;
}
} else if (WIFSIGNALED(exitstatus)) {
goto fork_retry;
} else {
goto fork_retry;
}
/*
* Once Early Manifest Import completed, the Early Manifest
* Import service must have been imported so set log file and
* state properties. Since this information is required for
* late manifest import and common admin operations, failing to
* set these properties should result in su login so admin can
* correct the problem.
*/
(void) emi_set_state(RESTARTER_STATE_ONLINE,
return;
}
/* child */
/*
* Set our per-process core file path to leave core files in
*/
/*
* Similar to running legacy services, we need to manually set
* log files here and environment variables.
*/
np++;
/*
* Status code is used above to identify Early Manifest Import
* exec failure.
*/
exit(1);
}
extern char **environ;
/*
* A local variation on system(3c) which accepts a timeout argument. This
* allows us to better ensure that the system will actually shut down.
*
* gracetime specifies an amount of time in seconds which the routine must wait
* after the command exits, to allow for asynchronous effects (like sent
* signals) to take effect. This can be zero.
*/
void
{
int err = 0;
int status;
/*
* See also system(3c) in libc. This is very similar, except
* that we avoid some unneeded complexity.
*/
if (err == 0)
/*
* We choose to close fd's above 2, a deviation from system.
*/
if (err == 0)
if (err == 0)
STDERR_FILENO + 1);
(void) sigemptyset(&mask);
if (err == 0)
(void) posix_spawnattr_destroy(&attr);
(void) posix_spawn_file_actions_destroy(&factions);
if (err) {
} else {
for (;;) {
int w;
break;
if (w > 0) {
/*
* Command succeeded, so give it gracetime
* seconds for it to have an effect.
*/
if (status == 0 && msec_gracetime != 0)
break;
}
msec_spent += 100;
/*
* If we timed out, kill off the process, then try to
* wait for it-- it's possible that we could accumulate
* a zombie here since we don't allow waitpid to hang,
* but it's better to let that happen and continue to
* make progress.
*/
if (msec_spent >= msec_timeout) {
uu_warn("'%s' timed out after %d "
"seconds. Killing.\n", cmd,
timeout);
break;
}
}
}
}