/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #include #include #include #include #include #include "sd-bus.h" #include "sd-daemon.h" #include "alloc-util.h" #include "bus-error.h" #include "bus-util.h" #include "daemon-util.h" #include "def.h" #include "fd-util.h" #include "format-util.h" #include "initreq.h" #include "list.h" #include "log.h" #include "main-func.h" #include "memory-util.h" #include "process-util.h" #include "special.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 }, }; assert(isolate); for (size_t 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 int 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 0; } if (isolate) mode = "isolate"; else mode = "replace-irreversibly"; log_debug("Requesting %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) return log_error_errno(r, "Failed to change runlevel: %s", bus_error_message(&error, r)); return 0; } 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: (void) 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 Fifo* fifo_free(Fifo *f) { if (!f) return NULL; 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) (void) epoll_ctl(f->server->epoll_fd, EPOLL_CTL_DEL, f->fd, NULL); safe_close(f->fd); } return mfree(f); } DEFINE_TRIVIAL_CLEANUP_FUNC(Fifo*, fifo_free); static void server_done(Server *s) { assert(s); while (s->fifos) fifo_free(s->fifos); s->epoll_fd = safe_close(s->epoll_fd); s->bus = sd_bus_flush_close_unref(s->bus); } static int server_init(Server *s, unsigned n_sockets) { int r; /* This function will leave s partially initialized on failure. Caller needs to clean up. */ assert(s); assert(n_sockets > 0); s->epoll_fd = epoll_create1(EPOLL_CLOEXEC); if (s->epoll_fd < 0) return log_error_errno(errno, "Failed to create epoll object: %m"); for (unsigned i = 0; i < n_sockets; i++) { _cleanup_(fifo_freep) Fifo *f = NULL; int fd = SD_LISTEN_FDS_START + i; r = sd_is_fifo(fd, NULL); if (r < 0) return log_error_errno(r, "Failed to determine file descriptor type: %m"); if (!r) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Wrong file descriptor type."); f = new0(Fifo, 1); if (!f) return log_oom(); struct epoll_event ev = { .events = EPOLLIN, .data.ptr = f, }; if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) return log_error_errno(errno, "Failed to add fifo fd to epoll object: %m"); f->fd = fd; f->server = s; LIST_PREPEND(fifo, s->fifos, TAKE_PTR(f)); s->n_fifos++; } r = bus_connect_system_systemd(&s->bus); if (r < 0) return log_error_errno(r, "Failed to get D-Bus connection: %m"); return 0; } static int process_event(Server *s, struct epoll_event *ev) { int r; Fifo *f; assert(s); if (!(ev->events & EPOLLIN)) return log_info_errno(SYNTHETIC_ERRNO(EIO), "Got invalid event from epoll. (3)"); 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; } static int run(int argc, char *argv[]) { _cleanup_(server_done) Server server = { .epoll_fd = -1 }; _unused_ _cleanup_(notify_on_cleanup) const char *notify_stop = NULL; int r, n; if (argc > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program does not take arguments."); log_setup(); umask(0022); n = sd_listen_fds(true); if (n < 0) return log_error_errno(errno, "Failed to read listening file descriptors from environment: %m"); if (n <= 0 || n > SERVER_FD_MAX) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No or too many file descriptors passed."); r = server_init(&server, (unsigned) n); if (r < 0) return r; notify_stop = notify_start(NOTIFY_READY, NOTIFY_STOPPING); 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; return log_error_errno(errno, "epoll_wait() failed: %m"); } if (k == 0) break; r = process_event(&server, &event); if (r < 0) return r; } return 0; } DEFINE_MAIN_FUNCTION(run);