Add service(8).
This commit is contained in:
parent
6f70708ff5
commit
b676a5f2da
|
@ -8,26 +8,27 @@ CFLAGS?=$(OPTLEVEL)
|
|||
|
||||
CFLAGS:=$(CFLAGS) -Wall -Wextra
|
||||
|
||||
BINARY=init
|
||||
BINARIES=\
|
||||
init \
|
||||
service \
|
||||
|
||||
OBJS=\
|
||||
init.o \
|
||||
MANPAGES8=\
|
||||
init.8 \
|
||||
|
||||
all: $(BINARY)
|
||||
# TODO: service.8
|
||||
|
||||
all: $(BINARIES)
|
||||
|
||||
.PHONY: all install clean
|
||||
|
||||
$(BINARY): $(OBJS)
|
||||
$(CC) $(CFLAGS) $(OBJS) -o $(BINARY) -lmount $(LIBS)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) -c $< -o $@
|
||||
%: %.c
|
||||
$(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) $< -o $@ -lmount $(LIBS)
|
||||
|
||||
install: all
|
||||
mkdir -p $(DESTDIR)$(SBINDIR)
|
||||
install $(BINARY) $(DESTDIR)$(SBINDIR)
|
||||
install $(BINARIES) $(DESTDIR)$(SBINDIR)
|
||||
mkdir -p $(DESTDIR)$(MANDIR)/man8
|
||||
cp init.8 $(DESTDIR)$(MANDIR)/man8/init.8
|
||||
install $(MANPAGES8) $(DESTDIR)$(MANDIR)/man8
|
||||
|
||||
clean:
|
||||
rm -f $(BINARY) $(OBJS) *.o
|
||||
rm -f $(BINARY) service $(OBJS) *.o
|
||||
|
|
428
init/init.c
428
init/init.c
|
@ -20,8 +20,10 @@
|
|||
#include <sys/display.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
@ -137,6 +139,7 @@ struct dependency
|
|||
#define DEPENDENCY_FLAG_REQUIRE (1 << 0)
|
||||
#define DEPENDENCY_FLAG_AWAIT (1 << 1)
|
||||
#define DEPENDENCY_FLAG_EXIT_CODE (1 << 2)
|
||||
#define DEPENDENCY_FLAG_REFERENCED (1 << 30)
|
||||
|
||||
enum log_method
|
||||
{
|
||||
|
@ -223,7 +226,6 @@ struct daemon
|
|||
bool need_tty;
|
||||
bool was_ready;
|
||||
bool was_terminated;
|
||||
bool was_dereferenced;
|
||||
bool timeout_set;
|
||||
};
|
||||
|
||||
|
@ -255,10 +257,31 @@ struct daemon_config
|
|||
mode_t log_file_mode;
|
||||
};
|
||||
|
||||
struct server
|
||||
{
|
||||
size_t index;
|
||||
const char* path;
|
||||
int fd;
|
||||
};
|
||||
|
||||
struct connection
|
||||
{
|
||||
size_t index;
|
||||
char* input;
|
||||
size_t input_used;
|
||||
size_t input_size;
|
||||
char* output;
|
||||
size_t output_used;
|
||||
size_t output_size;
|
||||
int fd;
|
||||
};
|
||||
|
||||
enum communication_type
|
||||
{
|
||||
COMMUNICATION_TYPE_OUTPUT,
|
||||
COMMUNICATION_TYPE_READY,
|
||||
COMMUNICATION_TYPE_SERVER,
|
||||
COMMUNICATION_TYPE_CONNECTION,
|
||||
};
|
||||
|
||||
struct communication
|
||||
|
@ -1742,6 +1765,27 @@ static struct daemon* daemon_create_unconfigured(const char* name)
|
|||
return daemon;
|
||||
}
|
||||
|
||||
static struct daemon* daemon_find_or_create(const char* name)
|
||||
{
|
||||
// TODO: What happens for virtual daemons?
|
||||
struct daemon* daemon = daemon_find_by_name(name);
|
||||
// TODO: non-fatal daemon_create_unconfigured
|
||||
if ( !daemon )
|
||||
daemon = daemon_create_unconfigured(name);
|
||||
return daemon;
|
||||
}
|
||||
|
||||
static struct dependency* daemon_find_dependency(struct daemon* daemon,
|
||||
const char* target)
|
||||
{
|
||||
for ( size_t i = 0; i < daemon->dependencies_used; i++ )
|
||||
{
|
||||
if ( !strcmp(daemon->dependencies[i]->target->name, target) )
|
||||
return daemon->dependencies[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool daemon_add_dependency(struct daemon* daemon,
|
||||
struct daemon* target,
|
||||
int flags)
|
||||
|
@ -1772,6 +1816,7 @@ static bool daemon_add_dependency(struct daemon* daemon,
|
|||
if ( flags & DEPENDENCY_FLAG_EXIT_CODE )
|
||||
daemon->exit_code_from = dependency;
|
||||
target->reference_count++;
|
||||
dependency->flags |= DEPENDENCY_FLAG_REFERENCED;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1878,12 +1923,39 @@ static struct daemon* daemon_create(struct daemon_config* daemon_config)
|
|||
return daemon;
|
||||
}
|
||||
|
||||
static void schedule_daemon(struct daemon* daemon)
|
||||
static void daemon_schedule(struct daemon* daemon)
|
||||
{
|
||||
assert(daemon->state == DAEMON_STATE_TERMINATED);
|
||||
daemon_change_state_list(daemon, DAEMON_STATE_SCHEDULED);
|
||||
}
|
||||
|
||||
static void daemon_request_restart(struct daemon* daemon)
|
||||
{
|
||||
// TODO: Force a restart if STARTING, RUNNING.
|
||||
// TODO: In TERMINATING, FINISHING, schedule the restart.
|
||||
if ( daemon->state == DAEMON_STATE_FINISHED )
|
||||
{
|
||||
daemon_change_state_list(daemon, DAEMON_STATE_SCHEDULED);
|
||||
// TODO: Update dependencies_ready.
|
||||
// TODO: Update dependencies_finished.
|
||||
// TODO: Update dependencies_failed.
|
||||
daemon->was_ready = false;
|
||||
daemon->was_terminated = false;
|
||||
// TODO: was_dereferenced?
|
||||
daemon->timeout_set = false; // TODO: Reset before going to FINISHING.
|
||||
}
|
||||
}
|
||||
|
||||
static void daemon_request_start(struct daemon* daemon)
|
||||
{
|
||||
if ( daemon->state == DAEMON_STATE_TERMINATED )
|
||||
daemon_schedule(daemon);
|
||||
else if ( daemon->state == DAEMON_STATE_TERMINATING ||
|
||||
daemon->state == DAEMON_STATE_FINISHING ||
|
||||
daemon->state == DAEMON_STATE_FINISHED )
|
||||
daemon_request_restart(daemon);
|
||||
}
|
||||
|
||||
static void daemon_on_finished(struct daemon* daemon)
|
||||
{
|
||||
assert(daemon->state != DAEMON_STATE_FINISHING);
|
||||
|
@ -1947,12 +2019,19 @@ static void daemon_dereference(struct daemon* daemon)
|
|||
daemon_on_not_referenced(daemon);
|
||||
}
|
||||
|
||||
static void daemon_dereference_dependency(struct dependency* dependency)
|
||||
{
|
||||
if ( dependency->flags & DEPENDENCY_FLAG_REFERENCED )
|
||||
{
|
||||
daemon_dereference(dependency->target);
|
||||
dependency->flags &= ~(DEPENDENCY_FLAG_REFERENCED);
|
||||
}
|
||||
}
|
||||
|
||||
static void daemon_dereference_dependencies(struct daemon* daemon)
|
||||
{
|
||||
assert(!daemon->was_dereferenced);
|
||||
daemon->was_dereferenced = true;
|
||||
for ( size_t i = 0; i < daemon->dependencies_used; i++ )
|
||||
daemon_dereference(daemon->dependencies[i]->target);
|
||||
daemon_dereference_dependency(daemon->dependencies[i]);
|
||||
}
|
||||
|
||||
static void daemon_on_dependency_ready(struct dependency* dependency)
|
||||
|
@ -2015,6 +2094,96 @@ static void daemon_on_dependency_finished(struct dependency* dependency)
|
|||
daemon_on_finished(daemon);
|
||||
}
|
||||
|
||||
static bool daemon_depend(struct daemon* source,
|
||||
struct daemon* target,
|
||||
int flags,
|
||||
bool ensure_start)
|
||||
{
|
||||
struct dependency* dependency =
|
||||
daemon_find_dependency(source, target->name);
|
||||
if ( dependency )
|
||||
{
|
||||
// TODO: Updating the flags could be tricky and unsafe.
|
||||
return true;
|
||||
}
|
||||
else if ( !daemon_add_dependency(source, target, flags) )
|
||||
return false;
|
||||
// No need to start the target if the source isn't supposed to be running.
|
||||
if ( source->state == DAEMON_STATE_TERMINATED )
|
||||
return true;
|
||||
// No need to start the target if the source is a non-virtual daemon that is
|
||||
// terminating, or a virtual daemon with exit-code that is terminating.
|
||||
if ( (source->argv || source->exit_code_from) &&
|
||||
(source->state == DAEMON_STATE_TERMINATING ||
|
||||
source->state == DAEMON_STATE_FINISHING ||
|
||||
source->state == DAEMON_STATE_FINISHED) )
|
||||
return true;
|
||||
// No need to start the target if it is finishing, unless we are asked to
|
||||
// ensure it is started up again.
|
||||
// TODO: ensure_start can be always true once there's a proper one shot
|
||||
// vs persistent daemon concept.
|
||||
if ( !ensure_start &&
|
||||
(target->state == DAEMON_STATE_TERMINATING ||
|
||||
target->state == DAEMON_STATE_FINISHING ||
|
||||
target->state == DAEMON_STATE_FINISHED) )
|
||||
return true;
|
||||
if ( target->state == DAEMON_STATE_TERMINATED ||
|
||||
target->state == DAEMON_STATE_TERMINATING ||
|
||||
target->state == DAEMON_STATE_FINISHING ||
|
||||
target->state == DAEMON_STATE_FINISHED )
|
||||
daemon_request_start(target);
|
||||
if ( (target->state == DAEMON_STATE_TERMINATED ||
|
||||
target->state == DAEMON_STATE_SCHEDULED ||
|
||||
target->state == DAEMON_STATE_WAITING) )
|
||||
{
|
||||
if ( source->state == DAEMON_STATE_SATISFIED )
|
||||
daemon_change_state_list(target, DAEMON_STATE_WAITING);
|
||||
else if ( source->state == DAEMON_STATE_TERMINATING ||
|
||||
source->state == DAEMON_STATE_FINISHING ||
|
||||
source->state == DAEMON_STATE_FINISHED )
|
||||
{
|
||||
// TODO: Propagate recursive dependents to RUNNING.
|
||||
// TODO: dependencies_finished and such
|
||||
daemon_change_state_list(source, DAEMON_STATE_RUNNING);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void daemon_undepend(struct daemon* source,
|
||||
struct daemon* target,
|
||||
bool ensure_stop)
|
||||
{
|
||||
for ( size_t i = 0; i < source->dependencies_used; i++ )
|
||||
{
|
||||
if ( source->dependencies[i]->target != target )
|
||||
continue;
|
||||
struct dependency* dependency = source->dependencies[i];
|
||||
daemon_dereference_dependency(source->dependencies[i]);
|
||||
size_t last = source->dependencies_used-- - 1;
|
||||
if ( i != last )
|
||||
source->dependencies[i] = source->dependencies[last];
|
||||
for ( size_t n = 0; i < target->dependents_used; n++ )
|
||||
{
|
||||
if ( target->dependents[n] != dependency )
|
||||
continue;
|
||||
last = target->dependents_used-- - 1;
|
||||
if ( n != last )
|
||||
target->dependents[i] = target->dependents[last];
|
||||
}
|
||||
free(dependency);
|
||||
// TODO: Correct dependencies_ready.
|
||||
// TODO: Correct dependencies_finished.
|
||||
// TODO: Correct dependencies_failed.
|
||||
// TODO: State transition if now SATISFIED or FINISHED.
|
||||
break;
|
||||
}
|
||||
if ( ensure_stop &&
|
||||
(target->state == DAEMON_STATE_STARTING ||
|
||||
target->state == DAEMON_STATE_RUNNING) )
|
||||
daemon_terminate(target);
|
||||
}
|
||||
|
||||
static void daemon_finish(struct daemon* daemon)
|
||||
{
|
||||
assert(daemon->state != DAEMON_STATE_FINISHED);
|
||||
|
@ -2064,7 +2233,7 @@ static void daemon_wait(struct daemon* daemon)
|
|||
switch ( dependency->target->state )
|
||||
{
|
||||
case DAEMON_STATE_TERMINATED:
|
||||
schedule_daemon(dependency->target);
|
||||
daemon_schedule(dependency->target);
|
||||
if ( !(dependency->flags & DEPENDENCY_FLAG_AWAIT) )
|
||||
daemon_on_dependency_ready(dependency);
|
||||
break;
|
||||
|
@ -2378,6 +2547,241 @@ static void daemon_on_exit(struct daemon* daemon, int exit_code)
|
|||
daemon_on_finished(daemon);
|
||||
}
|
||||
|
||||
static void request_require(int argc, char** argv)
|
||||
{
|
||||
if ( argc < 3 )
|
||||
return warning("missing operand");
|
||||
const char* source_name = argv[1];
|
||||
const char* target_name = argv[2];
|
||||
bool start = 4 <= argc && !strcmp(argv[3], "start");
|
||||
// TODO: Parse flags.
|
||||
int flags = DEPENDENCY_FLAG_AWAIT;
|
||||
struct daemon* source = daemon_find_by_name(source_name);
|
||||
if ( !source )
|
||||
return warning("no %s", source_name);
|
||||
if ( source->argv )
|
||||
return warning("source had argv");
|
||||
struct daemon* target = daemon_find_or_create(target_name);
|
||||
if ( !target )
|
||||
return warning("failed to create %s", target_name);
|
||||
if ( !daemon_depend(source, target, flags, start) )
|
||||
return warning("failed to depend %s -> %s", source_name, target_name);
|
||||
}
|
||||
|
||||
static void request_unrequire(int argc, char** argv)
|
||||
{
|
||||
if ( argc < 3 )
|
||||
return warning("missing operand");
|
||||
const char* source_name = argv[1];
|
||||
const char* target_name = argv[2];
|
||||
bool stop = 4 <= argc && !strcmp(argv[3], "stop");
|
||||
struct daemon* source = daemon_find_by_name(source_name);
|
||||
if ( !source )
|
||||
return warning("no %s", source_name);
|
||||
struct daemon* target = daemon_find_by_name(target_name);
|
||||
if ( !target )
|
||||
return warning("no such daemon running: %s", target_name);
|
||||
daemon_undepend(source, target, stop);
|
||||
}
|
||||
|
||||
static void connection_free(struct connection* conn)
|
||||
{
|
||||
free(conn->input);
|
||||
free(conn->output);
|
||||
free(conn);
|
||||
}
|
||||
|
||||
static void connection_close(struct connection* conn)
|
||||
{
|
||||
communication_unregister(conn->index);
|
||||
close(conn->fd);
|
||||
conn->fd = -1;
|
||||
connection_free(conn);
|
||||
}
|
||||
|
||||
static struct connection* connection_new(int fd)
|
||||
{
|
||||
size_t buffer_size = 4096;
|
||||
struct connection* conn = calloc(1, sizeof(struct connection));
|
||||
char* input = malloc(buffer_size);
|
||||
char* output = malloc(buffer_size);
|
||||
if ( !conn || !input || !output )
|
||||
{
|
||||
free(conn);
|
||||
free(input);
|
||||
free(output);
|
||||
return NULL;
|
||||
}
|
||||
conn->input = input;
|
||||
conn->input_used = 0;
|
||||
conn->input_size = buffer_size;
|
||||
conn->output = output;
|
||||
conn->output_used = 0;
|
||||
conn->output_size = buffer_size;
|
||||
conn->fd = fd;
|
||||
if ( !communication_reserve(1) )
|
||||
return connection_free(conn), NULL;
|
||||
struct communication comm;
|
||||
comm.type = COMMUNICATION_TYPE_CONNECTION;
|
||||
comm.index_ptr = &conn->index;
|
||||
comm.connection = conn;
|
||||
communication_register(&comm, fd, POLLIN);
|
||||
return conn;
|
||||
}
|
||||
|
||||
static void connection_on_message(struct connection* conn, const char* message)
|
||||
{
|
||||
(void) conn; // TODO: Reply.
|
||||
|
||||
size_t argc = 0;
|
||||
char** argv = tokenize(&argc, message);
|
||||
if ( !argv )
|
||||
{
|
||||
if ( !errno )
|
||||
/* TODO: syntax error */{}
|
||||
else
|
||||
/* TODO: other error */{}
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( argc == 0 ) {}
|
||||
else if ( !strcmp(argv[0], "require") )
|
||||
request_require(argc, argv);
|
||||
else if ( !strcmp(argv[0], "unrequire") )
|
||||
request_unrequire(argc, argv);
|
||||
else
|
||||
warning("%s", message);
|
||||
|
||||
for ( size_t i = 0; i < argc; i++ )
|
||||
free(argv[i]);
|
||||
free(argv);
|
||||
}
|
||||
|
||||
static bool connection_on_event(struct connection* conn, int revents)
|
||||
{
|
||||
// TODO: POLLHUP and POLLERR.
|
||||
if ( (revents & POLLIN) && conn->input_used < conn->input_size )
|
||||
{
|
||||
ssize_t amount = recv(conn->fd, conn->input + conn->input_used,
|
||||
conn->input_size - conn->input_used, 0);
|
||||
if ( 0 < amount )
|
||||
{
|
||||
size_t i = conn->input_used;
|
||||
conn->input_used += amount;
|
||||
while ( i < conn->input_used )
|
||||
{
|
||||
if ( conn->input[i] == '\n' )
|
||||
{
|
||||
conn->input[i] = '\0';
|
||||
connection_on_message(conn, conn->input);
|
||||
memmove(conn->input, conn->input + i, conn->input_size - i);
|
||||
}
|
||||
else
|
||||
i++;
|
||||
}
|
||||
// Disconnect if the input line is too long.
|
||||
if ( conn->input_used == conn->input_size )
|
||||
{
|
||||
connection_close(conn);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if ( amount == 0 ||
|
||||
(amount < 0 && errno != EWOULDBLOCK && errno != EAGAIN ) )
|
||||
{
|
||||
connection_close(conn);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ( (revents & POLLOUT) && conn->output_used )
|
||||
{
|
||||
ssize_t amount = send(conn->fd, conn->output, conn->output_used,
|
||||
MSG_NOSIGNAL);
|
||||
if ( 0 < amount )
|
||||
{
|
||||
if ( (size_t) amount < conn->output_used )
|
||||
memmove(conn->output, conn->output + amount,
|
||||
conn->output_used - amount);
|
||||
conn->output_used -= amount;
|
||||
}
|
||||
else if ( errno != EWOULDBLOCK && errno != EAGAIN )
|
||||
{
|
||||
connection_close(conn);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ( (revents & (POLLHUP | POLLERR)) )
|
||||
{
|
||||
connection_close(conn);
|
||||
return false;
|
||||
}
|
||||
// TODO: Recompute poll bits.
|
||||
return true;
|
||||
}
|
||||
|
||||
static int open_local_server_socket(const char* path, int flags)
|
||||
{
|
||||
size_t path_length = strlen(path);
|
||||
size_t addr_size = offsetof(struct sockaddr_un, sun_path) + path_length + 1;
|
||||
struct sockaddr_un* sockaddr = malloc(addr_size);
|
||||
if ( !sockaddr )
|
||||
return -1;
|
||||
sockaddr->sun_family = AF_LOCAL;
|
||||
strcpy(sockaddr->sun_path, path);
|
||||
int fd = socket(AF_LOCAL, SOCK_STREAM | flags, 0);
|
||||
if ( fd < 0 )
|
||||
return free(sockaddr), -1;
|
||||
if ( fchmod(fd, 0600) < 0 )
|
||||
return close(fd), free(sockaddr), -1;
|
||||
if ( bind(fd, (const struct sockaddr*) sockaddr, addr_size) < 0 )
|
||||
return close(fd), free(sockaddr), -1;
|
||||
if ( listen(fd, SOMAXCONN) < 0 )
|
||||
return close(fd), free(sockaddr), -1;
|
||||
free(sockaddr);
|
||||
return fd;
|
||||
}
|
||||
|
||||
static struct server* server_start(const char* path)
|
||||
{
|
||||
if ( !communication_reserve(1) )
|
||||
return NULL;
|
||||
struct server* server = malloc(sizeof(struct server));
|
||||
if ( !server )
|
||||
return NULL;
|
||||
server->fd = open_local_server_socket(path, SOCK_NONBLOCK | SOCK_CLOEXEC);
|
||||
if ( server->fd < 0 )
|
||||
{
|
||||
free(server);
|
||||
return NULL;
|
||||
}
|
||||
struct communication comm;
|
||||
comm.type = COMMUNICATION_TYPE_SERVER;
|
||||
comm.index_ptr = &server->index;
|
||||
comm.server = server;
|
||||
communication_register(&comm, server->fd, POLLIN);
|
||||
return server;
|
||||
}
|
||||
|
||||
static bool server_on_event(struct server* server, int revents)
|
||||
{
|
||||
if ( revents & POLLIN )
|
||||
{
|
||||
int fd = accept4(server->fd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC);
|
||||
if ( 0 <= fd )
|
||||
{
|
||||
struct connection* conn = connection_new(fd);
|
||||
if ( !conn )
|
||||
{
|
||||
warning("Failed to allocate connection: %s: %m", server->path);
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
else if ( errno != EAGAIN && errno != EWOULDBLOCK )
|
||||
warning("accept: %s: %m", server->path);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void init(void)
|
||||
{
|
||||
int default_daemon_exit_code = -1;
|
||||
|
@ -2500,6 +2904,12 @@ static void init(void)
|
|||
case COMMUNICATION_TYPE_READY:
|
||||
closed = daemon_on_ready_event(comm->daemon, pfd->revents);
|
||||
break;
|
||||
case COMMUNICATION_TYPE_SERVER:
|
||||
closed = server_on_event(comm->server, pfd->revents);
|
||||
break;
|
||||
case COMMUNICATION_TYPE_CONNECTION:
|
||||
closed = connection_on_event(comm->connection, pfd->revents);
|
||||
break;
|
||||
}
|
||||
if ( closed )
|
||||
i--; // Process this index again (something new there).
|
||||
|
@ -3712,13 +4122,17 @@ int main(int argc, char* argv[])
|
|||
reinit();
|
||||
}
|
||||
|
||||
const char* server_path = "/var/run/init";
|
||||
if ( !server_start(server_path) )
|
||||
fatal("Failed to start init server: %s: %m", server_path);
|
||||
|
||||
// TODO: Use the arguments to specify additional things the default daemon
|
||||
// should depend on, as well as a denylist of things not to start
|
||||
// even if in default's dependencies. The easiest thing is probably to
|
||||
// be able to inject require and unset require lines into default.
|
||||
|
||||
// Request the default daemon be run.
|
||||
schedule_daemon(default_daemon);
|
||||
daemon_schedule(default_daemon);
|
||||
|
||||
// Initialize the operating system.
|
||||
init();
|
||||
|
|
Loading…
Reference in New Issue