Add sysinstall(8), sysmerge(8), and sysupgrade(8).

This commit is contained in:
Jonas 'Sortie' Termansen 2016-02-18 22:16:02 +01:00
parent 8af81a1864
commit f52fb3202c
34 changed files with 4894 additions and 60 deletions

View File

@ -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:

View File

@ -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"'

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

120
share/man/man7/upgrade.7 Normal file
View File

@ -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

View File

@ -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

4
sysinstall/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
sysinstall
sysmerge
sysupgrade
*.o

75
sysinstall/Makefile Normal file
View File

@ -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)

115
sysinstall/conf.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
conf.c
Utility functions to handle upgrade.conf(5).
*******************************************************************************/
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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);
}

37
sysinstall/conf.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
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

416
sysinstall/devices.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
devices.c
Utility functions to handle devices, partitions, and filesystems.
*******************************************************************************/
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fstab.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <timespec.h>
#include <unistd.h>
#include <mount/blockdevice.h>
#include <mount/devices.h>
#include <mount/filesystem.h>
#include <mount/harddisk.h>
#include <mount/partition.h>
#include <mount/uuid.h>
#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;
}

58
sysinstall/devices.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
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

131
sysinstall/execute.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
execute.h
Template for common execvp use cases.
*******************************************************************************/
#include <sys/types.h>
#include <sys/wait.h>
#include <err.h>
#include <fcntl.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#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;
}

28
sysinstall/execute.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
execute.h
Template for common execvp use cases.
*******************************************************************************/
#ifndef EXECUTE_H
#define EXECUTE_H
int execute(const char* const* argv, const char* flags, ...);
#endif

85
sysinstall/fileops.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
fileops.c
File operation utility functions.
*******************************************************************************/
#include <sys/types.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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);
}

30
sysinstall/fileops.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
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

222
sysinstall/interactive.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
interactive.c
Interactive utility functions.
*******************************************************************************/
#include <sys/termmode.h>
#include <sys/types.h>
#include <ctype.h>
#include <err.h>
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <wchar.h>
#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;
}

42
sysinstall/interactive.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
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

411
sysinstall/manifest.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
manifest.c
Manifest handling functions.
*******************************************************************************/
#include <sys/types.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <ioleast.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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);
}

33
sysinstall/manifest.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
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

206
sysinstall/release.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
release.c
Utility functions to handle release information.
*******************************************************************************/
#include <err.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

41
sysinstall/release.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
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

23
sysinstall/sysinstall.8 Normal file
View File

@ -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

985
sysinstall/sysinstall.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
sysinstall.c
Operating system installer.
*******************************************************************************/
#include <sys/display.h>
#include <sys/kernelinfo.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <assert.h>
#include <brand.h>
#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <fstab.h>
#include <limits.h>
#include <pwd.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
// Sortix libc doesn't have its own proper <limits.h> at this time.
#if defined(__sortix__)
#include <sortix/limits.h>
#endif
#include <mount/blockdevice.h>
#include <mount/devices.h>
#include <mount/filesystem.h>
#include <mount/harddisk.h>
#include <mount/partition.h>
#include <mount/uuid.h>
#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"));
}
}
}

123
sysinstall/sysmerge.8 Normal file
View File

@ -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

260
sysinstall/sysmerge.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
sysmerge.c
Upgrade current operating system from a sysroot.
*******************************************************************************/
#include <sys/types.h>
#include <err.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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 <http://gnu.org/licenses/gpl.html>.\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;
}

25
sysinstall/sysupgrade.8 Normal file
View File

@ -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

866
sysinstall/sysupgrade.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
sysupgrade.c
Operating system upgrader.
*******************************************************************************/
#include <sys/display.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <brand.h>
#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <fstab.h>
#include <sched.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <timespec.h>
#include <unistd.h>
#include <mount/blockdevice.h>
#include <mount/filesystem.h>
#include <mount/harddisk.h>
#include <mount/partition.h>
#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;
}
}

View File

@ -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