Files
finit/src/exec.c
T
Joachim Nilsson d1718d338e Set proper RAW mode for "Please press Enter to activate ..."
Also, while leading with a \n for the activation text and clearing the
line before getty is called, some getty implementations, e.g. BusyBox,
write an extra \n before printing /etc/issue.  For perfectionists that
want a single newline separating started services and the first line
of /etc/issue we now not only clear the line, we also move the cursor
one line up before handing over to getty.

Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
2020-01-06 18:58:08 +01:00

515 lines
12 KiB
C

/* Functions for exec'ing processes
*
* Copyright (c) 2008-2010 Claudio Matsuoka <cmatsuoka@gmail.com>
* Copyright (c) 2008-2015 Joachim Nilsson <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 <ctype.h> /* isdigit() */
#include <dirent.h>
#include <stdarg.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>
#include <lite/lite.h>
#include "finit.h"
#include "conf.h"
#include "helpers.h"
#include "sig.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)
_e("Caught unblocked signal waiting for %s, aborting", cmd);
else if (errno == ECHILD)
_e("Caught SIGCHLD waiting for %s, aborting", cmd);
else
_e("Failed starting %s, error %d: %s", cmd, errno, strerror (errno));
return -1;
}
return status;
}
int run(char *cmd)
{
int status, result, i = 0;
char *args[NUM_ARGS + 1], *arg, *backup;
pid_t pid;
/* We must create a copy that is possible to modify. */
backup = arg = strdup(cmd);
if (!arg)
return 1; /* Failed allocating a string to be modified. */
/* 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) {
_e("Command too long: %s", cmd);
free(backup);
errno = EOVERFLOW;
return 1;
}
pid = fork();
if (0 == pid) {
FILE *fp;
/* Reset signal handlers that were set by the parent process */
sig_unblock();
setsid();
/* Always redirect stdio for run() */
fp = fopen("/dev/null", "w");
if (fp) {
int fd = fileno(fp);
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
}
sig_unblock();
execvp(args[0], args);
_exit(1); /* Only if execv() fails. */
} else if (-1 == pid) {
_pe("%s", args[0]);
free(backup);
return -1;
}
status = complete(args[0], pid);
if (-1 == status) {
free(backup);
return 1;
}
result = WEXITSTATUS(status);
if (WIFEXITED(status)) {
_d("Started %s and ended OK: %d", args[0], result);
} else if (WIFSIGNALED(status)) {
_d("Process %s terminated by signal %d", args[0], WTERMSIG(status));
if (!result)
result = 1; /* Must alert callee that the command did complete successfully.
* This is necessary since not all programs trap signals and
* change their return code accordingly. --Jocke */
}
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 && !log_is_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 && !log_is_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 && !log_is_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[])
{
size_t i;
char buf[1024] = "";
char *argv[4] = {
"sh",
"-c",
buf,
NULL
};
strlcat(buf, cmd, sizeof(buf));
for (i = 1; args[i]; i++) {
strlcat(buf, " ", sizeof(buf));
strlcat(buf, args[i], sizeof(buf));
}
logit(LOG_DEBUG, "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[])
{
struct sigaction sa;
struct termios term;
char name[80];
/*
* Reset to sane defaults in case of messup from prev. session
*/
stty(STDIN_FILENO, speed);
/*
* Disable ISIG (INTR, QUIT, SUSP) before handing over to getty.
* It is up to the getty process to allow them again.
*/
tcdrain(STDIN_FILENO);
if (!tcgetattr(STDIN_FILENO, &term)) {
term.c_lflag &= ~ISIG;
term.c_cc[VEOF] = _POSIX_VDISABLE;
term.c_cc[VINTR] = _POSIX_VDISABLE;
tcsetattr(STDIN_FILENO, TCSANOW, &term);
}
/* Reset signal handlers that were set by the parent process */
sig_unblock();
/*
* Ignore a few signals, needed to prevent Ctrl-C at login:
* prompt and to prevent QUIT from dumping core.
*/
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = SIG_IGN;
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGQUIT, &sa, NULL);
/* 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));
}
/* Create new session and process group */
setsid();
/* 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)
{
struct termios orig;
int ret = 0;
if (!noclear)
(void)write(STDERR_FILENO, "\e[r\e[H\e[J", 9);
if (nowait)
return 1;
/* 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, TCSANOW, &c);
}
while (!fexist(SYNC_SHUTDOWN)) {
char c;
static const char clr[] = "\r\e[2K";
static const char cup[] = "\e[A";
static const char msg[] = "\nPlease press Enter to activate this console.";
if (fexist(SYNC_STOPPED)) {
sleep(5);
continue;
}
(void)write(STDERR_FILENO, clr, strlen(clr));
(void)write(STDERR_FILENO, msg, strlen(msg));
while (read(STDIN_FILENO, &c, 1) == 1 && c != '\r')
continue;
if (fexist(SYNC_STOPPED))
continue;
(void)write(STDERR_FILENO, clr, strlen(clr));
(void)write(STDERR_FILENO, cup, strlen(cup));
ret = 1;
break;
}
/* Restore TTY */
if (tcsetattr(STDIN_FILENO, TCSANOW, &orig) == -1)
ret = 0;
return ret;
}
/*
* 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 *baud, char *term, int noclear, int nowait, struct rlimit rlimit[])
{
pid_t pid;
pid = fork();
if (!pid) {
speed_t speed = B38400;
if (baud && baud[0]) {
speed = stty_parse_speed(baud);
if (B0 == speed)
logit(LOG_CRIT, "TTY %s: Invalid speed %s", tty, baud);
}
prepare_tty(tty, speed, "tty", rlimit);
if (activate_console(noclear, nowait))
_exit(getty(tty, speed, term, NULL));
}
return pid;
}
pid_t run_getty2(char *tty, char *cmd, char *args[], int noclear, int nowait, struct rlimit rlimit[])
{
pid_t pid;
pid = fork();
if (!pid) {
int i, fd;
struct sigaction sa;
/* Reset signal handlers that were set by the parent process */
for (i = 1; i < NSIG; i++)
DFLSIG(sa, i, 0);
/* Detach from initial controlling TTY */
vhangup();
close(STDERR_FILENO);
close(STDOUT_FILENO);
close(STDIN_FILENO);
/* Attach TTY to console */
fd = open(tty, O_RDWR);
if (fd != STDIN_FILENO)
exit(1);
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
/* Dunno speed, tell stty() to not mess with it */
prepare_tty(tty, B0, "getty", rlimit);
if (ioctl(STDIN_FILENO, TIOCSCTTY, 1) < 0)
_pe("Failed TIOCSCTTY");
if (activate_console(noclear, nowait))
_exit(execv(cmd, args));
close(fd);
vhangup();
exit(0);
}
return pid;
}
pid_t run_sh(char *tty, int noclear, int nowait, struct rlimit rlimit[])
{
pid_t pid;
pid = fork();
if (!pid) {
prepare_tty(tty, B0, "finit-sh", rlimit);
if (activate_console(noclear, nowait))
_exit(sh(tty));
}
return pid;
}
int run_parts(char *dir, char *cmd)
{
struct dirent **e;
int i, num;
num = scandir(dir, &e, NULL, alphasort);
if (num < 0) {
_d("No files found in %s, skipping ...", dir);
return -1;
}
for (i = 0; i < num; i++) {
struct stat st;
char path[512];
char *argv[4] = {
"sh",
"-c",
path,
NULL
};
char *name = e[i]->d_name;
pid_t pid = 0;
snprintf(path, sizeof(path), "%s/%s ", dir, name);
if (stat(path, &st)) {
_d("Failed stat(%s): %s", path, strerror(errno));
continue;
}
if (!S_ISEXEC(st.st_mode) || S_ISDIR(st.st_mode)) {
_d("Skipping %s ...", path);
continue;
}
/* If the callee didn't supply a run_parts() argument */
if (!cmd) {
/* Check if S<NUM>service or K<NUM>service notation is used */
_d("Checking if %s is a sysvinit startstop script ...", name);
if (name[0] == 'S' && isdigit(name[1]))
strlcat(path, "start", sizeof(path));
else if (name[0] == 'K' && isdigit(name[1]))
strlcat(path, "stop", sizeof(path));
} else {
strlcat(path, cmd, sizeof(path));
}
pid = fork();
if (!pid) {
sig_unblock();
return execvp(_PATH_BSHELL, argv);
}
complete(path, pid);
}
while (num--)
free(e[num]);
free(e);
return 0;
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/