/* * Copyright (c) 2011-2022 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * init.c * Start the operating system. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // TODO: The Sortix doesn't expose this at the moment. #if !defined(HOST_NAME_MAX) && defined(__sortix__) #include #endif #include #include #include #include #include #include #include struct device_match { const char* path; struct blockdevice* bdev; }; struct mountpoint { struct fstab entry; char* entry_line; pid_t pid; char* absolute; }; enum exit_code_meaning { EXIT_CODE_MEANING_DEFAULT, EXIT_CODE_MEANING_POWEROFF_REBOOT, }; enum daemon_state { DAEMON_STATE_TERMINATED, DAEMON_STATE_SCHEDULED, DAEMON_STATE_WAITING, DAEMON_STATE_SATISFIED, DAEMON_STATE_STARTING, DAEMON_STATE_READY, DAEMON_STATE_RUNNING, DAEMON_STATE_TERMINATING, DAEMON_STATE_FINISHED, NUM_DAEMON_STATES }; struct daemon; struct dependency { struct daemon* source; struct daemon* target; char* target_name; // TODO: Can we avoid this? int flags; // TODO: bool ready, finished, failed? }; #define DEPENDENCY_FLAG_REQUIRE (1 << 0) #define DEPENDENCY_FLAG_AWAIT (1 << 1) #define DEPENDENCY_FLAG_EXIT_CODE (1 << 2) // TODO: Flag to signal that it's okay if the dependency is restarted / isn't // there all the time. enum log_method { LOG_METHOD_APPEND, LOG_METHOD_ROTATE, }; enum log_format { LOG_FORMAT_NONE, LOG_FORMAT_SECONDS, LOG_FORMAT_NANOSECONDS, LOG_FORMAT_BASIC, LOG_FORMAT_FULL, LOG_FORMAT_SYSLOG, }; struct log { enum log_method method; enum log_format format; bool control_messages; bool rotate_on_start; size_t max_rotations; size_t max_line_size; off_t max_size; char* path; char* path_src; char* path_dst; size_t path_number_offset; size_t path_number_size; off_t size; int fd; bool line_terminated; bool line_begun; }; struct daemon { char* name; struct daemon* next_by_state; struct daemon* prev_by_state; struct daemon* parent_of_parameter; struct daemon* first_by_parameter; struct daemon* last_by_parameter; struct daemon* prev_by_parameter; struct daemon* next_by_parameter; struct dependency** dependencies; size_t dependencies_used; size_t dependencies_length; size_t dependencies_ready; size_t dependencies_finished; size_t dependencies_failed; struct dependency** dependents; size_t dependents_used; size_t dependents_length; size_t reference_count; size_t pfd_readyfd_index; size_t pfd_outputfd_index; struct dependency* exit_code_from; char* cd; char* exec; char* netif; struct termios oldtio; struct log log; pid_t pid; enum exit_code_meaning exit_code_meaning; enum daemon_state state; int exit_code; int readyfd; int outputfd; bool need_tty; bool was_ready; bool was_terminated; bool was_dereferenced; }; struct dependency_config { char* target; int flags; }; struct daemon_config { char* name; struct dependency_config** dependencies; size_t dependencies_used; size_t dependencies_length; char** variables; size_t variables_used; size_t variables_length; char* cd; char* exec; enum exit_code_meaning exit_code_meaning; bool per_if; bool need_tty; enum log_method log_method; enum log_format log_format; bool log_control_messages; bool log_rotate_on_start; size_t log_rotations; size_t log_line_size; off_t log_size; }; static pid_t main_pid; static pid_t forward_signal_pid = -1; static volatile sig_atomic_t caught_exit_signal = -1; static sigset_t handled_signals; static struct daemon_config default_config = { .log_method = LOG_METHOD_ROTATE, .log_format = LOG_FORMAT_NANOSECONDS, .log_control_messages = true, .log_rotate_on_start = false, .log_rotations = 3, .log_line_size = 4096, .log_size = 1048576, }; static struct log init_log = { .fd = -1 }; static struct harddisk** hds = NULL; static size_t hds_used = 0; static size_t hds_length = 0; static struct mountpoint* mountpoints = NULL; static size_t mountpoints_used = 0; static size_t mountpoints_length = 0; static struct daemon** daemons = NULL; static size_t daemons_used = 0; static size_t daemons_length = 0; static struct daemon* first_daemon_by_state[NUM_DAEMON_STATES]; static struct daemon* last_daemon_by_state[NUM_DAEMON_STATES]; static size_t count_daemon_by_state[NUM_DAEMON_STATES]; static struct pollfd* pfds = NULL; static size_t pfds_used = 0; static size_t pfds_length = 0; static struct daemon** pfds_daemon = NULL; static size_t pfds_daemon_length = 0; static bool chain_location_made = false; static char chain_location[] = "/tmp/fs.XXXXXX"; static bool chain_location_dev_made = false; static char chain_location_dev[] = "/tmp/fs.XXXXXX/dev"; static void signal_handler(int signum) { if ( getpid() != main_pid ) return; if ( forward_signal_pid != -1 ) { if ( 0 < forward_signal_pid ) kill(forward_signal_pid, signum); return; } switch ( signum ) { case SIGINT: caught_exit_signal = 1; break; case SIGTERM: caught_exit_signal = 0; break; case SIGQUIT: caught_exit_signal = 2; break; } } static void install_signal_handler(void) { sigemptyset(&handled_signals); sigaddset(&handled_signals, SIGINT); sigaddset(&handled_signals, SIGQUIT); sigaddset(&handled_signals, SIGTERM); sigprocmask(SIG_BLOCK, &handled_signals, NULL); struct sigaction sa = { .sa_handler = signal_handler, .sa_flags = 0 }; sigaction(SIGINT, &sa, NULL); sigaction(SIGQUIT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); } static void uninstall_signal_handler(void) { struct sigaction sa = { .sa_handler = SIG_DFL, .sa_flags = 0 }; sa.sa_handler = SIG_DFL; sa.sa_flags = 0; sigaction(SIGINT, &sa, NULL); sigaction(SIGQUIT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); sigprocmask(SIG_UNBLOCK, &handled_signals, NULL); } static char* read_single_line(FILE* fp) { char* ret = NULL; size_t ret_size = 0; ssize_t ret_length = getline(&ret, &ret_size, fp); if ( ret_length < 0 ) { free(ret); return NULL; } if ( ret[ret_length-1] == '\n' ) ret[--ret_length] = '\0'; return ret; } static char* join_paths(const char* a, const char* b) { size_t a_len = strlen(a); bool has_slash = (a_len && a[a_len-1] == '/') || b[0] == '/'; char* result; if ( (has_slash && asprintf(&result, "%s%s", a, b) < 0) || (!has_slash && asprintf(&result, "%s/%s", a, b) < 0) ) return NULL; return result; } static bool array_add(void*** array_ptr, size_t* used_ptr, size_t* length_ptr, void* value) { void** array; memcpy(&array, array_ptr, sizeof(array)); // Strict aliasing. if ( *used_ptr == *length_ptr ) { // TODO: Avoid overflow (in all copies of this code). size_t new_length = 2 * *length_ptr; if ( !new_length ) new_length = 16; // TODO: Avoid overflow and use reallocarray in all copies of this code. size_t new_size = new_length * sizeof(void*); void** new_array = (void**) realloc(array, new_size); if ( !new_array ) return false; array = new_array; memcpy(array_ptr, &array, sizeof(array)); // Strict aliasing. *length_ptr = new_length; } memcpy(array + (*used_ptr)++, &value, sizeof(value)); // Strict aliasing. return true; } static int exit_code_to_exit_status(int exit_code) { if ( WIFEXITED(exit_code) ) return WEXITSTATUS(exit_code); else if ( WIFSIGNALED(exit_code) ) return 128 + WTERMSIG(exit_code); else return 1; } __attribute__((noreturn)) __attribute__((format(printf, 1, 2))) static void fatal(const char* format, ...) { va_list ap; va_start(ap, format); fprintf(stderr, "%s: fatal: ", program_invocation_name); vfprintf(stderr, format, ap); fprintf(stderr, "\n"); fflush(stderr); va_end(ap); if ( getpid() == main_pid ) exit(2); _exit(2); } // TODO: error __attribute__((format(printf, 1, 2))) static void warning(const char* format, ...) { va_list ap; va_start(ap, format); fprintf(stderr, "%s: warning: ", program_invocation_name); vfprintf(stderr, format, ap); fprintf(stderr, "\n"); fflush(stderr); va_end(ap); } __attribute__((format(printf, 1, 2))) static void note(const char* format, ...) { va_list ap; va_start(ap, format); fprintf(stderr, "%s: ", program_invocation_name); vfprintf(stderr, format, ap); fprintf(stderr, "\n"); fflush(stderr); va_end(ap); } // TODO: Log fsck and such early logging? What about chain booting? static void log_close(struct log* log) { if ( 0 <= log->fd ) close(log->fd); log->fd = -1; } static bool log_open(struct log* log) { int logflags = O_CREAT | O_WRONLY | O_APPEND | O_NOFOLLOW; if ( log->method == LOG_METHOD_APPEND && log->rotate_on_start ) logflags |= O_TRUNC; if ( 0 <= log->fd ) close(log->fd); // TODO: Allow daemons to have non-public logs. log->fd = open(log->path, logflags, 0644); if ( log->fd < 0 ) { // Don't let read-only filesystems block daemons from trying to start. if ( errno == EROFS ) { // TODO: Warn once about /var/log being read-only. return true; } warning("%s: %m", log->path); return false; } struct stat st; if ( fstat(log->fd, &st) < 0 ) { warning("stat: %s: %m", log->path); close(log->fd); log->fd = -1; return false; } log->size = st.st_size; log->line_terminated = true; return true; } static bool log_rotate(struct log* log) { if ( 0 <= log->fd ) { close(log->fd); log->fd = -1; } for ( size_t i = log->max_rotations; 0 < i; i-- ) { snprintf(log->path_dst + log->path_number_offset, log->path_number_size, ".%zu", i); snprintf(log->path_src + log->path_number_offset, log->path_number_size, "%c%zu", i-1 != 0 ? '.' : '\0', i-1); if ( i == log->max_rotations ) { if ( !access(log->path_dst, F_OK) ) { // Ensure the file system space usage has an upper bound by // deleting the oldest log. However if another process has the // log open, the kernel will keep the file contents alive. The // file is truncated to zero size to avoid disk space remaining // temporarily in use that way, although the inode itself does // remain open temporarily. // TODO: Handle EROFS. // TODO: truncateat should have a flags parameter, to not follow // symbolic links. Otherwise a symlink in /var/log could be // used to truncate an arbitrary file, which is avoided here. int fd = open(log->path_dst, O_WRONLY | O_NOFOLLOW); if ( fd < 0 ) warning("archiving: opening: %s", log->path_dst); else { if ( ftruncate(fd, 0) < 0 ) { if ( errno == EROFS ) { // TODO: Warn once about /var/log being read-only. } else warning("archiving: truncate: %s", log->path_dst); } close(fd); } if ( unlink(log->path_dst) < 0 ) { if ( errno == EROFS ) { // TODO: Warn once about /var/log being read-only. } else warning("archiving: unlink: %s", log->path_dst); } } else if ( errno != ENOENT ) warning("archiving: %s", log->path_dst); } if ( rename(log->path_src, log->path_dst) < 0 ) { if ( errno == ENOENT ) { // Ignore non-existent logs. } else if ( errno == EROFS ) { // TODO: Warn once about /var/log being read-only. } else { // TODO: Should an error message be printed here or elsewhere? return false; } } } return log_open(log); } static bool log_begin(struct log* log) { if ( log->method == LOG_METHOD_ROTATE && log->rotate_on_start ) return log_rotate(log); return log_open(log); } static bool log_initialize(struct log* log, const char* name, struct daemon_config* daemon_config) { memset(log, 0, sizeof(*log)); log->fd = -1; log->method = daemon_config->log_method; log->format = daemon_config->log_format; log->control_messages = daemon_config->log_control_messages; log->rotate_on_start = daemon_config->log_rotate_on_start; log->max_rotations = daemon_config->log_rotations; log->max_line_size = daemon_config->log_line_size; log->max_size = daemon_config->log_size; if ( asprintf(&log->path, "/var/log/%s.log", name) < 0 ) return false; // Preallocate the paths used when renaming log files so there's no error // conditions when cycling logs. if ( asprintf(&log->path_src, "%s.%i", log->path, INT_MAX) < 0 ) return free(log->path), false; if ( asprintf(&log->path_dst, "%s.%i", log->path, INT_MAX) < 0 ) return free(log->path_src), free(log->path), false; log->path_number_offset = strlen(log->path); log->path_number_size = strlen(log->path_dst) + 1 - log->path_number_offset; return true; } static void log_data(struct log* log, const char* data, size_t length) { // TODO: Support for infinitely sized chunks / OFF_MAX chunks? // TODO: Ensure log->max_line_size <= log->max_size. const off_t chunk_cut_offset = log->max_size - log->max_line_size; size_t sofar = 0; while ( sofar < length ) { if ( log->fd < 0 ) { // TODO: Handle skipped data? //log->skipped += length - sofar; return; } // If the data is currently line terminated, then cut if we can't add // another line of the maximum length, otherwise cut if the chunk is // full. if ( log->method == LOG_METHOD_ROTATE && (log->line_terminated ? chunk_cut_offset : log->max_size) <= log->size ) { if ( !log_rotate(log) ) { // TODO: Handle skipped data? //log->skipped += length - sofar; return; } } // Decide the size of the new chunk to write out. const char* next_data = data + sofar; size_t remaining_length = length - sofar; size_t next_length = remaining_length; if ( log->method == LOG_METHOD_ROTATE ) { off_t chunk_left = log->max_size - log->size; next_length = (uintmax_t) remaining_length < (uintmax_t) chunk_left ? (size_t) remaining_length : (size_t) chunk_left; // Attempt to cut the log at a newline. if ( chunk_cut_offset <= log->size + (off_t) next_length ) { // Find where the data becomes eligible for a line cut, and // search for a newline after that. size_t first_cut_index = log->size < chunk_cut_offset ? 0 : (size_t) (chunk_cut_offset - log->size); for ( size_t i = first_cut_index; i < next_length; i++ ) { if ( next_data[i] == '\n' ) { next_length = i + 1; break; } } } } ssize_t amount = write(log->fd, next_data, next_length); if ( amount < 0 ) { // TODO: Don't spam here. warning("writing log: %s: %m", log->path); // TODO: Always rotate on error if non-empty? // TODO: Handle skipped data? //log->skipped += length - sofar; return; } sofar += amount; log->size += amount; log->line_terminated = next_data[amount - 1] == '\n'; } } static void log_formatted(struct log* log, const char* string, size_t length) { if ( log->format == LOG_FORMAT_NONE ) { log_data(log, string, length); return; } // TODO: This information should be part of struct log. const char* log_name = log->path; if ( strrchr(log_name, '/') ) log_name = strrchr(log_name, '/') + 1; size_t log_name_length = strlen(log_name) - 4 /* .log */; for ( size_t i = 0; i < length; ) { size_t fragment = 1; while ( string[i + fragment - 1] != '\n' && i + fragment < length ) fragment++; if ( !log->line_begun ) { struct timespec now; clock_gettime(CLOCK_REALTIME, &now); struct tm tm; gmtime_r(&now.tv_sec, &tm); char hostname[HOST_NAME_MAX + 1]; gethostname(hostname, sizeof(hostname)); if ( log->format == LOG_FORMAT_SYSLOG ) { int pri = 3 /* system daemons */ * 8 + 6 /* informational */; char header[64]; snprintf(header, sizeof(header), "<%d>1 ", pri); log_data(log, header, strlen(header)); } char timeformat[64] = "%F %T +0000"; if ( log->format == LOG_FORMAT_SYSLOG ) snprintf(timeformat, sizeof(timeformat), "%%FT%%T.%06liZ", now.tv_nsec / 1000); else if ( log->format != LOG_FORMAT_SECONDS ) snprintf(timeformat, sizeof(timeformat), "%%F %%T.%09li +0000", now.tv_nsec); char timestamp[64]; strftime(timestamp, sizeof(timestamp), timeformat, &tm); log_data(log, timestamp, strlen(timestamp)); if ( log->format == LOG_FORMAT_FULL || log->format == LOG_FORMAT_SYSLOG ) { log_data(log, " ", 1); log_data(log, hostname, strlen(hostname)); } if ( log->format == LOG_FORMAT_BASIC || log->format == LOG_FORMAT_FULL || log->format == LOG_FORMAT_SYSLOG ) { log_data(log, " ", 1); log_data(log, log_name, log_name_length); } // TODO: Pid of the daemon instead. if ( log->format == LOG_FORMAT_SYSLOG ) log_data(log, " - - - ", strlen(" - 1 - ")); else log_data(log, ": ", 2); } log_data(log, string + i, fragment); log->line_begun = string[i + fragment - 1] != '\n'; i += fragment; } } static size_t log_callback(void* ctx, const char* str, size_t len) { log_formatted((struct log*) ctx, str, len); return len; } __attribute__((format(printf, 2, 3))) static void log_status(const char* status, const char* format, ...) { va_list ap; va_start(ap, format); vcbprintf(&init_log, log_callback, format, ap); va_end(ap); // TODO: Verbose logging mode. if ( strcmp(status, "failed") != 0 ) return; struct timespec now; clock_gettime(CLOCK_REALTIME, &now); struct tm tm; localtime_r(&now.tv_sec, &tm); va_start(ap, format); fprintf(stderr, "%04d-%02d-%02d %02d:%02d:%02d ", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday + 1, tm.tm_hour, tm.tm_min, tm.tm_sec); if ( !strcmp(status, "starting") ) fprintf(stderr, "[ ] "); else if ( !strcmp(status, "started") ) fprintf(stderr, "[ \e[92mOK\e[m ] "); else if ( !strcmp(status, "finished") ) fprintf(stderr, "[ \e[92mDONE\e[m ] "); else if ( !strcmp(status, "failed") ) fprintf(stderr, "[\e[91mFAILED\e[m] "); else if ( !strcmp(status, "stopping") ) fprintf(stderr, "[ ] "); else if ( !strcmp(status, "stopped") ) fprintf(stderr, "[ \e[92mOK\e[m ] "); else fprintf(stderr, "[ ?? ] "); vfprintf(stderr, format, ap); fprintf(stderr, "\n"); fflush(stderr); va_end(ap); } static void daemon_config_free(struct daemon_config* daemon_config) { free(daemon_config->name); for ( size_t i = 0; i < daemon_config->dependencies_used; i++ ) { free(daemon_config->dependencies[i]->target); free(daemon_config->dependencies[i]); } free(daemon_config->dependencies); for ( size_t i = 0; i < daemon_config->variables_used; i++ ) free(daemon_config->variables[i]); free(daemon_config->variables); free(daemon_config->cd); free(daemon_config->exec); free(daemon_config); } static bool daemon_config_load_search(struct daemon_config* daemon_config, size_t next_search_path_index); static bool daemon_process_line(struct daemon_config* daemon_config, const char* path, char* line, off_t line_number, size_t next_search_path_index) { char* input = line; while ( isspace((unsigned char) input[0]) ) input++; if ( !input[0] || input[0] == '#' ) return true; char* operation = input; size_t operation_length = 0; while ( operation[operation_length] && !isspace((unsigned char) operation[operation_length]) ) operation_length++; char* parameter = operation + operation_length; while ( isspace((unsigned char) parameter[0]) ) parameter++; operation[operation_length] = '\0'; if ( !strcmp(operation, "cd") ) { free(daemon_config->cd); if ( !(daemon_config->cd = strdup(parameter)) ) { warning("strdup: %m"); return false; } } else if ( !strcmp(operation, "exec") ) { free(daemon_config->exec); if ( !(daemon_config->exec = strdup(parameter)) ) { warning("strdup: %m"); return false; } } else if ( !strcmp(operation, "exit-code-meaning") ) { if ( !strcmp(parameter, "default") ) daemon_config->exit_code_meaning = EXIT_CODE_MEANING_DEFAULT; else if ( !strcmp(parameter, "poweroff-reboot") ) daemon_config->exit_code_meaning = EXIT_CODE_MEANING_POWEROFF_REBOOT; else warning("%s:%ji: unknown %s: %s", path, (intmax_t) line_number, operation, parameter); } else if ( !strcmp(operation, "furthermore") ) { if ( parameter[0] ) warning("%s:%ji: unexpected parameter to %s: %s", path, (intmax_t) line_number, operation, parameter); // TODO: Only once per search path level. // TODO: How about requiring it to be the first statement? if ( !daemon_config_load_search(daemon_config, next_search_path_index) ) { if ( errno == ENOENT ) { warning("%s:%ji: 'furthermore' failed to locate next '%s' " "configuration file in search path: %m", path, (intmax_t) line_number, daemon_config->name); errno = EINVAL; } else warning("%s: while processing 'furthermore': %m", path); return false; } } else if ( !strcmp(operation, "log-control-messages") ) { if ( !strcmp(parameter, "true") ) daemon_config->log_control_messages = true; else if ( !strcmp(parameter, "false") ) daemon_config->log_control_messages = false; else warning("%s:%ji: unknown %s: %s", path, (intmax_t) line_number, operation, parameter); } else if ( !strcmp(operation, "log-format") ) { if ( !strcmp(parameter, "none") ) daemon_config->log_format = LOG_FORMAT_NONE; else if ( !strcmp(parameter, "seconds") ) daemon_config->log_format = LOG_FORMAT_SECONDS; else if ( !strcmp(parameter, "nanoseconds") ) daemon_config->log_format = LOG_FORMAT_NANOSECONDS; else if ( !strcmp(parameter, "basic") ) daemon_config->log_format = LOG_FORMAT_BASIC; else if ( !strcmp(parameter, "full") ) daemon_config->log_format = LOG_FORMAT_FULL; else if ( !strcmp(parameter, "syslog") ) daemon_config->log_format = LOG_FORMAT_SYSLOG; else warning("%s:%ji: unknown %s: %s", path, (intmax_t) line_number, operation, parameter); } else if ( !strcmp(operation, "log-line-size") ) { char* end; errno = 0; uintmax_t value = strtoumax(parameter, &end, 10); if ( parameter == end || errno || value != (size_t) value ) warning("%s:%ji: invalid %s: %s", path, (intmax_t) line_number, operation, parameter); else daemon_config->log_line_size = (size_t) value; } else if ( !strcmp(operation, "log-method") ) { if ( !strcmp(parameter, "append") ) daemon_config->log_method = LOG_METHOD_APPEND; else if ( !strcmp(parameter, "rotate") ) daemon_config->log_method = LOG_METHOD_ROTATE; else warning("%s:%ji: unknown %s: %s", path, (intmax_t) line_number, operation, parameter); } else if ( !strcmp(operation, "log-rotate-on-start") ) { if ( !strcmp(parameter, "true") ) daemon_config->log_rotate_on_start = true; else if ( !strcmp(parameter, "false") ) daemon_config->log_rotate_on_start = false; else warning("%s:%ji: unknown %s: %s", path, (intmax_t) line_number, operation, parameter); } else if ( !strcmp(operation, "log-size") ) { char* end; errno = 0; intmax_t value = strtoimax(parameter, &end, 10); if ( parameter == end || errno || value != (off_t) value ) warning("%s:%ji: invalid %s: %s", path, (intmax_t) line_number, operation, parameter); else daemon_config->log_size = (off_t) value; } else if ( !strcmp(operation, "per") ) { if ( !strcmp(parameter, "if") ) daemon_config->per_if = true; else warning("%s:%ji: unknown %s: %s", path, (intmax_t) line_number, operation, parameter); } else if ( !strcmp(operation, "need") ) { if ( !strcmp(parameter, "tty") ) daemon_config->need_tty = true; else warning("%s:%ji: unknown %s: %s", path, (intmax_t) line_number, operation, parameter); } else if ( !strcmp(operation, "require") ) { // TODO: Better tokenization. size_t target_name_length = 0; while ( parameter[target_name_length] && !isspace((unsigned char) parameter[target_name_length]) ) target_name_length++; if ( !target_name_length ) { warning("%s:%ji: error: No dependency was named: %s", path, (intmax_t) line_number, line); return true; } char* target = strndup(parameter, target_name_length); if ( !target ) { warning("strdup: %m"); return false; } int negated_flags = DEPENDENCY_FLAG_REQUIRE | DEPENDENCY_FLAG_AWAIT; int flags = negated_flags; size_t flag_index = target_name_length; while ( parameter[flag_index] ) { while ( isspace((unsigned char) parameter[flag_index]) ) flag_index++; if ( !parameter[flag_index] ) break; char* flag = parameter + flag_index; size_t flag_length = 0; while ( flag[flag_length] && !isspace((unsigned char) flag[flag_length]) ) flag_length++; flag_index += flag_length; if ( flag_length == strlen("optional") && !memcmp(flag, "optional", strlen("optional")) ) { flags &= ~DEPENDENCY_FLAG_REQUIRE; } else if ( flag_length == strlen("no-await") && !memcmp(flag, "no-await", strlen("no-await")) ) { flags &= ~DEPENDENCY_FLAG_AWAIT; } else if ( flag_length == strlen("exit-code") && !memcmp(flag, "exit-code", strlen("exit-code")) ) { // TODO: Warning if multiple requirements use exit-code. flags |= DEPENDENCY_FLAG_EXIT_CODE; } else { warning("%s:%ji: error: Unsupported flag '%.*s' (%zu): %s", path, (intmax_t) line_number, (int) flag_length, flag, flag_length, line); // TODO: Error handling? } } // TODO: Linear time lookup. struct dependency_config* dependency = NULL; for ( size_t i = 0; i < daemon_config->dependencies_used; i++ ) { if ( !strcmp(daemon_config->dependencies[i]->target, target) ) { dependency = daemon_config->dependencies[i]; break; } } if ( dependency ) { dependency->flags &= flags & negated_flags; dependency->flags |= flags & ~negated_flags; free(target); } else { dependency = (struct dependency_config*) calloc(1, sizeof(struct dependency_config)); if ( !dependency ) { warning("malloc: %m"); free(target); return false; } dependency->target = target; dependency->flags = flags; if ( !array_add((void***) &daemon_config->dependencies, &daemon_config->dependencies_used, &daemon_config->dependencies_length, dependency) ) { warning("malloc: %m"); free(target); free(dependency); return false; } } } else if ( !strcmp(operation, "tty") ) { // TODO: Implement. } else if ( !strcmp(operation, "unset") ) { if ( !strcmp(parameter, "cd") ) { free(daemon_config->cd); daemon_config->cd = NULL; } else if ( !strcmp(parameter, "exec") ) { free(daemon_config->exec); daemon_config->exec = NULL; } else if ( !strcmp(parameter, "exit-code-meaning") ) daemon_config->exit_code_meaning = EXIT_CODE_MEANING_DEFAULT; else if ( !strcmp(operation, "log-control-messages") ) daemon_config->log_control_messages = default_config.log_control_messages; else if ( !strcmp(operation, "log-format") ) daemon_config->log_format = default_config.log_format; else if ( !strcmp(operation, "log-line-size") ) daemon_config->log_line_size = default_config.log_line_size; else if ( !strcmp(operation, "log-method") ) daemon_config->log_method = default_config.log_method; else if ( !strcmp(operation, "log-rotate-on-start") ) daemon_config->log_rotate_on_start = default_config.log_rotate_on_start; else if ( !strcmp(operation, "log-size") ) daemon_config->log_line_size = default_config.log_line_size; // TODO: Proper tokenzation. else if ( !strcmp(parameter, "per if") ) { daemon_config->per_if = false; } // TODO: Proper tokenzation. else if ( !strcmp(parameter, "need tty") ) { daemon_config->need_tty = false; } // TODO: Better tokenzation. // TODO: Fully test this implementation. else if ( !strncmp(parameter, "require", strlen("require")) && isspace((unsigned char) parameter[strlen("require")]) ) { parameter += strlen("require"); while ( isspace((unsigned char) parameter[0]) ) parameter++; // TODO: Better tokenization. size_t target_name_length = 0; while ( parameter[target_name_length] && !isspace((unsigned char) parameter[target_name_length]) ) target_name_length++; if ( !target_name_length ) { // TODO: Instead delete all dependencies? // TODO: Proper error handling. warning("%s:%ji: error: No dependency was named: %s", path, (intmax_t) line_number, line); return true; } // TODO: Linear time lookup. struct dependency_config* dependency = NULL; size_t i; for ( i = 0; i < daemon_config->dependencies_used; i++ ) { if ( !strncmp(daemon_config->dependencies[i]->target, parameter, target_name_length) && !daemon_config->dependencies[i]->target[ target_name_length] ) { dependency = daemon_config->dependencies[i]; break; } } if ( !dependency ) { // TODO: Proper error handling. warning("%s:%ji: error: Dependency wasn't already required: %s", path, (intmax_t) line_number, line); return true; } parameter += target_name_length; while ( isspace((unsigned char) parameter[0]) ) parameter++; if ( !parameter[0] ) { // TODO: Shrink array. free(daemon_config->dependencies[i]->target); size_t last = daemon_config->dependencies_used - 1; if ( i != last ) { daemon_config->dependencies[i] = daemon_config->dependencies[last]; daemon_config->dependencies[last] = NULL; } daemon_config->dependencies_used--; return true; } size_t flag_index = target_name_length; while ( parameter[flag_index] ) { while ( isspace((unsigned char) parameter[flag_index]) ) flag_index++; if ( !parameter[flag_index] ) break; char* flag = parameter + flag_index; size_t flag_length = 0; while ( flag[flag_length] && !isspace((unsigned char) flag[flag_length]) ) flag_length++; flag_index += flag_length; if ( flag_length == strlen("optional") && !memcmp(flag, "optional", strlen("optional")) ) { if ( dependency->flags & DEPENDENCY_FLAG_REQUIRE ) warning("%s:%ji: error: " "Dependency wasn't already optional: %s", path, (intmax_t) line_number, line); else dependency->flags |= DEPENDENCY_FLAG_REQUIRE; } else if ( flag_length == strlen("no-await") && !memcmp(flag, "no-await", strlen("no-await")) ) { if ( dependency->flags & DEPENDENCY_FLAG_AWAIT ) warning("%s:%ji: error: " "Dependency wasn't already no-await: %s", path, (intmax_t) line_number, line); else dependency->flags |= DEPENDENCY_FLAG_AWAIT; } else if ( flag_length == strlen("exit-code") && !memcmp(flag, "exit-code", strlen("exit-code")) ) { if ( !(dependency->flags & DEPENDENCY_FLAG_EXIT_CODE) ) warning("%s:%ji: error: " "Dependency wasn't already exit-code: %s", path, (intmax_t) line_number, line); else dependency->flags &= ~DEPENDENCY_FLAG_EXIT_CODE; } else { warning("%s:%ji: error: Unsupported flag '%.*s' (%zu): %s", path, (intmax_t) line_number, (int) flag_length, flag, flag_length, line); // TODO: Error handling? } } } else if ( !strcmp(parameter, "tty") ) { // TODO: Implement. } else warning("%s:%ji: unknown unset operation: %s", path, (intmax_t) line_number, operation); } else warning("%s:%ji: unknown operation: %s", path, (intmax_t) line_number, operation); return true; } static bool daemon_config_load_from_path(struct daemon_config* daemon_config, const char* path, size_t next_search_path_index) { FILE* fp = fopen(path, "r"); if ( !fp ) { if ( errno != ENOENT ) warning("%s: Failed to load daemon configuration file: %m", path); return false; } char* line = NULL; size_t line_size = 0; ssize_t line_length; off_t line_number = 0; while ( 0 < (line_length = getline(&line, &line_size, fp)) ) { if ( line[line_length-1] == '\n' ) line[--line_length] = '\0'; line_number++; if ( !daemon_process_line(daemon_config, path, line, line_number, next_search_path_index) ) { fclose(fp); free(line); return false; } } free(line); if ( ferror(fp) ) { warning("%s: %m", path); fclose(fp); return false; } fclose(fp); return true; } static bool daemon_config_load_search(struct daemon_config* daemon_config, size_t next_search_path_index) { // If the search path ever becomes arbitrarily long, consider handling the // 'furthermore' feature in a manner using constant stack space rather than // recursion. const char* search_paths[] = { "/etc/init", "/share/init", }; size_t search_paths_count = sizeof(search_paths) / sizeof(search_paths[0]); for ( size_t i = next_search_path_index; i < search_paths_count; i++ ) { const char* search_path = search_paths[i]; char* path = join_paths(search_path, daemon_config->name); if ( !path ) { warning("malloc: %m"); return false; } if ( !daemon_config_load_from_path(daemon_config, path, i + 1) ) { // TODO: Ensure ENOENT is not set recursively when using // furthermore and the next file doesn't exist. if ( errno == ENOENT ) continue; free(path); return NULL; } free(path); return true; } errno = ENOENT; return false; } static void daemon_config_initialize(struct daemon_config* daemon_config) { memset(daemon_config, 0, sizeof(*daemon_config)); daemon_config->log_method = default_config.log_method; daemon_config->log_format = default_config.log_format; daemon_config->log_control_messages = default_config.log_control_messages; daemon_config->log_rotate_on_start = default_config.log_rotate_on_start; daemon_config->log_rotations = default_config.log_rotations; daemon_config->log_line_size = default_config.log_line_size; daemon_config->log_size = default_config.log_size; } static struct daemon_config* daemon_config_load(const char* name) { struct daemon_config* daemon_config = malloc(sizeof(struct daemon_config)); if ( !daemon_config ) { warning("malloc: %m"); return NULL; } daemon_config_initialize(daemon_config); if ( !(daemon_config->name = strdup(name)) ) { warning("malloc: %m"); daemon_config_free(daemon_config); return NULL; } if ( !daemon_config_load_search(daemon_config, 0) ) { if ( errno == ENOENT ) warning("failed to locate daemon configuration: %s: %m", name); daemon_config_free(daemon_config); return NULL; } return daemon_config; } static char** generate_argv(const char* command, const char* netif) { char** argv = NULL; size_t argv_used = 0; size_t argv_length = 0; // TODO: Proper shell quote expander and parser. while ( command[0] ) { size_t length = strcspn(command, "\t\n "); if ( length == 0 ) { command++; continue; } char* arg = strndup(command, length); if ( !arg ) fatal("malloc: %m"); if ( !array_add((void***) &argv, &argv_used, &argv_length, arg) ) fatal("malloc: %m"); command += length; } if ( netif ) { char* arg = strdup(netif); if ( !arg ) fatal("malloc: %m"); if ( !array_add((void***) &argv, &argv_used, &argv_length, arg) ) fatal("malloc: %m"); } if ( !array_add((void***) &argv, &argv_used, &argv_length, NULL) ) fatal("malloc: %m"); return argv; } // TODO: Replace with better data structure. static struct daemon* add_daemon(void) { if ( daemons_used == daemons_length ) { size_t new_length = 2 * daemons_length; if ( new_length == 0 ) new_length = 16; struct daemon** new_daemons = reallocarray(daemons, sizeof(struct daemon*), new_length); if ( !new_daemons ) fatal("malloc: %m"); daemons = new_daemons; daemons_length = new_length; } struct daemon* daemon = calloc(1, sizeof(struct daemon)); if ( !daemon ) fatal("malloc: %m"); daemons[daemons_used++] = daemon; return daemon; } // TODO: This runs in O(n) but could be in O(log n). static struct daemon* daemon_find_by_name(const char* name) { for ( size_t i = 0; i < daemons_used; i++ ) if ( !strcmp(daemons[i]->name, name) ) return daemons[i]; return NULL; } // TODO: This runs in O(n) but could be in O(log n). static struct daemon* daemon_find_by_pid(pid_t pid) { for ( size_t i = 0; i < daemons_used; i++ ) if ( daemons[i]->pid == pid ) return daemons[i]; return NULL; } static bool daemon_is_failed(struct daemon* daemon) { if ( daemon->was_terminated && WIFSIGNALED(daemon->exit_code) && WTERMSIG(daemon->exit_code) == SIGTERM ) return false; switch ( daemon->exit_code_meaning ) { case EXIT_CODE_MEANING_DEFAULT: return !WIFEXITED(daemon->exit_code) || WEXITSTATUS(daemon->exit_code) != 0; case EXIT_CODE_MEANING_POWEROFF_REBOOT: return !WIFEXITED(daemon->exit_code) || 3 <= WEXITSTATUS(daemon->exit_code); } return true; } static void daemon_insert_state_list(struct daemon* daemon) { assert(!daemon->prev_by_state); assert(!daemon->next_by_state); assert(first_daemon_by_state[daemon->state] != daemon); assert(last_daemon_by_state[daemon->state] != daemon); daemon->prev_by_state = last_daemon_by_state[daemon->state]; daemon->next_by_state = NULL; if ( last_daemon_by_state[daemon->state] ) last_daemon_by_state[daemon->state]->next_by_state = daemon; else first_daemon_by_state[daemon->state] = daemon; last_daemon_by_state[daemon->state] = daemon; count_daemon_by_state[daemon->state]++; } static void daemon_remove_state_list(struct daemon* daemon) { assert(daemon->prev_by_state || daemon == first_daemon_by_state[daemon->state]); assert(daemon->next_by_state || daemon == last_daemon_by_state[daemon->state]); assert(0 < count_daemon_by_state[daemon->state]); if ( daemon->prev_by_state ) daemon->prev_by_state->next_by_state = daemon->next_by_state; else first_daemon_by_state[daemon->state] = daemon->next_by_state; if ( daemon->next_by_state ) daemon->next_by_state->prev_by_state = daemon->prev_by_state; else last_daemon_by_state[daemon->state] = daemon->prev_by_state; count_daemon_by_state[daemon->state]--; daemon->prev_by_state = NULL; daemon->next_by_state = NULL; } static void daemon_change_state_list(struct daemon* daemon, enum daemon_state new_state) { daemon_remove_state_list(daemon); daemon->state = new_state; daemon_insert_state_list(daemon); } static struct daemon* daemon_create_sub(struct daemon_config* daemon_config, const char* netif) { struct daemon* daemon = add_daemon(); daemon->state = DAEMON_STATE_TERMINATED; daemon->readyfd = -1; daemon->outputfd = -1; daemon->log.fd = -1; daemon_insert_state_list(daemon); if ( netif ) { if ( asprintf(&daemon->name, "%s.%s", daemon_config->name, netif) < 0 ) fatal("asprintf"); } else { if ( !(daemon->name = strdup(daemon_config->name)) ) fatal("malloc: %m"); } daemon->dependencies = (struct dependency**) reallocarray(NULL, daemon_config->dependencies_used, sizeof(struct dependency*)); if ( !daemon->dependencies ) fatal("malloc: %m"); daemon->dependencies_used = daemon_config->dependencies_used; daemon->dependencies_length = daemon_config->dependencies_length; for ( size_t i = 0; i < daemon_config->dependencies_used; i++ ) { struct dependency_config* dependency_config = daemon_config->dependencies[i]; struct dependency* dependency = calloc(1, sizeof(struct dependency)); if ( !dependency ) fatal("malloc: %m"); if ( !(dependency->target_name = strdup(dependency_config->target)) ) fatal("malloc: %m"); dependency->flags = dependency_config->flags; daemon->dependencies[i] = dependency; // TODO: Either allow multiple dependencies to be exit code or enforce // that only a single one uses it. if ( dependency->flags & DEPENDENCY_FLAG_EXIT_CODE ) daemon->exit_code_from = dependency; } if ( daemon_config->cd && !(daemon->cd = strdup(daemon_config->cd)) ) fatal("malloc: %m"); if ( daemon_config->exec && !(daemon->exec = strdup(daemon_config->exec)) ) fatal("malloc: %m"); daemon->exit_code_meaning = daemon_config->exit_code_meaning; if ( netif && !(daemon->netif = strdup(netif)) ) fatal("malloc: %m"); if ( !log_initialize(&daemon->log, daemon->name, daemon_config) ) fatal("malloc: %m"); daemon->need_tty = daemon_config->need_tty; return daemon; } static struct daemon* daemon_create(struct daemon_config* daemon_config) { if ( daemon_config->per_if ) { struct daemon* daemon = add_daemon(); daemon->state = DAEMON_STATE_TERMINATED; daemon->readyfd = -1; daemon->outputfd = -1; daemon->log.fd = -1; daemon_insert_state_list(daemon); if ( !(daemon->name = strdup(daemon_config->name)) ) fatal("malloc: %m"); struct if_nameindex* ifs = if_nameindex(); if ( !ifs ) fatal("if_nameindex: %m"); for ( size_t i = 0; ifs[i].if_name; i++ ) { struct daemon* parameterized = daemon_create_sub(daemon_config, ifs[i].if_name); char* name_clone = strdup(parameterized->name); if ( !name_clone ) fatal("malloc: %m"); struct dependency* dependency = calloc(1, sizeof(struct dependency)); if ( !dependency ) fatal("malloc: %m"); dependency->target_name = name_clone; dependency->flags = DEPENDENCY_FLAG_REQUIRE | DEPENDENCY_FLAG_AWAIT; if ( !array_add((void***) &daemon->dependencies, &daemon->dependencies_used, &daemon->dependencies_length, dependency) ) fatal("malloc: %m"); } if_freenameindex(ifs); return daemon; } struct daemon* daemon = daemon_create_sub(daemon_config, NULL); if ( !daemon ) fatal("malloc: %m"); return daemon; } static struct daemon* daemon_load_config_and_create(const char* name) { struct daemon_config* daemon_config = daemon_config_load(name); if ( !daemon_config ) { warning("failed to load daemon configuration: %s: %m", name); return NULL; } struct daemon* daemon = daemon_create(daemon_config); if ( !daemon ) { warning("failed to create daemon: %s: %m", name); daemon_config_free(daemon_config); return NULL; } daemon_config_free(daemon_config); return daemon; } static void schedule_daemon(struct daemon* daemon) { assert(daemon->state == DAEMON_STATE_TERMINATED); daemon_change_state_list(daemon, DAEMON_STATE_SCHEDULED); } // TODO: Mutual recursion. static void daemon_on_finished(struct daemon* daemon); static void daemon_mark_finished(struct daemon* daemon); static void daemon_terminate(struct daemon* daemon) { assert(!daemon->was_terminated); daemon->was_terminated = true; if ( 0 < daemon->pid ) { log_status("stopping", "Stopping %s.\n", daemon->name); // TODO: Send SIGKILL after a timeout. kill(daemon->pid, SIGTERM); daemon_change_state_list(daemon, DAEMON_STATE_TERMINATING); } else { daemon_change_state_list(daemon, DAEMON_STATE_TERMINATING); // TODO: Recursion. daemon_on_finished(daemon); } } static void daemon_on_not_referenced(struct daemon* daemon) { assert(daemon->reference_count == 0); if ( daemon->state == DAEMON_STATE_TERMINATED || daemon->state == DAEMON_STATE_SCHEDULED || daemon->state == DAEMON_STATE_WAITING || daemon->state == DAEMON_STATE_SATISFIED ) { daemon_mark_finished(daemon); assert(daemon->state == DAEMON_STATE_FINISHED); } else if ( daemon->state == DAEMON_STATE_STARTING || daemon->state == DAEMON_STATE_READY || daemon->state == DAEMON_STATE_RUNNING ) { daemon_terminate(daemon); // Dependencies are dereferenced when the daemon terminates. } else { // Dependencies are dereferenced when the daemon terminates. } } static void daemon_dereference(struct daemon* daemon) { assert(0 < daemon->reference_count); daemon->reference_count--; // TODO: Recursion. if ( !daemon->reference_count ) daemon_on_not_referenced(daemon); } 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); } static void daemon_on_dependency_ready(struct dependency* dependency) { struct daemon* daemon = dependency->source; if ( !(dependency->flags & DEPENDENCY_FLAG_AWAIT) ) return; daemon->dependencies_ready++; if ( daemon->state == DAEMON_STATE_WAITING && daemon->dependencies_ready == daemon->dependencies_used ) daemon_change_state_list(daemon, DAEMON_STATE_SATISFIED); } static void daemon_mark_ready(struct daemon* daemon) { // TODO: Right place to do it? daemon_change_state_list(daemon, DAEMON_STATE_RUNNING); daemon->was_ready = true; for ( size_t i = 0; i < daemon->dependents_used; i++ ) daemon_on_dependency_ready(daemon->dependents[i]); } static void daemon_on_ready(struct daemon* daemon) { log_status("started", "Started %s.\n", daemon->name); daemon_mark_ready(daemon); } static void daemon_on_dependency_finished(struct dependency* dependency) { struct daemon* daemon = dependency->source; struct daemon* target = dependency->target; daemon->dependencies_finished++; // TODO: This is to protect against virtual daemons where daemon_terminate // has already been called from calling daemon_on_finished again. Best // way to do this? if ( daemon->state == DAEMON_STATE_FINISHED ) return; bool failed = (dependency->flags & DEPENDENCY_FLAG_REQUIRE) && daemon_is_failed(target); if ( failed ) daemon->dependencies_failed++; if ( daemon->exec ) { if ( failed ) { // TODO: If still waiting for dependencies to start, fail early. } } else if ( daemon->exit_code_from ) { // TODO: Either 1) enforce that only a single dependency can be exit // code, or 2) specifically allow this and support it. // TODO: Require exit_code_from to be a dependency. if ( dependency->flags & DEPENDENCY_FLAG_EXIT_CODE ) { daemon->exit_code = target->exit_code; // TODO: Recursion. daemon_on_finished(daemon); } // TODO: This ignores the exit code from other dependencies. Make it // possible to still fail early if a key dependency fails, even // if we want the exit code from a particular dependency. } else { // TODO: This gathers all the exit codes before the virtual daemon // finished, maybe have a kind of virtual daemon that finished on // first error? if ( failed ) daemon->exit_code = WCONSTRUCT(WNATURE_EXITED, 2, 0); if ( daemon->dependencies_finished == daemon->dependencies_used ) { // TODO: Recursion. daemon_on_finished(daemon); } } } static void daemon_mark_finished(struct daemon* daemon) { assert(daemon->state != DAEMON_STATE_FINISHED); if ( !daemon->was_ready ) { // TODO: Does this have unintended consequences? Can/should we do a // special transition bypassing the ready logic? daemon_mark_ready(daemon); } // TODO: Right place to do it? daemon_change_state_list(daemon, DAEMON_STATE_FINISHED); for ( size_t i = 0; i < daemon->dependents_used; i++ ) daemon_on_dependency_finished(daemon->dependents[i]); daemon_dereference_dependencies(daemon); } static void daemon_on_finished(struct daemon* daemon) { assert(daemon->state != DAEMON_STATE_FINISHED); if ( daemon_is_failed(daemon) ) log_status("failed", "%s exited unsuccessfully.\n", daemon->name); else if ( daemon->state == DAEMON_STATE_TERMINATING ) log_status("stopped", "Stopped %s.\n", daemon->name); else log_status("finished", "Finished %s.\n", daemon->name); daemon_mark_finished(daemon); } static void daemon_register_pollfd(struct daemon* daemon, int fd, size_t* out_index, short events) { assert(pfds_used < pfds_length); assert(pfds_used < pfds_daemon_length); size_t index = pfds_used++; struct pollfd* pfd = pfds + index; memset(pfd, 0, sizeof(*pfd)); pfd->fd = fd; pfd->events = events; pfds_daemon[index] = daemon; *out_index = index; } static void daemon_unregister_pollfd(struct daemon* daemon, size_t index) { assert(pfds_used <= pfds_length); assert(index < pfds_used); assert(pfds_daemon[index] == daemon); // This function is relied on to not mess with any pollfds prior to the // index, so it doesn't break a forward iteration on the pollfds. size_t last_index = pfds_used - 1; if ( index != last_index ) { memcpy(pfds + index, pfds + last_index, sizeof(*pfds)); pfds_daemon[index] = pfds_daemon[last_index]; if ( 0 <= pfds_daemon[index]->readyfd && pfds_daemon[index]->pfd_readyfd_index == last_index ) pfds_daemon[index]->pfd_readyfd_index = index; if ( 0 <= pfds_daemon[index]->outputfd && pfds_daemon[index]->pfd_outputfd_index == last_index ) pfds_daemon[index]->pfd_outputfd_index = index; } pfds_used--; memset(pfds + last_index, 0, sizeof(*pfds)); pfds_daemon[last_index] = NULL; } static void daemon_schedule(struct daemon* daemon) { for ( size_t i = 0; i < daemon->dependencies_used; i++ ) { // TODO: Require the dependency graph to be an directed acylic graph. struct dependency* dependency = daemon->dependencies[i]; dependency->source = daemon; dependency->target = daemon_find_by_name(dependency->target_name); if ( !dependency->target ) { // TODO: It's not possible to depend on a parameterized // daemon. dependency->target = daemon_load_config_and_create(dependency->target_name); if ( !dependency->target ) { // TODO: What should print this error message? log_status("failed", "%s could not load %s: %m\n", daemon->name, dependency->target_name); if ( dependency->flags & DEPENDENCY_FLAG_REQUIRE ) { // TODO: However, such a dependency hasn't been made below, // so it can be higher than dependents_used. // TODO: This causes the daemon to fail, but it doesn't set // the exit code. daemon->dependencies_failed++; } else { // TODO: A null target has been left in this dependency. We // should probably avoid that or take care everywhere // else. This is probably not a good and tested state. } continue; } } // TODO: This may already have been done if it has already run before. // TODO: Don't add as dependent if failed? if ( !array_add((void***) &dependency->target->dependents, &dependency->target->dependents_used, &dependency->target->dependents_length, dependency) ) fatal("malloc: %m"); dependency->target->reference_count++; if ( daemon->exit_code_from == dependency ) daemon->exit_code_meaning = daemon->exit_code_from->target->exit_code_meaning; if ( dependency->target->state == DAEMON_STATE_TERMINATED ) { schedule_daemon(dependency->target); if ( !(dependency->flags & DEPENDENCY_FLAG_AWAIT) ) daemon->dependencies_ready++; } else if ( dependency->target->state == DAEMON_STATE_SCHEDULED || dependency->target->state == DAEMON_STATE_SATISFIED || dependency->target->state == DAEMON_STATE_STARTING ) { // Daemon start is already in progress. if ( !(dependency->flags & DEPENDENCY_FLAG_AWAIT) ) daemon->dependencies_ready++; } else if ( dependency->target->state == DAEMON_STATE_RUNNING ) { daemon->dependencies_ready++; } else if ( dependency->target->state == DAEMON_STATE_TERMINATING ) { // TODO: Bring it back up first. How? } else if ( dependency->target->state == DAEMON_STATE_FINISHED ) { daemon->dependencies_ready++; daemon->dependencies_finished++; if ( (dependency->flags & DEPENDENCY_FLAG_REQUIRE) && daemon_is_failed(daemon) ) { daemon->dependencies_failed++; // TODO: Don't start more dependencies. //break; } } else { // TODO: This is unreachable. } } if ( daemon->dependencies_ready < daemon->dependencies_used ) daemon_change_state_list(daemon, DAEMON_STATE_WAITING); else daemon_change_state_list(daemon, DAEMON_STATE_SATISFIED); } static void daemon_start(struct daemon* daemon) { assert(daemon->state == DAEMON_STATE_SATISFIED); if ( !daemon->exec ) { daemon_on_ready(daemon); if ( daemon->exit_code_from ) { struct daemon* target = daemon->exit_code_from->target; if ( target->state == DAEMON_STATE_FINISHED ) { daemon->exit_code = target->exit_code; daemon_on_finished(daemon); } } else if ( daemon->dependencies_finished == daemon->dependencies_used ) { daemon_on_finished(daemon); } return; } // TODO: Support for a virtual daemon using the exit-code flag to still fail // if certain key dependencies fail. if ( 0 < daemon->dependencies_failed ) { // TODO: daemon_on_finished also makes a message. log_status("failed", "Failed to start %s.\n", daemon->name); // TODO: Should this vary with exit_code_meaning? daemon->exit_code = WCONSTRUCT(WNATURE_EXITED, 2, 0); daemon_on_finished(daemon); return; } log_status("starting", "Starting %s...\n", daemon->name); uid_t uid = getuid(); pid_t ppid = getpid(); struct passwd* pwd = getpwuid(uid); if ( !pwd ) fatal("looking up user by uid %" PRIuUID ": %m", uid); const char* home = pwd->pw_dir[0] ? pwd->pw_dir : "/"; const char* shell = pwd->pw_shell[0] ? pwd->pw_shell : "sh"; const char* cd = daemon->cd ? daemon->cd : "/"; // TODO: This is a hack. if ( !strcmp(cd, "\"$HOME\"") ) cd = home; const char* exec = daemon->exec; // TODO: This is a hack. if ( !strcmp(exec, "\"$SHELL\"") ) exec = shell; char** argv = generate_argv(exec, daemon->netif); if ( !argv ) fatal("malloc: %m"); int outputfds[2]; int readyfds[2]; if ( !daemon->need_tty ) { size_t required_fds = 2; if ( pfds_length - pfds_used < required_fds ) { size_t old_length = pfds_length ? pfds_length : required_fds; struct pollfd* new_pfds = reallocarray(pfds, old_length, 2 * sizeof(struct pollfd)); if ( !new_pfds ) fatal("malloc"); pfds = new_pfds; pfds_length = old_length * 2; } if ( pfds_daemon_length - pfds_used < required_fds ) { size_t old_length = pfds_daemon_length ? pfds_daemon_length : required_fds; struct daemon** new_pfds_daemon = reallocarray(pfds_daemon, old_length, 2 * sizeof(struct daemon*)); if ( !new_pfds_daemon ) fatal("malloc"); pfds_daemon = new_pfds_daemon; pfds_daemon_length = old_length * 2; } if ( !log_begin(&daemon->log) ) { // TODO: Should we not stop the daemon? } if ( pipe(outputfds) < 0 ) fatal("pipe"); daemon->outputfd = outputfds[0]; fcntl(daemon->outputfd, F_SETFL, O_NONBLOCK); // Setup the pollfd for the outputfd. daemon_register_pollfd(daemon, daemon->outputfd, &daemon->pfd_outputfd_index, POLLIN); // Create the readyfd. if ( pipe(readyfds) < 0 ) fatal("pipe"); daemon->readyfd = readyfds[0]; fcntl(daemon->readyfd, F_SETFL, O_NONBLOCK); // Setup the pollfd for the readyfd. daemon_register_pollfd(daemon, daemon->readyfd, &daemon->pfd_readyfd_index, POLLIN); } // TODO: This is not concurrency safe, build a environment array just for // this daemon. char ppid_str[sizeof(pid_t) * 3]; snprintf(ppid_str, sizeof(ppid_str), "%" PRIiPID, ppid); if ( (!daemon->need_tty && setenv("READYFD", "3", 1)) < 0 || setenv("INIT_PID", ppid_str, 1) < 0 || setenv("LOGNAME", pwd->pw_name, 1) < 0 || setenv("USER", pwd->pw_name, 1) < 0 || setenv("HOME", home, 1) < 0 || setenv("SHELL", shell, 1) < 0 ) fatal("setenv"); // TODO: If argv is empty daemon->pid = fork(); if ( daemon->pid < 0 ) fatal("fork: %m"); if ( daemon->need_tty ) { // TODO: Any chance of SIGTTIN? if ( tcgetattr(0, &daemon->oldtio) ) fatal("tcgetattr: %m"); } if ( daemon->pid == 0 ) { uninstall_signal_handler(); if ( chdir(cd) ) { // TODO: Right way to send a fatal error back? fatal("chdir: %s: %m", cd); } if ( daemon->need_tty ) { pid_t pid = getpid(); // TODO: Support for setsid(). if ( setpgid(0, 0) < 0 ) fatal("setpgid: %m"); sigset_t oldset, sigttou; sigemptyset(&sigttou); sigaddset(&sigttou, SIGTTOU); sigprocmask(SIG_BLOCK, &sigttou, &oldset); if ( tcsetpgrp(0, pid) < 0 ) fatal("tcsetpgrp: %m"); daemon->oldtio.c_cflag |= CREAD; if ( tcsetattr(0, TCSANOW, &daemon->oldtio) ) fatal("tcgetattr: %m"); sigprocmask(SIG_SETMASK, &oldset, NULL); closefrom(3); } else { close(0); close(1); close(2); open("/dev/null", O_RDONLY); // TODO: Fix daemon's logging to stdout instead of stderr. //open("/dev/null", O_WRONLY); dup2(outputfds[1], 1); dup2(outputfds[1], 2); dup2(readyfds[1], 3); closefrom(4); } execvp(argv[0], argv); // TODO: Use a pipe to send the errno back. warning("%s: %m", argv[0]); _exit(127); } if ( !daemon->need_tty ) { close(outputfds[1]); close(readyfds[1]); } // TODO: Not thread safe. // TODO: Also unset other things. if ( !daemon->need_tty ) unsetenv("READYFD"); unsetenv("INIT_PID"); unsetenv("LOGNAME"); unsetenv("USER"); unsetenv("HOME"); unsetenv("SHELL"); for ( size_t i = 0; argv[i]; i++ ) free(argv[i]); free(argv); if ( daemon->need_tty ) daemon_on_ready(daemon); else daemon_change_state_list(daemon, DAEMON_STATE_STARTING); } static bool daemon_process_ready(struct daemon* daemon) { char c; ssize_t amount = read(daemon->readyfd, &c, sizeof(c)); if ( amount < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) ) return true; if ( amount < 0 ) return false; else if ( amount == 0 ) return false; if ( c == '\n' ) { daemon_on_ready(daemon); return false; } return true; } static bool daemon_process_output(struct daemon* daemon) { char data[4096]; ssize_t amount = read(daemon->outputfd, data, sizeof(data)); if ( amount < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) ) return true; if ( amount < 0 ) return false; else if ( amount == 0 ) return false; log_formatted(&daemon->log, data, amount); return true; } static void daemon_on_exit(struct daemon* daemon, int exit_code) { assert(daemon->state != DAEMON_STATE_FINISHED); daemon->exit_code = exit_code; if ( 0 <= daemon->readyfd ) { daemon_unregister_pollfd(daemon, daemon->pfd_readyfd_index); close(daemon->readyfd); daemon->readyfd = -1; } if ( 0 <= daemon->outputfd ) { daemon_process_output(daemon); daemon_unregister_pollfd(daemon, daemon->pfd_outputfd_index); close(daemon->outputfd); daemon->outputfd = -1; } if ( 0 <= daemon->log.fd ) log_close(&daemon->log); if ( daemon->need_tty ) { // TODO: There is a race condition between getting the exit code from // waitpid and us reclaiming it here where some other process that // happened to get the right pid may own the tty. sigset_t oldset, sigttou; sigemptyset(&sigttou); sigaddset(&sigttou, SIGTTOU); sigprocmask(SIG_BLOCK, &sigttou, &oldset); if ( tcsetattr(0, TCSAFLUSH, &daemon->oldtio) ) fatal("tcsetattr: %m"); if ( tcsetpgrp(0, getpgid(0)) < 0 ) fatal("tcsetpgrp: %m"); sigprocmask(SIG_SETMASK, &oldset, NULL); } daemon_on_finished(daemon); } // TODO. This can just be directly inlined to main as the mainloop. static void schedule(void) { int default_daemon_exit_code = -1; while ( first_daemon_by_state[DAEMON_STATE_SCHEDULED] || //first_daemon_by_state[DAEMON_STATE_WAITING] || // TODO? first_daemon_by_state[DAEMON_STATE_SATISFIED] || first_daemon_by_state[DAEMON_STATE_STARTING] || first_daemon_by_state[DAEMON_STATE_READY] || first_daemon_by_state[DAEMON_STATE_RUNNING] || first_daemon_by_state[DAEMON_STATE_TERMINATING] ) { if ( caught_exit_signal != -1 && default_daemon_exit_code == -1) { struct daemon* default_daemon = daemon_find_by_name("default"); if ( caught_exit_signal == 0 ) log_status("stopped", "Powering off...\n"); else if ( caught_exit_signal == 1 ) log_status("stopped", "Rebooting...\n"); else if ( caught_exit_signal == 2 ) log_status("stopped", "Halting...\n"); else log_status("stopped", "Exiting %i...\n", caught_exit_signal); daemon_mark_finished(default_daemon); default_daemon_exit_code = WCONSTRUCT(WNATURE_EXITED, caught_exit_signal, 0); } caught_exit_signal = -1; while ( first_daemon_by_state[DAEMON_STATE_SCHEDULED] ) { struct daemon* daemon = first_daemon_by_state[DAEMON_STATE_SCHEDULED]; daemon_schedule(daemon); } while ( first_daemon_by_state[DAEMON_STATE_SATISFIED] ) { struct daemon* daemon = first_daemon_by_state[DAEMON_STATE_SATISFIED]; // TODO: Error handling. daemon_start(daemon); } struct timespec timeout = timespec_make(-1, 0); sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGCHLD); sigset_t oldset; sigprocmask(SIG_BLOCK, &mask, &oldset); sigset_t unhandled_signals; signotset(&unhandled_signals, &handled_signals); sigset_t pollset; sigandset(&pollset, &oldset, &unhandled_signals); int exit_code; pid_t pid; while ( 0 < (pid = waitpid(-1, &exit_code, WNOHANG)) ) { struct daemon* daemon = daemon_find_by_pid(pid); if ( daemon ) daemon_on_exit(daemon, exit_code); timeout = timespec_make(0, 0); } // TODO: Use kqueue or such. // Set a dummy SIGCHLD handler to ensure we get EINTR during ppoll(2). struct sigaction sa = { 0 }; sa.sa_handler = signal_handler; sa.sa_flags = 0; struct sigaction old_sa; sigaction(SIGCHLD, &sa, &old_sa); // Await either an event, a timeout, or SIGCHLD. int nevents = ppoll(pfds, pfds_used, &timeout, &pollset); sigaction(SIGCHLD, &old_sa, NULL); sigprocmask(SIG_SETMASK, &oldset, NULL); if ( nevents < 0 && errno != EINTR ) fatal("ppoll: %m"); for ( size_t i = 0; i < pfds_used; i++ ) { if ( nevents <= 0 ) break; struct pollfd* pfd = pfds + i; if ( !pfd->revents ) continue; nevents--; struct daemon* daemon = pfds_daemon[i]; if ( 0 <= daemon->readyfd && pfd->fd == daemon->readyfd ) { // TODO: POLLHUP? POLLERR? POLLNVAL? if ( pfd->revents & (POLLIN | POLLHUP) ) { if ( !daemon_process_ready(daemon) ) { daemon_unregister_pollfd(daemon, daemon->pfd_readyfd_index); close(daemon->readyfd); daemon->readyfd = -1; i--; // Process this index again (something new there). } } } else if ( 0 <= daemon->outputfd && pfd->fd == daemon->outputfd ) { // TODO: POLLHUP? POLLERR? POLLNVAL? if ( pfd->revents & (POLLIN | POLLHUP) ) { if ( !daemon_process_output(daemon) ) { daemon_unregister_pollfd(daemon, daemon->pfd_outputfd_index); close(daemon->outputfd); daemon->outputfd = -1; i--; // Process this index again (something new there). } } } else { assert(false); } } } // Collect child processes reparented to us that we don't know about and // attempt to politely shut them down with SIGTERM and SIGKILL after a // timeout. sigset_t saved_mask, sigchld_mask; sigemptyset(&sigchld_mask); sigaddset(&sigchld_mask, SIGCHLD); sigprocmask(SIG_BLOCK, &sigchld_mask, &saved_mask); struct sigaction sa = { .sa_handler = signal_handler }; struct sigaction old_sa; sigaction(SIGCHLD, &sa, &old_sa); struct timespec timeout = timespec_make(30, 0); struct timespec begun; clock_gettime(CLOCK_MONOTONIC, &begun); bool sent_sigterm = false; while ( true ) { int exit_code; for ( pid_t pid = 1; 0 < pid; pid = waitpid(-1, &exit_code, WNOHANG) ); struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); struct timespec elapsed = timespec_sub(now, begun); struct psctl_stat psst; if ( psctl(getpid(), PSCTL_STAT, &psst) < 0 ) fatal("psctl: %m"); bool any_unknown = false; for ( pid_t pid = psst.ppid_first; pid != -1; pid = psst.ppid_next ) { if ( psctl(pid, PSCTL_STAT, &psst) < 0 ) { warn("psctl: %ji", (intmax_t) pid); break; } bool known = false; for ( size_t i = 0; !known && i < mountpoints_used; i++ ) if ( mountpoints[i].pid == pid ) known = true; if ( !known ) { any_unknown = true; if ( !sent_sigterm ) kill(pid, SIGTERM); // TODO: Hostile processes can try to escape by spawning more // processes, a kernel feature is needed to recursively // send a signal to all descendants atomically, although // want to avoid known safe processes (mountpoints) and // still catch processes reparented to us. Otherwise // retrying until we succeed is the best we can do. else if ( timespec_le(timeout, elapsed) ) kill(pid, SIGKILL); } } sent_sigterm = true; if ( !any_unknown ) break; // Wait for the timeout to happen, or for another process to exit by // the poll failing with EINTR because a pending SIGCHLD was delivered // when the saved signal mask is restored. struct timespec left = timespec_sub(timeout, elapsed); if ( left.tv_sec < 0 || (left.tv_sec == 0 && left.tv_nsec == 0) ) left = timespec_make(1, 0); struct pollfd pfd = { .fd = -1 }; ppoll(&pfd, 1, &left, &saved_mask); } sigaction(SIGCHLD, &old_sa, NULL); sigprocmask(SIG_SETMASK, &saved_mask, NULL); if ( default_daemon_exit_code != -1 ) daemon_find_by_name("default")->exit_code = default_daemon_exit_code; } static void write_random_seed(void) { const char* will_not = "next boot will not have fresh randomness"; const char* path = "/boot/random.seed"; int fd = open(path, O_WRONLY | O_CREAT | O_NOFOLLOW, 0600); if ( fd < 0 ) { if ( errno != ENOENT && errno != EROFS ) warning("%s: %s: %m", will_not, path); return; } if ( fchown(fd, 0, 0) < 0 ) { warning("%s: chown: %s: %m", will_not, path); close(fd); return; } if ( fchmod(fd, 0600) < 0 ) { warning("%s: chown: %s: %m", will_not, path); close(fd); return; } // Write out randomness, but mix in some fresh kernel randomness in case the // randomness used to seed arc4random didn't have enough entropy, there may // be more now. unsigned char buf[256]; arc4random_buf(buf, sizeof(buf)); unsigned char newbuf[256]; getentropy(newbuf, sizeof(newbuf)); for ( size_t i = 0; i < 256; i++ ) buf[i] ^= newbuf[i]; size_t done = writeall(fd, buf, sizeof(buf)); explicit_bzero(buf, sizeof(buf)); if ( done < sizeof(buf) ) { warning("%s: write: %s: %m", will_not, path); close(fd); return; } if ( ftruncate(fd, sizeof(buf)) < 0 ) { warning("%s: truncate: %s: %m", will_not, path); close(fd); return; } close(fd); } static void prepare_filesystem(const char* path, struct blockdevice* bdev) { enum filesystem_error fserr = blockdevice_inspect_filesystem(&bdev->fs, bdev); if ( fserr == FILESYSTEM_ERROR_ABSENT || fserr == FILESYSTEM_ERROR_UNRECOGNIZED ) return; if ( fserr != FILESYSTEM_ERROR_NONE ) return warning("probing: %s: %s", path, filesystem_error_string(fserr)); } static bool prepare_block_device(void* ctx, const char* path) { (void) ctx; struct harddisk* hd = harddisk_openat(AT_FDCWD, path, O_RDONLY); if ( !hd ) { int true_errno = errno; struct stat st; if ( lstat(path, &st) == 0 && !S_ISBLK(st.st_mode) ) return true; errno = true_errno; fatal("%s: %m", path); } if ( !harddisk_inspect_blockdevice(hd) ) { if ( errno == ENOTBLK || errno == ENOMEDIUM ) return true; if ( errno == EINVAL ) return warning("%s: %m", path), true; fatal("%s: %m", path); } if ( hds_used == hds_length ) { size_t new_half_length = hds_length ? hds_length : 8; struct harddisk** new_hds = (struct harddisk**) reallocarray(hds, new_half_length, sizeof(struct harddisk*) * 2); if ( !new_hds ) fatal("realloc: %m"); hds = new_hds; hds_length = 2 * new_half_length; } hds[hds_used++] = hd; struct blockdevice* bdev = &hd->bdev; enum partition_error parterr = blockdevice_get_partition_table(&bdev->pt, bdev); if ( parterr == PARTITION_ERROR_ABSENT || parterr == PARTITION_ERROR_UNRECOGNIZED ) { prepare_filesystem(path, bdev); return true; } else if ( parterr == PARTITION_ERROR_ERRNO ) { if ( errno == EIO || errno == EINVAL ) warning("%s: %s", path, partition_error_string(parterr)); else fatal("%s: %s", path, partition_error_string(parterr)); return true; } else if ( parterr != PARTITION_ERROR_NONE ) { warning("%s: %s", path, partition_error_string(parterr)); return true; } for ( size_t i = 0; i < bdev->pt->partitions_count; i++ ) { struct partition* p = bdev->pt->partitions[i]; assert(p->path); struct stat st; if ( stat(p->path, &st) == 0 ) { // TODO: Check the existing partition has the right offset and // length, but definitely do not recreate it if it already // exists properly. } else if ( errno == ENOENT ) { int mountfd = open(p->path, O_RDONLY | O_CREAT | O_EXCL); if ( mountfd < 0 ) fatal("%s:˙%m", p->path); int partfd = mkpartition(hd->fd, p->start, p->length); if ( partfd < 0 ) fatal("mkpartition: %s:˙%m", p->path); if ( fsm_fsbind(partfd, mountfd, 0) < 0 ) fatal("fsbind: %s:˙%m", p->path); close(partfd); close(mountfd); } else { fatal("stat: %s: %m", p->path); } prepare_filesystem(p->path, &p->bdev); } return true; } static void prepare_block_devices(void) { static bool done = false; if ( done ) return; done = true; if ( !devices_iterate_path(prepare_block_device, NULL) ) fatal("iterating devices: %m"); } static void search_by_uuid(const char* uuid_string, void (*cb)(void*, struct device_match*), void* ctx) { unsigned char uuid[16]; uuid_from_string(uuid, uuid_string); for ( size_t i = 0; i < hds_used; i++ ) { struct blockdevice* bdev = &hds[i]->bdev; if ( bdev->fs ) { struct filesystem* fs = bdev->fs; if ( !(fs->flags & FILESYSTEM_FLAG_UUID) ) continue; if ( memcmp(uuid, fs->uuid, 16) != 0 ) continue; struct device_match match; match.path = hds[i]->path; match.bdev = bdev; cb(ctx, &match); } else if ( bdev->pt ) { for ( size_t j = 0; j < bdev->pt->partitions_count; j++ ) { struct partition* p = bdev->pt->partitions[j]; if ( !p->bdev.fs ) continue; struct filesystem* fs = p->bdev.fs; if ( !(fs->flags & FILESYSTEM_FLAG_UUID) ) continue; if ( memcmp(uuid, fs->uuid, 16) != 0 ) continue; struct device_match match; match.path = p->path; match.bdev = &p->bdev; cb(ctx, &match); } } } } static void ensure_single_device_match(void* ctx, struct device_match* match) { struct device_match* result = (struct device_match*) ctx; if ( result->path ) { if ( result->bdev ) note("duplicate match: %s", result->path); result->bdev = NULL; note("duplicate match: %s", match->path); return; } *result = *match; } static int sort_mountpoint(const void* a_ptr, const void* b_ptr) { const struct mountpoint* a = (const struct mountpoint*) a_ptr; const struct mountpoint* b = (const struct mountpoint*) b_ptr; return strcmp(a->entry.fs_file, b->entry.fs_file); } static void load_fstab(void) { FILE* fp = fopen("/etc/fstab", "r"); if ( !fp ) { if ( errno == ENOENT ) return; fatal("/etc/fstab: %m"); } char* line = NULL; size_t line_size; ssize_t line_length; while ( 0 < (line_length = getline(&line, &line_size, fp)) ) { if ( line[line_length - 1] == '\n' ) line[--line_length] = '\0'; struct fstab fstabent; if ( !scanfsent(line, &fstabent) ) continue; if ( mountpoints_used == mountpoints_length ) { size_t new_length = 2 * mountpoints_length; if ( !new_length ) new_length = 16; struct mountpoint* new_mountpoints = (struct mountpoint*) reallocarray(mountpoints, new_length, sizeof(struct mountpoint)); if ( !new_mountpoints ) fatal("malloc: %m"); mountpoints = new_mountpoints; mountpoints_length = new_length; } struct mountpoint* mountpoint = &mountpoints[mountpoints_used++]; memcpy(&mountpoint->entry, &fstabent, sizeof(fstabent)); mountpoint->entry_line = line; mountpoint->pid = -1; if ( !(mountpoint->absolute = strdup(mountpoint->entry.fs_file)) ) fatal("malloc: %m"); line = NULL; line_size = 0; } if ( ferror(fp) ) fatal("/etc/fstab: %m"); free(line); fclose(fp); qsort(mountpoints, mountpoints_used, sizeof(struct mountpoint), sort_mountpoint); } static void set_hostname(void) { FILE* fp = fopen("/etc/hostname", "r"); if ( !fp && errno == ENOENT ) return; if ( !fp ) return warning("unable to set hostname: /etc/hostname: %m"); char* hostname = read_single_line(fp); fclose(fp); if ( !hostname ) return warning("unable to set hostname: /etc/hostname: %m"); int ret = sethostname(hostname, strlen(hostname) + 1); if ( ret < 0 ) warning("unable to set hostname: `%s': %m", hostname); free(hostname); } static void set_kblayout(void) { int tty_fd = open("/dev/tty", O_RDWR); if ( !tty_fd ) return warning("unable to set keyboard layout: /dev/tty: %m"); bool unsupported = tcgetblob(tty_fd, "kblayout", NULL, 0) < 0 && (errno == ENOTTY || errno == ENOENT); close(tty_fd); if ( unsupported ) return; FILE* fp = fopen("/etc/kblayout", "r"); if ( !fp && errno == ENOENT ) return; if ( !fp ) return warning("unable to set keyboard layout: /etc/kblayout: %m"); char* kblayout = read_single_line(fp); fclose(fp); if ( !kblayout ) return warning("unable to set keyboard layout: /etc/kblayout: %m"); pid_t child_pid = fork(); if ( child_pid < 0 ) { free(kblayout); warning("unable to set keyboard layout: fork: %m"); return; } if ( !child_pid ) { uninstall_signal_handler(); execlp("chkblayout", "chkblayout", "--", kblayout, (const char*) NULL); warning("setting keyboard layout: chkblayout: %m"); _exit(127); } int status; waitpid(child_pid, &status, 0); free(kblayout); } static void set_videomode(void) { int tty_fd = open("/dev/tty", O_RDWR); if ( !tty_fd ) return warning("unable to set video mode: /dev/tty: %m"); struct tiocgdisplay display; struct tiocgdisplays gdisplays; memset(&gdisplays, 0, sizeof(gdisplays)); gdisplays.count = 1; gdisplays.displays = &display; bool unsupported = ioctl(tty_fd, TIOCGDISPLAYS, &gdisplays) < 0 || gdisplays.count == 0; close(tty_fd); if ( unsupported ) return; FILE* fp = fopen("/etc/videomode", "r"); if ( !fp && errno == ENOENT ) return; if ( !fp ) return warning("unable to set video mode: /etc/videomode: %m"); char* videomode = read_single_line(fp); fclose(fp); if ( !videomode ) return warning("unable to set video mode: /etc/videomode: %m"); unsigned int xres = 0; unsigned int yres = 0; unsigned int bpp = 0; if ( sscanf(videomode, "%ux%ux%u", &xres, &yres, &bpp) != 3 ) { warning("/etc/videomode: Invalid video mode `%s'", videomode); free(videomode); return; } free(videomode); struct dispmsg_get_crtc_mode get_mode; memset(&get_mode, 0, sizeof(get_mode)); get_mode.msgid = DISPMSG_GET_CRTC_MODE; get_mode.device = display.device; get_mode.connector = display.connector; // Don't set the resolution if it's already correct. if ( dispmsg_issue(&get_mode, sizeof(get_mode)) == 0 ) { if ( get_mode.mode.control & DISPMSG_CONTROL_VALID && !(get_mode.mode.control & DISPMSG_CONTROL_FALLBACK) && get_mode.mode.fb_format == bpp && get_mode.mode.view_xres == xres && get_mode.mode.view_yres == yres ) return; } struct dispmsg_set_crtc_mode set_mode; memset(&set_mode, 0, sizeof(set_mode)); set_mode.msgid = DISPMSG_SET_CRTC_MODE; set_mode.device = 0; set_mode.connector = 0; set_mode.mode.driver_index = 0; set_mode.mode.magic = 0; set_mode.mode.control = DISPMSG_CONTROL_VALID; set_mode.mode.fb_format = bpp; set_mode.mode.view_xres = xres; set_mode.mode.view_yres = yres; set_mode.mode.fb_location = 0; set_mode.mode.pitch = xres * (bpp / 8); set_mode.mode.surf_off_x = 0; set_mode.mode.surf_off_y = 0; set_mode.mode.start_x = 0; set_mode.mode.start_y = 0; set_mode.mode.end_x = 0; set_mode.mode.end_y = 0; set_mode.mode.desktop_height = yres; if ( dispmsg_issue(&set_mode, sizeof(set_mode)) < 0 ) warning("/etc/videomode: Failed to set video mode `%ux%ux%u': %m", xres, yres, bpp); } static int no_dot_nor_dot_dot(const struct dirent* entry, void* ctx) { (void) ctx; return !strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..") ? 0 : 1; } struct clean_tmp { struct clean_tmp* parent; DIR* dir; char* path; struct dirent** entries; int num_entries; int current_entry; }; static void clean_tmp(const char* tmp_path) { struct clean_tmp* state = calloc(1, sizeof(struct clean_tmp)); if ( !state ) { warning("malloc: %m"); return; } state->path = strdup(tmp_path); if ( !state->path ) { warning("malloc: %m"); free(state); return; } state->dir = opendir(state->path); if ( !state->dir ) { warning("%s: %m", state->path); free(state->path); free(state); return; } while ( state ) { if ( !state->entries ) { state->num_entries = dscandir_r(state->dir, &state->entries, no_dot_nor_dot_dot, NULL, alphasort_r, NULL); if ( state->num_entries < 0 ) warning("%s: %m", state->path); } if ( state->num_entries <= state->current_entry ) { closedir(state->dir); for ( int i = 0; i < state->num_entries; i++ ) free(state->entries[i]); free(state->entries); free(state->path); struct clean_tmp* new_state = state->parent; free(state); state = new_state; if ( state ) { struct dirent* entry = state->entries[state->current_entry]; const char* name = entry->d_name; int fd = dirfd(state->dir); if ( unlinkat(fd, name, AT_REMOVEDIR) < 0 ) warning("%s/%s: %m", state->path, name); state->current_entry++; } continue; } struct dirent* entry = state->entries[state->current_entry]; const char* name = entry->d_name; int fd = dirfd(state->dir); if ( unlinkat(fd, name, AT_REMOVEFILE | AT_REMOVEDIR) < 0 ) { if ( errno == ENOTEMPTY ) { struct clean_tmp* new_state = calloc(1, sizeof(struct clean_tmp)); if ( !new_state ) { warning("%s/%s: malloc: %m", state->path, name); state->current_entry++; continue; } new_state->path = join_paths(state->path, name); if ( !new_state->path ) { warning("%s/%s: malloc: %m", state->path, name); free(new_state); state->current_entry++; continue; } int flags = O_DIRECTORY | O_RDONLY | O_NOFOLLOW; int newfd = openat(fd, new_state->path, flags); if ( newfd < 0 ) { warning("%s: %m", new_state->path); free(new_state->path); free(new_state); state->current_entry++; continue; } new_state->dir = fdopendir(newfd); if ( !new_state->dir ) { warning("%s: %m", new_state->path); close(newfd); free(new_state->path); free(new_state); state->current_entry++; continue; } new_state->parent = state; state = new_state; continue; } else warning("%s/%s: %m", state->path, name); } state->current_entry++; } } static bool is_chain_init_mountpoint(const struct mountpoint* mountpoint) { return !strcmp(mountpoint->entry.fs_file, "/"); } static struct filesystem* mountpoint_lookup(const struct mountpoint* mountpoint) { const char* path = mountpoint->entry.fs_file; const char* spec = mountpoint->entry.fs_spec; if ( strncmp(spec, "UUID=", strlen("UUID=")) == 0 ) { const char* uuid = spec + strlen("UUID="); if ( !uuid_validate(uuid) ) { warning("%s: `%s' is not a valid uuid", path, uuid); return NULL; } struct device_match match; memset(&match, 0, sizeof(match)); search_by_uuid(uuid, ensure_single_device_match, &match); if ( !match.path ) { warning("%s: No devices matching uuid %s were found", path, uuid); return NULL; } if ( !match.bdev ) { warning("%s: Don't know which particular device to boot with uuid " "%s", path, uuid); return NULL; } assert(match.bdev->fs); return match.bdev->fs; } // TODO: Lookup by device name. // TODO: Use this function in the chain init case too. warning("%s: Don't know how to resolve `%s' to a filesystem", path, spec); return NULL; } static bool mountpoint_mount(struct mountpoint* mountpoint) { struct filesystem* fs = mountpoint_lookup(mountpoint); if ( !fs ) return false; // TODO: It would be ideal to get an exclusive lock so that no other // processes have currently mounted that filesystem. struct blockdevice* bdev = fs->bdev; const char* bdev_path = bdev->p ? bdev->p->path : bdev->hd->path; assert(bdev_path); do if ( fs->flags & (FILESYSTEM_FLAG_FSCK_SHOULD | FILESYSTEM_FLAG_FSCK_MUST) ) { assert(fs->fsck); if ( fs->flags & FILESYSTEM_FLAG_FSCK_MUST ) note("%s: Repairing filesystem due to inconsistency...", bdev_path); else note("%s: Checking filesystem consistency...", bdev_path); pid_t child_pid = fork(); if ( child_pid < 0 ) { if ( fs->flags & FILESYSTEM_FLAG_FSCK_MUST ) { warning("%s: Mandatory repair failed: fork: %m", bdev_path); return false; } warning("%s: Skipping filesystem check: fork: %m:", bdev_path); break; } if ( child_pid == 0 ) { uninstall_signal_handler(); execlp(fs->fsck, fs->fsck, "-fp", "--", bdev_path, (const char*) NULL); note("%s: Failed to load filesystem checker: %s: %m", bdev_path, fs->fsck); _exit(127); } int code; if ( waitpid(child_pid, &code, 0) < 0 ) fatal("waitpid: %m"); if ( WIFEXITED(code) && (WEXITSTATUS(code) == 0 || WEXITSTATUS(code) == 1) ) { // Successfully checked filesystem. } else if ( fs->flags & FILESYSTEM_FLAG_FSCK_MUST ) { if ( WIFSIGNALED(code) ) warning("%s: Mandatory repair failed: %s: %s", bdev_path, fs->fsck, strsignal(WTERMSIG(code))); else if ( !WIFEXITED(code) ) warning("%s: Mandatory repair failed: %s: %s", bdev_path, fs->fsck, "Unexpected unusual termination"); else if ( WEXITSTATUS(code) == 127 ) warning("%s: Mandatory repair failed: %s: %s", bdev_path, fs->fsck, "Filesystem checker is absent"); else if ( WEXITSTATUS(code) & 2 ) warning("%s: Mandatory repair: %s: %s", bdev_path, fs->fsck, "System reboot is necessary"); else warning("%s: Mandatory repair failed: %s: %s", bdev_path, fs->fsck, "Filesystem checker was unsuccessful"); return false; } else { bool ignore = false; if ( WIFSIGNALED(code) ) warning("%s: Filesystem check failed: %s: %s", bdev_path, fs->fsck, strsignal(WTERMSIG(code))); else if ( !WIFEXITED(code) ) warning("%s: Filesystem check failed: %s: %s", bdev_path, fs->fsck, "Unexpected unusual termination"); else if ( WEXITSTATUS(code) == 127 ) { warning("%s: Skipping filesystem check: %s: %s", bdev_path, fs->fsck, "Filesystem checker is absent"); ignore = true; } else if ( WEXITSTATUS(code) & 2 ) warning("%s: Filesystem check: %s: %s", bdev_path, fs->fsck, "System reboot is necessary"); else warning("%s: Filesystem check failed: %s: %s", bdev_path, fs->fsck, "Filesystem checker was unsuccessful"); if ( !ignore ) return false; } } while ( 0 ); if ( !fs->driver ) { warning("%s: Don't know how to mount a %s filesystem", bdev_path, fs->fstype_name); return false; } const char* pretend_where = mountpoint->entry.fs_file; const char* where = mountpoint->absolute; struct stat st; if ( stat(where, &st) < 0 ) { warning("stat: %s: %m", where); return false; } if ( (mountpoint->pid = fork()) < 0 ) { warning("%s: Unable to mount: fork: %m", bdev_path); return false; } // TODO: This design is broken. The filesystem should tell us when it is // ready instead of having to poll like this. if ( mountpoint->pid == 0 ) { uninstall_signal_handler(); execlp(fs->driver, fs->driver, "--foreground", bdev_path, where, "--pretend-mount-path", pretend_where, (const char*) NULL); warning("%s: Failed to load filesystem driver: %s: %m", bdev_path, fs->driver); _exit(127); } // TODO: Use daemon readiness notification method (READYFD). while ( true ) { struct stat newst; if ( stat(where, &newst) < 0 ) { warning("stat: %s: %m", where); if ( unmount(where, 0) < 0 && errno != ENOMOUNT ) warning("unmount: %s: %m", where); else if ( errno == ENOMOUNT ) kill(mountpoint->pid, SIGQUIT); int code; waitpid(mountpoint->pid, &code, 0); mountpoint->pid = -1; return false; } if ( newst.st_dev != st.st_dev || newst.st_ino != st.st_ino ) break; int code; pid_t child = waitpid(mountpoint->pid, &code, WNOHANG); if ( child < 0 ) fatal("waitpid: %m"); if ( child != 0 ) { mountpoint->pid = -1; if ( WIFSIGNALED(code) ) warning("%s: Mount failed: %s: %s", bdev_path, fs->driver, strsignal(WTERMSIG(code))); else if ( !WIFEXITED(code) ) warning("%s: Mount failed: %s: %s", bdev_path, fs->driver, "Unexpected unusual termination"); else if ( WEXITSTATUS(code) == 127 ) warning("%s: Mount failed: %s: %s", bdev_path, fs->driver, "Filesystem driver is absent"); else if ( WEXITSTATUS(code) == 0 ) warning("%s: Mount failed: %s: Unexpected successful exit", bdev_path, fs->driver); else warning("%s: Mount failed: %s: Exited with status %i", bdev_path, fs->driver, WEXITSTATUS(code)); return false; } struct timespec delay = timespec_make(0, 50L * 1000L * 1000L); nanosleep(&delay, NULL); } return true; } static void mountpoints_mount(bool is_chain_init) { for ( size_t i = 0; i < mountpoints_used; i++ ) { struct mountpoint* mountpoint = &mountpoints[i]; if ( is_chain_init_mountpoint(mountpoint) != is_chain_init ) continue; mountpoint_mount(mountpoint); } } static void mountpoints_unmount(void) { for ( size_t n = mountpoints_used; n != 0; n-- ) { size_t i = n - 1; struct mountpoint* mountpoint = &mountpoints[i]; if ( mountpoint->pid < 0 ) continue; if ( unmount(mountpoint->absolute, 0) < 0 && errno != ENOMOUNT ) warning("unmount: %s: %m", mountpoint->entry.fs_file); else if ( errno == ENOMOUNT ) kill(mountpoint->pid, SIGTERM); int code; if ( waitpid(mountpoint->pid, &code, 0) < 0 ) note("waitpid: %m"); mountpoint->pid = -1; } } // This function must be usable as an atexit handler, which means it is // undefined behavior for it to invoke exit(), including through calls to fatal // in any function transitively called by this function. static void niht(void) { if ( getpid() != main_pid ) return; // TODO: Unify with new daemon system? // TODO: Don't do this unless all the mountpoints were mounted (not for // chain init). write_random_seed(); // Stop logging when unmounting the filesystems. log_close(&init_log); if ( chain_location_dev_made ) { unmount(chain_location_dev, 0); chain_location_dev_made = false; } mountpoints_unmount(); if ( chain_location_made ) { rmdir(chain_location); chain_location_made = false; } } static void compact_arguments(int* argc, char*** argv) { for ( int i = 0; i < *argc; i++ ) { while ( i < *argc && !(*argv)[i] ) { for ( int n = i; n < *argc; n++ ) (*argv)[n] = (*argv)[n+1]; (*argc)--; } } } int main(int argc, char* argv[]) { main_pid = getpid(); setlocale(LC_ALL, ""); const char* target_name = "default"; for ( int i = 1; i < argc; i++ ) { const char* arg = argv[i]; if ( arg[0] != '-' || !arg[1] ) continue; argv[i] = NULL; if ( !strcmp(arg, "--") ) break; if ( arg[1] != '-' ) { char c; while ( (c = *++arg) ) switch ( c ) { default: errx(2, "unknown option -- '%c'", c); } } else if ( !strncmp(arg, "--target=", strlen("--target=")) ) target_name = arg + strlen("--target="); else if ( !strcmp(arg, "--target") ) { if ( i + 1 == argc ) errx(2, "option '--target' requires an argument"); target_name = argv[i+1]; argv[++i] = NULL; } else errx(2, "unknown option: %s", arg); } compact_arguments(&argc, &argv); // Prevent recursive init without care. if ( getenv("INIT_PID") ) fatal("System is already managed by an init process"); // Register handler that shuts down the system when init exits. if ( atexit(niht) != 0 ) fatal("atexit: %m"); // Handle signals but block them until the safe points where we handle them. // All child processes have to uninstall the signal handler and unblock the // signals or they keep blocking the signals. install_signal_handler(); // The default daemon brings up the operating system. struct daemon_config* default_daemon_config = daemon_config_load("default"); // Daemons inherit their default settings from the default daemon. Load its // configuration (if it exists) even if another default target has been set. if ( default_daemon_config ) { default_config.log_method = default_daemon_config->log_method; default_config.log_format = default_daemon_config->log_format; default_config.log_control_messages = default_daemon_config->log_control_messages; default_config.log_rotate_on_start = default_daemon_config->log_rotate_on_start; default_config.log_rotations = default_daemon_config->log_rotations; default_config.log_line_size = default_daemon_config->log_line_size; default_config.log_size = default_daemon_config->log_size; } // If another daemon has been specified as the boot target, create a fake // default daemon that depends on the specified boot target daemon. if ( strcmp(target_name, "default") != 0 ) { if ( default_daemon_config ) daemon_config_free(default_daemon_config); default_daemon_config = malloc(sizeof(struct daemon_config)); if ( !default_daemon_config ) fatal("malloc: %m"); daemon_config_initialize(default_daemon_config); if ( !(default_daemon_config->name = strdup("default")) ) fatal("malloc: %m"); struct dependency_config* dependency_config = calloc(1, sizeof(struct dependency_config)); if ( !dependency_config ) fatal("malloc: %m"); if ( !(dependency_config->target = strdup(target_name)) ) fatal("malloc: %m"); dependency_config->flags = DEPENDENCY_FLAG_REQUIRE | DEPENDENCY_FLAG_AWAIT | DEPENDENCY_FLAG_EXIT_CODE; if ( !array_add((void***) &default_daemon_config->dependencies, &default_daemon_config->dependencies_used, &default_daemon_config->dependencies_length, dependency_config) ) fatal("malloc: %m"); } else if ( !default_daemon_config ) fatal("Failed to load /etc/init/default: %m"); // Instantiate the default daemon from its configuration. struct daemon* default_daemon = daemon_create(default_daemon_config); daemon_config_free(default_daemon_config); // The default daemon should depend on exactly one top level daemon. const char* first_requirement = 1 <= default_daemon->dependencies_used ? default_daemon->dependencies[0]->target_name : ""; // Make sure that we have a /tmp directory. umask(0000); mkdir("/tmp", 01777); clean_tmp("/tmp"); // Make sure that we have a /var/run directory. umask(0000); mkdir("/var", 0755); mkdir("/var/run", 0755); clean_tmp("/var/run"); // Set the default file creation mask. umask(0022); // Set up the PATH variable. if ( setenv("PATH", "/bin:/sbin", 1) < 0 ) fatal("setenv: %m"); // Load partition tables and create all the block devices. prepare_block_devices(); // Load the filesystem table. load_fstab(); // If the default daemon's top level dependency is a chain boot target, then // chain boot the actual root filesystem. // TODO: Document this special kind of daemon. if ( !strcmp(first_requirement, "chain") || !strcmp(first_requirement, "chain-merge") ) { int next_argc = argc - 1; char** next_argv = argv + 1; // Create a temporary directory where the real root filesystem will be // mounted. if ( !mkdtemp(chain_location) ) fatal("mkdtemp: /tmp/fs.XXXXXX: %m"); chain_location_made = true; // Rewrite the filesystem table to mount inside the temporary directory. bool found_root = false; for ( size_t i = 0; i < mountpoints_used; i++ ) { struct mountpoint* mountpoint = &mountpoints[i]; if ( !strcmp(mountpoint->entry.fs_file, "/") ) found_root = true; char* absolute = join_paths(chain_location, mountpoint->absolute); free(mountpoint->absolute); mountpoint->absolute = absolute; } if ( !found_root ) fatal("/etc/fstab: Root filesystem not found in filesystem table"); // Mount the filesystem table entries marked for chain boot. mountpoints_mount(true); // Additionally bind the /dev filesystem inside the root filesystem. snprintf(chain_location_dev, sizeof(chain_location_dev), "%s/dev", chain_location); if ( mkdir(chain_location_dev, 0755) < 0 && errno != EEXIST ) fatal("mkdir: %s: %m", chain_location_dev); int old_dev_fd = open("/dev", O_DIRECTORY | O_RDONLY); if ( old_dev_fd < 0 ) fatal("%s: %m", "/dev"); int new_dev_fd = open(chain_location_dev, O_DIRECTORY | O_RDONLY); if ( new_dev_fd < 0 ) fatal("%s: %m", chain_location_dev); if ( fsm_fsbind(old_dev_fd, new_dev_fd, 0) < 0 ) fatal("mount: `%s' onto `%s': %m", "/dev", chain_location_dev); close(new_dev_fd); close(old_dev_fd); // Run the chain booted operating system. pid_t child_pid = fork(); if ( child_pid < 0 ) fatal("fork: %m"); if ( !child_pid ) { uninstall_signal_handler(); if ( chroot(chain_location) < 0 ) fatal("chroot: %s: %m", chain_location); if ( chdir("/") < 0 ) fatal("chdir: %s: %m", chain_location); unsetenv("INIT_PID"); const char* program = next_argv[0]; // Chain boot the operating system upgrade if needed. if ( !strcmp(first_requirement, "chain-merge") ) { program = "/sysmerge/sbin/init"; // TODO: Concat next_argv onto this argv_next, so the arguments // can be passed to the final make. next_argv = (char*[]) { (char*) program, "--target=merge", NULL }; } else if ( next_argc < 1 ) { program = "/sbin/init"; next_argv = (char*[]) { "init", NULL }; } execvp(program, (char* const*) next_argv); fatal("Failed to chain load init: %s: %m", next_argv[0]); } forward_signal_pid = child_pid; sigprocmask(SIG_UNBLOCK, &handled_signals, NULL); int status; while ( waitpid(child_pid, &status, 0) < 0 ) { if ( errno != EINTR ) fatal("waitpid: %m"); } sigprocmask(SIG_BLOCK, &handled_signals, NULL); forward_signal_pid = -1; // Racy with waitpid. if ( WIFEXITED(status) ) return WEXITSTATUS(status); else if ( WIFSIGNALED(status) ) fatal("Chain booted init failed with signal: %s", strsignal(WTERMSIG(status))); else fatal("Chain booted init failed unusually"); } // Mount the filesystems, except for the filesystems that would have been // mounted by the chain init. mountpoints_mount(false); // Logging works now that the filesystems have been mounted. if ( !log_initialize(&init_log, "init", &default_config) ) fatal("malloc: %m"); log_begin(&init_log); // Update the random seed in case the system fails before it can be written // out during the system shutdown. write_random_seed(); set_hostname(); set_kblayout(); set_videomode(); // Run the operating system upgrade if requested. // TODO: Document this special kind of daemon. if ( !strcmp(first_requirement, "merge") ) { pid_t child_pid = fork(); if ( child_pid < 0 ) fatal("fork: %m"); if ( !child_pid ) { uninstall_signal_handler(); const char* argv[] = { "sysmerge", "--booting", NULL }; execv("/sysmerge/sbin/sysmerge", (char* const*) argv); fatal("Failed to load system upgrade: %s: %m", argv[0]); } forward_signal_pid = child_pid; sigprocmask(SIG_UNBLOCK, &handled_signals, NULL); int status; while ( waitpid(child_pid, &status, 0) < 0 ) { if ( errno != EINTR ) fatal("waitpid: %m"); } sigprocmask(SIG_BLOCK, &handled_signals, NULL); forward_signal_pid = -1; // Racy with waitpid. if ( WIFEXITED(status) && WEXITSTATUS(status) != 0 ) fatal("Automatic upgrade failed: Exit status %i", WEXITSTATUS(status)); else if ( WIFSIGNALED(status) ) fatal("Automatic upgrade failed: %s", strsignal(WTERMSIG(status))); else if ( !WIFEXITED(status) ) fatal("Automatic upgrade failed: Unexpected unusual termination"); // Soft reinit into the freshly upgraded operating system. niht(); unsetenv("INIT_PID"); // TODO: Use next_argv here. const char* argv[] = { "init", NULL }; execv("/sbin/init", (char* const*) argv); fatal("Failed to load init during reinit: %s: %m", argv[0]); } // TODO: Use the arguments to specify additional things the default daemon // should depend on, as well as a blacklist of things not to start // even if in default's dependencies. Alternatively the blacklist can // be done with variables (that could be cleaner, if more flexible)? // Request the default daemon be run. schedule_daemon(default_daemon); // Run the operating system. schedule(); // Exit with the exit code of the default daemon. return exit_code_to_exit_status(default_daemon->exit_code); }