Skip to content

Commit

Permalink
Add --single-step support for update
Browse files Browse the repository at this point in the history
Allow users to update by stepping through each release between their
current version and the latest. This option is primarily for cases
where update is failing due to memory or disk space running out when
updating normally.

Signed-off-by: William Douglas <[email protected]>
  • Loading branch information
bryteise committed Nov 18, 2024
1 parent 8cc2d86 commit 129e82b
Show file tree
Hide file tree
Showing 8 changed files with 430 additions and 31 deletions.
2 changes: 2 additions & 0 deletions docs/swupd.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ update
--download Do not perform an update, instead download all resources needed
to perform the update, and exit.

--incremental Update to target by one release at a time instead of last release of each format

--update-search-file-index Update the index used by search-file to speed up
searches. Don't enable this if you have download or space restrictions.

Expand Down
37 changes: 29 additions & 8 deletions src/cmds/update.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@
#define FLAG_DOWNLOAD_ONLY 2000
#define FLAG_UPDATE_SEARCH_FILE_INDEX 2001
#define FLAG_UPDATE_3RD_PARTY 2002
#define FLAG_INCREMENTAL 2003

static int requested_version = -1;
static bool download_only = false;
static bool update_search_file_index = false;
static bool keepcache = false;
static bool cmdline_option_single_step = false;
static char swupd_binary[LINE_MAX] = { 0 };

#ifdef THIRDPARTY
Expand Down Expand Up @@ -142,11 +144,11 @@ int add_included_manifests(struct manifest *mom, struct list **subs)
return 0;
}

