Fix system upgrade leaking files.

This commit is contained in:
Jonas 'Sortie' Termansen 2021-01-19 00:04:56 +01:00
parent 29598b4fde
commit cb590ff205
18 changed files with 2897 additions and 237 deletions

View File

@ -74,6 +74,14 @@ all: sysroot
sysmerge: sysroot
sysmerge "$(SYSROOT)"
.PHONY: sysmerge-full
sysmerge-full: sysroot
sysmerge --full "$(SYSROOT)"
.PHONY: sysmerge-full-wait
sysmerge-full-wait: sysroot
sysmerge --full --wait "$(SYSROOT)"
.PHONY: sysmerge-wait
sysmerge-wait: sysroot
sysmerge --wait "$(SYSROOT)"

View File

@ -71,6 +71,7 @@ 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.
Ports that don't exist anymore will be removed.
.It Sy src Ns "=" Ns Oo Sy no "|" yes Oc (default Sy no ) .
Place the new source code in
.Pa /src

View File

@ -121,10 +121,22 @@ 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-full
Like
.Sy sysmerge
but do a full operating system upgrade that uninstalls ports not present in the
sysroot using
.Fl \-full .
.It Sy sysmerge-full-wait
The combination of
.Sy sysmerge-full
and
.Sy sysmerge-full-wait .
.It Sy sysmerge-wait
Like
.Sy sysmerge
but delay the upgrade until the next boot.
but delay the upgrade until the next boot using
.Fl \-wait .
.It Sy sysroot-base-headers
Create the sysroot and install only the headers of the standard library and
kernel into it.
@ -190,6 +202,16 @@ For instance, to build and install libc, run as root:
make install
.Ed
.Pp
Note the individual makefiles only install the new system files and leak any
files that don't exist anymore; and they also don't run any upgrade hooks to
migrate the current system.
This mechanism isn't supported unless you are building the same source code as
the current operating system.
The global
.Sy sysmerge
makefile targets should be used instead as the supported mechanism for operating
system upgrades.
.Pp
System libraries are statically linked and you will have to relink programs with
the new library for changes to take effect.
Building the whole operating system from the root makefile ensures components

View File

@ -69,6 +69,26 @@ releasing Sortix x.y, foo." to allow the maintainer to easily
.Xr grep 1
for it after a release.
.Sh CHANGES
.Ss Fix system upgrade leaking files
.Xr sysupgrade 8
and
.Xr sysmerge 8
will now delete files that no longer exist in the new system and ports.
However, files may already have leaked if a 1.0 installation was upgraded to
a development build prior to this change.
An upgrade hook will delete any well known leaked files.
.Pp
Note:
You must use the
.Fl \-wait
option to do a two-stage upgrade if doing a
.Xr sysmerge 8
upgrade from an installation prior to this change to a version after this
change.
This requirement is because the old
.Xr sysmerge 8
will leak files and the upgrade hook only deal with well known files as of this
change, and doesn't handle future changes.
.Ss Fix /tix/manifest permissions in installations
The
.Pa /tix/manifest

View File

@ -134,6 +134,10 @@ all.
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.
Only the selected ports are loaded into the live environment and installed onto
the new installation.
If upgrading an existing installation, then any ports not loaded will be removed
from the installation being upgraded.
.Ss Installer
This guide assumes you selected the operating system installation option in the
bootloader.

View File

@ -122,7 +122,8 @@ changes.
.It
Updating the system.
.It
Updating the ports.
Updating the ports, installing any new ports, and removing any ports that
don't exist anymore or weren't loaded.
.It
Updating the source code.
.It

View File

