sortix-mirror/sysinstall/sysupgrade.c

996 lines
28 KiB
C

/*
* Copyright (c) 2015, 2016, 2020, 2021 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* sysupgrade.c
* Operating system upgrader.
*/
#include <sys/display.h>
#include <sys/ioctl.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 <signal.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
#include <mount/blockdevice.h>
#include <mount/filesystem.h>
#include <mount/harddisk.h>
#include <mount/partition.h>
#include "autoconf.h"
#include "conf.h"
#include "devices.h"
#include "execute.h"
#include "fileops.h"
#include "hooks.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;
struct mountpoint* mountpoints;
size_t mountpoints_used;
char* machine;
};
static struct installation* installations;
static size_t installations_count;
static size_t installations_length;
static pid_t main_pid;
static struct mountpoint* mountpoints;
static size_t mountpoints_used;
static bool fs_made = false;
static char fs[] = "/tmp/fs.XXXXXX";
static int exit_gui_code = -1;
static bool add_installation(struct blockdevice* bdev,
struct release* release,
struct mountpoint* mountpoints,
size_t mountpoints_used,
char* machine)
{
if ( installations_count == installations_length )
{
size_t length = installations_length;
if ( !length )
length = 8;
struct installation* new_installations = (struct installation*)
reallocarray(NULL, length, 2 * sizeof(struct installation));
if ( !new_installations )
return false;
installations = new_installations;
installations_length = 2 * length;
}
struct installation* installation = &installations[installations_count++];
installation->bdev = bdev;
installation->release = *release;
installation->mountpoints = mountpoints;
installation->mountpoints_used = mountpoints_used;
installation->machine = machine;
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;
bool status = os_release_load(&release, release_path, release_errpath);
free(release_path);
free(release_errpath);
if ( !status )
return;
char* fstab_path;
if ( asprintf(&fstab_path, "%s/etc/fstab", mnt) < 0 )
{
warn("malloc");
release_free(&release);
return;
}
struct mountpoint* mountpoints;
size_t mountpoints_used;
status = load_mountpoints(fstab_path, &mountpoints, &mountpoints_used);
free(fstab_path);
if ( !status )
{
warn("%s: %s", path_of_blockdevice(bdev), "/etc/fstab");
release_free(&release);
return;
}
char* machine_path;
if ( asprintf(&machine_path, "%s/etc/machine", mnt) < 0 )
{
warn("%s: malloc", path_of_blockdevice(bdev));
free_mountpoints(mountpoints, mountpoints_used);
release_free(&release);
return;
}
char* machine = read_string_file(machine_path);
free(machine_path);
if ( !machine )
{
warn("%s/etc/machine", path_of_blockdevice(bdev));
free_mountpoints(mountpoints, mountpoints_used);
release_free(&release);
return;
}
if ( !add_installation(bdev, &release, mountpoints, mountpoints_used,
machine) )
{
free(machine);
free_mountpoints(mountpoints, mountpoints_used);
release_free(&release);
return;
}
}
static void search_installation_bdev(const char* mnt, struct blockdevice* bdev)
{
if ( !bdev->fs )
return;
if ( !bdev->fs->driver )
return;
struct mountpoint mp = { 0 };
mp.absolute = (char*) mnt;
mp.fs = bdev->fs;
mp.entry.fs_file = (char*) mnt;
if ( !mountpoint_mount(&mp) )
return;
search_installation_path(mnt, bdev);
mountpoint_unmount(&mp);
}
static void search_installations(const char* mnt)
{
for ( size_t i = 0; i < installations_count; i++ )
{
struct installation* inst = &installations[i];
free_mountpoints(inst->mountpoints, inst->mountpoints_used);
release_free(&inst->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_minor < old->version_minor )
return true;
if ( new->version_minor > old->version_minor )
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(2);
}
}
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(2);
}
rmdir(buf);
}
}
printf(" - Moving /%s to /%s\n", where, buf);
if ( rename(where, buf) < 0 )
{
warn("rename: %s -> %s", where, buf);
_exit(2);
}
}
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 ( 0 <= exit_gui_code )
gui_shutdown(exit_gui_code);
}
void exit_gui(int code)
{
exit_gui_code = code;
exit(code);
}
static void cancel_on_sigint(int signum)
{
(void) signum;
errx(2, "fatal: Upgrade canceled");
}
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");
autoconf_load("/etc/autoupgrade.conf");
char* accepts_defaults = autoconf_eval("accept_defaults");
bool non_interactive = accepts_defaults &&
!strcasecmp(accepts_defaults, "yes");
free(accepts_defaults);
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);
if ( autoconf_has("ready") ||
autoconf_has("confirm_upgrade") )
{
int countdown = 10;
if ( autoconf_has("countdown") )
{
char* string = autoconf_eval("countdown");
countdown = atoi(string);
free(string);
}
sigset_t old_set;
sigset_t new_set;
sigemptyset(&new_set);
sigaddset(&new_set, SIGINT);
sigprocmask(SIG_BLOCK, &new_set, &old_set);
struct sigaction old_sa;
struct sigaction new_sa = { 0 };
new_sa.sa_handler = cancel_on_sigint;
sigaction(SIGINT, &new_sa, &old_sa);
for ( ; 0 < countdown; countdown-- )
{
textf("Automatically upgrading to " BRAND_DISTRIBUTION_NAME " "
VERSIONSTR " in %i %s... (Control-C to cancel)\n", countdown,
countdown != 1 ? "seconds" : "second");
sigprocmask(SIG_SETMASK, &old_set, NULL);
sleep(1);
sigprocmask(SIG_BLOCK, &new_set, &old_set);
}
textf("Automatically upgrading " BRAND_DISTRIBUTION_NAME " "
VERSIONSTR "...\n");
text("\n");
sigaction(SIGINT, &old_sa, NULL);
sigprocmask(SIG_SETMASK, &old_set, NULL);
}
// '|' 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), "ignore_missing_programs",
"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)];
if ( autoconf_has("confirm_upgrade") )
text("Warning: This upgrader will automatically upgrade an operating "
"system!\n");
prompt(input, sizeof(input), "ready", "Ready?", ready);
text("\n");
bool kblayout_setable = 0 <= tcgetblob(0, "kblayout", NULL, 0) ||
getenv("DISPLAY_SOCKET");
while ( kblayout_setable )
{
// TODO: Detect the name of the current keyboard layout.
prompt(input, sizeof(input), "kblayout",
"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 ( kblayout_setable )
text("\n");
struct dispmsg_crtc_mode mode;
if ( get_video_mode(&mode) )
{
bool good = (mode.control & DISPMSG_CONTROL_VALID) &&
(mode.control & DISPMSG_CONTROL_GOOD_DEFAULT);
if ( mode.control & DISPMSG_CONTROL_VM_AUTO_SCALE )
{
text("The display resolution will automatically change to "
"match the size of the virtual machine window.\n\n");
good = true;
}
const char* def = non_interactive || good ? "no" : "yes";
while ( true )
{
prompt(input, sizeof(input), "videomode",
"Select display resolution? "
"(yes/no/WIDTHxHEIGHTxBPP)", def);
unsigned int xres, yres, bpp;
bool set = sscanf(input, "%ux%ux%u", &xres, &yres, &bpp) == 3;
if ( !set && strcasecmp(input, "no") && strcasecmp(input, "yes") )
continue;
if ( strcasecmp(input, "no") == 0 )
break;
const char* r = set ? input : NULL;
if ( execute((const char*[]) { "chvideomode", r, NULL }, "f") != 0 )
continue;
break;
}
text("\n");
}
struct release new_release;
if ( !os_release_load(&new_release, "/etc/sortix-release",
"/etc/sortix-release") )
{
if ( errno == ENOENT )
warn("/etc/sortix-release");
exit(2);
}
if ( !mkdtemp(fs) )
err(2, "mkdtemp: %s", "/tmp/fs.XXXXXX");
fs_made = true;
// Export for the convenience of users escaping to a shell.
setenv("SYSINSTALL_TARGET", fs, 1);
struct installation* target = NULL;
while ( true )
{
text("Searching for existing installations...\n");
scan_devices();
search_installations(fs);
text("\n");
if ( installations_count == 0 )
{
while ( true )
{
prompt(input, sizeof(input), "run_installer_instead",
"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(fs);
execlp("sysinstall", "sysinstall", (const char*) NULL);
err(2, "sysinstall");
}
errx(2, "Upgrade aborted since no installations were found");
}
while ( true )
{
for ( size_t i = 0; i < installations_count; i++ )
{
struct installation* installation = &installations[i];
printf(" %-16s %s (%s)\n",
path_of_blockdevice(installation->bdev),
installation->release.pretty_name,
installation->machine);
}
text("\n");
const char* def = NULL;
if ( installations_count == 1 )
def = path_of_blockdevice(installations[0].bdev);
prompt(input, sizeof(input), "which_installaton",
"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;
char* source_machine = read_string_file("/etc/machine");
if ( !source_machine )
err(2, "/etc/machine");
if ( strcmp(target->machine, source_machine) != 0 )
{
textf("Warning: You are changing an existing installation to another "
"architecture! (%s -> %s) This is not supported and there is no "
"promise this will work!\n", target->machine, source_machine);
while ( true )
{
prompt(input, sizeof(input), "switch_architecture",
"Change the existing installation to another architecture?",
"no");
if ( !strcasecmp(input, "no") || !strcasecmp(input, "yes") )
break;
}
if ( !strcasecmp(input, "no") )
errx(2, "upgrade aborted because of architecture mismatch");
text("\n");
}
free(source_machine);
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_release",
"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_release",
"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 ( abi_compare(new_release.abi_major, new_release.abi_minor,
target_release->abi_major, target_release->abi_minor) < 0 )
{
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_abi",
"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 =
abi_compatible(target_release->abi_major, target_release->abi_minor,
new_release.abi_major, new_release.abi_minor);
mountpoints = target->mountpoints;
mountpoints_used = target->mountpoints_used;
struct blockdevice* bdev = target->bdev;
struct blockdevice* bootloader_bdev = target->bdev;
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)) )
errx(2, "fstab: %s: Found no mountable filesystem matching `%s'",
mnt->entry.fs_file, spec);
if ( !mnt->fs->driver )
errx(2, "fstab: %s: %s: Don't know how to mount this %s filesystem",
mnt->entry.fs_file,
path_of_blockdevice(mnt->fs->bdev),
mnt->fs->fstype_name);
}
for ( size_t i = 0; i < mountpoints_used; i++ )
{
struct mountpoint* mnt = &mountpoints[i];
if ( !strcmp(mnt->entry.fs_file, "/boot") )
bootloader_bdev = mnt->fs->bdev;
char* absolute;
if ( asprintf(&absolute, "%s%s", fs, mnt->absolute) < 0 )
err(2, "asprintf");
free(mnt->absolute);
mnt->absolute = absolute;
if ( !mountpoint_mount(mnt) )
exit(2);
}
const char* bdev_path = path_of_blockdevice(bdev);
const char* bootloader_dev_path = device_path_of_blockdevice(bootloader_bdev);
if ( chdir(fs) < 0 )
err(2, "chdir: %s", fs);
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 cancel 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",
"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");
}
bool do_upgrade_bootloader;
struct conf conf;
conf_init(&conf);
while ( true )
{
conf_free(&conf);
if ( !conf_load(&conf, "etc/upgrade.conf") && errno != ENOENT )
err(2, "etc/upgrade.conf");
do_upgrade_bootloader =
conf.grub && (conf.ports || (conf.system && can_run_old_abi));
textf("We are now ready to upgrade to %s %s. Take a moment to verify "
"everything is in order.\n", BRAND_DISTRIBUTION_NAME, VERSIONSTR);
text("\n");
char abibuf[16];
printf(" %-16s system architecture\n", uts.machine);
printf(" %-16s root filesystem\n", bdev_path);
if ( do_upgrade_bootloader )
printf(" %-16s bootloader installation target\n", bootloader_dev_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");
if ( do_upgrade_bootloader )
printf(" %-16s will be updated\n", "bootloader");
else
printf(" %-16s will not be updated\n", "bootloader");
text("\n");
while ( true )
{
promptx(input, sizeof(input), "confirm_upgrade",
"Upgrade? (yes/no/exit/poweroff/reboot/halt)", "yes", true);
if ( !strcasecmp(input, "yes") )
break;
else if ( !strcasecmp(input, "no") )
{
text("Answer '!' to get a shell. Type !man to view the "
"upgrade(7) manual page. You can edit the upgrade.conf(5) "
"configuration file of the target system to change which "
"upgrade operations are performed.\n");
text("Alternatively, you can answer 'poweroff', 'reboot', or "
"'halt' or cancel the upgrade.\n");
continue;
}
else if ( !strcasecmp(input, "exit") )
exit(0);
else if ( !strcasecmp(input, "poweroff") )
exit_gui(0);
else if ( !strcasecmp(input, "reboot") )
exit_gui(1);
else if ( !strcasecmp(input, "halt") )
exit_gui(2);
else if ( !strcasecmp(input, "!") )
break;
else
continue;
}
if ( !strcasecmp(input, "yes") )
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);
if ( conf.system )
upgrade_prepare(target_release, &new_release, "", ".");
install_manifests_detect("", ".", conf.system, conf.ports, conf.ports);
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(2);
}
}
install_manifest("src", "", ".", (const char*[]){}, 0);
if ( has_src )
{
if ( rename("src", "newsrc") < 0 )
{
warn("rename: /src -> /newsrc");
_exit(2);
}
if ( rename("src.tmp", "src") < 0 )
{
warn("rename: /src.tmp -> /src");
_exit(2);
}
}
}
else if ( conf.src )
{
preserve_src("src");
install_manifest("src", "", ".", (const char*[]){}, 0);
}
}
if ( conf.system )
upgrade_finalize(target_release, &new_release, "", ".");
if ( conf.system )
{
printf(" - Creating initrd...\n");
execute((const char*[]) { "update-initrd", "--sysroot", fs, NULL }, "_e");
}
if ( do_upgrade_bootloader )
{
printf(" - Installing bootloader...\n");
execute((const char*[]) { "chroot", "-d", ".", "grub-install",
bootloader_dev_path, NULL },
"_eqQ");
printf(" - Configuring bootloader...\n");
execute((const char*[]) { "chroot", "-d", ".", "update-grub", NULL },
"_eqQ");
}
else if ( conf.system &&
access_or_die("etc/default/grub.d/10_sortix", F_OK) == 0 )
{
// Help dual booters by making /etc/default/grub.d/10_sortix.cache.
printf(" - Creating bootloader fragment...\n");
execute((const char*[]) { "chroot", "-d", ".",
"/etc/grub.d/default/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 ( 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), "finally",
"What now? (exit/poweroff/reboot/halt)", "reboot");
if ( !strcasecmp(input, "exit") )
exit(0);
else if ( !strcasecmp(input, "poweroff") )
exit_gui(0);
else if ( !strcasecmp(input, "reboot") )
exit_gui(1);
else if ( !strcasecmp(input, "halt") )
exit_gui(2);
}
}