static enum swupd_code check_versions(int *current_version, int *server_version, int req_version, char *path_prefix)
static enum swupd_code check_versions(int *current_version, int *server_version, int req_version, char *path_prefix, bool single_step)
{
int ret;

ret = read_versions(current_version, server_version, path_prefix);
ret = read_versions(current_version, server_version, path_prefix, single_step);
if (ret != SWUPD_OK) {
return ret;
}
Expand All @@ -156,8 +158,12 @@ static enum swupd_code check_versions(int *current_version, int *server_version,
}
if (req_version != -1) {
if (req_version < *current_version) {
error("Requested version for update (%d) must be greater than current version (%d)\n",
req_version, *current_version);
// This error message isn't valid if the
// system has gone through a format bump
if (!on_new_format()) {
error("Requested version for update (%d) must be greater than current version (%d)\n",
req_version, *current_version);
}
return SWUPD_INVALID_OPTION;
}
if (req_version < *server_version) {
Expand Down Expand Up @@ -285,7 +291,7 @@ enum swupd_code execute_update_extra(extra_proc_fn_t post_update_fn, extra_proc_

/* get versions */
timelist_timer_start(globals.global_times, "Get versions");
ret = check_versions(&current_version, &server_version, requested_version, globals.path_prefix);
ret = check_versions(&current_version, &server_version, requested_version, globals.path_prefix, cmdline_option_single_step);
if (ret != SWUPD_OK) {
goto clean_curl;
}
Expand Down Expand Up @@ -448,7 +454,17 @@ enum swupd_code execute_update_extra(extra_proc_fn_t post_update_fn, extra_proc_
progress_next_step("run_postupdate_scripts", PROGRESS_UNDEFINED);

/* Determine if another update is needed so the scripts block */
int new_current_version = get_current_version(globals.path_prefix);
int new_current_version, new_server_version;
int versions_check = check_versions(&new_current_version, &new_server_version, requested_version, globals.path_prefix, false);
if (versions_check != SWUPD_OK) {
// update was fine, but we aren't seeing the new server version
// for some reason so don't set re_update on single_step
new_server_version = new_current_version;
}

if (cmdline_option_single_step && new_server_version > new_current_version) {
re_update = true;
}
if (on_new_format() && (requested_version == -1 || (requested_version > new_current_version))) {
re_update = true;
}
Expand Down Expand Up @@ -560,6 +576,7 @@ static const struct option prog_opts[] = {
#ifdef THIRDPARTY
{ "3rd-party", no_argument, 0, FLAG_UPDATE_3RD_PARTY },
#endif
{ "incremental", no_argument, 0, FLAG_INCREMENTAL },
};

static void print_help(void)
Expand All @@ -579,6 +596,7 @@ static void print_help(void)
#ifdef THIRDPARTY
print(" --3rd-party Also update content from 3rd-party repositories\n");
#endif
print(" --incremental Update to target by one release at a time instead of last release of each format\n");
print("\n");
}

Expand Down Expand Up @@ -617,6 +635,9 @@ static bool parse_opt(int opt, char *optarg)
cmdline_option_3rd_party = optarg_to_bool(optarg);
return true;
#endif
case FLAG_INCREMENTAL:
cmdline_option_single_step = optarg_to_bool(optarg);
return true;
default:
return false;
}
Expand Down Expand Up @@ -697,7 +718,7 @@ enum swupd_code update_main(int argc, char **argv)
ret = check_update();

#ifdef THIRDPARTY
if (cmdline_option_3rd_party) {
if (cmdline_option_3rd_party && !cmdline_option_single_step) {
progress_finish_steps(ret);
info("\nChecking update status of content from 3rd-party repositories\n\n");
ret = third_party_execute_check_update();
Expand All @@ -707,7 +728,7 @@ enum swupd_code update_main(int argc, char **argv)
ret = execute_update();

#ifdef THIRDPARTY
if (cmdline_option_3rd_party) {
if (cmdline_option_3rd_party && !cmdline_option_single_step) {
if (ret == SWUPD_OK) {
progress_finish_steps(ret);
info("\nUpdating content from 3rd-party repositories\n\n");
Expand Down
2 changes: 1 addition & 1 deletion src/swupd.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ extern enum swupd_code walk_tree(struct manifest *, const char *, bool, const re

extern int get_latest_version(char *v_url);
extern int get_int_from_url(const char *url);
extern enum swupd_code read_versions(int *current_version, int *server_version, char *path_prefix);
extern enum swupd_code read_versions(int *current_version, int *server_version, char *path_prefix, bool single_step);
extern int get_current_version(char *path_prefix);
extern bool get_distribution_string(char *path_prefix, char *dist);
extern int get_current_format(void);
Expand Down
141 changes: 126 additions & 15 deletions src/swupd_lib/version.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
int get_int_from_url(const char *url)
{
int err, value;
char tmp_string[MAX_VERSION_STR_SIZE+1];
char tmp_string[MAX_VERSION_STR_SIZE + 1];
struct curl_file_data tmp_data = {
MAX_VERSION_STR_SIZE, 0,
tmp_string
Expand Down Expand Up @@ -147,11 +147,11 @@ static int verify_signature(char *url, struct curl_file_data *tmp_version)
return ret;
}

static int get_version_from_url(char *url)
static int get_version_from_url_inmemory(char *url)
{
int ret = 0;
int err = 0;
char version_str[MAX_VERSION_STR_SIZE+1];
char version_str[MAX_VERSION_STR_SIZE + 1];
int sig_verified = 0;

/* struct for version data */
Expand Down Expand Up @@ -188,49 +188,154 @@ static int get_version_from_url(char *url)
}
return -SWUPD_ERROR_SIGNATURE_VERIFICATION;
}
return ret;
}

static int get_version_from_file(char *filename, int current_version)
{
FILE *infile;
char *line = NULL;
// start as -EINVAL to report cases of an empty file
int next_version = -EINVAL;
int tmp_version = 0;
int err = 0;
size_t len = 0;
ssize_t read;

infile = fopen(filename, "r");
if (infile == NULL) {
return -EINVAL;
}

while ((read = getline(&line, &len, infile)) != -1) {
err = str_to_int(line, &tmp_version);
if (err) {
break;
}
if (current_version < 0) {
next_version = tmp_version;
break;
} else if (tmp_version <= current_version) {
// We did get a valid version so report it
// but it isn't usable for update
if (next_version < 0) {
next_version = tmp_version;
}
break;
}
next_version = tmp_version;
}

(void)fclose(infile);

if (line) {
FREE(line);
}

if (err) {
return err;
}
return next_version;
}

static int get_version_from_url(char *url, char *filename, int current_version)
{
int ret = 0;
char *sig_filename;
char *sig_url;
char *download_file;
char *download_sig;

string_or_die(&sig_filename, "%s.sig", filename);
string_or_die(&sig_url, "%s.sig", url);
download_file = statedir_get_download_file(filename);
download_sig = statedir_get_download_file(sig_filename);
ret = swupd_curl_get_file(url, download_file);
if (ret) {
goto out;
}

/* Sig check */
if (globals.sigcheck && globals.sigcheck_latest) {
ret = swupd_curl_get_file(sig_url, download_sig);
if (ret) {
ret = -SWUPD_ERROR_SIGNATURE_VERIFICATION;
error("Signature for latest file (%s) is missing\n", url);
goto out;
}
if (!signature_verify(download_file, download_sig, SIGNATURE_IGNORE_EXPIRATION)) {
ret = -SWUPD_ERROR_SIGNATURE_VERIFICATION;
error("Signature verification failed for URL: %s\n", url);
goto out;
}
} else {
warn_nosigcheck(url);
}

ret = get_version_from_file(download_file, current_version);

out:
FREE(sig_filename);
FREE(sig_url);
FREE(download_file);
FREE(download_sig);

return ret;
}

/* this function attempts to download the latest server version string file from
* the preferred server to a memory buffer, returning either a negative integer
* error code or >= 0 representing the server version
* the preferred server, returning either a negative integer error code or >= 0
* representing the server version
*
* if v_url is non-NULL the version at v_url is fetched. If v_url is NULL the
* global version_url is used and the cached version may be used instead of
* attempting to download the version string again. If v_url is the empty string
* the global version_url is used and the cached version is ignored. */
int get_latest_version(char *v_url)
* attempting to download the version string again (unless current_version is
* negative, indicating incremental and the version needs to be recalculated).
* If v_url is the empty string the global version_url is used and the cached
* version is ignored.
*
* if current_version >= 0 it is used to indicate the next version rather than
* the latest version should be returned. */
static int get_version(char *v_url, int current_version)
{
char *filename = "latest";
char *url = NULL;
int ret = 0;
static int cached_version = -1;

if (cached_version > 0 && v_url == NULL) {
if (cached_version > 0 && v_url == NULL && current_version < 0) {
return cached_version;
}

// TODO: why allow "" when we do NULL test above?
// maybe consolidate.
if (v_url == NULL || str_cmp(v_url, "") == 0) {
v_url = globals.version_url;
}

string_or_die(&url, "%s/version/format%s/latest", v_url, globals.format_string);
ret = get_version_from_url(url);
string_or_die(&url, "%s/version/format%s/%s", v_url, globals.format_string, filename);
ret = get_version_from_url(url, filename, current_version);
FREE(url);
cached_version = ret;

return ret;
}

int get_latest_version(char *v_url)
{
return get_version(v_url, -1);
}

/* gets the latest version of the update content regardless of what format we
* are currently in */
int version_get_absolute_latest(void)
{
char *filename = "latest_version";
char *url = NULL;
int ret;

string_or_die(&url, "%s/version/latest_version", globals.version_url);
ret = get_version_from_url(url);
string_or_die(&url, "%s/version/%s", globals.version_url, filename);
ret = get_version_from_url_inmemory(url);
FREE(url);

return ret;
Expand Down Expand Up @@ -329,15 +434,21 @@ bool get_distribution_string(char *path_prefix, char *dist)
return true;
}

enum swupd_code read_versions(int *current_version, int *server_version, char *path_prefix)
enum swupd_code read_versions(int *current_version, int *server_version, char *path_prefix, bool single_step)
{
*current_version = get_current_version(path_prefix);
*server_version = get_latest_version("");

if (*current_version < 0) {
error("Unable to determine current OS version\n");
return SWUPD_CURRENT_VERSION_UNKNOWN;
}

if (single_step) {
*server_version = get_version("", *current_version);
} else {
*server_version = get_latest_version("");
}

if (*server_version == -SWUPD_ERROR_SIGNATURE_VERIFICATION) {
error("Unable to determine the server version as signature verification failed\n");
return SWUPD_SIGNATURE_VERIFICATION_FAILED;
Expand Down
1 change: 1 addition & 0 deletions swupd.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ local -a update=(
'(-)'{-s,--status}'[Show current OS version and latest version available on server. Equivalent to "swupd check-update"]'
'(help -s --status -k --keepcache)'{-k,--keepcache}'[Do not delete the swupd state directory content after updating the system]'
'(help -s --status)--download[Download all content, but do not actually install the update]'
'(help -s --status)--incremental[Update to target by one release at a time instead of last release of each format]'
)
local update_official=(
'(help -s --status)--update-search-file-index[Update the index used by search-file to speed up searches]'
Expand Down
14 changes: 7 additions & 7 deletions test/functional/only_in_ci_system/update-no-disk-space.bats
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,23 @@ test_setup() {

}

@test "UPD045: Updating a system with no disk space left (downloading the current MoM)" {
@test "UPD045: Updating a system with no disk space left (fails to get the server version)" {

# When updating a system and we run out of disk space while downloading the
# MoMs we should not retry the download since it will fail for sure
# server version we should not retry the download since it will fail for sure

# fill up all the space in the disk
sudo dd if=/dev/zero of="$TEST_NAME"/testfs/dummy >& /dev/null || print "Using all space left in disk"

run sudo sh -c "timeout 30 $SWUPD update $SWUPD_OPTS"

assert_status_is "$SWUPD_COULDNT_LOAD_MOM"
assert_status_is "$SWUPD_SERVER_CONNECTION_ERROR"
expected_output=$(cat <<-EOM
Update started
Preparing to update from 10 to 20
Error: Curl - Error downloading to local file - 'file://$ABS_TEST_DIR/web-dir/10/Manifest.MoM.tar'
Error: Curl - Check free space for $ABS_TEST_DIR/testfs/state?
Error: Failed to retrieve 10 MoM manifest
Error: Curl - Cannot close file '$ABS_CACHE_DIR/download/latest' after writing - 'No space left on device'
Error: Curl - Error downloading to local file - 'file://$ABS_TEST_DIR/web-dir/version/formatstaging/latest'
Error: Curl - Check free space for $ABS_STATE_DIR?
Error: Unable to determine the server version
Update failed
EOM
)
Expand Down
2 changes: 2 additions & 0 deletions test/functional/signature/version-sig-check.bats
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ test_setup() {
No extra files need to be downloaded
Installing files...
Update was applied
Warning: THE SIGNATURE OF file://$ABS_TEST_DIR/web-dir/version/formatstaging/latest WILL NOT BE VERIFIED
Calling post-update helper scripts
Update successful - System updated from version 10 to version 20
EOM
Expand Down Expand Up @@ -127,6 +128,7 @@ test_setup() {
No extra files need to be downloaded
Installing files...
Update was applied
Warning: THE SIGNATURE OF file://$ABS_TEST_DIR/web-dir/version/formatstaging/latest WILL NOT BE VERIFIED
Calling post-update helper scripts
Update successful - System updated from version 10 to version 20
EOM
Expand Down
Loading

0 comments on commit 129e82b

Please sign in to comment.