sortix-mirror/sysinstall/sysupgrade.c
Jonas 'Sortie' Termansen 6c8e6e5fff Add display server.
This change adds the display(1) graphical user interface and desktop
environment with basic windowing support and the graphical terminal(1)
emulator along with integrations in chkblayout(1), chvideomode(1),
sysinstall(8), sysupgrade(8), as well as the games and ports.

Adopt the Aurora procedural wallpaper in display(1) and login(8).

Remove the obsolete dispd.

Juhani contributed keyboard and video mode APIs to the display protocol
and other miscellaneous changes.

dzwdz contributed the initial functioning window buttons, improved title
bar, window tiling, and minor bug fixes

Co-authored-by: Juhani Krekelä <juhani@krekelä.fi>
Co-authored-by: dzwdz <kg67199@gmail.com>
2023-06-24 00:05:47 +02:00

995 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/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 ( 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);
}
}