Add sysinstall(8), sysmerge(8), and sysupgrade(8).
This commit is contained in:
parent
8af81a1864
commit
f52fb3202c
35
Makefile
35
Makefile
|
@ -24,6 +24,7 @@ mkinitrd \
|
|||
regress \
|
||||
sf \
|
||||
sh \
|
||||
sysinstall \
|
||||
tix \
|
||||
trianglix \
|
||||
update-initrd \
|
||||
|
@ -67,37 +68,13 @@ SYSTEM_INITRD:=$(SORTIX_BUILDS_DIR)/$(BUILD_NAME).system.initrd
|
|||
.PHONY: all
|
||||
all: sysroot
|
||||
|
||||
.PHONY: install
|
||||
install: sysroot
|
||||
@if test -z '$(INSTALL_ROOTFS)' ; then \
|
||||
echo "error: You must set INSTALL_ROOTFS to where you want Sortix installed" >&2; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if test -d '$(INSTALL_ROOTFS)' && test -z '$(STUPIDLY_FORCE_SORTIX_INSTALL_OVERWRITE)'; then \
|
||||
for ENTRY in $$(ls -A "$(SYSROOT)"); do \
|
||||
if test -e "$(INSTALL_ROOTFS)/$$ENTRY"; then \
|
||||
echo "Error: Refusing to corrupt the existing installation at $(INSTALL_ROOTFS)" >&2; \
|
||||
echo "Use sysmerge to update an existence installation." >&2; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
done; \
|
||||
fi
|
||||
cp -RTv "$(SYSROOT)" "$(INSTALL_ROOTFS)"
|
||||
|
||||
.PHONY: sysmerge
|
||||
sysmerge: sysroot
|
||||
ifeq ($(BUILD_IS_SORTIX),0)
|
||||
if test -z '$(DESTDIR)' || test 'x$(DESTDIR)' = 'x/'; then \
|
||||
echo "error: Refusing to corrupt the local operating system by sysmerging it with Sortix" >&2 \
|
||||
exit 1 \
|
||||
fi
|
||||
endif
|
||||
for ENTRY in $$(ls -A "$(SYSROOT)" | grep -Ev '^(dev|etc|home|mnt|root|src|tix|tmp|var)$$'); do \
|
||||
cp -RTv "$(SYSROOT)/$$ENTRY" "$(DESTDIR)/$$ENTRY" || exit $$?; \
|
||||
done
|
||||
cp -TPv "$(SYSROOT)/etc/machine" "$(DESTDIR)/etc/machine"
|
||||
cp -TPv "$(SYSROOT)/etc/os-release" "$(DESTDIR)/etc/os-release"
|
||||
cp -TPv "$(SYSROOT)/etc/sortix-release" "$(DESTDIR)/etc/sortix-release"
|
||||
"$(SYSROOT)/sbin/sysmerge" "$(SYSROOT)"
|
||||
|
||||
.PHONY: sysmerge-wait
|
||||
sysmerge-wait: sysroot
|
||||
"$(SYSROOT)/sbin/sysmerge" --wait "$(SYSROOT)"
|
||||
|
||||
.PHONY: clean-build-tools
|
||||
clean-build-tools:
|
||||
|
|
|
@ -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"'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
sysinstall
|
||||
sysmerge
|
||||
sysupgrade
|
||||
*.o
|
|
@ -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)
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue