diff --git a/init/Makefile b/init/Makefile index 6ec3626d..8ee7bbdb 100644 --- a/init/Makefile +++ b/init/Makefile @@ -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 diff --git a/init/init.c b/init/init.c index 18a2cd80..371edb2e 100644 --- a/init/init.c +++ b/init/init.c @@ -20,8 +20,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -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();