0c69794138
These lines are generally out-of-date, incomplete and unnecessary. With SPDX and git repository much more accurate and fine grained information about licensing and authorship is available, hence let's drop the per-file copyright notice. Of course, removing copyright lines of others is problematic, hence this commit only removes my own lines and leaves all others untouched. It might be nicer if sooner or later those could go away too, making git the only and accurate source of authorship information.
413 lines
11 KiB
C
413 lines
11 KiB
C
/* SPDX-License-Identifier: LGPL-2.1+ */
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <sys/epoll.h>
|
|
#include <unistd.h>
|
|
|
|
#include "sd-bus.h"
|
|
#include "sd-daemon.h"
|
|
|
|
#include "alloc-util.h"
|
|
#include "bus-error.h"
|
|
#include "bus-util.h"
|
|
#include "def.h"
|
|
#include "fd-util.h"
|
|
#include "format-util.h"
|
|
#include "initreq.h"
|
|
#include "list.h"
|
|
#include "log.h"
|
|
#include "special.h"
|
|
#include "util.h"
|
|
#include "process-util.h"
|
|
|
|
#define SERVER_FD_MAX 16
|
|
#define TIMEOUT_MSEC ((int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC))
|
|
|
|
typedef struct Fifo Fifo;
|
|
|
|
typedef struct Server {
|
|
int epoll_fd;
|
|
|
|
LIST_HEAD(Fifo, fifos);
|
|
unsigned n_fifos;
|
|
|
|
sd_bus *bus;
|
|
|
|
bool quit;
|
|
} Server;
|
|
|
|
struct Fifo {
|
|
Server *server;
|
|
|
|
int fd;
|
|
|
|
struct init_request buffer;
|
|
size_t bytes_read;
|
|
|
|
LIST_FIELDS(Fifo, fifo);
|
|
};
|
|
|
|
static const char *translate_runlevel(int runlevel, bool *isolate) {
|
|
static const struct {
|
|
const int runlevel;
|
|
const char *special;
|
|
bool isolate;
|
|
} table[] = {
|
|
{ '0', SPECIAL_POWEROFF_TARGET, false },
|
|
{ '1', SPECIAL_RESCUE_TARGET, true },
|
|
{ 's', SPECIAL_RESCUE_TARGET, true },
|
|
{ 'S', SPECIAL_RESCUE_TARGET, true },
|
|
{ '2', SPECIAL_MULTI_USER_TARGET, true },
|
|
{ '3', SPECIAL_MULTI_USER_TARGET, true },
|
|
{ '4', SPECIAL_MULTI_USER_TARGET, true },
|
|
{ '5', SPECIAL_GRAPHICAL_TARGET, true },
|
|
{ '6', SPECIAL_REBOOT_TARGET, false },
|
|
};
|
|
|
|
unsigned i;
|
|
|
|
assert(isolate);
|
|
|
|
for (i = 0; i < ELEMENTSOF(table); i++)
|
|
if (table[i].runlevel == runlevel) {
|
|
*isolate = table[i].isolate;
|
|
if (runlevel == '6' && kexec_loaded())
|
|
return SPECIAL_KEXEC_TARGET;
|
|
return table[i].special;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void change_runlevel(Server *s, int runlevel) {
|
|
const char *target;
|
|
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
|
const char *mode;
|
|
bool isolate = false;
|
|
int r;
|
|
|
|
assert(s);
|
|
|
|
target = translate_runlevel(runlevel, &isolate);
|
|
if (!target) {
|
|
log_warning("Got request for unknown runlevel %c, ignoring.", runlevel);
|
|
return;
|
|
}
|
|
|
|
if (isolate)
|
|
mode = "isolate";
|
|
else
|
|
mode = "replace-irreversibly";
|
|
|
|
log_debug("Running request %s/start/%s", target, mode);
|
|
|
|
r = sd_bus_call_method(
|
|
s->bus,
|
|
"org.freedesktop.systemd1",
|
|
"/org/freedesktop/systemd1",
|
|
"org.freedesktop.systemd1.Manager",
|
|
"StartUnit",
|
|
&error,
|
|
NULL,
|
|
"ss", target, mode);
|
|
if (r < 0) {
|
|
log_error("Failed to change runlevel: %s", bus_error_message(&error, -r));
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void request_process(Server *s, const struct init_request *req) {
|
|
assert(s);
|
|
assert(req);
|
|
|
|
if (req->magic != INIT_MAGIC) {
|
|
log_error("Got initctl request with invalid magic. Ignoring.");
|
|
return;
|
|
}
|
|
|
|
switch (req->cmd) {
|
|
|
|
case INIT_CMD_RUNLVL:
|
|
if (!isprint(req->runlevel))
|
|
log_error("Got invalid runlevel. Ignoring.");
|
|
else
|
|
switch (req->runlevel) {
|
|
|
|
/* we are async anyway, so just use kill for reexec/reload */
|
|
case 'u':
|
|
case 'U':
|
|
if (kill(1, SIGTERM) < 0)
|
|
log_error_errno(errno, "kill() failed: %m");
|
|
|
|
/* The bus connection will be
|
|
* terminated if PID 1 is reexecuted,
|
|
* hence let's just exit here, and
|
|
* rely on that we'll be restarted on
|
|
* the next request */
|
|
s->quit = true;
|
|
break;
|
|
|
|
case 'q':
|
|
case 'Q':
|
|
if (kill(1, SIGHUP) < 0)
|
|
log_error_errno(errno, "kill() failed: %m");
|
|
break;
|
|
|
|
default:
|
|
change_runlevel(s, req->runlevel);
|
|
}
|
|
return;
|
|
|
|
case INIT_CMD_POWERFAIL:
|
|
case INIT_CMD_POWERFAILNOW:
|
|
case INIT_CMD_POWEROK:
|
|
log_warning("Received UPS/power initctl request. This is not implemented in systemd. Upgrade your UPS daemon!");
|
|
return;
|
|
|
|
case INIT_CMD_CHANGECONS:
|
|
log_warning("Received console change initctl request. This is not implemented in systemd.");
|
|
return;
|
|
|
|
case INIT_CMD_SETENV:
|
|
case INIT_CMD_UNSETENV:
|
|
log_warning("Received environment initctl request. This is not implemented in systemd.");
|
|
return;
|
|
|
|
default:
|
|
log_warning("Received unknown initctl request. Ignoring.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
static int fifo_process(Fifo *f) {
|
|
ssize_t l;
|
|
|
|
assert(f);
|
|
|
|
errno = EIO;
|
|
l = read(f->fd,
|
|
((uint8_t*) &f->buffer) + f->bytes_read,
|
|
sizeof(f->buffer) - f->bytes_read);
|
|
if (l <= 0) {
|
|
if (errno == EAGAIN)
|
|
return 0;
|
|
|
|
return log_warning_errno(errno, "Failed to read from fifo: %m");
|
|
}
|
|
|
|
f->bytes_read += l;
|
|
assert(f->bytes_read <= sizeof(f->buffer));
|
|
|
|
if (f->bytes_read == sizeof(f->buffer)) {
|
|
request_process(f->server, &f->buffer);
|
|
f->bytes_read = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void fifo_free(Fifo *f) {
|
|
assert(f);
|
|
|
|
if (f->server) {
|
|
assert(f->server->n_fifos > 0);
|
|
f->server->n_fifos--;
|
|
LIST_REMOVE(fifo, f->server->fifos, f);
|
|
}
|
|
|
|
if (f->fd >= 0) {
|
|
if (f->server)
|
|
epoll_ctl(f->server->epoll_fd, EPOLL_CTL_DEL, f->fd, NULL);
|
|
|
|
safe_close(f->fd);
|
|
}
|
|
|
|
free(f);
|
|
}
|
|
|
|
static void server_done(Server *s) {
|
|
assert(s);
|
|
|
|
while (s->fifos)
|
|
fifo_free(s->fifos);
|
|
|
|
safe_close(s->epoll_fd);
|
|
|
|
if (s->bus) {
|
|
sd_bus_flush(s->bus);
|
|
sd_bus_unref(s->bus);
|
|
}
|
|
}
|
|
|
|
static int server_init(Server *s, unsigned n_sockets) {
|
|
int r;
|
|
unsigned i;
|
|
|
|
assert(s);
|
|
assert(n_sockets > 0);
|
|
|
|
zero(*s);
|
|
|
|
s->epoll_fd = epoll_create1(EPOLL_CLOEXEC);
|
|
if (s->epoll_fd < 0) {
|
|
r = log_error_errno(errno,
|
|
"Failed to create epoll object: %m");
|
|
goto fail;
|
|
}
|
|
|
|
for (i = 0; i < n_sockets; i++) {
|
|
struct epoll_event ev;
|
|
Fifo *f;
|
|
int fd;
|
|
|
|
fd = SD_LISTEN_FDS_START+i;
|
|
|
|
r = sd_is_fifo(fd, NULL);
|
|
if (r < 0) {
|
|
log_error_errno(r, "Failed to determine file descriptor type: %m");
|
|
goto fail;
|
|
}
|
|
|
|
if (!r) {
|
|
log_error("Wrong file descriptor type.");
|
|
r = -EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
f = new0(Fifo, 1);
|
|
if (!f) {
|
|
r = -ENOMEM;
|
|
log_error_errno(errno, "Failed to create fifo object: %m");
|
|
goto fail;
|
|
}
|
|
|
|
f->fd = -1;
|
|
|
|
zero(ev);
|
|
ev.events = EPOLLIN;
|
|
ev.data.ptr = f;
|
|
if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
|
|
r = -errno;
|
|
fifo_free(f);
|
|
log_error_errno(errno, "Failed to add fifo fd to epoll object: %m");
|
|
goto fail;
|
|
}
|
|
|
|
f->fd = fd;
|
|
LIST_PREPEND(fifo, s->fifos, f);
|
|
f->server = s;
|
|
s->n_fifos++;
|
|
}
|
|
|
|
r = bus_connect_system_systemd(&s->bus);
|
|
if (r < 0) {
|
|
log_error_errno(r, "Failed to get D-Bus connection: %m");
|
|
r = -EIO;
|
|
goto fail;
|
|
}
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
server_done(s);
|
|
|
|
return r;
|
|
}
|
|
|
|
static int process_event(Server *s, struct epoll_event *ev) {
|
|
int r;
|
|
Fifo *f;
|
|
|
|
assert(s);
|
|
|
|
if (!(ev->events & EPOLLIN)) {
|
|
log_info("Got invalid event from epoll. (3)");
|
|
return -EIO;
|
|
}
|
|
|
|
f = (Fifo*) ev->data.ptr;
|
|
r = fifo_process(f);
|
|
if (r < 0) {
|
|
log_info_errno(r, "Got error on fifo: %m");
|
|
fifo_free(f);
|
|
return r;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
Server server;
|
|
int r = EXIT_FAILURE, n;
|
|
|
|
if (getppid() != 1) {
|
|
log_error("This program should be invoked by init only.");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (argc > 1) {
|
|
log_error("This program does not take arguments.");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
log_set_target(LOG_TARGET_AUTO);
|
|
log_parse_environment();
|
|
log_open();
|
|
|
|
umask(0022);
|
|
|
|
n = sd_listen_fds(true);
|
|
if (n < 0) {
|
|
log_error_errno(r, "Failed to read listening file descriptors from environment: %m");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (n <= 0 || n > SERVER_FD_MAX) {
|
|
log_error("No or too many file descriptors passed.");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (server_init(&server, (unsigned) n) < 0)
|
|
return EXIT_FAILURE;
|
|
|
|
log_debug("systemd-initctl running as pid "PID_FMT, getpid_cached());
|
|
|
|
sd_notify(false,
|
|
"READY=1\n"
|
|
"STATUS=Processing requests...");
|
|
|
|
while (!server.quit) {
|
|
struct epoll_event event;
|
|
int k;
|
|
|
|
k = epoll_wait(server.epoll_fd, &event, 1, TIMEOUT_MSEC);
|
|
if (k < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
log_error_errno(errno, "epoll_wait() failed: %m");
|
|
goto fail;
|
|
}
|
|
|
|
if (k <= 0)
|
|
break;
|
|
|
|
if (process_event(&server, &event) < 0)
|
|
goto fail;
|
|
}
|
|
|
|
r = EXIT_SUCCESS;
|
|
|
|
log_debug("systemd-initctl stopped as pid "PID_FMT, getpid_cached());
|
|
|
|
fail:
|
|
sd_notify(false,
|
|
"STOPPING=1\n"
|
|
"STATUS=Shutting down...");
|
|
|
|
server_done(&server);
|
|
|
|
return r;
|
|
}
|