From 9fe234d4d81e8f00118c1e0c4d95aef401ffa589 Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Fri, 24 Jul 2015 02:24:49 +0200 Subject: [PATCH] Rewrite init(8). --- Makefile | 11 +- ext/Makefile | 4 +- ext/extfs.cpp | 139 +---- init/Makefile | 8 +- init/init.8 | 182 ++++++ init/init.c++ | 1216 +++++++++++++++++++++++------------- kernel/kernel.cpp | 12 +- share/man/man5/videomode.5 | 46 ++ share/man/man7/initrd.7 | 2 +- share/man/man7/kernel.7 | 2 +- 10 files changed, 1053 insertions(+), 569 deletions(-) create mode 100644 init/init.8 create mode 100644 share/man/man5/videomode.5 diff --git a/Makefile b/Makefile index d6196f08..12f89536 100644 --- a/Makefile +++ b/Makefile @@ -88,12 +88,15 @@ endif rm -rf "$(INSTALL_ROOTFS)/boot/sortix.initrd.d" mkdir -p "$(INSTALL_ROOTFS)/boot/sortix.initrd.d" mkdir -p "$(INSTALL_ROOTFS)/boot/sortix.initrd.d/bin" - for PROGRAM in init mbrfs extfs; do \ - cp "$(INSTALL_ROOTFS)/bin/$$PROGRAM" "$(INSTALL_ROOTFS)/boot/sortix.initrd.d/bin/$$PROGRAM"; \ - done + mkdir -p "$(INSTALL_ROOTFS)/boot/sortix.initrd.d/sbin" + test ! -e "$(INSTALL_ROOTFS)/bin/fsck.ext2" || \ + cp "$(INSTALL_ROOTFS)/bin/fsck.ext2" "$(INSTALL_ROOTFS)/boot/sortix.initrd.d/bin/fsck.ext2" + cp "$(INSTALL_ROOTFS)/sbin/extfs" "$(INSTALL_ROOTFS)/boot/sortix.initrd.d/sbin/extfs" + cp "$(INSTALL_ROOTFS)/sbin/init" "$(INSTALL_ROOTFS)/boot/sortix.initrd.d/sbin/init" mkdir -p "$(INSTALL_ROOTFS)/boot/sortix.initrd.d/etc" mkdir -p "$(INSTALL_ROOTFS)/boot/sortix.initrd.d/etc/init" - cp "$(INSTALL_ROOTFS)/etc/rootfs.uuid" "$(INSTALL_ROOTFS)/boot/sortix.initrd.d/etc/init/rootfs.uuid" + cp "$(INSTALL_ROOTFS)/etc/fstab" "$(INSTALL_ROOTFS)/boot/sortix.initrd.d/etc/fstab" + echo chain > "$(INSTALL_ROOTFS)/boot/sortix.initrd.d/etc/init/target" mkinitrd --format=sortix-initrd-2 "$(INSTALL_ROOTFS)/boot/sortix.initrd.d" -o "$(INSTALL_ROOTFS)/boot/sortix.initrd" rm -rf "$(INSTALL_ROOTFS)/boot/sortix.initrd.d" diff --git a/ext/Makefile b/ext/Makefile index 8f0e1eab..43f1d076 100644 --- a/ext/Makefile +++ b/ext/Makefile @@ -24,8 +24,8 @@ all: $(BINARIES) .PHONY: all install clean install: all - mkdir -p $(DESTDIR)$(BINDIR) - install $(BINARIES) $(DESTDIR)$(BINDIR) + mkdir -p $(DESTDIR)$(SBINDIR) + install $(BINARIES) $(DESTDIR)$(SBINDIR) extfs: *.cpp *.h $(CXX) $(PTHREAD_OPTION) -std=gnu++11 $(CPPFLAGS) $(CXXFLAGS) *.cpp -o $@ $(LIBS) diff --git a/ext/extfs.cpp b/ext/extfs.cpp index bf1a3f88..509298e0 100644 --- a/ext/extfs.cpp +++ b/ext/extfs.cpp @@ -128,66 +128,6 @@ void StatInode(Inode* inode, struct stat* st) st->st_blocks = inode->data->i_blocks; } -static bool is_hex_digit(char c) -{ - return ('0' <= c && c <= '9') || - ('a' <= c && c <= 'f') || - ('A' <= c && c <= 'F'); -} - -static bool is_valid_uuid(const char* uuid) -{ - if ( strlen(uuid) != 36 ) - return false; - // Format: 01234567-0123-0123-0123-0123456789AB - for ( size_t i = 0; i < 36; i++ ) - { - if ( i == 8 || i == 13 || i == 18 || i == 23 ) - { - if ( uuid[i] != '-' ) - return false; - } - else - { - if ( !is_hex_digit(uuid[i]) ) - return false; - } - } - return true; -} - -static unsigned char debase(char c) -{ - if ( '0' <= c && c <= '9' ) - return (unsigned char) (c - '0'); - if ( 'a' <= c && c <= 'f' ) - return (unsigned char) (c - 'a' + 10); - if ( 'A' <= c && c <= 'F' ) - return (unsigned char) (c - 'A' + 10); - return 0; -} - -static void uuid_from_string(uint8_t uuid[16], const char* string) -{ - assert(is_valid_uuid(string)); - size_t output_index = 0; - size_t i = 0; - while ( i < 36 ) - { - assert(string[i + 0] != '\0'); - if ( i == 8 || i == 13 || i == 18 || i == 23 ) - { - i++; - continue; - } - assert(string[i + 1] != '\0'); - uuid[output_index++] = debase(string[i + 0]) << 4 | - debase(string[i + 1]) << 0; - i += 2; - } - assert(string[i] == '\0'); -} - static void compact_arguments(int* argc, char*** argv) { for ( int i = 0; i < *argc; i++ ) @@ -217,9 +157,8 @@ static void version(FILE* fp, const char* argv0) int main(int argc, char* argv[]) { const char* argv0 = argv[0]; - const char* test_uuid = NULL; + const char* pretend_mount_path = NULL; bool foreground = false; - bool probe = false; bool read = false; bool write = false; for ( int i = 1; i < argc; i++ ) @@ -250,20 +189,21 @@ int main(int argc, char* argv[]) foreground = false; else if ( !strcmp(arg, "--foreground") ) foreground = true; - else if ( !strcmp(arg, "--probe") ) - probe = true; else if ( !strcmp(arg, "--read") ) read = true; else if ( !strcmp(arg, "--write") ) write = true; - else if ( !strcmp(arg, "--test-uuid") ) + else if ( !strncmp(arg, "--pretend-mount-path=", strlen("--pretend-mount-path=")) ) + pretend_mount_path = arg + strlen("--pretend-mount-path="); + else if ( !strcmp(arg, "--pretend-mount-path") ) { if ( i+1 == argc ) { - fprintf(stderr, "%s: --test-uuid: Missing operand\n", argv0); + fprintf(stderr, "%s: --pretend-mount-path: Missing operand\n", argv0); exit(1); } - test_uuid = argv[++i], argv[i] = NULL; + pretend_mount_path = argv[++i]; + argv[i] = NULL; } else { @@ -296,6 +236,9 @@ int main(int argc, char* argv[]) exit(1); } + if ( !pretend_mount_path ) + pretend_mount_path = mount_path; + int fd = open(device_path, write ? O_RDWR : O_RDONLY); if ( fd < 0 ) error(1, errno, "`%s'", device_path); @@ -304,98 +247,50 @@ int main(int argc, char* argv[]) struct ext_superblock sb; if ( preadall(fd, &sb, sizeof(sb), 1024) != sizeof(sb) ) { - if ( probe ) - exit(1); - else if ( errno == EEOF ) + if ( errno == EEOF ) error(1, 0, "`%s' isn't a valid extended filesystem", device_path); else error(1, errno, "read: `%s'", device_path); } // Verify the magic value to detect a compatible filesystem. - if ( !probe && sb.s_magic != EXT2_SUPER_MAGIC ) + if ( sb.s_magic != EXT2_SUPER_MAGIC ) error(1, 0, "`%s' isn't a valid extended filesystem", device_path); - if ( probe && sb.s_magic != EXT2_SUPER_MAGIC ) - exit(1); - - // Test whether this was the filesystem the user was looking for. - if ( test_uuid ) - { - if ( !is_valid_uuid(test_uuid) ) - { - if ( !probe ) - error(1, 0, "`%s' isn't a valid uuid", test_uuid); - exit(1); - } - - uint8_t uuid[16]; - uuid_from_string(uuid, test_uuid); - - if ( memcmp(sb.s_uuid, uuid, 16) != 0 ) - { - if ( !probe ) - error(1, 0, "uuid `%s' did not match the ext2 filesystem at `%s'", test_uuid, device_path); - exit(1); - } - } - // Test whether this revision of the extended filesystem is supported. if ( sb.s_rev_level == EXT2_GOOD_OLD_REV ) - { - if ( probe ) - exit(1); error(1, 0, "`%s' is formatted with an obsolete filesystem revision", device_path); - } // Verify that no incompatible features are in use. if ( sb.s_feature_compat & ~EXT2_FEATURE_INCOMPAT_SUPPORTED ) - { - if ( probe ) - exit(1); error(1, 0, "`%s' uses unsupported and incompatible features", device_path); - } // Verify that no incompatible features are in use if opening for write. if ( !default_access && write && sb.s_feature_ro_compat & ~EXT2_FEATURE_RO_COMPAT_SUPPORTED ) - { - if ( probe ) - exit(1); error(1, 0, "`%s' uses unsupported and incompatible features, " "read-only access is possible, but write-access was " "requested", device_path); - } if ( write && sb.s_feature_ro_compat & ~EXT2_FEATURE_RO_COMPAT_SUPPORTED ) { - if ( !probe ) - fprintf(stderr, "Warning: `%s' uses unsupported and incompatible " - "features, falling back to read-only access\n", - device_path); + fprintf(stderr, "Warning: `%s' uses unsupported and incompatible " + "features, falling back to read-only access\n", + device_path); // TODO: Modify the file descriptor such that writing fails! read = true; } // Check whether any features are in use that we can safely disregard. - if ( !probe && sb.s_feature_compat & ~EXT2_FEATURE_COMPAT_SUPPORTED ) + if ( sb.s_feature_compat & ~EXT2_FEATURE_COMPAT_SUPPORTED ) fprintf(stderr, "Note: filesystem uses unsupported but compatible " "features\n"); // Check the block size is sane. 64 KiB may have issues, 32 KiB then. if ( sb.s_log_block_size > (15-10) /* 32 KiB blocks */ ) - { - if ( probe ) - exit(1); error(1, 0, "`%s': excess block size", device_path); - } - - // We have found no critical problems, so let the caller know that this - // filesystem satisfies the probe request. - if ( probe ) - exit(0); // Check whether the filesystem was unmounted cleanly. if ( sb.s_state != EXT2_VALID_FS ) @@ -407,7 +302,7 @@ int main(int argc, char* argv[]) Device* dev = new Device(fd, device_path, block_size, write); if ( !dev ) // TODO: Use operator new nothrow! error(1, errno, "malloc"); - Filesystem* fs = new Filesystem(dev, mount_path); + Filesystem* fs = new Filesystem(dev, pretend_mount_path); if ( !fs ) // TODO: Use operator new nothrow! error(1, errno, "malloc"); diff --git a/init/Makefile b/init/Makefile index d0ace7c3..1038d5dc 100644 --- a/init/Makefile +++ b/init/Makefile @@ -19,14 +19,16 @@ all: $(BINARY) .PHONY: all install clean $(BINARY): $(OBJS) - $(CXX) $(OBJS) -o $(BINARY) $(CXXFLAGS) $(LIBS) + $(CXX) $(OBJS) -o $(BINARY) $(CXXFLAGS) -lmount $(LIBS) %.o: %.c++ $(CXX) -std=gnu++11 $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ install: all - mkdir -p $(DESTDIR)$(BINDIR) - install $(BINARY) $(DESTDIR)$(BINDIR) + mkdir -p $(DESTDIR)$(SBINDIR) + install $(BINARY) $(DESTDIR)$(SBINDIR) + mkdir -p $(DESTDIR)$(MANDIR)/man8 + cp init.8 $(DESTDIR)$(MANDIR)/man8/init.8 clean: rm -f $(BINARY) $(OBJS) *.o diff --git a/init/init.8 b/init/init.8 new file mode 100644 index 00000000..dbef876e --- /dev/null +++ b/init/init.8 @@ -0,0 +1,182 @@ +.Dd $Mdocdate: October 5 2015 $ +.Dt INIT 8 +.Os +.Sh NAME +.Nm init +.Nd system initialization +.Sh SYNOPSIS +.Nm init +.Op Fl \-target Ns "=" Ns Ar init-target +.Op Fl \-chain Ns "=" Ns Ar path-or-uuid +.Sh DESCRIPTION +.Nm +is the first program run after system startup and is responsible for +initializing the operating system and starting the specified +.Ar init-target . +This is normally a login screen, a root shell, or a dedicated special purpose +program. +.Pp +The +.Xr kernel 7 +starts the system in a temporary environment with a root filesystem +backed by system memory and extracts the +.Xr initrd 7 +into it. The kernel runs the +.Pa /sbin/init +program of the system memory root filesystem as the first process. If the +system is bootable cdrom, then the initrd will be a fully functional system and +.Nm +will start a live environment or an operating system installer. If the +system is installed on a harddisk, then the initrd is a minimal system made with +.Xr update-initrd 8 +that will search for the actual root filesystem and chain init it. The next +stage init will recognize it as the intended system and complete the system +startup. +.Ss Initialization Target +.Nm +first determines its target from the +.Fl \-target +option if specified or +.Pa /etc/init/target +otherwise. Supported targets are: +.Pp +.Bl -tag -width "single-user" -compact -offset indent +.It chain +mount real root filesystem and run its +.Nm +.It multi-user +boot to +.Xr login 8 +.It single-user +boot to root shell without password (not secure) +.It sysinstall +boot to operating system installer (not secure) +.It sysupgrade +boot to operating system upgrader (not secure) +.El +.Pp +It is a full system compromise if unauthenticated users are able to boot the +wrong target. The kernel command line can specify the path to +.Nm +and its arguments. Unprivileged users can change the kernel command line from +the bootloader command line if it hasn't been password protected. Likewise +unprivileged users can use their own replacement bootloader by booting a +portable device under their control if the firmware configuration has not been +password protected. +.Ss Partition Creation +.Nm +will scan every block device for valid partition tables and create the +corresponding partition devices in +.Pa /dev . +.Ss Chain Initialization +The chain target triggers a search for the root filesystem. The +.Fl \-chain Ns "=" Ns Ar path-or-uuid +option implies +.Fl \-target Ns "=" Ns chain +if it is not set and specifies the method to locate the root filesystem. The +.Ar path-or-uuid +value is either a path to a directory, in which case it is used as the root +filesystem, or a file that contains a uuid of the root filesystem, or it can be +a valid uuid of the intended root filesystem. If the +.Fl \-chain +option is not specified, the root filesystem of +.Pa /etc/fstab +is used as described in +.Xr fstab 5 . +.Pp +Every block device and partition is scanned to determine if it is the filesystem +by matching the desired uuid. The root filesystem is checked for consistency +and mounted at +.Pa /tmp/fs.XXXXXX +and the +.Pa /dev +filesystem directory is bound at +.Pa /tmp/fs.XXXXXX/dev . +.Pp +Finally the +.Pa /sbin/init +program of the target root filesystem is run inside a chroot. +.Ss Configuration +Once the +.Nm +of the real root filesystem runs, it will process basic configuration files and +apply them: +.Pp +.Bl -tag -width "/etc/videomode" -compact -offset indent +.It Pa /etc/hostname +set hostname (see +.Xr hostname 5 ) +.It Pa /etc/kblayout +set keyboard layout (see +.Xr kblayout 5 ) +.It Pa /etc/videomode +set graphics resolution (see +.Xr videomode 5 ) +.El +.Ss Session +Finally +.Nm +will start the target program according to its initialization target. This will +be a login screen, a root shell, or something else. If the process exits +abnormally +.Nm +will automatically restart it. +.Nm +will exit with the same exit status as the process if it exits +normally. The kernel decides whether to power off, reboot or halt based on this +exit status. +.Sh ENVIRONMENT +.Nm +sets the following environment variables. +.Bl -tag -width "INIT_PID" +.It Ev HOME +root's home directory +.It Ev INIT_PID +.Nm Ns 's +process id +.It Ev LOGNAME +root +.It Ev PATH +.Pa /bin:/sbin +.It Ev SHELL +root's shell +.It Ev TERM +sortix +.It Ev USER +root +.El +.Sh FILES +.Bl -tag -width "/etc/init/target" -compact +.It Pa /etc/init/target +default initialization target +.It Pa /etc/fstab +filesystem table (see +.Xr fstab 5 ) +.It Pa /etc/hostname +hostname (see +.Xr hostname 5 ) +.It Pa /etc/kblayout +keyboard layout (see +.Xr kblayout 5 ) +.It Pa /etc/videomode +graphics resolution (see +.Xr videomode 5 ) +.El +.Sh EXIT STATUS +.Nm +exits 0 if the kernel should power off, exits 1 if the kernel should reboot, or +exits 2 if the boot failed and the kernel should halt. Any other exit by the +initial +.Nm +will trigger a kernel panic. +.Nm +exits with the same exit status as its target session if it terminates normally. +.Sh SEE ALSO +.Xr fstab 5 , +.Xr hostname 5 , +.Xr kblayout 5 , +.Xr videomode 5 , +.Xr initrd 7 , +.Xr kernel 7 , +.Xr login 8 , +.Xr update-initrd 8 diff --git a/init/init.c++ b/init/init.c++ index d9c2c8a4..529d6ed8 100644 --- a/init/init.c++ +++ b/init/init.c++ @@ -23,32 +23,42 @@ #define __STDC_CONSTANT_MACROS #define __STDC_LIMIT_MACROS +#include #include #include -#include #include #include #include -#include +#include #include #include #include #include +#include #include +#include #include #include #include #include #include #include +#include #include #include #include #include -char* read_single_line(FILE* fp) +#include +#include +#include +#include +#include +#include + +static char* read_single_line(FILE* fp) { char* ret = NULL; size_t ret_size = 0; @@ -63,505 +73,845 @@ char* read_single_line(FILE* fp) return ret; } -__attribute__((format(printf, 1, 2))) -char* print_string(const char* format, ...) -{ - char* ret; - va_list ap; - va_start(ap, format); - int status = vasprintf(&ret, format, ap); - va_end(ap); - assert(0 <= status); - return ret; -} - -char* join_paths(const char* a, const char* b) +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] == '/'; - return has_slash ? print_string("%s%s", a, b) - : print_string("%s/%s", a, b); + 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; } -typedef struct +__attribute__((noreturn)) +__attribute__((format(printf, 1, 2))) +static void fatal(const char* format, ...) { - char** strings; - size_t length; - size_t capacity; -} string_array_t; - -string_array_t string_array_make() -{ - string_array_t sa; - sa.strings = NULL; - sa.length = sa.capacity = 0; - return sa; + 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); + _exit(2); } -void string_array_reset(string_array_t* sa) +__attribute__((format(printf, 1, 2))) +static void warning(const char* format, ...) { - for ( size_t i = 0; i < sa->length; i++ ) - free(sa->strings[i]); - free(sa->strings); - *sa = string_array_make(); + va_list ap; + va_start(ap, format); + fprintf(stderr, "%s: warning: ", program_invocation_name); + vfprintf(stderr, format, ap); + fprintf(stderr, "\n"); + fflush(stderr); + va_end(ap); } -size_t string_array_find(string_array_t* sa, const char* str) +__attribute__((format(printf, 1, 2))) +static void note(const char* format, ...) { - for ( size_t i = 0; i < sa->length; i++ ) - if ( !strcmp(sa->strings[i], str) ) - return i; - return SIZE_MAX; + va_list ap; + va_start(ap, format); + fprintf(stderr, "%s: ", program_invocation_name); + vfprintf(stderr, format, ap); + fprintf(stderr, "\n"); + fflush(stderr); + va_end(ap); } -bool string_array_contains(string_array_t* sa, const char* str) +struct harddisk** hds = NULL; +size_t hds_used = 0; +size_t hds_length = 0; + +void prepare_filesystem(const char* path, struct blockdevice* bdev) { - return string_array_find(sa, str) != SIZE_MAX; + 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)); } -bool string_array_append(string_array_t* sa, const char* str) +bool prepare_block_device(void* ctx, const char* path) { - if ( sa->length == sa->capacity ) + (void) ctx; + struct harddisk* hd = harddisk_openat(AT_FDCWD, path, O_RDONLY); + if ( !hd ) { - size_t new_capacity = sa->capacity ? sa->capacity * 2 : 8; - size_t new_size = sizeof(char*) * new_capacity; - char** new_strings = (char**) realloc(sa->strings, new_size); - if ( !new_strings ) - return false; - sa->strings = new_strings; - sa->capacity = new_capacity; + 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 ) + return true; + if ( errno == EINVAL ) + return warning("%s: %m", path), true; + fatal("%s: %m", path); + } + if ( hds_used == hds_length ) + { + // TODO: Potential overflow. + size_t new_length = hds_length ? 2 * hds_length : 16; + struct harddisk** new_hds = (struct harddisk**) + reallocarray(hds, new_length, sizeof(struct harddisk*)); + if ( !new_hds ) + fatal("realloc: %m"); + hds = new_hds; + hds_length = new_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); } - char* copy = str ? strdup(str) : NULL; - if ( str && !copy ) - return false; - sa->strings[sa->length++] = copy; return true; } -int child() +void prepare_block_devices() { - pid_t init_pid = getppid(); - char init_pid_str[sizeof(pid_t)*3]; - snprintf(init_pid_str, sizeof(pid_t)*3, "%ju", (uintmax_t) init_pid); - setenv("INIT_PID", init_pid_str, 1); - - setpgid(0, 0); - - sigset_t oldset, sigs; - sigemptyset(&sigs); - sigaddset(&sigs, SIGTTOU); - sigprocmask(SIG_BLOCK, &sigs, &oldset); - tcsetpgrp(0, getpgid(0)); - sigprocmask(SIG_SETMASK, &oldset, NULL); - - unsigned int termmode = 0; - gettermmode(0, &termmode); - settermmode(0, termmode & ~TERMMODE_DISABLE); - - const char* default_shell = "sh"; - const char* default_home = "/root"; - const char* shell; - const char* home; - if ( struct passwd* passwd = getpwuid(getuid()) ) - { - setenv("USERNAME", passwd->pw_name, 1); - home = passwd->pw_dir[0] ? passwd->pw_dir : default_home; - setenv("HOME", home, 1); - shell = passwd->pw_shell[0] ? passwd->pw_shell : default_shell; - setenv("SHELL", shell, 1); - } - else - { - setenv("USERNAME", "root", 1); - setenv("HOME", home = default_home, 1); - setenv("SHELL", shell = default_shell, 1); - } - - chdir(home); - - const char* newargv[] = { shell, NULL }; - - execvp(shell, (char* const*) newargv); - error(0, errno, "%s", shell); - - return 2; -} - -int runsystem() -{ - pid_t childpid = fork(); - if ( childpid < 0 ) - error(2, errno, "fork"); - - if ( childpid ) - { - int status; - waitpid(childpid, &status, 0); - // TODO: Use the proper macro! - if ( 128 <= WEXITSTATUS(status) || WIFSIGNALED(status) ) - { - printf("Looks like the system crashed, trying to bring it back up.\n"); - return runsystem(); - } - return WEXITSTATUS(status); - } - - exit(child()); -} - -int chain_boot_path(const char* path) -{ - // Run the next init program and restart it in case of a crash. -try_reboot_system: - if ( pid_t child_pid = fork() ) - { - int status; - waitpid(child_pid, &status, 0); - // TODO: Use the proper macro! - if ( 128 <= WEXITSTATUS(status) || WIFSIGNALED(status) ) - { - printf("Looks like the system crashed, trying to bring it back up.\n"); - goto try_reboot_system; - } - return WEXITSTATUS(status); - } - - // Switch to the new root directory, - chroot(path); - chdir("/"); - - const char* init_path = "/bin/init"; - execl(init_path, init_path, NULL); - exit(127); -} - -int init_emergency(int errnum, const char* format, ...) -{ - fprintf(stderr, "init: emergency: "); - va_list ap; - va_start(ap, format); - vfprintf(stderr, format, ap); - va_end(ap); - if ( errnum ) - fprintf(stderr, ": %s", strerror(errnum)); - fprintf(stderr, "\n"); - - fprintf(stderr, "init: Dropping you to an emergency shell.\n"); - fprintf(stderr, "init: Run `init' again when you have resolved the " - "situation to continue.\n"); - - return runsystem(); -} - -void add_block_devices_to_string_array(const char* path, string_array_t* sa) -{ - DIR* dir = opendir(path); - if ( !dir ) + static bool done = false; + if ( done ) return; - while ( struct dirent* entry = readdir(dir) ) - { - if ( entry->d_name[0] == '.' ) - continue; - char* dev_path = join_paths(path, entry->d_name); - struct stat st; - if ( !(stat(dev_path, &st) == 0 && - S_ISBLK(st.st_mode) && - string_array_append(sa, dev_path)) ) - free(dev_path); - } - closedir(dir); + done = true; + + if ( !devices_iterate_path(prepare_block_device, NULL) ) + fatal("iterating devices: %m"); } -bool is_ext2_filesystem(const char* path, const char* uuid = NULL) +struct device_match { - if ( pid_t child_pid = fork() ) - { - int exit_status; - waitpid(child_pid, &exit_status, 0); - return WIFEXITED(exit_status) && WEXITSTATUS(exit_status) == 0; - } - if ( uuid ) - execlp("extfs", "extfs", "--probe", "--test-uuid", uuid, path, NULL); - else - execlp("extfs", "extfs", "--probe", path, NULL); - exit(127); -} + const char* path; + struct blockdevice* bdev; +}; -bool is_master_boot_record(const char* path) +void search_by_uuid(const char* uuid_string, + void (*cb)(void*, struct device_match*), + void* ctx) { - if ( pid_t child_pid = fork() ) + unsigned char uuid[16]; + uuid_from_string(uuid, uuid_string); + for ( size_t i = 0; i < hds_used; i++ ) { - int exit_status; - waitpid(child_pid, &exit_status, 0); - return WIFEXITED(exit_status) && WEXITSTATUS(exit_status) == 0; - } - execlp("mbrfs", "mbrfs", "--probe", path, NULL); - exit(127); -} - -bool create_master_boot_record_partitions(const char* path, string_array_t* sa) -{ - int pipe_fds[2]; - pipe(pipe_fds); - if ( pid_t child_pid = fork() ) - { - close(pipe_fds[1]); - FILE* mbrfp = fdopen(pipe_fds[0], "r"); - while ( char* partition = read_single_line(mbrfp) ) + struct blockdevice* bdev = &hds[i]->bdev; + if ( bdev->fs ) { - if ( string_array_contains(sa, partition) || - !string_array_append(sa, partition) ) - free(partition); + 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); } - fclose(mbrfp); - int exit_status; - waitpid(child_pid, &exit_status, 0); - return WIFEXITED(exit_status) && WEXITSTATUS(exit_status) == 0; - } - dup2(pipe_fds[1], 1); - close(pipe_fds[0]); - close(pipe_fds[1]); - execlp("mbrfs", "mbrfs", path, NULL); - exit(127); -} - -int chain_boot_device(const char* dev_path) -{ - // Create a directory where we will mount the root filesystem. - const char* mount_point = "/fs"; - const char* mount_point_dev = "/fs/dev"; - mkdir(mount_point, 0666); - - // Get information about the mount point before mounting. - struct stat orig_st, new_st; - stat(mount_point, &orig_st); - - // Spawn the filesystem server for the root filesystem. - pid_t fs_pid = fork(); - if ( !fs_pid ) - { - execlp("extfs", "extfs", "--foreground", dev_path, mount_point, NULL); - exit(127); - } - - // Wait for the filesystem server to come online. - struct timespec mount_wait_ts = timespec_make(0, 50L * 1000L * 1000L); - do nanosleep(&mount_wait_ts, NULL), stat(mount_point, &new_st); - while ( new_st.st_ino == orig_st.st_ino && new_st.st_dev == orig_st.st_dev ); - - // Create a device directory in the root filesystem. - mkdir(mount_point_dev, 0666); - - // Mount the current device directory inside the new root filesystem. - int old_dev_fd = open("/dev", O_DIRECTORY | O_RDONLY); - int new_dev_fd = open(mount_point_dev, O_DIRECTORY | O_RDONLY); - fsm_fsbind(old_dev_fd, new_dev_fd, 0); - close(new_dev_fd); - close(old_dev_fd); - - int ret = chain_boot_path(mount_point); - - int root_fd = open(mount_point, O_RDONLY); - if ( 0 <= root_fd ) - { - fsync(root_fd); - close(root_fd); - } - - unmount(mount_point, 0); - - int fs_exitstatus; - waitpid(fs_pid, &fs_exitstatus, 0); - - if ( ret == 127 ) - return init_emergency(errno, "Unable to locate the next init program"); - return ret; -} - -int chain_boot_uuid(const char* root_uuid) -{ - string_array_t block_devices = string_array_make(); - add_block_devices_to_string_array("/dev", &block_devices); - - string_array_t root_block_devices = string_array_make(); - - // Scan through all the block devices and check for a filesystem with the - // desired uuid while creating partitions if encountering partition tables. - for ( size_t i = 0; i < block_devices.length; i++ ) - { - const char* device_path = block_devices.strings[i]; - assert(device_path); - if ( is_ext2_filesystem(device_path) ) + else if ( bdev->pt ) { - if ( is_ext2_filesystem(device_path, root_uuid) ) - string_array_append(&root_block_devices, device_path); - } - else if ( is_master_boot_record(device_path) ) - { - create_master_boot_record_partitions(device_path, &block_devices); + 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); + } } } +} - string_array_reset(&block_devices); - - // Panic if we are unable to locate the desired root filesystem. - if ( !root_block_devices.length ) - return init_emergency(0, "Unable to locate root filesystem with uuid=" - "`%s'", root_uuid); - - // If we only found a single matching filesystem, we can just boot it. - if ( root_block_devices.length == 1 ) - return chain_boot_device(root_block_devices.strings[0]); - - // Handle the case where multiple root filesystems with the correct uuid is - // found - we have to ask the user for help in this case. - fprintf(stderr, "init: Found multiple devices with uuid=`%s'.\n", root_uuid); - fprintf(stderr, "init: Select the correct boot device or nothing to get an emergency shell.\n"); -retry_ask_root_block_device: - for ( size_t i = 0; i < root_block_devices.length; i++ ) - fprintf(stderr, "%zu.\t%s\n", i, root_block_devices.strings[i]); - printf("Enter index or name of boot device [root shell]: "); - fflush(stdout); - - char* input = read_single_line(stdin); - if ( !input ) - return init_emergency(errno, "Unable read line from standard input"); - - if ( !input[0] ) - return init_emergency(0, "ambigious root filesystem - shell selected"); - - char* input_end; - unsigned long index = strtoul(input, &input_end, 0); - if ( *input_end ) +void ensure_single_device_match(void* ctx, struct device_match* match) +{ + struct device_match* result = (struct device_match*) ctx; + if ( result->path ) { - if ( string_array_contains(&root_block_devices, input) ) - return chain_boot_device(input); - fprintf(stderr, "init: error: `%s' is not an allowed choice\n", input); - goto retry_ask_root_block_device; + if ( result->bdev ) + note("duplicate match: %s", result->path); + result->bdev = NULL; + note("duplicate match: %s", match->path); + return; } - - if ( root_block_devices.length <= index ) - { - fprintf(stderr, "init: error: `%lu' is not an allowed choice\n", index); - goto retry_ask_root_block_device; - } - - return chain_boot_device(root_block_devices.strings[index]); + *result = *match; } void set_hostname() { - FILE* hostname_fp = fopen("/etc/hostname", "r"); - if ( !hostname_fp ) - { - if ( errno == ENOENT ) - return; - error(0, errno, "unable to open /etc/hostname, hostname is not set"); + FILE* fp = fopen("/etc/hostname", "r"); + if ( !fp && errno == ENOENT ) return; - } - - char* hostname = read_single_line(hostname_fp); + if ( !fp ) + return warning("unable to set hostname: /etc/hostname: %m"); + char* hostname = read_single_line(fp); if ( !hostname ) - { - error(0, errno, "unable to read /etc/hostname, hostname is not set"); - fclose(hostname_fp); - return; - } - - fclose(hostname_fp); - - if ( sethostname(hostname, strlen(hostname) + 1) < 0 ) - { - error(0, errno, "unable to set hostname to `%s'", hostname); - free(hostname); - return; - } - + return warning("unable to set hostname: /etc/hostname: %m"); + fclose(fp); + int ret = sethostname(hostname, strlen(hostname) + 1); free(hostname); + if ( ret < 0 ) + return warning("unable to set hostname: `%s': %m", hostname); } void set_kblayout() { - FILE* kblayout_fp = fopen("/etc/kblayout", "r"); - if ( !kblayout_fp ) - { - if ( errno == ENOENT ) - return; - error(0, errno, "unable to open /etc/kblayout, keyboard layout is not set"); + FILE* fp = fopen("/etc/kblayout", "r"); + if ( !fp && errno == ENOENT ) return; - } - - char* kblayout = read_single_line(kblayout_fp); + if ( !fp ) + return warning("unable to set keyboard layout: /etc/kblayout: %m"); + char* kblayout = read_single_line(fp); if ( !kblayout ) - { - error(0, errno, "unable to read /etc/kblayout, keyboard layout is not set"); - fclose(kblayout_fp); - return; - } - - fclose(kblayout_fp); - + return warning("unable to set keyboard layout: /etc/kblayout: %m"); + fclose(fp); pid_t child_pid = fork(); if ( child_pid < 0 ) - { - error(0, errno, "setting keyboard layout: fork"); - free(kblayout); - return; - } - + return warning("unable to set keyboard layout: fork: %m"); if ( !child_pid ) { execlp("chkblayout", "chkblayout", "--", kblayout, (char*) NULL); - error(0, errno, "setting keyboard layout: exec: chkblayout"); + warning("setting keyboard layout: chkblayout: %m"); _exit(127); } - int status; waitpid(child_pid, &status, 0); - free(kblayout); } +void set_videomode() +{ + FILE* fp = fopen("/etc/videomode", "r"); + if ( !fp && errno == ENOENT ) + return; + if ( !fp ) + return warning("unable to set video mode: /etc/videomode: %m"); + char* videomode = read_single_line(fp); + if ( !videomode ) + return warning("unable to set video mode: /etc/videomode: %m"); + fclose(fp); + unsigned int xres = 0; + unsigned int yres = 0; + unsigned int bpp = 0; + if ( sscanf(videomode, "%ux%ux%u", &xres, &yres, &bpp) != 3 ) + { + warning("/etc/videomode: Invalid video mode `%s'", videomode); + free(videomode); + return; + } + free(videomode); + struct dispmsg_set_crtc_mode set_mode; + memset(&set_mode, 0, sizeof(set_mode)); + set_mode.msgid = DISPMSG_SET_CRTC_MODE; + set_mode.device = 0; + set_mode.connector = 0; + set_mode.mode.driver_index = 0; + set_mode.mode.magic = 0; + set_mode.mode.control = DISPMSG_CONTROL_VALID; + set_mode.mode.fb_format = bpp; + set_mode.mode.view_xres = xres; + set_mode.mode.view_yres = yres; + set_mode.mode.fb_location = 0; + set_mode.mode.pitch = xres * (bpp / 8); + set_mode.mode.surf_off_x = 0; + set_mode.mode.surf_off_y = 0; + set_mode.mode.start_x = 0; + set_mode.mode.start_y = 0; + set_mode.mode.end_x = 0; + set_mode.mode.end_y = 0; + set_mode.mode.desktop_height = yres; + if ( dispmsg_issue(&set_mode, sizeof(set_mode)) < 0 ) + warning("/etc/videomode: Failed to set video mode `%ux%ux%u': %m", + xres, yres, bpp); +} + +void init_early() +{ + static bool done = false; + if ( done ) + return; + done = true; + + // Make sure that we have a /tmp directory. + umask(0000); + mkdir("/tmp", 01777); + + // Set the default file creation mask. + umask(0022); + + // Set up the PATH variable. + if ( setenv("PATH", "/bin:/sbin", 1) < 0 ) + fatal("setenv: %m"); + + // Set the terminal type. + if ( setenv("TERM", "sortix", 1) < 0 ) + fatal("setenv: %m"); +} + +int init(const char* target) +{ + if ( getenv("INIT_PID") ) + fatal("System is already managed by an init process"); + init_early(); + set_hostname(); + set_kblayout(); + set_videomode(); + prepare_block_devices(); + sigset_t oldset, sigttou; + sigemptyset(&sigttou); + sigaddset(&sigttou, SIGTTOU); + while ( true ) + { + struct termios tio; + if ( tcgetattr(0, &tio) ) + fatal("tcgetattr: %m"); + pid_t child_pid = fork(); + if ( child_pid < 0 ) + fatal("fork: %m"); + if ( !child_pid ) + { + uid_t uid = getuid(); + pid_t pid = getpid(); + pid_t ppid = getppid(); + if ( setpgid(0, 0) < 0 ) + fatal("setpgid: %m"); + sigprocmask(SIG_BLOCK, &sigttou, &oldset); + if ( tcsetpgrp(0, pid) < 0 ) + fatal("tcsetpgrp: %m"); + sigprocmask(SIG_SETMASK, &oldset, NULL); + struct passwd* pwd = getpwuid(uid); + if ( !pwd ) + fatal("looking up user by uid %" PRIuUID ": %m", uid); + const char* home = pwd->pw_dir[0] ? pwd->pw_dir : "/"; + const char* shell = pwd->pw_shell[0] ? pwd->pw_shell : "sh"; + char ppid_str[sizeof(pid_t) * 3]; + snprintf(ppid_str, sizeof(ppid_str), "%" PRIiPID, ppid); + if ( setenv("INIT_PID", ppid_str, 1) < 0 || + setenv("LOGNAME", pwd->pw_name, 1) < 0 || + setenv("USER", pwd->pw_name, 1) < 0 || + setenv("HOME", home, 1) < 0 || + setenv("SHELL", shell, 1) < 0 ) + fatal("setenv: %m"); + if ( chdir(home) < 0 ) + warning("chdir: %s: %m", home); + const char* program = "login"; + bool activate_terminal = false; + if ( !strcmp(target, "single-user") ) + { + activate_terminal = true; + program = shell; + } + if ( activate_terminal ) + { + tio.c_cflag |= CREAD; + if ( tcsetattr(0, TCSANOW, &tio) ) + fatal("tcgetattr: %m"); + } + const char* argv[] = { program, NULL }; + execvp(program, (char* const*) argv); + fatal("%s: %m", program); + } + int status; + if ( waitpid(child_pid, &status, 0) < 0 ) + fatal("waitpid"); + sigprocmask(SIG_BLOCK, &sigttou, &oldset); + if ( tcsetattr(0, TCSAFLUSH, &tio) ) + fatal("tcgetattr: %m"); + if ( tcsetpgrp(0, getpgid(0)) < 0 ) + fatal("tcsetpgrp: %m"); + sigprocmask(SIG_SETMASK, &oldset, NULL); + const char* back = ": Trying to bring it back up again"; + if ( WIFEXITED(status) ) + return WEXITSTATUS(status); + else if ( WIFSIGNALED(status) ) + note("session: %s%s", strsignal(WTERMSIG(status)), back); + else + note("session: Unexpected unusual termination%s", back); + } +} + +static pid_t chain_init_pid_by_path = -1; +static char* chain_mount_point_dev = NULL; +static void init_chain_by_path_unmount(void) +{ + if ( chain_init_pid_by_path != getpid() ) + return; + if ( chain_mount_point_dev ) + { + unmount(chain_mount_point_dev, 0); + free(chain_mount_point_dev); + chain_mount_point_dev = NULL; + } +} + +int init_chain_by_path(const char* path) +{ + struct stat rootst; + struct stat pathst; + if ( stat("/", &rootst) < 0 ) + fatal("stat: /: %m"); + if ( stat(path, &pathst) < 0 ) + fatal("stat: %s: %m", path); + if ( rootst.st_dev == pathst.st_dev && rootst.st_ino == pathst.st_ino ) + { + if ( getenv("INIT_PID") ) + fatal("System is already managed by an init process"); + } + init_early(); + chain_init_pid_by_path = getpid(); + if ( atexit(init_chain_by_path_unmount) != 0 ) + fatal("atexit: %m"); + if ( !(chain_mount_point_dev = join_paths(path, "dev")) ) + fatal("asprintf: %m"); + if ( mkdir(chain_mount_point_dev, 755) < 0 && errno != EEXIST ) + fatal("mkdir: %s: %m", chain_mount_point_dev); + int old_dev_fd = open("/dev", O_DIRECTORY | O_RDONLY); + if ( old_dev_fd < 0 ) + fatal("%s: %m", "/dev"); + int new_dev_fd = open(chain_mount_point_dev, O_DIRECTORY | O_RDONLY); + if ( new_dev_fd < 0 ) + fatal("%s: %m", chain_mount_point_dev); + if ( fsm_fsbind(old_dev_fd, new_dev_fd, 0) < 0 ) + fatal("mount: `%s' onto `%s': %m", "/dev", chain_mount_point_dev); + close(new_dev_fd); + close(old_dev_fd); + while ( true ) + { + pid_t child_pid = fork(); + if ( child_pid < 0 ) + fatal("fork: %m"); + if ( !child_pid ) + { + if ( chroot(path) < 0 ) + fatal("chroot: %s: %m", path); + if ( chdir("/") < 0 ) + fatal("chdir: %s: %m", path); + unsetenv("INIT_PID"); + const char* argv[] = { "init", NULL }; + execv("/sbin/init", (char* const*) argv); + fatal("Failed to load chain init: %s: %m", argv[0]); + } + int status; + if ( waitpid(child_pid, &status, 0) < 0 ) + fatal("waitpid"); + const char* back = ": Trying to bring it back up again"; + if ( WIFEXITED(status) ) + return WEXITSTATUS(status); + else if ( WIFSIGNALED(status) ) + note("chain init: %s%s", strsignal(WTERMSIG(status)), back); + else + note("chain init: Unexpected unusual termination%s", back); + } +} + +static pid_t chain_init_pid_by_filesystem = -1; +static char* chain_mount_point = NULL; +static pid_t chain_mount_pid = -1; +static void init_chain_by_filesystem_unmount(void) +{ + if ( chain_init_pid_by_filesystem != getpid() ) + return; + if ( chain_mount_point ) + { + if ( unmount(chain_mount_point, 0) < 0 && errno != ENOMOUNT ) + warning("unmount: %s: %m", chain_mount_point); + else if ( errno == ENOMOUNT && 0 <= chain_mount_pid ) + kill(chain_mount_pid, SIGQUIT); + } + if ( 0 <= chain_mount_pid ) + { + int code; + if ( waitpid(chain_mount_pid, &code, 0) < 0 ) + note("waitpid: %m"); + chain_mount_pid = -1; + } + if ( chain_mount_point ) + { + if ( chain_mount_point && rmdir(chain_mount_point) < 0 ) + warning("rmdir: %s: %m", chain_mount_point); + free(chain_mount_point); + } + chain_mount_point = NULL; +} + +int init_chain_by_filesystem(struct filesystem* fs) +{ + // TODO: It would be ideal to get an exclusive lock so that no other + // processes have currently mounted that filesystem. + struct blockdevice* bdev = fs->bdev; + const char* bdev_path = bdev->p ? bdev->p->path : bdev->hd->path; + assert(bdev_path); + do if ( fs->flags & (FILESYSTEM_FLAG_FSCK_SHOULD | FILESYSTEM_FLAG_FSCK_MUST) ) + { + assert(fs->fsck); + if ( fs->flags & FILESYSTEM_FLAG_FSCK_MUST ) + note("%s: Repairing filesystem due to inconsistency...", bdev_path); + else + note("%s: Checking filesystem consistency...", bdev_path); + pid_t child_pid = fork(); + if ( child_pid < 0 ) + { + if ( fs->flags & FILESYSTEM_FLAG_FSCK_MUST ) + fatal("%s: Mandatory repair failed: fork: %m", bdev_path); + warning("%s: Skipping filesystem check: fork: %m:", bdev_path); + break; + } + if ( child_pid == 0 ) + { + execlp(fs->fsck, fs->fsck, "-fp", "--", bdev_path, (const char*) NULL); + note("%s: Failed to load filesystem checker: %s: %m", bdev_path, fs->fsck); + _Exit(127); + } + int code; + if ( waitpid(child_pid, &code, 0) < 0 ) + fatal("waitpid: %m"); + if ( fs->flags & FILESYSTEM_FLAG_FSCK_MUST ) + { + if ( WIFSIGNALED(code) ) + fatal("%s: Mandatory repair failed: %s: %s", bdev_path, + fs->fsck, strsignal(WTERMSIG(code))); + else if ( !WIFEXITED(code) ) + fatal("%s: Mandatory repair failed: %s: %s", bdev_path, + fs->fsck, "Unexpected unusual termination"); + else if ( WEXITSTATUS(code) == 127 ) + fatal("%s: Mandatory repair failed: %s: %s", bdev_path, + fs->fsck, "Filesystem checker is absent"); + else if ( WEXITSTATUS(code) & 2 ) + fatal("%s: Mandatory repair: %s: %s", bdev_path, + fs->fsck, "System reboot is necessary"); + else if ( WEXITSTATUS(code) != 0 && WEXITSTATUS(code) != 1 ) + fatal("%s: Mandatory repair failed: %s: %s", bdev_path, + fs->fsck, "Filesystem checker was unsuccessful"); + } + else + { + if ( WIFSIGNALED(code) ) + fatal("%s: Filesystem check failed: %s: %s", bdev_path, + fs->fsck, strsignal(WTERMSIG(code))); + else if ( !WIFEXITED(code) ) + fatal("%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 ) + fatal("%s: Filesystem check: %s: %s", bdev_path, + fs->fsck, "System reboot is necessary"); + else if ( WEXITSTATUS(code) != 0 && WEXITSTATUS(code) != 1 ) + fatal("%s: Filesystem check failed: %s: %s", bdev_path, + fs->fsck, "Filesystem checker was unsuccessful"); + } + } while ( 0 ); + if ( !fs->driver ) + fatal("%s: Don't know how to mount a %s filesystem", + bdev_path, fs->fstype_name); + chain_init_pid_by_filesystem = getpid(); + if ( atexit(init_chain_by_filesystem_unmount) != 0 ) + fatal("atexit: %m"); + if ( !(chain_mount_point = strdup("/tmp/fs.XXXXXX")) ) + fatal("strdup: %m"); + if ( !mkdtemp(chain_mount_point) ) + fatal("mkdtemp: %s: %m", "/tmp/fs.XXXXXX"); + struct stat st; + if ( stat(chain_mount_point, &st) < 0 ) + fatal("stat: %s: %m", chain_mount_point); + if ( (chain_mount_pid = fork()) < 0 ) + fatal("%s: Unable to mount: fork: %m", bdev_path); + // TODO: This design is broken. The filesystem should tell us when it is + // ready instead of having to poll like this. + if ( chain_mount_pid == 0 ) + { + execlp(fs->driver, fs->driver, "--foreground", "--pretend-mount-path=/", + bdev_path, chain_mount_point, (const char*) NULL); + warning("%s: Failed to load filesystem driver: %s: %m", bdev_path, fs->driver); + _exit(127); + } + while ( true ) + { + struct stat newst; + if ( stat(chain_mount_point, &newst) < 0 ) + fatal("stat: %s: %m", chain_mount_point); + if ( newst.st_dev != st.st_dev || newst.st_ino != st.st_ino ) + break; + int code; + pid_t child = waitpid(chain_mount_pid, &code, WNOHANG); + if ( child < 0 ) + fatal("waitpid: %m"); + if ( child != 0 ) + { + chain_mount_pid = -1; + if ( WIFSIGNALED(code) ) + fatal("%s: Mount failed: %s: %s", bdev_path, fs->driver, + strsignal(WTERMSIG(code))); + else if ( !WIFEXITED(code) ) + fatal("%s: Mount failed: %s: %s", bdev_path, fs->driver, + "Unexpected unusual termination"); + else if ( WEXITSTATUS(code) == 127 ) + fatal("%s: Mount failed: %s: %s", bdev_path, fs->driver, + "Filesystem driver is absent"); + else if ( WEXITSTATUS(code) == 0 ) + fatal("%s: Mount failed: %s: Unexpected successful exit", + bdev_path, fs->driver); + else + fatal("%s: Mount failed: %s: Exited with status %i", bdev_path, + fs->driver, WEXITSTATUS(code)); + } + struct timespec delay = timespec_make(0, 50L * 1000L * 1000L); + nanosleep(&delay, NULL); + } + int result = init_chain_by_path(chain_mount_point); + init_chain_by_filesystem_unmount(); + return result; +} + +int init_chain_by_uuid(const char* uuid) +{ + init_early(); + prepare_block_devices(); + if ( !uuid_validate(uuid) ) + fatal("`%s' is not a valid uuid", uuid); + struct device_match match; + memset(&match, 0, sizeof(match)); + search_by_uuid(uuid, ensure_single_device_match, &match); + if ( !match.path ) + fatal("No devices matching uuid %s were found", uuid); + if ( !match.bdev ) + fatal("Don't know which particular device to boot with uuid %s", uuid); + assert(match.bdev->fs); + return init_chain_by_filesystem(match.bdev->fs); +} + +int init_chain_by_uuid_file(const char* path) +{ + // Find the uuid of the root filesystem. + FILE* fp = fopen(path, "r"); + if ( !fp ) + fatal("%s: %m", path); + char* uuid = read_single_line(fp); + if ( !uuid ) + fatal("read: %s: %m", path); + fclose(fp); + if ( !uuid_validate(uuid) ) + fatal("%s: `%s' is not a valid uuid", path, uuid); + int result = init_chain_by_uuid(uuid); + free(uuid); + return result; +} + +int init_chain(const char* parameter) +{ + bool parameter_was_null = parameter == NULL; + if ( !parameter ) + { + if ( !setfsent() ) + fatal("/etc/fstab: %m"); + errno = 0; + struct fstab* fs = getfsfile("/"); + if ( !fs && errno ) + fatal("/etc/fstab: %m"); + if ( !fs ) + fatal("/etc/fstab: Root filesystem not found in filesystem table"); + parameter = fs->fs_spec; + if ( strncmp(parameter, "UUID=", strlen("UUID=")) == 0 ) + parameter += strlen("UUID="); + } + if ( uuid_validate(parameter) ) + return init_chain_by_uuid(parameter); + struct stat st; + if ( stat(parameter, &st) < 0 ) + error(2, errno, "%s", parameter); + if ( S_ISDIR(st.st_mode) && !parameter_was_null ) + return init_chain_by_path(parameter); + // TODO: Implement this case so it works in /etc/fstab. + //else if ( S_ISBLK(st.st_mode) ) + // return init_chain_by_device(parameter); + else if ( S_ISREG(st.st_mode) && !parameter_was_null ) + return init_chain_by_uuid_file(parameter); + else + fatal("%s: Don't how how to chain initialize this", parameter); +} + +static void compact_arguments(int* argc, char*** argv) +{ + for ( int i = 0; i < *argc; i++ ) + { + while ( i < *argc && !(*argv)[i] ) + { + for ( int n = i; n < *argc; n++ ) + (*argv)[n] = (*argv)[n+1]; + (*argc)--; + } + } +} + +static void help(FILE* fp, const char* argv0) +{ + fprintf(fp, "Usage: %s [OPTION]...\n", argv0); + fprintf(fp, "Initialize and manage the userland.\n"); +} + +static void version(FILE* fp, const char* argv0) +{ + fprintf(fp, "%s (Sortix) %s\n", argv0, VERSIONSTR); + fprintf(fp, "License GPLv3+: GNU GPL version 3 or later .\n"); + fprintf(fp, "This is free software: you are free to change and redistribute it.\n"); + fprintf(fp, "There is NO WARRANTY, to the extent permitted by law.\n"); +} + int main(int argc, char* argv[]) { - if ( 3 <= argc && !strcmp(argv[1], "--chain") ) - return chain_boot_device(argv[2]); + setlocale(LC_ALL, ""); - // Set the default file creation mask. - umask(022); + const char* chain = NULL; + const char* target = NULL; - // Set up the PATH variable. - setenv("PATH", "/bin:/sbin", 1); + const char* argv0 = argv[0]; + for ( int i = 1; i < argc; i++ ) + { + const char* arg = argv[i]; + if ( arg[0] != '-' || !arg[1] ) + continue; + argv[i] = NULL; + if ( !strcmp(arg, "--") ) + break; + if ( arg[1] != '-' ) + { + while ( char c = *++arg ) switch ( c ) + { + default: + fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c); + help(stderr, argv0); + exit(1); + } + } + else if ( !strncmp(arg, "--chain=", strlen("--chain=")) ) + chain = arg + strlen("--chain="); + else if ( !strcmp(arg, "--chain") ) + { + if ( i + 1 == argc ) + { + error(0, 0, "option '--chain' requires an argument"); + fprintf(stderr, "Try `%s --help' for more information.\n", argv[0]); + exit(125); + } + chain = argv[i+1]; + argv[++i] = NULL; + } + else if ( !strncmp(arg, "--target=", strlen("--target=")) ) + target = arg + strlen("--target="); + else if ( !strcmp(arg, "--target") ) + { + if ( i + 1 == argc ) + { + error(0, 0, "option '--target' requires an argument"); + fprintf(stderr, "Try `%s --help' for more information.\n", argv[0]); + exit(125); + } + target = argv[i+1]; + argv[++i] = NULL; + } + else if ( !strcmp(arg, "--help") ) + help(stdout, argv0), exit(0); + else if ( !strcmp(arg, "--version") ) + version(stdout, argv0), exit(0); + else + { + fprintf(stderr, "%s: unknown option: %s\n", argv0, arg); + help(stderr, argv0); + exit(1); + } + } - // Set the terminal type. - setenv("TERM", "sortix", 1); + compact_arguments(&argc, &argv); - // Make sure that we have a /tmp directory. - mkdir("/tmp", 01777); + char* target_string = NULL; + if ( !target ) + { + const char* target_path = "/etc/init/target"; + if ( chain ) + target = "chain"; + else if ( access(target_path, F_OK) == 0 ) + { + FILE* target_fp = fopen(target_path, "r"); + if ( !target_fp ) + fatal("%s: %m", target_path); + target_string = read_single_line(target_fp); + if ( !target_string ) + fatal("read: %s: %m", target_path); + target = target_string; + fclose(target_fp); + } + else + target = "single-user"; + } - // Set the hostname as found in /etc/hostname. - set_hostname(); - - // Set the keyboard layout as found in /etc/kblayout. - set_kblayout(); - - // Find the uuid of the root filesystem. - const char* root_uuid_file = "/etc/init/rootfs.uuid"; - FILE* root_uuid_fp = fopen(root_uuid_file, "r"); - - // If there is no uuid of the root filesystem, the current root filesystem - // is the real and final root filesystem and we boot it. - if ( !root_uuid_fp && errno == ENOENT ) - return runsystem(); - - if ( !root_uuid_fp ) - init_emergency(errno, "unable to open: `%s'", root_uuid_file); - - char* root_uuid = read_single_line(root_uuid_fp); - if ( !root_uuid ) - init_emergency(errno, "unable to read: `%s'", root_uuid_file); - - fclose(root_uuid_fp); - - return chain_boot_uuid(root_uuid); + int result; + if ( !strcmp(target, "chain") ) + return init_chain(chain); + else if ( !strcmp(target, "single-user") || + !strcmp(target, "multi-user") ) + return init(target); + else + fatal("Unknown initialization target `%s'", target); + free(target_string); + return result; } diff --git a/kernel/kernel.cpp b/kernel/kernel.cpp index bcdc0332..8eb5efd7 100644 --- a/kernel/kernel.cpp +++ b/kernel/kernel.cpp @@ -586,8 +586,14 @@ static void BootThread(void* /*user*/) switch ( status ) { - case 0: CPU::ShutDown(); - case 1: CPU::Reboot(); + case 0: + CPU::ShutDown(); + case 1: + CPU::Reboot(); + case 2: + Log::Print("kernel: fatal: Halting system due to init fatality\n"); + Log::Sync(); + HaltKernel(); default: PanicF("Init returned with unexpected return code %i", status); } @@ -618,7 +624,7 @@ static void InitThread(void* /*user*/) dtable.Reset(); - static char default_init_cmdline[] = "/bin/init"; + static char default_init_cmdline[] = "/sbin/init"; if ( !init_cmdline ) init_cmdline = default_init_cmdline; diff --git a/share/man/man5/videomode.5 b/share/man/man5/videomode.5 new file mode 100644 index 00000000..26bc4d38 --- /dev/null +++ b/share/man/man5/videomode.5 @@ -0,0 +1,46 @@ +.Dd $Mdocdate: October 5 2015 $ +.Dt VIDEOMODE 5 +.Os +.Sh NAME +.Nm videomode +.Nd initial graphics resolution +.Sh SYNOPSIS +.Nm /etc/videomode +.Sh DESCRIPTION +The +.Nm videomode +file is read on boot by +.Xr init 8 +and is used as the initial graphics resolution on the primary monitor. The +resolution provided by the bootloader remains in effect if the file is missing. +The resolution is usable only if the graphics card has a driver and the +resolution is supported. +.Sh FORMAT +.Ar width Ns x Ns Ar height Ns x Ns Ar bits-per-pixel +.Pp +The file specifies a graphics resolution as a single line with three numeric +fields separated by x characters and no whitespace whatsoever. The first field +is used as the +.Ar width +in pixels, the second field is used as the +.Ar height +in pixels, and the third field is used as the +.Ar bits-per-pixel . +.Sh FILES +.Bl -tag -width "/etc/videomode" -compact +.It Pa /etc/videomode +primary monitor graphics resolution. +.El +.Sh EXAMPLES +.Bd -literal +1920x1080x32 +.Ed +.Sh SEE ALSO +.Xr chvideomode 1 , +.Xr dispmsg_issue 2 , +.Xr init 8 +.Sh BUGS +This scheme only supports a single monitor. The kernel console doesn't handle +resolution changes gracefully, the console will be cleared and the console +cursor might be out of the screen upon resolution shrink. Early boot messages +will be unavailable. diff --git a/share/man/man7/initrd.7 b/share/man/man7/initrd.7 index d860d122..29c3548b 100644 --- a/share/man/man7/initrd.7 +++ b/share/man/man7/initrd.7 @@ -14,7 +14,7 @@ that extracts it into the initial kernel memory root filesystem. The kernel invokes the .Xr init 8 extracted from the initrd as -.Pa /bin/init . +.Pa /sbin/init . .Pp The initrd is in the custom format made by .Xr mkinitrd 8 diff --git a/share/man/man7/kernel.7 b/share/man/man7/kernel.7 index 8ad674db..d0ce93de 100644 --- a/share/man/man7/kernel.7 +++ b/share/man/man7/kernel.7 @@ -22,7 +22,7 @@ The kernel extracts the initrd into the initial kernel memory root filesystem and executes .Xr init 8 as -.Pa /bin/init . +.Pa /sbin/init . The computer is powered off if this process exits 0, rebooted if it exits 1, halted if it exits 2, and paniced otherwise. .Pp