4896 lines
137 KiB
C
4896 lines
137 KiB
C
/*
|
|
* Copyright (c) 2011-2025 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 <sys/display.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/statvfs.h>
|
|
#include <sys/types.h>
|
|
#include <sys/un.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <dirent.h>
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <fsmarshall.h>
|
|
#include <fstab.h>
|
|
#include <getopt.h>
|
|
#include <grp.h>
|
|
#include <inttypes.h>
|
|
#include <ioleast.h>
|
|
#include <limits.h>
|
|
#include <locale.h>
|
|
#include <net/if.h>
|
|
#include <poll.h>
|
|
#include <psctl.h>
|
|
#include <pwd.h>
|
|
#include <signal.h>
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdnoreturn.h>
|
|
#include <string.h>
|
|
#include <termios.h>
|
|
#include <time.h>
|
|
#include <timespec.h>
|
|
#include <unistd.h>
|
|
|
|
// TODO: The Sortix <limits.h> doesn't expose this at the moment.
|
|
#if !defined(HOST_NAME_MAX) && defined(__sortix__)
|
|
#include <sortix/limits.h>
|
|
#endif
|
|
|
|
#include <mount/blockdevice.h>
|
|
#include <mount/devices.h>
|
|
#include <mount/filesystem.h>
|
|
#include <mount/harddisk.h>
|
|
#include <mount/partition.h>
|
|
#include <mount/uuid.h>
|
|
|
|
struct device_match
|
|
{
|
|
const char* path;
|
|
struct blockdevice* bdev;
|
|
};
|
|
|
|
struct mountpoint
|
|
{
|
|
struct fstab entry;
|
|
char* entry_line;
|
|
pid_t pid;
|
|
char* absolute;
|
|
};
|
|
|
|
enum verbosity
|
|
{
|
|
VERBOSITY_SILENT,
|
|
VERBOSITY_QUIET,
|
|
VERBOSITY_VERBOSE,
|
|
};
|
|
|
|
enum exit_code_meaning
|
|
{
|
|
EXIT_CODE_MEANING_DEFAULT,
|
|
EXIT_CODE_MEANING_POWEROFF_REBOOT,
|
|
};
|
|
|
|
enum type
|
|
{
|
|
TYPE_DAEMON,
|
|
TYPE_ONESHOT,
|
|
};
|
|
|
|
enum daemon_state
|
|
{
|
|
// Daemon is not running and should not be running.
|
|
DAEMON_STATE_TERMINATED,
|
|
|
|
// Daemon is not running but should be scheduled to run.
|
|
DAEMON_STATE_SCHEDULED,
|
|
|
|
// Daemon is not running but will start after its dependencies are ready.
|
|
DAEMON_STATE_WAITING,
|
|
|
|
// Daemon is not running and can start now that the dependencies are ready.
|
|
DAEMON_STATE_SATISFIED,
|
|
|
|
// Daemon is running but isn't ready.
|
|
DAEMON_STATE_STARTING,
|
|
|
|
// Daemon is running and is ready.
|
|
DAEMON_STATE_RUNNING,
|
|
|
|
// Daemon is running and is being terminated.
|
|
DAEMON_STATE_TERMINATING,
|
|
|
|
// Daemon just finished running and the other daemons needs to be notified.
|
|
DAEMON_STATE_FINISHING,
|
|
|
|
// Daemon has finished running.
|
|
DAEMON_STATE_FINISHED,
|
|
};
|
|
|
|
#define NUM_DAEMON_STATES (DAEMON_STATE_FINISHED + 1)
|
|
|
|
const char* daemon_state_names[] =
|
|
{
|
|
"terminated",
|
|
"scheduled",
|
|
"waiting",
|
|
"satisfied",
|
|
"starting",
|
|
"running",
|
|
"terminating",
|
|
"finishing",
|
|
"finished",
|
|
};
|
|
|
|
struct daemon;
|
|
|
|
struct dependency
|
|
{
|
|
struct daemon* source;
|
|
struct daemon* target;
|
|
int flags;
|
|
int status;
|
|
};
|
|
|
|
#define DEPENDENCY_FLAG_REQUIRE (1 << 0)
|
|
#define DEPENDENCY_FLAG_AWAIT (1 << 1)
|
|
#define DEPENDENCY_FLAG_EXIT_CODE (1 << 2)
|
|
|
|
#define DEPENDENCY_STATUS_REFERENCED (1 << 0)
|
|
#define DEPENDENCY_STATUS_READY (1 << 1)
|
|
#define DEPENDENCY_STATUS_FINISHED (1 << 2)
|
|
#define DEPENDENCY_STATUS_FAILED (1 << 3)
|
|
|
|
enum log_method
|
|
{
|
|
LOG_METHOD_NONE,
|
|
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
|
|
{
|
|
char* name;
|
|
pid_t pid;
|
|
enum log_method method;
|
|
enum log_format format;
|
|
bool control_messages;
|
|
bool rotate_on_start;
|
|
size_t max_rotations;
|
|
off_t max_line_size;
|
|
size_t skipped;
|
|
off_t max_size;
|
|
char* path;
|
|
char* path_src;
|
|
char* path_dst;
|
|
size_t path_number_offset;
|
|
size_t path_number_size;
|
|
char* buffer;
|
|
size_t buffer_used;
|
|
size_t buffer_size;
|
|
off_t size;
|
|
int fd;
|
|
int last_errno;
|
|
bool line_terminated;
|
|
bool line_begun;
|
|
mode_t file_mode;
|
|
};
|
|
|
|
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* netif;
|
|
int argc;
|
|
char** argv;
|
|
struct termios oldtio;
|
|
struct log log;
|
|
struct timespec timeout;
|
|
pid_t pid;
|
|
enum exit_code_meaning exit_code_meaning;
|
|
enum type type;
|
|
enum daemon_state state;
|
|
int exit_code;
|
|
int readyfd;
|
|
int outputfd;
|
|
bool configured;
|
|
bool echo;
|
|
bool need_tty;
|
|
bool was_ready;
|
|
bool was_terminated;
|
|
bool want_reconfigure;
|
|
bool want_reload;
|
|
bool want_restart;
|
|
bool timeout_set;
|
|
};
|
|
|
|
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* cd;
|
|
int argc;
|
|
char** argv;
|
|
enum exit_code_meaning exit_code_meaning;
|
|
bool echo;
|
|
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;
|
|
off_t log_line_size;
|
|
off_t log_size;
|
|
mode_t log_file_mode;
|
|
enum type type;
|
|
};
|
|
|
|
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;
|
|
size_t state;
|
|
};
|
|
|
|
enum communication_type
|
|
{
|
|
COMMUNICATION_TYPE_OUTPUT,
|
|
COMMUNICATION_TYPE_READY,
|
|
COMMUNICATION_TYPE_SERVER,
|
|
COMMUNICATION_TYPE_CONNECTION,
|
|
};
|
|
|
|
struct communication
|
|
{
|
|
enum communication_type type;
|
|
size_t* index_ptr;
|
|
union
|
|
{
|
|
struct daemon* daemon;
|
|
struct connection* connection;
|
|
struct server* server;
|
|
};
|
|
};
|
|
|
|
static const char* prefix;
|
|
static const char* static_prefix;
|
|
static char* bin_path;
|
|
static char* etc_path;
|
|
static char* etc_init_path;
|
|
static char* log_path;
|
|
static char* run_path;
|
|
static char* sbin_path;
|
|
static char* share_init_path;
|
|
static char* tmp_path;
|
|
static char* var_path;
|
|
static char* random_seed_path;
|
|
static char* server_path;
|
|
static char* chain_path;
|
|
static bool chain_path_made;
|
|
static char* chain_dev_path;
|
|
static bool chain_dev_path_made;
|
|
static char* chain_tmp_path;
|
|
static char* chain_tmp_path_real;
|
|
static bool chain_tmp_path_made;
|
|
|
|
static pid_t main_pid;
|
|
static pid_t forward_signal_pid = -1;
|
|
static int tty_fd;
|
|
static bool tty_gifted;
|
|
|
|
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,
|
|
.log_file_mode = 0644,
|
|
};
|
|
|
|
static struct log init_log = { .fd = -1 };
|
|
|
|
static enum verbosity verbosity = VERBOSITY_QUIET;
|
|
|
|
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 communication* communications = NULL;
|
|
static size_t communications_length = 0;
|
|
|
|
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;
|
|
case SIGHUP: caught_exit_signal = 3; break;
|
|
}
|
|
}
|
|
|
|
static void install_signal_handler(void)
|
|
{
|
|
sigemptyset(&handled_signals);
|
|
sigaddset(&handled_signals, SIGINT);
|
|
sigaddset(&handled_signals, SIGQUIT);
|
|
sigaddset(&handled_signals, SIGTERM);
|
|
sigaddset(&handled_signals, SIGHUP);
|
|
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);
|
|
sigaction(SIGHUP, &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);
|
|
sigaction(SIGHUP, &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 )
|
|
{
|
|
size_t length = *length_ptr;
|
|
if ( !length )
|
|
length = 4;
|
|
void** new_array = reallocarray(array, length, 2 * sizeof(void*));
|
|
if ( !new_array )
|
|
return false;
|
|
array = new_array;
|
|
memcpy(array_ptr, &array, sizeof(array)); // Strict aliasing.
|
|
*length_ptr = length * 2;
|
|
}
|
|
|
|
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 2;
|
|
}
|
|
|
|
static void log_close(struct log* log)
|
|
{
|
|
if ( 0 <= log->fd )
|
|
close(log->fd);
|
|
log->fd = -1;
|
|
free(log->buffer);
|
|
log->buffer = NULL;
|
|
}
|
|
|
|
static void log_error(struct log* log, const char* logprefix, const char* path)
|
|
{
|
|
// TODO: Log to the init log unless about the init log.
|
|
if ( !errno )
|
|
warnx("%s%s", logprefix, path ? path : log->path);
|
|
else if ( errno != log->last_errno )
|
|
warn("%s%s", logprefix, path ? path : log->path);
|
|
log->last_errno = errno;
|
|
}
|
|
|
|
static bool log_open(struct log* log)
|
|
{
|
|
if ( log->method == LOG_METHOD_NONE )
|
|
return true;
|
|
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);
|
|
log->fd = open(log->path, logflags, log->file_mode);
|
|
if ( log->fd < 0 )
|
|
{
|
|
if ( errno != EROFS )
|
|
log_error(log, "", NULL);
|
|
// Don't block daemon startup on read-only filesystems.
|
|
return errno == EROFS;
|
|
}
|
|
struct stat st;
|
|
if ( fstat(log->fd, &st) < 0 )
|
|
{
|
|
log_error(log, "stat: ", NULL);
|
|
close(log->fd);
|
|
log->fd = -1;
|
|
return false;
|
|
}
|
|
if ( (st.st_mode & 07777) != log->file_mode )
|
|
{
|
|
if ( fchmod(log->fd, log->file_mode) < 0 )
|
|
{
|
|
log_error(log, "fchmod: ", NULL);
|
|
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 ( log->method == LOG_METHOD_NONE )
|
|
return true;
|
|
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: 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 )
|
|
{
|
|
// Don't rotate logs on read-only filesystems.
|
|
if ( errno == EROFS )
|
|
break;
|
|
log_error(log, "archiving: opening: ", log->path_dst);
|
|
}
|
|
else
|
|
{
|
|
if ( ftruncate(fd, 0) < 0 )
|
|
log_error(log, "archiving: truncate: ", log->path_dst);
|
|
close(fd);
|
|
}
|
|
if ( unlink(log->path_dst) < 0 )
|
|
log_error(log, "archiving: unlink: ", log->path_dst);
|
|
}
|
|
else if ( errno != ENOENT )
|
|
log_error(log, "archiving: ", log->path_dst);
|
|
}
|
|
if ( rename(log->path_src, log->path_dst) < 0 )
|
|
{
|
|
// Don't rotate logs on read-only filesystems.
|
|
if ( errno == EROFS )
|
|
break;
|
|
// Ignore non-existent logs.
|
|
if ( errno != ENOENT )
|
|
{
|
|
log_error(log, "archiving: ", log->path_src);
|
|
return errno == EROFS;
|
|
}
|
|
}
|
|
}
|
|
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 ( log->max_size < log->max_line_size )
|
|
log->max_line_size = log->max_size;
|
|
log->file_mode = daemon_config->log_file_mode;
|
|
log->name = strdup(name);
|
|
if ( !log->name )
|
|
return false;
|
|
if ( asprintf(&log->path, "%s/%s.log", log_path, name) < 0 )
|
|
return free(log->name), 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), free(log->name), false;
|
|
if ( asprintf(&log->path_dst, "%s.%i", log->path, INT_MAX) < 0 )
|
|
return free(log->path_src), free(log->path), free(log->name), 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_deinitialize(struct log* log)
|
|
{
|
|
free(log->name);
|
|
free(log->path);
|
|
free(log->path_src);
|
|
free(log->path_dst);
|
|
memset(log, 0, sizeof(*log));
|
|
log->fd = -1;
|
|
}
|
|
|
|
static bool log_begin_buffer(struct log* log)
|
|
{
|
|
log->buffer_used = 0;
|
|
log->buffer_size = 4096;
|
|
log->buffer = malloc(log->buffer_size);
|
|
if ( !log->buffer )
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static void log_data_to_buffer(struct log* log, const char* data, size_t length)
|
|
{
|
|
assert(log->buffer);
|
|
if ( log->skipped )
|
|
{
|
|
log->skipped += length;
|
|
return;
|
|
}
|
|
while ( length )
|
|
{
|
|
size_t available = log->buffer_size - log->buffer_used;
|
|
if ( !available )
|
|
{
|
|
if ( 1048576 <= log->buffer_size )
|
|
{
|
|
errno = 0;
|
|
log_error(log, "in-memory buffer exhausted: ", NULL);
|
|
log->skipped += length;
|
|
return;
|
|
}
|
|
size_t new_size = 2 * log->buffer_size;
|
|
char* new_buffer = realloc(log->buffer, new_size);
|
|
if ( !new_buffer )
|
|
{
|
|
log_error(log, "expanding in-memory buffer: ", NULL);
|
|
log->skipped += length;
|
|
return;
|
|
}
|
|
log->buffer = new_buffer;
|
|
log->buffer_size = new_size;
|
|
available = log->buffer_size - log->buffer_used;
|
|
}
|
|
size_t amount = length < available ? length : available;
|
|
memcpy(log->buffer + log->buffer_used, data, amount);
|
|
data += amount;
|
|
length -= amount;
|
|
log->buffer_used += amount;
|
|
}
|
|
}
|
|
|
|
static void log_data(struct log* log, const char* data, size_t length)
|
|
{
|
|
if ( log->method == LOG_METHOD_NONE )
|
|
return;
|
|
if ( log->fd < 0 && log->buffer )
|
|
{
|
|
log_data_to_buffer(log, data, length);
|
|
return;
|
|
}
|
|
if ( log->skipped )
|
|
{
|
|
// TODO: Try to log a mesage about how many bytes were skipped and
|
|
// resume logging if that worked. For now, let's just lose data.
|
|
}
|
|
const off_t chunk_cut_offset = log->max_size - log->max_line_size;
|
|
size_t sofar = 0;
|
|
while ( sofar < length )
|
|
{
|
|
if ( log->fd < 0 )
|
|
{
|
|
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) )
|
|
{
|
|
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 )
|
|
{
|
|
log_error(log, "writing: ", NULL);
|
|
log->skipped += length - sofar;
|
|
return;
|
|
}
|
|
sofar += amount;
|
|
log->size += amount;
|
|
log->line_terminated = next_data[amount - 1] == '\n';
|
|
log->last_errno = 0;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
size_t log_name_length = strlen(log->name);
|
|
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);
|
|
}
|
|
if ( log->format == LOG_FORMAT_SYSLOG )
|
|
{
|
|
pid_t pid = 0 < log->pid ? log->pid : getpid();
|
|
char part[64];
|
|
snprintf(part, sizeof(part), " %ji - - ", (intmax_t) pid);
|
|
log_data(log, part, strlen(part));
|
|
}
|
|
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;
|
|
}
|
|
|
|
static bool log_begin(struct log* log)
|
|
{
|
|
if ( log->method == LOG_METHOD_NONE )
|
|
return true;
|
|
bool opened;
|
|
if ( log->method == LOG_METHOD_ROTATE && log->rotate_on_start )
|
|
opened = log_rotate(log);
|
|
else
|
|
opened = log_open(log);
|
|
if ( !opened )
|
|
return false;
|
|
if ( log->buffer )
|
|
{
|
|
log_data(log, log->buffer, log->buffer_used);
|
|
free(log->buffer);
|
|
log->buffer = NULL;
|
|
log->buffer_used = 0;
|
|
log->buffer_size = 0;
|
|
// TODO: Warn about any skipped data.
|
|
log->skipped = 0;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
__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);
|
|
if ( verbosity == VERBOSITY_SILENT ||
|
|
(verbosity == VERBOSITY_QUIET &&
|
|
strcmp(status, "failed") != 0 &&
|
|
strcmp(status, "timeout") != 0) )
|
|
return;
|
|
if ( tty_gifted )
|
|
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, "killing") )
|
|
fprintf(stderr, "[ ] ");
|
|
else if ( !strcmp(status, "reconfigure") )
|
|
fprintf(stderr, "[ ] ");
|
|
else if ( !strcmp(status, "reconfiguring") )
|
|
fprintf(stderr, "[ ] ");
|
|
else if ( !strcmp(status, "restart") )
|
|
fprintf(stderr, "[ ] ");
|
|
else if ( !strcmp(status, "restarting") )
|
|
fprintf(stderr, "[ ] ");
|
|
else if ( !strcmp(status, "stopped") )
|
|
fprintf(stderr, "[ \e[92mOK\e[m ] ");
|
|
else if ( !strcmp(status, "timeout") )
|
|
fprintf(stderr, "[\e[93m TIME \e[m] ");
|
|
else if ( !strcmp(status, "signal") )
|
|
fprintf(stderr, "[SIGNAL] ");
|
|
else
|
|
fprintf(stderr, "[ ?? ] ");
|
|
vfprintf(stderr, format, ap);
|
|
fflush(stderr);
|
|
va_end(ap);
|
|
}
|
|
|
|
__attribute__((format(printf, 1, 2)))
|
|
noreturn static void fatal(const char* format, ...)
|
|
{
|
|
if ( tty_gifted )
|
|
{
|
|
(void) ioctl(tty_fd, TIOCSCTTY, 1);
|
|
tty_gifted = false;
|
|
}
|
|
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 )
|
|
{
|
|
va_start(ap, format);
|
|
vcbprintf(&init_log, log_callback, format, ap);
|
|
log_formatted(&init_log, "\n", 1);
|
|
va_end(ap);
|
|
}
|
|
if ( getpid() == main_pid )
|
|
exit(2);
|
|
_exit(2);
|
|
}
|
|
|
|
__attribute__((format(printf, 1, 2)))
|
|
static void warning(const char* format, ...)
|
|
{
|
|
va_list ap;
|
|
if ( !tty_gifted )
|
|
{
|
|
va_start(ap, format);
|
|
fprintf(stderr, "%s: warning: ", program_invocation_name);
|
|
vfprintf(stderr, format, ap);
|
|
fprintf(stderr, "\n");
|
|
fflush(stderr);
|
|
va_end(ap);
|
|
}
|
|
if ( getpid() == main_pid )
|
|
{
|
|
va_start(ap, format);
|
|
vcbprintf(&init_log, log_callback, format, ap);
|
|
log_formatted(&init_log, "\n", 1);
|
|
va_end(ap);
|
|
}
|
|
}
|
|
|
|
__attribute__((format(printf, 1, 2)))
|
|
static void note(const char* format, ...)
|
|
{
|
|
va_list ap;
|
|
if ( !tty_gifted )
|
|
{
|
|
va_start(ap, format);
|
|
fprintf(stderr, "%s: ", program_invocation_name);
|
|
vfprintf(stderr, format, ap);
|
|
fprintf(stderr, "\n");
|
|
fflush(stderr);
|
|
va_end(ap);
|
|
}
|
|
if ( getpid() == main_pid )
|
|
{
|
|
va_start(ap, format);
|
|
vcbprintf(&init_log, log_callback, format, ap);
|
|
log_formatted(&init_log, "\n", 1);
|
|
va_end(ap);
|
|
}
|
|
}
|
|
|
|
static char** tokenize(size_t* out_tokens_used, const char* string)
|
|
{
|
|
size_t tokens_used = 0;
|
|
size_t tokens_length = 0;
|
|
char** tokens = malloc(sizeof(char*));
|
|
if ( !tokens )
|
|
return NULL;
|
|
bool failed = false;
|
|
bool invalid = false;
|
|
while ( *string )
|
|
{
|
|
if ( isspace((unsigned char) *string) )
|
|
{
|
|
string++;
|
|
continue;
|
|
}
|
|
if ( *string == '#' )
|
|
break;
|
|
char* token;
|
|
size_t token_size;
|
|
FILE* fp = open_memstream(&token, &token_size);
|
|
if ( !fp )
|
|
{
|
|
failed = true;
|
|
break;
|
|
}
|
|
bool singly = false;
|
|
bool doubly = false;
|
|
bool escaped = false;
|
|
while ( *string )
|
|
{
|
|
char c = *string++;
|
|
if ( !escaped && !singly && !doubly && isspace((unsigned char) c) )
|
|
break;
|
|
if ( !escaped && !doubly && c == '\'' )
|
|
{
|
|
singly = !singly;
|
|
continue;
|
|
}
|
|
if ( !escaped && !singly && c == '"' )
|
|
{
|
|
doubly = !doubly;
|
|
continue;
|
|
}
|
|
if ( !singly && !escaped && c == '\\' )
|
|
{
|
|
escaped = true;
|
|
continue;
|
|
}
|
|
if ( escaped )
|
|
{
|
|
switch ( c )
|
|
{
|
|
case 'a': c = '\a'; break;
|
|
case 'b': c = '\b'; break;
|
|
case 'e': c = '\e'; break;
|
|
case 'f': c = '\f'; break;
|
|
case 'n': c = '\n'; break;
|
|
case 'r': c = '\r'; break;
|
|
case 't': c = '\t'; break;
|
|
case 'v': c = '\v'; break;
|
|
default: break;
|
|
};
|
|
}
|
|
escaped = false;
|
|
if ( fputc((unsigned char) c, fp) == EOF )
|
|
{
|
|
failed = true;
|
|
break;
|
|
}
|
|
}
|
|
if ( singly || doubly || escaped )
|
|
{
|
|
fclose(fp);
|
|
free(token);
|
|
invalid = true;
|
|
break;
|
|
}
|
|
if ( fflush(fp) == EOF )
|
|
{
|
|
fclose(fp);
|
|
free(token);
|
|
failed = true;
|
|
break;
|
|
}
|
|
fclose(fp);
|
|
if ( !array_add((void***) &tokens, &tokens_used, &tokens_length,
|
|
token) )
|
|
{
|
|
free(token);
|
|
failed = true;
|
|
break;
|
|
}
|
|
}
|
|
if ( failed || invalid )
|
|
{
|
|
for ( size_t i = 0; i < tokens_used; i++ )
|
|
free(tokens[i]);
|
|
free(tokens);
|
|
if ( invalid )
|
|
errno = 0;
|
|
return NULL;
|
|
}
|
|
char** new_tokens = reallocarray(tokens, tokens_used, sizeof(char*));
|
|
if ( new_tokens )
|
|
tokens = new_tokens;
|
|
*out_tokens_used = tokens_used;
|
|
return tokens;
|
|
}
|
|
|
|
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);
|
|
free(daemon_config->cd);
|
|
for ( int i = 0; i < daemon_config->argc; i++ )
|
|
free(daemon_config->argv[i]);
|
|
free(daemon_config->argv);
|
|
free(daemon_config);
|
|
}
|
|
|
|
static bool daemon_config_load_search(struct daemon_config* daemon_config,
|
|
size_t next_search_path_index);
|
|
|
|
static bool daemon_process_command(struct daemon_config* daemon_config,
|
|
const char* path,
|
|
size_t argc,
|
|
const char* const* argv,
|
|
off_t line_number,
|
|
size_t next_search_path_index)
|
|
{
|
|
if ( !argc )
|
|
return true;
|
|
if ( !strcmp(argv[0], "furthermore") )
|
|
{
|
|
if ( 2 <= argc )
|
|
warning("%s:%ji: unexpected parameter to %s: %s",
|
|
path, (intmax_t) line_number, argv[0], argv[1]);
|
|
// 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;
|
|
}
|
|
return true;
|
|
}
|
|
if ( argc == 1 )
|
|
{
|
|
warning("%s:%ji: expected parameter: %s",
|
|
path, (intmax_t) line_number, argv[0]);
|
|
return false;
|
|
}
|
|
if ( !strcmp(argv[0], "cd") )
|
|
{
|
|
free(daemon_config->cd);
|
|
if ( !(daemon_config->cd = strdup(argv[1])) )
|
|
{
|
|
warning("strdup: %m");
|
|
return false;
|
|
}
|
|
}
|
|
else if ( !strcmp(argv[0], "echo") )
|
|
{
|
|
if ( !strcmp(argv[1], "true") )
|
|
daemon_config->echo = true;
|
|
else if ( !strcmp(argv[1], "false") )
|
|
daemon_config->echo = false;
|
|
else
|
|
warning("%s:%ji: unknown %s: %s",
|
|
path, (intmax_t) line_number, argv[0], argv[1]);
|
|
}
|
|
else if ( !strcmp(argv[0], "exec") )
|
|
{
|
|
for ( int i = 0; i < daemon_config->argc; i++ )
|
|
free(daemon_config->argv[i]);
|
|
free(daemon_config->argv);
|
|
daemon_config->argc = 0;
|
|
daemon_config->argv = NULL;
|
|
if ( INT_MAX - 1 < argc - 1 )
|
|
{
|
|
warning("%s:%ji: too many arguments: %s",
|
|
path, (intmax_t) line_number, argv[0]);
|
|
return false;
|
|
}
|
|
int new_argc = argc - 1;
|
|
char** new_argv = calloc(new_argc + 1, sizeof(char*));
|
|
if ( !new_argv )
|
|
{
|
|
warning("malloc: %m");
|
|
return false;
|
|
}
|
|
for ( int i = 0; i < new_argc; i++ )
|
|
{
|
|
size_t n = 1 + (size_t) i;
|
|
if ( !(new_argv[i] = strdup(argv[n])) )
|
|
{
|
|
warning("malloc: %m");
|
|
for ( int j = 0; j < i; j++ )
|
|
free(new_argv[j]);
|
|
free(new_argv);
|
|
return false;
|
|
}
|
|
}
|
|
daemon_config->argc = new_argc;
|
|
daemon_config->argv = new_argv;
|
|
}
|
|
else if ( !strcmp(argv[0], "exit-code-meaning") )
|
|
{
|
|
if ( !strcmp(argv[1], "default") )
|
|
daemon_config->exit_code_meaning = EXIT_CODE_MEANING_DEFAULT;
|
|
else if ( !strcmp(argv[1], "poweroff-reboot") )
|
|
daemon_config->exit_code_meaning =
|
|
EXIT_CODE_MEANING_POWEROFF_REBOOT;
|
|
else
|
|
warning("%s:%ji: unknown %s: %s",
|
|
path, (intmax_t) line_number, argv[0], argv[1]);
|
|
}
|
|
else if ( !strcmp(argv[0], "log-control-messages") )
|
|
{
|
|
if ( !strcmp(argv[1], "true") )
|
|
daemon_config->log_control_messages = true;
|
|
else if ( !strcmp(argv[1], "false") )
|
|
daemon_config->log_control_messages = false;
|
|
else
|
|
warning("%s:%ji: unknown %s: %s",
|
|
path, (intmax_t) line_number, argv[0], argv[1]);
|
|
}
|
|
else if ( !strcmp(argv[0], "log-file-mode") )
|
|
{
|
|
char* end;
|
|
errno = 0;
|
|
uintmax_t value = strtoumax(argv[1], &end, 8);
|
|
if ( argv[1] == end || errno || value != (value & 07777) )
|
|
warning("%s:%ji: invalid %s: %s",
|
|
path, (intmax_t) line_number, argv[0], argv[1]);
|
|
else
|
|
daemon_config->log_file_mode = (mode_t) value;
|
|
}
|
|
else if ( !strcmp(argv[0], "log-format") )
|
|
{
|
|
if ( !strcmp(argv[1], "none") )
|
|
daemon_config->log_format = LOG_FORMAT_NONE;
|
|
else if ( !strcmp(argv[1], "seconds") )
|
|
daemon_config->log_format = LOG_FORMAT_SECONDS;
|
|
else if ( !strcmp(argv[1], "nanoseconds") )
|
|
daemon_config->log_format = LOG_FORMAT_NANOSECONDS;
|
|
else if ( !strcmp(argv[1], "basic") )
|
|
daemon_config->log_format = LOG_FORMAT_BASIC;
|
|
else if ( !strcmp(argv[1], "full") )
|
|
daemon_config->log_format = LOG_FORMAT_FULL;
|
|
else if ( !strcmp(argv[1], "syslog") )
|
|
daemon_config->log_format = LOG_FORMAT_SYSLOG;
|
|
else
|
|
warning("%s:%ji: unknown %s: %s",
|
|
path, (intmax_t) line_number, argv[0], argv[1]);
|
|
}
|
|
else if ( !strcmp(argv[0], "log-line-size") )
|
|
{
|
|
char* end;
|
|
errno = 0;
|
|
intmax_t value = strtoimax(argv[1], &end, 10);
|
|
if ( argv[1] == end || errno || value != (off_t) value || value < 0 )
|
|
warning("%s:%ji: invalid %s: %s",
|
|
path, (intmax_t) line_number, argv[0], argv[1]);
|
|
else
|
|
daemon_config->log_line_size = (off_t) value;
|
|
}
|
|
else if ( !strcmp(argv[0], "log-method") )
|
|
{
|
|
if ( !strcmp(argv[1], "append") )
|
|
daemon_config->log_method = LOG_METHOD_APPEND;
|
|
else if ( !strcmp(argv[1], "rotate") )
|
|
daemon_config->log_method = LOG_METHOD_ROTATE;
|
|
else
|
|
warning("%s:%ji: unknown %s: %s",
|
|
path, (intmax_t) line_number, argv[0], argv[1]);
|
|
}
|
|
else if ( !strcmp(argv[0], "log-rotate-on-start") )
|
|
{
|
|
if ( !strcmp(argv[1], "true") )
|
|
daemon_config->log_rotate_on_start = true;
|
|
else if ( !strcmp(argv[1], "false") )
|
|
daemon_config->log_rotate_on_start = false;
|
|
else
|
|
warning("%s:%ji: unknown %s: %s",
|
|
path, (intmax_t) line_number, argv[0], argv[1]);
|
|
}
|
|
else if ( !strcmp(argv[0], "log-size") )
|
|
{
|
|
char* end;
|
|
errno = 0;
|
|
intmax_t value = strtoimax(argv[1], &end, 10);
|
|
if ( argv[1] == end || errno || value != (off_t) value || value < 0 )
|
|
warning("%s:%ji: invalid %s: %s",
|
|
path, (intmax_t) line_number, argv[0], argv[1]);
|
|
else
|
|
daemon_config->log_size = (off_t) value;
|
|
}
|
|
else if ( !strcmp(argv[0], "per") )
|
|
{
|
|
if ( !strcmp(argv[1], "if") )
|
|
daemon_config->per_if = true;
|
|
else
|
|
warning("%s:%ji: unknown %s: %s",
|
|
path, (intmax_t) line_number, argv[0], argv[1]);
|
|
}
|
|
else if ( !strcmp(argv[0], "need") )
|
|
{
|
|
if ( !strcmp(argv[1], "tty") )
|
|
daemon_config->need_tty = true;
|
|
else
|
|
warning("%s:%ji: unknown %s: %s",
|
|
path, (intmax_t) line_number, argv[0], argv[1]);
|
|
}
|
|
else if ( !strcmp(argv[0], "require") )
|
|
{
|
|
char* target = strdup(argv[1]);
|
|
if ( !target )
|
|
{
|
|
warning("strdup: %m");
|
|
return false;
|
|
}
|
|
int negated_flags = DEPENDENCY_FLAG_REQUIRE | DEPENDENCY_FLAG_AWAIT;
|
|
int flags = negated_flags;
|
|
for ( size_t i = 2; i < argc; i++ )
|
|
{
|
|
if ( !strcmp(argv[i], "optional") )
|
|
flags &= ~DEPENDENCY_FLAG_REQUIRE;
|
|
else if ( !strcmp(argv[i], "no-await") )
|
|
flags &= ~DEPENDENCY_FLAG_AWAIT;
|
|
else if ( !strcmp(argv[i], "exit-code") )
|
|
flags |= DEPENDENCY_FLAG_EXIT_CODE;
|
|
else
|
|
warning("%s:%ji: %s %s: unknown flag: %s", path,
|
|
(intmax_t) line_number, argv[0], argv[1], argv[i]);
|
|
}
|
|
bool had_exit_code = false;
|
|
struct dependency_config* dependency = NULL;
|
|
for ( size_t i = 0; i < daemon_config->dependencies_used; i++ )
|
|
{
|
|
struct dependency_config* dep = daemon_config->dependencies[i];
|
|
if ( dep->flags & DEPENDENCY_FLAG_EXIT_CODE )
|
|
had_exit_code = true;
|
|
if ( !strcmp(dep->target, target) )
|
|
dependency = dep;
|
|
}
|
|
if ( (flags & DEPENDENCY_FLAG_EXIT_CODE) && had_exit_code )
|
|
{
|
|
warning("%s:%ji: %s %s: exit-code had already been set",
|
|
path, (intmax_t) line_number, argv[0], argv[1]);
|
|
flags &= ~DEPENDENCY_FLAG_EXIT_CODE;
|
|
}
|
|
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(argv[0], "tty") )
|
|
{
|
|
// TODO: Implement.
|
|
}
|
|
else if ( !strcmp(argv[0], "type") )
|
|
{
|
|
if ( !strcmp(argv[1], "daemon") )
|
|
daemon_config->type = TYPE_DAEMON;
|
|
else if ( !strcmp(argv[1], "oneshot") )
|
|
daemon_config->type = TYPE_ONESHOT;
|
|
else
|
|
warning("%s:%ji: unknown %s: %s",
|
|
path, (intmax_t) line_number, argv[0], argv[1]);
|
|
}
|
|
else if ( !strcmp(argv[0], "unset") )
|
|
{
|
|
if ( !strcmp(argv[1], "cd") )
|
|
{
|
|
free(daemon_config->cd);
|
|
daemon_config->cd = NULL;
|
|
}
|
|
else if ( !strcmp(argv[1], "echo") )
|
|
daemon_config->echo = default_config.echo;
|
|
else if ( !strcmp(argv[1], "exec") )
|
|
{
|
|
for ( int i = 0; i < daemon_config->argc; i++ )
|
|
free(daemon_config->argv[i]);
|
|
free(daemon_config->argv);
|
|
daemon_config->argc = 0;
|
|
daemon_config->argv = NULL;
|
|
}
|
|
else if ( !strcmp(argv[1], "exit-code-meaning") )
|
|
daemon_config->exit_code_meaning = EXIT_CODE_MEANING_DEFAULT;
|
|
else if ( !strcmp(argv[1], "log-control-messages") )
|
|
daemon_config->log_control_messages =
|
|
default_config.log_control_messages;
|
|
else if ( !strcmp(argv[1], "log-file-mode") )
|
|
daemon_config->log_file_mode = default_config.log_file_mode;
|
|
else if ( !strcmp(argv[1], "log-format") )
|
|
daemon_config->log_format = default_config.log_format;
|
|
else if ( !strcmp(argv[1], "log-line-size") )
|
|
daemon_config->log_line_size = default_config.log_line_size;
|
|
else if ( !strcmp(argv[1], "log-method") )
|
|
daemon_config->log_method = default_config.log_method;
|
|
else if ( !strcmp(argv[1], "log-rotate-on-start") )
|
|
daemon_config->log_rotate_on_start =
|
|
default_config.log_rotate_on_start;
|
|
else if ( !strcmp(argv[1], "log-size") )
|
|
daemon_config->log_line_size = default_config.log_line_size;
|
|
else if ( !strcmp(argv[1], "per") )
|
|
{
|
|
if ( argc < 3 )
|
|
warning("%s:%ji: expected parameter: %s: %s",
|
|
path, (intmax_t) line_number, argv[0], argv[1]);
|
|
else if ( !strcmp(argv[2], "if") )
|
|
daemon_config->per_if = false;
|
|
else
|
|
warning("%s:%ji: %s %s: unknown: %s", path,
|
|
(intmax_t) line_number, argv[0], argv[1], argv[2]);
|
|
}
|
|
else if ( !strcmp(argv[1], "need") )
|
|
{
|
|
if ( argc < 3 )
|
|
warning("%s:%ji: expected parameter: %s: %s",
|
|
path, (intmax_t) line_number, argv[0], argv[1]);
|
|
else if ( !strcmp(argv[2], "tty") )
|
|
daemon_config->need_tty = false;
|
|
else
|
|
warning("%s:%ji: %s %s: unknown: %s", path,
|
|
(intmax_t) line_number, argv[0], argv[1], argv[2]);
|
|
}
|
|
else if ( !strcmp(argv[1], "require") )
|
|
{
|
|
if ( argc < 3 )
|
|
{
|
|
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);
|
|
daemon_config->dependencies_used = 0;
|
|
daemon_config->dependencies = NULL;
|
|
return true;
|
|
}
|
|
const char* target = argv[2];
|
|
// TODO: Linear time lookup.
|
|
struct dependency_config* dependency = NULL;
|
|
size_t i;
|
|
for ( i = 0; i < daemon_config->dependencies_used; i++ )
|
|
{
|
|
if ( !strcmp(daemon_config->dependencies[i]->target, target) )
|
|
{
|
|
dependency = daemon_config->dependencies[i];
|
|
break;
|
|
}
|
|
}
|
|
if ( !dependency )
|
|
{
|
|
warning("%s:%ji: dependency wasn't already required: %s",
|
|
path, (intmax_t) line_number, target);
|
|
return true;
|
|
}
|
|
if ( argc <= 3 )
|
|
{
|
|
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--;
|
|
}
|
|
else for ( size_t i = 3; i < argc; i++ )
|
|
{
|
|
if ( !strcmp(argv[i], "optional") )
|
|
dependency->flags |= DEPENDENCY_FLAG_REQUIRE;
|
|
else if ( !strcmp(argv[i], "no-await") )
|
|
dependency->flags |= DEPENDENCY_FLAG_AWAIT;
|
|
else if ( !strcmp(argv[i], "exit-code") )
|
|
dependency->flags &= ~DEPENDENCY_FLAG_EXIT_CODE;
|
|
else
|
|
warning("%s:%ji: %s %s %s: unknown flag: %s",
|
|
path, (intmax_t) line_number, argv[0], argv[1],
|
|
argv[2], argv[i]);
|
|
}
|
|
}
|
|
else if ( !strcmp(argv[1], "tty") )
|
|
{
|
|
// TODO: Implement.
|
|
}
|
|
else if ( !strcmp(argv[1], "type") )
|
|
daemon_config->type = TYPE_DAEMON;
|
|
else
|
|
warning("%s:%ji: unknown unset operation: %s",
|
|
path, (intmax_t) line_number, argv[0]);
|
|
}
|
|
else
|
|
warning("%s:%ji: unknown operation: %s",
|
|
path, (intmax_t) line_number, argv[0]);
|
|
return true;
|
|
}
|
|
|
|
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)
|
|
{
|
|
size_t argc = 0;
|
|
char** argv = tokenize(&argc, line);
|
|
if ( !argv )
|
|
{
|
|
if ( !errno )
|
|
warning("%s:%ji: syntax error", path, (intmax_t) line_number);
|
|
else
|
|
warning("%s: %m", path);
|
|
return false;
|
|
}
|
|
bool result = daemon_process_command(daemon_config, path, argc,
|
|
(const char* const*) argv, line_number,
|
|
next_search_path_index);
|
|
for ( size_t i = 0; i < argc; i++ )
|
|
free(argv[i]);
|
|
free(argv);
|
|
return result;
|
|
}
|
|
|
|
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 open 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);
|
|
if ( errno == ENOENT )
|
|
errno = EINVAL;
|
|
return false;
|
|
}
|
|
}
|
|
free(line);
|
|
if ( ferror(fp) )
|
|
{
|
|
warning("%s: %m", path);
|
|
fclose(fp);
|
|
return false;
|
|
}
|
|
fclose(fp);
|
|
return true;
|
|
}
|
|
|
|
static bool daemon_has_config(const char* name)
|
|
{
|
|
const char* search_paths[] =
|
|
{
|
|
etc_init_path,
|
|
share_init_path,
|
|
};
|
|
size_t search_paths_count = sizeof(search_paths) / sizeof(search_paths[0]);
|
|
for ( size_t i = 0; i < search_paths_count; i++ )
|
|
{
|
|
const char* search_path = search_paths[i];
|
|
char* path = join_paths(search_path, name);
|
|
if ( !path )
|
|
return false;
|
|
if ( access(path, F_OK) < 0 && errno == ENOENT )
|
|
{
|
|
free(path);
|
|
continue;
|
|
}
|
|
free(path);
|
|
return true;
|
|
}
|
|
errno = ENOENT;
|
|
return false;
|
|
}
|
|
|
|
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_path,
|
|
share_init_path,
|
|
};
|
|
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) )
|
|
{
|
|
free(path);
|
|
if ( errno == ENOENT )
|
|
continue;
|
|
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;
|
|
daemon_config->log_file_mode = default_config.log_file_mode;
|
|
}
|
|
|
|
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 bool communication_reserve(size_t required)
|
|
{
|
|
if ( pfds_length - pfds_used < required )
|
|
{
|
|
size_t old_length = pfds_length ? pfds_length : required;
|
|
struct pollfd* new_pfds =
|
|
reallocarray(pfds, old_length, 2 * sizeof(struct pollfd));
|
|
if ( !new_pfds )
|
|
return false;
|
|
pfds = new_pfds;
|
|
pfds_length = old_length * 2;
|
|
}
|
|
if ( communications_length - pfds_used < required )
|
|
{
|
|
size_t old_length =
|
|
communications_length ? communications_length : required;
|
|
struct communication* new_communications =
|
|
reallocarray(communications, old_length,
|
|
2 * sizeof(struct communication));
|
|
if ( !new_communications )
|
|
return false;
|
|
communications = new_communications;
|
|
communications_length = old_length * 2;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void communication_register(struct communication* comm,
|
|
int fd,
|
|
short events)
|
|
{
|
|
assert(pfds_used < pfds_length);
|
|
assert(pfds_used < communications_length);
|
|
size_t index = pfds_used++;
|
|
struct pollfd* pfd = pfds + index;
|
|
memset(pfd, 0, sizeof(*pfd));
|
|
pfd->fd = fd;
|
|
pfd->events = events;
|
|
communications[index] = *comm;
|
|
*communications[index].index_ptr = index;
|
|
}
|
|
|
|
static void communication_unregister(size_t index)
|
|
{
|
|
assert(pfds_used <= pfds_length);
|
|
assert(pfds_used <= communications_length);
|
|
assert(index < pfds_used);
|
|
assert(index < communications_length);
|
|
// 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));
|
|
communications[index] = communications[last_index];
|
|
*communications[index].index_ptr = index;
|
|
}
|
|
pfds_used--;
|
|
memset(pfds + last_index, 0, sizeof(*pfds));
|
|
communications[last_index].daemon = NULL;
|
|
}
|
|
|
|
// TODO: Replace with better data structure.
|
|
static struct daemon* add_daemon(void)
|
|
{
|
|
struct daemon* daemon = calloc(1, sizeof(struct daemon));
|
|
if ( !daemon )
|
|
fatal("malloc: %m");
|
|
if ( !array_add((void***) &daemons, &daemons_used, &daemons_length,
|
|
daemon) )
|
|
fatal("malloc: %m");
|
|
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) ||
|
|
4 <= 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_unconfigured(const char* name)
|
|
{
|
|
struct daemon* daemon = add_daemon();
|
|
if ( !(daemon->name = strdup(name)) )
|
|
fatal("malloc: %m");
|
|
daemon->state = DAEMON_STATE_TERMINATED;
|
|
daemon->readyfd = -1;
|
|
daemon->outputfd = -1;
|
|
daemon->log.fd = -1;
|
|
daemon_insert_state_list(daemon);
|
|
return daemon;
|
|
}
|
|
|
|
static struct daemon* daemon_find_or_create(const char* name)
|
|
{
|
|
struct daemon* daemon = daemon_find_by_name(name);
|
|
if ( !daemon && !daemon_has_config(name) && errno == ENOENT )
|
|
return NULL;
|
|
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 struct dependency* daemon_add_dependency(struct daemon* daemon,
|
|
struct daemon* target,
|
|
int flags)
|
|
{
|
|
struct dependency* dependency = calloc(1, sizeof(struct dependency));
|
|
if ( !dependency )
|
|
return false;
|
|
dependency->source = daemon;
|
|
dependency->target = target;
|
|
dependency->flags = flags;
|
|
if ( !array_add((void***) &daemon->dependencies,
|
|
&daemon->dependencies_used,
|
|
&daemon->dependencies_length,
|
|
dependency) )
|
|
{
|
|
free(dependency);
|
|
return false;
|
|
}
|
|
if ( !array_add((void***) &target->dependents,
|
|
&target->dependents_used,
|
|
&target->dependents_length,
|
|
dependency) )
|
|
{
|
|
daemon->dependencies_used--;
|
|
free(dependency);
|
|
return false;
|
|
}
|
|
if ( flags & DEPENDENCY_FLAG_EXIT_CODE )
|
|
daemon->exit_code_from = dependency;
|
|
return dependency;
|
|
}
|
|
|
|
static void daemon_unconfigure(struct daemon* daemon)
|
|
{
|
|
assert(daemon->state == DAEMON_STATE_TERMINATED ||
|
|
daemon->state == DAEMON_STATE_FINISHED);
|
|
for ( size_t i = 0; i < daemon->dependencies_used; i++ )
|
|
{
|
|
struct dependency* dependency = daemon->dependencies[i];
|
|
assert(dependency->status == 0);
|
|
struct daemon* target = dependency->target;
|
|
for ( size_t n = 0; n < target->dependents_used; n++ )
|
|
{
|
|
if ( target->dependents[n] != dependency )
|
|
continue;
|
|
size_t last = --target->dependents_used;
|
|
if ( n != last )
|
|
target->dependents[n] = target->dependents[last];
|
|
break;
|
|
}
|
|
free(dependency);
|
|
}
|
|
free(daemon->dependencies);
|
|
daemon->dependencies = NULL;
|
|
daemon->dependencies_used = 0;
|
|
daemon->dependencies_length = 0;
|
|
daemon->dependencies_ready = 0;
|
|
daemon->dependencies_finished = 0;
|
|
daemon->dependencies_failed = 0;
|
|
daemon->exit_code_from = NULL;
|
|
free(daemon->cd);
|
|
daemon->cd = NULL;
|
|
for ( int i = 0; i < daemon->argc; i++ )
|
|
free(daemon->argv[i]);
|
|
free(daemon->argv);
|
|
log_deinitialize(&daemon->log);
|
|
daemon->exit_code_meaning = EXIT_CODE_MEANING_DEFAULT;
|
|
daemon->type = TYPE_DAEMON;
|
|
daemon->echo = false;
|
|
daemon->need_tty = false;
|
|
daemon->configured = false;
|
|
daemon->want_reconfigure = false;
|
|
}
|
|
|
|
static void daemon_configure_sub(struct daemon* daemon,
|
|
struct daemon_config* daemon_config,
|
|
const char* netif)
|
|
{
|
|
assert(!daemon->configured);
|
|
daemon->dependencies = (struct dependency**)
|
|
reallocarray(NULL, daemon_config->dependencies_used,
|
|
sizeof(struct dependency*));
|
|
if ( !daemon->dependencies )
|
|
fatal("malloc: %m");
|
|
daemon->dependencies_used = 0;
|
|
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 daemon* target = daemon_find_by_name(dependency_config->target);
|
|
if ( !target )
|
|
target = daemon_create_unconfigured(dependency_config->target);
|
|
if ( target->netif )
|
|
{
|
|
// daemon_find_by_name cannot create daemons per if.
|
|
warning("%s cannot depend on parameterized daemon %s",
|
|
daemon->name, target->name);
|
|
continue;
|
|
}
|
|
if ( !daemon_add_dependency(daemon, target, dependency_config->flags) )
|
|
fatal("malloc: %m");
|
|
}
|
|
if ( daemon_config->cd && !(daemon->cd = strdup(daemon_config->cd)) )
|
|
fatal("malloc: %m");
|
|
if ( daemon_config->argv )
|
|
{
|
|
daemon->argc = daemon_config->argc;
|
|
if ( netif )
|
|
{
|
|
if ( INT_MAX - 1 <= daemon->argc )
|
|
{
|
|
errno = ENOMEM;
|
|
fatal("malloc: %m");
|
|
}
|
|
daemon->argc++;
|
|
}
|
|
daemon->argv = calloc(daemon->argc + 1, sizeof(char*));
|
|
if ( !daemon->argv )
|
|
fatal("malloc: %m");
|
|
for ( int i = 0; i < daemon_config->argc; i++ )
|
|
if ( !(daemon->argv[i] = strdup(daemon_config->argv[i])) )
|
|
fatal("malloc: %m");
|
|
if ( netif && !(daemon->argv[daemon_config->argc] = strdup(netif)) )
|
|
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->echo = daemon_config->echo;
|
|
daemon->need_tty = daemon_config->need_tty;
|
|
daemon->type = daemon_config->type;
|
|
daemon->configured = true;
|
|
}
|
|
|
|
static void daemon_configure(struct daemon* daemon,
|
|
struct daemon_config* daemon_config)
|
|
{
|
|
if ( daemon_config->per_if )
|
|
{
|
|
struct if_nameindex* ifs = if_nameindex();
|
|
if ( !ifs )
|
|
fatal("if_nameindex: %m");
|
|
for ( size_t i = 0; ifs[i].if_name; i++ )
|
|
{
|
|
const char* netif = ifs[i].if_name;
|
|
char* parameterized_name;
|
|
if ( asprintf(¶meterized_name, "%s.%s",
|
|
daemon_config->name, netif) < 0 )
|
|
fatal("malloc: %m");
|
|
struct daemon* parameterized =
|
|
daemon_find_by_name(parameterized_name);
|
|
if ( !parameterized )
|
|
parameterized = daemon_create_unconfigured(parameterized_name);
|
|
free(parameterized_name);
|
|
if ( !parameterized->configured )
|
|
{
|
|
if ( !(parameterized->netif = strdup(netif)) )
|
|
fatal("malloc: %m");
|
|
daemon_configure_sub(parameterized, daemon_config, netif);
|
|
}
|
|
int flags = DEPENDENCY_FLAG_REQUIRE | DEPENDENCY_FLAG_AWAIT;
|
|
if ( !daemon_add_dependency(daemon, parameterized, flags) )
|
|
fatal("malloc: %m");
|
|
}
|
|
if_freenameindex(ifs);
|
|
daemon->configured = true;
|
|
}
|
|
else
|
|
daemon_configure_sub(daemon, daemon_config, NULL);
|
|
}
|
|
|
|
static struct daemon* daemon_create(struct daemon_config* daemon_config)
|
|
{
|
|
struct daemon* daemon = daemon_create_unconfigured(daemon_config->name);
|
|
daemon_configure(daemon, daemon_config);
|
|
return 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_reset_dependency(struct dependency* dependency)
|
|
{
|
|
if ( dependency->status & DEPENDENCY_STATUS_READY )
|
|
{
|
|
dependency->source->dependencies_ready--;
|
|
dependency->status &= ~(DEPENDENCY_STATUS_READY);
|
|
}
|
|
if ( dependency->status & DEPENDENCY_STATUS_FINISHED )
|
|
{
|
|
dependency->source->dependencies_finished--;
|
|
dependency->status &= ~(DEPENDENCY_STATUS_FINISHED);
|
|
}
|
|
if ( dependency->status & DEPENDENCY_STATUS_FAILED )
|
|
{
|
|
dependency->source->dependencies_failed--;
|
|
dependency->status &= ~(DEPENDENCY_STATUS_FAILED);
|
|
}
|
|
}
|
|
|
|
static void daemon_on_finished(struct daemon* daemon)
|
|
{
|
|
assert(daemon->state != DAEMON_STATE_FINISHING);
|
|
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_change_state_list(daemon, DAEMON_STATE_FINISHING);
|
|
}
|
|
|
|
static void daemon_terminate(struct daemon* daemon)
|
|
{
|
|
if ( daemon->state != DAEMON_STATE_SCHEDULED &&
|
|
daemon->state != DAEMON_STATE_SATISFIED &&
|
|
daemon->state != DAEMON_STATE_STARTING &&
|
|
daemon->state != DAEMON_STATE_RUNNING )
|
|
return;
|
|
if ( daemon->was_terminated )
|
|
return;
|
|
daemon->was_terminated = true;
|
|
daemon->want_restart = false;
|
|
daemon->want_reload = false;
|
|
daemon_change_state_list(daemon, DAEMON_STATE_TERMINATING);
|
|
if ( 0 < daemon->pid )
|
|
{
|
|
log_status("stopping", "Stopping %s...\n", daemon->name);
|
|
kill(daemon->pid, SIGTERM);
|
|
struct timespec now;
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
daemon->timeout = timespec_add(now, timespec_make(30, 0));
|
|
daemon->timeout_set = true;
|
|
}
|
|
else
|
|
daemon_on_finished(daemon);
|
|
}
|
|
|
|
static void daemon_reload(struct daemon* daemon)
|
|
{
|
|
if ( daemon->state == DAEMON_STATE_STARTING )
|
|
daemon->want_reload = true;
|
|
else if ( daemon->state == DAEMON_STATE_RUNNING )
|
|
{
|
|
if ( 0 < daemon->pid )
|
|
{
|
|
log_status("reload", "Reloading %s.\n", daemon->name);
|
|
kill(daemon->pid, SIGHUP); // TODO: Configuration.
|
|
}
|
|
daemon->want_reload = false;
|
|
}
|
|
}
|
|
|
|
static void daemon_signal(struct daemon* daemon, const char* signame, int sig)
|
|
{
|
|
if ( daemon->state != DAEMON_STATE_STARTING &&
|
|
daemon->state != DAEMON_STATE_RUNNING &&
|
|
daemon->state != DAEMON_STATE_TERMINATING )
|
|
return;
|
|
|
|
if ( 0 < daemon->pid )
|
|
{
|
|
log_status("signal", "Sending %s to %s.\n", signame, daemon->name);
|
|
kill(daemon->pid, sig);
|
|
}
|
|
}
|
|
|
|
static void daemon_on_not_referenced(struct daemon* daemon)
|
|
{
|
|
assert(daemon->reference_count == 0);
|
|
daemon->want_restart = false;
|
|
daemon->want_reload = false;
|
|
switch ( daemon->state )
|
|
{
|
|
case DAEMON_STATE_TERMINATED:
|
|
break;
|
|
case DAEMON_STATE_SCHEDULED:
|
|
case DAEMON_STATE_WAITING:
|
|
case DAEMON_STATE_SATISFIED:
|
|
daemon_change_state_list(daemon, DAEMON_STATE_FINISHING);
|
|
break;
|
|
case DAEMON_STATE_STARTING:
|
|
case DAEMON_STATE_RUNNING:
|
|
daemon_terminate(daemon);
|
|
// Dependencies are dereferenced when the daemon terminates.
|
|
break;
|
|
case DAEMON_STATE_TERMINATING:
|
|
case DAEMON_STATE_FINISHING:
|
|
case DAEMON_STATE_FINISHED:
|
|
// Dependencies are dereferenced when the daemon terminates.
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void daemon_dereference(struct daemon* daemon)
|
|
{
|
|
assert(0 < daemon->reference_count);
|
|
daemon->reference_count--;
|
|
if ( !daemon->reference_count )
|
|
daemon_on_not_referenced(daemon);
|
|
}
|
|
|
|
static void daemon_dereference_dependency(struct dependency* dependency)
|
|
{
|
|
if ( dependency->status & DEPENDENCY_STATUS_REFERENCED )
|
|
{
|
|
daemon_reset_dependency(dependency);
|
|
daemon_dereference(dependency->target);
|
|
dependency->status &= ~(DEPENDENCY_STATUS_REFERENCED);
|
|
}
|
|
else
|
|
assert(dependency->status == 0);
|
|
}
|
|
|
|
static void daemon_dereference_dependencies(struct daemon* daemon)
|
|
{
|
|
for ( size_t i = 0; i < daemon->dependencies_used; i++ )
|
|
daemon_dereference_dependency(daemon->dependencies[i]);
|
|
}
|
|
|
|
static void daemon_consider_satisfied(struct daemon* daemon)
|
|
{
|
|
if ( daemon->state == DAEMON_STATE_WAITING &&
|
|
daemon->dependencies_ready == daemon->dependencies_used )
|
|
daemon_change_state_list(daemon, DAEMON_STATE_SATISFIED);
|
|
}
|
|
|
|
static void daemon_on_dependency_ready(struct dependency* dependency)
|
|
{
|
|
struct daemon* daemon = dependency->source;
|
|
if ( !(dependency->status & DEPENDENCY_STATUS_READY) )
|
|
{
|
|
daemon->dependencies_ready++;
|
|
dependency->status |= DEPENDENCY_STATUS_READY;
|
|
}
|
|
daemon_consider_satisfied(daemon);
|
|
}
|
|
|
|
static void daemon_mark_ready(struct daemon* daemon)
|
|
{
|
|
daemon->was_ready = true;
|
|
for ( size_t i = 0; i < daemon->dependents_used; i++ )
|
|
{
|
|
if ( (daemon->dependents[i]->flags & DEPENDENCY_FLAG_AWAIT) )
|
|
daemon_on_dependency_ready(daemon->dependents[i]);
|
|
}
|
|
}
|
|
|
|
static void daemon_on_ready(struct daemon* daemon)
|
|
{
|
|
log_status("started", "Started %s.\n", daemon->name);
|
|
daemon_change_state_list(daemon, DAEMON_STATE_RUNNING);
|
|
if ( daemon->want_reload )
|
|
daemon_reload(daemon);
|
|
daemon_mark_ready(daemon);
|
|
}
|
|
|
|
static void daemon_on_dependency_finished(struct dependency* dependency)
|
|
{
|
|
struct daemon* daemon = dependency->source;
|
|
struct daemon* target = dependency->target;
|
|
if ( target->state != DAEMON_STATE_FINISHED )
|
|
return;
|
|
bool failed = (dependency->flags & DEPENDENCY_FLAG_REQUIRE) &&
|
|
daemon_is_failed(target);
|
|
if ( !(dependency->status & DEPENDENCY_STATUS_FINISHED) )
|
|
{
|
|
daemon->dependencies_finished++;
|
|
dependency->status |= DEPENDENCY_STATUS_FINISHED;
|
|
}
|
|
if ( failed && !(dependency->status & DEPENDENCY_STATUS_FAILED) )
|
|
{
|
|
daemon->dependencies_failed++;
|
|
dependency->status |= DEPENDENCY_STATUS_FAILED;
|
|
}
|
|
if ( daemon->state == DAEMON_STATE_FINISHING ||
|
|
daemon->state == DAEMON_STATE_FINISHED )
|
|
return;
|
|
// Don't stop a running non-virtual daemon if dependencies failed.
|
|
if ( daemon->argv &&
|
|
(daemon->state == DAEMON_STATE_STARTING ||
|
|
daemon->state == DAEMON_STATE_RUNNING ||
|
|
daemon->state == DAEMON_STATE_TERMINATING) )
|
|
return;
|
|
if ( daemon->exit_code_from &&
|
|
(dependency->flags & DEPENDENCY_FLAG_EXIT_CODE) )
|
|
{
|
|
daemon->exit_code = target->exit_code;
|
|
daemon->exit_code_meaning = target->exit_code_meaning;
|
|
daemon_on_finished(daemon);
|
|
return;
|
|
}
|
|
if ( failed )
|
|
daemon->exit_code = WCONSTRUCT(WNATURE_EXITED, 3, 0);
|
|
if ( failed ||
|
|
(!daemon->argv && daemon->type == TYPE_ONESHOT &&
|
|
daemon->dependencies_finished == daemon->dependencies_used) )
|
|
daemon_on_finished(daemon);
|
|
}
|
|
|
|
static void daemon_reset(struct daemon* daemon, enum daemon_state new_state)
|
|
{
|
|
assert(new_state == DAEMON_STATE_TERMINATED ||
|
|
new_state == DAEMON_STATE_FINISHED);
|
|
daemon_change_state_list(daemon, new_state);
|
|
for ( size_t i = 0; i < daemon->dependents_used; i++ )
|
|
{
|
|
if ( new_state == DAEMON_STATE_TERMINATED )
|
|
daemon_reset_dependency(daemon->dependents[i]);
|
|
else if ( new_state == DAEMON_STATE_FINISHED )
|
|
daemon_on_dependency_finished(daemon->dependents[i]);
|
|
}
|
|
daemon->was_ready = false;
|
|
daemon->was_terminated = false;
|
|
daemon->timeout_set = false;
|
|
}
|
|
|
|
static void daemon_request_restart(struct daemon* daemon)
|
|
{
|
|
if ( !daemon->reference_count )
|
|
return;
|
|
if ( daemon->state == DAEMON_STATE_SCHEDULED ||
|
|
daemon->state == DAEMON_STATE_WAITING ||
|
|
daemon->state == DAEMON_STATE_SATISFIED )
|
|
return;
|
|
if ( !daemon->want_restart )
|
|
{
|
|
log_status("restart", "Restart requested of %s.\n", daemon->name);
|
|
if ( daemon->state == DAEMON_STATE_STARTING ||
|
|
daemon->state == DAEMON_STATE_RUNNING )
|
|
daemon_terminate(daemon);
|
|
daemon->want_restart = true;
|
|
}
|
|
if ( daemon->state == DAEMON_STATE_TERMINATED ||
|
|
daemon->state == DAEMON_STATE_FINISHED )
|
|
{
|
|
log_status("restarting", "Restarting %s.\n", daemon->name);
|
|
daemon_reset(daemon, DAEMON_STATE_TERMINATED);
|
|
daemon->want_restart = false;
|
|
}
|
|
if ( daemon->state == DAEMON_STATE_TERMINATED )
|
|
daemon_schedule(daemon);
|
|
}
|
|
|
|
static void daemon_on_startup_error(struct daemon* daemon)
|
|
{
|
|
assert(daemon->state != DAEMON_STATE_FINISHING);
|
|
assert(daemon->state != DAEMON_STATE_FINISHED);
|
|
daemon_change_state_list(daemon, DAEMON_STATE_FINISHING);
|
|
if ( daemon->want_restart )
|
|
daemon_request_restart(daemon);
|
|
}
|
|
|
|
static bool daemon_load_and_configure(struct daemon* daemon)
|
|
{
|
|
assert(!daemon->configured);
|
|
struct daemon_config* daemon_config = daemon_config_load(daemon->name);
|
|
if ( !daemon_config )
|
|
{
|
|
log_status("failed", "Failed to load configuration for %s.\n",
|
|
daemon->name);
|
|
daemon->exit_code = WCONSTRUCT(WNATURE_EXITED, 3, 0);
|
|
daemon->want_restart = false;
|
|
daemon_on_startup_error(daemon);
|
|
return false;
|
|
}
|
|
daemon_configure(daemon, daemon_config);
|
|
daemon_config_free(daemon_config);
|
|
return true;
|
|
}
|
|
|
|
static void daemon_reconfigure(struct daemon* daemon)
|
|
{
|
|
assert(!daemon->netif);
|
|
assert(daemon->state == DAEMON_STATE_TERMINATED ||
|
|
daemon->state == DAEMON_STATE_FINISHED);
|
|
log_status("reconfiguring", "Reconfiguring %s.\n", daemon->name);
|
|
daemon_unconfigure(daemon);
|
|
daemon_load_and_configure(daemon);
|
|
}
|
|
|
|
static void daemon_request_reconfigure(struct daemon* daemon)
|
|
{
|
|
// TODO: Support reconfiguring instantiated daemons.
|
|
if ( daemon->netif )
|
|
return;
|
|
if ( !daemon->want_reconfigure )
|
|
{
|
|
log_status("reconfigure", "Reconfigure requested of %s.\n", daemon->name);
|
|
bool should_run =
|
|
daemon->state == DAEMON_STATE_WAITING ||
|
|
daemon->state == DAEMON_STATE_SATISFIED ||
|
|
daemon->state == DAEMON_STATE_STARTING ||
|
|
daemon->state == DAEMON_STATE_RUNNING;
|
|
if ( daemon->state == DAEMON_STATE_STARTING ||
|
|
daemon->state == DAEMON_STATE_RUNNING )
|
|
daemon_terminate(daemon);
|
|
daemon->want_reconfigure = true;
|
|
daemon->want_restart = should_run;
|
|
}
|
|
if ( daemon->state == DAEMON_STATE_TERMINATED ||
|
|
daemon->state == DAEMON_STATE_FINISHED )
|
|
daemon_reconfigure(daemon);
|
|
}
|
|
|
|
static void daemon_kill(struct daemon* daemon)
|
|
{
|
|
if ( daemon->state != DAEMON_STATE_SCHEDULED &&
|
|
daemon->state != DAEMON_STATE_SATISFIED &&
|
|
daemon->state != DAEMON_STATE_STARTING &&
|
|
daemon->state != DAEMON_STATE_RUNNING &&
|
|
daemon->state != DAEMON_STATE_TERMINATING )
|
|
return;
|
|
daemon->was_terminated = true;
|
|
daemon->want_restart = false;
|
|
daemon->want_reload = false;
|
|
daemon_change_state_list(daemon, DAEMON_STATE_TERMINATING);
|
|
if ( 0 < daemon->pid )
|
|
{
|
|
log_status("killing", "Killing %s.\n", daemon->name);
|
|
kill(daemon->pid, SIGKILL);
|
|
daemon->timeout_set = false;
|
|
}
|
|
else
|
|
daemon_on_finished(daemon);
|
|
}
|
|
|
|
static void daemon_on_referenced(struct daemon* daemon)
|
|
{
|
|
switch ( daemon->state )
|
|
{
|
|
case DAEMON_STATE_TERMINATED:
|
|
daemon_schedule(daemon);
|
|
break;
|
|
case DAEMON_STATE_SCHEDULED:
|
|
case DAEMON_STATE_WAITING:
|
|
case DAEMON_STATE_SATISFIED:
|
|
case DAEMON_STATE_STARTING:
|
|
case DAEMON_STATE_RUNNING:
|
|
break;
|
|
case DAEMON_STATE_TERMINATING:
|
|
case DAEMON_STATE_FINISHING:
|
|
if ( daemon->type == TYPE_DAEMON )
|
|
daemon_request_restart(daemon);
|
|
break;
|
|
case DAEMON_STATE_FINISHED:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void daemon_reference(struct daemon* daemon)
|
|
{
|
|
daemon->reference_count++;
|
|
if ( daemon->reference_count == 1 )
|
|
daemon_on_referenced(daemon);
|
|
}
|
|
|
|
static void daemon_reference_dependency(struct dependency* dependency)
|
|
{
|
|
if ( !(dependency->status & DEPENDENCY_STATUS_REFERENCED) )
|
|
{
|
|
assert(dependency->status == 0);
|
|
daemon_reference(dependency->target);
|
|
dependency->status |= DEPENDENCY_STATUS_REFERENCED;
|
|
}
|
|
}
|
|
|
|
static void daemon_wait_dependency(struct dependency* dependency)
|
|
{
|
|
daemon_reference_dependency(dependency);
|
|
switch ( dependency->target->state )
|
|
{
|
|
case DAEMON_STATE_TERMINATED:
|
|
case DAEMON_STATE_SCHEDULED:
|
|
case DAEMON_STATE_WAITING:
|
|
case DAEMON_STATE_SATISFIED:
|
|
case DAEMON_STATE_STARTING:
|
|
if ( !(dependency->flags & DEPENDENCY_FLAG_AWAIT) )
|
|
daemon_on_dependency_ready(dependency);
|
|
break;
|
|
case DAEMON_STATE_RUNNING:
|
|
daemon_on_dependency_ready(dependency);
|
|
break;
|
|
case DAEMON_STATE_TERMINATING:
|
|
case DAEMON_STATE_FINISHING:
|
|
if ( dependency->target->type == TYPE_ONESHOT )
|
|
daemon_on_dependency_ready(dependency);
|
|
else if ( !(dependency->flags & DEPENDENCY_FLAG_AWAIT) )
|
|
daemon_on_dependency_ready(dependency);
|
|
break;
|
|
case DAEMON_STATE_FINISHED:
|
|
daemon_on_dependency_ready(dependency);
|
|
daemon_on_dependency_finished(dependency);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static bool daemon_depend(struct daemon* source,
|
|
struct daemon* target,
|
|
int flags)
|
|
{
|
|
if ( !source->configured )
|
|
return false;
|
|
struct dependency* dependency =
|
|
daemon_find_dependency(source, target->name);
|
|
// TODO: Updating the flags is tricky.
|
|
if ( dependency )
|
|
return true;
|
|
else if ( !(dependency = daemon_add_dependency(source, target, flags)) )
|
|
return false;
|
|
// No need to start the target if the source isn't supposed to be running,
|
|
// and no need if the source is scheduled (it will be done later).
|
|
if ( source->state == DAEMON_STATE_TERMINATED ||
|
|
source->state == DAEMON_STATE_SCHEDULED ||
|
|
source->state == DAEMON_STATE_TERMINATING ||
|
|
source->state == DAEMON_STATE_FINISHING ||
|
|
source->state == DAEMON_STATE_FINISHED )
|
|
return true;
|
|
// Return source to WAITING if the target isn't ready.
|
|
if ( source->state == DAEMON_STATE_SATISFIED &&
|
|
(target->state == DAEMON_STATE_TERMINATED ||
|
|
target->state == DAEMON_STATE_SCHEDULED ||
|
|
target->state == DAEMON_STATE_SATISFIED ||
|
|
target->state == DAEMON_STATE_STARTING) )
|
|
daemon_change_state_list(target, DAEMON_STATE_WAITING);
|
|
// Wait on the new dependency.
|
|
daemon_wait_dependency(dependency);
|
|
return true;
|
|
}
|
|
|
|
static void daemon_undepend(struct daemon* source, struct daemon* target)
|
|
{
|
|
for ( size_t i = 0; i < source->dependencies_used; i++ )
|
|
{
|
|
if ( source->dependencies[i]->target != target )
|
|
continue;
|
|
struct dependency* dependency = source->dependencies[i];
|
|
size_t last = --source->dependencies_used;
|
|
if ( i != last )
|
|
source->dependencies[i] = source->dependencies[last];
|
|
for ( size_t n = 0; n < target->dependents_used; n++ )
|
|
{
|
|
if ( target->dependents[n] != dependency )
|
|
continue;
|
|
last = --target->dependents_used;
|
|
if ( n != last )
|
|
target->dependents[n] = target->dependents[last];
|
|
break;
|
|
}
|
|
daemon_dereference_dependency(dependency);
|
|
if ( dependency->flags & DEPENDENCY_FLAG_EXIT_CODE )
|
|
dependency->source->exit_code_from = NULL;
|
|
free(dependency);
|
|
daemon_consider_satisfied(source);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void daemon_finish(struct daemon* daemon)
|
|
{
|
|
assert(daemon->state == DAEMON_STATE_FINISHING);
|
|
if ( !daemon->was_ready )
|
|
daemon_mark_ready(daemon);
|
|
enum daemon_state new_state =
|
|
daemon->type == TYPE_ONESHOT ||
|
|
daemon->exit_code_meaning == EXIT_CODE_MEANING_POWEROFF_REBOOT ||
|
|
(daemon->type == TYPE_DAEMON && !daemon->was_terminated &&
|
|
daemon->exit_code_meaning == EXIT_CODE_MEANING_DEFAULT &&
|
|
WIFEXITED(daemon->exit_code) && WEXITSTATUS(daemon->exit_code) == 0) ||
|
|
daemon_is_failed(daemon) ||
|
|
daemon->exit_code_from ?
|
|
DAEMON_STATE_FINISHED : DAEMON_STATE_TERMINATED;
|
|
daemon_reset(daemon, new_state);
|
|
if ( daemon->want_reconfigure )
|
|
{
|
|
daemon_dereference_dependencies(daemon);
|
|
daemon_reconfigure(daemon);
|
|
}
|
|
if ( daemon->want_restart )
|
|
{
|
|
daemon_request_restart(daemon);
|
|
return;
|
|
}
|
|
daemon_dereference_dependencies(daemon);
|
|
}
|
|
|
|
static void daemon_wait(struct daemon* daemon)
|
|
{
|
|
assert(daemon->state == DAEMON_STATE_SCHEDULED);
|
|
if ( !daemon->configured && !daemon_load_and_configure(daemon) )
|
|
return;
|
|
if ( daemon->dependencies_ready == daemon->dependencies_used )
|
|
daemon_change_state_list(daemon, DAEMON_STATE_SATISFIED);
|
|
else
|
|
daemon_change_state_list(daemon, DAEMON_STATE_WAITING);
|
|
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];
|
|
assert(dependency->source == daemon);
|
|
assert(dependency->target);
|
|
daemon_wait_dependency(dependency);
|
|
if ( daemon->state != DAEMON_STATE_WAITING )
|
|
break;
|
|
}
|
|
}
|
|
|
|
noreturn static void exit_errfd(int errfd, const char* action)
|
|
{
|
|
int errnum = errno;
|
|
write(errfd, &errnum, sizeof(errnum));
|
|
write(errfd, action, strlen(action));
|
|
_exit(127);
|
|
}
|
|
|
|
static void daemon_start(struct daemon* daemon)
|
|
{
|
|
assert(daemon->state == DAEMON_STATE_SATISFIED);
|
|
if ( !daemon->argv )
|
|
{
|
|
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->exit_code_meaning = target->exit_code_meaning;
|
|
daemon_on_finished(daemon);
|
|
}
|
|
}
|
|
else if ( daemon->type == TYPE_ONESHOT &&
|
|
daemon->dependencies_finished == daemon->dependencies_used )
|
|
daemon_on_finished(daemon);
|
|
return;
|
|
}
|
|
if ( 0 < daemon->dependencies_failed )
|
|
{
|
|
log_status("failed", "Failed to start %s due to failed dependencies.\n",
|
|
daemon->name);
|
|
daemon->exit_code = WCONSTRUCT(WNATURE_EXITED, 3, 0);
|
|
daemon_on_startup_error(daemon);
|
|
return;
|
|
}
|
|
log_status("starting", "Starting %s...\n", daemon->name);
|
|
uid_t uid = getuid();
|
|
pid_t ppid = getpid();
|
|
errno = 0;
|
|
struct passwd* pwd = getpwuid(uid);
|
|
if ( !pwd && errno )
|
|
fatal("looking up user by uid %" PRIuUID ": %m", uid);
|
|
else if ( !pwd )
|
|
fatal("no such user with uid: %" PRIuUID, 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;
|
|
int outputfds[2];
|
|
int readyfds[2];
|
|
if ( !daemon->need_tty )
|
|
{
|
|
if ( !communication_reserve(2) )
|
|
fatal("malloc");
|
|
if ( !log_begin(&daemon->log) )
|
|
{
|
|
// TODO: Mode where daemons are stopped if logging fails.
|
|
}
|
|
if ( pipe(outputfds) < 0 )
|
|
fatal("pipe");
|
|
daemon->outputfd = outputfds[0];
|
|
fcntl(daemon->outputfd, F_SETFL, O_NONBLOCK);
|
|
// Setup the pollfd for the outputfd.
|
|
struct communication output_comm;
|
|
output_comm.type = COMMUNICATION_TYPE_OUTPUT;
|
|
output_comm.index_ptr = &daemon->pfd_outputfd_index;
|
|
output_comm.daemon = daemon;
|
|
communication_register(&output_comm, daemon->outputfd, 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.
|
|
struct communication ready_comm;
|
|
ready_comm.type = COMMUNICATION_TYPE_READY;
|
|
ready_comm.index_ptr = &daemon->pfd_readyfd_index;
|
|
ready_comm.daemon = daemon;
|
|
communication_register(&ready_comm, daemon->readyfd, 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("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");
|
|
int errfds[2];
|
|
if ( pipe2(errfds, O_CLOEXEC) < 0 )
|
|
fatal("pipe");
|
|
if ( daemon->need_tty )
|
|
tty_gifted = true;
|
|
daemon->pid = daemon->log.pid = fork();
|
|
if ( daemon->pid < 0 )
|
|
fatal("fork: %m");
|
|
if ( daemon->need_tty )
|
|
{
|
|
if ( tcgetattr(tty_fd, &daemon->oldtio) )
|
|
fatal("tcgetattr: %m");
|
|
}
|
|
if ( daemon->pid == 0 )
|
|
{
|
|
uninstall_signal_handler();
|
|
close(errfds[0]);
|
|
if ( setsid() < 0 )
|
|
exit_errfd(errfds[1], "setsid");
|
|
if ( chdir(cd) < 0 )
|
|
exit_errfd(errfds[1], "chdir");
|
|
if ( daemon->need_tty )
|
|
{
|
|
if ( ioctl(tty_fd, TIOCSCTTY, 1) < 0 )
|
|
exit_errfd(errfds[1], "TIOCSCTTY");
|
|
daemon->oldtio.c_cflag |= CREAD;
|
|
if ( tcsetattr(tty_fd, TCSANOW, &daemon->oldtio) < 0 )
|
|
exit_errfd(errfds[1], "tcsetattr");
|
|
dup3(errfds[1], 3, O_CLOEXEC);
|
|
closefrom(4);
|
|
}
|
|
else
|
|
{
|
|
close(0);
|
|
close(1);
|
|
close(2);
|
|
if ( open("/dev/null", O_RDONLY) < 0 )
|
|
exit_errfd(errfds[1], "open");
|
|
// The lowest available file descriptor is always allocated, and
|
|
// because outputfds is allocated first, then readyfds, and finally
|
|
// errfds, then it's safe to move them downwards in that order.
|
|
dup2(outputfds[1], 1);
|
|
dup2(outputfds[1], 2);
|
|
dup2(readyfds[1], 3);
|
|
dup3(errfds[1], 4, O_CLOEXEC);
|
|
closefrom(5);
|
|
}
|
|
// TODO: This is a hack.
|
|
if ( !strcmp(daemon->argv[0], "$SHELL") )
|
|
daemon->argv[0] = (char*) shell;
|
|
execvp(daemon->argv[0], daemon->argv);
|
|
exit_errfd(4, "execve");
|
|
}
|
|
if ( !daemon->need_tty )
|
|
{
|
|
close(outputfds[1]);
|
|
close(readyfds[1]);
|
|
}
|
|
close(errfds[1]);
|
|
int errnum;
|
|
if ( read(errfds[0], &errnum, sizeof(errnum)) == sizeof(errnum) )
|
|
{
|
|
char action[16] = "";
|
|
ssize_t amount = read(errfds[0], action, sizeof(action) - 1);
|
|
if ( 0 <= amount )
|
|
action[amount] = '\0';
|
|
errno = errnum;
|
|
// TODO: Write control messages to the daemon log.
|
|
if ( !strcmp(action, "chdir") )
|
|
warning("Failed to start %s: %s: %s: %m",
|
|
daemon->name, action, cd);
|
|
else if ( !strcmp(action, "open") )
|
|
warning("Failed to start %s: %s: %s: %m",
|
|
daemon->name, action, "/dev/null");
|
|
else if ( !strcmp(action, "execve") )
|
|
warning("Failed to start %s: %s: %s: %m",
|
|
daemon->name, action, daemon->argv[0]);
|
|
else
|
|
warning("Failed to start %s: %s: %m", daemon->name, action);
|
|
}
|
|
close(errfds[0]);
|
|
// TODO: Not thread safe.
|
|
// TODO: Also unset other things.
|
|
if ( !daemon->need_tty )
|
|
unsetenv("READYFD");
|
|
unsetenv("LOGNAME");
|
|
unsetenv("USER");
|
|
unsetenv("HOME");
|
|
unsetenv("SHELL");
|
|
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_on_ready_event(struct daemon* daemon, int revents)
|
|
{
|
|
if ( (revents & (POLLIN | POLLHUP)) && !daemon_process_ready(daemon) )
|
|
{
|
|
communication_unregister(daemon->pfd_readyfd_index);
|
|
close(daemon->readyfd);
|
|
daemon->readyfd = -1;
|
|
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);
|
|
if ( daemon->echo && !tty_gifted )
|
|
writeall(1, data, amount);
|
|
return true;
|
|
}
|
|
|
|
static bool daemon_on_output_event(struct daemon* daemon, int revents)
|
|
{
|
|
if ( (revents & (POLLIN | POLLHUP)) && !daemon_process_output(daemon) )
|
|
{
|
|
communication_unregister(daemon->pfd_outputfd_index);
|
|
close(daemon->outputfd);
|
|
daemon->outputfd = -1;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void daemon_on_exit(struct daemon* daemon, int exit_code)
|
|
{
|
|
assert(daemon->state != DAEMON_STATE_FINISHING);
|
|
assert(daemon->state != DAEMON_STATE_FINISHED);
|
|
daemon->pid = 0;
|
|
daemon->exit_code = exit_code;
|
|
if ( 0 <= daemon->readyfd )
|
|
{
|
|
communication_unregister(daemon->pfd_readyfd_index);
|
|
close(daemon->readyfd);
|
|
daemon->readyfd = -1;
|
|
}
|
|
if ( 0 <= daemon->outputfd )
|
|
{
|
|
daemon_process_output(daemon);
|
|
communication_unregister(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(tty_fd, TCSAFLUSH, &daemon->oldtio) )
|
|
fatal("tcsetattr: %m");
|
|
// TODO: Do this in niht too?
|
|
if ( ioctl(tty_fd, TIOCSCTTY, 1) < 0 )
|
|
fatal("TIOCSCTTY: %m");
|
|
tty_gifted = false;
|
|
sigprocmask(SIG_SETMASK, &oldset, NULL);
|
|
}
|
|
daemon_on_finished(daemon);
|
|
}
|
|
|
|
static void daemon_get_exit_string(struct daemon* daemon, char* buf, size_t len)
|
|
{
|
|
if ( daemon->state != DAEMON_STATE_FINISHED )
|
|
snprintf(buf, len, "n/a");
|
|
else if ( WIFEXITED(daemon->exit_code) )
|
|
snprintf(buf, len, "%i", WEXITSTATUS(daemon->exit_code));
|
|
else if ( WIFSIGNALED(daemon->exit_code) )
|
|
{
|
|
assert(3 + SIG2STR_MAX <= len);
|
|
snprintf(buf, len, "SIG");
|
|
sig2str(WTERMSIG(daemon->exit_code), buf + 3);
|
|
}
|
|
else
|
|
snprintf(buf, len, "%#x", WEXITSTATUS(daemon->exit_code));
|
|
}
|
|
|
|
static const char* daemon_get_state_string(struct daemon* daemon)
|
|
{
|
|
if ( daemon->state == DAEMON_STATE_TERMINATED && daemon_is_failed(daemon) )
|
|
return "failed";
|
|
return daemon_state_names[daemon->state];
|
|
}
|
|
|
|
static void connection_close(struct connection* conn)
|
|
{
|
|
communication_unregister(conn->index);
|
|
close(conn->fd);
|
|
conn->fd = -1;
|
|
}
|
|
|
|
static void connection_free(struct connection* conn)
|
|
{
|
|
if ( 0 <= conn->fd )
|
|
connection_close(conn);
|
|
free(conn->input);
|
|
free(conn->output);
|
|
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 )
|
|
return free(conn), free(input), free(output), 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_reply(struct connection* conn, const char* string)
|
|
{
|
|
if ( conn->fd < 0 )
|
|
return;
|
|
size_t length = strlen(string);
|
|
size_t available = conn->output_size - conn->output_used;
|
|
if ( available < length )
|
|
return connection_close(conn);
|
|
memcpy(conn->output + conn->output_used, string, length);
|
|
conn->output_used += length;
|
|
}
|
|
|
|
static void connection_replyf(struct connection* conn,
|
|
const char* restrict format,
|
|
...)
|
|
{
|
|
if ( conn->fd < 0 )
|
|
return;
|
|
char* buffer = conn->output + conn->output_used;
|
|
size_t available = conn->output_size - conn->output_used;
|
|
va_list list;
|
|
va_start(list, format);
|
|
size_t length = vsnprintf(buffer, available, format, list);
|
|
va_end(list);
|
|
if ( available <= length )
|
|
return connection_close(conn);
|
|
conn->output_used += length;
|
|
}
|
|
|
|
static void connection_ok(struct connection* conn)
|
|
{
|
|
connection_reply(conn, "ok\n");
|
|
}
|
|
|
|
static void connection_error(struct connection* conn, const char* code)
|
|
{
|
|
connection_reply(conn, "error ");
|
|
connection_reply(conn, code);
|
|
connection_reply(conn, "\n");
|
|
}
|
|
|
|
static void reply_status(struct connection* conn, struct daemon* daemon)
|
|
{
|
|
connection_replyf(conn, " daemon=%s state=%s",
|
|
daemon->name, daemon_get_state_string(daemon));
|
|
if ( 0 < daemon->pid )
|
|
connection_replyf(conn, " pid=%ji", (intmax_t) daemon->pid);
|
|
if ( daemon->state == DAEMON_STATE_FINISHED )
|
|
{
|
|
char exit_str[64];
|
|
daemon_get_exit_string(daemon, exit_str, sizeof(exit_str));
|
|
connection_replyf(conn, " exit=%s", exit_str);
|
|
}
|
|
}
|
|
|
|
static void reply_dependency(struct connection* conn,
|
|
struct dependency* dependency)
|
|
{
|
|
char flags[sizeof(" optional no-await exit-code")];
|
|
snprintf(flags, sizeof(flags), "%s%s%s",
|
|
!(dependency->flags & DEPENDENCY_FLAG_REQUIRE) ? " optional" : "",
|
|
!(dependency->flags & DEPENDENCY_FLAG_AWAIT) ? " no-await" : "",
|
|
dependency->flags & DEPENDENCY_FLAG_EXIT_CODE ? " exit-code" : "");
|
|
connection_replyf(conn, "require %s %s %s", dependency->source->name,
|
|
dependency->target->name, flags);
|
|
}
|
|
|
|
static void request_require(struct connection* conn, int argc, char** argv)
|
|
{
|
|
if ( argc < 3 )
|
|
return connection_error(conn, "missing_operand");
|
|
const char* source_name = argv[1];
|
|
const char* target_name = argv[2];
|
|
int flags = DEPENDENCY_FLAG_REQUIRE | DEPENDENCY_FLAG_AWAIT;
|
|
for ( int i = 3; i < argc; i++ )
|
|
{
|
|
if ( !strcmp(argv[i], "optional") )
|
|
flags &= ~DEPENDENCY_FLAG_REQUIRE;
|
|
else if ( !strcmp(argv[i], "no-await") )
|
|
flags &= ~DEPENDENCY_FLAG_AWAIT;
|
|
else if ( !strcmp(argv[i], "exit-code") )
|
|
flags |= DEPENDENCY_FLAG_EXIT_CODE;
|
|
else
|
|
return connection_error(conn, "unknown_flag");
|
|
}
|
|
struct daemon* source = daemon_find_by_name(source_name);
|
|
if ( !source )
|
|
return connection_error(conn, "no_such_source_daemon");
|
|
struct daemon* target = daemon_find_or_create(target_name);
|
|
if ( !target )
|
|
return connection_error(conn, errno == ENOENT ? "no_such_daemon" :
|
|
"creating_daemon_failed");
|
|
if ( !daemon_depend(source, target, flags) )
|
|
return connection_error(conn, "adding_connection_failed");
|
|
connection_ok(conn);
|
|
}
|
|
|
|
static void request_unrequire(struct connection* conn, int argc, char** argv)
|
|
{
|
|
if ( argc < 3 )
|
|
return connection_error(conn, "missing_operand");
|
|
const char* source_name = argv[1];
|
|
const char* target_name = argv[2];
|
|
struct daemon* source = daemon_find_by_name(source_name);
|
|
if ( !source )
|
|
return connection_error(conn, "no_such_source_daemon");
|
|
struct daemon* target = daemon_find_by_name(target_name);
|
|
if ( !target )
|
|
return connection_error(conn, "no_such_daemon");
|
|
daemon_undepend(source, target);
|
|
connection_ok(conn);
|
|
}
|
|
|
|
static void request_reconfigure(struct connection* conn, int argc, char** argv)
|
|
{
|
|
if ( argc != 2 )
|
|
return connection_error(conn, "missing_operand");
|
|
const char* name = argv[1];
|
|
struct daemon* daemon = daemon_find_or_create(name);
|
|
if ( !daemon )
|
|
return connection_error(conn, errno == ENOENT ? "no_such_daemon" :
|
|
"creating_daemon_failed");
|
|
daemon_request_reconfigure(daemon);
|
|
connection_ok(conn);
|
|
}
|
|
|
|
static void request_reload(struct connection* conn, int argc, char** argv)
|
|
{
|
|
if ( argc != 2 )
|
|
return connection_error(conn, "missing_operand");
|
|
const char* name = argv[1];
|
|
struct daemon* daemon = daemon_find_or_create(name);
|
|
if ( !daemon )
|
|
return connection_error(conn, errno == ENOENT ? "no_such_daemon" :
|
|
"creating_daemon_failed");
|
|
daemon_reload(daemon);
|
|
connection_ok(conn);
|
|
}
|
|
|
|
static void request_restart(struct connection* conn, int argc, char** argv)
|
|
{
|
|
if ( argc != 2 )
|
|
return connection_error(conn, "missing_operand");
|
|
const char* name = argv[1];
|
|
struct daemon* daemon = daemon_find_or_create(name);
|
|
if ( !daemon )
|
|
return connection_error(conn, errno == ENOENT ? "no_such_daemon" :
|
|
"creating_daemon_failed");
|
|
daemon_request_restart(daemon);
|
|
connection_ok(conn);
|
|
}
|
|
|
|
static void request_terminate(struct connection* conn, int argc, char** argv)
|
|
{
|
|
if ( argc != 2 )
|
|
return connection_error(conn, "missing_operand");
|
|
const char* name = argv[1];
|
|
struct daemon* daemon = daemon_find_by_name(name);
|
|
if ( !daemon )
|
|
return connection_error(conn, "no_such_daemon");
|
|
daemon_terminate(daemon);
|
|
connection_ok(conn);
|
|
}
|
|
|
|
static void request_kill(struct connection* conn, int argc, char** argv)
|
|
{
|
|
if ( argc != 2 )
|
|
return connection_error(conn, "missing_operand");
|
|
const char* name = argv[1];
|
|
struct daemon* daemon = daemon_find_by_name(name);
|
|
if ( !daemon )
|
|
return connection_error(conn, "no_such_daemon");
|
|
daemon_kill(daemon);
|
|
connection_ok(conn);
|
|
}
|
|
|
|
static void request_signal(struct connection* conn, int argc, char** argv)
|
|
{
|
|
if ( argc != 3 )
|
|
return connection_error(conn, "missing_operand");
|
|
const char* name = argv[1];
|
|
struct daemon* daemon = daemon_find_by_name(name);
|
|
if ( !daemon )
|
|
return connection_error(conn, "no_such_daemon");
|
|
const char* signame = argv[2];
|
|
int sig;
|
|
if ( strncmp(signame, "SIG", 3) != 0 ||
|
|
str2sig(signame + 3, &sig) < 0 )
|
|
return connection_error(conn, "no_such_signal");
|
|
daemon_signal(daemon, signame, sig);
|
|
connection_ok(conn);
|
|
}
|
|
|
|
static void request_status(struct connection* conn, int argc, char** argv)
|
|
{
|
|
if ( argc != 2 )
|
|
return connection_error(conn, "missing_operand");
|
|
const char* name = argv[1];
|
|
struct daemon* daemon = daemon_find_by_name(name);
|
|
if ( !daemon )
|
|
return connection_error(conn, "no_such_daemon");
|
|
connection_reply(conn, "ok");
|
|
reply_status(conn, daemon);
|
|
connection_reply(conn, "\n");
|
|
}
|
|
|
|
static void request_list(struct connection* conn, int argc, char** argv)
|
|
{
|
|
(void) argv;
|
|
if ( argc != 1 )
|
|
return connection_error(conn, "unexpected_operand");
|
|
if ( !conn->state )
|
|
{
|
|
connection_reply(conn, "ok");
|
|
conn->state = 1;
|
|
return;
|
|
}
|
|
while ( conn->state - 1 < daemons_used )
|
|
{
|
|
size_t i = conn->state - 1;
|
|
if ( i )
|
|
connection_reply(conn, " , ");
|
|
reply_status(conn, daemons[i]);
|
|
conn->state++;
|
|
return;
|
|
}
|
|
connection_reply(conn, "\n");
|
|
conn->state = 0;
|
|
}
|
|
|
|
static void request_requirements(struct connection* conn, int argc, char** argv)
|
|
{
|
|
if ( argc != 2 )
|
|
return connection_error(conn, "missing_operand");
|
|
const char* name = argv[1];
|
|
struct daemon* daemon = daemon_find_by_name(name);
|
|
if ( !daemon )
|
|
return connection_error(conn, "no_such_daemon");
|
|
if ( !conn->state )
|
|
{
|
|
connection_reply(conn, "ok");
|
|
conn->state = 1;
|
|
return;
|
|
}
|
|
bool in = !strcmp(argv[0], "edges") || !strcmp(argv[0], "dependents");
|
|
bool out = !strcmp(argv[0], "edges") || !strcmp(argv[0], "requirements");
|
|
size_t dependents_used = in ? daemon->dependents_used : 0;
|
|
size_t dependencies_used = out ? daemon->dependencies_used : 0;
|
|
while ( conn->state - 1 < dependents_used )
|
|
{
|
|
size_t i = conn->state - 1;
|
|
connection_reply(conn, conn->state == 1 ? " " : " , ");
|
|
reply_dependency(conn, daemon->dependents[i]);
|
|
conn->state++;
|
|
return;
|
|
}
|
|
while ( conn->state - 1 < dependents_used + dependencies_used )
|
|
{
|
|
size_t i = conn->state - (1 + dependents_used);
|
|
connection_reply(conn, conn->state == 1 ? " " : " , ");
|
|
reply_dependency(conn, daemon->dependencies[i]);
|
|
conn->state++;
|
|
return;
|
|
}
|
|
connection_reply(conn, "\n");
|
|
conn->state = 0;
|
|
}
|
|
|
|
static void request_poweroff(struct connection* conn, int argc, char** argv)
|
|
{
|
|
if ( argc != 1 )
|
|
return connection_error(conn, "unexpected_operand");
|
|
if ( !strcmp(argv[0], "poweroff") )
|
|
caught_exit_signal = 0;
|
|
else if ( !strcmp(argv[0], "reboot") )
|
|
caught_exit_signal = 1;
|
|
else if ( !strcmp(argv[0], "halt") )
|
|
caught_exit_signal = 2;
|
|
else if ( !strcmp(argv[0], "reinit") )
|
|
caught_exit_signal = 3;
|
|
connection_ok(conn);
|
|
}
|
|
|
|
static void connection_on_message(struct connection* conn, const char* message)
|
|
{
|
|
size_t argc = 0;
|
|
char** argv = tokenize(&argc, message);
|
|
if ( !argv )
|
|
{
|
|
connection_error(conn, !errno ? "syntax_error" : "out_of_memory");
|
|
return;
|
|
}
|
|
|
|
if ( argc == 0 ) {}
|
|
else if ( !strcmp(argv[0], "require") )
|
|
request_require(conn, argc, argv);
|
|
else if ( !strcmp(argv[0], "unrequire") )
|
|
request_unrequire(conn, argc, argv);
|
|
else if ( !strcmp(argv[0], "reconfigure") )
|
|
request_reconfigure(conn, argc, argv);
|
|
else if ( !strcmp(argv[0], "reload") )
|
|
request_reload(conn, argc, argv);
|
|
else if ( !strcmp(argv[0], "restart") )
|
|
request_restart(conn, argc, argv);
|
|
else if ( !strcmp(argv[0], "terminate") )
|
|
request_terminate(conn, argc, argv);
|
|
else if ( !strcmp(argv[0], "kill") )
|
|
request_kill(conn, argc, argv);
|
|
else if ( !strcmp(argv[0], "signal") )
|
|
request_signal(conn, argc, argv);
|
|
else if ( !strcmp(argv[0], "status") )
|
|
request_status(conn, argc, argv);
|
|
else if ( !strcmp(argv[0], "list") )
|
|
request_list(conn, argc, argv);
|
|
else if ( !strcmp(argv[0], "edges") ||
|
|
!strcmp(argv[0], "dependents") ||
|
|
!strcmp(argv[0], "requirements") )
|
|
request_requirements(conn, argc, argv);
|
|
else if ( !strcmp(argv[0], "poweroff") ||
|
|
!strcmp(argv[0], "reboot") ||
|
|
!strcmp(argv[0], "halt") ||
|
|
!strcmp(argv[0], "reinit") )
|
|
request_poweroff(conn, argc, argv);
|
|
else
|
|
connection_error(conn, "unknown_command");
|
|
|
|
for ( size_t i = 0; i < argc; i++ )
|
|
free(argv[i]);
|
|
free(argv);
|
|
}
|
|
|
|
static bool connection_on_event(struct connection* conn, int revents)
|
|
{
|
|
if ( !(revents & (POLLIN | POLLOUT | POLLHUP | POLLERR | POLLNVAL)) )
|
|
return true;
|
|
size_t from = 0;
|
|
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 )
|
|
{
|
|
from = conn->input_used;
|
|
conn->input_used += amount;
|
|
}
|
|
else if ( amount == 0 ||
|
|
(amount < 0 && errno != EWOULDBLOCK && errno != EAGAIN ) )
|
|
return connection_free(conn), false;
|
|
}
|
|
if ( !conn->output_used )
|
|
{
|
|
size_t i = from;
|
|
while ( i < conn->input_used )
|
|
{
|
|
if ( conn->input[i] == '\n' )
|
|
{
|
|
size_t n = i + 1;
|
|
conn->input[i] = '\0';
|
|
connection_on_message(conn, conn->input);
|
|
conn->input[i] = '\n';
|
|
if ( conn->fd < 0 )
|
|
return connection_free(conn), false;
|
|
if ( conn->state )
|
|
break;
|
|
memmove(conn->input, conn->input + n, conn->input_used - n);
|
|
conn->input_used -= n;
|
|
i = 0;
|
|
}
|
|
else
|
|
i++;
|
|
}
|
|
// Disconnect if the input line is too long.
|
|
if ( i == conn->input_size )
|
|
return connection_free(conn), 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 )
|
|
return connection_free(conn), false;
|
|
}
|
|
if ( (revents & (POLLHUP | POLLERR | POLLNVAL)) )
|
|
return connection_free(conn), false;
|
|
pfds[conn->index].events =
|
|
(conn->input_used < conn->input_size ? POLLIN : 0) |
|
|
(conn->output_used || conn->state ? POLLOUT : 0);
|
|
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 = calloc(1, sizeof(struct server));
|
|
if ( !server )
|
|
return NULL;
|
|
server->fd = open_local_server_socket(path, SOCK_NONBLOCK | SOCK_CLOEXEC);
|
|
server->path = path;
|
|
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;
|
|
|
|
while ( true )
|
|
{
|
|
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 if ( caught_exit_signal == 3 )
|
|
log_status("stopped", "Reinitializing...\n");
|
|
else
|
|
log_status("stopped", "Exiting %i...\n", caught_exit_signal);
|
|
if ( default_daemon->state != DAEMON_STATE_FINISHING &&
|
|
default_daemon->state != DAEMON_STATE_FINISHED )
|
|
daemon_change_state_list(default_daemon,
|
|
DAEMON_STATE_FINISHING);
|
|
default_daemon_exit_code =
|
|
WCONSTRUCT(WNATURE_EXITED, caught_exit_signal, 0);
|
|
default_daemon->exit_code = default_daemon_exit_code;
|
|
default_daemon->exit_code_meaning =
|
|
EXIT_CODE_MEANING_POWEROFF_REBOOT;
|
|
}
|
|
caught_exit_signal = -1;
|
|
|
|
while ( first_daemon_by_state[DAEMON_STATE_SCHEDULED] ||
|
|
first_daemon_by_state[DAEMON_STATE_SATISFIED] ||
|
|
first_daemon_by_state[DAEMON_STATE_FINISHING] )
|
|
{
|
|
struct daemon* daemon;
|
|
while ( (daemon = first_daemon_by_state[DAEMON_STATE_SCHEDULED]) )
|
|
daemon_wait(daemon);
|
|
while ( (daemon = first_daemon_by_state[DAEMON_STATE_SATISFIED]) )
|
|
daemon_start(daemon);
|
|
while ( (daemon = first_daemon_by_state[DAEMON_STATE_FINISHING]) )
|
|
daemon_finish(daemon);
|
|
}
|
|
|
|
if ( !first_daemon_by_state[DAEMON_STATE_STARTING] &&
|
|
!first_daemon_by_state[DAEMON_STATE_RUNNING] &&
|
|
!first_daemon_by_state[DAEMON_STATE_TERMINATING] )
|
|
break;
|
|
|
|
struct timespec timeout = timespec_make(-1, 0);
|
|
struct timespec now;
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
|
|
for ( struct daemon* daemon =
|
|
first_daemon_by_state[DAEMON_STATE_TERMINATING];
|
|
daemon;
|
|
daemon = daemon->next_by_state )
|
|
{
|
|
if ( !daemon->timeout_set )
|
|
continue;
|
|
if ( timespec_le(daemon->timeout, now) )
|
|
{
|
|
log_status("timeout",
|
|
"Stopping %s timed out, sending SIGKILL.\n",
|
|
daemon->name);
|
|
kill(daemon->pid, SIGKILL);
|
|
daemon->timeout_set = false;
|
|
}
|
|
else
|
|
{
|
|
struct timespec left = timespec_sub(daemon->timeout, now);
|
|
if ( timeout.tv_sec < 0 || timespec_lt(left, timeout) )
|
|
timeout = left;
|
|
}
|
|
}
|
|
|
|
// Block SIGCHLD so the signal will be delivered during poll(2).
|
|
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);
|
|
}
|
|
// 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 communication* comm = &communications[i];
|
|
bool closed = false;
|
|
switch ( comm->type )
|
|
{
|
|
case COMMUNICATION_TYPE_OUTPUT:
|
|
closed = daemon_on_output_event(comm->daemon, pfd->revents);
|
|
break;
|
|
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).
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
psctl(getpid(), PSCTL_STAT, &psst);
|
|
bool any_unknown = false;
|
|
for ( pid_t pid = psst.ppid_first; pid != -1; pid = psst.ppid_next )
|
|
{
|
|
if ( psctl(pid, PSCTL_STAT, &psst) < 0 )
|
|
{
|
|
warning("psctl: %ji", (intmax_t) pid);
|
|
continue;
|
|
}
|
|
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 )
|
|
{
|
|
struct daemon* default_daemon = daemon_find_by_name("default");
|
|
default_daemon->exit_code = default_daemon_exit_code;
|
|
default_daemon->exit_code_meaning = EXIT_CODE_MEANING_POWEROFF_REBOOT;
|
|
}
|
|
}
|
|
|
|
static void write_random_seed(void)
|
|
{
|
|
// This function is as robust as possible to ensure randomness doesn't fail.
|
|
const char* will_not = "next boot will not have fresh randomness";
|
|
int fd = open(random_seed_path, O_RDWR | O_CREAT | O_NOFOLLOW, 0600);
|
|
if ( fd < 0 )
|
|
{
|
|
if ( errno != ENOENT && errno != EROFS )
|
|
warning("%s: %s: %m", will_not, random_seed_path);
|
|
return;
|
|
}
|
|
if ( fchown(fd, 0, 0) < 0 )
|
|
{
|
|
warning("%s: chown: %s: %m", will_not, random_seed_path);
|
|
close(fd);
|
|
return;
|
|
}
|
|
if ( fchmod(fd, 0600) < 0 )
|
|
{
|
|
warning("%s: chown: %s: %m", will_not, random_seed_path);
|
|
close(fd);
|
|
return;
|
|
}
|
|
// Mix in the old random seed so the sysadmin can add new randomness here.
|
|
unsigned char old[256] = {0};
|
|
readall(fd, old, sizeof(old));
|
|
lseek(fd, 0, SEEK_SET);
|
|
// 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] ^ old[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, random_seed_path);
|
|
close(fd);
|
|
return;
|
|
}
|
|
if ( ftruncate(fd, sizeof(buf)) < 0 )
|
|
{
|
|
warning("%s: truncate: %s: %m", will_not, random_seed_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)
|
|
{
|
|
char* fstab_path = join_paths(etc_path, "fstab");
|
|
if ( !fstab_path )
|
|
fatal("malloc: %m");
|
|
FILE* fp = fopen(fstab_path, "r");
|
|
if ( !fp )
|
|
{
|
|
if ( errno != ENOENT )
|
|
fatal("%s: %m", fstab_path);
|
|
free(fstab_path);
|
|
return;
|
|
}
|
|
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("%s: %m", fstab_path);
|
|
free(line);
|
|
fclose(fp);
|
|
qsort(mountpoints, mountpoints_used, sizeof(struct mountpoint),
|
|
sort_mountpoint);
|
|
free(fstab_path);
|
|
}
|
|
|
|
static char* read_configuration(const char* name, const char* action)
|
|
{
|
|
char* path = join_paths(etc_path, name);
|
|
if ( !path )
|
|
fatal("malloc: %m");
|
|
FILE* fp = fopen(path, "r");
|
|
if ( !fp )
|
|
{
|
|
if ( errno != ENOENT )
|
|
warning("%s: %s: %m", action, name);
|
|
free(path);
|
|
return NULL;
|
|
}
|
|
char* value = read_single_line(fp);
|
|
fclose(fp);
|
|
if ( !value )
|
|
warning("%s: %s: %m", action, path);
|
|
free(path);
|
|
return value;
|
|
}
|
|
|
|
static void set_hostname(void)
|
|
{
|
|
char* action = "unable to set hostname";
|
|
char* hostname = read_configuration("hostname", action);
|
|
if ( !hostname )
|
|
return;
|
|
int ret = sethostname(hostname, strlen(hostname));
|
|
if ( ret < 0 )
|
|
warning("%s: `%s': %m", action, hostname);
|
|
free(hostname);
|
|
}
|
|
|
|
static void set_kblayout(void)
|
|
{
|
|
bool unsupported = tcgetblob(tty_fd, "kblayout", NULL, 0) < 0 &&
|
|
(errno == ENOTTY || errno == ENOENT);
|
|
if ( unsupported )
|
|
return;
|
|
char* action = "unable to set keyboard layout";
|
|
char* kblayout = read_configuration("kblayout", action);
|
|
if ( !kblayout )
|
|
return;
|
|
pid_t child_pid = fork();
|
|
if ( child_pid < 0 )
|
|
warning("unable to set keyboard layout: fork: %m");
|
|
else if ( !child_pid )
|
|
{
|
|
uninstall_signal_handler();
|
|
execlp("chkblayout", "chkblayout", "--", kblayout,
|
|
(const char*) NULL);
|
|
warning("setting keyboard layout: chkblayout: %m");
|
|
_exit(127);
|
|
}
|
|
else
|
|
{
|
|
int status;
|
|
waitpid(child_pid, &status, 0);
|
|
}
|
|
free(kblayout);
|
|
}
|
|
|
|
static void set_videomode(void)
|
|
{
|
|
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;
|
|
if ( unsupported )
|
|
return;
|
|
char* action = "unable to set video mode";
|
|
char* videomode = read_configuration("videomode", action);
|
|
if ( !videomode )
|
|
return;
|
|
unsigned int xres = 0;
|
|
unsigned int yres = 0;
|
|
unsigned int bpp = 0;
|
|
if ( sscanf(videomode, "%ux%ux%u", &xres, &yres, &bpp) != 3 )
|
|
{
|
|
warning("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("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 fsck(struct filesystem* fs)
|
|
{
|
|
struct blockdevice* bdev = fs->bdev;
|
|
const char* bdev_path = bdev->p ? bdev->p->path : bdev->hd->path;
|
|
assert(bdev_path);
|
|
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 pid = fork();
|
|
if ( pid < 0 )
|
|
{
|
|
if ( fs->flags & FILESYSTEM_FLAG_FSCK_MUST )
|
|
warning("%s: Mandatory repair failed: fork: %m", bdev_path);
|
|
else
|
|
warning("%s: Skipping filesystem check: fork: %m:", bdev_path);
|
|
return false;
|
|
}
|
|
if ( pid == 0 )
|
|
{
|
|
uninstall_signal_handler();
|
|
execlp(fs->fsck, fs->fsck, "-fp", "--", bdev_path, (const char*) NULL);
|
|
warning("%s: Failed to load filesystem checker: %s: %m",
|
|
bdev_path, fs->fsck);
|
|
_exit(127);
|
|
}
|
|
int code;
|
|
if ( waitpid(pid, &code, 0) < 0 )
|
|
warning("%s: Filesystem check: waitpid: %m", bdev_path);
|
|
else if ( WIFEXITED(code) &&
|
|
(WEXITSTATUS(code) == 0 || WEXITSTATUS(code) == 1) )
|
|
{
|
|
// Successfully checked filesystem.
|
|
fs->flags &= ~(FILESYSTEM_FLAG_FSCK_SHOULD | FILESYSTEM_FLAG_FSCK_MUST);
|
|
return true;
|
|
}
|
|
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");
|
|
}
|
|
else
|
|
{
|
|
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");
|
|
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");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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 || !match.bdev )
|
|
{
|
|
warning("%s: No devices matching uuid %s were found", path, uuid);
|
|
return NULL;
|
|
}
|
|
assert(match.bdev->fs);
|
|
return match.bdev->fs;
|
|
}
|
|
// TODO: Lookup by device name.
|
|
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);
|
|
const char* pretend_where = mountpoint->entry.fs_file;
|
|
const char* where = mountpoint->absolute;
|
|
const char* read_only = NULL;
|
|
if ( !(fs->flags & FILESYSTEM_FLAG_WRITABLE) )
|
|
read_only = "-r";
|
|
else if ( fs->flags & (FILESYSTEM_FLAG_FSCK_SHOULD |
|
|
FILESYSTEM_FLAG_FSCK_MUST) )
|
|
{
|
|
if ( !fsck(fs) && (fs->flags & FILESYSTEM_FLAG_FSCK_MUST) )
|
|
{
|
|
warning("Mounting inconsistent filesystem %s read-only on %s",
|
|
bdev_path, pretend_where);
|
|
read_only = "-r";
|
|
}
|
|
}
|
|
if ( !fs->driver )
|
|
{
|
|
warning("Failed mounting %s on %s: "
|
|
"Don't know how to mount a %s filesystem",
|
|
bdev_path, pretend_where, fs->fstype_name);
|
|
return false;
|
|
}
|
|
struct stat st;
|
|
if ( stat(where, &st) < 0 )
|
|
{
|
|
warning("Failed mounting %s on %s: stat: %s: %m",
|
|
bdev_path, pretend_where, where);
|
|
return false;
|
|
}
|
|
int readyfds[2];
|
|
if ( pipe(readyfds) < 0 )
|
|
{
|
|
warning("Failed mounting %s on %s: pipe: %m", bdev_path, pretend_where);
|
|
return false;
|
|
}
|
|
if ( (mountpoint->pid = fork()) < 0 )
|
|
{
|
|
warning("Failed mounting %s on %s: fork: %m", bdev_path, pretend_where);
|
|
close(readyfds[0]);
|
|
close(readyfds[1]);
|
|
return false;
|
|
}
|
|
if ( mountpoint->pid == 0 )
|
|
{
|
|
uninstall_signal_handler();
|
|
close(readyfds[0]);
|
|
char readyfdstr[sizeof(int) * 3];
|
|
snprintf(readyfdstr, sizeof(readyfdstr), "%d", readyfds[1]);
|
|
if ( setenv("READYFD", readyfdstr, 1) < 0 )
|
|
{
|
|
warning("Failed mounting %s on %s: setenv: %m",
|
|
bdev_path, pretend_where);
|
|
_exit(127);
|
|
}
|
|
execlp(fs->driver, fs->driver, "--foreground", bdev_path, where,
|
|
"--pretend-mount-path", pretend_where, read_only,
|
|
(const char*) NULL);
|
|
warning("Failed mount %s on %s: execvp: %s: %m",
|
|
bdev_path, pretend_where, fs->driver);
|
|
_exit(127);
|
|
}
|
|
close(readyfds[1]);
|
|
char c;
|
|
struct stat newst;
|
|
ssize_t amount = read(readyfds[0], &c, 1);
|
|
close(readyfds[0]);
|
|
if ( 0 <= amount )
|
|
{
|
|
if ( !stat(where, &newst) )
|
|
{
|
|
if ( newst.st_dev != st.st_dev || newst.st_ino != st.st_ino )
|
|
return true;
|
|
else
|
|
warning("Failed mount %s on %s: %s: "
|
|
"No mounted filesystem appeared: %s",
|
|
bdev_path, pretend_where, fs->driver, where);
|
|
}
|
|
else
|
|
warning("Failed mounting %s on %s: %s, stat: %s: %m",
|
|
bdev_path, pretend_where, fs->driver, where);
|
|
}
|
|
else
|
|
warning("Failed mounting %s on %s: %s, Failed to read readiness: %m",
|
|
bdev_path, pretend_where, fs->driver);
|
|
if ( unmount(where, 0) < 0 )
|
|
{
|
|
if ( errno != ENOMOUNT )
|
|
warning("Failed mounting %s on %s: unmount: %s: %m",
|
|
bdev_path, pretend_where, where);
|
|
kill(mountpoint->pid, SIGQUIT);
|
|
}
|
|
int code;
|
|
pid_t child = waitpid(mountpoint->pid, &code, 0);
|
|
mountpoint->pid = -1;
|
|
if ( child < 0 )
|
|
warning("Failed mounting %s on %s: %s: waitpid: %m",
|
|
bdev_path, pretend_where, fs->driver);
|
|
else if ( WIFSIGNALED(code) )
|
|
warning("Failed mounting %s on %s: %s: %s",
|
|
bdev_path, pretend_where, fs->driver,
|
|
strsignal(WTERMSIG(code)));
|
|
else if ( !WIFEXITED(code) )
|
|
warning("Failed mounting %s on %s: %s: Unexpected unusual termination",
|
|
bdev_path, pretend_where, fs->driver);
|
|
else if ( WEXITSTATUS(code) == 127 )
|
|
warning("Failed mounting %s on %s: %s: "
|
|
"Filesystem driver could not be executed",
|
|
bdev_path, pretend_where, fs->driver);
|
|
else if ( WEXITSTATUS(code) == 0 )
|
|
warning("Failed mounting %s on %s: %s: Unexpected successful exit",
|
|
bdev_path, pretend_where, fs->driver);
|
|
else
|
|
warning("Failed mounting %s on %s: %s: Exited with status %i",
|
|
bdev_path, pretend_where, fs->driver, WEXITSTATUS(code));
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
|
|
if ( tty_gifted )
|
|
{
|
|
(void) ioctl(tty_fd, TIOCSCTTY, 1);
|
|
tty_gifted = false;
|
|
}
|
|
|
|
// TODO: Unify with new daemon system? At least it needs to recursively kill
|
|
// all processes. Ideally fatal wouldn't be called for daemons.
|
|
|
|
// TODO: Don't do this unless all the mountpoints were mounted (not for
|
|
// chain init).
|
|
write_random_seed();
|
|
|
|
// Stop logging when unmounting the filesystems.
|
|
cbprintf(&init_log, log_callback, "Finished operating system.\n");
|
|
log_close(&init_log);
|
|
|
|
if ( chain_dev_path_made )
|
|
{
|
|
unmount(chain_dev_path, 0);
|
|
chain_dev_path_made = false;
|
|
}
|
|
|
|
if ( chain_tmp_path_made )
|
|
{
|
|
clean_tmp(chain_tmp_path);
|
|
unmount(chain_tmp_path, 0);
|
|
rmdir(chain_tmp_path_real);
|
|
chain_tmp_path_made = false;
|
|
}
|
|
|
|
mountpoints_unmount();
|
|
|
|
if ( chain_path_made )
|
|
{
|
|
rmdir(chain_path);
|
|
chain_path_made = false;
|
|
}
|
|
}
|
|
|
|
static void reinit(void)
|
|
{
|
|
niht();
|
|
// Drop the static prefix on reinit and boot with defaults.
|
|
const char* argv[] = { "init", NULL };
|
|
char* init_path = join_paths(prefix, "sbin/init");
|
|
if ( !init_path )
|
|
fatal("malloc: %m");
|
|
execv(init_path, (char* const*) argv);
|
|
fatal("Failed to load init during reinit: %s: %m", argv[0]);
|
|
}
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
main_pid = getpid();
|
|
|
|
setlocale(LC_ALL, "");
|
|
|
|
const char* target_name = "default";
|
|
|
|
const struct option longopts[] =
|
|
{
|
|
{"prefix", required_argument, NULL, 'p'},
|
|
{"quiet", no_argument, NULL, 'q'},
|
|
{"silent", no_argument, NULL, 's'},
|
|
{"static-prefix", required_argument, NULL, 'P'},
|
|
{"target", required_argument, NULL, 't'},
|
|
{"verbose", no_argument, NULL, 'v'},
|
|
{0, 0, 0, 0}
|
|
};
|
|
const char* opts = "t:qsv";
|
|
int opt;
|
|
while ( (opt = getopt_long(argc, argv, opts, longopts, NULL)) != -1 )
|
|
{
|
|
switch ( opt )
|
|
{
|
|
case 'p': prefix = optarg; break;
|
|
case 'P': static_prefix = optarg; break;
|
|
case 'q': verbosity = VERBOSITY_QUIET; break;
|
|
case 's': verbosity = VERBOSITY_SILENT; break;
|
|
case 't': target_name = optarg; break;
|
|
case 'v': verbosity = VERBOSITY_VERBOSE; break;
|
|
default: return 2;
|
|
}
|
|
}
|
|
|
|
// Prevent recursive init without care.
|
|
if ( getinit(0) != getpid() )
|
|
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");
|
|
|
|
// Determine the directory structure.
|
|
if ( !prefix ) prefix = "";
|
|
if ( !static_prefix ) static_prefix = prefix;
|
|
if ( !(bin_path = join_paths(static_prefix, "bin")) ||
|
|
!(sbin_path = join_paths(static_prefix, "sbin")) ||
|
|
!(var_path = join_paths(prefix, "var")) ||
|
|
!(log_path = join_paths(var_path, "log")) ||
|
|
!(run_path = join_paths(var_path, "run")) ||
|
|
!(tmp_path = join_paths(prefix, "tmp")) ||
|
|
!(etc_path = join_paths(prefix, "etc")) ||
|
|
!(etc_init_path = join_paths(etc_path, "init")) ||
|
|
!(share_init_path = join_paths(static_prefix, "share/init")) ||
|
|
!(random_seed_path = join_paths(prefix, "boot/random.seed")) ||
|
|
!(server_path = join_paths(run_path, "init")) ||
|
|
!(chain_path = join_paths(tmp_path, "fs.XXXXXX")) ||
|
|
!(chain_dev_path = join_paths(chain_path, "dev")) ||
|
|
!(chain_tmp_path = join_paths(chain_path, "tmp")) ||
|
|
!(chain_tmp_path_real = strdup("/dev/tmp/XXXXXX")) )
|
|
fatal("malloc: %m");
|
|
|
|
// Remember the controlling terminal to give it away and reclaim it.
|
|
if ( (tty_fd = open("/dev/tty", O_RDWR | O_CLOEXEC)) < 0 )
|
|
fatal("/dev/tty: %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 and supplies default
|
|
// values for the other daemon configuration files.
|
|
struct daemon_config* default_daemon_config = NULL;
|
|
char* default_path = join_paths(etc_init_path, "default");
|
|
if ( !default_path )
|
|
fatal("malloc: %m");
|
|
if ( !access(default_path, F_OK) || errno != ENOENT )
|
|
{
|
|
if ( !(default_daemon_config = daemon_config_load("default")) )
|
|
fatal("Failed to load default daemon configuration");
|
|
}
|
|
|
|
// The default daemon must be configured if no target is specified.
|
|
if ( !default_daemon_config && !strcmp(target_name, "default") )
|
|
fatal("No default daemon configuration was found");
|
|
|
|
// 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 %s: %m", default_path);
|
|
free(default_path);
|
|
|
|
// 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 :
|
|
"";
|
|
|
|
// Log to memory until the log directory has been mounted.
|
|
if ( !log_initialize(&init_log, "init", &default_config) )
|
|
fatal("malloc: %m");
|
|
if ( !log_begin_buffer(&init_log) )
|
|
fatal("malloc: %m");
|
|
init_log.pid = getpid();
|
|
cbprintf(&init_log, log_callback, "Initializing operating system...\n");
|
|
|
|
// Make sure that we have a /tmp directory.
|
|
umask(0000);
|
|
mkdir(tmp_path, 01777);
|
|
clean_tmp(tmp_path);
|
|
if ( strcmp(tmp_path, "/tmp") != 0 &&
|
|
setenv("TMPDIR", tmp_path, 1) < 0 )
|
|
fatal("setenv: %m");
|
|
|
|
// Make sure that we have a /var/run directory.
|
|
umask(0000);
|
|
mkdir(var_path, 0755);
|
|
mkdir(run_path, 0755);
|
|
clean_tmp(run_path);
|
|
|
|
// Set the default file creation mask.
|
|
umask(0022);
|
|
|
|
// Set up the PATH variable.
|
|
char* path;
|
|
if ( asprintf(&path, "%s:%s", bin_path, sbin_path) < 0 )
|
|
fatal("malloc: %m");
|
|
if ( setenv("PATH", path, 1) < 0 )
|
|
fatal("setenv: %m");
|
|
free(path);
|
|
|
|
// 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.
|
|
if ( !strcmp(first_requirement, "chain") ||
|
|
!strcmp(first_requirement, "chain-sysmerge") )
|
|
{
|
|
int next_argc = argc - optind;
|
|
char** next_argv = argv + optind;
|
|
// Create a temporary directory where the real root filesystem will be
|
|
// mounted.
|
|
if ( !mkdtemp(chain_path) )
|
|
fatal("mkdtemp: %s: %m", chain_path);
|
|
chain_path_made = true;
|
|
// Rewrite the filesystem table to mount inside the temporary directory.
|
|
bool found_root = false;
|
|
bool found_tmp = false;
|
|
for ( size_t i = 0; i < mountpoints_used; i++ )
|
|
{
|
|
struct mountpoint* mountpoint = &mountpoints[i];
|
|
if ( !strcmp(mountpoint->entry.fs_file, "/") )
|
|
found_root = true;
|
|
if ( !strcmp(mountpoint->entry.fs_file, "/tmp") )
|
|
found_tmp = true;
|
|
char* absolute = join_paths(chain_path, mountpoint->absolute);
|
|
if ( !absolute )
|
|
fatal("malloc: %m");
|
|
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.
|
|
memcpy(chain_dev_path, chain_path, strlen(chain_path));
|
|
if ( mkdir(chain_dev_path, 0755) < 0 &&
|
|
errno != EEXIST && errno != EROFS )
|
|
fatal("mkdir: %s: %m", chain_dev_path);
|
|
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_dev_path, O_DIRECTORY | O_RDONLY);
|
|
if ( new_dev_fd < 0 )
|
|
fatal("%s: %m", chain_dev_path);
|
|
if ( fsm_fsbind(old_dev_fd, new_dev_fd, 0) < 0 )
|
|
fatal("mount: `%s' onto `%s': %m", "/dev", chain_dev_path);
|
|
chain_dev_path_made = true;
|
|
close(new_dev_fd);
|
|
close(old_dev_fd);
|
|
// Mount /tmp as a ramfs if the root filesystem isn't writable.
|
|
// TODO: Move this to the nested init instead when there is a system
|
|
// call that lets one instantiate a new ramfs.
|
|
struct statvfs stvfs;
|
|
if ( statvfs(chain_path, &stvfs) < 0 )
|
|
fatal("statvfs: /: %m");
|
|
if ( !found_tmp && (stvfs.f_flag & ST_RDONLY) )
|
|
{
|
|
if ( mkdir("/dev/tmp", 01777) && errno != EEXIST )
|
|
fatal("mkdir: /dev/tmp: %m");
|
|
if ( !mkdtemp(chain_tmp_path_real) )
|
|
fatal("mkdtemp: %s: %m", chain_tmp_path_real);
|
|
memcpy(chain_tmp_path, chain_path, strlen(chain_path));
|
|
if ( mkdir(chain_tmp_path, 0755) < 0 &&
|
|
errno != EEXIST && errno != EROFS )
|
|
fatal("mkdir: %s: %m", chain_tmp_path);
|
|
int old_tmp_fd = open(chain_tmp_path_real, O_DIRECTORY | O_RDONLY);
|
|
if ( old_tmp_fd < 0 )
|
|
fatal("%s: %m", chain_tmp_path_real);
|
|
int new_tmp_fd = open(chain_tmp_path, O_DIRECTORY | O_RDONLY);
|
|
if ( new_tmp_fd < 0 )
|
|
fatal("%s: %m", chain_tmp_path);
|
|
if ( fsm_fsbind(old_tmp_fd, new_tmp_fd, 0) < 0 )
|
|
fatal("mount: `%s' onto `%s': %m", chain_tmp_path_real,
|
|
chain_tmp_path);
|
|
chain_tmp_path_made = true;
|
|
close(new_tmp_fd);
|
|
close(old_tmp_fd);
|
|
}
|
|
// TODO: Forward the early init log to the chain init.
|
|
// 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_path) < 0 )
|
|
fatal("chroot: %s: %m", chain_path);
|
|
if ( chdir("/") < 0 )
|
|
fatal("chdir: %s: %m", chain_path);
|
|
if ( setinit() < 0 )
|
|
fatal("setinit: %m");
|
|
if ( ioctl(tty_fd, TIOCSCTTY, 1) < 0 )
|
|
fatal("ioctl: TIOCSCTTY: %m");
|
|
char verbose_opt[] = {'-', "sqv"[verbosity], '\0'};
|
|
// TODO: Concat next_argv onto this argv_next, so the arguments
|
|
// can be passed to the final init.
|
|
char* sysmerge_argv[] =
|
|
{
|
|
"/sysmerge/sbin/init",
|
|
"--static-prefix=/sysmerge",
|
|
"--target=sysmerge",
|
|
verbose_opt,
|
|
NULL,
|
|
};
|
|
char* chain_argv[] =
|
|
{
|
|
"init",
|
|
verbose_opt,
|
|
NULL,
|
|
};
|
|
// Chain boot the operating system upgrade if needed.
|
|
const char* program;
|
|
if ( !strcmp(first_requirement, "chain-sysmerge") )
|
|
{
|
|
next_argv = sysmerge_argv;
|
|
program = next_argv[0];
|
|
}
|
|
else if ( next_argc < 1 )
|
|
{
|
|
next_argv = chain_argv;
|
|
program = "/sbin/init";
|
|
}
|
|
else
|
|
program = next_argv[0];
|
|
execvp(program, 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 ( ioctl(tty_fd, TIOCSCTTY, 1) < 0 )
|
|
fatal("ioctl: TIOCSCTTY: %m");
|
|
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);
|
|
|
|
// TODO: After releasing Sortix 1.1, remove this compatibility since a
|
|
// sysmerge from 1.0 will not have a /var/log directory.
|
|
if ( !strcmp(first_requirement, "sysmerge") &&
|
|
access(log_path, F_OK) < 0 )
|
|
mkdir(log_path, 0755);
|
|
|
|
// Logging works now that the filesystems have been mounted. Reopen the init
|
|
// log and write the contents buffered up in memory.
|
|
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();
|
|
|
|
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.
|
|
daemon_schedule(default_daemon);
|
|
|
|
// Initialize the operating system.
|
|
init();
|
|
|
|
// Reinitialize the operating system if requested.
|
|
if ( default_daemon->exit_code_meaning ==
|
|
EXIT_CODE_MEANING_POWEROFF_REBOOT &&
|
|
WIFEXITED(default_daemon->exit_code) &&
|
|
WEXITSTATUS(default_daemon->exit_code) == 3 )
|
|
reinit();
|
|
|
|
// Finish with the exit code of the default daemon.
|
|
return exit_code_to_exit_status(default_daemon->exit_code);
|
|
}
|