@ -24,12 +24,13 @@ hooks.o \
interactive.o \
manifest.o \
release.o \
string_array.o \
OBJS=$(MAIN_OBJS) $(UTIL_OBJS)
SYSINSTALL_DEPS=conf devices execute fileops interactive manifest release
SYSMERGE_DEPS=conf fileops execute hooks manifest release
SYSUPGRADE_DEPS=conf devices execute fileops hooks interactive manifest release
SYSINSTALL_DEPS=conf devices execute fileops interactive manifest release string_array
SYSMERGE_DEPS=conf fileops execute hooks manifest release string_array
SYSUPGRADE_DEPS=conf devices execute fileops hooks interactive manifest release string_array
SYSINSTALL_OBJS:=sysinstall.o $(SYSINSTALL_DEPS:=.o)
SYSMERGE_OBJS:=sysmerge.o $(SYSMERGE_DEPS:=.o)
@ -52,6 +53,7 @@ install: all
# TODO: After releasing Sortix 1.1, remove this compatibility.
touch $(DESTDIR)$(DATADIR)/sysinstall/hooks/sortix-1.1-random-seed
touch $(DESTDIR)$(DATADIR)/sysinstall/hooks/sortix-1.1-tix-manifest-mode
touch $(DESTDIR)$(DATADIR)/sysinstall/hooks/sortix-1.1-leaked-files
sysinstall: $(SYSINSTALL_OBJS)
$(CC) $(SYSINSTALL_OBJS) -o $@ -lmount
@ -71,10 +73,11 @@ sysupgrade.o: $(SYSUPGRADE_DEPS:=.h)
conf.o: conf.h
devices.o: devices.h
execute.o: execute.h
fileops.o: fileops.h
hooks.o: fileops.h release.h
fileops.o: fileops.h string_array.h
string_array.o: string_array.h
hooks.o: fileops.h manifest.h release.h string_array.h
interactive.o: interactive.h execute.h
manifest.o: manifest.h execute.h fileops.h
manifest.o: manifest.h fileops.h string_array.h
release.o: release.h
clean:

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2016, 2017 Jonas 'Sortie' Termansen.
* Copyright (c) 2015, 2016, 2017, 2020 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
@ -32,6 +32,7 @@
#include <unistd.h>
#include "fileops.h"
#include "string_array.h"
char* join_paths(const char* a, const char* b)
{
@ -176,3 +177,45 @@ char* read_string_file(const char* path)
content[amount] = '\0';
return content;
}
char** read_lines_file(const char* path, size_t* out_count)
{
FILE* fp = fopen(path, "r");
if ( !fp )
return NULL;
size_t count;
size_t length;
char** lines;
if ( !string_array_init(&lines, &count, &length) )
{
fclose(fp);
return NULL;
}
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 ( !string_array_append_nodup(&lines, &count, &length, line) )
{
free(line);
string_array_free(&lines, &count, &length);
fclose(fp);
return false;
}
line = NULL;
line_size = 0;
}
free(line);
if ( ferror(fp) )
{
string_array_free(&lines, &count, &length);
fclose(fp);
return NULL;
}
fclose(fp);
*out_count = count;
return lines;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2016 Jonas 'Sortie' Termansen.
* Copyright (c) 2015, 2016, 2020 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
@ -26,5 +26,6 @@ int access_or_die(const char* path, int mode);
void mkdir_or_chmod_or_die(const char* path, mode_t mode);
void write_random_seed(const char* path);
char* read_string_file(const char* path);
char** read_lines_file(const char* path, size_t* out_count);
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2018 Jonas 'Sortie' Termansen.
* Copyright (c) 2015, 2018, 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
@ -27,27 +27,85 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "execute.h"
#include "fileops.h"
#include "manifest.h"
#include "string_array.h"
bool has_manifest(const char* manifest)
{
char* path;
if ( asprintf(&path, "/tix/manifest/%s", manifest) < 0 )
char* path = join_paths("/tix/manifest", manifest);
if ( !path )
{
warn("asprintf");
_exit(2);
}
bool result = access(path, F_OK) == 0;
bool result = access_or_die(path, F_OK) == 0;
free(path);
return result;
}
char** read_manifest(const char* path, size_t* out_count)
{
char** files = read_lines_file(path, out_count);
if ( !files )
return NULL;
// TODO: Remove this compatibility after releasing Sortix 1.1. The manifests
// in Sortix 1.0 have spurious trailing slashes due to a bug in the
// kernel binary package extractor. Remove them here to normalize the
// manifests.
for ( size_t i = 0; i < *out_count; i++ )
{
char* file = files[i];
size_t len = strlen(file);
if ( 2 <= len && file[len - 1] == '/' )
file[len - 1] = '\0';
}
string_array_sort_strcmp(files, *out_count);
return files;
}
static void unlink_rename_conflict(const char* path)
{
if ( !unlink(path) || errno == ENOENT )
return;
if ( errno != EISDIR )
{
warn("unlink: %s", path);
_exit(2);
}
if ( !rmdir(path) )
return;
if ( errno != ENOTEMPTY && errno != EEXIST )
{
warn("rmdir: %s", path);
_exit(2);
}
char* conflict;
if ( asprintf(&conflict, "%s.conflict.XXXXXX", path) < 0 )
{
warn("malloc");
_exit(2);
}
if ( !mkdtemp(conflict) )
{
warn("mkdtemp: %s.conflict.XXXXXX", path);
_exit(2);
}
if ( rename(path, conflict) < 0 )
{
warn("rename: %s -> %s", path, conflict);
rmdir(conflict);
_exit(2);
}
printf("warning: Moving conflicting directory %s to %s\n", path, conflict);
free(conflict);
}
struct hardlink
{
dev_t dev;
@ -57,9 +115,10 @@ struct hardlink
void install_manifest(const char* manifest,
const char* from_prefix,
const char* to_prefix)
const char* to_prefix,
const char* const* preserved,
size_t preserved_count)
{
printf(" - Installing %s...\n", manifest);
struct hardlink* hardlinks = NULL;
size_t hardlinks_used = 0;
size_t hardlinks_length = 0;
@ -71,52 +130,141 @@ void install_manifest(const char* manifest,
_exit(2);
}
mode_t old_umask = umask(0000);
// Read the input and output manifests if they exist. Consider a manifest
// that doesn't exist as being empty.
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 )
char* outnewmanifest;
if ( asprintf(&inmanifest, "%s/tix/manifest/%s", from_prefix,
manifest) < 0 ||
asprintf(&outmanifest, "%s/tix/manifest/%s", to_prefix,
manifest) < 0 ||
asprintf(&outnewmanifest, "%s/tix/manifest/%s.new", to_prefix,
manifest) < 0 )
{
warn("asprintf");
warn("malloc");
_exit(2);
}
FILE* fpin = fopen(inmanifest, "r");
if ( !fpin )
bool in_exists = !access_or_die(inmanifest, F_OK);
bool out_exists = !access_or_die(outmanifest, F_OK);
const char* action = in_exists && out_exists ? "Upgrading" :
in_exists ? "Installing" :
"Uninstalling";
printf(" - %s %s...\n", action, manifest);
char** empty = (char*[]){};
char** in_files = empty;
size_t in_files_count = 0;
if ( in_exists &&
!(in_files = read_manifest(inmanifest, &in_files_count)) )
{
warn("%s", inmanifest);
_exit(2);
}
FILE* fpout = fopen(outmanifest, "w");
if ( !fpout )
char** out_files = empty;
size_t out_files_count = 0;
if ( out_exists &&
!(out_files = read_manifest(outmanifest, &out_files_count)) )
{
warn("%s", outmanifest);
_exit(2);
}
char* line = NULL;
size_t line_size = 0;
ssize_t line_length;
while ( 0 < (line_length = getline(&line, &line_size, fpin)) )
// Directories to be cleaned up afterwards when they might be empty.
size_t rmdirs_count;
size_t rmdirs_length;
char** rmdirs;
if ( !string_array_init(&rmdirs, &rmdirs_count, &rmdirs_length) )
{
if ( line[line_length-1] == '\n' )
line[--line_length] = '\0';
if ( fprintf(fpout, "%s\n", line) < 0 )
warn("malloc");
_exit(2);
}
// Find the differences by mutually iterating the manifests in sorted
// order.
size_t in_i = 0;
size_t out_i = 0;
while ( in_i < in_files_count || out_i < out_files_count )
{
const char* in = in_i < in_files_count ? in_files[in_i] : NULL;
const char* out = out_i < out_files_count ? out_files[out_i] : NULL;
if ( !in || (out && strcmp(in, out) > 0) )
{
warn("write: %s", outmanifest);
_exit(2);
}
if ( line[0] != '/' )
out_i++;
const char* path = out;
char* out_path = join_paths(to_prefix, path);
if ( !out_path )
{
warn("asprintf");
_exit(2);
}
// Don't delete a path if it will be added in later by another
// manifest. This supports files moving from one manifest to another
// and directories only being cleaned up when no manifest mentions
// them.
if ( string_array_contains_bsearch_strcmp(preserved,
preserved_count, path) )
{
// Handle a directory becoming a symbolic link, which will be
// renamed to a conflict directory and replaced with a symbolic
// link, but we must take care not to delete anything through
// the symbolic link. This case happens if the directory becomes
// a symlink in another manifest.
struct stat outst;
if ( !lstat(out_path, &outst) )
{
if ( S_ISLNK(outst.st_mode) )
{
size_t path_length = strlen(path);
while ( out_i < out_files_count &&
!strncmp(path, out_files[out_i], path_length) &&
out_files[out_i][path_length] == '/' )
out_i++;
}
}
else if ( errno != ENOENT && errno != ENOTDIR )
{
warn("%s", out_path);
_exit(2);
}
free(out_path);
continue;
}
if ( unlink(out_path) < 0 )
{
if ( errno == EISDIR )
{
if ( rmdir(out_path) < 0 )
{
if ( errno == ENOTEMPTY || errno == EEXIST )
{
if ( !string_array_append(&rmdirs, &rmdirs_count,
&rmdirs_length, path) )
{
warn("malloc");
_exit(2);
}
}
else if ( errno != ENOENT )
{
warn("unlink: %s", out_path);
_exit(2);
}
}
}
else if ( errno != ENOENT )
{
warn("unlink: %s", out_path);
_exit(2);
}
}
free(out_path);
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 )
in_i++;
if ( out && !strcmp(in, out) )
out_i++;
const char* path = in;
char* in_path = join_paths(from_prefix, path);
char* out_path = join_paths(to_prefix, path);
if ( !in_path || !out_path )
{
warn("asprintf");
_exit(2);
@ -141,7 +289,7 @@ void install_manifest(const char* manifest,
}
if ( hardlink )
{
unlink(out_path);
unlink_rename_conflict(out_path);
if ( link(hardlink->path, out_path) < 0 )
{
warn("link: %s -> %s", hardlink->path, out_path);
@ -150,23 +298,38 @@ void install_manifest(const char* manifest,
}
else if ( S_ISDIR(inst.st_mode) )
{
if ( mkdir(out_path, inst.st_mode & 07777) < 0 && errno != EEXIST )
if ( unlink(out_path) < 0 && errno != ENOENT && errno != EISDIR )
{
warn("mkdir: %s", out_path);
warn("unlink: %s", out_path);
_exit(2);
}
if ( mkdir(out_path, inst.st_mode & 07777) < 0 )
{
if ( errno == EEXIST )
{
if ( chmod(out_path, inst.st_mode & 07777) < 0 )
{
warn("chmod: %s", out_path);
_exit(2);
}
}
else
{
warn("mkdir: %s", out_path);
_exit(2);
}
}
}
else if ( S_ISREG(inst.st_mode) )
{
unlink_rename_conflict(out_path);
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,
int out_fd = open(out_path, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL,
inst.st_mode & 07777);
if ( out_fd < 0 )
{
@ -183,7 +346,8 @@ void install_manifest(const char* manifest,
}
if ( amount == 0 )
break;
if ( writeall(out_fd, buffer, (size_t) amount) < (size_t) amount )
if ( writeall(out_fd, buffer, (size_t) amount) <
(size_t) amount )
{
warn("write: %s", out_path);
_exit(2);
@ -195,17 +359,17 @@ void install_manifest(const char* manifest,
{
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));
size_t new_length = hardlinks_length ? hardlinks_length : 8;
struct hardlink* new_hardlinks =
reallocarray(hardlinks, new_length,
2 * sizeof(struct hardlink));
if ( !new_hardlinks )
{
warn("malloc");
_exit(2);
}
hardlinks = new_hardlinks;
hardlinks_length = new_length;
hardlinks_length = 2 * new_length;
}
hardlinks[hardlinks_used].ino = inst.st_ino;
hardlinks[hardlinks_used].dev = inst.st_dev;
@ -226,12 +390,22 @@ void install_manifest(const char* manifest,
_exit(2);
}
buffer[amount] = '\0';
unlink(out_path);
unlink_rename_conflict(out_path);
if ( symlink(buffer, out_path) < 0 && errno != EEXIST )
{
warn("symlink: %s", out_path);
_exit(2);
}
// Handle a directory becoming a symbolic link, which will be
// renamed to a conflict directory and replaced with a symbolic
// link, but we must take care not to delete anything through
// the symbolic link. This case happens if the directory becomes a
// symlink in the same manifest.
size_t path_length = strlen(path);
while ( out_i < out_files_count &&
!strncmp(path, out_files[out_i], path_length) &&
out_files[out_i][path_length] == '/' )
out_i++;
}
else
{
@ -241,20 +415,218 @@ void install_manifest(const char* manifest,
free(in_path);
free(out_path);
}
free(line);
if ( ferror(fpin) )
// Delete directories that might not be empty in backwards order to ensure
// subdirectories are deleted before their parent directories.
for ( size_t i = rmdirs_count; i; i-- )
{
warn("%s", inmanifest);
const char* path = rmdirs[i - 1];
char* out_path;
if ( asprintf(&out_path, "%s%s", to_prefix, path) < 0 )
{
warn("asprintf");
_exit(2);
}
if ( rmdir(out_path) < 0 &&
errno != ENOTEMPTY && errno != EEXIST && errno != ENOENT )
{
warn("unlink: %s", out_path);
_exit(2);
}
free(out_path);
(void) path;
}
string_array_free(&rmdirs, &rmdirs_count, &rmdirs_length);
if ( in_exists )
{
if ( unlink(outnewmanifest) < 0 && errno != ENOENT )
{
warn("unlink: %s", outnewmanifest);
_exit(2);
}
mode_t temp_umask = umask(0022);
FILE* fp = fopen(outnewmanifest, "w");
if ( !fp )
{
warn("%s", outnewmanifest);
_exit(2);
}
umask(temp_umask);
for ( size_t i = 0; i < in_files_count; i++ )
{
const char* path = in_files[i];
if ( fputs(path, fp) == EOF || fputc('\n', fp) == EOF )
{
warn("%s", outnewmanifest);
_exit(2);
}
}
if ( fclose(fp) == EOF )
{
warn("%s", outnewmanifest);
_exit(2);
}
if ( rename(outnewmanifest, outmanifest) < 0 )
{
warn("rename: %s -> %s", outnewmanifest, outmanifest);
_exit(2);
}
}
else if ( out_exists )
{
if ( unlink(outmanifest) < 0 && errno != ENOENT )
{
warn("unlink: %s", outmanifest);
_exit(2);
}
}
// Write out the new manifests atomically afterwards to ensure no paths are
// leaked if the operation is aborted part way.
char* in_tixinfo;
char* out_tixinfo;
if ( asprintf(&in_tixinfo, "%s/tix/tixinfo/%s", from_prefix,
manifest) < 0 ||
asprintf(&out_tixinfo, "%s/tix/tixinfo/%s", to_prefix,
manifest) < 0 )
{
warn("malloc");
_exit(2);
}
fclose(fpin);
if ( fclose(fpout) == EOF )
// Update or delete the tixinfo accordingly.
bool is_tix = !access_or_die(in_tixinfo, F_OK);
if ( is_tix )
{
warn("close: %s", outmanifest);
int in_fd = open(in_tixinfo, O_RDONLY);
if ( in_fd < 0 )
{
warn("%s", in_tixinfo);
_exit(2);
}
unlink(out_tixinfo);
int out_fd = open(out_tixinfo, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if ( out_fd < 0 )
{
warn("%s", out_tixinfo);
_exit(2);
}
while ( true )
{
ssize_t amount = read(in_fd, buffer, buffer_size);
if ( amount < 0 )
{
warn("read: %s", in_tixinfo);
_exit(2);
}
if ( amount == 0 )
break;
if ( writeall(out_fd, buffer, (size_t) amount) < (size_t) amount )
{
warn("write: %s", out_tixinfo);
_exit(2);
}
}
close(out_fd);
close(in_fd);
}
else
{
if ( unlink(out_tixinfo) < 0 && errno != ENOENT )
{
warn("unlink: %s", out_tixinfo);
_exit(2);
}
}
free(in_tixinfo);
free(out_tixinfo);
// Likewise write out the new installation list atomically afterwards to
// ensure no manifests are leaked if the operation is aborted part way.
char* installed_path;
char* installed_path_new;
if ( asprintf(&installed_path, "%s/tix/installed.list", to_prefix) < 0 ||
asprintf(&installed_path_new, "%s/tix/installed.list.new",
to_prefix) < 0 )
{
warn("malloc");
_exit(2);
}
size_t installed_count;
char** installed = read_lines_file(installed_path, &installed_count);
if ( !installed )
{
warn("%s", installed_path);
_exit(2);
}
size_t installed_length = installed_count;
if ( is_tix )
{
bool found = false;
for ( size_t i = 0; !found && i < installed_count; i++ )
found = !strcmp(installed[i], manifest);
if ( !found && !string_array_append(&installed, &installed_count,
&installed_length, manifest) )
{
warn("malloc");
_exit(2);
}
}
else
{
size_t o = 0;
for ( size_t i = 0; i < installed_count; i++ )
{
if ( !strcmp(installed[i], manifest) )
free(installed[i]);
else
installed[o++] = installed[i];
}
installed_count = o;
}
string_array_sort_strcmp(installed, installed_count);
mode_t temp_umask = umask(0022);
FILE* installed_fp = fopen(installed_path_new, "w");
if ( !installed_fp )
{
warn("%s", installed_path_new);
_exit(2);
}
umask(temp_umask);
for ( size_t i = 0; i < installed_count; i++ )
{
const char* name = installed[i];
if ( fputs(name, installed_fp) == EOF ||
fputc('\n', installed_fp) == EOF )
{
warn("%s", installed_path_new);
_exit(2);
}
}
if ( fclose(installed_fp) == EOF )
{
warn("%s", installed_path_new);
_exit(2);
}
if ( rename(installed_path_new, installed_path) < 0 )
{
warn("rename: %s -> %s", installed_path_new, installed_path);
_exit(2);
}
string_array_free(&installed, &installed_count, &installed_length);
free(installed_path);
free(installed_path_new);
if ( in_files != empty )
{
for ( size_t i = 0; i < in_files_count; i++ )
free(in_files[i]);
free(in_files);
}
if ( out_files != empty )
{
for ( size_t i = 0; i < out_files_count; i++ )
free(out_files[i]);
free(out_files);
}
free(inmanifest);
free(outmanifest);
free(outnewmanifest);
umask(old_umask);
free(buffer);
for ( size_t i = 0; i < hardlinks_used; i++ )
@ -262,148 +634,152 @@ void install_manifest(const char* manifest,
free(hardlinks);
}
bool check_installed(const char* path, const char* package)
void install_manifests(const char* const* manifests,
size_t manifests_count,
const char* from_prefix,
const char* to_prefix)
{
FILE* fp = fopen(path, "r");
if ( !fp )
// Load all the paths mentioned in the new set of manifests, which are used
// to ensure no files and directories are deleted part way if they are moved
// from one manifest to another.
printf(" - Loading manifests...\n");
size_t all_count;
size_t all_length;
char** all;
if ( !string_array_init(&all, &all_count, &all_length) )
{
if ( errno != ENOENT )
warn("%s", path);
return false;
}
char* line = NULL;
size_t line_size = 0;
ssize_t line_length;
while ( 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 ( ferror(fp) )
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");
warn("malloc");
_exit(2);
}
if ( access_or_die(inst_in_path, F_OK) < 0 )
for ( size_t i = 0; i < manifests_count; i++ )
{
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 < (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 )
// Read the input manifests if they exist. Consider a manifest that
// doesn't exist as being empty.
const char* manifest = manifests[i];
char* inmanifest;
if ( asprintf(&inmanifest, "%s/tix/manifest/%s", from_prefix,
manifest) < 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);
char** empty = (char*[]){};
char** in_files = empty;
size_t in_files_count = 0;
if ( !access_or_die(inmanifest, F_OK) &&
!(in_files = read_manifest(inmanifest, &in_files_count)) )
{
warn("%s", inmanifest);
_exit(2);
}
// Directories can appear in multiple manifests, so keep track of all
// input paths so we later can find duplicates.
for ( size_t i = 0; i < in_files_count; i++ )
{
if ( !string_array_append(&all, &all_count, &all_length,
in_files[i]) )
{
warn("malloc");
_exit(2);
}
}
if ( in_files != empty )
{
for ( size_t i = 0; i < in_files_count; i++ )
free(in_files[i]);
free(in_files);
}
free(inmanifest);
}
free(line);
if ( ferror(fp) )
string_array_sort_strcmp(all, all_count);
all_count = string_array_deduplicate(all, all_count);
for ( size_t i = 0; i < manifests_count; i++ )
install_manifest(manifests[i], from_prefix, to_prefix,
(const char* const*) all, all_count);
string_array_free(&all, &all_count, &all_length);
}
char** read_installed_list(const char* prefix, size_t* out_count)
{
char* path;
if ( asprintf(&path, "%s/tix/installed.list", prefix) < 0 )
{
warn("%s", cmd);
pclose(fp);
warn("malloc");
_exit(2);
}
pclose(fp);
free(cmd);
free(inst_in_path);
free(inst_out_path);
char** installed;
size_t installed_count;
if ( !access_or_die(path, F_OK) )
{
if ( !(installed = read_lines_file(path, &installed_count)) )
{
warn("%s", path);
_exit(2);
}
string_array_sort_strcmp(installed, installed_count);
}
else
{
installed = malloc(1);
if ( !installed )
{
warn("malloc");
_exit(2);
}
installed_count = 0;
}
free(path);
*out_count = installed_count;
return installed;
}
void install_manifests_detect(const char* from_prefix,
const char* to_prefix,
bool system,
bool detect_from,
bool detect_to)
{
char** manifests;
size_t manifests_count;
size_t manifests_length;
string_array_init(&manifests, &manifests_count, &manifests_length);
if ( system &&
!string_array_append(&manifests, &manifests_count, &manifests_length,
"system") )
{
warn("malloc");
_exit(2);
}
size_t system_offset = system ? 1 : 0;
const char* prefixes[] =
{
detect_from ? from_prefix : NULL,
detect_to ? to_prefix : NULL,
};
for ( size_t i = 0; i < sizeof(prefixes) / sizeof(prefixes[0]); i++ )
{
const char* prefix = prefixes[i];
if ( !prefix )
continue;
size_t installed_count;
char** installed = read_installed_list(prefix, &installed_count);
for ( size_t i = 0; i < installed_count; i++ )
{
if ( !string_array_append(&manifests, &manifests_count,
&manifests_length, installed[i]) )
{
warn("malloc");
_exit(2);
}
free(installed[i]);
}
free(installed);
}
// Keep the system manifest first and otherwise sort and deduplicate.
string_array_sort_strcmp(manifests + system_offset,
manifests_count - system_offset);
manifests_count = string_array_deduplicate(manifests, manifests_count);
install_manifests((const char* const*) manifests, manifests_count,
from_prefix, to_prefix);
string_array_free(&manifests, &manifests_count, &manifests_length);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2016 Jonas 'Sortie' Termansen.
* Copyright (c) 2015, 2016, 2020 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
@ -21,10 +21,21 @@
#define MANIFEST_H
bool has_manifest(const char* manifest);
char** read_manifest(const char* path, size_t* out_count);
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);
const char* to_prefix,
const char* const* preserved,
size_t preserved_count);
void install_manifests(const char* const* manifests,
size_t manifests_count,
const char* from_prefix,
const char* to_prefix);
char** read_installed_list(const char* prefix, size_t* out_count);
void install_manifests_detect(const char* from_prefix,
const char* to_prefix,
bool system,
bool detect_from,
bool detect_to);
#endif

118
sysinstall/string_array.c Normal file
View File

@ -0,0 +1,118 @@
/*
* Copyright (c) 2020 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.
*
* string_array.c
* String array utility functions.
*/
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "string_array.h"
bool string_array_init(char*** array, size_t* count, size_t* length)
{
*count = 0;
*length = 0;
size_t initial_length = 4;
if ( !(*array = calloc(initial_length, sizeof(char*))) )
return false;
*length = initial_length;
return true;
}
void string_array_free(char*** array, size_t* count, size_t* length)
{
for ( size_t i = 0; i < *count; i++ )
free((*array)[i]);
free((*array));
*array = NULL;
*count = 0;
*length = 0;
}
bool string_array_append_nodup(char*** array,
size_t* count,
size_t* length,
char* str)
{
if ( *count == *length )
{
char** new_array =
reallocarray(*array, *length, 2 * sizeof(char*));
if ( !new_array )
return false;
*array = new_array;
*length *= 2;
}
(*array)[(*count)++] = str;
return true;
}
bool string_array_append(char*** array,
size_t* count,
size_t* length,
const char* str)
{
char* dup = strdup(str);
if ( !dup )
return false;
if ( !string_array_append_nodup(array, count, length, dup) )
{
free(dup);
return false;
}
return true;
}
static int strcmp_indirect(const void* a_ptr, const void* b_ptr)
{
const char* a = *(const char* const*) a_ptr;
const char* b = *(const char* const*) b_ptr;
return strcmp(a, b);
}
void string_array_sort_strcmp(char** array, size_t count)
{
qsort(array, count, sizeof(char*), strcmp_indirect);
}
static int search_strcmp(const void* str_ptr, const void* elem_ptr)
{
const char* str = (const char*) str_ptr;
char* elem = *(char**) elem_ptr;
return strcmp(str, elem);
}
bool string_array_contains_bsearch_strcmp(const char* const* array,
size_t count,
const char* str)
{
return bsearch(str, array, count, sizeof(char*), search_strcmp);
}
size_t string_array_deduplicate(char** strings, size_t count)
{
size_t o = 0;
for ( size_t i = 0; i < count; i++ )
{
if ( !o || strcmp(strings[o - 1], strings[i]) != 0 )
strings[o++] = strings[i];
else
free(strings[i]);
}
return o;
}

39
sysinstall/string_array.h Normal file
View File

@ -0,0 +1,39 @@
/*
* Copyright (c) 2020 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.
*
* string_array.h
* String array utility functions.
*/
#ifndef STRING_ARRAY_H
#define STRING_ARRAY_H
bool string_array_init(char*** array, size_t* count, size_t* length);
void string_array_free(char*** array, size_t* count, size_t* length);
bool string_array_append_nodup(char*** array,
size_t* count,
size_t* length,
char* str);
bool string_array_append(char*** array,
size_t* count,
size_t* length,
const char* str);
void string_array_sort_strcmp(char** array, size_t count);
bool string_array_contains_bsearch_strcmp(const char* const* array,
size_t count,
const char* str);
size_t string_array_deduplicate(char** strings, size_t count);
#endif

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2016, 2020 Jonas 'Sortie' Termansen.
* 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
@ -884,40 +884,19 @@ int main(void)
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", 0755);
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", "", ".");
install_manifests_detect("", ".", true, true, true);
// TODO: Preserve the existing /src if it exists like in sysupgrade.
if ( has_manifest("src") )
install_manifest("src", "", ".");
install_manifest("src", "", ".", (const char*[]){}, 0);
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("", ".");
if ( access_or_die("boot/random.seed", F_OK) < 0 )
{
printf(" - Creating random seed...\n");
@ -991,6 +970,16 @@ int main(void)
}
text("\n");
if ( mkdir("root", 0700) < 0 )
{
if ( errno == EEXIST )
{
if ( chmod("root", 0700) < 0 )
warn("chmod: root");
}
else
warn("mkdir: root");
}
if ( passwd_has_uid("etc/passwd", 0) ||
passwd_has_name("etc/passwd", "root") )
{
@ -1036,6 +1025,16 @@ int main(void)
}
text("\n");
if ( mkdir("etc/init", 0755) < 0 )
{
if ( errno == EEXIST )
{
if ( chmod("etc/init", 0755) < 0 )
warn("chmod: etc/init");
}
else
warn("mkdir: etc/init");
}
install_configurationf("etc/init/target", "w", "multi-user\n");
text("Congratulations, the system is now functional! This is a good time "

View File

@ -6,7 +6,7 @@
.Nd upgrade current operating system from a sysroot
.Sh SYNOPSIS
.Nm sysmerge
.Op Fl cw
.Op Fl cfw
.Op Fl \-booting
.Op Fl \-hook-finalize
.Op Fl \-hook-prepare
@ -27,6 +27,7 @@ installs the
manifest from the tix repository in the
.Ar source
directory, as well as all the ports found.
If a full upgrade is done, then all ports not found will be uninstalled.
Upgrade hooks will be run if further actions are needed to migrate the system to
the new version as described in
.Xr following-development 7 .
@ -68,6 +69,10 @@ directory and restore the old
.Xr kernel 7
and
.Xr initrd 7 .
.It Fl f , Fl \-full
Full system upgrade that uninstalls ports not present in the
.Ar source
directory.
.It Fl \-hook-finalize
Run the post-upgrade hooks.
This is meant to be used by the old

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2018, 2020 Jonas 'Sortie' Termansen.
* Copyright (c) 2016, 2018, 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
@ -22,6 +22,7 @@
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
@ -73,6 +74,7 @@ int main(int argc, char* argv[])
bool booting = false;
bool cancel = false;
bool full = false;
bool hook_finalize = false;
bool hook_prepare = false;
bool wait = false;
@ -92,6 +94,7 @@ int main(int argc, char* argv[])
while ( (c = *++arg) ) switch ( c )
{
case 'c': cancel = true; break;
case 'f': full = true; break;
case 'w': wait = true; break;
default:
fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c);
@ -107,6 +110,8 @@ int main(int argc, char* argv[])
booting = true;
else if ( !strcmp(arg, "--cancel") )
cancel = true;
else if ( !strcmp(arg, "--full") )
full = true;
else if ( !strcmp(arg, "--hook-finalize") )
hook_finalize = true;
else if ( !strcmp(arg, "--hook-prepare") )
@ -142,6 +147,7 @@ int main(int argc, char* argv[])
source = "/sysmerge";
if ( 1 < argc )
errx(2, "Unexpected extra operand `%s'", argv[1]);
full = access_or_die("/sysmerge/tix/sysmerge.full", F_OK) == 0;
}
else
{
@ -307,13 +313,19 @@ int main(int argc, char* argv[])
execute((const char*[]) { "tix-collection", "/sysmerge", "create",
NULL }, "e");
}
install_manifest("system", source, target);
install_ports(source, target);
install_manifests_detect(source, target, true, true, full);
}
if ( wait )
{
printf(" - Scheduling upgrade on next boot...\n");
if ( full )
{
int fd = open("/sysmerge/tix/sysmerge.full", O_WRONLY | O_CREAT);
if ( fd < 0 )
err(1, "/sysmerge/tix/sysmerge.full");
close(fd);
}
execute((const char*[]) { "cp", "/boot/sortix.bin",
"/boot/sortix.bin.sysmerge.orig", NULL }, "e");
execute((const char*[]) { "cp", "/boot/sortix.initrd",

View File

@ -803,13 +803,9 @@ int main(void)
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 )
{
upgrade_prepare(target_release, &new_release, "", ".");
install_manifest("system", "", ".");
}
install_manifests_detect("", ".", conf.system, conf.ports, conf.ports);
if ( has_manifest("src") )
{
if ( conf.newsrc )
@ -824,7 +820,7 @@ int main(void)
_exit(1);
}
}
install_manifest("src", "", ".");
install_manifest("src", "", ".", (const char*[]){}, 0);
if ( has_src )
{
if ( rename("src", "newsrc") < 0 )
@ -842,11 +838,9 @@ int main(void)
else if ( conf.src )
{
preserve_src("src");
install_manifest("src", "", ".");
install_manifest("src", "", ".", (const char*[]){}, 0);
}
}
if ( conf.ports )
install_ports("", ".");
if ( conf.system )
upgrade_finalize(target_release, &new_release, "", ".");
if ( conf.system )