toaruos/apps/init.c

138 lines
3.8 KiB
C

/* vim: tabstop=4 shiftwidth=4 noexpandtab
* This file is part of ToaruOS and is released under the terms
* of the NCSA / University of Illinois License - see LICENSE.md
* Copyright (C) 2018 K. Lange
*
* init - First process.
*
* `init` calls startup scripts and then waits for them to complete.
* It also waits for orphaned proceses so they can be collected.
*
* `init` itself is statically-linked, so minimizing libc dependencies
* is worthwhile as it reduces to the total size of init itself, which
* remains in memory throughout the entire lifetime of the OS.
*
* Startup scripts for init are stored in /etc/startup.d and are run
* in sorted alphabetical order. It is generally recommended that these
* startup scripts be named with numbers at the front to ensure easy
* ordering. This system of running a set of scripts on startup is
* somewhat similar to how sysvinit worked, but no claims of
* compatibility are made.
*
* Startup scripts can be any executable binary. Shell scripts are
* generally used to allow easy editing, but you could also use
* a binary (even a dynamically linked one) as a startup script.
* `init` will wait for each startup script (that is, it will wait for
* the original process it started to exit) before running the next one.
* So if you wish to run daemons, be sure to fork them off and then
* exit so that the rest of the startup process can continue.
*
* When the last startup script finishes, `init` will reboot the system.
*/
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
#include <unistd.h>
#include <wait.h>
#include <sys/wait.h>
#define INITD_PATH "/etc/startup.d"
/* Initialize fd 0, 1, 2 */
void set_console(void) {
syscall_open("/dev/null", 0, 0);
syscall_open("/dev/null", 1, 0);
syscall_open("/dev/null", 1, 0);
}
/* Run a startup script and wait for it to finish */
int start_options(char * args[]) {
/* Fork child to run script */
int cpid = syscall_fork();
/* Child process... */
if (!cpid) {
/* Pass environment from init to child */
syscall_execve(args[0], args, environ);
/* exec failed, exit this subprocess */
syscall_exit(0);
}
/* Wait for the child process to finish */
int pid = 0;
do {
/*
* Wait, ignoring kernel threads
* (which also end up as children to init)
*/
pid = waitpid(-1, NULL, WNOKERN);
if (pid == -1 && errno == ECHILD) {
/* There are no more children */
break;
}
if (pid == cpid) {
/* The child process finished */
break;
}
/* Continue while no error (or error was "interrupted") */
} while ((pid > 0) || (pid == -1 && errno == EINTR));
return cpid;
}
int main(int argc, char * argv[]) {
/* Initialize stdin/out/err */
set_console();
/* Get directory listing for /etc/startup.d */
int initd_dir = syscall_open(INITD_PATH, 0, 0);
if (initd_dir < 0) {
/* No init scripts; try to start getty as a fallback */
start_options((char *[]){"/bin/getty",NULL});
} else {
int count = 0, i = 0, ret = 0;
/* Figure out how many entries we have with a dry run */
do {
struct dirent ent;
ret = syscall_readdir(initd_dir, ++count, &ent);
} while (ret > 0);
/* Read each directory entry */
struct dirent entries[count];
do {
syscall_readdir(initd_dir, i, &entries[i]);
i++;
} while (i < count);
/* Sort the directory entries */
int comparator(const void * c1, const void * c2) {
const struct dirent * d1 = c1;
const struct dirent * d2 = c2;
return strcmp(d1->d_name, d2->d_name);
}
qsort(entries, count, sizeof(struct dirent), comparator);
/* Run scripts */
for (int i = 0; i < count; ++i) {
if (entries[i].d_name[0] != '.') {
char path[256];
sprintf(path, "/etc/startup.d/%s", entries[i].d_name);
start_options((char *[]){path, NULL});
}
}
}
/* Self-explanatory */
syscall_reboot();
return 0;
}