Files
systemd-stable/src/initctl.c
T
Tom Gundersen 46e964c5ce initctl: support 'telinit u/q'
We send SIGTERM (resp., SIGHUP) when we receive request for changing to
runlevel 'u' (resp., 'q').

As initctl is already async, we just send the signals rather than first
attempting to connect to sytemd over dbus and then falling back to kill.
2011-08-01 23:22:24 +02:00

441 lines
12 KiB
C

/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2010 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <sys/socket.h>
#include <sys/types.h>
#include <assert.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/poll.h>
#include <sys/epoll.h>
#include <sys/un.h>
#include <fcntl.h>
#include <ctype.h>
#include <dbus/dbus.h>
#include "util.h"
#include "log.h"
#include "list.h"
#include "initreq.h"
#include "special.h"
#include "sd-daemon.h"
#include "dbus-common.h"
#include "def.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;
DBusConnection *bus;
} 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_RUNLEVEL2_TARGET, true },
{ '3', SPECIAL_RUNLEVEL3_TARGET, true },
{ '4', SPECIAL_RUNLEVEL4_TARGET, true },
{ '5', SPECIAL_RUNLEVEL5_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;
return table[i].special;
}
return NULL;
}
static void change_runlevel(Server *s, int runlevel) {
const char *target;
DBusMessage *m = NULL, *reply = NULL;
DBusError error;
const char *mode;
bool isolate = false;
assert(s);
dbus_error_init(&error);
if (!(target = translate_runlevel(runlevel, &isolate))) {
log_warning("Got request for unknown runlevel %c, ignoring.", runlevel);
goto finish;
}
if (isolate)
mode = "isolate";
else
mode = "replace";
log_debug("Running request %s/start/%s", target, mode);
if (!(m = dbus_message_new_method_call("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "StartUnit"))) {
log_error("Could not allocate message.");
goto finish;
}
if (!dbus_message_append_args(m,
DBUS_TYPE_STRING, &target,
DBUS_TYPE_STRING, &mode,
DBUS_TYPE_INVALID)) {
log_error("Could not attach target and flag information to message.");
goto finish;
}
if (!(reply = dbus_connection_send_with_reply_and_block(s->bus, m, -1, &error))) {
log_error("Failed to start unit: %s", bus_error_message(&error));
goto finish;
}
finish:
if (m)
dbus_message_unref(m);
if (reply)
dbus_message_unref(reply);
dbus_error_free(&error);
}
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("kill() failed: %m");
break;
case 'q':
case 'Q':
if (kill(1, SIGHUP) < 0)
log_error("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;
if ((l = read(f->fd, ((uint8_t*) &f->buffer) + f->bytes_read, sizeof(f->buffer) - f->bytes_read)) <= 0) {
if (errno == EAGAIN)
return 0;
log_warning("Failed to read from fifo: %s", strerror(errno));
return -1;
}
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, fifo, f->server->fifos, f);
}
if (f->fd >= 0) {
if (f->server)
epoll_ctl(f->server->epoll_fd, EPOLL_CTL_DEL, f->fd, NULL);
close_nointr_nofail(f->fd);
}
free(f);
}
static void server_done(Server *s) {
assert(s);
while (s->fifos)
fifo_free(s->fifos);
if (s->epoll_fd >= 0)
close_nointr_nofail(s->epoll_fd);
if (s->bus) {
dbus_connection_flush(s->bus);
dbus_connection_close(s->bus);
dbus_connection_unref(s->bus);
}
}
static int server_init(Server *s, unsigned n_sockets) {
int r;
unsigned i;
DBusError error;
assert(s);
assert(n_sockets > 0);
dbus_error_init(&error);
zero(*s);
if ((s->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0) {
r = -errno;
log_error("Failed to create epoll object: %s", strerror(errno));
goto fail;
}
for (i = 0; i < n_sockets; i++) {
struct epoll_event ev;
Fifo *f;
int fd;
fd = SD_LISTEN_FDS_START+i;
if ((r = sd_is_fifo(fd, NULL)) < 0) {
log_error("Failed to determine file descriptor type: %s", strerror(-r));
goto fail;
}
if (!r) {
log_error("Wrong file descriptor type.");
r = -EINVAL;
goto fail;
}
if (!(f = new0(Fifo, 1))) {
r = -ENOMEM;
log_error("Failed to create fifo object: %s", strerror(errno));
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("Failed to add fifo fd to epoll object: %s", strerror(errno));
goto fail;
}
f->fd = fd;
LIST_PREPEND(Fifo, fifo, s->fifos, f);
f->server = s;
s->n_fifos ++;
}
if (bus_connect(DBUS_BUS_SYSTEM, &s->bus, NULL, &error) < 0) {
log_error("Failed to get D-Bus connection: %s", bus_error_message(&error));
goto fail;
}
return 0;
fail:
server_done(s);
dbus_error_free(&error);
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;
if ((r = fifo_process(f)) < 0) {
log_info("Got error on fifo: %s", strerror(-r));
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_SYSLOG_OR_KMSG);
log_parse_environment();
log_open();
umask(0022);
if ((n = sd_listen_fds(true)) < 0) {
log_error("Failed to read listening file descriptors from environment: %s", strerror(-r));
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 %lu", (unsigned long) getpid());
sd_notify(false,
"READY=1\n"
"STATUS=Processing requests...");
for (;;) {
struct epoll_event event;
int k;
if ((k = epoll_wait(server.epoll_fd,
&event, 1,
TIMEOUT_MSEC)) < 0) {
if (errno == EINTR)
continue;
log_error("epoll_wait() failed: %s", strerror(errno));
goto fail;
}
if (k <= 0)
break;
if (process_event(&server, &event) < 0)
goto fail;
}
r = EXIT_SUCCESS;
log_debug("systemd-initctl stopped as pid %lu", (unsigned long) getpid());
fail:
sd_notify(false,
"STATUS=Shutting down...");
server_done(&server);
dbus_shutdown();
return r;
}