diff --git a/Makefile b/Makefile
index 2b9d0057..d8bbf86b 100644
--- a/Makefile
+++ b/Makefile
@@ -24,6 +24,7 @@ mkinitrd \
regress \
sf \
sh \
+sysinstall \
tix \
trianglix \
update-initrd \
@@ -67,37 +68,13 @@ SYSTEM_INITRD:=$(SORTIX_BUILDS_DIR)/$(BUILD_NAME).system.initrd
.PHONY: all
all: sysroot
-.PHONY: install
-install: sysroot
- @if test -z '$(INSTALL_ROOTFS)' ; then \
- echo "error: You must set INSTALL_ROOTFS to where you want Sortix installed" >&2; \
- exit 1; \
- fi
- @if test -d '$(INSTALL_ROOTFS)' && test -z '$(STUPIDLY_FORCE_SORTIX_INSTALL_OVERWRITE)'; then \
- for ENTRY in $$(ls -A "$(SYSROOT)"); do \
- if test -e "$(INSTALL_ROOTFS)/$$ENTRY"; then \
- echo "Error: Refusing to corrupt the existing installation at $(INSTALL_ROOTFS)" >&2; \
- echo "Use sysmerge to update an existence installation." >&2; \
- exit 1; \
- fi; \
- done; \
- fi
- cp -RTv "$(SYSROOT)" "$(INSTALL_ROOTFS)"
-
.PHONY: sysmerge
sysmerge: sysroot
-ifeq ($(BUILD_IS_SORTIX),0)
- if test -z '$(DESTDIR)' || test 'x$(DESTDIR)' = 'x/'; then \
- echo "error: Refusing to corrupt the local operating system by sysmerging it with Sortix" >&2 \
- exit 1 \
- fi
-endif
- for ENTRY in $$(ls -A "$(SYSROOT)" | grep -Ev '^(dev|etc|home|mnt|root|src|tix|tmp|var)$$'); do \
- cp -RTv "$(SYSROOT)/$$ENTRY" "$(DESTDIR)/$$ENTRY" || exit $$?; \
- done
- cp -TPv "$(SYSROOT)/etc/machine" "$(DESTDIR)/etc/machine"
- cp -TPv "$(SYSROOT)/etc/os-release" "$(DESTDIR)/etc/os-release"
- cp -TPv "$(SYSROOT)/etc/sortix-release" "$(DESTDIR)/etc/sortix-release"
+ "$(SYSROOT)/sbin/sysmerge" "$(SYSROOT)"
+
+.PHONY: sysmerge-wait
+sysmerge-wait: sysroot
+ "$(SYSROOT)/sbin/sysmerge" --wait "$(SYSROOT)"
.PHONY: clean-build-tools
clean-build-tools:
diff --git a/build-aux/iso-grub-cfg.sh b/build-aux/iso-grub-cfg.sh
index 326ef038..f66da626 100755
--- a/build-aux/iso-grub-cfg.sh
+++ b/build-aux/iso-grub-cfg.sh
@@ -138,3 +138,5 @@ EOF
}
menuentry "live environment" ''
+menuentry "new installation" '--init="/sbin/init --target=sysinstall"'
+menuentry "upgrade existing installation" '--init="/sbin/init --target=sysupgrade"'
diff --git a/doc/welcome b/doc/welcome
index a3fa49af..b5884860 100644
--- a/doc/welcome
+++ b/doc/welcome
@@ -1,3 +1,7 @@
You can view the documentation for new users by typing:
man user-guide
+
+You can view the installation instructions by typing:
+
+ man installation
diff --git a/init/init.8 b/init/init.8
index ec29456e..f85fc8cf 100644
--- a/init/init.8
+++ b/init/init.8
@@ -43,6 +43,10 @@ otherwise. Supported targets are:
.It chain
mount real root filesystem and run its
.Nm
+.It chain-merge
+complete a
+.Xr sysmerge 8
+upgrade during a chain boot
.It multi-user
boot to
.Xr login 8
@@ -173,4 +177,5 @@ exits with the same exit status as its target session if it terminates normally.
.Xr initrd 7 ,
.Xr kernel 7 ,
.Xr login 8 ,
+.Xr sysmerge 8 ,
.Xr update-initrd 8
diff --git a/init/init.c++ b/init/init.c++
index e8dc576f..a4857897 100644
--- a/init/init.c++
+++ b/init/init.c++
@@ -765,6 +765,16 @@ static int init(const char* target)
activate_terminal = true;
program = shell;
}
+ if ( !strcmp(target, "sysinstall") )
+ {
+ activate_terminal = true;
+ program = "sysinstall";
+ }
+ if ( !strcmp(target, "sysupgrade") )
+ {
+ program = "sysupgrade";
+ activate_terminal = true;
+ }
if ( activate_terminal )
{
tio.c_cflag |= CREAD;
@@ -868,16 +878,40 @@ static int init_chain(const char* target)
fatal("chroot: %s: %m", chain_location);
if ( chdir("/") < 0 )
fatal("chdir: %s: %m", chain_location);
- (void) target;
- unsetenv("INIT_PID");
- const char* argv[] = { "init", NULL };
- execv("/sbin/init", (char* const*) argv);
- fatal("Failed to load chain init: %s: %m", argv[0]);
+ if ( !strcmp(target, "chain-merge") )
+ {
+ const char* argv[] = { "sysmerge", "--booting", NULL };
+ execv("/sysmerge/sbin/sysmerge", (char* const*) argv);
+ fatal("Failed to run automatic update: %s: %m", argv[0]);
+ }
+ else
+ {
+ 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 ( !strcmp(target, "chain-merge") )
+ {
+ if ( WIFEXITED(status) && WEXITSTATUS(status) == 0 )
+ {
+ target = "chain";
+ continue;
+ }
+ if ( WIFEXITED(status) )
+ fatal("Automatic upgrade failed: Exit status %i",
+ WEXITSTATUS(status));
+ else if ( WIFSIGNALED(status) )
+ fatal("Automatic upgrade failed: %s",
+ strsignal(WTERMSIG(status)));
+ else
+ fatal("Automatic upgrade failed: Unexpected unusual termination");
+ }
if ( WIFEXITED(status) )
{
result = WEXITSTATUS(status);
@@ -990,17 +1024,20 @@ int main(int argc, char* argv[])
fclose(target_fp);
}
else
- target = "single-user";
+ fatal("Refusing to initialize because %s doesn't exist", target_path);
}
if ( getenv("INIT_PID") )
fatal("System is already managed by an init process");
if ( !strcmp(target, "single-user") ||
- !strcmp(target, "multi-user") )
+ !strcmp(target, "multi-user") ||
+ !strcmp(target, "sysinstall") ||
+ !strcmp(target, "sysupgrade") )
return init(target);
- if ( !strcmp(target, "chain") )
+ if ( !strcmp(target, "chain") ||
+ !strcmp(target, "chain-merge") )
return init_chain(target);
fatal("Unknown initialization target `%s'", target);
diff --git a/share/man/man5/upgrade.conf.5 b/share/man/man5/upgrade.conf.5
new file mode 100644
index 00000000..c29a1e04
--- /dev/null
+++ b/share/man/man5/upgrade.conf.5
@@ -0,0 +1,105 @@
+.Dd $Mdocdate: January 4 2016 $
+.Dt UPGRADE.CONF 5
+.Os
+.Sh NAME
+.Nm upgrade.conf
+.Nd upgrade configuration
+.Sh SYNOPSIS
+.Nm /etc/upgrade.conf
+.Sh DESCRIPTION
+The
+.Nm upgrade.conf
+controls the actions taken by
+.Xr sysupgrade 8
+during a system upgrade as described in
+.Xr upgrade 7 .
+The file allows customizing whether the system itself is replaced, whether new
+ports are installed, whether the new source code is installed and what happens
+to the old source code. It also records information about the system such as
+how the system is booted. The file is created automatically by
+.Xr sysinstall 8
+as part of
+.Xr installation 7
+to match what was installed.
+.Pp
+Developers may wish to customize what happens to
+.Pa /src
+on a system upgrade. The new source code can be installed or not. If it is
+installed, it can be installed in
+.Pa /newsrc
+or by default in
+.Pa /src .
+Any existing source code will be safely moved inside a subdirectory of
+.Pa /oldsrc .
+.Pp
+It is possible to disable any upgrading whatsoever. If you do this, except the
+new source code is installed, then you can upgrade to the new system manually as
+described in
+.Xr development 7 .
+Upgrading to the next release from source is not supported.
+.Sh FORMAT
+The file is read line by line. The # character starts a comment and the rest of
+the line is ignored. Lines are supposed to contain assignments to variables. An
+assignment is the name of the variable, whitespace, an equal character,
+whitespace, the value, whitespace, and then the end of the line.
+.Bl -tag -width "12345678"
+.It Sy grub Ns "=" Ns Oo Sy no "|" yes Oc (default Sy no ) .
+States GRUB is used as the bootloader. If either the
+.Sy system
+or
+.Sy ports
+are set to
+.Sy yes ,
+then the bootloader is reinstalled
+.Xr ( grub-install 8 )
+and updated
+.Xr ( update-grub 8 ) .
+.It Sy newsrc Ns "=" Ns Oo Sy no "|" yes Oc (default Sy no ) .
+Place the new source code in
+.Pa /newsrc
+and move any existing
+.Pa /newsrc
+into
+.Pa /oldsrc .
+This preserves the current
+.Pa /src
+directory. This takes precedence over and disables the behavior described under
+.Sy src .
+.It Sy ports Ns "=" Ns Oo Sy no "|" yes Oc (default Sy yes ) .
+Install the new ports.
+.It Sy src Ns "=" Ns Oo Sy no "|" yes Oc (default Sy no ) .
+Place the new source code in
+.Pa /src
+and move any existing
+.Pa /src
+into
+.Pa /oldsrc .
+.It Sy system Ns "=" Ns Oo Sy no "|" yes Oc (default Sy yes ) .
+Install the new system. This will run
+.Xr update-initrd 8
+and if
+.Sy grub
+is set to
+.Sy no ,
+then regenerate
+.Pa /etc/grub.d/10_sortix.cache .
+.El
+.Pp
+The defaults will be used if
+.Pa /etc/upgrade.conf
+is missing.
+.Sh FILES
+.Bl -tag -width "/etc/upgrade.conf" -compact
+.It Pa /etc/upgrade.conf
+Upgrade configuration.
+.El
+.Sh EXAMPLES
+.Bd -literal
+system = yes
+ports = yes
+src = no
+grub = yes
+.Ed
+.Sh SEE ALSO
+.Xr upgrade 7 ,
+.Xr sysupgrade 8
diff --git a/share/man/man7/development.7 b/share/man/man7/development.7
index 6197061b..1d74d1e5 100644
--- a/share/man/man7/development.7
+++ b/share/man/man7/development.7
@@ -39,7 +39,6 @@ system, run as root:
cd /src
make # build new operating system in /src/sysroot
make sysmerge # upgrade current operating system with /src/sysroot
- update-initrd # update boot initrd with new files
.Ed
.Pp
The build system creates a minimal root filesystem structure in the
@@ -55,20 +54,22 @@ The
.Sy sysmerge
make target ensures a system is built in
.Pa /src/sysroot
-and then uses it to replace the current operating system. It ignores
-configuration files and other things that could cause conflicts when merging
-with an existing system. The new user-space is running on completition, though
-existing processes will be running the old programs. Likewise a reboot is needed
-to run the new kernel.
-.Pp
-The
-.Xr update-initrd 8
-program regenerates the
+and then runs the
+.Xr sysmerge 8
+program which installs the new system files onto the existing system. It
+updates the system manifest as well all ports installed in the sysroot. The
.Xr initrd 7
-by embedding configuration and programs from the root filesystem. It needs to
-be run when init system files,
-.Xr fstab 5 ,
-filesystem drivers, or filesystem checkers are modified.
+is automatically regenerated using
+.Xr update-initrd 8 .
+The bootloader, if enabled in
+.Xr upgrade.conf 5 ,
+is reinstalled and configured as necessary. The new user-space is running on
+completion, though existing processes will be running the old programs.
+A reboot is needed to run the new kernel. If the ABI changed and the current
+kernel isn't able to run the new programs, then the upgrade is delayed and will
+be automatically completed on the next boot. The
+.Sy sysmerge-wait
+make target forces waiting until the next boot.
.Ss Root Makefile
The
.Pa /src/Makefile
@@ -112,6 +113,10 @@ and place it in the current directory as
Upgrade the current operating system using the sysroot after making the
.Sy all
target.
+.It Sy sysmerge-wait
+Like
+.Sy sysmerge
+but delay the upgrade until the next boot.
.It Sy sysroot-base-headers
Create the sysroot and install only the headers of the standard library and
kernel into it. This is useful when bootstrapping the runtime libraries of the
@@ -314,6 +319,10 @@ formal online release.
.Xr git 1 ,
.Xr make 1 ,
.Xr cross-development 7 ,
+.Xr installation 7 ,
.Xr porting-guide 7 ,
.Xr serial-transfer 7 ,
+.Xr upgrade 7 ,
+.Xr sysinstall 8 ,
+.Xr sysmerge 8 ,
.Xr update-initrd 8
diff --git a/share/man/man7/hier.7 b/share/man/man7/hier.7
index 028464d1..54039668 100644
--- a/share/man/man7/hier.7
+++ b/share/man/man7/hier.7
@@ -6,7 +6,7 @@
.Nd layout of filesystems
.Sh DESCRIPTION
The filesystem hierarchy is as follows:
-.Bl -tag -width "12345678"
+.Bl -tag -width "12345678910"
.It Pa /
Root directory.
.It Pa /bin
@@ -38,6 +38,10 @@ System programs.
Architecture independent files.
.It Pa /src
System source code.
+.It Pa /sysmerge
+Temporary area for
+.Xr sysmerge 8
+delayed upgrades.
.It Pa /tix
Package management
.It Pa /tmp
diff --git a/share/man/man7/installation.7 b/share/man/man7/installation.7
new file mode 100644
index 00000000..8434e87b
--- /dev/null
+++ b/share/man/man7/installation.7
@@ -0,0 +1,338 @@
+.Dd $Mdocdate: December 25 2015 $
+.Dt INSTALLATION 7
+.Os
+.Sh NAME
+.Nm installation
+.Nd operating system installation instructions
+.Sh DESCRIPTION
+This document describes how to install Sortix on a computer from a cdrom
+release. Please read it through carefully before beginning the installation so
+you know what to expect and things you need to keep in mind. The
+.Xr upgrade 7
+manual page covers upgrading an existing installation.
+.Ss Prerequisites
+.Bl -bullet -compact
+.It
+A
+.Pa sortix-x.y-arch.iso
+release for your architecture.
+.It
+A cdrom onto which the release has been burned, or USB portable storage onto
+which the release has been placed at the first byte and onwards.
+.It
+A computer meeting the system requirements.
+.El
+.Ss System Requirements
+.Bl -bullet -compact
+.It
+32-bit x86 CPU with SSE (i486 release), or 64-bit x86 CPU (x86_64 release).
+.It
+1 GiB RAM (recommended) to run iso live environment (including installer) with
+all ports loaded, or significantly less if unimportant ports are not loaded. An
+installation on a harddisk will require very little RAM to run after
+installation.
+.It
+ATA or AHCI harddisk with at least 1 GiB of unpartitioned space.
+.It
+BIOS firmware, or UEFI firmware in legacy mode.
+.It
+PS/2 keyboard/mouse firmware emulation to use those devices.
+.It
+If you wish to dual boot, you need an existing operating system with a multiboot
+compliant bootloader such as GRUB.
+.El
+.Ss Preparation
+Read this document through before beginning the installation. The installation
+process is designed to be reasonable, but you need to patient and in an
+emotionally stable place. It is important you understand the current
+limitations of the system and carefully consider whether you want to go through
+with the installation at this time.
+.Pp
+Before installing any operating system, be sure to have backed up local data in
+the event something goes wrong. This operating system comes without any
+warranty at all (see the license).
+.Pp
+Consider the partitioning scheme and whether you wish to dual boot. Consult the
+partitioning instructions below. If dual-booting and there isn't enough
+unpartitioned space, use the native partition editor of the existing operating
+system to shrink its installation.
+.Pp
+Determine how the target machine will boot the release. If the firmware
+supports usb-iso hybrid images, you can use
+.Xr dd 1
+or such to copy the release physically onto a USB portable storage device.
+If the target machine has a cdrom drive, you can burn the release to a cdrom.
+Insert the installation medium in the computer and power it on. If needed,
+change the boot order in the firmware to prefer the installation medium over any
+existing operating system installations.
+.Pp
+After the installation is complete, remove the installation medium and restore
+the firmware boot order to prioritize the local harddisk. Then power the
+computer on normally to run the new operating system.
+.Ss Qemu
+Virtual machines are a well-supported installation target. For instance, to
+prepare a 1 GiB harddisk and install the operating system onto it, run something
+like:
+.Bd -literal
+qemu-img create sortix.raw 1G
+qemu-system-x86_64 -enable-kvm -m 1024 -vga std -cdrom sortix.iso \\
+ -drive file=sortix.raw,format=raw
+.Ed
+.Pp
+After the installation is complete, power off the computer and remove the
+.Ar -cdrom
+.Pa sortix.iso
+option.
+.Ss Bootloader Menu
+Booting the release will present you with a GRUB bootloader menu. You have
+three primary options:
+.Pp
+.Bl -bullet -compact
+.It
+Running a fully-featured temporary live environment.
+.It
+Running the operating system installer
+.Xr ( sysinstall 8 ) .
+.It
+Upgrading an existing installation to this release
+.Xr ( sysupgrade 8 ) .
+.El
+.Pp
+Each of these options are a live environment running exclusively in RAM. The
+difference is only what program is run after the system has booted. The
+bootloader will load the whole operating system and ports into memory from the
+installation medium. This may take a moment. You need enough memory to store
+the whole system and the runtime usage. If the system memory is really
+insufficient, then the bootloader may have strange behavior, take a really long
+time to load, or not complete the boot at all.
+.Pp
+You can configure which ports gets loaded using the bootloader menu. The base
+system is rather lean and can be made quite small. You need some ports to
+complete an installation.
+.Ss Installer
+This guide assumes you selected the operating system installation option in the
+bootloader. If not, you can run the installer by running the
+.Xr sysinstall 8
+command.
+.Pp
+The installer is an interactive command line program that asks you questions and
+you answer them. It provides useful information you shouldn't accidentally
+overlook. Before answering any question, read all output since your last
+answer.
+.Pp
+You should have this installation guide ready at all times. You can view this
+.Xr installation 7
+page during the installation by answering
+.Sy '!man'
+to any regular prompt (excluding password prompts). Likewise you can answer
+.Sy '!'
+to get an interactive shell. Upon completion, you will be asked the question
+again.
+.Ss Keyboard Layout
+You need to choose the applicable keyboard layout. By default, a standard US
+keyboard layout is used. You can view a list of keyboard layouts if you wish.
+This layout is then loaded and the preference will be stored in
+.Xr kblayout 5 .
+.Ss Display Resolution
+If a driver exists for your graphics card, then you will be asked for your
+preferred display resolution by
+.Xr chvideomode 1 .
+The display will then use this resolution and your preference will be stored in
+.Xr videomode 5 .
+.Ss Bootloader
+The
+.Xr kernel 7
+is a multiboot compatible binary that can be loaded by any multiboot
+specification compatible bootloader such as GRUB. You need to use such a
+bootloader to boot the operating system. You will be offered the choice of
+installing GRUB as the bootloader. Note however that this GRUB is not able to
+detect other operating systems and you will have to configure it manually if you
+wish to use it in a dual boot scheme. The answer will default to yes if no
+existing partitions are found, and will default to no if some are found.
+.Pp
+Single-boot configurations should use the offered bootloader. Dual-boot
+configurations should refuse it and arrange for bootloading by other means. The
+installer will generate
+.Pa /etc/grub.d/10_sortix.cache
+which is a fragment of GRUB configuration that offers the menu option of running
+Sortix. You can splice that into
+.Pa /etc/grub.d/40_custom
+of an existing GRUB installation and run
+.Xr update-grub 8
+to add it as a boot option.
+.Pp
+If you accept the bootloader, you will be asked if you wish to password protect
+the bootloader. If you don't, anyone able to use the keyboard during system
+bootloading will be trivially able to gain root access using the bootloader
+command line. If you use this, you should also password protect the firmware and
+prohibit it from booting from anything but the harddisk. An attacker will then
+need to tamper with the computer itself physically. The password will be hashed
+and stored in
+.Xr grubpw 5
+and is inserted into the GRUB configuration when
+.Xr update-grub 8
+is run.
+.Ss Partitioning
+You will now need to set up a partition for the root filesystem and other
+filesystems you wish to use. The installer will give you instructions and run
+the
+.Xr disked 8
+partitioning program. You can view its man page by typing
+.Sy man
+and you can view this man page by typing
+.Sy man 7 installation .
+.Pp
+.Nm disked
+defaults to the first detected harddisk as the current harddisk. You can switch
+to another harddisk using the
+.Sy device Ar device-name
+command. You can view all devices with the
+.Sy devices
+command.
+.Pp
+If the current device does not already have a partition table, you can create a
+.Xr mbr 7
+or
+.Xr gpt 7
+partition table using the
+.Sy mktable
+command.
+.Xr gpt 7
+is the preferred choice for new partition tables as
+.Xr mbr 7 has unfortunate limitations.
+If you are dissatisfied with the current partition table, you can use
+the
+.Sy rmtable
+command which will destroy the partition table and effectively delete all data
+on the harddisk.
+.Pp
+The
+.Sy ls
+command to lists all partitions and unused space on the current device.
+The
+.Sy mkpart
+command creates a partition. You will be asked interactive questions to
+determine its location. You will be asked if you wish to format a filesystem.
+.Nm ext2
+is the native filesystem. If applicable, you will be asked if you wish to create
+a mountpoint for it in
+.Xr fstab 5 .
+The
+.Sy rmpart Ar partition-number
+command removes a partition table entry and effectively deletes all data on the
+partition.
+.Pp
+If the device containing the root filesystem uses the GPT partitioning scheme,
+and you accepted the included bootloader, then you must create a
+.Sy biosboot
+partition onto which the bootloader is installed. It should be at the start of
+the harddisk and a size of 1 MiB will be more than sufficient.
+.Pp
+You need to make a partition containing the root filesystem mounted at
+.Pa / .
+A size of 1 GiB will be comfortable for the base system and ports and basic
+usage. There is no inherent need for a
+.Pa /home
+partition so you are encouraged to make the root filesystem as large as you
+wish. Operating systems upgrades will preserve the root filesystem and the
+installer handles installing on top of an existing installation and preserves
+user files and local configuration.
+.Pp
+Type
+.Sy exit
+when you are done to continue the installation. If the installer detects a
+problem with your partitioning, it will offer to run
+.Xr disked 8
+again.
+.Ss Installation
+The installer will show its installation intentions ask you to confirm the
+installation. If you answer yes, then the installation will begin.
+.Pp
+The installer will copy the live environment into the target root filesystem
+according to the file lists in
+.Pa /tix/manifest
+and create configuration files matching your earlier choices. It will generate
+an initrd that locates and boots the root filesystem. It will install the
+bootloader if desired. The installation will take a moment.
+.Ss Configuration
+After the installation is complete, a bare system is installed but it lacks
+crucial configuration files and it will refuse to start when booted.
+.Ss Hostname
+You will be asked for the hostname of the new system which be stored in
+.Xr hostname 5 .
+This question is skipped if the file already exits.
+.Ss Root
+You will be asked for the root password. A root account is made in
+.Xr passwd 5
+and
+.Xr group 5 .
+This question is skipped if the root account already exists.
+.Ss Users
+You will be asked in a loop if you wish to make another user. Answer
+.Sy no
+when you are done. Otherwise enter the name of the new account. If you wish to
+create an account by the name of
+.Li no
+then simply add a space in front as leading spaces are trimmed.
+.Pp
+You will then be asked for the full name and the password for the new user. A
+user directory will be made in
+.Pa /home .
+The new user is added to
+.Xr passwd 5
+and
+.Xr group 5 .
+.Pp
+Please note that Sortix is not currently secure as a multi-user system and
+filesystem permissions are not enforced.
+.Ss Completion
+This will complete the operating system installation. Upon reboot, the new
+system will start normally. After powering off your system, you need to remove
+the installation medium and if applicable restore boot priorities in your
+firmware. If you did not accept the bootloader, you will need to manually
+configure a bootloader to boot the new operating system.
+.Pp
+You will be given the choice between powering off the system, rebooting it, or
+directly booting the new system. The last option will directly boot the new
+system in a chroot while the live environment remains in the background. If you
+invoked
+.Xr sysinstall 8
+yourself, then you will be returned to your live environment shell. Otherwise
+the computer will power off when the chroot environment terminates.
+.Pp
+Upon boot of the new system it will be configured in multi-user mode and you
+will be presented with a login screen. Authenticate as one of the local users
+and you will be given a shell. To power off the computer login as user
+.Sy poweroff
+and to reboot the computer login as user
+.Sy reboot .
+.Pp
+The
+.Xr user-guide 7
+manual page is a basic overview of the system for new users.
+.Pp
+Congratulations on your new Sortix system.
+.Sh SEE ALSO
+.Xr chkblayout 1 ,
+.Xr chvideomode 1 ,
+.Xr man 1 ,
+.Xr fstab 5 ,
+.Xr group 5 ,
+.Xr grubpw 5 ,
+.Xr kblayout 5 ,
+.Xr passwd 5 ,
+.Xr videomode 5 ,
+.Xr development 7 ,
+.Xr gpt 7 ,
+.Xr initrd 7 ,
+.Xr kernel 7 ,
+.Xr mbr 7 ,
+.Xr upgrade 7 ,
+.Xr user-guide 7 ,
+.Xr disked 8 ,
+.Xr fsck 8 ,
+.Xr init 8 ,
+.Xr sysinstall 8 ,
+.Xr sysupgrade 8 ,
+.Xr update-grub 8 ,
+.Xr update-initrd 8
diff --git a/share/man/man7/upgrade.7 b/share/man/man7/upgrade.7
new file mode 100644
index 00000000..b5a71d02
--- /dev/null
+++ b/share/man/man7/upgrade.7
@@ -0,0 +1,120 @@
+.Dd $Mdocdate: January 5 2016 $
+.Dt UPGRADE 7
+.Os
+.Sh NAME
+.Nm upgrade
+.Nd operating system upgrade instructions
+.Sh DESCRIPTION
+This document describes how to upgrade an existing Sortix installation to a
+newer release. The
+.Xr installation 7
+manual page covers creating a new installation.
+.Ss Prerequisites
+You need to prepare a bootable medium with the new release as described in
+.Xr installation 7 .
+Read all the instructions up to Bootloader Menu and continue with this document.
+.Pp
+It is not possible to skip releases. If you want to upgrade to a release, you
+must upgrade first to all releases before that one. If the existing installation
+is a development snapshot, you must either upgrade to a newer development
+snapshot of that version, or a the final release of that version.
+.Ss Bootloader Menu
+Pick the
+.Li upgrade existing installation
+option in the bootloader menu to begin the upgrade. This will load a live
+environment set to automatically run the
+.Xr sysupgrade 8
+program.
+.Ss Upgrader
+This guide assumes you selected the upgrade option in the bootloader. If not,
+you can run the installer by running the
+.Xr sysupgrade 8
+command.
+.Pp
+The upgrader is an interactive command line program that asks you questions and
+you answer them. It provides useful information you shouldn't accidentally
+overlook. Before answering any question, read all output since your last
+answer.
+.Pp
+You should have this upgrade guide ready at all times. You can view this
+.Xr upgrade 7
+page during the installation by answering
+.Sy '!man'
+to any regular prompt (excluding password prompts). Likewise you can answer
+.Sy '!'
+to get an interactive shell. Upon completion, you will be asked the question
+again.
+.Ss Keyboard Layout
+You need to choose the applicable keyboard layout. By default, a standard US
+keyboard layout is used. You can view a list of keyboard layouts if you wish.
+.Ss Display Resolution
+If a driver exists for your graphics card, then you will be asked for your
+preferred display resolution by
+.Xr chvideomode 1 .
+.Ss Installation Search
+The upgrader will search for existing Sortix installations by probing local
+filesystems for
+.Pa /etc/sortix-release .
+You will be asked which installation you wish to upgrade. Enter the name of the
+root filesystem device. If none are found, it asks if you want to run
+.Xr sysinstall 8 . Filesystems will be repaired by
+.Xr fsck 8
+as needed.
+.Ss Confirmation
+The upgrader will warn you if it detects you are not following proper upgrade
+procedure either by downgrading or by skipping a release, neither of which is
+supported. It will also warn you if it detects an ABI downgrade.
+.Pp
+The upgrade will load the upgrade preferences from
+.Pa /etc/upgrade.conf
+file of the target system as described in
+.Xr upgrade.conf 5 .
+.Pp
+The new release may have a new ABI. A major ABI change means the new kernel
+will be unable to properly execute old programs. A minor ABI change means the
+new kernel has new compatible features and will be able to run older programs,
+but older kernels will not be able to run programs using the new ABI.
+.Pp
+A confirmation screen will tell you what actions the upgrader plan on doing.
+Answer
+.Sy yes
+to proceed with the upgrade. Otherwise you can escape to a shell, edit
+.Xr upgrade.conf 5 ,
+return and answer
+.Sy no
+and the upgrader will reload the configuration.
+.Ss Upgrade
+The upgrader will take the appropriate actions:
+.Pp
+.Bl -bullet -compact
+.It
+Updating the system.
+.It
+Updating the ports.
+.It
+Updating the source code.
+.It
+Updating the initrd.
+.It
+Updating the bootloader.
+.El
+.Ss Completion
+The upgrade is now complete. The new system will run after a reboot. The
+upgrader will give you an overview of what it has done. If you upgraded across
+a major ABI change, then you will be told that you need to recompile all local
+programs to use the new ABI.
+.Pp
+Congratulations on your freshly upgraded Sortix system.
+.Sh SEE ALSO
+.Xr chkblayout 1 ,
+.Xr chvideomode 1 ,
+.Xr man 1 ,
+.Xr development 7 ,
+.Xr initrd 7 ,
+.Xr installation 7 ,
+.Xr kernel 7 ,
+.Xr user-guide 7 ,
+.Xr sysinstall 8 ,
+.Xr sysupgrade 8 ,
+.Xr update-grub 8 ,
+.Xr update-initrd 8
diff --git a/share/man/man7/user-guide.7 b/share/man/man7/user-guide.7
index 549510cd..76a52c2f 100644
--- a/share/man/man7/user-guide.7
+++ b/share/man/man7/user-guide.7
@@ -9,8 +9,13 @@ Sortix is a small self-hosting Unix-like operating system developed since 2011.
This document covers matters relevant to new users from other Unix-like
operating systems.
.Ss Introduction
-You will be presented a with standard Unix command line environment upon booting
-the live environment.
+The installation process is covered in
+.Xr installation 7 .
+Bootable cdrom releases will offer the options of running a live environment,
+installing the operating system, or upgrading an existing installation.
+.Pp
+You will be presented a with standard Unix command line environment upon login or
+booting the live environment.
.Ss Shutdown
.Xr init 8
spawns a session after boot. This is
@@ -125,4 +130,6 @@ but it is becoming feasible to build a large number of them natively.
.Sh SEE ALSO
.Xr cross-development 7 ,
.Xr development 7 ,
-.Xr serial-transfer 7
+.Xr installation 7 ,
+.Xr serial-transfer 7 ,
+.Xr upgrade 7
diff --git a/sysinstall/.gitignore b/sysinstall/.gitignore
new file mode 100644
index 00000000..2d94ec95
--- /dev/null
+++ b/sysinstall/.gitignore
@@ -0,0 +1,4 @@
+sysinstall
+sysmerge
+sysupgrade
+*.o
diff --git a/sysinstall/Makefile b/sysinstall/Makefile
new file mode 100644
index 00000000..c50f28cc
--- /dev/null
+++ b/sysinstall/Makefile
@@ -0,0 +1,75 @@
+SOFTWARE_MEANT_FOR_SORTIX=1
+include ../build-aux/platform.mak
+include ../build-aux/compiler.mak
+include ../build-aux/version.mak
+include ../build-aux/dirs.mak
+
+OPTLEVEL?=$(DEFAULT_OPTLEVEL)
+CFLAGS?=$(OPTLEVEL)
+
+CFLAGS:=$(CFLAGS) -Wall -Wextra
+CPPFLAGS:=$(CPPFLAGS) -DVERSIONSTR=\"$(VERSION)\"
+
+MAIN_OBJS=\
+sysinstall.o \
+sysmerge.o \
+sysupgrade.o \
+
+UTIL_OBJS=\
+conf.o \
+devices.o \
+execute.o \
+fileops.o \
+interactive.o \
+manifest.o \
+release.o \
+
+OBJS=$(MAIN_OBJS) $(UTIL_OBJS)
+
+SYSINSTALL_DEPS=devices execute fileops interactive manifest
+SYSMERGE_DEPS=conf fileops execute manifest release
+SYSUPGRADE_DEPS=conf devices execute fileops interactive manifest release
+
+SYSINSTALL_OBJS:=sysinstall.o $(SYSINSTALL_DEPS:=.o)
+SYSMERGE_OBJS:=sysmerge.o $(SYSMERGE_DEPS:=.o)
+SYSUPGRADE_OBJS:=sysupgrade.o $(SYSUPGRADE_DEPS:=.o)
+
+all: sysinstall sysmerge sysupgrade
+
+.PHONY: all install clean
+
+install: all
+ mkdir -p $(DESTDIR)$(SBINDIR)
+ install sysinstall $(DESTDIR)$(SBINDIR)
+ install sysmerge $(DESTDIR)$(SBINDIR)
+ install sysupgrade $(DESTDIR)$(SBINDIR)
+ mkdir -p $(DESTDIR)$(MANDIR)/man8
+ install sysinstall.8 $(DESTDIR)$(MANDIR)/man8/sysinstall.8
+ install sysmerge.8 $(DESTDIR)$(MANDIR)/man8/sysmerge.8
+ install sysupgrade.8 $(DESTDIR)$(MANDIR)/man8/sysupgrade.8
+
+sysinstall: $(SYSINSTALL_OBJS)
+ $(CC) $(SYSINSTALL_OBJS) -o $@ -lmount
+
+sysmerge: $(SYSMERGE_OBJS)
+ $(CC) $(SYSMERGE_OBJS) -o $@ -lmount
+
+sysupgrade: $(SYSUPGRADE_OBJS)
+ $(CC) $(SYSUPGRADE_OBJS) -o $@ -lmount
+
+%.o: %.c
+ $(CC) $(CFLAGS) $(CPPFLAGS) -std=gnu11 -c $< -o $@
+
+sysinstall.o: $(SYSINSTALL_DEPS:=.h)
+sysmerge.o: $(SYSMERGE_DEPS:=.h)
+sysupgrade.o: $(SYSUPGRADE_DEPS:=.h)
+conf.o: conf.h
+devices.o: devices.h
+execute.o: execute.h
+fileops.o: fileops.h
+interactive.o: interactive.h execute.h
+manifest.o: manifest.h execute.h fileops.h
+release.o: release.h
+
+clean:
+ rm -f sysinstall sysmerge sysupgrade $(OBJS)
diff --git a/sysinstall/conf.c b/sysinstall/conf.c
new file mode 100644
index 00000000..b4850eb0
--- /dev/null
+++ b/sysinstall/conf.c
@@ -0,0 +1,115 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2015, 2016.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ conf.c
+ Utility functions to handle upgrade.conf(5).
+
+*******************************************************************************/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "conf.h"
+
+static bool conf_boolean(const char* name, const char* value, const char* path)
+{
+ if ( !strcmp(value, "yes") )
+ return true;
+ if ( !strcmp(value, "no") )
+ return false;
+ printf("%s: %s: Expected yes or no instead of unsupported value\n",
+ path, name);
+ return false;
+}
+
+static void conf_assign(struct conf* conf,
+ const char* name,
+ const char* value,
+ const char* path)
+{
+ if ( !strcmp(name, "grub") )
+ conf->grub = conf_boolean(name, value, path);
+ else if ( !strcmp(name, "newsrc") )
+ conf->newsrc = conf_boolean(name, value, path);
+ else if ( !strcmp(name, "ports") )
+ conf->ports = conf_boolean(name, value, path);
+ else if ( !strcmp(name, "src") )
+ conf->src = conf_boolean(name, value, path);
+ else if ( !strcmp(name, "system") )
+ conf->system = conf_boolean(name, value, path);
+ else
+ printf("%s: %s: Unsupported variable\n", path, name);
+}
+
+void load_upgrade_conf(struct conf* conf, const char* path)
+{
+ memset(conf, 0, sizeof(*conf));
+ conf->ports = true;
+ conf->system = true;
+
+ FILE* fp = fopen(path, "r");
+ if ( !fp )
+ {
+ if ( errno == ENOENT )
+ return;
+ err(2, "%s", path);
+ }
+ char* line = NULL;
+ size_t line_size = 0;
+ ssize_t line_length;
+ while ( 0 < (errno = 0, line_length = getline(&line, &line_size, fp)) )
+ {
+ if ( line[line_length - 1] == '\n' )
+ line[--line_length] = '\0';
+ line_length = 0;
+ while ( line[line_length] && line[line_length] != '#' )
+ line_length++;
+ line[line_length] = '\0';
+ while ( line_length && isblank((unsigned char) line[line_length - 1]) )
+ line[--line_length] = '\0';
+ char* name = line;
+ while ( *name && isblank((unsigned char) *name) )
+ name++;
+ if ( !*name || *name == '=' )
+ continue;
+ size_t name_length = 1;
+ while ( name[name_length] &&
+ !isblank((unsigned char) name[name_length]) &&
+ name[name_length] != '=' )
+ name_length++;
+ char* value = name + name_length;
+ while ( *value && isblank((unsigned char) *value) )
+ value++;
+ if ( *value != '=' )
+ continue;
+ value++;
+ while ( *value && isblank((unsigned char) *value) )
+ value++;
+ name[name_length] = '\0';
+ conf_assign(conf, name, value, path);
+ }
+ if ( errno )
+ err(2, "%s", path);
+ free(line);
+ fclose(fp);
+}
diff --git a/sysinstall/conf.h b/sysinstall/conf.h
new file mode 100644
index 00000000..43c798af
--- /dev/null
+++ b/sysinstall/conf.h
@@ -0,0 +1,37 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2015, 2016.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ conf.h
+ Utility functions to handle upgrade.conf(5).
+
+*******************************************************************************/
+
+#ifndef CONF_H
+#define CONF_H
+
+struct conf
+{
+ bool grub;
+ bool newsrc;
+ bool ports;
+ bool src;
+ bool system;
+};
+
+void load_upgrade_conf(struct conf* conf, const char* path);
+
+#endif
diff --git a/sysinstall/devices.c b/sysinstall/devices.c
new file mode 100644
index 00000000..7ad7994e
--- /dev/null
+++ b/sysinstall/devices.c
@@ -0,0 +1,416 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2015, 2016.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ devices.c
+ Utility functions to handle devices, partitions, and filesystems.
+
+*******************************************************************************/
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "devices.h"
+
+struct harddisk** hds;
+size_t hds_count;
+
+const char* path_of_blockdevice(struct blockdevice* bdev)
+{
+ return bdev->p ? bdev->p->path : bdev->hd->path;
+}
+
+const char* device_path_of_blockdevice(struct blockdevice* bdev)
+{
+ while ( bdev->p )
+ bdev = bdev->p->parent_bdev;
+ return bdev->hd->path;
+}
+
+void unscan_filesystem(struct blockdevice* bdev)
+{
+ if ( bdev->fs )
+ {
+ filesystem_release(bdev->fs);
+ bdev->fs = NULL;
+ }
+}
+
+void scan_filesystem(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; // TODO: Perhaps print an error here?
+}
+
+void unscan_device(struct harddisk* hd)
+{
+ if ( hd->bdev.pt )
+ {
+ for ( size_t i = 0; i < hd->bdev.pt->partitions_count; i++ )
+ unscan_filesystem(&hd->bdev.pt->partitions[i]->bdev);
+ partition_table_release(hd->bdev.pt);
+ hd->bdev.pt = NULL;
+ }
+ if ( hd->bdev.fs )
+ unscan_filesystem(&hd->bdev);
+}
+
+void scan_device(struct harddisk* hd)
+{
+ unscan_device(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 )
+ {
+ scan_filesystem(bdev);
+ return;
+ }
+ else if ( parterr == PARTITION_ERROR_ERRNO ||
+ parterr != PARTITION_ERROR_NONE )
+ return; // TODO: Perhaps print an error here?
+ for ( size_t i = 0; i < bdev->pt->partitions_count; i++ )
+ scan_filesystem(&bdev->pt->partitions[i]->bdev);
+}
+
+void unscan_devices(void)
+{
+ for ( size_t i = 0; i < hds_count; i++ )
+ {
+ unscan_device(hds[i]);
+ harddisk_close(hds[i]);
+ }
+ hds_count = 0;
+ free(hds);
+ hds = NULL;
+}
+
+void scan_devices(void)
+{
+ unscan_devices();
+ if ( !devices_open_all(&hds, &hds_count) )
+ {
+ // TODO: How should callers deal with error conditions from here?
+ warn("iterating devices");
+ }
+ for ( size_t i = 0; i < hds_count; i++ )
+ scan_device(hds[i]);
+}
+
+struct filesystem* search_for_filesystem_by_uuid(const unsigned char* uuid)
+{
+ for ( size_t di = 0; di < hds_count; di++ )
+ {
+ struct blockdevice* dbdev = &hds[di]->bdev;
+ if ( dbdev->fs )
+ {
+ if ( (dbdev->fs->flags & FILESYSTEM_FLAG_UUID) &&
+ memcmp(dbdev->fs->uuid, uuid, 16) == 0 )
+ return dbdev->fs;
+ }
+ else if ( dbdev->pt )
+ {
+ for ( size_t pi = 0; pi < dbdev->pt->partitions_count; pi++ )
+ {
+ struct blockdevice* pbdev = &dbdev->pt->partitions[pi]->bdev;
+ if ( !pbdev->fs )
+ continue;
+ if ( (pbdev->fs->flags & FILESYSTEM_FLAG_UUID) &&
+ memcmp(pbdev->fs->uuid, uuid, 16) == 0 )
+ return pbdev->fs;
+ }
+ }
+ }
+ return NULL;
+}
+
+struct filesystem* search_for_filesystem_by_spec(const char* spec)
+{
+ if ( strncmp(spec, "UUID=", strlen("UUID=")) == 0 )
+ {
+ const char* uuid_string = spec + strlen("UUID=");
+ if ( !uuid_validate(uuid_string) )
+ return NULL;
+ unsigned char uuid[16];
+ uuid_from_string(uuid, uuid_string);
+ return search_for_filesystem_by_uuid(uuid);
+ }
+ return NULL;
+}
+
+bool check_existing_systems(void)
+{
+ for ( size_t di = 0; di < hds_count; di++ )
+ {
+ struct blockdevice* dbdev = &hds[di]->bdev;
+ if ( dbdev->fs )
+ return true;
+ else if ( dbdev->pt )
+ return 1 <= dbdev->pt->partitions_count;
+ }
+ return false;
+}
+
+bool check_lacking_partition_table(void)
+{
+ for ( size_t di = 0; di < hds_count; di++ )
+ {
+ struct blockdevice* dbdev = &hds[di]->bdev;
+ if ( dbdev->fs )
+ continue;
+ if ( !dbdev->pt )
+ return true;
+ }
+ return false;
+}
+
+bool fsck(struct filesystem* fs)
+{
+ const char* bdev_path = path_of_blockdevice(fs->bdev);
+ printf("%s: Repairing filesystem due to inconsistency...\n", bdev_path);
+ assert(fs->fsck);
+ pid_t child_pid = fork();
+ if ( child_pid < 0 )
+ {
+ warn("%s: Mandatory repair failed: fork", bdev_path);
+ return false;
+ }
+ if ( child_pid == 0 )
+ {
+ execlp(fs->fsck, fs->fsck, "-fp", "--", bdev_path, (const char*) NULL);
+ warn("%s: Failed to load filesystem checker: %s", bdev_path, fs->fsck);
+ _Exit(127);
+ }
+ int code;
+ if ( waitpid(child_pid, &code, 0) < 0 )
+ {
+ warn("waitpid");
+ return false;
+ }
+ if ( WIFSIGNALED(code) )
+ warnx("%s: Mandatory repair failed: %s: %s", bdev_path,
+ fs->fsck, strsignal(WTERMSIG(code)));
+ else if ( !WIFEXITED(code) )
+ warnx("%s: Mandatory repair failed: %s: %s", bdev_path,
+ fs->fsck, "Unexpected unusual termination");
+ else if ( WEXITSTATUS(code) == 127 )
+ warnx("%s: Mandatory repair failed: %s: %s", bdev_path,
+ fs->fsck, "Filesystem checker is absent");
+ else if ( WEXITSTATUS(code) & 2 )
+ warnx("%s: Mandatory repair: %s: %s", bdev_path,
+ fs->fsck, "System reboot is necessary");
+ else if ( WEXITSTATUS(code) != 0 && WEXITSTATUS(code) != 1 )
+ warnx("%s: Mandatory repair failed: %s: %s", bdev_path,
+ fs->fsck, "Filesystem checker was unsuccessful");
+ else
+ return true;
+ return false;
+}
+
+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);
+}
+
+void free_mountpoints(struct mountpoint* mnts, size_t mnts_count)
+{
+ for ( size_t i = 0; i < mnts_count; i++ )
+ {
+ free(mnts[i].entry_line);
+ free(mnts[i].absolute);
+ }
+ free(mnts);
+}
+
+bool load_mountpoints(const char* fstab_path,
+ struct mountpoint** mountpoints_out,
+ size_t* mountpoints_used_out)
+{
+ FILE* fp = fopen(fstab_path, "r");
+ if ( !fp )
+ return false;
+ struct mountpoint* mountpoints = NULL;
+ size_t mountpoints_used = 0;
+ size_t mountpoints_length = 0;
+ char* line = NULL;
+ size_t line_size;
+ ssize_t line_length;
+ while ( 0 < (errno = 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 )
+ {
+ free_mountpoints(mountpoints, mountpoints_used);
+ free(line);
+ fclose(fp);
+ return false;
+ }
+ 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)) )
+ {
+ free_mountpoints(mountpoints, mountpoints_used);
+ fclose(fp);
+ return false;
+ }
+ line = NULL;
+ line_size = 0;
+ }
+ bool failure = errno;
+ free(line);
+ fclose(fp);
+ if ( failure )
+ {
+ free_mountpoints(mountpoints, mountpoints_used);
+ return false;
+ }
+ qsort(mountpoints, mountpoints_used, sizeof(struct mountpoint),
+ sort_mountpoint);
+ *mountpoints_out = mountpoints;
+ *mountpoints_used_out = mountpoints_used;
+ return true;
+}
+
+void mountpoint_mount(struct mountpoint* mountpoint)
+{
+ struct filesystem* fs = mountpoint->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);
+ if ( fs->flags & FILESYSTEM_FLAG_FSCK_MUST && !fsck(fs) )
+ errx(2, "Failed to fsck %s", bdev_path);
+ if ( !fs->driver )
+ errx(2, "%s: Don't know how to mount a %s filesystem",
+ bdev_path, fs->fstype_name);
+ const char* pretend_where = mountpoint->entry.fs_file;
+ const char* where = mountpoint->absolute;
+ struct stat st;
+ if ( stat(where, &st) < 0 )
+ err(2, "stat: %s", where);
+ if ( (mountpoint->pid = fork()) < 0 )
+ err(2, "%s: Unable to mount: fork", bdev_path);
+ // TODO: This design is broken. The filesystem should tell us when it is
+ // ready instead of having to poll like this.
+ if ( mountpoint->pid == 0 )
+ {
+ execlp(fs->driver, fs->driver, "--foreground", bdev_path, where,
+ "--pretend-mount-path", pretend_where, (const char*) NULL);
+ warn("%s: Failed to load filesystem driver: %s", bdev_path, fs->driver);
+ _exit(127);
+ }
+ while ( true )
+ {
+ struct stat newst;
+ if ( stat(where, &newst) < 0 )
+ {
+ warn("stat: %s", where);
+ if ( unmount(where, 0) < 0 && errno != ENOMOUNT )
+ warn("unmount: %s", where);
+ else if ( errno == ENOMOUNT )
+ kill(mountpoint->pid, SIGQUIT);
+ int code;
+ waitpid(mountpoint->pid, &code, 0);
+ mountpoint->pid = -1;
+ exit(2);
+ }
+ if ( newst.st_dev != st.st_dev || newst.st_ino != st.st_ino )
+ break;
+ int code;
+ pid_t child = waitpid(mountpoint->pid, &code, WNOHANG);
+ if ( child < 0 )
+ err(2, "waitpid");
+ if ( child != 0 )
+ {
+ mountpoint->pid = -1;
+ if ( WIFSIGNALED(code) )
+ err(2, "%s: Mount failed: %s: %s", bdev_path, fs->driver,
+ strsignal(WTERMSIG(code)));
+ else if ( !WIFEXITED(code) )
+ err(2, "%s: Mount failed: %s: %s", bdev_path, fs->driver,
+ "Unexpected unusual termination");
+ else if ( WEXITSTATUS(code) == 127 )
+ err(2, "%s: Mount failed: %s: %s", bdev_path, fs->driver,
+ "Filesystem driver is absent");
+ else if ( WEXITSTATUS(code) == 0 )
+ err(2, "%s: Mount failed: %s: Unexpected successful exit",
+ bdev_path, fs->driver);
+ else
+ err(2, "%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);
+ }
+}
+
+void mountpoint_unmount(struct mountpoint* mountpoint)
+{
+ if ( mountpoint->pid < 0 )
+ return;
+ if ( unmount(mountpoint->absolute, 0) < 0 && errno != ENOMOUNT )
+ warn("unmount: %s", mountpoint->entry.fs_file);
+ else if ( errno == ENOMOUNT )
+ kill(mountpoint->pid, SIGQUIT);
+ int code;
+ if ( waitpid(mountpoint->pid, &code, 0) < 0 )
+ warn("waitpid");
+ mountpoint->pid = -1;
+}
diff --git a/sysinstall/devices.h b/sysinstall/devices.h
new file mode 100644
index 00000000..1c19dafd
--- /dev/null
+++ b/sysinstall/devices.h
@@ -0,0 +1,58 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2015, 2016.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ devices.h
+ Utility functions to handle devices, partitions, and filesystems.
+
+*******************************************************************************/
+
+#ifndef DEVICES_H
+#define DEVICES_H
+
+struct mountpoint
+{
+ struct fstab entry;
+ char* entry_line;
+ pid_t pid;
+ char* absolute;
+ struct filesystem* fs;
+};
+
+extern struct harddisk** hds;
+extern size_t hds_count;
+
+const char* path_of_blockdevice(struct blockdevice* bdev);
+const char* device_path_of_blockdevice(struct blockdevice* bdev);
+void unscan_filesystem(struct blockdevice* bdev);
+void scan_filesystem(struct blockdevice* bdev);
+void unscan_device(struct harddisk* hd);
+void scan_device(struct harddisk* hd);
+void unscan_devices(void);
+void scan_devices(void);
+struct filesystem* search_for_filesystem_by_uuid(const unsigned char* uuid);
+struct filesystem* search_for_filesystem_by_spec(const char* spec);
+bool check_existing_systems(void);
+bool check_lacking_partition_table(void);
+bool fsck(struct filesystem* fs);
+void free_mountpoints(struct mountpoint* mnts, size_t mnts_count);
+bool load_mountpoints(const char* fstab_path,
+ struct mountpoint** mountpoints_out,
+ size_t* mountpoints_used_out);
+void mountpoint_mount(struct mountpoint* mountpoint);
+void mountpoint_unmount(struct mountpoint* mountpoint);
+
+#endif
diff --git a/sysinstall/execute.c b/sysinstall/execute.c
new file mode 100644
index 00000000..f3944a2b
--- /dev/null
+++ b/sysinstall/execute.c
@@ -0,0 +1,131 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2015, 2016.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ execute.h
+ Template for common execvp use cases.
+
+*******************************************************************************/
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "execute.h"
+
+int execute(const char* const* argv, const char* flags, ...)
+{
+ bool _exit_instead = false;
+ bool exit_on_failure = false;
+ bool foreground = false;
+ bool gid_set = false;
+ bool raw_exit_code = false;
+ bool uid_set = false;
+ bool quiet = false;
+ bool quiet_stderr = false;
+ gid_t gid = 0;
+ uid_t uid = 0;
+ va_list ap;
+ va_start(ap, flags);
+ for ( size_t i = 0; flags[i]; i++ )
+ {
+ switch ( flags[i] )
+ {
+ case '_': _exit_instead = true; break;
+ case 'e': exit_on_failure = true; break;
+ case 'f': foreground = true; break;
+ case 'g': gid_set = true; gid = va_arg(ap, gid_t); break;
+ case 'r': raw_exit_code = true; break;
+ case 'u': uid_set = true; uid = va_arg(ap, uid_t); break;
+ case 'q': quiet = true; break;
+ case 'Q': quiet_stderr = true; break;
+ }
+ }
+ va_end(ap);
+ sigset_t oldset, sigttou;
+ if ( foreground )
+ {
+ sigemptyset(&sigttou);
+ sigaddset(&sigttou, SIGTTOU);
+ }
+ pid_t child_pid = fork();
+ if ( child_pid < 0 )
+ {
+ warn("fork");
+ if ( exit_on_failure )
+ (_exit_instead ? _exit : exit)(2);
+ return -1;
+ }
+ if ( child_pid == 0 )
+ {
+ if ( gid_set )
+ {
+ setegid(gid);
+ setgid(gid);
+ }
+ if ( uid_set )
+ {
+ seteuid(uid);
+ setuid(uid);
+ }
+ if ( foreground )
+ {
+ setpgid(0, 0);
+ sigprocmask(SIG_BLOCK, &sigttou, &oldset);
+ tcsetpgrp(0, getpgid(0));
+ sigprocmask(SIG_SETMASK, &oldset, NULL);
+ }
+ if ( quiet )
+ {
+ close(1);
+ open("/dev/null", O_WRONLY);
+ }
+ if ( quiet_stderr )
+ {
+ close(2);
+ open("/dev/null", O_WRONLY);
+ }
+ execvp(argv[0], (char* const*) argv);
+ warn("%s", argv[0]);
+ _exit(127);
+ }
+ int code;
+ waitpid(child_pid, &code, 0);
+ if ( foreground )
+ {
+ sigprocmask(SIG_BLOCK, &sigttou, &oldset);
+ tcsetpgrp(0, getpgid(0));
+ sigprocmask(SIG_SETMASK, &oldset, NULL);
+ }
+ if ( exit_on_failure )
+ {
+ if ( !WIFEXITED(code) || WEXITSTATUS(code) != 0 )
+ (_exit_instead ? _exit : exit)(2);
+ }
+ int exit_status;
+ if ( WIFEXITED(code) )
+ exit_status = WEXITSTATUS(code);
+ else
+ exit_status = 128 + WTERMSIG(code);
+ return raw_exit_code ? code : exit_status;
+}
diff --git a/sysinstall/execute.h b/sysinstall/execute.h
new file mode 100644
index 00000000..7e9dcc5f
--- /dev/null
+++ b/sysinstall/execute.h
@@ -0,0 +1,28 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2015, 2016.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ execute.h
+ Template for common execvp use cases.
+
+*******************************************************************************/
+
+#ifndef EXECUTE_H
+#define EXECUTE_H
+
+int execute(const char* const* argv, const char* flags, ...);
+
+#endif
diff --git a/sysinstall/fileops.c b/sysinstall/fileops.c
new file mode 100644
index 00000000..d470e982
--- /dev/null
+++ b/sysinstall/fileops.c
@@ -0,0 +1,85 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2015, 2016.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ fileops.c
+ File operation utility functions.
+
+*******************************************************************************/
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "fileops.h"
+
+int mkdir_p(const char* path, mode_t mode)
+{
+ int saved_errno = errno;
+ if ( !mkdir(path, mode) )
+ return 0;
+ if ( errno == ENOENT )
+ {
+ char* prev = strdup(path);
+ if ( !prev )
+ return -1;
+ int status = mkdir_p(dirname(prev), mode | 0500);
+ free(prev);
+ if ( status < 0 )
+ return -1;
+ errno = saved_errno;
+ if ( !mkdir(path, mode) )
+ return 0;
+ }
+ if ( errno == EEXIST )
+ return errno = saved_errno, 0;
+ return -1;
+}
+
+int access_or_die(const char* path, int mode)
+{
+ if ( access(path, mode) < 0 )
+ {
+ if ( errno == EACCES ||
+ errno == ENOENT ||
+ errno == ELOOP ||
+ errno == ENAMETOOLONG ||
+ errno == ENOTDIR )
+ return -1;
+ warn("%s", path);
+ _exit(2);
+ }
+ return 0;
+}
+
+void mkdir_or_chmod_or_die(const char* path, mode_t mode)
+{
+ if ( mkdir(path, mode) == 0 )
+ return;
+ if ( errno == EEXIST )
+ {
+ if ( chmod(path, mode) == 0 )
+ return;
+ err(2, "chmod: %s", path);
+ }
+ err(2, "mkdir: %s", path);
+}
diff --git a/sysinstall/fileops.h b/sysinstall/fileops.h
new file mode 100644
index 00000000..54225d8f
--- /dev/null
+++ b/sysinstall/fileops.h
@@ -0,0 +1,30 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2015, 2016.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ fileops.h
+ File operation utility functions.
+
+*******************************************************************************/
+
+#ifndef FILEOPS_H
+#define FILEOPS_H
+
+int mkdir_p(const char* path, mode_t mode);
+int access_or_die(const char* path, int mode);
+void mkdir_or_chmod_or_die(const char* path, mode_t mode);
+
+#endif
diff --git a/sysinstall/interactive.c b/sysinstall/interactive.c
new file mode 100644
index 00000000..d28dba7a
--- /dev/null
+++ b/sysinstall/interactive.c
@@ -0,0 +1,222 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2015, 2016.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ interactive.c
+ Interactive utility functions.
+
+*******************************************************************************/
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "execute.h"
+#include "interactive.h"
+
+void shlvl(void)
+{
+ long shlvl = 0;
+ if ( getenv("SHLVL") && (shlvl = atol(getenv("SHLVL"))) < 0 )
+ shlvl = 0;
+ if ( shlvl < LONG_MAX )
+ shlvl++;
+ char shlvl_string[sizeof(long) * 3];
+ snprintf(shlvl_string, sizeof(shlvl_string), "%li", shlvl);
+ setenv("SHLVL", shlvl_string, 1);
+}
+
+void text(const char* str)
+{
+ fflush(stdout);
+ struct winsize ws;
+ if ( tcgetwinsize(1, &ws) < 0 )
+ err(2, "tcgetwinsize");
+ struct wincurpos wcp;
+ if ( tcgetwincurpos(1, &wcp) < 0 )
+ err(2, "tcgetwinsize");
+ size_t columns = ws.ws_col;
+ size_t column = wcp.wcp_col;
+ bool blank = false;
+ while ( str[0] )
+ {
+ if ( str[0] == '\n' )
+ {
+ putchar('\n');
+ blank = false;
+ column = 0;
+ str++;
+ continue;
+ }
+ else if ( isblank((unsigned char) str[0]) )
+ {
+ blank = true;
+ str++;
+ continue;
+ }
+ size_t word_length = 0;
+ size_t word_columns = 0;
+ mbstate_t ps = { 0 };
+ while ( str[word_length] &&
+ str[word_length] != '\n' &&
+ !isblank((unsigned char) str[word_length]) )
+ {
+ wchar_t wc;
+ size_t amount = mbrtowc(&wc, str + word_length, SIZE_MAX, &ps);
+ if ( amount == (size_t) -2 )
+ break;
+ if ( amount == (size_t) -1 )
+ {
+ memset(&ps, 0, sizeof(ps));
+ amount = 1;
+ }
+ if ( amount == (size_t) 0 )
+ break;
+ word_length += amount;
+ int width = wcwidth(wc);
+ if ( width < 0 )
+ continue;
+ word_columns += width;
+ }
+ if ( (column && blank ? 1 : 0) + word_columns <= columns - column )
+ {
+ if ( column && blank )
+ {
+ putchar(' ');
+ column++;
+ }
+ blank = false;
+ fwrite(str, 1, word_length, stdout);
+ column += word_columns;
+ if ( column == columns )
+ column = 0;
+ }
+ else
+ {
+ if ( column != 0 && column != columns )
+ putchar('\n');
+ column = 0;
+ blank = false;
+ fwrite(str, 1, word_length, stdout);
+ column += word_columns;
+ column %= columns;
+ }
+ str += word_length;
+ }
+ fflush(stdout);
+}
+
+void textf(const char* format, ...)
+{
+ va_list ap;
+ va_start(ap, format);
+ char* str;
+ int len = vasprintf(&str, format, ap);
+ va_end(ap);
+ if ( len < 0 )
+ {
+ vprintf(format, ap);
+ return;
+ }
+ text(str);
+ free(str);
+}
+
+void prompt(char* buffer,
+ size_t buffer_size,
+ const char* question,
+ const char* answer)
+{
+ while ( true )
+ {
+ text(question);
+ if ( answer )
+ printf(" [%s] ", answer);
+ else
+ printf(" ");
+ fflush(stdout);
+ fgets(buffer, buffer_size, stdin);
+ size_t buffer_length = strlen(buffer);
+ if ( buffer_length && buffer[buffer_length-1] == '\n' )
+ buffer[--buffer_length] = '\0';
+ while ( buffer_length && buffer[buffer_length-1] == ' ' )
+ buffer[--buffer_length] = '\0';
+ if ( !strcmp(buffer, "") )
+ {
+ if ( !answer )
+ continue;
+ strlcpy(buffer, answer, buffer_size);
+ }
+ if ( !strcmp(buffer, "!") )
+ {
+ printf("Type 'exit' to return to install.\n");
+ fflush(stdout);
+ execute((const char*[]) { "sh", NULL }, "f");
+ continue;
+ }
+ if ( !strcmp(buffer, "!man") )
+ {
+ execute((const char*[]) { "man", prompt_man_section,
+ prompt_man_page, NULL }, "f");
+ continue;
+ }
+ break;
+ }
+}
+
+void password(char* buffer,
+ size_t buffer_size,
+ const char* question)
+{
+ unsigned int termmode;
+ gettermmode(0, &termmode);
+ settermmode(0, termmode & ~TERMMODE_ECHO);
+ text(question);
+ printf(" ");
+ fflush(stdout);
+ fflush(stdin);
+ // TODO: This may leave a copy of the password in the stdio buffer.
+ fgets(buffer, buffer_size, stdin);
+ fflush(stdin);
+ printf("\n");
+ size_t buffer_length = strlen(buffer);
+ if ( buffer_length && buffer[buffer_length-1] == '\n' )
+ buffer[--buffer_length] = '\0';
+ settermmode(0, termmode);
+}
+
+static bool has_program(const char* program)
+{
+ return execute((const char*[]) { "which", "--", program, NULL }, "q") == 0;
+}
+
+bool missing_program(const char* program)
+{
+ if ( has_program(program) )
+ return false;
+ warnx("%s: Program is absent", program);
+ return true;
+}
diff --git a/sysinstall/interactive.h b/sysinstall/interactive.h
new file mode 100644
index 00000000..315bf34e
--- /dev/null
+++ b/sysinstall/interactive.h
@@ -0,0 +1,42 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2015, 2016.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ interactive.h
+ Interactive utility functions.
+
+*******************************************************************************/
+
+#ifndef INTERACTIVE_H
+#define INTERACTIVE_H
+
+extern const char* prompt_man_section;
+extern const char* prompt_man_page;
+
+void shlvl(void);
+void text(const char* str);
+__attribute__((format(printf, 1, 2)))
+void textf(const char* format, ...);
+void prompt(char* buffer,
+ size_t buffer_size,
+ const char* question,
+ const char* answer);
+void password(char* buffer,
+ size_t buffer_size,
+ const char* question);
+bool missing_program(const char* program);
+
+#endif
diff --git a/sysinstall/manifest.c b/sysinstall/manifest.c
new file mode 100644
index 00000000..1edaca27
--- /dev/null
+++ b/sysinstall/manifest.c
@@ -0,0 +1,411 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2015, 2016.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ manifest.c
+ Manifest handling functions.
+
+*******************************************************************************/
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "execute.h"
+#include "fileops.h"
+#include "manifest.h"
+
+bool has_manifest(const char* manifest)
+{
+ char* path;
+ if ( asprintf(&path, "/tix/manifest/%s", manifest) < 0 )
+ {
+ warn("asprintf");
+ _exit(2);
+ }
+ bool result = access(path, F_OK) == 0;
+ free(path);
+ return result;
+}
+
+struct hardlink
+{
+ dev_t dev;
+ ino_t ino;
+ char* path;
+};
+
+void install_manifest(const char* manifest,
+ const char* from_prefix,
+ const char* to_prefix)
+{
+ printf(" - Installing %s...\n", manifest);
+ struct hardlink* hardlinks = NULL;
+ size_t hardlinks_used = 0;
+ size_t hardlinks_length = 0;
+ size_t buffer_size = 1 << 16;
+ char* buffer = malloc(buffer_size);
+ if ( !buffer )
+ {
+ warn("malloc");
+ _exit(2);
+ }
+ mode_t old_umask = umask(0000);
+ char* inmanifest;
+ if ( asprintf(&inmanifest, "%s/tix/manifest/%s", from_prefix, manifest) < 0 )
+ {
+ warn("asprintf");
+ _exit(2);
+ }
+ char* outmanifest;
+ if ( asprintf(&outmanifest, "%s/tix/manifest/%s", to_prefix, manifest) < 0 )
+ {
+ warn("asprintf");
+ _exit(2);
+ }
+ FILE* fpin = fopen(inmanifest, "r");
+ if ( !fpin )
+ {
+ warn("%s", inmanifest);
+ _exit(2);
+ }
+ FILE* fpout = fopen(outmanifest, "w");
+ if ( !fpout )
+ {
+ warn("%s", outmanifest);
+ _exit(2);
+ }
+ char* line = NULL;
+ size_t line_size = 0;
+ ssize_t line_length;
+ while ( 0 <= (errno = 0, line_length = getline(&line, &line_size, fpin)) )
+ {
+ if ( line_length && line[line_length-1] == '\n' )
+ line[--line_length] = '\0';
+ if ( fprintf(fpout, "%s\n", line) < 0 )
+ {
+ warn("write: %s", outmanifest);
+ _exit(2);
+ }
+ if ( line[0] != '/' )
+ continue;
+ char* in_path;
+ if ( asprintf(&in_path, "%s%s", from_prefix, line) < 0 )
+ {
+ warn("asprintf");
+ _exit(2);
+ }
+ char* out_path = line;
+ if ( asprintf(&out_path, "%s%s", to_prefix, line) < 0 )
+ {
+ warn("asprintf");
+ _exit(2);
+ }
+ struct stat inst;
+ if ( lstat(in_path, &inst) < 0 )
+ {
+ warn("%s", in_path);
+ _exit(2);
+ }
+ struct hardlink* hardlink = NULL;
+ if ( S_ISREG(inst.st_mode) && 2 <= inst.st_nlink )
+ {
+ for ( size_t i = 0; i < hardlinks_used; i++ )
+ {
+ if ( hardlinks[i].dev != inst.st_dev ||
+ hardlinks[i].ino != inst.st_ino )
+ continue;
+ hardlink = &hardlinks[i];
+ break;
+ }
+ }
+ if ( hardlink )
+ {
+ unlink(out_path);
+ if ( link(hardlink->path, out_path) < 0 )
+ {
+ warn("link: %s -> %s", hardlink->path, out_path);
+ _exit(2);
+ }
+ }
+ else if ( S_ISDIR(inst.st_mode) )
+ {
+ if ( mkdir(out_path, inst.st_mode & 07777) < 0 && errno != EEXIST )
+ {
+ warn("mkdir: %s", out_path);
+ _exit(2);
+ }
+ }
+ else if ( S_ISREG(inst.st_mode) )
+ {
+
+ int in_fd = open(in_path, O_RDONLY);
+ if ( in_fd < 0 )
+ {
+ warn("%s", in_path);
+ _exit(2);
+ }
+ unlink(out_path);
+ int out_fd = open(out_path, O_WRONLY | O_CREAT | O_TRUNC,
+ inst.st_mode & 07777);
+ if ( out_fd < 0 )
+ {
+ warn("%s", out_path);
+ _exit(2);
+ }
+ while ( true )
+ {
+ ssize_t amount = read(in_fd, buffer, buffer_size);
+ if ( amount < 0 )
+ {
+ warn("read: %s", in_path);
+ _exit(2);
+ }
+ if ( amount == 0 )
+ break;
+ if ( writeall(out_fd, buffer, (size_t) amount) < (size_t) amount )
+ {
+ warn("write: %s", out_path);
+ _exit(2);
+ }
+ }
+ close(out_fd);
+ close(in_fd);
+ if ( 2 <= inst.st_nlink )
+ {
+ if ( hardlinks_used == hardlinks_length )
+ {
+ // TODO: Multiplication overflow.
+ size_t new_length = hardlinks_length ? 2 * hardlinks_length : 16;
+ struct hardlink* new_hardlinks = (struct hardlink*)
+ reallocarray(hardlinks, new_length, sizeof(struct hardlink));
+ if ( !new_hardlinks )
+ {
+ warn("malloc");
+ _exit(2);
+ }
+ hardlinks = new_hardlinks;
+ hardlinks_length = new_length;
+ }
+ hardlinks[hardlinks_used].ino = inst.st_ino;
+ hardlinks[hardlinks_used].dev = inst.st_dev;
+ if ( !(hardlinks[hardlinks_used].path = strdup(out_path)) )
+ {
+ warn("strdup");
+ _exit(2);
+ }
+ hardlinks_used++;
+ }
+ }
+ else if ( S_ISLNK(inst.st_mode) )
+ {
+ ssize_t amount = readlink(in_path, buffer, buffer_size - 1);
+ if ( amount < 0 )
+ {
+ warn("readlink: %s", in_path);
+ _exit(2);
+ }
+ buffer[amount] = '\0';
+ unlink(out_path);
+ if ( symlink(buffer, out_path) < 0 && errno != EEXIST )
+ {
+ warn("symlink: %s", out_path);
+ _exit(2);
+ }
+ }
+ else
+ {
+ warnx("%s: Don't know how to copy this object", in_path);
+ _exit(2);
+ }
+ free(in_path);
+ free(out_path);
+ }
+ free(line);
+ if ( errno )
+ {
+ warn("%s", inmanifest);
+ _exit(2);
+ }
+ fclose(fpin);
+ if ( fclose(fpout) == EOF )
+ {
+ warn("close: %s", outmanifest);
+ _exit(2);
+ }
+ free(inmanifest);
+ free(outmanifest);
+ umask(old_umask);
+ free(buffer);
+ for ( size_t i = 0; i < hardlinks_used; i++ )
+ free(hardlinks[i].path);
+ free(hardlinks);
+}
+
+bool check_installed(const char* path, const char* package)
+{
+ FILE* fp = fopen(path, "r");
+ if ( !fp )
+ {
+ if ( errno != ENOENT )
+ warn("%s", path);
+ return false;
+ }
+ char* line = NULL;
+ size_t line_size = 0;
+ ssize_t line_length;
+ while ( 0 < (errno = 0, line_length = getline(&line, &line_size, fp)) )
+ {
+ if ( line[line_length-1] == '\n' )
+ line[--line_length] = '\0';
+ if ( !strcmp(line, package) )
+ {
+ free(line);
+ fclose(fp);
+ return true;
+ }
+ }
+ if ( errno != 0 )
+ warn("%s", path);
+ free(line);
+ fclose(fp);
+ return false;
+}
+
+static char* shell_single_quote(const char* string)
+{
+ char* result;
+ size_t result_size;
+ FILE* fp = open_memstream(&result, &result_size);
+ if (!fp)
+ return NULL;
+ fputc('\'', fp);
+ for ( size_t i = 0; string[i]; i++ )
+ {
+ if ( string[i] == '\'' )
+ fputs("\'\\\'\'", fp);
+ else
+ fputc((unsigned char) string[i], fp);
+ }
+ fputc('\'', fp);
+ fflush(fp);
+ int waserr = ferror(fp);
+ fclose(fp);
+ if (waserr) {
+ free(result);
+ return NULL;
+ }
+ return result;
+}
+
+static char* sort_file_cmd(const char* file)
+{
+ char* file_esc = shell_single_quote(file);
+ if ( !file_esc )
+ return NULL;
+ char* cmd;
+ if ( asprintf(&cmd, "sort -- %s", file_esc) < 0 )
+ {
+ free(file_esc);
+ return NULL;
+ }
+ free(file_esc);
+ return cmd;
+}
+
+void install_ports(const char* from_prefix, const char* to_prefix)
+{
+ char* inst_in_path;
+ char* inst_out_path;
+ if ( asprintf(&inst_in_path, "%s/tix/installed.list", from_prefix) < 0 ||
+ asprintf(&inst_out_path, "%s/tix/installed.list", to_prefix) < 0 )
+ {
+ warn("asprintf");
+ _exit(2);
+ }
+ if ( access_or_die(inst_in_path, F_OK) < 0 )
+ {
+ free(inst_in_path);
+ free(inst_out_path);
+ return;
+ }
+ char* cmd = sort_file_cmd(inst_in_path);
+ if ( !cmd )
+ {
+ warn("sort_file_cmd");
+ _exit(2);
+ }
+ FILE* fp = popen(cmd, "r");
+ if ( !fp )
+ {
+ warn("%s", cmd);
+ _exit(2);
+ }
+ char* line = NULL;
+ size_t line_size = 0;
+ ssize_t line_length;
+ while ( 0 < (errno = 0, line_length = getline(&line, &line_size, fp)) )
+ {
+ if ( line[line_length-1] == '\n' )
+ line[--line_length] = '\0';
+ if ( !check_installed(inst_out_path, line) )
+ {
+ FILE* inst_out_fp = fopen(inst_out_path, "a");
+ if ( !inst_out_fp ||
+ fprintf(inst_out_fp, "%s\n", line) < 0 ||
+ fflush(inst_out_fp) == EOF )
+ {
+ warn("%s", inst_out_path);
+ pclose(fp);
+ _exit(2);
+ }
+ fclose(inst_out_fp);
+ }
+ char* tixinfo_in;
+ char* tixinfo_out;
+ if ( asprintf(&tixinfo_in, "%s/tix/tixinfo/%s", from_prefix, line) < 0 ||
+ asprintf(&tixinfo_out, "%s/tix/tixinfo/%s", to_prefix, line) < 0 )
+ {
+ warn("asprintf");
+ pclose(fp);
+ _exit(2);
+ }
+ execute((const char*[]) { "cp", "--", tixinfo_in, tixinfo_out, NULL }, "_e");
+ free(tixinfo_in);
+ free(tixinfo_out);
+ install_manifest(line, from_prefix, to_prefix);
+ }
+ free(line);
+ if ( errno )
+ {
+ warn("%s", cmd);
+ pclose(fp);
+ _exit(2);
+ }
+ pclose(fp);
+ free(cmd);
+ free(inst_in_path);
+ free(inst_out_path);
+}
diff --git a/sysinstall/manifest.h b/sysinstall/manifest.h
new file mode 100644
index 00000000..a6c7099e
--- /dev/null
+++ b/sysinstall/manifest.h
@@ -0,0 +1,33 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2015, 2016.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ manifest.h
+ Manifest handling functions.
+
+*******************************************************************************/
+
+#ifndef MANIFEST_H
+#define MANIFEST_H
+
+bool has_manifest(const char* manifest);
+void install_manifest(const char* manifest,
+ const char* from_prefix,
+ const char* to_prefix);
+bool check_installed(const char* path, const char* package);
+void install_ports(const char* from_prefix, const char* to_prefix);
+
+#endif
diff --git a/sysinstall/release.c b/sysinstall/release.c
new file mode 100644
index 00000000..8c3c62cb
--- /dev/null
+++ b/sysinstall/release.c
@@ -0,0 +1,206 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2015, 2016.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ release.c
+ Utility functions to handle release information.
+
+*******************************************************************************/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "release.h"
+
+void release_free(struct release* release)
+{
+ free(release->pretty_name);
+}
+
+static char* os_release_eval(const char* string)
+{
+ char* result;
+ size_t result_size;
+ FILE* fp = open_memstream(&result, &result_size);
+ if ( !fp )
+ return NULL;
+ bool escaped = false;
+ bool singly_quote = false;
+ bool doubly_quote = false;
+ while ( *string )
+ {
+ char c = *string++;
+ if ( !escaped && !singly_quote && c == '\\' )
+ escaped = true;
+ else if ( !escaped && !doubly_quote && c == '\'' )
+ singly_quote = !singly_quote;
+ else if ( !escaped && !singly_quote && c == '"' )
+ doubly_quote = !doubly_quote;
+ else
+ {
+ fputc((unsigned char) c, fp);
+ escaped = false;
+ }
+ }
+ if ( fclose(fp) == EOF )
+ {
+ free(result);
+ return NULL;
+ }
+ return result;
+}
+
+static void parse_version(const char* string,
+ unsigned long* major_ptr,
+ unsigned long* minor_ptr)
+
+{
+ *major_ptr = strtoul(string, (char**) &string, 10);
+ if ( *string == '.' )
+ string++;
+ *minor_ptr = strtoul(string, (char**) &string, 10);
+}
+
+static void parse_release(const char* string,
+ unsigned long* major_ptr,
+ unsigned long* minor_ptr,
+ bool* dev_ptr)
+
+{
+ *major_ptr = strtoul(string, (char**) &string, 10);
+ if ( *string == '.' )
+ string++;
+ *minor_ptr = strtoul(string, (char**) &string, 10);
+ *dev_ptr = *string != '\0';
+}
+
+bool os_release_load(struct release* release,
+ const char* path,
+ const char* errpath)
+{
+ memset(release, 0, sizeof(*release));
+ FILE* fp = fopen(path, "r");
+ if ( !fp )
+ {
+ if ( errno != ENOENT )
+ warn("%s", errpath);
+ return false;
+ }
+ bool failure = false;
+ bool success = false;
+ bool found_id = false;
+ bool found_pretty_name = false;
+ bool found_sortix_abi = false;
+ bool found_version_id = false;
+ char* line = NULL;
+ size_t line_size = 0;
+ ssize_t line_length;
+ while ( 0 < (errno = 0, line_length = getline(&line, &line_size, fp)) )
+ {
+ if ( line[line_length-1] == '\n' )
+ line[--line_length] = '\0';
+ if ( !strncmp(line, "ID=", strlen("ID=")) )
+ {
+ const char* param = line + strlen("ID=");
+ found_id = true;
+ char* value = os_release_eval(param);
+ if ( value )
+ {
+ if ( strcmp(value, "sortix") != 0 )
+ {
+ warn("%s: ID does not specify a 'sortix' system", errpath);
+ failure = true;
+ }
+ free(value);
+ }
+ else
+ {
+ warn("malloc");
+ failure = true;
+ }
+ }
+ else if ( !strncmp(line, "PRETTY_NAME=", strlen("PRETTY_NAME=")) )
+ {
+ const char* param = line + strlen("PRETTY_NAME=");
+ found_pretty_name = true;
+ if ( !(release->pretty_name = os_release_eval(param)) )
+ {
+ warn("malloc");
+ failure = true;
+ }
+ }
+ else if ( !strncmp(line, "SORTIX_ABI=", strlen("SORTIX_ABI=")) )
+ {
+ const char* param = line + strlen("SORTIX_ABI=");
+ found_sortix_abi = true;
+ char* value = os_release_eval(param);
+ if ( value )
+ {
+ parse_version(value, &release->abi_major, &release->abi_minor);
+ free(value);
+ }
+ else
+ {
+ warn("malloc");
+ failure = true;
+ }
+ }
+ else if ( !strncmp(line, "VERSION_ID=", strlen("VERSION_ID=")) )
+ {
+ const char* param = line + strlen("VERSION_ID=");
+ found_version_id = true;
+ char* value = os_release_eval(param);
+ if ( value )
+ {
+ parse_release(value, &release->version_major,
+ &release->version_minor, &release->version_dev);
+ free(value);
+ }
+ else
+ {
+ warn("malloc");
+ failure = true;
+ }
+ }
+ }
+ if ( errno )
+ warn("%s", errpath);
+ else if ( failure )
+ ;
+ else if ( !found_id )
+ warnx("%s: No ID", errpath);
+ else if ( !found_pretty_name )
+ warnx("%s: No PRETTY_NAME", errpath);
+ else if ( !found_sortix_abi )
+ warnx("%s: No SORTIX_ABI", errpath);
+ else if ( !found_version_id )
+ warnx("%s: No VERSION_ID", errpath);
+ else
+ success = true;
+ free(line);
+ fclose(fp);
+ if ( failure || !success )
+ {
+ release_free(release);
+ return false;
+ }
+ return true;
+}
diff --git a/sysinstall/release.h b/sysinstall/release.h
new file mode 100644
index 00000000..e8ce67ef
--- /dev/null
+++ b/sysinstall/release.h
@@ -0,0 +1,41 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2015, 2016.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ release.h
+ Utility functions to handle release information.
+
+*******************************************************************************/
+
+#ifndef RELEASE_H
+#define RELEASE_H
+
+struct release
+{
+ char* pretty_name;
+ unsigned long version_major;
+ unsigned long version_minor;
+ bool version_dev;
+ unsigned long abi_major;
+ unsigned long abi_minor;
+};
+
+void release_free(struct release* release);
+bool os_release_load(struct release* release,
+ const char* path,
+ const char* errpath);
+
+#endif
diff --git a/sysinstall/sysinstall.8 b/sysinstall/sysinstall.8
new file mode 100644
index 00000000..04a53acb
--- /dev/null
+++ b/sysinstall/sysinstall.8
@@ -0,0 +1,23 @@
+.Dd $Mdocdate: January 5 2016 $
+.Dt SYSINSTALL 8
+.Os
+.Sh NAME
+.Nm sysinstall
+.Nd operating system installer
+.Sh SYNOPSIS
+.Nm sysinstall
+.Sh DESCRIPTION
+.Nm
+is an interactive command line program that creates a new installation of the
+operating system. It asks basic questions, prepares a new root filesystem,
+makes a copy of the current operating system there and configures it for usage.
+The installation proceeds as described in
+.Xr installation 7 .
+.Pp
+.Nm
+must be run as root with stdin, stdout and stderr all pointing to the terminal.
+It is intended to be run from a live environment and not from an actual
+installation.
+.Sh SEE ALSO
+.Xr installation 7 ,
+.Xr sysupgrade 8
diff --git a/sysinstall/sysinstall.c b/sysinstall/sysinstall.c
new file mode 100644
index 00000000..d213a415
--- /dev/null
+++ b/sysinstall/sysinstall.c
@@ -0,0 +1,985 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2015, 2016.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ sysinstall.c
+ Operating system installer.
+
+*******************************************************************************/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+// Sortix libc doesn't have its own proper at this time.
+#if defined(__sortix__)
+#include
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "devices.h"
+#include "execute.h"
+#include "fileops.h"
+#include "interactive.h"
+#include "manifest.h"
+
+const char* prompt_man_section = "7";
+const char* prompt_man_page = "installation";
+
+static struct partition_table* search_bios_boot_pt(struct filesystem* root_fs)
+{
+ char firmware[64];
+ if ( kernelinfo("firmware", firmware, sizeof(firmware)) != 0 )
+ return NULL;
+ if ( strcmp(firmware, "bios") != 0 )
+ return NULL;
+ struct blockdevice* bdev = root_fs->bdev;
+ while ( bdev->p )
+ bdev = bdev->p->parent_bdev;
+ if ( !bdev->pt )
+ return NULL;
+ struct partition_table* pt = bdev->pt;
+ if ( pt->type != PARTITION_TABLE_TYPE_GPT )
+ return NULL;
+ return pt;
+}
+
+static struct partition* search_bios_boot_search(struct partition_table* pt)
+{
+ for ( size_t i = 0; i < pt->partitions_count; i++ )
+ {
+ struct partition* p = pt->partitions[i];
+ if ( p->bdev.fs && !strcmp(p->bdev.fs->fstype_name, "biosboot") )
+ return p;
+ }
+ return NULL;
+}
+
+static struct partition* search_bios_boot_partition(struct filesystem* root_fs)
+{
+ struct partition_table* pt = search_bios_boot_pt(root_fs);
+ if ( !pt )
+ return NULL;
+ return search_bios_boot_search(pt);
+}
+
+static bool missing_bios_boot_partition(struct filesystem* root_fs)
+{
+ struct partition_table* pt = search_bios_boot_pt(root_fs);
+ if ( !pt )
+ return NULL;
+ return !search_bios_boot_search(pt);
+}
+
+static bool passwd_check(const char* passwd_path,
+ bool (*check)(struct passwd*, void*),
+ void* check_ctx)
+{
+ FILE* passwd = fopen(passwd_path, "r");
+ if ( !passwd )
+ {
+ if ( errno != ENOENT )
+ warn("%s", passwd_path);
+ return false;
+ }
+ struct passwd* pwd;
+ while ( (errno = 0, pwd = fgetpwent(passwd)) )
+ {
+ if ( check(pwd, check_ctx) )
+ {
+ fclose(passwd);
+ return true;
+ }
+ }
+ if ( errno != 0 )
+ warn("%s", passwd_path);
+ fclose(passwd);
+ return false;
+}
+
+static bool passwd_has_uid_check(struct passwd* pwd, void* ctx)
+{
+ return pwd->pw_uid == *(uid_t*) ctx;
+}
+
+static bool passwd_has_uid(const char* passwd_path, uid_t uid)
+{
+ return passwd_check(passwd_path, passwd_has_uid_check, &uid);
+}
+
+static bool passwd_has_name_check(struct passwd* pwd, void* ctx)
+{
+ return !strcmp(pwd->pw_name, (const char*) ctx);
+}
+
+static bool passwd_has_name(const char* passwd_path, const char* name)
+{
+ return passwd_check(passwd_path, passwd_has_name_check, (void*) name);
+}
+
+static void install_skel(const char* home, uid_t uid, gid_t gid)
+{
+ const char* argv[] = { "cp", "-RT", "--", "etc/skel", home, NULL };
+ execute(argv, "ug", uid, gid);
+}
+
+__attribute__((format(printf, 3, 4)))
+static bool install_configurationf(const char* path,
+ const char* mode,
+ const char* format,
+ ...)
+{
+ FILE* fp = fopen(path, mode);
+ if ( !fp )
+ {
+ warn("%s", path);
+ return false;
+ }
+ va_list ap;
+ va_start(ap, format);
+ int status = vfprintf(fp, format, ap);
+ va_end(ap);
+ if ( status < 0 )
+ {
+ warn("%s", path);
+ fclose(fp);
+ return false;
+ }
+ if ( fclose(fp) == EOF )
+ {
+ warn("%s", path);
+ return false;
+ }
+ return true;
+}
+
+static void grub_hash_password(char* buffer, size_t buffer_size, const char* pw)
+{
+ int pipe_fds[2];
+ if ( pipe(pipe_fds) < 0 )
+ err(2, "pipe");
+ pid_t pid = fork();
+ if ( pid < 0 )
+ err(2, "fork");
+ if ( pid == 0 )
+ {
+ close(pipe_fds[0]);
+ if ( dup2(pipe_fds[1], 1) < 0 )
+ _exit(2);
+ close(pipe_fds[1]);
+ const char* argv[] = { "grub-mkpasswd-pbkdf2", "-p", pw, NULL };
+ execvp(argv[0], (char* const*) argv);
+ _exit(127);
+ }
+ close(pipe_fds[1]);
+ size_t done = 0;
+ while ( done < buffer_size )
+ {
+ ssize_t amount = read(pipe_fds[0], buffer + done, buffer_size - done);
+ if ( amount < 0 )
+ err(2, "read");
+ if ( amount == 0 )
+ break;
+ done += amount;
+ }
+ if ( done && buffer[done-1] == '\n' )
+ done--;
+ if ( done == buffer_size )
+ done--;
+ buffer[done] = '\0';
+ close(pipe_fds[0]);
+ int exit_code;
+ waitpid(pid, &exit_code, 0);
+ if ( !WIFEXITED(exit_code) || WEXITSTATUS(exit_code) != 0 )
+ errx(2, "grub password hash failed");
+}
+
+static pid_t main_pid;
+static struct mountpoint* mountpoints = NULL;
+static size_t mountpoints_used = 0;
+static bool etc_made = false;
+static char etc[] = "/tmp/etc.XXXXXX";
+static bool fs_made = false;
+static char fs[] = "/tmp/fs.XXXXXX";
+
+static void unmount_all_but_root()
+{
+ for ( size_t n = mountpoints_used; n != 0; n-- )
+ {
+ size_t i = n - 1;
+ struct mountpoint* mountpoint = &mountpoints[i];
+ if ( !strcmp(mountpoint->entry.fs_file, "/") )
+ continue;
+ mountpoint_unmount(mountpoint);
+ }
+}
+
+void exit_handler(void)
+{
+ if ( getpid() != main_pid )
+ return;
+ chdir("/");
+ for ( size_t n = mountpoints_used; n != 0; n-- )
+ {
+ size_t i = n - 1;
+ struct mountpoint* mountpoint = &mountpoints[i];
+ mountpoint_unmount(mountpoint);
+ }
+ if ( fs_made )
+ rmdir(fs);
+ if ( etc_made )
+ execute((const char*[]) { "rm", "-rf", etc, NULL }, "");
+}
+
+int main(void)
+{
+ shlvl();
+
+ if ( !isatty(0) )
+ errx(2, "fatal: stdin is not a terminal");
+ if ( !isatty(1) )
+ errx(2, "fatal: stdout is not a terminal");
+ if ( !isatty(2) )
+ errx(2, "fatal: stderr is not a terminal");
+
+ if ( getuid() != 0 )
+ errx(2, "You need to be root to install %s", BRAND_DISTRIBUTION_NAME);
+ if ( getgid() != 0 )
+ errx(2, "You need to be group root to install %s", BRAND_DISTRIBUTION_NAME);
+
+ main_pid = getpid();
+ if ( atexit(exit_handler) != 0 )
+ err(2, "atexit");
+
+ if ( !mkdtemp(etc) )
+ err(2, "mkdtemp: %s", "/tmp/etc.XXXXXX");
+ etc_made = true;
+
+ if ( chdir(etc) < 0 )
+ err(2, "chdir: %s", etc);
+
+ struct utsname uts;
+ uname(&uts);
+
+ static char input[256];
+
+ textf("Hello and welcome to the " BRAND_DISTRIBUTION_NAME " " VERSIONSTR ""
+ " installer for %s.\n\n", uts.machine);
+
+ // '|' rather than '||' is to ensure side effects.
+ if ( missing_program("cut") |
+ missing_program("dash") |
+ missing_program("fsck.ext2") |
+ missing_program("grub-install") |
+ missing_program("man") |
+ missing_program("sed") |
+ missing_program("xargs") )
+ {
+ text("Warning: This system does not have the necessary third party "
+ "software installed to properly install this operating system.\n");
+ while ( true )
+ {
+ prompt(input, sizeof(input), "Sure you want to proceed?", "no");
+ if ( strcasecmp(input, "no") == 0 )
+ return 0;
+ if ( strcasecmp(input, "yes") == 0 )
+ break;
+ }
+ text("\n");
+ }
+
+ text("You are about to install a new operating system on this computer. "
+ "This is not something you should do on a whim or when you are "
+ "impatient. Take the time to read the documentation and be patient "
+ "while you learn the new system. This is a very good time to start an "
+ "external music player that plays soothing classical music on loop.\n\n");
+ const char* readies[] =
+ {
+ "Ready",
+ "Yes",
+ "Yeah",
+ "Yep",
+ "Let's go",
+ "Let's do this",
+ "Betcha",
+ "Sure am",
+ "You bet",
+ "It's very good music",
+ };
+ size_t num_readies = sizeof(readies) / sizeof(readies[0]);
+ const char* ready = readies[arc4random_uniform(num_readies)];
+ prompt(input, sizeof(input), "Ready?", ready);
+ text("\n");
+
+ text("This is not yet a fully fledged operating system. You should adjust "
+ "your expectations accordingly. You should not consider the system safe "
+ "for multi-user use. Filesystem permissions are not enforced yet. There "
+ "are many security issues so setuid(2) blatantly allows every user to "
+ "become root if they want to.\n\n");
+
+ text("You can always escape to a shell by answering '!' to any regular "
+ "prompt. You can view the installation(7) manual page by "
+ "answering '!man'. Default answers are in []'s and can be selected by "
+ "pressing enter.\n\n");
+ // TODO: You can leave this program by pressing ^C but it can leave your
+ // system in an inconsistent state.
+
+ install_configurationf("upgrade.conf", "a", "src = yes\n");
+
+ while ( true )
+ {
+ // TODO: Detect the name of the current keyboard layout.
+ prompt(input, sizeof(input),
+ "Choose your keyboard layout ('?' or 'L' for list)", "default");
+ if ( !strcmp(input, "?") ||
+ !strcmp(input, "l") ||
+ !strcmp(input, "L") )
+ {
+ DIR* dir = opendir("/share/kblayout");
+ if ( !dir )
+ {
+ warn("%s", "/share/kblayout");
+ continue;
+ }
+ bool space = false;
+ struct dirent* entry;
+ while ( (entry = readdir(dir)) )
+ {
+ if ( entry->d_name[0] == '.' )
+ continue;
+ if ( space )
+ putchar(' ');
+ fputs(entry->d_name, stdout);
+ space = true;
+ }
+ closedir(dir);
+ if ( !space )
+ fputs("(No keyboard layouts available)", stdout);
+ putchar('\n');
+ continue;
+ }
+ if ( !strcmp(input, "default") )
+ break;
+ const char* argv[] = { "chkblayout", "--", input, NULL };
+ if ( execute(argv, "f") == 0 )
+ break;
+ }
+ if ( !input[0] || !strcmp(input, "default") )
+ text("/etc/kblayout will not be created (default).\n");
+ else
+ {
+ textf("/etc/kblayout will be set to \"%s\".\n", input);
+ mode_t old_umask = getumask();
+ umask(022);
+ install_configurationf("kblayout", "w", "%s\n", input);
+ umask(old_umask);
+ }
+ text("\n");
+
+ struct dispmsg_get_driver_name dgdn = { 0 };
+ dgdn.msgid = DISPMSG_GET_DRIVER_NAME;
+ dgdn.device = 0;
+ dgdn.driver_index = 0;
+ dgdn.name.byte_size = 0;
+ dgdn.name.str = NULL;
+ if ( dispmsg_issue(&dgdn, sizeof(dgdn)) == 0 || errno != ENODEV )
+ {
+ while ( true )
+ {
+ prompt(input, sizeof(input),
+ "Select default display resolution? (yes/no)", "yes");
+ if ( strcasecmp(input, "no") && strcasecmp(input, "yes") )
+ continue;
+ bool was_no = strcasecmp(input, "no") == 0;
+ input[0] = '\0';
+ if ( was_no )
+ break;
+ if ( execute((const char*[]) { "chvideomode", NULL }, "f") != 0 )
+ continue;
+ struct dispmsg_get_crtc_mode get_mode;
+ get_mode.msgid = DISPMSG_GET_CRTC_MODE;
+ get_mode.device = 0;
+ get_mode.connector = 0;
+ if ( dispmsg_issue(&get_mode, sizeof(get_mode)) < 0 )
+ break;
+ if ( !(get_mode.mode.control & DISPMSG_CONTROL_VALID) )
+ break;
+ if ( get_mode.mode.control & DISPMSG_CONTROL_VGA )
+ break;
+ snprintf(input, sizeof(input), "%ux%ux%u",
+ get_mode.mode.view_xres,
+ get_mode.mode.view_yres,
+ get_mode.mode.fb_format);
+ break;
+ }
+ }
+ if ( !input[0] )
+ text("/etc/videomode will not be created.\n");
+ else
+ {
+ textf("/etc/videomode will be set to \"%s\".\n", input);
+ mode_t old_umask = getumask();
+ umask(022);
+ install_configurationf("videomode", "w", "%s\n", input);
+ umask(old_umask);
+ }
+ text("\n");
+
+ scan_devices();
+
+ textf("You need a bootloader to start the operating system. GRUB is the "
+ "standard %s bootloader and this installer comes with a copy. "
+ "This copy is however unable to automatically detect other operating "
+ "systems.\n\n", BRAND_DISTRIBUTION_NAME);
+ text("Single-boot installations should accept this bootloader.\n");
+ text("Dual-boot systems should refuse it and manually arrange for "
+ "bootloading by configuring any existing multiboot compliant "
+ "bootloader.\n");
+ text("\n");
+ char accept_grub[10];
+ char accept_grub_password[10];
+ char grub_password[512];
+ while ( true )
+ {
+ const char* def = "yes";
+ if ( check_existing_systems() )
+ def = "no";
+ prompt(accept_grub, sizeof(accept_grub),
+ "Install a new GRUB bootloader?", def);
+ if ( strcasecmp(accept_grub, "no") == 0 ||
+ strcasecmp(accept_grub, "yes") == 0 )
+ break;
+ }
+ text("\n");
+
+ if ( strcasecmp(accept_grub, "yes") == 0 )
+ {
+ install_configurationf("upgrade.conf", "a", "grub = yes\n");
+
+ text("If an unauthorized person has access to the bootloader command "
+ "line, then the whole system security can be compromised. You can "
+ "prevent this by password protecting interactive use of the "
+ "bootloader, but still allowing anyone to start the system "
+ "normally. Similarly you may wish to manually go into your "
+ "firmware and password protect it.\n");
+ text("\n");
+ while ( true )
+ {
+ prompt(accept_grub_password, sizeof(accept_grub_password),
+ "Password protect interactive bootloader? (yes/no)", "yes");
+ if ( strcasecmp(accept_grub_password, "no") == 0 ||
+ strcasecmp(accept_grub_password, "yes") == 0 )
+ break;
+ }
+ while ( !strcasecmp(accept_grub_password, "yes") )
+ {
+ char first[128];
+ char second[128];
+ password(first, sizeof(first),
+ "Bootloader root password? (will not echo)");
+ password(second, sizeof(second),
+ "Bootloader root password? (again)");
+ if ( strcmp(first, second) != 0 )
+ {
+ printf("Passwords do not match, try again.\n");
+ continue;
+ }
+ explicit_bzero(second, sizeof(second));
+ if ( !strcmp(first, "") )
+ {
+ char answer[32];
+ prompt(answer, sizeof(answer),
+ "Empty password is stupid, are you sure? (yes/no)", "no");
+ if ( strcasecmp(answer, "yes") != 0 )
+ continue;
+ }
+ grub_hash_password(grub_password, sizeof(grub_password), first);
+ textf("/etc/grubpw will be made with grub-mkpasswd-pbkdf2.\n");
+ mode_t old_umask = getumask();
+ umask(077);
+ install_configurationf("grubpw", "w", "%s\n", grub_password);
+ umask(old_umask);
+ break;
+ }
+ text("\n");
+ }
+
+ // TODO: Offer the user an automatic layout of partitions if the disk is
+ // empty.
+
+ // TODO: Perhaps let the user know the size of the system that will be
+ // installed?
+
+ text("You need select a root filesystem and other mountpoints now. You "
+ "will now be dumped into a partition editor. Create and format a "
+ "root filesystem partition as needed.\n");
+ text("\n");
+ const char* mktable_tip = "";
+ if ( check_lacking_partition_table() )
+ mktable_tip = "Type mktable to make a new partition table. ";
+ textf("Type ls to list partitions on the device. "
+ "%s"
+ "Type mkpart to make a new partition. "
+ "Type mount 2 /mnt to create a mountpoint for partition 2. "
+ "Type exit when done. "
+ "There is partitioning advise in installation(7). "
+ "Type man 8 disked to display the disked(8) man page.\n",
+ mktable_tip);
+ struct filesystem* root_filesystem = NULL;
+ bool not_first = false;
+ while ( true )
+ {
+ if ( not_first )
+ text("Type man to display the disked(8) man page.\n");
+ not_first = true;
+ const char* argv[] = { "disked", "--fstab=fstab", NULL };
+ if ( execute(argv, "f") != 0 )
+ {
+ // TODO: We also end up here on SIGINT.
+ // TODO: Offer a shell here instead of failing?
+ warnx("partitioning failed");
+ sleep(1);
+ continue;
+ }
+ free_mountpoints(mountpoints, mountpoints_used);
+ mountpoints = NULL;
+ mountpoints_used = 0;
+ scan_devices();
+ if ( !load_mountpoints("fstab", &mountpoints, &mountpoints_used) )
+ {
+ if ( errno == ENOENT )
+ text("You have not created any mountpoints. Try again.\n");
+ else
+ warn("fstab");
+ continue;
+ }
+ bool found_rootfs = false;
+ for ( size_t i = 0; !found_rootfs && i < mountpoints_used; i++ )
+ if ( !strcmp(mountpoints[i].entry.fs_file, "/") )
+ found_rootfs = true;
+ if ( !found_rootfs )
+ {
+ text("You have no root filesystem mountpoint. Try again.\n");
+ continue;
+ }
+ root_filesystem = NULL;
+ for ( size_t i = 0; i < mountpoints_used; i++ )
+ {
+ struct mountpoint* mnt = &mountpoints[i];
+ const char* spec = mnt->entry.fs_spec;
+ if ( !(mnt->fs = search_for_filesystem_by_spec(spec)) )
+ {
+ warnx("fstab: Found no filesystem matching `%s'", spec);
+ continue;
+ }
+ if ( !mnt->fs->driver )
+ {
+ textf("Don't know how to mount a root filesystem of type %s. "
+ "Try again.\n", mnt->fs->fstype_name);
+ continue;
+ }
+ if ( !strcmp(mnt->entry.fs_file, "/") )
+ root_filesystem = mnt->fs;
+ }
+ assert(root_filesystem);
+ if ( !strcasecmp(accept_grub, "yes") &&
+ missing_bios_boot_partition(root_filesystem) )
+ {
+ textf("You are a installing BIOS bootloader and the root "
+ "filesystem is located on a GPT partition, but you haven't "
+ "made a BIOS boot partition on the root GPT disk. Pick "
+ "biosboot during mkpart and make a 1 MiB partition.\n");
+ char return_to_disked[10];
+ while ( true )
+ {
+ prompt(return_to_disked, sizeof(return_to_disked),
+ "Return to disked to make a BIOS boot partition?", "yes");
+ if ( strcasecmp(accept_grub, "no") == 0 ||
+ strcasecmp(accept_grub, "yes") == 0 )
+ break;
+ }
+ if ( !strcasecmp(return_to_disked, "yes") )
+ continue;
+ text("Proceeding, but expect the installation to fail.\n");
+ }
+ break;
+ }
+ text("\n");
+
+ textf("We are now ready to install %s %s. Take a moment to verify "
+ "everything is sane.\n", BRAND_DISTRIBUTION_NAME, VERSIONSTR);
+ text("\n");
+ printf(" %-16s system architecture\n", uts.machine);
+ for ( size_t i = 0; i < mountpoints_used; i++ )
+ {
+ struct mountpoint* mnt = &mountpoints[i];
+ const char* devname = path_of_blockdevice(mnt->fs->bdev);
+ const char* where = mnt->entry.fs_file;
+ printf(" %-16s use as %s\n", devname, where);
+ }
+ if ( strcasecmp(accept_grub, "yes") == 0 )
+ {
+ struct partition* bbp = search_bios_boot_partition(root_filesystem);
+ if ( bbp )
+ printf(" %-16s bios boot partition\n",
+ path_of_blockdevice(&bbp->bdev));
+ printf(" %-16s bootloader installation target\n",
+ device_path_of_blockdevice(root_filesystem->bdev));
+ }
+ text("\n");
+
+ while ( true )
+ {
+ prompt(input, sizeof(input),
+ "Install " BRAND_DISTRIBUTION_NAME "? (yes/no)", "yes");
+ if ( strcasecmp(input, "yes") != 0 )
+ {
+ text("Everything isn't sane? Answer '!' to get a shell or type ^C "
+ "to abort the install.\n");
+ continue;
+ }
+ break;
+ }
+ text("\n");
+
+ text("Installing " BRAND_DISTRIBUTION_NAME " " VERSIONSTR " now:\n");
+
+ printf(" - Mounting filesystems...\n");
+
+ if ( !mkdtemp(fs) )
+ err(2, "mkdtemp: %s", "/tmp/fs.XXXXXX");
+ fs_made = true;
+ setenv("SYSINSTALL_TARGET", fs, 1);
+
+ for ( size_t i = 0; i < mountpoints_used; i++ )
+ {
+ struct mountpoint* mnt = &mountpoints[i];
+ char* absolute;
+ if ( asprintf(&absolute, "%s%s", fs, mnt->absolute) < 0 )
+ err(2, "asprintf");
+ free(mnt->absolute);
+ mnt->absolute = absolute;
+ if ( mkdir_p(mnt->absolute, 0755) < 0 )
+ err(2, "mkdir: %s", mnt->absolute);
+ mountpoint_mount(mnt);
+ }
+
+ if ( chdir(fs) < 0 )
+ err(2, "chdir: %s", fs);
+
+ pid_t install_pid = fork();
+ if ( install_pid < 0 )
+ err(2, "fork");
+ if ( install_pid == 0 )
+ {
+ printf(" - Populating root filesystem...\n");
+ umask(0000);
+ chmod(".", 0755);
+ mkdir_or_chmod_or_die("bin", 0755);
+ mkdir_or_chmod_or_die("boot", 0755);
+ mkdir_or_chmod_or_die("dev", 0755);
+ mkdir_or_chmod_or_die("etc", 0755);
+ mkdir_or_chmod_or_die("etc/skel", 0755);
+ mkdir_or_chmod_or_die("etc/init", 0755);
+ mkdir_or_chmod_or_die("home", 0755);
+ mkdir_or_chmod_or_die("include", 0755);
+ mkdir_or_chmod_or_die("lib", 0755);
+ mkdir_or_chmod_or_die("mnt", 0755);
+ mkdir_or_chmod_or_die("root", 0700);
+ mkdir_or_chmod_or_die("sbin", 0755);
+ mkdir_or_chmod_or_die("share", 0755);
+ mkdir_or_chmod_or_die("tix", 0755);
+ mkdir_or_chmod_or_die("tix/manifest", 7555);
+ mkdir_or_chmod_or_die("tmp", 01777);
+ mkdir_or_chmod_or_die("var", 0755);
+ mkdir_or_chmod_or_die("var/empty", 0555);
+ umask(0022);
+ if ( access("tix/collection.conf", F_OK) < 0 )
+ execute((const char*[]) { "tix-collection", ".", "create",
+ "--prefix=", NULL }, "_e");
+ install_manifest("system", "", ".");
+ // TODO: Preserve the existing /src if it exists like in sysupgrade.
+ if ( has_manifest("src") )
+ install_manifest("src", "", ".");
+ printf(" - Creating configuration files...\n");
+ // TODO: Preserve mode/ownership/timestamps?
+ execute((const char*[]) { "cp", "-RTP", etc, "etc", NULL }, "_e");
+ // TODO: Auto detect appropriate bcrypt rounds and set up etc/login.conf
+ // and use those below instead of blowfish,a.
+ install_ports("", ".");
+ printf(" - Creating initrd...\n");
+ execute((const char*[]) { "update-initrd", "--sysroot", fs, NULL }, "_e");
+ if ( strcasecmp(accept_grub, "yes") == 0 )
+ {
+ printf(" - Installing bootloader...\n");
+ execute((const char*[]) { "chroot", "-d", ".", "grub-install",
+ device_path_of_blockdevice(root_filesystem->bdev), NULL },
+ "_eqQ");
+ printf(" - Configuring bootloader...\n");
+ execute((const char*[]) { "chroot", "-d", ".", "update-grub", NULL },
+ "_eqQ");
+ }
+ else if ( access_or_die("/etc/grub.d/10_sortix", F_OK) == 0 )
+ {
+ // Help dual booters by making /etc/grub.d/10_sortix.cache.
+ printf(" - Creating bootloader fragment...\n");
+ execute((const char*[]) { "chroot", "-d", ".",
+ "/etc/grub.d/10_sortix", NULL }, "_eq");
+ }
+ printf(" - Finishing installation...\n");
+ _exit(0);
+ }
+ int install_code;
+ waitpid(install_pid, &install_code, 0);
+ if ( WIFEXITED(install_code) && WEXITSTATUS(install_code) == 0 )
+ {
+ }
+ else if ( WIFEXITED(install_code) )
+ errx(2, "installation failed with exit status %i", WEXITSTATUS(install_code));
+ else if ( WIFSIGNALED(install_code) )
+ errx(2, "installation failed: %s", strsignal(WTERMSIG(install_code)));
+ else
+ errx(2, "installation failed: unknown waitpid code %i", install_code);
+
+ unsetenv("SYSINSTALL_ETC");
+ execute((const char*[]) { "rm", "-r", etc, NULL }, "");
+ etc_made = false;
+
+ text("\n");
+ text("System files are now installed. We'll now make the system functional "
+ "by configuring a few essential matters.\n\n");
+
+ umask(0022);
+
+ if ( access("etc/hostname", F_OK) == 0 )
+ textf("/etc/hostname already exists, skipping creating it.\n");
+ else while ( true )
+ {
+ char defhost[HOST_NAME_MAX + 1] = "";
+ FILE* defhost_fp = fopen("etc/hostname", "r");
+ if ( defhost_fp )
+ {
+ fgets(defhost, sizeof(defhost), defhost_fp);
+ size_t defhostlen = strlen(defhost);
+ if ( defhostlen && defhost[defhostlen-1] == '\n' )
+ defhost[defhostlen-1] = '\0';
+ fclose(defhost_fp);
+ }
+ char hostname[HOST_NAME_MAX + 1] = "";
+ prompt(hostname, sizeof(hostname), "System hostname?",
+ defhost[0] ? defhost : NULL);
+ if ( !install_configurationf("etc/hostname", "w", "%s\n", hostname) )
+ continue;
+ textf("/etc/hostname was set to \"%s\".\n", hostname);
+ break;
+ }
+ text("\n");
+
+ if ( passwd_has_uid("etc/passwd", 0) ||
+ passwd_has_name("etc/passwd", "root") )
+ {
+ textf("Root account already exists, skipping creating it.\n");
+ }
+ else while ( true )
+ {
+ char first[128];
+ char second[128];
+ password(first, sizeof(first), "Password for root account? (will not echo)");
+ password(second, sizeof(second), "Password for root account? (again)");
+ if ( strcmp(first, second) != 0 )
+ {
+ printf("Passwords do not match, try again.\n");
+ continue;
+ }
+ explicit_bzero(second, sizeof(second));
+ if ( !strcmp(first, "") )
+ {
+ char answer[32];
+ prompt(answer, sizeof(answer),
+ "Empty password is stupid, are you sure? (yes/no)", "no");
+ if ( strcasecmp(answer, "yes") != 0 )
+ continue;
+ }
+ char hash[128];
+ if ( crypt_newhash(first, "blowfish,a", hash, sizeof(hash)) < 0 )
+ {
+ explicit_bzero(first, sizeof(first));
+ warn("crypt_newhash");
+ continue;
+ }
+ explicit_bzero(first, sizeof(first));
+ if ( !install_configurationf("etc/passwd", "a",
+ "root:%s:0:0:root:/root:sh\n", hash) )
+ continue;
+ textf("User '%s' added to /etc/passwd\n", "root");
+ if ( !install_configurationf("etc/group", "a", "root::0:root\n") )
+ continue;
+ install_skel("/root", 0, 0);
+ textf("Group '%s' added to /etc/group.\n", "root");
+ break;
+ }
+ text("\n");
+
+ install_configurationf("etc/init/target", "w", "multi-user\n");
+
+ text("Congratulations, the system is now functional! This is a good time "
+ "to do further customization of the system.\n\n");
+
+ bool made_user = false;
+ for ( uid_t uid = 1000; true; )
+ {
+ while ( passwd_has_uid("etc/passwd", uid) )
+ uid++;
+ gid_t gid = (gid_t) uid;
+ static char userstr[256];
+ const char* question = "Setup a user? (enter username or 'no')";
+ if ( made_user )
+ question = "Setup another user? (enter username or 'no')";
+ prompt(userstr, sizeof(userstr), question, "no");
+ if ( !strcmp(userstr, "no") )
+ break;
+ if ( !strcmp(userstr, "yes") )
+ continue;
+ const char* user = userstr;
+ while ( user[0] == ' ')
+ user++;
+ if ( passwd_has_name("etc/passwd", user) )
+ {
+ textf("Account '%s' already exists.\n", user);
+ continue;
+ }
+ static char name[256];
+ prompt(name, sizeof(name), "Full name of user?", user);
+ char first[128];
+ char second[128];
+ while ( true )
+ {
+ password(first, sizeof(first), "Password for user? (will not echo)");
+ password(second, sizeof(second), "Password for user? (again)");
+ if ( strcmp(first, second) != 0 )
+ {
+ printf("Passwords do not match, try again.\n");
+ continue;
+ }
+ explicit_bzero(second, sizeof(second));
+ if ( !strcmp(first, "") )
+ {
+ char answer[32];
+ prompt(answer, sizeof(answer),
+ "Empty password is stupid, are you sure? (yes/no)", "no");
+ if ( strcasecmp(answer, "yes") != 0 )
+ continue;
+ }
+ break;
+ }
+ char hash[128];
+ if ( crypt_newhash(first, "blowfish,a", hash, sizeof(hash)) < 0 )
+ {
+ explicit_bzero(first, sizeof(first));
+ warn("crypt_newhash");
+ continue;unmount_all_but_root();
+ }
+ explicit_bzero(first, sizeof(first));
+ if ( !install_configurationf("etc/passwd", "a",
+ "%s:%s:%" PRIuUID ":%" PRIuGID ":%s:/home/%s:sh\n",
+ user, hash, uid, gid, name, user) )
+ continue;
+ if ( !install_configurationf("etc/group", "a",
+ "%s::%" PRIuGID ":%s\n", user, gid, user) )
+ continue;
+ char* home;
+ if ( asprintf(&home, "home/%s", user) < 0 )
+ {
+ warn("asprintf");
+ continue;
+ }
+ if ( mkdir(home, 0700) < 0 && errno != EEXIST )
+ {
+ warn("mkdir: %s", home);
+ free(home);
+ continue;
+ }
+ chown(home, uid, gid);
+ install_skel(home, uid, gid);
+ free(home);
+ textf("User '%s' added to /etc/passwd\n", user);
+ textf("Group '%s' added to /etc/group.\n", user);
+ text("\n");
+ uid++;
+ made_user = true;
+ }
+ text("\n");
+
+ text("It's time to boot into the newly installed system.\n\n");
+
+ if ( strcasecmp(accept_grub, "no") == 0 )
+ {
+ text("You did not accept a bootloader and need to arrange by "
+ "bootloading by your own means. /etc/grub.d/10_sortix.cache is a "
+ "GRUB configuration fragment that boots the newly installed "
+ "system.\n\n");
+ }
+
+ text("Upon boot, you'll be greeted with a login screen. Enter your "
+ "credentials to get a command line. Login as user 'poweroff' as "
+ "described in login(8) to power off the machine. After logging in, "
+ "type 'man user-guide' to view the introductory documentation.\n");
+ text("\n");
+
+ while ( true )
+ {
+ prompt(input, sizeof(input), "What now? (poweroff/reboot/boot)", "boot");
+ if ( !strcasecmp(input, "poweroff") )
+ exit(0);
+ if ( !strcasecmp(input, "reboot") )
+ exit(1);
+ if ( !strcasecmp(input, "boot") )
+ {
+ unmount_all_but_root();
+ unsetenv("SYSINSTALL_TARGET");
+ unsetenv("SHLVL");
+ unsetenv("INIT_PID");
+ // TODO: If / is a kernel ramfs, and this is a live environment,
+ // then uninstall the base system to save memory.
+ exit(execute((const char*[]) { "chroot", "-d", fs,
+ "/sbin/init", NULL }, "f"));
+ }
+ }
+}
diff --git a/sysinstall/sysmerge.8 b/sysinstall/sysmerge.8
new file mode 100644
index 00000000..9d9c58ea
--- /dev/null
+++ b/sysinstall/sysmerge.8
@@ -0,0 +1,123 @@
+.Dd $Mdocdate: February 14 2016 $
+.Dt SYSMERGE 8
+.Os
+.Sh NAME
+.Nm sysmerge
+.Nd upgrade current operating system from a sysroot
+.Sh SYNOPSIS
+.Nm sysmerge
+.Op Fl w
+.Op Fl \-booting
+.Op Fl \-cancel
+.Op Fl \-wait
+.Ar source
+.Sh DESCRIPTION
+.Nm
+upgrades the current operating system by copying the system files from the
+specified
+.Ar source
+directory (usually a sysroot) onto the current root filesystem. This is meant
+to be used when building the operating system from source as described in
+.Xr development 7 .
+.Pp
+.Nm
+installs the
+.Sy system
+manifest from the tix repository in the
+.Ar source
+directory, as well as all the ports found. The
+.Xr initrd 7
+is regenerated using
+.Xr update-initrd 8 .
+If grub is enabled in
+.Xr upgrade.conf 5 ,
+then the bootloader is reinstalled and reconfigured as needed.
+.Pp
+.Nm
+is an automatic and non-interactive upgrade. It is meant to be used as part of
+the development process to upgrade to locally built versions. It does not
+contain compatibility to help upgrade across larger changes, or to easily handle
+changes to configuration file formats, or other caveats. It places a burden on
+the system administrator/developer to know the system changes in detail and to
+do any necessary tasks manually.
+.Pp
+The
+.Xr sysupgrade 8
+program is by contrast an interactive program, meant to help upgrading across
+much larger development distances. It contains compatibility to automatically
+handle changes in configuration files and other larger changes.
+.Pp
+The options are as follows:
+.Bl -tag -width "12345678"
+.It Fl \-booting
+It's boot time, complete the system upgrade that was delayed. This is meant to
+be used by
+.Xr init 8
+through the
+.Sy chain-merge
+boot target. This installs the
+.Pa /sysmerge
+directory onto the root filesystem and removes the
+.Pa /sysmerge
+directory.
+.It Fl \-cancel
+Cancel a pending upgrade that would trigger on the next boot. Remove the
+.Pa /sysmerge
+directory and restore the old
+.Xr kernel 7
+and
+.Xr initrd 7 .
+.It Fl w , Fl \-wait
+Wait until the next boot to complete the upgrade, rather than finishing it now.
+This installs into the
+.Pa /sysmerge
+directory instead and replaces the
+.Xr kernel 7
+with the new kernel
+and
+.Xr initrd 7
+with an initrd that runs
+.Sy /sysmerge/sbin/sysmerge --booting
+on boot through the
+.Sy chain-merge
+.Xr init 8
+boot target. Backups are made of the
+.Xr kernel 7
+and
+.Xr initrd 7
+such that the operation can be rolled back.
+.El
+.Sh FILES
+.Bl -tag -width "/boot/sortix.initrd.sysmerge.orig" -compact
+.It Pa /boot/sortix.bin
+system
+.Xr kernel 7
+.It Pa /boot/sortix.bin.sysmerge.orig
+system
+.Xr kernel 7
+(backup)
+.It Pa /boot/sortix.initrd
+system
+.Xr initrd 7
+.It Pa /boot/sortix.initrd.sysmerge.orig
+system
+.Xr initrd 7
+(backup)
+.It Pa /etc/machine
+processor platform of this installation
+.It Pa /etc/sortix-release
+the current system release
+.It Pa /etc/upgrade.conf
+controls the bootloader upgrade behavior (see
+.Xr upgrade.conf 5 )
+.It Pa /sysmerge
+pending upgrade is stored here
+.El
+.Sh SEE ALSO
+.Xr development 7 ,
+.Xr initrd 7 ,
+.Xr installation 7 ,
+.Xr kernel 7 ,
+.Xr upgrade 7 ,
+.Xr sysinstall 8 ,
+.Xr sysupgrade 8
diff --git a/sysinstall/sysmerge.c b/sysinstall/sysmerge.c
new file mode 100644
index 00000000..a0d64e4d
--- /dev/null
+++ b/sysinstall/sysmerge.c
@@ -0,0 +1,260 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2016.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ sysmerge.c
+ Upgrade current operating system from a sysroot.
+
+*******************************************************************************/
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "conf.h"
+#include "execute.h"
+#include "fileops.h"
+#include "manifest.h"
+#include "release.h"
+
+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 bool has_pending_upgrade(void)
+{
+ return access_or_die("/boot/sortix.bin.sysmerge.orig", F_OK) == 0 ||
+ access_or_die("/boot/sortix.initrd.sysmerge.orig", F_OK) == 0 ||
+ access_or_die("/sysmerge", F_OK) == 0;
+}
+
+static void help(FILE* fp, const char* argv0)
+{
+ fprintf(fp, "Usage: %s [OPTION]... SOURCE\n", argv0);
+ fprintf(fp, "Merge the files from SOURCE onto the current system.\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[])
+{
+ setvbuf(stdout, NULL, _IOLBF, 0); // Pipes.
+
+ bool cancel = false;
+ bool booting = false;
+ bool wait = false;
+
+ 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] != '-' )
+ {
+ char c;
+ while ( (c = *++arg) ) switch ( c )
+ {
+ case 'w': wait = true; break;
+ default:
+ fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c);
+ help(stderr, argv0);
+ exit(1);
+ }
+ }
+ else if ( !strcmp(arg, "--help") )
+ help(stdout, argv0), exit(0);
+ else if ( !strcmp(arg, "--version") )
+ version(stdout, argv0), exit(0);
+ else if ( !strcmp(arg, "--cancel") )
+ cancel = true;
+ else if ( !strcmp(arg, "--booting") )
+ booting = true;
+ else if ( !strcmp(arg, "--wait") )
+ wait = true;
+ else
+ {
+ fprintf(stderr, "%s: unknown option: %s\n", argv0, arg);
+ help(stderr, argv0);
+ exit(1);
+ }
+ }
+
+ compact_arguments(&argc, &argv);
+
+ const char* source;
+ if ( cancel )
+ {
+ source = NULL;
+ if ( 1 < argc )
+ err(2, "Unexpected extra operand `%s'", argv[2]);
+ }
+ else if ( booting )
+ {
+ source = "/sysmerge";
+ if ( 1 < argc )
+ err(2, "Unexpected extra operand `%s'", argv[2]);
+ }
+ else
+ {
+ if ( argc < 2 )
+ err(2, "No source operand was given");
+ source = argv[1];
+ if ( 2 < argc )
+ err(2, "Unexpected extra operand `%s'", argv[2]);
+ }
+
+ if ( booting )
+ {
+ }
+ else if ( has_pending_upgrade() )
+ {
+ rename("/boot/sortix.bin.sysmerge.orig", "/boot/sortix.bin");
+ rename("/boot/sortix.initrd.sysmerge.orig", "/boot/sortix.initrd");
+ execute((const char*[]) { "rm", "-rf", "/sysmerge", NULL }, "");
+ execute((const char*[]) { "update-initrd", NULL }, "_e");
+ printf("Cancelled pending system upgrade.\n");
+ }
+ else if ( cancel )
+ printf("No system upgrade was pending.\n");
+
+ if ( cancel )
+ return 0;
+
+ const char* old_release_path = "/etc/sortix-release";
+ struct release old_release;
+ if ( !os_release_load(&old_release, old_release_path, old_release_path) )
+ exit(2);
+
+ char* new_release_path;
+ if ( asprintf(&new_release_path, "%s/etc/sortix-release", source) < 0 )
+ err(2, "asprintf");
+ struct release new_release;
+ if ( !os_release_load(&new_release, new_release_path, new_release_path) )
+ exit(2);
+ free(new_release_path);
+
+ // TODO: Check if /etc/machine matches the current architecture.
+ // TODO: Check for version (skipping, downgrading).
+
+ struct conf conf;
+ load_upgrade_conf(&conf, "/etc/upgrade.conf");
+
+ bool can_run_old_abi = old_release.abi_major == new_release.abi_major &&
+ old_release.abi_minor <= new_release.abi_minor;
+ if ( !can_run_old_abi && !wait )
+ {
+ printf("Incompatible %lu.%lu -> %lu.%lu ABI transition, "
+ "delaying upgrade to next boot.\n",
+ old_release.abi_major, old_release.abi_major,
+ new_release.abi_major, new_release.abi_major);
+ wait = true;
+ }
+
+ const char* target;
+ if ( wait )
+ {
+ printf("Scheduling upgrade to %s on next boot using %s:\n",
+ new_release.pretty_name, source);
+ target = "/sysmerge";
+ if ( mkdir(target, 0755) < 0 )
+ err(2, "%s", target);
+ execute((const char*[]) { "tix-collection", "/sysmerge", "create",
+ NULL }, "_e");
+ }
+ else
+ {
+ printf("Upgrading to %s using %s:\n", new_release.pretty_name, source);
+ target = "";
+ }
+
+ install_manifest("system", source, target);
+ install_ports(source, target);
+
+ if ( wait )
+ {
+ printf(" - Scheduling upgrade on next boot...\n");
+ execute((const char*[]) { "cp", "/boot/sortix.bin",
+ "/boot/sortix.bin.sysmerge.orig" }, "_e");
+ execute((const char*[]) { "cp", "/boot/sortix.initrd",
+ "/boot/sortix.initrd.sysmerge.orig" }, "_e");
+ execute((const char*[]) { "cp", "/sysmerge/boot/sortix.bin",
+ "/boot/sortix.bin" }, "_e");
+ execute((const char*[]) { "/sysmerge/sbin/update-initrd", NULL }, "_e");
+
+ printf("The system will be upgraded to %s on the next boot.\n",
+ new_release.pretty_name);
+ printf("Run %s --cancel to cancel the upgrade.\n", argv[0]);
+
+ return 0;
+ }
+
+ if ( !wait && access_or_die("/etc/fstab", F_OK) == 0 )
+ {
+ printf(" - Creating initrd...\n");
+ execute((const char*[]) { "update-initrd", NULL }, "_e");
+
+ if ( conf.grub )
+ {
+ // TODO: Figure out the root device.
+ //printf(" - Installing bootloader...\n");
+ //execute((const char*[]) { "grub-install", "/", NULL }, "_eqQ");
+ printf(" - Configuring bootloader...\n");
+ execute((const char*[]) { "update-grub", NULL }, "_eqQ");
+ }
+ else if ( access_or_die("/etc/grub.d/10_sortix", F_OK) == 0 )
+ {
+ printf(" - Creating bootloader fragment...\n");
+ execute((const char*[]) { "/etc/grub.d/10_sortix", NULL }, "_eq");
+ }
+ }
+
+ if ( booting )
+ {
+ unlink("/boot/sortix.bin.sysmerge.orig");
+ unlink("/boot/sortix.initrd.sysmerge.orig");
+ execute((const char*[]) { "rm", "-rf", "/sysmerge", NULL }, "");
+ }
+
+ printf("Successfully upgraded to %s.\n", new_release.pretty_name);
+
+ return 0;
+}
diff --git a/sysinstall/sysupgrade.8 b/sysinstall/sysupgrade.8
new file mode 100644
index 00000000..f6c50fb6
--- /dev/null
+++ b/sysinstall/sysupgrade.8
@@ -0,0 +1,25 @@
+.Dd $Mdocdate: January 5 2016 $
+.Dt SYSUPGRADE 8
+.Os
+.Sh NAME
+.Nm sysupgrade
+.Nd operating system upgrader
+.Sh SYNOPSIS
+.Nm sysupgrade
+.Sh DESCRIPTION
+.Nm
+is an interactive command line program that upgrades another installation to the
+current operating system. It asks basic questions, searches for existing
+installations, verifies the upgrade makes sense, and upgrades the target system
+according to
+.Xr upgrade.conf 5
+of the target system. The upgrade proceeds as described in
+.Xr upgrade 7 .
+.Pp
+.Nm
+must be run as root with stdin, stdout and stderr all pointing to the terminal.
+It is intended to be run from a live environment and not from an actual
+installation.
+.Sh SEE ALSO
+.Xr upgrade 7 ,
+.Xr sysinstall 8
diff --git a/sysinstall/sysupgrade.c b/sysinstall/sysupgrade.c
new file mode 100644
index 00000000..c552d1b9
--- /dev/null
+++ b/sysinstall/sysupgrade.c
@@ -0,0 +1,866 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2015, 2016.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ sysupgrade.c
+ Operating system upgrader.
+
+*******************************************************************************/
+
+#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 "conf.h"
+#include "devices.h"
+#include "execute.h"
+#include "fileops.h"
+#include "interactive.h"
+#include "manifest.h"
+#include "release.h"
+
+const char* prompt_man_section = "7";
+const char* prompt_man_page = "upgrade";
+
+struct installation
+{
+ struct blockdevice* bdev;
+ struct release release;
+};
+
+static struct installation* installations;
+static size_t installations_count;
+static size_t installations_length;
+
+static bool add_installation(struct blockdevice* bdev, struct release* release)
+{
+ if ( installations_count == installations_length )
+ {
+ size_t new_length = installations_length;
+ if ( !new_length )
+ new_length = 16;
+ struct installation* new_installations = (struct installation*)
+ reallocarray(NULL, new_length, sizeof(struct installation));
+ if ( !new_installations )
+ return false;
+ installations = new_installations;
+ installations_length = new_length;
+ }
+ struct installation* installation = &installations[installations_count++];
+ installation->bdev = bdev;
+ installation->release = *release;
+ return true;
+}
+
+static void search_installation_path(const char* mnt, struct blockdevice* bdev)
+{
+ char* release_errpath;
+ if ( asprintf(&release_errpath, "%s: /etc/sortix-release",
+ path_of_blockdevice(bdev)) < 0 )
+ {
+ warn("malloc");
+ return;
+ }
+ char* release_path;
+ if ( asprintf(&release_path, "%s/etc/sortix-release", mnt) < 0 )
+ {
+ warn("malloc");
+ free(release_errpath);
+ return;
+ }
+ struct release release;
+ if ( os_release_load(&release, release_path, release_errpath) &&
+ !add_installation(bdev, &release) )
+ release_free(&release);
+ free(release_path);
+ free(release_errpath);
+}
+
+// TODO: Switch to mountpoint_mount().
+static bool await_mount(const char* mnt, pid_t pid, struct stat* oldst,
+ const char* bdev_path, const char* driver)
+{
+ while ( true )
+ {
+ struct stat newst;
+ if ( stat(mnt, &newst) < 0 )
+ {
+ warn("%s", mnt);
+ return false;
+ }
+ if ( newst.st_dev != oldst->st_dev || newst.st_ino != oldst->st_ino )
+ break;
+ int code;
+ pid_t child = waitpid(pid, &code, WNOHANG);
+ if ( child < 0 )
+ {
+ err(2, "waitpid");
+ return false;
+ }
+ if ( child != 0 )
+ {
+ if ( WIFSIGNALED(code) )
+ warnx("%s: Mount failed: %s: %s", bdev_path, driver,
+ strsignal(WTERMSIG(code)));
+ else if ( !WIFEXITED(code) )
+ warnx("%s: Mount failed: %s: %s", bdev_path, driver,
+ "Unexpected unusual termination");
+ else if ( WEXITSTATUS(code) == 127 )
+ warnx("%s: Mount failed: %s: %s", bdev_path, driver,
+ "Filesystem driver is absent");
+#if 0
+ else if ( WEXITSTATUS(code) == 0 )
+ warnx("%s: Mount failed: %s: Unexpected successful exit",
+ bdev_path, driver);
+ else
+ warnx("%s: Mount failed: %s: Exited with status %i", bdev_path,
+ driver, WEXITSTATUS(code));
+#endif
+ return false;
+ }
+ struct timespec delay = timespec_make(0, 50L * 1000L * 1000L);
+ nanosleep(&delay, NULL);
+ }
+ return true;
+}
+
+static pid_t begin_mount(const char* mnt, struct blockdevice* bdev)
+{
+ struct filesystem* fs = bdev->fs;
+ if ( !fs )
+ return -1;
+ if ( !fs->driver )
+ return -1;
+ if ( fs->flags & FILESYSTEM_FLAG_FSCK_MUST && !fsck(fs) )
+ return -1;
+ struct stat fs_oldstat;
+ if ( stat(mnt, &fs_oldstat) < 0 )
+ {
+ warn("stat: %s", mnt);
+ return -1;
+ }
+ const char* bdev_path = path_of_blockdevice(fs->bdev);
+ pid_t fs_pid = fork();
+ if ( fs_pid < 0 )
+ {
+ warn("fork");
+ return -1;
+ }
+ if ( fs_pid == 0 )
+ {
+ setpgid(0, 0);
+ const char* argv[] =
+ {
+ fs->driver,
+ "--foreground",
+ bdev_path,
+ mnt,
+ NULL
+ };
+ execvp(argv[0], (char* const*) argv);
+ warn("%s", argv[0]);
+ _exit(127);
+ }
+ if ( !await_mount(mnt, fs_pid, &fs_oldstat, bdev_path, fs->driver) )
+ return false;
+ return fs_pid;
+}
+
+static void end_mount(const char* mnt, pid_t fs_pid)
+{
+ sched_yield();
+ unmount(mnt, 0);
+ waitpid(fs_pid, NULL, 0);
+}
+
+static void search_installation_bdev(const char* mnt, struct blockdevice* bdev)
+{
+ pid_t fs_pid = begin_mount(mnt, bdev);
+ if ( fs_pid < 0 )
+ return;
+ search_installation_path(mnt, bdev);
+ end_mount(mnt, fs_pid);
+}
+
+static void search_installations(const char* mnt)
+{
+ for ( size_t i = 0; i < installations_count; i++ )
+ release_free(&installations[i].release);
+ free(installations);
+ installations_count = 0;
+ installations_length = 0;
+
+ for ( size_t i = 0; i < hds_count; i++ )
+ {
+ struct harddisk* hd = hds[i];
+ if ( hd->bdev.pt )
+ {
+ for ( size_t n = 0; n < hd->bdev.pt->partitions_count; n++ )
+ {
+ struct partition* p = hd->bdev.pt->partitions[n];
+ search_installation_bdev(mnt, &p->bdev);
+ }
+ }
+ else
+ search_installation_bdev(mnt, &hd->bdev);
+ }
+}
+
+static void next_version(const struct release* current, struct release* new)
+{
+ // Next release of a development snapshot is the final release.
+ if ( current->version_dev )
+ {
+ new->version_major = current->version_major;
+ new->version_minor = current->version_minor;
+ new->version_dev = false;
+ return;
+ }
+
+ // Releases increment by 0.1.
+ new->version_major = current->version_major;
+ new->version_minor = current->version_minor + 1;
+ new->version_dev = false;
+
+ // Major increments instead of minor release 10.
+ if ( new->version_minor == 10 )
+ {
+ new->version_major++;
+ new->version_minor = 0;
+ }
+}
+
+static bool downgrading_version(const struct release* old,
+ const struct release* new)
+{
+ if ( new->version_major < old->version_major )
+ return true;
+ if ( new->version_major > old->version_major )
+ return false;
+ if ( new->version_major < old->version_major )
+ return true;
+ if ( new->version_major > old->version_major )
+ return false;
+ if ( new->version_dev && !old->version_dev )
+ return true;
+ return false;
+}
+
+static bool skipping_version(const struct release* old,
+ const struct release* new)
+{
+ // Not skipping a release if upgrading to older release.
+ if ( downgrading_version(old, new) )
+ return false;
+
+ // Not skipping a release if upgrading to same release.
+ if ( new->version_major == old->version_major &&
+ new->version_minor == old->version_minor &&
+ new->version_dev == old->version_dev )
+ return false;
+
+ // Not skipping a release if upgrading to the next release.
+ struct release next;
+ next_version(old, &next);
+ if ( new->version_major == next.version_major &&
+ new->version_minor == next.version_minor )
+ return false;
+
+ return true;
+}
+
+static void preserve_src(const char* where)
+{
+ if ( access_or_die(where, F_OK) < 0 )
+ return;
+ if ( access_or_die("oldsrc", F_OK) < 0 )
+ {
+ if ( mkdir("oldsrc", 0755) < 0 )
+ {
+ warn("oldsrc");
+ _exit(1);
+ }
+ }
+ time_t now = time(NULL);
+ struct tm tm;
+ localtime_r(&now, &tm);
+ char buf[64];
+ snprintf(buf, sizeof(buf), "oldsrc/%s-%i-%02i-%02i",
+ where, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
+ if ( access_or_die(buf, F_OK) == 0 )
+ {
+ snprintf(buf, sizeof(buf), "oldsrc/%s-%i-%02i-%02i-%02i-%02i-%02i",
+ where, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec);
+ if ( access_or_die(buf, F_OK) == 0 )
+ {
+ snprintf(buf, sizeof(buf), "oldsrc/%s.XXXXXX", where);
+ if ( !mkdtemp(buf) )
+ {
+ warnx("failed to find location to store old /%s", where);
+ _exit(1);
+ }
+ rmdir(buf);
+ }
+ }
+ printf(" - Moving /%s to /%s\n", where, buf);
+ if ( rename(where, buf) < 0 )
+ {
+ warn("rename: %s -> %s", where, buf);
+ _exit(1);
+ }
+}
+
+int main(void)
+{
+ shlvl();
+
+ if ( !isatty(0) )
+ errx(2, "fatal: stdin is not a terminal");
+ if ( !isatty(1) )
+ errx(2, "fatal: stdout is not a terminal");
+ if ( !isatty(2) )
+ errx(2, "fatal: stderr is not a terminal");
+
+ if ( getuid() != 0 )
+ errx(2, "You need to be root to install %s", BRAND_DISTRIBUTION_NAME);
+ if ( getgid() != 0 )
+ errx(2, "You need to be group root to install %s", BRAND_DISTRIBUTION_NAME);
+
+ struct utsname uts;
+ uname(&uts);
+
+ static char input[256];
+
+ textf("Hello and welcome to the " BRAND_DISTRIBUTION_NAME " " VERSIONSTR ""
+ " upgrader for %s.\n\n", uts.machine);
+
+ // '|' rather than '||' is to ensure side effects.
+ if ( missing_program("cut") |
+ missing_program("dash") |
+ missing_program("fsck.ext2") |
+ missing_program("grub-install") |
+ missing_program("man") |
+ missing_program("sed") |
+ missing_program("xargs") )
+ {
+ text("Warning: This system does not have the necessary third party "
+ "software installed to properly upgrade installations.\n");
+ while ( true )
+ {
+ prompt(input, sizeof(input), "Sure you want to proceed?", "no");
+ if ( strcasecmp(input, "no") == 0 )
+ return 0;
+ if ( strcasecmp(input, "yes") == 0 )
+ break;
+ }
+ text("\n");
+ }
+
+ text("This program will upgrade an existing installation to this "
+ "version. You can always escape to a shell by answering '!' to any "
+ "regular prompt. You can view the upgrade(7) manual page by answering "
+ "'!man'. Default answers are in []'s and can be selected by pressing "
+ "enter.\n\n");
+
+ const char* readies[] =
+ {
+ "Ready",
+ "Yes",
+ "Yeah",
+ "Yep",
+ "Let's go",
+ "Let's do this",
+ "Betcha",
+ "Sure am",
+ "You bet",
+ "This time it will listen to my music",
+ };
+ size_t num_readies = sizeof(readies) / sizeof(readies[0]);
+ const char* ready = readies[arc4random_uniform(num_readies)];
+ prompt(input, sizeof(input), "Ready?", ready);
+ text("\n");
+
+ while ( true )
+ {
+ // TODO: Detect the name of the current keyboard layout.
+ prompt(input, sizeof(input),
+ "Choose your keyboard layout ('?' or 'L' for list)", "default");
+ if ( !strcmp(input, "?") ||
+ !strcmp(input, "l") ||
+ !strcmp(input, "L") )
+ {
+ DIR* dir = opendir("/share/kblayout");
+ if ( !dir )
+ {
+ warn("%s", "/share/kblayout");
+ continue;
+ }
+ bool space = false;
+ struct dirent* entry;
+ while ( (entry = readdir(dir)) )
+ {
+ if ( entry->d_name[0] == '.' )
+ continue;
+ if ( space )
+ putchar(' ');
+ fputs(entry->d_name, stdout);
+ space = true;
+ }
+ closedir(dir);
+ if ( !space )
+ fputs("(No keyboard layouts available)", stdout);
+ putchar('\n');
+ continue;
+ }
+ if ( !strcmp(input, "default") )
+ break;
+ const char* argv[] = { "chkblayout", "--", input, NULL };
+ if ( execute(argv, "f") == 0 )
+ break;
+ }
+ text("\n");
+
+ struct dispmsg_get_driver_name dgdn = { 0 };
+ dgdn.msgid = DISPMSG_GET_DRIVER_NAME;
+ dgdn.device = 0;
+ dgdn.driver_index = 0;
+ dgdn.name.byte_size = 0;
+ dgdn.name.str = NULL;
+ if ( dispmsg_issue(&dgdn, sizeof(dgdn)) == 0 || errno != ENODEV )
+ {
+ while ( true )
+ {
+ prompt(input, sizeof(input),
+ "Select display resolution? (yes/no)", "yes");
+ if ( strcasecmp(input, "no") && strcasecmp(input, "yes") )
+ continue;
+ if ( strcasecmp(input, "no") == 0 )
+ break;
+ if ( execute((const char*[]) { "chvideomode", NULL }, "f") != 0 )
+ continue;
+ break;
+ }
+ }
+ text("\n");
+
+ struct release new_release;
+ if ( !os_release_load(&new_release, "/etc/sortix-release",
+ "/etc/sortix-release") )
+ exit(2);
+
+ char mnt[] = "/tmp/fs.XXXXXX";
+ if ( !mkdtemp(mnt) )
+ err(2, "mkdtemp: %s", "/tmp/fs.XXXXXX");
+
+ struct installation* target = NULL;
+ while ( true )
+ {
+ text("Searching for existing installations...\n");
+ scan_devices();
+ search_installations(mnt);
+ text("\n");
+
+ if ( installations_count == 0 )
+ {
+ while ( true)
+ {
+ prompt(input, sizeof(input), "No existing installations found, "
+ "run installer instead? (yes/no)", "yes");
+ if ( !strcasecmp(input, "no") || !strcasecmp(input, "yes") )
+ break;
+ }
+ if ( !strcasecmp(input, "yes") )
+ {
+ text("\n");
+ rmdir(mnt);
+ execlp("sysinstall", "sysinstall", (const char*) NULL);
+ warn("sysinstall");
+ if ( !mkdtemp(mnt) )
+ err(2, "mkdtemp: %s", "/tmp/fs.XXXXXX");
+ text("\n");
+ }
+ continue;
+ }
+
+ while ( true )
+ {
+ for ( size_t i = 0; i < installations_count; i++ )
+ {
+ struct installation* installation = &installations[i];
+ printf(" %-16s %s\n",
+ path_of_blockdevice(installation->bdev),
+ installation->release.pretty_name);
+ }
+ text("\n");
+
+ const char* def = NULL;
+ if ( installations_count == 1 )
+ def = path_of_blockdevice(installations[0].bdev);
+ prompt(input, sizeof(input), "Which installation to upgrade?", def);
+ target = NULL;
+ for ( size_t i = 0; i < installations_count; i++ )
+ {
+ struct installation* installation = &installations[i];
+ const char* path = path_of_blockdevice(installation->bdev);
+ if ( strcmp(input, path) != 0 )
+ continue;
+ target = installation;
+ }
+ if ( !target )
+ {
+ text("Answer was not one of the found devices.\n\n");
+ continue;
+ }
+ break;
+ }
+
+ break;
+ }
+ text("\n");
+
+ struct release* target_release = &target->release;
+
+ // TODO: Check if /etc/machine matches the current architecture.
+ // TODO: Some 1.0dev systems don't have /etc/machine, assume it matches if
+ // absent. Remove this compatibility after releasing Sortix 1.0.
+
+ if ( downgrading_version(target_release, &new_release) )
+ {
+ text("Warning: You are downgrading an existing installation to an "
+ "earlier release. This is not supported and there is no promise "
+ "this will work!\n\n");
+
+ while ( true)
+ {
+ prompt(input, sizeof(input),
+ "Downgrade to an earlier release?", "no");
+ if ( !strcasecmp(input, "no") || !strcasecmp(input, "yes") )
+ break;
+ }
+ if ( !strcasecmp(input, "no") )
+ errx(2, "Upgrade aborted due to version downgrade");
+ text("\n");
+ }
+ else if ( skipping_version(target_release, &new_release) )
+ {
+ text("Warning: You are not upgrading this installation to its next "
+ "release. You cannot skip releases. This is not supported and "
+ "there is no promise this will will work!\n\n");
+
+ while ( true )
+ {
+ prompt(input, sizeof(input),
+ "Skip across releases?", "no");
+ if ( !strcasecmp(input, "no") || !strcasecmp(input, "yes") )
+ break;
+ }
+ if ( !strcasecmp(input, "no") )
+ errx(2, "Upgrade aborted due to skipping releases");
+ text("\n");
+ }
+
+ if ( new_release.abi_major < target_release->abi_major ||
+ (target_release->abi_major == new_release.abi_major &&
+ new_release.abi_minor < target_release->abi_minor) )
+ {
+ text("Warning: You are downgrading an existing installation to an "
+ "release with an earlier ABI. This is not supported and there is "
+ "no promise this will work!\n\n");
+
+ while ( true)
+ {
+ prompt(input, sizeof(input),
+ "Downgrade to an earlier ABI?", "no");
+ if ( !strcasecmp(input, "no") || !strcasecmp(input, "yes") )
+ break;
+ }
+ if ( !strcasecmp(input, "no") )
+ errx(2, "Upgrade aborted due to ABI downgrade");
+ text("\n");
+ }
+
+ bool can_run_old_abi = target_release->abi_major == new_release.abi_major &&
+ target_release->abi_minor <= new_release.abi_minor;
+
+ struct blockdevice* bdev = target->bdev;
+ const char* bdev_path = path_of_blockdevice(bdev);
+
+ pid_t fs_pid = begin_mount(mnt, bdev);
+ if ( fs_pid < 0 )
+ err(2, "mounting %s at %s", bdev_path, mnt);
+
+ if ( chdir(mnt) < 0 )
+ err(2, "%s", mnt);
+
+ if ( access_or_die("sysmerge", F_OK) == 0 )
+ {
+ text("Warning: A sysmerge(8) upgrade is scheduled for the next boot. "
+ "You must cancel this to proceed.\n\n");
+ if ( !can_run_old_abi )
+ {
+ text("Error: Can't pending upgrade due to ABI change.\n");
+ errx(2, "Upgrade aborted due to pending sysmerge(8) upgrade");
+ }
+
+ while ( true )
+ {
+ prompt(input, sizeof(input),
+ "Cancel pending sysmerge upgrade?", "yes");
+ if ( !strcasecmp(input, "no") || !strcasecmp(input, "yes") )
+ break;
+ }
+ if ( !strcasecmp(input, "no") )
+ errx(2, "Upgrade aborted due to pending sysmerge(8) upgrade");
+ text("\n");
+ execute((const char*[]) { "chroot", "-d", "sysmerge", "--cancel", NULL }, "e");
+ }
+
+ // TODO: Remove after releasing Sortix 1.0.
+ if ( target_release->version_major == 1 &&
+ target_release->version_minor == 0 &&
+ target_release->version_dev &&
+ access_or_die("etc/upgrade.conf", F_OK) < 0 )
+ {
+ text("1.0dev compatibility: Creating /etc/upgrade.conf\n");
+ FILE* fp = fopen("etc/upgrade.conf", "w");
+ if ( !fp )
+ err(2, "etc/upgrade.conf");
+ if ( access_or_die("src", F_OK) == 0 )
+ fprintf(fp, "src = yes\n");
+ if ( access_or_die("boot/grub/grub.cfg", F_OK) == 0 )
+ fprintf(fp, "grub = yes\n");
+ if ( ferror(fp) || fflush(fp) == EOF )
+ err(2, "etc/upgrade.conf");
+ if ( fclose(fp) == EOF )
+ err(2, "etc/upgrade.conf");
+ text("\n");
+ }
+
+ struct conf conf;
+ while ( true )
+ {
+ load_upgrade_conf(&conf, "etc/upgrade.conf");
+
+ textf("We are now ready to upgrade to %s %s. Take a moment to verify "
+ "everything is sane.\n", BRAND_DISTRIBUTION_NAME, VERSIONSTR);
+ text("\n");
+ char abibuf[16];
+ printf(" %-16s system architecture\n", uts.machine);
+ printf(" %-16s root filesystem\n", bdev_path);
+ printf(" %-16s old version\n", target_release->pretty_name);
+ printf(" %-16s new version\n", new_release.pretty_name);
+ snprintf(abibuf, sizeof(abibuf), "%lu.%lu",
+ target_release->abi_major, target_release->abi_minor);
+ printf(" %-16s old ABI\n", abibuf);
+ snprintf(abibuf, sizeof(abibuf), "%lu.%lu",
+ new_release.abi_major, new_release.abi_minor);
+ printf(" %-16s new ABI\n", abibuf);
+ if ( conf.system )
+ printf(" %-16s will be updated\n", "system");
+ else
+ printf(" %-16s will not be updated\n", "system");
+ if ( conf.ports )
+ printf(" %-16s will be updated\n", "ports");
+ else
+ printf(" %-16s will not be updated\n", "ports");
+ if ( has_manifest("src") )
+ {
+ if ( conf.newsrc )
+ printf(" %-16s new source code\n", "/newsrc");
+ else if ( conf.src )
+ printf(" %-16s will be updated\n", "/src");
+ else
+ printf(" %-16s will not be updated\n", "/src");
+ }
+ else
+ printf(" %-16s will not be updated\n", "/src");
+ text("\n");
+
+ prompt(input, sizeof(input),
+ "Upgrade? (yes/no)", "yes");
+ if ( strcasecmp(input, "yes") != 0 )
+ {
+ text("Everything isn't sane? Answer '!' to get a shell or type ^C "
+ "to abort the upgrade.\n");
+ continue;
+ }
+ break;
+ }
+ text("\n");
+
+ // TODO: Switch to local time zone of the existing system?
+
+ text("Upgrading to " BRAND_DISTRIBUTION_NAME " " VERSIONSTR " now:\n");
+
+ pid_t upgrade_pid = fork();
+ if ( upgrade_pid < 0 )
+ err(2, "fork");
+ if ( upgrade_pid == 0 )
+ {
+ umask(0022);
+ // TODO: Use an upgrade manifest system that notices files that are now
+ // untracked or moved from one manifest to another.
+ if ( conf.system )
+ install_manifest("system", "", ".");
+ if ( has_manifest("src") )
+ {
+ if ( conf.newsrc )
+ {
+ bool has_src = access_or_die("src", F_OK) == 0;
+ if ( has_src )
+ {
+ preserve_src("newsrc");
+ if ( rename("src", "src.tmp") < 0 )
+ {
+ warn("rename: /src -> /src.tmp");
+ _exit(1);
+ }
+ }
+ install_manifest("src", "", ".");
+ if ( has_src )
+ {
+ if ( rename("src", "newsrc") < 0 )
+ {
+ warn("rename: /src -> /newsrc");
+ _exit(1);
+ }
+ if ( rename("src.tmp", "src") < 0 )
+ {
+ warn("rename: /src.tmp -> /src");
+ _exit(1);
+ }
+ }
+ }
+ else if ( conf.src )
+ {
+ preserve_src("src");
+ install_manifest("src", "", ".");
+ }
+ }
+ if ( conf.ports )
+ install_ports("", ".");
+ if ( conf.system )
+ {
+ printf(" - Creating initrd...\n");
+ execute((const char*[]) { "update-initrd", "--sysroot", mnt, NULL }, "_e");
+ }
+ if ( (conf.ports || (conf.system && can_run_old_abi)) && conf.grub )
+ {
+ printf(" - Installing bootloader...\n");
+ execute((const char*[]) { "chroot", "-d", ".", "grub-install",
+ device_path_of_blockdevice(bdev), NULL },
+ "_eqQ");
+ printf(" - Configuring bootloader...\n");
+ execute((const char*[]) { "chroot", "-d", ".", "update-grub", NULL },
+ "_eqQ");
+ }
+ else if ( conf.system &&
+ access_or_die("/etc/grub.d/10_sortix", F_OK) == 0 )
+ {
+ // Help dual booters by making /etc/grub.d/10_sortix.cache.
+ printf(" - Creating bootloader fragment...\n");
+ execute((const char*[]) { "chroot", "-d", ".",
+ "/etc/grub.d/10_sortix", NULL }, "_eq");
+ }
+ printf(" - Finishing upgrade...\n");
+ _exit(0);
+ }
+ int upgrade_code;
+ waitpid(upgrade_pid, &upgrade_code, 0);
+ if ( WIFEXITED(upgrade_code) && WEXITSTATUS(upgrade_code) == 0 )
+ {
+ }
+ else if ( WIFEXITED(upgrade_code) )
+ errx(2, "upgrade failed with exit status %i", WEXITSTATUS(upgrade_code));
+ else if ( WIFSIGNALED(upgrade_code) )
+ errx(2, "upgrade failed: %s", strsignal(WTERMSIG(upgrade_code)));
+ else
+ errx(2, "upgrade failed: unknown waitpid code %i", upgrade_code);
+ text("\n");
+
+ if ( chdir("/") < 0 )
+ err(2, "%s", "/");
+
+ end_mount(mnt, fs_pid);
+
+ if ( conf.system )
+ textf("The %s installation has now been upgraded to %s.\n\n",
+ bdev_path, new_release.pretty_name);
+ else if ( conf.newsrc )
+ textf("The %s installation now contains the new source code in /newsrc. "
+ "You need to build it as described in development(7).\n\n",
+ bdev_path);
+ else if ( conf.src )
+ textf("The %s installation now contains the new source code in /src. "
+ "You need to build it as described in development(7).\n\n",
+ bdev_path);
+ else
+ textf("The %s installation has been upgraded to %s as requested.\n\n",
+ bdev_path, new_release.pretty_name);
+
+ if ( target_release->abi_major < new_release.abi_major )
+ {
+ text("Note: The system has been upgraded across a major ABI change. "
+ "Locally compiled programs must be recompiled as they no longer "
+ "can be expected to work.\n\n");
+ }
+ else if ( target_release->abi_major == new_release.abi_major &&
+ target_release->abi_minor < new_release.abi_minor )
+ {
+ text("Note: The system has been upgraded across a minor ABI change.\n\n");
+ }
+ else if ( new_release.abi_major < target_release->abi_major ||
+ (target_release->abi_major == new_release.abi_major &&
+ new_release.abi_minor < target_release->abi_minor) )
+ {
+ text("Note: The system has been downgraded to an earlier ABI. "
+ "Locally compiled programs must be recompiled as they no longer "
+ "can be expected to work.\n\n");
+ }
+
+ while ( true )
+ {
+ prompt(input, sizeof(input), "What now? (poweroff/reboot)", "reboot");
+ if ( !strcasecmp(input, "poweroff") )
+ return 0;
+ if ( !strcasecmp(input, "reboot") )
+ return 1;
+ }
+}
diff --git a/update-initrd/update-initrd b/update-initrd/update-initrd
index 71ba3ad8..dc32f4bf 100755
--- a/update-initrd/update-initrd
+++ b/update-initrd/update-initrd
@@ -78,17 +78,27 @@ if [ ! -e "$sysroot/etc/fstab" ]; then
echo "$0: $sysroot/etc/fstab: Need a filesystem table to make an initrd" >&2
exit 1
fi
+sysmerge=false
+exec_prefix="$sysroot"
+if [ -d "$sysroot/sysmerge" ]; then
+ sysmerge=true
+ exec_prefix="$sysroot/sysmerge"
+fi
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT HUP INT QUIT TERM
mkdir "$tmp/bin"
mkdir "$tmp/sbin"
-cp "$sysroot/sbin/init" "$tmp/sbin"
-cp "$sysroot/sbin/extfs" "$tmp/sbin"
-test -f "$sysroot/sbin/fsck.ext2" &&
-cp "$sysroot/sbin/fsck.ext2" "$tmp/sbin"
+cp "$exec_prefix/sbin/init" "$tmp/sbin"
+cp "$exec_prefix/sbin/extfs" "$tmp/sbin"
+test -f "$exec_prefix/sbin/fsck.ext2" &&
+cp "$exec_prefix/sbin/fsck.ext2" "$tmp/sbin"
mkdir "$tmp/etc"
cp "$sysroot/etc/fstab" "$tmp/etc/fstab"
mkdir "$tmp/etc/init"
-echo chain > "$tmp/etc/init/target"
+if $sysmerge; then
+ echo chain-merge > "$tmp/etc/init/target"
+else
+ echo chain > "$tmp/etc/init/target"
+fi
mkdir -p "$sysroot/boot"
mkinitrd --format=sortix-initrd-2 "$tmp" -o "$sysroot/boot/sortix.initrd" > /dev/null