mirror of
https://github.com/clearlinux/swupd-client.git
synced 2026-09-07 22:21:32 +00:00
Separate the main statedir clean functionality into a callable function and call it after performing an update. This helps clean up unused files that bloat the state directory on a regular basis and helps prevent the state directory from getting into a weird state over updates. Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
808 lines
24 KiB
C
808 lines
24 KiB
C
/*
|
|
* Software Updater - client side
|
|
*
|
|
* Copyright © 2012-2016 Intel Corporation.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, version 2 or later of the License.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Authors:
|
|
* Arjan van de Ven <arjan@linux.intel.com>
|
|
* Tim Pepper <timothy.c.pepper@linux.intel.com>
|
|
*
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <getopt.h>
|
|
#include <libgen.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "config.h"
|
|
#include "signature.h"
|
|
#include "swupd.h"
|
|
|
|
static int requested_version = -1;
|
|
|
|
int nonpack;
|
|
|
|
void increment_retries(int *retries, int *timeout)
|
|
{
|
|
(*retries)++;
|
|
sleep(*timeout);
|
|
*timeout *= 2;
|
|
}
|
|
|
|
/* This loads the upstream Clear Manifest.Full and local
|
|
* Manifest.full, and then checks that there are no conflicts between
|
|
* the files they both include */
|
|
static int check_manifests_uniqueness(int clrver, int mixver)
|
|
{
|
|
struct manifest *clear = load_manifest_full(clrver, false);
|
|
struct manifest *mixer = load_manifest_full(mixver, true);
|
|
if (!clear || !mixer) {
|
|
fprintf(stderr, "ERROR: Could not load full manifests\n");
|
|
return -1;
|
|
}
|
|
|
|
struct file **clearfull = manifest_files_to_array(clear);
|
|
struct file **mixerfull = manifest_files_to_array(mixer);
|
|
|
|
if (clearfull == NULL || mixerfull == NULL) {
|
|
fprintf(stderr, "Could not convert full manifest to array\n");
|
|
abort();
|
|
}
|
|
|
|
int ret = enforce_compliant_manifest(mixerfull, clearfull, mixer->filecount, clear->filecount);
|
|
|
|
free_manifest_array(clearfull);
|
|
free_manifest_array(mixerfull);
|
|
free_manifest(clear);
|
|
free_manifest(mixer);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int update_loop(struct list *updates, struct manifest *server_manifest)
|
|
{
|
|
int ret;
|
|
struct file *file;
|
|
struct list *iter;
|
|
|
|
ret = download_fullfiles(updates, MAX_TRIES, 10);
|
|
if (ret) {
|
|
fprintf(stderr, "ERROR: Could not download all files, aborting update\n");
|
|
}
|
|
|
|
if (download_only) {
|
|
return 0;
|
|
}
|
|
|
|
/*********** rootfs critical section starts ***************************
|
|
NOTE: the next loop calls do_staging() which can remove files, starting a critical section
|
|
which ends after rename_all_files_to_final() succeeds
|
|
*/
|
|
|
|
/* from here onward we're doing real update work modifying "the disk" */
|
|
|
|
/* starting at list_head in the filename alpha-sorted updates list
|
|
* means node directories are added before leaf files */
|
|
fprintf(stderr, "Staging file content\n");
|
|
iter = list_head(updates);
|
|
while (iter) {
|
|
file = iter->data;
|
|
iter = iter->next;
|
|
|
|
if (file->do_not_update || file->is_deleted) {
|
|
continue;
|
|
}
|
|
|
|
/* for each file: fdatasync to persist changed content over reboot, or maybe a global sync */
|
|
/* for each file: check hash value; on mismatch delete and queue full download */
|
|
/* todo: hash check */
|
|
|
|
ret = do_staging(file, server_manifest);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "File staging failed: %s\n", file->filename);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* check policy, and if policy says, "ask", ask the user at this point */
|
|
/* check for reboot need - if needed, wait for reboot */
|
|
|
|
/* sync */
|
|
sync();
|
|
|
|
/* rename to apply update */
|
|
printf("Applying update\n");
|
|
ret = rename_all_files_to_final(updates);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* TODO: do we need to optimize directory-permission-only changes (directories
|
|
* are now sent as tar's so permissions are handled correctly, even
|
|
* if less than efficiently)? */
|
|
|
|
sync();
|
|
|
|
/* NOTE: critical section starts when update_loop() calls do_staging() */
|
|
/*********** critical section ends *************************************/
|
|
|
|
return ret;
|
|
}
|
|
|
|
int add_included_manifests(struct manifest *mom, int current, struct list **subs)
|
|
{
|
|
struct list *subbed = NULL;
|
|
struct list *iter;
|
|
int ret;
|
|
|
|
iter = list_head(*subs);
|
|
while (iter) {
|
|
subbed = list_prepend_data(subbed, ((struct sub *)iter->data)->component);
|
|
iter = iter->next;
|
|
}
|
|
|
|
/* Pass the current version here, not the new, otherwise we will never
|
|
* hit the Manifest delta path. */
|
|
if (add_subscriptions(subbed, subs, current, mom, false, 0) >= 0) {
|
|
ret = 0;
|
|
} else {
|
|
ret = -1;
|
|
}
|
|
list_free_list(subbed);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int re_exec_update(bool versions_match)
|
|
{
|
|
if (!versions_match) {
|
|
fprintf(stderr, "ERROR: Inconsistency between version files, exiting now.\n");
|
|
return 1;
|
|
}
|
|
|
|
if (!swupd_cmd) {
|
|
fprintf(stderr, "ERROR: Unable to determine re-update command, exiting now.\n");
|
|
return 1;
|
|
}
|
|
|
|
/* Run the swupd_cmd saved from main */
|
|
if (system(swupd_cmd) != 0) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool need_new_upstream(int server)
|
|
{
|
|
if (!access(MIX_DIR "upstreamversion", R_OK)) {
|
|
int version = read_mix_version_file(MIX_DIR "upstreamversion", path_prefix);
|
|
if (version < server) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static int main_update()
|
|
{
|
|
int current_version = -1, server_version = -1;
|
|
int mix_current_version = -1, mix_server_version = -1;
|
|
struct manifest *current_manifest = NULL, *server_manifest = NULL;
|
|
struct list *updates = NULL;
|
|
struct list *current_subs = NULL;
|
|
struct list *latest_subs = NULL;
|
|
int ret;
|
|
int lock_fd;
|
|
int retries = 0;
|
|
int timeout = 10;
|
|
struct timespec ts_start, ts_stop; // For main swupd update time
|
|
timelist times;
|
|
double delta;
|
|
bool mix_exists;
|
|
bool re_update = false;
|
|
bool versions_match = false;
|
|
|
|
srand(time(NULL));
|
|
|
|
ret = swupd_init(&lock_fd);
|
|
if (ret != 0) {
|
|
/* being here means we already close log by a previously caught error */
|
|
fprintf(stderr, "Updater failed to initialize, exiting now.\n");
|
|
return ret;
|
|
}
|
|
|
|
times = init_timelist();
|
|
|
|
clock_gettime(CLOCK_MONOTONIC_RAW, &ts_start);
|
|
grabtime_start(×, "Main Update");
|
|
|
|
ret = swupd_curl_check_network();
|
|
if (ret) {
|
|
fprintf(stderr, "Error: Network issue, unable to proceed with update\n");
|
|
goto clean_curl;
|
|
}
|
|
|
|
mix_exists = check_mix_exists();
|
|
|
|
fprintf(stderr, "Update started.\n");
|
|
|
|
grabtime_start(×, "Update Step 1: get versions");
|
|
|
|
read_subscriptions(¤t_subs);
|
|
|
|
handle_mirror_if_stale();
|
|
|
|
/* Step 1: get versions */
|
|
version_check:
|
|
ret = check_versions(¤t_version, &server_version, requested_version, path_prefix);
|
|
|
|
if (ret < 0) {
|
|
ret = EXIT_FAILURE;
|
|
goto clean_curl;
|
|
}
|
|
|
|
if (mix_exists) {
|
|
check_mix_versions(&mix_current_version, &mix_server_version, path_prefix);
|
|
if (mix_current_version == -1 || mix_server_version == -1) {
|
|
ret = EXIT_FAILURE;
|
|
goto clean_curl;
|
|
}
|
|
/* Check if a new upstream version is available so we can update to it still */
|
|
if (need_new_upstream(server_version)) {
|
|
printf("NEW CLEAR AVAILABLE %d\n", server_version);
|
|
/* Update the upstreamversion that will be used to generate the new mix content */
|
|
FILE *verfile = fopen(MIX_DIR "upstreamversion", "w+");
|
|
if (!verfile) {
|
|
fprintf(stderr, "ERROR: fopen() %s/upstreamversion returned %s\n", MIX_DIR, strerror(errno));
|
|
} else {
|
|
fprintf(verfile, "%d", server_version);
|
|
fclose(verfile);
|
|
}
|
|
|
|
if (system("/usr/bin/mixin build") != 0) {
|
|
fprintf(stderr, "ERROR: Could not execute mixin\n");
|
|
ret = EXIT_FAILURE;
|
|
goto clean_curl;
|
|
}
|
|
|
|
// new mix version
|
|
check_mix_versions(&mix_current_version, &mix_server_version, path_prefix);
|
|
ret = check_manifests_uniqueness(server_version, mix_server_version);
|
|
if (ret) {
|
|
printf("\n\t!! %i collisions were found between mix and upstream, please re-create mix !!\n", ret);
|
|
if (!allow_mix_collisions) {
|
|
ret = EXIT_FAILURE;
|
|
goto clean_curl;
|
|
}
|
|
}
|
|
|
|
goto version_check;
|
|
}
|
|
current_version = mix_current_version;
|
|
server_version = mix_server_version;
|
|
}
|
|
|
|
if (server_version <= current_version) {
|
|
fprintf(stderr, "Version on server (%i) is not newer than system version (%i)\n", server_version, current_version);
|
|
ret = EXIT_SUCCESS;
|
|
goto clean_curl;
|
|
}
|
|
|
|
fprintf(stderr, "Preparing to update from %i to %i\n", current_version, server_version);
|
|
/* Step 2: housekeeping */
|
|
|
|
if (rm_staging_dir_contents("download")) {
|
|
fprintf(stderr, "Error cleaning download directory\n");
|
|
ret = EXIT_FAILURE;
|
|
goto clean_curl;
|
|
}
|
|
grabtime_stop(×);
|
|
grabtime_stop(×); // Close step 1
|
|
grabtime_start(×, "Load Manifests:");
|
|
load_current_mom:
|
|
/* Step 3: setup manifests */
|
|
|
|
/* get the from/to MoM manifests */
|
|
if (system_on_mix()) {
|
|
current_manifest = load_mom(current_version, false, mix_exists);
|
|
} else {
|
|
current_manifest = load_mom(current_version, false, false);
|
|
}
|
|
if (!current_manifest) {
|
|
/* TODO: possibly remove this as not getting a "from" manifest is not fatal
|
|
* - we just don't apply deltas */
|
|
if (retries < MAX_TRIES) {
|
|
increment_retries(&retries, &timeout);
|
|
fprintf(stderr, "Retry #%d downloading from/to MoM Manifests\n", retries);
|
|
goto load_current_mom;
|
|
}
|
|
fprintf(stderr, "Failure retrieving manifest from server\n");
|
|
ret = EMOM_NOTFOUND;
|
|
goto clean_exit;
|
|
}
|
|
|
|
/* Reset the retries and timeout for subsequent download calls */
|
|
retries = 0;
|
|
timeout = 10;
|
|
|
|
load_server_mom:
|
|
grabtime_stop(×); // Close step 2
|
|
grabtime_start(×, "Recurse and Consolidate Manifests");
|
|
int server_manifest_err;
|
|
server_manifest = load_mom_err(server_version, true, mix_exists, &server_manifest_err);
|
|
if (!server_manifest) {
|
|
if (retries < MAX_TRIES && server_manifest_err != -ENET404) {
|
|
increment_retries(&retries, &timeout);
|
|
fprintf(stderr, "Retry #%d downloading server Manifests\n", retries);
|
|
goto load_server_mom;
|
|
}
|
|
fprintf(stderr, "Failure retrieving manifest from server\n");
|
|
if (server_manifest_err == -ENET404) {
|
|
fprintf(stderr, "Version %d not available\n", server_version);
|
|
} else {
|
|
fprintf(stderr, "Unable to load manifest after retrying (config or network problem?)\n");
|
|
}
|
|
ret = EMOM_NOTFOUND;
|
|
goto clean_exit;
|
|
}
|
|
|
|
retries = 0;
|
|
timeout = 10;
|
|
|
|
load_current_submanifests:
|
|
/* Read the current collective of manifests that we are subscribed to.
|
|
* First load up the old (current) manifests. Statedir could have been cleared
|
|
* or corrupt, so don't assume things are already there. Updating subscribed
|
|
* manifests is done as part of recurse_manifest */
|
|
current_manifest->submanifests = recurse_manifest(current_manifest, current_subs, NULL, false);
|
|
if (!current_manifest->submanifests) {
|
|
if (retries < MAX_TRIES) {
|
|
increment_retries(&retries, &timeout);
|
|
fprintf(stderr, "Retry #%d downloading current sub-manifests\n", retries);
|
|
goto load_current_submanifests;
|
|
}
|
|
ret = ERECURSE_MANIFEST;
|
|
printf("Cannot load current MoM sub-manifests, exiting\n");
|
|
goto clean_exit;
|
|
}
|
|
retries = 0;
|
|
timeout = 10;
|
|
|
|
/* consolidate the current collective manifests down into one in memory */
|
|
current_manifest->files = files_from_bundles(current_manifest->submanifests);
|
|
|
|
current_manifest->files = consolidate_files(current_manifest->files);
|
|
|
|
latest_subs = list_clone(current_subs);
|
|
set_subscription_versions(server_manifest, current_manifest, &latest_subs);
|
|
link_submanifests(current_manifest, server_manifest, current_subs, latest_subs, false);
|
|
|
|
/* The new subscription is seeded from the list of currently installed bundles
|
|
* This calls add_subscriptions which recurses for new includes */
|
|
grabtime_start(×, "Add Included Manifests");
|
|
ret = add_included_manifests(server_manifest, current_version, &latest_subs);
|
|
grabtime_stop(×);
|
|
if (ret) {
|
|
ret = EMANIFEST_LOAD;
|
|
goto clean_exit;
|
|
}
|
|
|
|
load_server_submanifests:
|
|
/* read the new collective of manifests that we are subscribed to in the new MoM */
|
|
server_manifest->submanifests = recurse_manifest(server_manifest, latest_subs, NULL, false);
|
|
if (!server_manifest->submanifests) {
|
|
if (retries < MAX_TRIES) {
|
|
increment_retries(&retries, &timeout);
|
|
printf("Retry #%d downloading server sub-manifests\n", retries);
|
|
goto load_server_submanifests;
|
|
}
|
|
ret = ERECURSE_MANIFEST;
|
|
printf("Error: Cannot load server MoM sub-manifests, exiting\n");
|
|
goto clean_exit;
|
|
}
|
|
retries = 0;
|
|
timeout = 10;
|
|
/* consolidate the new collective manifests down into one in memory */
|
|
server_manifest->files = files_from_bundles(server_manifest->submanifests);
|
|
|
|
server_manifest->files = consolidate_files(server_manifest->files);
|
|
|
|
set_subscription_versions(server_manifest, current_manifest, &latest_subs);
|
|
link_submanifests(current_manifest, server_manifest, current_subs, latest_subs, true);
|
|
|
|
/* prepare for an update process based on comparing two in memory manifests */
|
|
link_manifests(current_manifest, server_manifest);
|
|
grabtime_stop(×);
|
|
/* Step 4: check disk state before attempting update */
|
|
grabtime_start(×, "Pre-Update Scripts");
|
|
run_preupdate_scripts(server_manifest);
|
|
grabtime_stop(×);
|
|
|
|
grabtime_start(×, "Download Packs");
|
|
|
|
download_packs:
|
|
/* Step 5: get the packs and untar */
|
|
ret = download_subscribed_packs(latest_subs, server_manifest, false, true);
|
|
if (ret) {
|
|
// packs don't always exist, tolerate that but not ENONET
|
|
if (retries < MAX_TRIES) {
|
|
increment_retries(&retries, &timeout);
|
|
printf("Retry #%d downloading packs\n", retries);
|
|
goto download_packs;
|
|
}
|
|
printf("Pack download failed\n");
|
|
ret = EDOWNLOADPACKS;
|
|
goto clean_exit;
|
|
}
|
|
grabtime_stop(×);
|
|
|
|
grabtime_start(×, "Apply deltas");
|
|
apply_deltas(current_manifest);
|
|
grabtime_stop(×);
|
|
|
|
/* Step 6: some more housekeeping */
|
|
/* TODO: consider trying to do less sorting of manifests */
|
|
|
|
grabtime_start(×, "Create Update List");
|
|
|
|
updates = create_update_list(server_manifest);
|
|
|
|
print_statistics(current_version, server_version);
|
|
grabtime_stop(×);
|
|
/* Step 7: apply the update */
|
|
|
|
/*
|
|
* need update list in filename order to insure directories are
|
|
* created before their contents
|
|
*/
|
|
grabtime_start(×, "Update Loop");
|
|
updates = list_sort(updates, file_sort_filename);
|
|
|
|
ret = update_loop(updates, server_manifest);
|
|
if (ret == 0 && !download_only) {
|
|
/* Failure to write the version file in the state directory
|
|
* should not affect exit status. */
|
|
(void)update_device_latest_version(server_version);
|
|
printf("Update was applied.\n");
|
|
} else if (ret < 0) {
|
|
// Ensure a positive exit status for the main program.
|
|
ret = -ret;
|
|
}
|
|
|
|
delete_motd();
|
|
grabtime_stop(×);
|
|
/* Run any scripts that are needed to complete update */
|
|
grabtime_start(×, "Run Scripts");
|
|
|
|
/* Determine if another update is needed so the scripts block */
|
|
int new_current_version = get_current_version(path_prefix);
|
|
if (on_new_format() && (requested_version == -1 || (requested_version > new_current_version))) {
|
|
re_update = true;
|
|
}
|
|
|
|
run_scripts(re_update);
|
|
grabtime_stop(×);
|
|
|
|
/* Create the state file that will tell swupd it's on a mix on future runs */
|
|
if (mix_exists && !system_on_mix()) {
|
|
int fd = open(MIXED_FILE, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
if (fd == -1) {
|
|
fprintf(stderr, "ERROR: Failed to create 'mixed' statefile\n");
|
|
ret = -1;
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
clean_exit:
|
|
list_free_list(updates);
|
|
free_manifest(current_manifest);
|
|
free_manifest(server_manifest);
|
|
|
|
clean_curl:
|
|
grabtime_stop(×);
|
|
clock_gettime(CLOCK_MONOTONIC_RAW, &ts_stop);
|
|
delta = ts_stop.tv_sec - ts_start.tv_sec + ts_stop.tv_nsec / 1000000000.0 - ts_start.tv_nsec / 1000000000.0;
|
|
telemetry(ret ? TELEMETRY_CRIT : TELEMETRY_INFO,
|
|
"update",
|
|
"current_version=%d\n"
|
|
"server_version=%d\n"
|
|
"result=%d\n"
|
|
"time=%5.1f\n",
|
|
current_version,
|
|
server_version,
|
|
ret,
|
|
delta);
|
|
|
|
if (server_version > current_version) {
|
|
printf("Update took %0.1f seconds\n", delta);
|
|
}
|
|
print_time_stats(×);
|
|
|
|
/* version_files_match must be done before swupd_deinit to use globals */
|
|
if (re_update) {
|
|
versions_match = version_files_consistent();
|
|
}
|
|
if (latest_subs) {
|
|
list_free_list(current_subs);
|
|
} else {
|
|
free_subscriptions(¤t_subs);
|
|
}
|
|
/* clean up our cached content from the update. It is likely much more than
|
|
* we need and the clean helps us prevent cache bloat. */
|
|
clean_statedir(false, false);
|
|
swupd_deinit(lock_fd, &latest_subs);
|
|
|
|
if (nonpack > 0) {
|
|
printf("%i files were not in a pack\n", nonpack);
|
|
}
|
|
|
|
if (!download_only) {
|
|
if ((current_version < server_version) && (ret == 0)) {
|
|
printf("Update successful. System updated from version %d to version %d\n",
|
|
current_version, server_version);
|
|
} else if (ret == 0) {
|
|
printf("Update complete. System already up-to-date at version %d\n", current_version);
|
|
}
|
|
}
|
|
|
|
if (re_update && ret == 0) {
|
|
ret = re_exec_update(versions_match);
|
|
}
|
|
|
|
/* free swupd_cmd now that is no longer needed */
|
|
free_string(&swupd_cmd);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool cmd_line_status = false;
|
|
|
|
static const struct option prog_opts[] = {
|
|
{ "download", no_argument, 0, 'd' },
|
|
{ "help", no_argument, 0, 'h' },
|
|
{ "manifest", required_argument, 0, 'm' },
|
|
{ "url", required_argument, 0, 'u' },
|
|
{ "port", required_argument, 0, 'P' },
|
|
{ "contenturl", required_argument, 0, 'c' },
|
|
{ "versionurl", required_argument, 0, 'v' },
|
|
{ "status", no_argument, 0, 's' },
|
|
{ "format", required_argument, 0, 'F' },
|
|
{ "path", required_argument, 0, 'p' },
|
|
{ "force", no_argument, 0, 'x' },
|
|
{ "nosigcheck", no_argument, 0, 'n' },
|
|
{ "ignore-time", no_argument, 0, 'I' },
|
|
{ "statedir", required_argument, 0, 'S' },
|
|
{ "certpath", required_argument, 0, 'C' },
|
|
{ "time", no_argument, 0, 't' },
|
|
{ "no-scripts", no_argument, 0, 'N' },
|
|
{ "no-boot-update", no_argument, 0, 'b' },
|
|
{ "migrate", no_argument, 0, 'T' },
|
|
{ "allow-mix-collisions", no_argument, 0, 'a' },
|
|
{ 0, 0, 0, 0 }
|
|
};
|
|
|
|
static void print_help(const char *name)
|
|
{
|
|
fprintf(stderr, "Usage:\n");
|
|
fprintf(stderr, " swupd %s [OPTION...]\n\n", basename((char *)name));
|
|
fprintf(stderr, "Help Options:\n");
|
|
fprintf(stderr, " -h, --help Show help options\n\n");
|
|
fprintf(stderr, "Application Options:\n");
|
|
fprintf(stderr, " -m, --manifest=M Update to version M, also accepts 'latest' (default)\n");
|
|
fprintf(stderr, " -d, --download Download all content, but do not actually install the update\n");
|
|
//TODO: "remove user configurable url when alternative exists"
|
|
fprintf(stderr, " -u, --url=[URL] RFC-3986 encoded url for version string and content file downloads\n");
|
|
fprintf(stderr, " -P, --port=[port #] Port number to connect to at the url for version string and content file downloads\n");
|
|
//TODO: "remove user configurable content url when alternative exists"
|
|
fprintf(stderr, " -c, --contenturl=[URL] RFC-3986 encoded url for content file downloads\n");
|
|
//TODO: "remove user configurable version url when alternative exists"
|
|
fprintf(stderr, " -v, --versionurl=[URL] RFC-3986 encoded url for version string download\n");
|
|
fprintf(stderr, " -s, --status Show current OS version and latest version available on server\n");
|
|
fprintf(stderr, " -F, --format=[staging,1,2,etc.] the format suffix for version file downloads\n");
|
|
fprintf(stderr, " -p, --path=[PATH...] Use [PATH...] as the path to verify (eg: a chroot or btrfs subvol\n");
|
|
fprintf(stderr, " -x, --force Attempt to proceed even if non-critical errors found\n");
|
|
fprintf(stderr, " -n, --nosigcheck Do not attempt to enforce certificate or signature checking\n");
|
|
fprintf(stderr, " -I, --ignore-time Ignore system/certificate time when validating signature\n");
|
|
fprintf(stderr, " -S, --statedir Specify alternate swupd state directory\n");
|
|
fprintf(stderr, " -C, --certpath Specify alternate path to swupd certificates\n");
|
|
fprintf(stderr, " -t, --time Show verbose time output for swupd operations\n");
|
|
fprintf(stderr, " -N, --no-scripts Do not run the post-update scripts and boot update tool\n");
|
|
fprintf(stderr, " -b, --no-boot-update Do not update the boot files using clr-boot-manager\n");
|
|
fprintf(stderr, " -T, --migrate Migrate to augmented upstream/mix content\n");
|
|
fprintf(stderr, " -a, --allow-mix-collisions Ignore and continue if custom user content conflicts with upstream provided content\n");
|
|
fprintf(stderr, "\n");
|
|
}
|
|
|
|
static bool parse_options(int argc, char **argv)
|
|
{
|
|
int opt;
|
|
|
|
while ((opt = getopt_long(argc, argv, "hm:xnIdtNbTau:P:c:v:sF:p:S:C:", prog_opts, NULL)) != -1) {
|
|
switch (opt) {
|
|
case '?':
|
|
case 'h':
|
|
print_help(argv[0]);
|
|
exit(EXIT_SUCCESS);
|
|
case 'm':
|
|
if (strcmp("latest", optarg) == 0) {
|
|
requested_version = -1;
|
|
} else if (sscanf(optarg, "%i", &requested_version) != 1) {
|
|
fprintf(stderr, "Invalid --manifest argument\n\n");
|
|
goto err;
|
|
}
|
|
break;
|
|
case 'a':
|
|
allow_mix_collisions = true;
|
|
break;
|
|
case 'd':
|
|
download_only = true;
|
|
break;
|
|
case 'u':
|
|
if (!optarg) {
|
|
fprintf(stderr, "Invalid --url argument\n\n");
|
|
goto err;
|
|
}
|
|
set_version_url(optarg);
|
|
set_content_url(optarg);
|
|
break;
|
|
case 't':
|
|
verbose_time = true;
|
|
break;
|
|
case 'N':
|
|
no_scripts = true;
|
|
break;
|
|
case 'b':
|
|
no_boot_update = true;
|
|
break;
|
|
case 'P':
|
|
if (sscanf(optarg, "%ld", &update_server_port) != 1) {
|
|
fprintf(stderr, "Invalid --port argument\n\n");
|
|
goto err;
|
|
}
|
|
break;
|
|
case 'c':
|
|
if (!optarg) {
|
|
fprintf(stderr, "Invalid --contenturl argument\n\n");
|
|
goto err;
|
|
}
|
|
set_content_url(optarg);
|
|
break;
|
|
case 'v':
|
|
if (!optarg) {
|
|
fprintf(stderr, "Invalid --versionurl argument\n\n");
|
|
goto err;
|
|
}
|
|
set_version_url(optarg);
|
|
break;
|
|
case 's':
|
|
cmd_line_status = true;
|
|
break;
|
|
case 'F':
|
|
if (!optarg || !set_format_string(optarg)) {
|
|
fprintf(stderr, "Invalid --format argument\n\n");
|
|
goto err;
|
|
}
|
|
break;
|
|
case 'S':
|
|
if (!optarg || !set_state_dir(optarg)) {
|
|
fprintf(stderr, "Invalid --statedir argument\n\n");
|
|
goto err;
|
|
}
|
|
break;
|
|
case 'p': /* default empty path_prefix updates the running OS */
|
|
if (!optarg || !set_path_prefix(optarg)) {
|
|
fprintf(stderr, "Invalid --path argument\n\n");
|
|
goto err;
|
|
}
|
|
break;
|
|
case 'x':
|
|
force = true;
|
|
break;
|
|
case 'T':
|
|
migrate = true;
|
|
fprintf(stderr, "Attempting to migrate to your mix content...\n\n");
|
|
break;
|
|
case 'n':
|
|
sigcheck = false;
|
|
break;
|
|
case 'I':
|
|
timecheck = false;
|
|
break;
|
|
case 'C':
|
|
if (!optarg) {
|
|
fprintf(stderr, "Invalid --certpath argument\n\n");
|
|
goto err;
|
|
}
|
|
set_cert_path(optarg);
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Unrecognized option\n\n");
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
err:
|
|
print_help(argv[0]);
|
|
return false;
|
|
}
|
|
|
|
/* return 0 if server_version is ahead of current_version,
|
|
* 1 if the current_version is current or ahead
|
|
* 2 if one or more versions can't be found
|
|
*/
|
|
static int print_versions()
|
|
{
|
|
int current_version, server_version, ret = 0;
|
|
|
|
check_root();
|
|
(void)init_globals();
|
|
swupd_curl_init();
|
|
|
|
read_versions(¤t_version, &server_version, path_prefix);
|
|
|
|
if (current_version < 0) {
|
|
fprintf(stderr, "Cannot determine current OS version\n");
|
|
ret = 2;
|
|
} else {
|
|
fprintf(stderr, "Current OS version: %d\n", current_version);
|
|
}
|
|
|
|
if (server_version < 0) {
|
|
fprintf(stderr, "Cannot get the latest server version. Could not reach server\n");
|
|
ret = 2;
|
|
} else {
|
|
fprintf(stderr, "Latest server version: %d\n", server_version);
|
|
}
|
|
if ((ret == 0) && (server_version <= current_version)) {
|
|
ret = 1;
|
|
}
|
|
|
|
telemetry(ret ? TELEMETRY_WARN : TELEMETRY_INFO,
|
|
"check",
|
|
"result=%d\n"
|
|
"version_current=%d\n"
|
|
"version_server=%d\n",
|
|
ret,
|
|
current_version,
|
|
server_version);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int update_main(int argc, char **argv)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!parse_options(argc, argv)) {
|
|
return EINVALID_OPTION;
|
|
}
|
|
|
|
if (cmd_line_status) {
|
|
ret = print_versions();
|
|
} else {
|
|
ret = main_update();
|
|
}
|
|
return ret;
|
|
}
|