From f52fb3202c477cb313bc4fa7e12a7486e5cf18a8 Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Thu, 18 Feb 2016 22:16:02 +0100 Subject: [PATCH] Add sysinstall(8), sysmerge(8), and sysupgrade(8). --- Makefile | 35 +- build-aux/iso-grub-cfg.sh | 2 + doc/welcome | 4 + init/init.8 | 5 + init/init.c++ | 53 +- share/man/man5/upgrade.conf.5 | 105 ++++ share/man/man7/development.7 | 37 +- share/man/man7/hier.7 | 6 +- share/man/man7/installation.7 | 338 ++++++++++++ share/man/man7/upgrade.7 | 120 +++++ share/man/man7/user-guide.7 | 13 +- sysinstall/.gitignore | 4 + sysinstall/Makefile | 75 +++ sysinstall/conf.c | 115 ++++ sysinstall/conf.h | 37 ++ sysinstall/devices.c | 416 ++++++++++++++ sysinstall/devices.h | 58 ++ sysinstall/execute.c | 131 +++++ sysinstall/execute.h | 28 + sysinstall/fileops.c | 85 +++ sysinstall/fileops.h | 30 ++ sysinstall/interactive.c | 222 ++++++++ sysinstall/interactive.h | 42 ++ sysinstall/manifest.c | 411 ++++++++++++++ sysinstall/manifest.h | 33 ++ sysinstall/release.c | 206 +++++++ sysinstall/release.h | 41 ++ sysinstall/sysinstall.8 | 23 + sysinstall/sysinstall.c | 985 ++++++++++++++++++++++++++++++++++ sysinstall/sysmerge.8 | 123 +++++ sysinstall/sysmerge.c | 260 +++++++++ sysinstall/sysupgrade.8 | 25 + sysinstall/sysupgrade.c | 866 ++++++++++++++++++++++++++++++ update-initrd/update-initrd | 20 +- 34 files changed, 4894 insertions(+), 60 deletions(-) create mode 100644 share/man/man5/upgrade.conf.5 create mode 100644 share/man/man7/installation.7 create mode 100644 share/man/man7/upgrade.7 create mode 100644 sysinstall/.gitignore create mode 100644 sysinstall/Makefile create mode 100644 sysinstall/conf.c create mode 100644 sysinstall/conf.h create mode 100644 sysinstall/devices.c create mode 100644 sysinstall/devices.h create mode 100644 sysinstall/execute.c create mode 100644 sysinstall/execute.h create mode 100644 sysinstall/fileops.c create mode 100644 sysinstall/fileops.h create mode 100644 sysinstall/interactive.c create mode 100644 sysinstall/interactive.h create mode 100644 sysinstall/manifest.c create mode 100644 sysinstall/manifest.h create mode 100644 sysinstall/release.c create mode 100644 sysinstall/release.h create mode 100644 sysinstall/sysinstall.8 create mode 100644 sysinstall/sysinstall.c create mode 100644 sysinstall/sysmerge.8 create mode 100644 sysinstall/sysmerge.c create mode 100644 sysinstall/sysupgrade.8 create mode 100644 sysinstall/sysupgrade.c 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