mirror of
https://github.com/troglobit/finit.git
synced 2026-10-01 21:42:40 +07:00
The code refactored in this commit has long been an eyesore. The
hand-over, from the sequential main() function, to the event loop
was clumsy at best and was very difficult to debug.
Instead of using a series of workers we now delegate everything to
the big state machine by introducing a new SM_BOOTSRTAP_WAIT_STATE.
Both the runparts and rc.local scripts now run in the background, no
longer blocking the event loop from responding to initctl calls. To
ensure they run at the same point in time two things have been done:
1. A new <int/bootstrap> condition has been added which triggers
runparts, which now is a regular task created by conf_init()
2. /etc/rc.local is started from SM_RUNLEVEL_WAIT_STATE when we
transition from runlevel S to any other runlevel.
Fixes #356
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
466 lines
11 KiB
C
466 lines
11 KiB
C
/* Functions for exec'ing processes
|
|
*
|
|
* Copyright (c) 2008-2010 Claudio Matsuoka <cmatsuoka@gmail.com>
|
|
* Copyright (c) 2008-2023 Joachim Wiberg <troglobit@gmail.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#include "config.h" /* Generated by configure script */
|
|
|
|
#include <err.h>
|
|
#include <stdarg.h>
|
|
#include <sysexits.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/prctl.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/ttydefaults.h> /* Not included by default in musl libc */
|
|
#include <termios.h>
|
|
#ifdef _LIBITE_LITE
|
|
# include <libite/lite.h>
|
|
#else
|
|
# include <lite/lite.h>
|
|
#endif
|
|
#include <linux/vt.h>
|
|
|
|
#include "finit.h"
|
|
#include "cgroup.h"
|
|
#include "conf.h"
|
|
#include "helpers.h"
|
|
#include "sig.h"
|
|
#include "util.h"
|
|
#include "utmp-api.h"
|
|
|
|
#define NUM_ARGS 16
|
|
|
|
|
|
/* Wait for process completion, returns status of waitpid(2) syscall */
|
|
int complete(char *cmd, int pid)
|
|
{
|
|
int status = 0;
|
|
|
|
if (waitpid(pid, &status, 0) == -1) {
|
|
if (errno == EINTR)
|
|
warnx("Caught unblocked signal waiting for %s, aborting", cmd);
|
|
else if (errno == ECHILD)
|
|
warnx("Caught SIGCHLD waiting for %s, aborting", cmd);
|
|
else
|
|
warnx("Failed starting %s, error %d: %s", cmd, errno, strerror(errno));
|
|
|
|
return -1;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static int do_redirect(void)
|
|
{
|
|
FILE *fp;
|
|
|
|
fp = fopen("/dev/null", "w");
|
|
if (fp) {
|
|
int fd = fileno(fp);
|
|
|
|
dup2(fd, STDIN_FILENO);
|
|
dup2(fd, STDOUT_FILENO);
|
|
dup2(fd, STDERR_FILENO);
|
|
|
|
return fclose(fp);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Run 'cmd' and wait for completion. If 'log' is not NULL any output
|
|
* from the command is logged with the given string as prefix, use "" to
|
|
* skip. If 'log' is NULL all output is redirected to /dev/null, which
|
|
* is what this function originally did.
|
|
*/
|
|
int run(char *cmd, char *log)
|
|
{
|
|
char *args[NUM_ARGS + 1] = { 0 };
|
|
int status, result, i = 0;
|
|
char *backup = NULL;
|
|
pid_t pid;
|
|
|
|
if (!log) {
|
|
char *arg;
|
|
|
|
/* We must create a copy that is possible to modify. */
|
|
backup = arg = strdup(cmd);
|
|
if (!arg)
|
|
return 1;
|
|
|
|
/* Split command line into tokens of an argv[] array. */
|
|
args[i++] = strsep(&arg, "\t ");
|
|
while (arg && i < NUM_ARGS) {
|
|
/* Handle run("su -c \"dbus-daemon --system\" messagebus");
|
|
* => "su", "-c", "\"dbus-daemon --system\"", "messagebus" */
|
|
if (*arg == '\'' || *arg == '"') {
|
|
char *p, delim[2] = " ";
|
|
|
|
delim[0] = arg[0];
|
|
args[i++] = arg++;
|
|
strsep(&arg, delim);
|
|
p = arg - 1;
|
|
*p = *delim;
|
|
*arg++ = 0;
|
|
} else {
|
|
args[i++] = strsep(&arg, "\t ");
|
|
}
|
|
}
|
|
args[i] = NULL;
|
|
|
|
if (i == NUM_ARGS && arg) {
|
|
errx(1, "Command too long: %s", cmd);
|
|
free(backup);
|
|
errno = EOVERFLOW;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
pid = fork();
|
|
if (0 == pid) {
|
|
int rc = EX_OSERR;
|
|
|
|
setsid();
|
|
sig_unblock();
|
|
if (!log) {
|
|
do_redirect();
|
|
execvp(args[0], args);
|
|
} else {
|
|
char *pfx = *log ? ": " : "";
|
|
FILE *pp;
|
|
|
|
pp = popen(cmd, "r");
|
|
if (pp) {
|
|
char buf[256];
|
|
|
|
while (fgets(buf, sizeof(buf), pp)) {
|
|
chomp(buf);
|
|
logit(LOG_NOTICE, "%s%s%s", log, pfx, buf);
|
|
}
|
|
|
|
rc = pclose(pp);
|
|
}
|
|
}
|
|
|
|
_exit(rc);
|
|
} else if (-1 == pid) {
|
|
err(1, "%s", !log ? args[0] : cmd);
|
|
if (backup)
|
|
free(backup);
|
|
|
|
return -1;
|
|
}
|
|
|
|
status = complete(args[0], pid);
|
|
if (-1 == status) {
|
|
if (backup)
|
|
free(backup);
|
|
return 1;
|
|
}
|
|
|
|
result = WEXITSTATUS(status);
|
|
if (WIFEXITED(status)) {
|
|
dbg("Started '%s' and exit without signal, status: %d", cmd, result);
|
|
} else if (WIFSIGNALED(status)) {
|
|
dbg("Process '%s' terminated by signal %d", cmd, WTERMSIG(status));
|
|
if (!result)
|
|
result = 1; /* Must alert callee the command did not complete successfully.
|
|
* This is necessary since not all programs trap signals and
|
|
* change their return code accordingly. --Jocke */
|
|
}
|
|
|
|
if (backup)
|
|
free(backup);
|
|
|
|
return result;
|
|
}
|
|
|
|
int run_interactive(char *cmd, char *fmt, ...)
|
|
{
|
|
int status, oldout = 1, olderr = 2;
|
|
char line[LINE_SIZE];
|
|
FILE *fp;
|
|
|
|
if (!cmd) {
|
|
errno = EINVAL;
|
|
return 1;
|
|
}
|
|
|
|
if (fmt) {
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
printv(fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
/* Redirect output from cmd to a tempfile */
|
|
fp = tempfile();
|
|
if (fp && !debug) {
|
|
oldout = dup(STDOUT_FILENO);
|
|
olderr = dup(STDERR_FILENO);
|
|
dup2(fileno(fp), STDOUT_FILENO);
|
|
dup2(fileno(fp), STDERR_FILENO);
|
|
}
|
|
|
|
/* Run cmd ... */
|
|
status = run(cmd, "");
|
|
|
|
/* Restore stderr/stdout */
|
|
if (fp && !debug) {
|
|
if (oldout >= 0) {
|
|
dup2(oldout, STDOUT_FILENO);
|
|
close(oldout);
|
|
}
|
|
if (olderr >= 0) {
|
|
dup2(olderr, STDERR_FILENO);
|
|
close(olderr);
|
|
}
|
|
}
|
|
|
|
if (fmt)
|
|
print_result(status);
|
|
|
|
/* Dump any results of cmd on stderr after we've printed [ OK ] or [FAIL] */
|
|
if (fp && !debug) {
|
|
size_t len, written;
|
|
|
|
rewind(fp);
|
|
do {
|
|
len = fread(line, 1, sizeof(line), fp);
|
|
written = fwrite(line, len, sizeof(char), stderr);
|
|
} while (len > 0 && written == len);
|
|
}
|
|
|
|
if (fp)
|
|
fclose(fp);
|
|
|
|
return status;
|
|
}
|
|
|
|
int exec_runtask(char *cmd, char *args[])
|
|
{
|
|
char buf[1024] = "";
|
|
char *argv[4] = {
|
|
"sh",
|
|
"-c",
|
|
buf,
|
|
NULL
|
|
};
|
|
size_t i;
|
|
|
|
strlcat(buf, cmd, sizeof(buf));
|
|
for (i = 1; args && args[i]; i++) {
|
|
strlcat(buf, " ", sizeof(buf));
|
|
strlcat(buf, args[i], sizeof(buf));
|
|
}
|
|
logit(LOG_DEBUG, "Calling %s %s", _PATH_BSHELL, buf);
|
|
dbg("Calling %s %s", _PATH_BSHELL, buf);
|
|
|
|
return execvp(_PATH_BSHELL, argv);
|
|
}
|
|
|
|
static void prepare_tty(char *tty, speed_t speed, char *procname, struct rlimit rlimit[])
|
|
{
|
|
char name[80];
|
|
int fd, dummy;
|
|
|
|
fd = open(tty, O_RDWR);
|
|
if (fd < 0) {
|
|
logit(LOG_ERR, "Failed opening %s: %s", tty, strerror(errno));
|
|
_exit(1);
|
|
}
|
|
|
|
dup2(fd, STDIN_FILENO);
|
|
dup2(fd, STDOUT_FILENO);
|
|
dup2(fd, STDERR_FILENO);
|
|
|
|
/* Set default TERM, run_getty() below allows override */
|
|
if (ioctl(fd, VT_OPENQRY, &dummy) == -1)
|
|
setenv("TERM", "vt102", 1); /* likely a serial line */
|
|
else
|
|
setenv("TERM", "linux", 1);
|
|
|
|
/*
|
|
* Reset to sane defaults in case of messup from prev. session
|
|
*/
|
|
stty(fd, speed);
|
|
close(fd);
|
|
|
|
/* Set configured limits */
|
|
for (int i = 0; i < RLIMIT_NLIMITS; i++) {
|
|
if (setrlimit(i, &rlimit[i]) == -1)
|
|
logit(LOG_WARNING, "%s: rlimit: Failed setting %s", tty, rlim2str(i));
|
|
}
|
|
|
|
/* Finit is responsible for the UTMP INIT_PROCESS record */
|
|
utmp_set_init(tty, 0);
|
|
if (!strncmp("/dev/", tty, 5))
|
|
tty += 5;
|
|
snprintf(name, sizeof(name), "%s %s", procname, tty);
|
|
prctl(PR_SET_NAME, name, 0, 0, 0);
|
|
}
|
|
|
|
static int activate_console(int noclear, int nowait)
|
|
{
|
|
static const char msg[] = "\nPlease press Enter to activate this console.";
|
|
static const char clr[] = "\r\e[K";
|
|
static const char cup[] = "\e[A";
|
|
struct termios orig;
|
|
char ch;
|
|
int rc;
|
|
|
|
if (nowait || rescue)
|
|
return 1;
|
|
|
|
if (!noclear)
|
|
dprint(STDERR_FILENO, "\e[r\e[H\e[J", 9);
|
|
|
|
/* Disable ECHO, XON/OFF while waiting for <CR> */
|
|
if (!tcgetattr(STDIN_FILENO, &orig)) {
|
|
struct termios c = orig;
|
|
|
|
c.c_iflag &= ~(BRKINT|ICRNL|INPCK|ISTRIP|IXON|IXOFF);
|
|
c.c_oflag &= ~(OPOST);
|
|
c.c_cflag |= (CS8);
|
|
c.c_lflag &= ~(ECHO|ICANON|IEXTEN|ISIG);
|
|
tcsetattr(STDIN_FILENO, TCSAFLUSH, &c);
|
|
}
|
|
|
|
dprint(STDERR_FILENO, clr, strlen(clr));
|
|
dprint(STDERR_FILENO, msg, strlen(msg));
|
|
while ((rc = read(STDIN_FILENO, &ch, 1)) > 0 && ch != '\r' && ch != '\n')
|
|
continue;
|
|
|
|
/* On any error (likely EINTR), we avoid starting getty */
|
|
if (rc == -1)
|
|
rc = 0;
|
|
|
|
/* Clear msg and move cursor up for next message/login: */
|
|
dprint(STDERR_FILENO, clr, strlen(clr));
|
|
dprint(STDERR_FILENO, cup, strlen(cup));
|
|
|
|
/* Restore TTY */
|
|
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig) == -1)
|
|
rc = 0; /* TTY in bad shape, restart */
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Start a getty on @tty
|
|
*
|
|
* At the login: prompt, no signals are allowed, both Ctrl-C and Ctrl-D
|
|
* should be disabled. Ctrl-S and Ctrl-Q are optional, but most getty
|
|
* allow them.
|
|
*
|
|
* Prior to getty is called and login: is printed, Finit may display the
|
|
* "Please press Enter ..." if @nowait is unset. This mode must be RAW,
|
|
* only accepting <CR> and not echoing anything, this also means no
|
|
* signals are allowed. For ease of implementation Finit will call the
|
|
* stty() function to reset the TTY and then force RAW mode until a <CR>
|
|
* has been received. This is handled in the same fashion for both this
|
|
* function and run_getty2(), which is used for external getty.
|
|
*
|
|
* When handing over to /bin/login, Ctrl-C and Ctrl-D must be enabled
|
|
* since /bin/login usually only disables ECHO until a password line has
|
|
* been entered. Upon starting the user's $SHELL the ISIG flag is reset
|
|
*/
|
|
pid_t run_getty(char *tty, char *cmd, char *args[], int noclear, int nowait, struct rlimit rlimit[])
|
|
{
|
|
int rc = 1;
|
|
|
|
/* Dunno speed, tell stty() to not mess with it */
|
|
prepare_tty(tty, B0, "getty", rlimit);
|
|
if (activate_console(noclear, nowait)) {
|
|
logit(LOG_INFO, "Starting getty on %s", tty);
|
|
rc = execv(cmd, args);
|
|
}
|
|
|
|
vhangup();
|
|
|
|
return rc;
|
|
}
|
|
|
|
int sh(char *tty)
|
|
{
|
|
char *args[2] = {
|
|
NULL,
|
|
NULL
|
|
};
|
|
char *arg0;
|
|
size_t len;
|
|
|
|
/* The getty process is usually responsible for the UTMP login record */
|
|
utmp_set_login(tty, NULL);
|
|
|
|
/* Start /bin/sh as a login shell, i.e. with a prefix '-' */
|
|
len = strlen(_PATH_BSHELL) + 2;
|
|
arg0 = malloc(len);
|
|
if (!arg0) {
|
|
err(1, "Failed allocating memory");
|
|
return 1;
|
|
}
|
|
snprintf(arg0, len, "-%s", _PATH_BSHELL);
|
|
args[0] = arg0;
|
|
|
|
/* Unblock signals inherited from parent */
|
|
sig_unblock();
|
|
|
|
return execv(_PATH_BSHELL, args);
|
|
}
|
|
|
|
pid_t run_sh(char *tty, int noclear, int nowait, struct rlimit rlimit[])
|
|
{
|
|
int rc = 1;
|
|
|
|
prepare_tty(tty, B0, "finitsh", rlimit);
|
|
if (activate_console(noclear, nowait))
|
|
rc = sh(tty);
|
|
|
|
return rc;
|
|
}
|
|
|
|
pid_t run_bg(char *cmd, char *args[])
|
|
{
|
|
pid_t pid;
|
|
|
|
if (access(cmd, X_OK))
|
|
return 0;
|
|
|
|
print(-1, "Calling %s in the background", cmd);
|
|
pid = fork();
|
|
if (!pid)
|
|
_exit(exec_runtask(cmd, args));
|
|
print_result(pid == -1);
|
|
|
|
return pid;
|
|
}
|
|
|
|
/**
|
|
* Local Variables:
|
|
* indent-tabs-mode: t
|
|
* c-file-style: "linux"
|
|
* End:
|
|
*/
|