mirror of
https://github.com/clearlinux/swupd-client.git
synced 2026-09-08 14:42:02 +00:00
Initial commit
Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
This commit is contained in:
+465
@@ -0,0 +1,465 @@
|
||||
/*
|
||||
* Software Updater - client side
|
||||
*
|
||||
* Copyright (c) 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:
|
||||
* Jaime A. Garcia <jaime.garcia.naranjo@linux.intel.com>
|
||||
* Tim Pepper <timothy.c.pepper@linux.intel.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "swupd.h"
|
||||
|
||||
#define MODE_RW_O (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
|
||||
|
||||
/*
|
||||
* list_installable_bundles()
|
||||
* Parse the full manifest for the current version of the OS and print
|
||||
* all available bundles.
|
||||
*/
|
||||
int list_installable_bundles()
|
||||
{
|
||||
struct list *list;
|
||||
struct file *file;
|
||||
struct manifest *MoM = NULL;
|
||||
int current_version;
|
||||
int lock_fd;
|
||||
int ret;
|
||||
|
||||
if (!init_globals()) {
|
||||
return EINIT_GLOBALS;
|
||||
}
|
||||
|
||||
current_version = read_version_from_subvol_file(path_prefix);
|
||||
|
||||
if (swupd_init(&lock_fd) != 0) {
|
||||
printf("Error: Failed updater initialization. Exiting now\n");
|
||||
return ECURL_INIT;
|
||||
}
|
||||
|
||||
ret = create_required_dirs();
|
||||
if (ret != 0) {
|
||||
printf("State directory %s cannot be recreated, aborting removal\n", STATE_DIR);
|
||||
v_lockfile(lock_fd);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
get_mounted_directories();
|
||||
|
||||
if (!check_network()) {
|
||||
printf("Error: Network issue, unable to download manifest\n");
|
||||
v_lockfile(lock_fd);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
swupd_curl_set_current_version(current_version);
|
||||
|
||||
ret = load_manifests(current_version, current_version, "MoM", NULL, &MoM);
|
||||
if (ret != 0) {
|
||||
v_lockfile(lock_fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
list = MoM->manifests;
|
||||
while (list) {
|
||||
file = list->data;
|
||||
list = list->next;
|
||||
printf("%s\n", file->filename);
|
||||
}
|
||||
|
||||
free_manifest(MoM);
|
||||
v_lockfile(lock_fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* bundle_name: this is the name for the bundle we want to be loaded
|
||||
* version: this is the MoM version from which we pull last changed for bundle manifest
|
||||
* submanifest: where bundle manifest is going to be loaded
|
||||
*
|
||||
* Basically we read MoM version then get the submanifest only for our bundle (component)
|
||||
* put it into submanifest pointer, then dispose MoM data.
|
||||
*/
|
||||
static int load_bundle_manifest(const char *bundle_name, int version, struct manifest **submanifest)
|
||||
{
|
||||
struct list *sub_list = NULL;
|
||||
struct manifest *mom = NULL;
|
||||
int ret = 0;
|
||||
|
||||
*submanifest = NULL;
|
||||
|
||||
swupd_curl_set_current_version(version);
|
||||
ret = load_manifests(version, version, "MoM", NULL, &mom);
|
||||
if (ret != 0) {
|
||||
ret = EMOM_NOTFOUND;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = recurse_manifest(mom, bundle_name);
|
||||
if (ret != 0) {
|
||||
ret = ERECURSE_MANIFEST;
|
||||
goto free_out;
|
||||
}
|
||||
|
||||
sub_list = list_head(mom->submanifests);
|
||||
if (sub_list != NULL) {
|
||||
*submanifest = sub_list->data;
|
||||
sub_list->data = NULL;
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
free_out:
|
||||
free_manifest(mom);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Finds out whether bundle_name is tracked bundle on
|
||||
* current system.
|
||||
*/
|
||||
static bool is_tracked_bundle(const char *bundle_name)
|
||||
{
|
||||
struct stat statb;
|
||||
char *filename = NULL;
|
||||
bool ret = true;
|
||||
|
||||
string_or_die(&filename, "%s/%s/%s", path_prefix, BUNDLES_DIR, bundle_name);
|
||||
|
||||
if (stat(filename, &statb) == -1) {
|
||||
ret = false;
|
||||
}
|
||||
|
||||
free(filename);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* When loading all tracked bundles into memory, they happen
|
||||
* to be hold in subs global var, for some reasons it is
|
||||
* needed to just pop out one or more of this loaded tracked
|
||||
* bundles, this function search for bundle_name into subs
|
||||
* struct and if it found then free it from the list.
|
||||
*/
|
||||
static int unload_tracked_bundle(const char *bundle_name)
|
||||
{
|
||||
struct list *bundles;
|
||||
struct list *cur_item;
|
||||
struct sub *bundle;
|
||||
|
||||
bundles = list_head(subs);
|
||||
while (bundles) {
|
||||
bundle = bundles->data;
|
||||
cur_item = bundles;
|
||||
bundles = bundles->next;
|
||||
if (strcmp(bundle->component, bundle_name) == 0) {
|
||||
/* unlink (aka untrack) matching bundle name from tracked ones */
|
||||
subs = free_bundle(cur_item);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
return EBUNDLE_NOT_TRACKED;
|
||||
}
|
||||
|
||||
/* touch bundle filename in system bundles directory,
|
||||
* this is called after bundle installation to make sure bundle is kept tracked
|
||||
*/
|
||||
static int track_bundle_in_system(char *bundle)
|
||||
{
|
||||
char *filename;
|
||||
int f;
|
||||
int ret = 0;
|
||||
|
||||
string_or_die(&filename, "%s/%s/%s", path_prefix, BUNDLES_DIR, bundle);
|
||||
|
||||
f = open(filename, O_WRONLY | O_CREAT | O_NONBLOCK | O_NOCTTY, MODE_RW_O);
|
||||
if (f < 0) {
|
||||
ret = EBUNDLE_NOT_TRACKED;
|
||||
} else {
|
||||
close(f);
|
||||
}
|
||||
|
||||
free(filename);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* This function is a fresh new implementation for a bundle
|
||||
* remove without being tied to verify loop, this means
|
||||
* improved speed and space as well as more roubustness and
|
||||
* flexibility. What it does is basically:
|
||||
*
|
||||
* 1) Read MoM and load all submanifests except the one to be
|
||||
* removed and then consolidate them.
|
||||
* 2) Load the removed bundle submanifest.
|
||||
* 3) Order the file list by filename
|
||||
* 4) Deduplicate removed submanifest file list that happens
|
||||
* to be on the MoM (minus bundle to be removed).
|
||||
* 5) iterate over to be removed bundle submanifest file list
|
||||
* performing a unlink(2) for each filename.
|
||||
* 6) Done.
|
||||
*/
|
||||
int remove_bundle(const char *bundle_name)
|
||||
{
|
||||
int lock_fd;
|
||||
int ret = 0;
|
||||
int current_version = CURRENT_OS_VERSION;
|
||||
struct manifest *current_mom, *bundle_manifest;
|
||||
|
||||
/* Initially we don't support format nor path_prefix
|
||||
* for bundle_rm but eventually that will be added, then
|
||||
* set_format_string() and init_globals() must be pulled out
|
||||
* to the caller to properly initialize in case those opts
|
||||
* passed to the command.
|
||||
*/
|
||||
set_format_string(NULL);
|
||||
if (!init_globals()) {
|
||||
return EINIT_GLOBALS;
|
||||
}
|
||||
|
||||
ret = swupd_init(&lock_fd);
|
||||
if (ret != 0) {
|
||||
printf("Failed updater initialization, exiting now.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* os-core bundle not allowed to be removed...
|
||||
* although this is going to be caught later because of all files
|
||||
* being marked as 'duplicated' and note removing anything
|
||||
* anyways, better catch here and return success, no extra work to be done.
|
||||
*/
|
||||
if (strcmp(bundle_name, "os-core") == 0) {
|
||||
ret = EBUNDLE_NOT_TRACKED;
|
||||
goto out_free_curl;
|
||||
}
|
||||
|
||||
if (!is_tracked_bundle(bundle_name)) {
|
||||
ret = EBUNDLE_NOT_TRACKED;
|
||||
goto out_free_curl;
|
||||
}
|
||||
|
||||
current_version = read_version_from_subvol_file(path_prefix);
|
||||
swupd_curl_set_current_version(current_version);
|
||||
|
||||
/* first of all, make sure STATE_DIR is there, recreate if necessary*/
|
||||
ret = create_required_dirs();
|
||||
if (ret != 0) {
|
||||
printf("State directory %s cannot be recreated, aborting removal\n", STATE_DIR);
|
||||
goto out_free_curl;
|
||||
}
|
||||
|
||||
|
||||
ret = load_manifests(current_version, current_version, "MoM", NULL, ¤t_mom);
|
||||
if (ret != 0) {
|
||||
goto out_free_curl;
|
||||
}
|
||||
|
||||
/* load all tracked bundles into memory */
|
||||
read_subscriptions_alt();
|
||||
/* now popout the one to be removed */
|
||||
ret = unload_tracked_bundle(bundle_name);
|
||||
if (ret != 0) {
|
||||
goto out_free_mom;
|
||||
}
|
||||
|
||||
subscription_versions_from_MoM(current_mom, 0);
|
||||
/* load all submanifest minus the one to be removed */
|
||||
recurse_manifest(current_mom, NULL);
|
||||
consolidate_submanifests(current_mom);
|
||||
|
||||
/* Now that we have the consolidated list of all files, load bundle to be removed submanifest*/
|
||||
ret = load_bundle_manifest(bundle_name, current_version, &bundle_manifest);
|
||||
if (ret != 0) {
|
||||
goto out_free_mom;
|
||||
}
|
||||
|
||||
/* deduplication needs file list sorted by filename, do so */
|
||||
bundle_manifest->files = list_sort(bundle_manifest->files, file_sort_filename);
|
||||
deduplicate_files_from_manifest(&bundle_manifest, current_mom);
|
||||
|
||||
printf("Deleting bundle files...\n");
|
||||
remove_files_in_manifest_from_fs(bundle_manifest);
|
||||
|
||||
printf("Untracking bundle from system...\n");
|
||||
rm_bundle_file(bundle_name);
|
||||
|
||||
printf("Success: Bundle removed\n");
|
||||
|
||||
free_manifest(bundle_manifest);
|
||||
out_free_mom:
|
||||
free_manifest(current_mom);
|
||||
out_free_curl:
|
||||
|
||||
if (ret) {
|
||||
printf("Error: Bundle remove failed\n");
|
||||
}
|
||||
|
||||
swupd_curl_cleanup();
|
||||
v_lockfile(lock_fd);
|
||||
dump_file_descriptor_leaks();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Bundle install one ore more bundles passed in bundles
|
||||
* param as a null terminated array of strings
|
||||
*/
|
||||
int install_bundles(char **bundles)
|
||||
{
|
||||
int lock_fd;
|
||||
int ret = 0;
|
||||
int current_version;
|
||||
struct manifest *mom;
|
||||
struct list *iter;
|
||||
struct sub *sub;
|
||||
struct file *file;
|
||||
|
||||
/* step 1: initialize swupd and get current version from OS */
|
||||
|
||||
if (!init_globals()) {
|
||||
return EINIT_GLOBALS;
|
||||
}
|
||||
|
||||
ret = swupd_init(&lock_fd);
|
||||
if (ret != 0) {
|
||||
printf("Failed updater initialization, exiting now.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
current_version = read_version_from_subvol_file(path_prefix);
|
||||
swupd_curl_set_current_version(current_version);
|
||||
|
||||
/* first of all, make sure STATE_DIR is there, recreate if necessary*/
|
||||
ret = create_required_dirs();
|
||||
if (ret != 0) {
|
||||
printf("State directory %s cannot be recreated, aborting installation\n", STATE_DIR);
|
||||
goto clean_and_exit;
|
||||
}
|
||||
|
||||
|
||||
ret = load_manifests(current_version, current_version, "MoM", NULL, &mom);
|
||||
if (ret != 0) {
|
||||
printf("Cannot load official manifest MoM for version %i\n", current_version);
|
||||
ret = EMOM_NOTFOUND;
|
||||
goto clean_and_exit;
|
||||
}
|
||||
|
||||
/* step 2: check bundle args are valid if so populate subs struct */
|
||||
|
||||
int i;
|
||||
for (i = 0; *bundles; ++bundles) {
|
||||
if (is_tracked_bundle(*bundles)) {
|
||||
printf("%s bundle already installed, skipping it\n", *bundles);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!manifest_has_component(mom, *bundles)) {
|
||||
printf("%s bundle name is invalid, skipping it...\n", *bundles);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (component_subscribed(*bundles)) {
|
||||
continue;
|
||||
}
|
||||
create_and_append_subscription(*bundles);
|
||||
i++;
|
||||
printf("Added bundle %s for installation\n", *bundles);
|
||||
}
|
||||
|
||||
if (i == 0) {
|
||||
printf("There are no pending bundles to install, exiting now\n");
|
||||
ret = EBUNDLE_INSTALL;
|
||||
goto clean_manifest_and_exit;
|
||||
}
|
||||
|
||||
subscription_versions_from_MoM(mom, 0);
|
||||
recurse_manifest(mom, NULL);
|
||||
consolidate_submanifests(mom);
|
||||
|
||||
/* step 3: download neccessary packs */
|
||||
|
||||
ret = rm_staging_dir_contents("download");
|
||||
|
||||
printf("Downloading required packs...\n");
|
||||
ret = download_subscribed_packs(0, current_version, true);
|
||||
if (ret != 0) {
|
||||
printf("pack downloads failed, cannot proceed with the installation, exiting.\n");
|
||||
goto clean_subs_and_exit;
|
||||
}
|
||||
|
||||
/* step 4: Install all bundle(s) files into the fs */
|
||||
|
||||
printf("Installing bundle(s) files...\n");
|
||||
iter = list_head(mom->files);
|
||||
while (iter) {
|
||||
file = iter->data;
|
||||
iter = iter->next;
|
||||
|
||||
if (file->is_deleted || file->do_not_update || ignore(file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = do_staging(file);
|
||||
if (ret == 0) {
|
||||
rename_staged_file_to_final(file);
|
||||
}
|
||||
}
|
||||
|
||||
sync();
|
||||
|
||||
/* step 5: create bundle(s) subscription entries to track them
|
||||
*
|
||||
* Strictly speaking each manifest has an entry to write its own bundle filename
|
||||
* and thus tracking automagically, here just making sure.
|
||||
*/
|
||||
|
||||
iter = list_head(subs);
|
||||
while (iter) {
|
||||
sub = iter->data;
|
||||
iter = iter->next;
|
||||
|
||||
printf("Tracking %s bundle on the system\n", sub->component);
|
||||
ret = track_bundle_in_system(sub->component);
|
||||
if (ret != 0) {
|
||||
printf("Cannot track %s bundle on the system\n", sub->component);
|
||||
}
|
||||
}
|
||||
|
||||
/* Run any scripts that are needed to complete update */
|
||||
run_scripts();
|
||||
|
||||
ret = 0;
|
||||
printf("Bundle(s) installation done.\n");
|
||||
|
||||
clean_subs_and_exit:
|
||||
free_subscriptions();
|
||||
clean_manifest_and_exit:
|
||||
free_manifest(mom);
|
||||
clean_and_exit:
|
||||
swupd_curl_cleanup();
|
||||
v_lockfile(lock_fd);
|
||||
dump_file_descriptor_leaks();
|
||||
free_globals();
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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 <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#include "swupd.h"
|
||||
|
||||
static void print_help(const char *name)
|
||||
{
|
||||
printf("Usage:\n");
|
||||
printf(" swupd %s [options] bundlename\n\n", basename((char*)name));
|
||||
printf("Help Options:\n");
|
||||
printf(" -h, --help Show help options\n");
|
||||
printf(" -u, --url=[URL] RFC-3986 encoded url for version string and content file downloads\n");
|
||||
printf(" -P, --port=[port #] Port number to connect to at the url for version string and content file downloads\n");
|
||||
printf(" -F, --format=[staging,1,2,etc.] the format suffix for version file downloads\n");
|
||||
printf(" -x, --force Attempt to proceed even if non-critical errors found\n");
|
||||
printf(" -p, --path=[PATH...] Use [PATH...] as the path to verify (eg: a chroot or btrfs subvol\n");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static const struct option prog_opts[] =
|
||||
{
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"url", required_argument, 0, 'u'},
|
||||
{"port", required_argument, 0, 'P'},
|
||||
{"format", required_argument, 0, 'F'},
|
||||
{"force", no_argument, 0, 'x'},
|
||||
{"path", required_argument, 0, 'p'},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
static bool parse_options(int argc, char **argv)
|
||||
{
|
||||
int opt;
|
||||
|
||||
set_format_string(NULL);
|
||||
|
||||
while ((opt = getopt_long(argc, argv, "hxu:P:F:p:", prog_opts, NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case '?':
|
||||
case 'h':
|
||||
print_help(argv[0]);
|
||||
exit(EXIT_SUCCESS);
|
||||
case 'u':
|
||||
if (!optarg) {
|
||||
printf("error: invalid --url argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
if (version_server_urls[0]) {
|
||||
free(version_server_urls[0]);
|
||||
}
|
||||
string_or_die(&version_server_urls[0], "%s", optarg);
|
||||
break;
|
||||
case 'P':
|
||||
if (sscanf(optarg, "%ld", &update_server_port) != 1) {
|
||||
printf("Invalid --port argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
break;
|
||||
case 'F':
|
||||
if (!optarg || !set_format_string(optarg)) {
|
||||
printf("Invalid --format argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
break;
|
||||
case 'p': /* default empty path_prefix checks the running OS */
|
||||
if (!optarg) {
|
||||
printf("Invalid --path argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
if (path_prefix) { /* multiple -p options */
|
||||
free(path_prefix);
|
||||
}
|
||||
string_or_die(&path_prefix, "%s", optarg);
|
||||
break;
|
||||
case 'x':
|
||||
force = true;
|
||||
break;
|
||||
default:
|
||||
printf("error: unrecognized option\n\n");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if (!init_globals()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
err:
|
||||
print_help(argv[0]);
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
static int check_update()
|
||||
{
|
||||
int current_version, server_version;
|
||||
|
||||
check_root();
|
||||
swupd_curl_init();
|
||||
read_versions(¤t_version, ¤t_version, &server_version, path_prefix);
|
||||
|
||||
if (server_version < 0) {
|
||||
printf("Cannot reach update server\n");
|
||||
return -1;
|
||||
} else if (current_version < 0) {
|
||||
printf("Unable to determine current OS version\n");
|
||||
return -1;
|
||||
} else {
|
||||
if (current_version != -1 && current_version < server_version) {
|
||||
printf("There is a new OS version available: %d\n", server_version);
|
||||
update_motd(server_version);
|
||||
} else if (current_version >= server_version) {
|
||||
printf("There are no updates available\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int check_update_main(int argc, char **argv) {
|
||||
int ret;
|
||||
copyright_header("software update checker");
|
||||
|
||||
if (!parse_options(argc, argv)) {
|
||||
free_globals();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
ret = check_update();
|
||||
free_globals();
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Software Updater - client side
|
||||
*
|
||||
* Copyright (c) 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>
|
||||
* Jaime A. Garcia <jaime.garcia.naranjo@linux.intel.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <libgen.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#include "swupd.h"
|
||||
|
||||
#define MODE_RW_O (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
|
||||
#define VERIFY_NOPICKY 0
|
||||
|
||||
bool list = false;
|
||||
static char **bundles;
|
||||
|
||||
static void print_help(const char *name) {
|
||||
printf("Usage:\n");
|
||||
printf(" swupd %s [options] [bundle1 bundle2 (...)]\n\n", basename((char*)name));
|
||||
printf("Help Options:\n");
|
||||
printf(" -h, --help Show help options\n");
|
||||
printf(" -u, --url=[URL] RFC-3986 encoded url for version string and content file downloads\n");
|
||||
printf(" -P, --port=[port #] Port number to connect to at the url for version string and content file downloads\n");
|
||||
printf(" -p, --path=[PATH...] Use [PATH...] as the path to verify (eg: a chroot or btrfs subvol\n");
|
||||
printf(" -F, --format=[staging,1,2,etc.] the format suffix for version file downloads\n");
|
||||
printf(" -l, --list List all available bundles for the current version of Clear Linux\n");
|
||||
printf(" -x, --force Attempt to proceed even if non-critical errors found\n");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static const struct option prog_opts[] = {
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"url", required_argument, 0, 'u'},
|
||||
{"port", required_argument, 0, 'P'},
|
||||
{"list", no_argument, 0, 'l'},
|
||||
{"path", required_argument, 0, 'p'},
|
||||
{"format", required_argument, 0, 'F'},
|
||||
{"force", no_argument, 0, 'x'},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
static bool parse_options(int argc, char **argv)
|
||||
{
|
||||
int opt;
|
||||
|
||||
set_format_string(NULL);
|
||||
|
||||
while ((opt = getopt_long(argc, argv, "hxu:P:p:F:l", prog_opts, NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case '?':
|
||||
case 'h':
|
||||
print_help(argv[0]);
|
||||
exit(EXIT_SUCCESS);
|
||||
case 'u':
|
||||
if (!optarg) {
|
||||
printf("error: invalid --url argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
if (version_server_urls[0]) {
|
||||
free(version_server_urls[0]);
|
||||
}
|
||||
if (content_server_urls[0]) {
|
||||
free(content_server_urls[0]);
|
||||
}
|
||||
string_or_die(&version_server_urls[0], "%s", optarg);
|
||||
string_or_die(&content_server_urls[0], "%s", optarg);
|
||||
break;
|
||||
case 'p': /* default empty path_prefix verifies the running OS */
|
||||
if (!optarg) {
|
||||
printf("Invalid --path argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
if (path_prefix) { /* multiple -p options */
|
||||
free(path_prefix);
|
||||
}
|
||||
string_or_die(&path_prefix, "%s", optarg);
|
||||
break;
|
||||
case 'P':
|
||||
if (sscanf(optarg, "%ld", &update_server_port) != 1) {
|
||||
printf("Invalid --port argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
break;
|
||||
case 'F':
|
||||
if (!optarg || !set_format_string(optarg)) {
|
||||
printf("Invalid --format argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
break;
|
||||
case 'l':
|
||||
list = true;
|
||||
break;
|
||||
case 'x':
|
||||
force = true;
|
||||
break;
|
||||
default:
|
||||
printf("error: unrecognized option\n\n");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if (!list) {
|
||||
if (argc <= optind) {
|
||||
printf("error: missing bundle(s) to be installed\n\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
bundles = argv + optind;
|
||||
}
|
||||
|
||||
return true;
|
||||
err:
|
||||
print_help(argv[0]);
|
||||
return false;
|
||||
}
|
||||
|
||||
int bundle_add_main(int argc, char **argv)
|
||||
{
|
||||
copyright_header("bundle adder");
|
||||
|
||||
if (!parse_options(argc, argv)) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (list) {
|
||||
return list_installable_bundles();
|
||||
} else {
|
||||
return install_bundles(bundles);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Software Updater - client side
|
||||
*
|
||||
* Copyright (c) 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>
|
||||
* Jaime A. Garcia <jaime.garcia.naranjo@linux.intel.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <libgen.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#include "swupd.h"
|
||||
|
||||
#define VERIFY_PICKY 1
|
||||
|
||||
static char *bundle_name = NULL;
|
||||
|
||||
static void print_help(const char *name) {
|
||||
printf("Usage:\n");
|
||||
printf(" swupd %s [options] bundlename\n\n", basename((char*)name));
|
||||
printf("Help Options:\n");
|
||||
printf(" -h, --help Show help options\n");
|
||||
printf(" -p, --path=[PATH...] Use [PATH...] as the path to verify (eg: a chroot or btrfs subvol\n");
|
||||
printf(" -u, --url=[URL] RFC-3986 encoded url for version string and content file downloads\n");
|
||||
printf(" -P, --port=[port #] Port number to connect to at the url for version string and content file downloads\n");
|
||||
printf(" -x, --force Attempt to proceed even if non-critical errors found\n");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static const struct option prog_opts[] = {
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"path", required_argument, 0, 'p'},
|
||||
{"url", required_argument, 0, 'u'},
|
||||
{"port", required_argument, 0, 'P'},
|
||||
{"force", no_argument, 0, 'x'},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
static bool parse_options(int argc, char **argv)
|
||||
{
|
||||
int opt;
|
||||
|
||||
while ((opt = getopt_long(argc, argv, "hxp:u:P:", prog_opts, NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case '?':
|
||||
case 'h':
|
||||
print_help(argv[0]);
|
||||
exit(EXIT_SUCCESS);
|
||||
case 'p': /* default empty path_prefix removes on the running OS */
|
||||
if (!optarg) {
|
||||
printf("Invalid --path argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
if (path_prefix) { /* multiple -p options */
|
||||
free(path_prefix);
|
||||
}
|
||||
string_or_die(&path_prefix, "%s", optarg);
|
||||
break;
|
||||
case 'u':
|
||||
if (!optarg) {
|
||||
printf("error: invalid --url argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
if (version_server_urls[0]) {
|
||||
free(version_server_urls[0]);
|
||||
}
|
||||
if (content_server_urls[0]) {
|
||||
free(content_server_urls[0]);
|
||||
}
|
||||
string_or_die(&version_server_urls[0], "%s", optarg);
|
||||
string_or_die(&content_server_urls[0], "%s", optarg);
|
||||
break;
|
||||
case 'P':
|
||||
if (sscanf(optarg, "%ld", &update_server_port) != 1) {
|
||||
printf("Invalid --port argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
break;
|
||||
case 'x':
|
||||
force = true;
|
||||
break;
|
||||
default:
|
||||
printf("error: unrecognized option\n\n");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if (argc == optind) {
|
||||
printf("error: bundle name missing\n\n");
|
||||
goto err;
|
||||
}
|
||||
string_or_die(&bundle_name, "%s", argv[optind]);
|
||||
|
||||
return true;
|
||||
err:
|
||||
print_help(argv[0]);
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
int bundle_remove_main(int argc, char **argv)
|
||||
{
|
||||
int ret;
|
||||
|
||||
copyright_header("bundle remover");
|
||||
|
||||
if (!parse_options(argc, argv)) {
|
||||
return EINVALID_OPTION;
|
||||
}
|
||||
|
||||
ret = remove_bundle(bundle_name);
|
||||
free(bundle_name);
|
||||
|
||||
return ret;
|
||||
}
|
||||
+388
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* 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>
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* The curl library is great, but it is a little bit of a pain to get it to
|
||||
* reuse connections properly for simple cases. This file will manage our
|
||||
* curl handle properly so that we have a standing chance to get reuse
|
||||
* of our connections.
|
||||
*
|
||||
* NOTE NOTE NOTE
|
||||
*
|
||||
* Only use these from the main thread of the program. For multithreaded
|
||||
* use, you need to manage your own curl mutli environment.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "swupd.h"
|
||||
|
||||
static CURL *curl = NULL;
|
||||
|
||||
static int curr_version = -1;
|
||||
static int req_version = -1;
|
||||
|
||||
int swupd_curl_init(void)
|
||||
{
|
||||
CURLcode curl_ret;
|
||||
|
||||
curl_ret = curl_global_init(CURL_GLOBAL_ALL);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
curl = curl_easy_init();
|
||||
if (curl == NULL) {
|
||||
curl_global_cleanup();
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void swupd_curl_cleanup(void)
|
||||
{
|
||||
if (curl) {
|
||||
curl_easy_cleanup(curl);
|
||||
}
|
||||
curl = NULL;
|
||||
curl_global_cleanup();
|
||||
}
|
||||
|
||||
void swupd_curl_set_current_version(int v)
|
||||
{
|
||||
curr_version = v;
|
||||
}
|
||||
|
||||
void swupd_curl_set_requested_version(int v)
|
||||
{
|
||||
req_version = v;
|
||||
}
|
||||
|
||||
static size_t swupd_download_version_to_memory(void *ptr, size_t size, size_t nmemb, void *userdata)
|
||||
{
|
||||
char *tmp_version = (char *)userdata;
|
||||
size_t data_len = size * nmemb;
|
||||
|
||||
if (data_len >= LINE_MAX) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(tmp_version, ptr, data_len);
|
||||
tmp_version[data_len] = '\0';
|
||||
|
||||
return data_len;
|
||||
}
|
||||
|
||||
/* curl easy CURLOPT_WRITEFUNCTION callback */
|
||||
size_t swupd_download_file(void *ptr, size_t size, size_t nmemb, void *userdata)
|
||||
{
|
||||
struct file *file = (struct file*)userdata;
|
||||
const char *outfile;
|
||||
int fd;
|
||||
FILE *f;
|
||||
size_t written;
|
||||
|
||||
outfile = file->staging;
|
||||
|
||||
fd = open(outfile, O_CREAT | O_RDWR , 00600);
|
||||
if (fd < 0) {
|
||||
printf("Error: Cannot open %s for write: %s\n",
|
||||
outfile, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
f = fdopen(fd, "a");
|
||||
if (!f) {
|
||||
printf("Error: Cannot fdopen %s for write: %s\n",
|
||||
outfile, strerror(errno));
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
written = fwrite(ptr, size*nmemb, 1, f);
|
||||
|
||||
fflush(f);
|
||||
fclose(f);
|
||||
|
||||
if (written != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return size*nmemb;
|
||||
}
|
||||
|
||||
/* Download a single file SYNCHRONOUSLY
|
||||
* - If (in_memory_version_string != NULL) the file downloaded is expected
|
||||
* to be a version file and it is downloaded to memory instead of disk.
|
||||
* - Packs are big. If (pack == true) then the function allows resuming
|
||||
* a previous/interrupted download.
|
||||
* - This function returns zero or a standard < 0 status code.
|
||||
* - If failure to download, partial download is not deleted.
|
||||
* - NOTE: See full_download() for multi/asynchronous downloading of fullfiles.
|
||||
*/
|
||||
int swupd_curl_get_file(const char *url, char *filename, struct file *file,
|
||||
char *in_memory_version_string, bool pack)
|
||||
{
|
||||
CURLcode curl_ret;
|
||||
long ret = 0;
|
||||
int err;
|
||||
struct file *local = NULL;
|
||||
|
||||
if (!curl) {
|
||||
abort();
|
||||
}
|
||||
curl_easy_reset(curl);
|
||||
|
||||
if (in_memory_version_string == NULL) {
|
||||
// normal file download
|
||||
struct stat stat;
|
||||
|
||||
if (file) {
|
||||
local = file;
|
||||
} else {
|
||||
local = calloc(1, sizeof(struct file));
|
||||
if (!local) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
local->staging = filename;
|
||||
|
||||
if (lstat(filename, &stat) == 0) {
|
||||
if (pack) {
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE, (curl_off_t) stat.st_size);
|
||||
} else {
|
||||
unlink(filename);
|
||||
}
|
||||
}
|
||||
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_PRIVATE, (void*)local);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto exit;
|
||||
}
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, swupd_download_file);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto exit;
|
||||
}
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)local);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto exit;
|
||||
}
|
||||
} else {
|
||||
// only download latest version number, storing in the provided pointer
|
||||
printf("Attempting to download version string to memory\n");
|
||||
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, swupd_download_version_to_memory);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto exit;
|
||||
}
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)in_memory_version_string);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto exit;
|
||||
}
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_COOKIE, "request=uncached");
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
curl_ret = swupd_curl_set_basic_options(curl, url);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
curl_ret = curl_easy_perform(curl);
|
||||
if (curl_ret == CURLE_OK || curl_ret == CURLE_HTTP_RETURNED_ERROR) {
|
||||
curl_ret = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &ret);
|
||||
}
|
||||
|
||||
exit:
|
||||
if (curl_ret == CURLE_OK) {
|
||||
/* curl command succeeded, download might've failed, let our caller handle */
|
||||
switch (ret) {
|
||||
case 200:
|
||||
case 206:
|
||||
err = 0;
|
||||
break;
|
||||
case 403:
|
||||
err = -EACCES;
|
||||
break;
|
||||
case 404:
|
||||
err = -ENET404;
|
||||
break;
|
||||
default:
|
||||
err = -1;
|
||||
break;
|
||||
}
|
||||
} else { /* download failed but let our caller do it */
|
||||
switch (curl_ret) {
|
||||
case CURLE_COULDNT_RESOLVE_PROXY:
|
||||
case CURLE_COULDNT_RESOLVE_HOST:
|
||||
case CURLE_COULDNT_CONNECT:
|
||||
err = -ENONET;
|
||||
break;
|
||||
case CURLE_PARTIAL_FILE:
|
||||
case CURLE_RECV_ERROR:
|
||||
err = -ENOLINK;
|
||||
break;
|
||||
case CURLE_WRITE_ERROR:
|
||||
err = -EIO;
|
||||
break;
|
||||
case CURLE_OPERATION_TIMEDOUT:
|
||||
err = -ETIMEDOUT;
|
||||
break;
|
||||
default :
|
||||
err = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (err) {
|
||||
if (!pack) {
|
||||
unlink(filename);
|
||||
}
|
||||
}
|
||||
|
||||
if (local != file) {
|
||||
free(local);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
static CURLcode swupd_curl_set_security_opts(CURL *curl)
|
||||
{
|
||||
CURLcode curl_ret = CURLE_OK;
|
||||
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, true);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// TODO: change this to to use tlsv1.2 when it is supported and enabled
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_0);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_SSL_CIPHER_LIST, "HIGH");
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_PINNEDPUBLICKEY, "/usr/share/clear/update-ca/425b0f6b.key");
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_CAPATH , UPDATE_CA_CERTS_PATH);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// TODO: add the below when you know the paths:
|
||||
//curl_easy_setopt(curl, CURLOPT_CRLFILE, path-to-cert-revoc-list);
|
||||
//if (curl_ret != CURLE_OK) {
|
||||
// goto exit;
|
||||
//}
|
||||
|
||||
exit:
|
||||
return curl_ret;
|
||||
}
|
||||
|
||||
CURLcode swupd_curl_set_basic_options(CURL *curl, const char *url)
|
||||
{
|
||||
static bool use_ssl = true;
|
||||
|
||||
CURLcode curl_ret = CURLE_OK;
|
||||
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (update_server_port > 0) {
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_PORT, update_server_port);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
if (strncmp(url, content_server_urls[1], strlen(content_server_urls[1])) == 0) {
|
||||
#warning SECURITY HOLE since we can't SSL pin arbitrary servers
|
||||
curl_ret = swupd_curl_set_security_opts(curl);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto exit;
|
||||
}
|
||||
} else {
|
||||
if (use_ssl) {
|
||||
use_ssl = false;
|
||||
}
|
||||
}
|
||||
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, SWUPD_CURL_CONNECT_TIMEOUT);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, SWUPD_CURL_LOW_SPEED_LIMIT);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, SWUPD_CURL_RCV_TIMEOUT);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
#warning setup a means to validate IPv6 works end to end
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Avoid downloading HTML files for error responses if the HTTP code is >= 400 */
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
exit:
|
||||
return curl_ret;
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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 <bsdiff.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/fs.h>
|
||||
#include <libgen.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "swupd.h"
|
||||
#include "xattrs.h"
|
||||
|
||||
|
||||
static void do_delta(struct file* file);
|
||||
|
||||
|
||||
void try_delta(struct file *file)
|
||||
{
|
||||
char *filename;
|
||||
struct stat stat;
|
||||
|
||||
if (file->is_file == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (file->deltapeer == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (file->deltapeer->is_file == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (file->deltapeer->is_deleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* check if the full file is there already, because if it is, don't do the delta */
|
||||
string_or_die(&filename, "%s/staged/%s", STATE_DIR, file->hash);
|
||||
if (lstat(filename, &stat) == 0) {
|
||||
free(filename);
|
||||
return;
|
||||
}
|
||||
free(filename);
|
||||
|
||||
do_delta(file);
|
||||
}
|
||||
|
||||
static void do_delta(struct file *file)
|
||||
{
|
||||
char *origin;
|
||||
char *dir, *base, *tmp = NULL, *tmp2 = NULL;
|
||||
char *deltafile = NULL;
|
||||
char *filename;
|
||||
int ret;
|
||||
struct stat stat;
|
||||
|
||||
string_or_die(&deltafile, "%s/delta/%i-%i-%s", STATE_DIR,
|
||||
file->deltapeer->last_change, file->last_change, file->hash);
|
||||
|
||||
/* check if the full file is there already, because if it is, don't do the delta */
|
||||
string_or_die(&filename, "%s/staged/%s", STATE_DIR, file->hash);
|
||||
ret = lstat(filename, &stat);
|
||||
if (ret == 0) {
|
||||
unlink(deltafile);
|
||||
free(deltafile);
|
||||
free(filename);
|
||||
return;
|
||||
}
|
||||
|
||||
tmp = strdup(file->deltapeer->filename);
|
||||
tmp2 = strdup(file->deltapeer->filename);
|
||||
|
||||
dir = dirname(tmp);
|
||||
base = basename(tmp2);
|
||||
|
||||
string_or_die(&origin, "%s/%s/%s", STAGING_SUBVOL, dir, base);
|
||||
|
||||
ret = apply_bsdiff_delta(origin, filename, deltafile);
|
||||
if (ret) {
|
||||
unlink_all_staged_content(file);
|
||||
goto out;
|
||||
}
|
||||
xattrs_copy(origin, filename);
|
||||
|
||||
if (!verify_file(file, filename)) {
|
||||
unlink_all_staged_content(file);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (xattrs_compare(origin, filename) != 0) {
|
||||
unlink_all_staged_content(file);
|
||||
goto out;
|
||||
}
|
||||
|
||||
unlink(deltafile);
|
||||
|
||||
out:
|
||||
free(origin);
|
||||
free(deltafile);
|
||||
free(filename);
|
||||
free(tmp);
|
||||
free(tmp2);
|
||||
}
|
||||
+543
@@ -0,0 +1,543 @@
|
||||
/*
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/fs.h>
|
||||
#include <curl/curl.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "swupd-build-variant.h"
|
||||
#include "swupd.h"
|
||||
#include "bsdiff.h"
|
||||
|
||||
/* This file provides a managed download facility for code that needs a set of
|
||||
* update tar files. Such code starts with one call to "start_full_download()",
|
||||
* then makes a series of calls to "full_download()" once per desired file, and
|
||||
* then finishes with one call to "end_full_downlad()" which will block until
|
||||
* the previously queued downloads are completed and and untarred.
|
||||
*/
|
||||
|
||||
static CURLM *mcurl = NULL;
|
||||
static struct list *failed = NULL;
|
||||
|
||||
/*
|
||||
* The following represents a hash data structure used inside download.c
|
||||
* to track file downloads asynchonously via libcurl. In the past a
|
||||
* single linked list was used, but we may have a very large number of
|
||||
* files and repeatedly scan the list could become expensive. The hashmap
|
||||
* gives info on what HASH.tar files will be downloaded. A de-duplicated
|
||||
* set of files is downloaded. The downloads must complete prior to
|
||||
* traversing a manifest, doing hash comparisons, and (re)staging any files
|
||||
* whose hash miscompares.
|
||||
*
|
||||
* file->hash[0] acts as index into the arrays
|
||||
*/
|
||||
struct swupd_curl_hashbucket {
|
||||
pthread_mutex_t mutex;
|
||||
struct list *list;
|
||||
};
|
||||
#define SWUPD_CURL_HASH_BUCKETS 256
|
||||
static struct swupd_curl_hashbucket swupd_curl_hashmap[SWUPD_CURL_HASH_BUCKETS];
|
||||
|
||||
/* try to insert the file into the hashmap download queue
|
||||
* returns 1 if no download is needed
|
||||
* returns 0 if download is needed
|
||||
* returns -1 if error */
|
||||
static int swupd_curl_hashmap_insert(struct file *file) {
|
||||
struct list *iter;
|
||||
struct file *tmp;
|
||||
char *tar_dotfile;
|
||||
char *targetfile;
|
||||
struct stat stat;
|
||||
int hashmap_index = file->hash[0];
|
||||
struct swupd_curl_hashbucket *bucket = &swupd_curl_hashmap[hashmap_index];
|
||||
|
||||
pthread_mutex_lock(&bucket->mutex);
|
||||
|
||||
iter = bucket->list;
|
||||
while (iter) {
|
||||
tmp = iter->data;
|
||||
if (hash_compare(tmp->hash, file->hash)) {
|
||||
// hash already in download queue
|
||||
pthread_mutex_unlock(&bucket->mutex);
|
||||
return 1;
|
||||
}
|
||||
iter = iter->next;
|
||||
}
|
||||
|
||||
// if valid target file is already here, no need to download
|
||||
string_or_die(&targetfile, "%s/staged/%s", STATE_DIR, file->hash);
|
||||
|
||||
if (lstat(targetfile, &stat) == 0) {
|
||||
if (verify_file(file, targetfile)) {
|
||||
free(targetfile);
|
||||
pthread_mutex_unlock(&bucket->mutex);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
free(targetfile);
|
||||
|
||||
// hash not in queue and not present in staged
|
||||
|
||||
// clean up in case any prior download failed in a partial state
|
||||
string_or_die(&tar_dotfile, "%s/download/.%s.tar", STATE_DIR, file->hash);
|
||||
unlink(tar_dotfile);
|
||||
free(tar_dotfile);
|
||||
|
||||
// queue the hash for download
|
||||
iter = bucket->list;
|
||||
if ((iter = list_prepend_data(iter, file)) == NULL) {
|
||||
pthread_mutex_unlock(&bucket->mutex);
|
||||
return -1;
|
||||
}
|
||||
bucket->list = iter;
|
||||
|
||||
pthread_mutex_unlock(&bucket->mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* hysteresis thresholds */
|
||||
static int MAX_XFER = 25;
|
||||
static int MAX_XFER_BOTTOM = 15;
|
||||
|
||||
int start_full_download(bool pipelining)
|
||||
{
|
||||
int i;
|
||||
|
||||
failed = NULL;
|
||||
for (i = 0; i < SWUPD_CURL_HASH_BUCKETS; i++) {
|
||||
pthread_mutex_init(&swupd_curl_hashmap[i].mutex, NULL);
|
||||
}
|
||||
|
||||
mcurl = curl_multi_init();
|
||||
if (mcurl == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* we want to not do HTTP pipelining once things have failed once.. in case some transpoxy in the middle
|
||||
* is even more broken than average. This at least will allow the user to update, albeit slowly.
|
||||
*/
|
||||
if (pipelining) {
|
||||
curl_multi_setopt(mcurl, CURLMOPT_PIPELINING, 1);
|
||||
} else {
|
||||
/* survival: don't go too parallel in verify/fix loop */
|
||||
MAX_XFER = 1;
|
||||
MAX_XFER_BOTTOM = 1;
|
||||
}
|
||||
|
||||
printf("Starting download of remaining update content. This may take a while...\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void free_curl_list_data(void *data)
|
||||
{
|
||||
struct file *file = (struct file*)data;
|
||||
CURL *curl = file->curl;
|
||||
if (curl != NULL) {
|
||||
curl_multi_remove_handle(mcurl, curl);
|
||||
curl_easy_cleanup(curl);
|
||||
}
|
||||
}
|
||||
|
||||
void clean_curl_multi_queue(void)
|
||||
{
|
||||
int i;
|
||||
struct swupd_curl_hashbucket* bucket;
|
||||
|
||||
for (i = 0; i < SWUPD_CURL_HASH_BUCKETS; i++) {
|
||||
bucket = &swupd_curl_hashmap[i];
|
||||
pthread_mutex_lock(&bucket->mutex);
|
||||
list_free_list_and_data(bucket->list, free_curl_list_data);
|
||||
bucket->list = NULL;
|
||||
pthread_mutex_unlock(&bucket->mutex);
|
||||
}
|
||||
}
|
||||
|
||||
/* list the tarfile content, and verify it contains only one line equal to the expected hash.
|
||||
* loop through all the content to detect the case where archive contains more than one file.
|
||||
*/
|
||||
static int check_tarfile_content(struct file *file, const char *tarfilename)
|
||||
{
|
||||
int err;
|
||||
char *tarcommand;
|
||||
FILE *tar;
|
||||
int count = 0;
|
||||
|
||||
string_or_die(&tarcommand, "tar -tf %s/download/%s.tar 2> /dev/null", STATE_DIR, file->hash);
|
||||
|
||||
err = access(tarfilename, R_OK);
|
||||
if (err) {
|
||||
goto free_tarcommand;
|
||||
}
|
||||
|
||||
tar = popen(tarcommand, "r");
|
||||
if (tar == NULL) {
|
||||
err = -1;
|
||||
goto free_tarcommand;
|
||||
}
|
||||
|
||||
while (!feof(tar)) {
|
||||
char *c;
|
||||
char buffer[PATH_MAXLEN];
|
||||
|
||||
if (fgets(buffer, PATH_MAXLEN, tar) == NULL) {
|
||||
if (count != 1) {
|
||||
err = -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
c = strchr(buffer, '\n');
|
||||
if (c) {
|
||||
*c = 0;
|
||||
}
|
||||
if (c && (c != buffer) && (*(c-1)=='/')) {
|
||||
/* strip trailing '/' from directory tar */
|
||||
*(c-1) = 0;
|
||||
}
|
||||
if (strcmp(buffer, file->hash) != 0) {
|
||||
err = -1;
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
pclose(tar);
|
||||
free_tarcommand:
|
||||
free(tarcommand);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/* This function will break if the same HASH.tar full file is downloaded
|
||||
* multiple times in parallel. */
|
||||
static void untar_full_download(void *data)
|
||||
{
|
||||
struct file *file = data;
|
||||
char *tarfile;
|
||||
char *tar_dotfile;
|
||||
char *targetfile;
|
||||
struct stat stat;
|
||||
int err;
|
||||
char *tarcommand;
|
||||
|
||||
string_or_die(&tar_dotfile, "%s/download/.%s.tar", STATE_DIR, file->hash);
|
||||
string_or_die(&tarfile, "%s/download/%s.tar", STATE_DIR, file->hash);
|
||||
string_or_die(&targetfile, "%s/staged/%s", STATE_DIR, file->hash);
|
||||
|
||||
/* If valid target file already exists, we're done.
|
||||
* NOTE: this should NEVER happen given the checking that happens
|
||||
* ahead of queueing a download. But... */
|
||||
if (lstat(targetfile, &stat) == 0) {
|
||||
if (verify_file(file, targetfile)) {
|
||||
unlink(tar_dotfile);
|
||||
unlink(tarfile);
|
||||
free(tar_dotfile);
|
||||
free(tarfile);
|
||||
free(targetfile);
|
||||
return;
|
||||
} else {
|
||||
unlink(tarfile);
|
||||
unlink(targetfile);
|
||||
}
|
||||
} else if (lstat(tarfile, &stat) == 0) {
|
||||
/* remove tar file from possible past failure */
|
||||
unlink(tarfile);
|
||||
}
|
||||
|
||||
err = rename(tar_dotfile, tarfile);
|
||||
if (err) {
|
||||
free(tar_dotfile);
|
||||
goto exit;
|
||||
}
|
||||
free(tar_dotfile);
|
||||
|
||||
err = check_tarfile_content(file, tarfile);
|
||||
if (err) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* modern tar will automatically determine the compression type used */
|
||||
string_or_die(&tarcommand, "tar -C %s/staged/ " TAR_PERM_ATTR_ARGS " -xf %s 2> /dev/null",
|
||||
STATE_DIR, tarfile);
|
||||
|
||||
err = system(tarcommand);
|
||||
if (WIFEXITED(err)) {
|
||||
err = WEXITSTATUS(err);
|
||||
}
|
||||
free(tarcommand);
|
||||
if (err) {
|
||||
printf("ignoring tar extract failure for fullfile %s.tar (ret %d)\n",
|
||||
file->hash, err);
|
||||
goto exit;
|
||||
/* FIXME: can we respond meaningfully to tar error codes?
|
||||
* symlink untars may have perm/xattr complaints and non-zero
|
||||
* tar return, but symlink (probably?) untarred ok.
|
||||
*
|
||||
* Also getting complaints on some new regular files?
|
||||
*
|
||||
* Either way we verify the hash later, so on error there,
|
||||
* something could try to recover? */
|
||||
} else {
|
||||
/* Only unlink when tar succeeded, so we can examine the tar file
|
||||
* in the failure case. */
|
||||
unlink(tarfile);
|
||||
}
|
||||
|
||||
err = lstat(targetfile, &stat);
|
||||
exit:
|
||||
free(tarfile);
|
||||
free(targetfile);
|
||||
if (err) {
|
||||
unlink_all_staged_content(file);
|
||||
}
|
||||
}
|
||||
|
||||
static int perform_curl_io_and_complete(int *left)
|
||||
{
|
||||
CURLMsg *msg;
|
||||
long ret;
|
||||
CURLMcode curlm_ret;
|
||||
CURLcode curl_ret;
|
||||
|
||||
curlm_ret = curl_multi_perform(mcurl, left);
|
||||
if (curlm_ret != CURLM_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
CURL *handle;
|
||||
struct file *file;
|
||||
|
||||
msg = curl_multi_info_read(mcurl, left);
|
||||
if (!msg) {
|
||||
break;
|
||||
}
|
||||
if (msg->msg != CURLMSG_DONE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
handle = msg->easy_handle;
|
||||
ret = 404;
|
||||
curl_ret = curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &ret);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
curl_ret = curl_easy_getinfo(handle, CURLINFO_PRIVATE, (char **)&file);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
curl_easy_cleanup(handle);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The easy handle may have an error set, even if the server returns
|
||||
* HTTP 200, so retry the download for this case. */
|
||||
if (ret == 200 && msg->data.result != CURLE_OK) {
|
||||
printf("Error for %s download: %s\n", file->hash,
|
||||
curl_easy_strerror(msg->data.result));
|
||||
failed = list_prepend_data(failed, file);
|
||||
} else if (ret == 200) {
|
||||
/* When both web server and CURL report success, only then
|
||||
* proceed to uncompress. */
|
||||
untar_full_download(file);
|
||||
} else if (ret == 0) {
|
||||
/* For this case, the CURLINFO_RESPONSE_CODE(3) man page states:
|
||||
* "The stored value will be zero if no server response has been received."
|
||||
* This seems to indicate misuse of libcurl, so report this condition.
|
||||
*/
|
||||
printf("error: received http \"0\" response for %s download\n",
|
||||
file->hash);
|
||||
failed = list_prepend_data(failed, file);
|
||||
} else {
|
||||
char *url = NULL;
|
||||
|
||||
printf("error: received %ld response for download\n", ret);
|
||||
failed = list_prepend_data(failed, file);
|
||||
curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url);
|
||||
|
||||
unlink_all_staged_content(file);
|
||||
}
|
||||
if (file->staging) {
|
||||
free(file->staging);
|
||||
file->staging = NULL;
|
||||
}
|
||||
|
||||
/* NOTE: Intentionally no removal of file from hashmap. All needed files
|
||||
* need determined and queued in one complete preparation phase. Once all
|
||||
* needed files are all present, they can be staged. Otherwise a complex
|
||||
* datastructure and retries are needed to insure only one download of a file
|
||||
* happens fully to success AND a HASH.tar is uncompressed to and HASH and
|
||||
* staged to the _multiple_ filenames with that hash. */
|
||||
|
||||
curl_multi_remove_handle(mcurl, handle);
|
||||
curl_easy_cleanup(handle);
|
||||
file->curl = NULL;
|
||||
}
|
||||
|
||||
curlm_ret = curl_multi_perform(mcurl, left);
|
||||
if (curlm_ret != CURLM_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 2 limits, so that we can have hysteresis in behavior. We let the caller
|
||||
* add new transfer up until the queue reaches the high threshold. At this point
|
||||
* we don't return to the caller and instead process the queue until its len
|
||||
* gets below the low threshold */
|
||||
static int poll_fewer_than(int xfer_queue_high, int xfer_queue_low)
|
||||
{
|
||||
int left;
|
||||
CURLMcode curlm_ret;
|
||||
|
||||
curlm_ret = curl_multi_perform(mcurl, &left);
|
||||
if (curlm_ret != CURLM_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (left <= xfer_queue_high) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (left > xfer_queue_low) {
|
||||
usleep(500); /* TODO this really ought to be a select() statement */
|
||||
if (perform_curl_io_and_complete(&left) != 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* full_download() attempts to enqueue a file for later asynchronous download
|
||||
- NOTE: See swupd_curl_get_file() for single file synchronous downloads. */
|
||||
void full_download(struct file *file)
|
||||
{
|
||||
char *url = NULL;
|
||||
CURL *curl = NULL;
|
||||
int ret = -EFULLDOWNLOAD;
|
||||
char *filename = NULL;
|
||||
CURLMcode curlm_ret = CURLM_OK;
|
||||
CURLcode curl_ret = CURLE_OK;
|
||||
|
||||
ret = swupd_curl_hashmap_insert(file);
|
||||
if (ret > 0) { /* no download needed */
|
||||
/* File already exists - report success */
|
||||
ret = 0;
|
||||
goto out_good;
|
||||
} else if (ret < 0) { /* error */
|
||||
goto out_bad;
|
||||
} /* else (ret == 0) download needed */
|
||||
|
||||
curl = curl_easy_init();
|
||||
if (curl == NULL) {
|
||||
goto out_bad;
|
||||
}
|
||||
file->curl = curl;
|
||||
|
||||
ret = poll_fewer_than(MAX_XFER, MAX_XFER_BOTTOM);
|
||||
if (ret) {
|
||||
clean_curl_multi_queue();
|
||||
goto out_bad;
|
||||
}
|
||||
|
||||
string_or_die(&url, "%s/%i/files/%s.tar", preferred_content_url, file->last_change, file->hash);
|
||||
|
||||
string_or_die(&filename, "%s/download/.%s.tar", STATE_DIR, file->hash);
|
||||
file->staging = filename;
|
||||
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto out_bad;
|
||||
}
|
||||
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_PRIVATE, (void*)file);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto out_bad;
|
||||
}
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, swupd_download_file);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto out_bad;
|
||||
}
|
||||
curl_ret = curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)file);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto out_bad;
|
||||
}
|
||||
|
||||
curl_ret = swupd_curl_set_basic_options(curl, url);
|
||||
if (curl_ret != CURLE_OK) {
|
||||
goto out_bad;
|
||||
}
|
||||
|
||||
curlm_ret = curl_multi_add_handle(mcurl, curl);
|
||||
if (curlm_ret != CURLM_OK) {
|
||||
goto out_bad;
|
||||
}
|
||||
|
||||
if (poll_fewer_than(MAX_XFER + 10, MAX_XFER) != 0) {
|
||||
clean_curl_multi_queue();
|
||||
}
|
||||
ret = 0;
|
||||
goto out_good;
|
||||
|
||||
out_bad:
|
||||
failed = list_prepend_data(failed, file);
|
||||
if (curl != NULL) {
|
||||
/* Must remove handle out of multi queue first!*/
|
||||
curl_multi_remove_handle(mcurl, curl);
|
||||
curl_easy_cleanup(curl);
|
||||
}
|
||||
free(filename);
|
||||
out_good:
|
||||
free(url);
|
||||
}
|
||||
|
||||
struct list *end_full_download(void)
|
||||
{
|
||||
int left;
|
||||
int err;
|
||||
|
||||
printf("Finishing download of update content...\n");
|
||||
|
||||
if (poll_fewer_than(0, 0) == 0) {
|
||||
err = perform_curl_io_and_complete(&left);
|
||||
if (err) {
|
||||
clean_curl_multi_queue();
|
||||
}
|
||||
}
|
||||
|
||||
curl_multi_cleanup(mcurl);
|
||||
return failed;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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>
|
||||
*
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#include "swupd.h"
|
||||
|
||||
|
||||
void dump_file_descriptor_leaks(void)
|
||||
{
|
||||
DIR *dir;
|
||||
struct dirent *entry;
|
||||
|
||||
dir = opendir("/proc/self/fd");
|
||||
if (!dir) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
char *filename;
|
||||
char buffer[PATH_MAXLEN + 1];
|
||||
entry = readdir(dir);
|
||||
size_t size;
|
||||
if (!entry) {
|
||||
break;
|
||||
}
|
||||
if (strcmp(entry->d_name, ".") == 0) {
|
||||
continue;
|
||||
}
|
||||
if (strcmp(entry->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
/* skip stdin/out/err */
|
||||
if (strcmp(entry->d_name, "0") == 0) {
|
||||
continue;
|
||||
}
|
||||
if (strcmp(entry->d_name, "1") == 0) {
|
||||
continue;
|
||||
}
|
||||
if (strcmp(entry->d_name, "2") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* we hold an fd open, the one from opendir above */
|
||||
sprintf(buffer, "%i", dirfd(dir));
|
||||
if (strcmp(entry->d_name, buffer) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
string_or_die(&filename, "/proc/self/fd/%s", entry->d_name);
|
||||
memset(&buffer, 0, sizeof(buffer));
|
||||
size = readlink(filename, buffer, PATH_MAXLEN);
|
||||
if (size) {
|
||||
printf("Possible filedescriptor leak: fd_number=\"%s\",fd_details=\"%s\"\n", entry->d_name, buffer);
|
||||
}
|
||||
free(filename);
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Software Updater - client side
|
||||
*
|
||||
* Copyright © 2014-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:
|
||||
* Regis Merlino <regis.merlino@intel.com>
|
||||
* Tim Pepper <timothy.c.pepper@linux.intel.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "config.h"
|
||||
#include "swupd.h"
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
|
||||
bool force = false;
|
||||
bool verify_esp_only;
|
||||
bool verify_bundles_only = false;
|
||||
int update_count = 0;
|
||||
int update_skip = 0;
|
||||
bool need_update_boot = false;
|
||||
bool need_update_bootloader = false;
|
||||
bool update_complete = false;
|
||||
#if 0
|
||||
/* disabled unused global variables */
|
||||
bool ignore_config = true;
|
||||
bool ignore_state = true;
|
||||
#endif
|
||||
bool ignore_orphans = true;
|
||||
bool fix = false;
|
||||
char *format_string = NULL;
|
||||
char *path_prefix = NULL; /* must always end in '/' */
|
||||
char *mounted_dirs = NULL;
|
||||
char *bundle_to_add = NULL;
|
||||
struct timeval start_time;
|
||||
|
||||
/* NOTE: Today the content and version server urls are the same in
|
||||
* all cases. It is highly likely these will eventually differ, eg:
|
||||
* swupd-version.01.org and swupd-files.01.org as this enables
|
||||
* different quality of server and control of the servers
|
||||
*/
|
||||
bool download_only;
|
||||
bool have_manifest_diskspace = false; /* assume no until checked */
|
||||
bool have_network = false; /* assume no access until proved */
|
||||
#define URL_COUNT 2
|
||||
char *version_server_urls[URL_COUNT] = {
|
||||
NULL,
|
||||
"https://download.clearlinux.org/update",
|
||||
};
|
||||
char *content_server_urls[URL_COUNT] = {
|
||||
NULL,
|
||||
"https://download.clearlinux.org/update",
|
||||
};
|
||||
char *preferred_version_url;
|
||||
char *preferred_content_url;
|
||||
long update_server_port = -1;
|
||||
|
||||
#define SWUPD_DEFAULT_FORMAT "3"
|
||||
bool set_format_string(char *userinput)
|
||||
{
|
||||
int version;
|
||||
|
||||
if (userinput == NULL) {
|
||||
if (format_string) {
|
||||
free(format_string);
|
||||
}
|
||||
string_or_die(&format_string, "%s", SWUPD_DEFAULT_FORMAT);
|
||||
return true;
|
||||
}
|
||||
|
||||
// allow "staging" as a format string
|
||||
if ((strcmp(userinput, "staging") == 0)) {
|
||||
if (format_string) {
|
||||
free(format_string);
|
||||
}
|
||||
string_or_die(&format_string, "%s", userinput);
|
||||
return true;
|
||||
}
|
||||
|
||||
// otherwise, expect a positive integer
|
||||
errno = 0;
|
||||
version = strtoull(userinput, NULL, 10);
|
||||
if ((errno < 0) || (version <= 0)) {
|
||||
return false;
|
||||
}
|
||||
if (format_string) {
|
||||
free(format_string);
|
||||
}
|
||||
string_or_die(&format_string, "%d", version);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool init_globals(void)
|
||||
{
|
||||
struct stat statbuf;
|
||||
int ret;
|
||||
|
||||
gettimeofday(&start_time, NULL);
|
||||
|
||||
/* pick urls simply from user specified or default */
|
||||
if (version_server_urls[0] != NULL) {
|
||||
preferred_version_url = version_server_urls[0];
|
||||
} else {
|
||||
preferred_version_url = version_server_urls[1];
|
||||
}
|
||||
if (content_server_urls[0] != NULL) {
|
||||
preferred_content_url = content_server_urls[0];
|
||||
} else {
|
||||
preferred_content_url = content_server_urls[1];
|
||||
}
|
||||
|
||||
/* insure path_prefix is absolute, at least '/', ends in '/',
|
||||
* and is a valid dir */
|
||||
if (path_prefix != NULL) {
|
||||
int len;
|
||||
char *tmp;
|
||||
|
||||
if (path_prefix[0] != '/') {
|
||||
char *cwd;
|
||||
|
||||
cwd = get_current_dir_name();
|
||||
if (cwd == NULL) {
|
||||
printf("Unable to getwd() (%s)\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
string_or_die(&tmp, "%s/%s", cwd, path_prefix);
|
||||
|
||||
free(path_prefix);
|
||||
path_prefix = tmp;
|
||||
free(cwd);
|
||||
}
|
||||
|
||||
len = strlen(path_prefix);
|
||||
if (!len || (path_prefix[len-1] != '/')) {
|
||||
string_or_die(&tmp, "%s/", path_prefix);
|
||||
free(path_prefix);
|
||||
path_prefix = tmp;
|
||||
}
|
||||
} else {
|
||||
string_or_die(&path_prefix, "/");
|
||||
}
|
||||
ret = stat(path_prefix, &statbuf);
|
||||
if (ret != 0 || !S_ISDIR(statbuf.st_mode)) {
|
||||
printf("Bad path_prefix %s (%s), cannot continue.\n",
|
||||
path_prefix, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void free_globals(void)
|
||||
{
|
||||
free(content_server_urls[0]);
|
||||
free(version_server_urls[0]);
|
||||
free(path_prefix);
|
||||
free(format_string);
|
||||
free(mounted_dirs);
|
||||
if (bundle_to_add != NULL) {
|
||||
free(bundle_to_add);
|
||||
}
|
||||
}
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
#include <openssl/hmac.h>
|
||||
|
||||
#include "swupd.h"
|
||||
#include "xattrs.h"
|
||||
|
||||
void hash_assign(char *src, char *dst)
|
||||
{
|
||||
memcpy(dst, src, SWUPD_HASH_LEN-1);
|
||||
dst[SWUPD_HASH_LEN-1] = '\0';
|
||||
}
|
||||
|
||||
bool hash_compare(char *hash1, char *hash2)
|
||||
{
|
||||
if (bcmp(hash1, hash2, SWUPD_HASH_LEN-1) == 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool hash_is_zeros(char *hash)
|
||||
{
|
||||
return hash_compare("0000000000000000000000000000000000000000000000000000000000000000", hash);
|
||||
}
|
||||
|
||||
static void hash_set_zeros(char *hash)
|
||||
{
|
||||
hash_assign("0000000000000000000000000000000000000000000000000000000000000000", hash);
|
||||
}
|
||||
|
||||
#if 0
|
||||
static bool hash_is_ones(char *hash)
|
||||
{
|
||||
return hash_compare("1111111111111111111111111111111111111111111111111111111111111111", hash);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void hash_set_ones(char *hash)
|
||||
{
|
||||
hash_assign("1111111111111111111111111111111111111111111111111111111111111111", hash);
|
||||
}
|
||||
|
||||
static void hmac_sha256_for_data(char *hash,
|
||||
const unsigned char *key, size_t key_len,
|
||||
const unsigned char *data, size_t data_len)
|
||||
{
|
||||
unsigned char digest[EVP_MAX_MD_SIZE];
|
||||
unsigned int digest_len = 0;
|
||||
char *digest_str;
|
||||
unsigned int i;
|
||||
|
||||
if (data == NULL) {
|
||||
hash_set_zeros(hash);
|
||||
return;
|
||||
}
|
||||
|
||||
if (HMAC(EVP_sha256(), (const void *)key, key_len, data, data_len, digest, &digest_len) == NULL) {
|
||||
hash_set_zeros(hash);
|
||||
return;
|
||||
}
|
||||
|
||||
digest_str = calloc((digest_len * 2) + 1, sizeof(char));
|
||||
if (digest_str == NULL) {
|
||||
abort();
|
||||
}
|
||||
|
||||
for (i = 0; i < digest_len; i++) {
|
||||
sprintf(&digest_str[i * 2], "%02x", (unsigned int)digest[i]);
|
||||
}
|
||||
|
||||
hash_assign(digest_str, hash);
|
||||
free(digest_str);
|
||||
}
|
||||
|
||||
static void hmac_sha256_for_string(char *hash,
|
||||
const unsigned char *key, size_t key_len,
|
||||
const char *str)
|
||||
{
|
||||
if (str == NULL) {
|
||||
hash_set_zeros(hash);
|
||||
return;
|
||||
}
|
||||
|
||||
hmac_sha256_for_data(hash, key, key_len, (const unsigned char *)str, strlen(str));
|
||||
}
|
||||
|
||||
static void hmac_compute_key(const char *filename,
|
||||
const struct update_stat *updt_stat,
|
||||
char *key, size_t *key_len, bool use_xattrs)
|
||||
{
|
||||
char *xattrs_blob = (void *)0xdeadcafe;
|
||||
size_t xattrs_blob_len = 0;
|
||||
|
||||
if (use_xattrs) {
|
||||
xattrs_get_blob(filename, &xattrs_blob, &xattrs_blob_len);
|
||||
}
|
||||
|
||||
hmac_sha256_for_data(key, (const unsigned char *)updt_stat,
|
||||
sizeof(struct update_stat),
|
||||
(const unsigned char *)xattrs_blob,
|
||||
xattrs_blob_len);
|
||||
|
||||
if (hash_is_zeros(key)) {
|
||||
*key_len = 0;
|
||||
} else {
|
||||
*key_len = SWUPD_HASH_LEN-1;
|
||||
}
|
||||
|
||||
if (xattrs_blob_len != 0) {
|
||||
free(xattrs_blob);
|
||||
}
|
||||
}
|
||||
|
||||
/* provide a wrapper for compute_hash() because we want a cheap-out option in
|
||||
* case we are looking for missing files only:
|
||||
* zeros hash: file missing
|
||||
* ones hash: file present */
|
||||
int compute_hash_lazy(struct file *file, char *filename)
|
||||
{
|
||||
struct stat sb;
|
||||
if (lstat(filename, &sb) == 0) {
|
||||
hash_set_ones(file->hash);
|
||||
} else {
|
||||
hash_set_zeros(file->hash);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* this function MUST be kept in sync with the server
|
||||
* return is -1 if there was an error. If the file does not exist,
|
||||
* a "0000000..." hash is returned as is our convention in the manifest
|
||||
* for deleted files. Otherwise file->hash is set to a non-zero hash. */
|
||||
/* TODO: how should we properly handle compute_hash() failures? */
|
||||
int compute_hash(struct file *file, char *filename)
|
||||
{
|
||||
int ret;
|
||||
char key[SWUPD_HASH_LEN];
|
||||
size_t key_len;
|
||||
unsigned char *blob;
|
||||
FILE *fl;
|
||||
|
||||
if (file->is_deleted) {
|
||||
hash_set_zeros(file->hash);
|
||||
return 0;
|
||||
}
|
||||
|
||||
hash_set_zeros(key);
|
||||
|
||||
if (file->is_link) {
|
||||
char link[PATH_MAXLEN];
|
||||
memset(link, 0, PATH_MAXLEN);
|
||||
|
||||
ret = readlink(filename, link, PATH_MAXLEN - 1);
|
||||
|
||||
if (ret >= 0) {
|
||||
hmac_compute_key(filename, &file->stat, key, &key_len, file->use_xattrs);
|
||||
hmac_sha256_for_string(file->hash,
|
||||
(const unsigned char *)key,
|
||||
key_len,
|
||||
link);
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (file->is_dir) {
|
||||
hmac_compute_key(filename, &file->stat, key, &key_len, file->use_xattrs);
|
||||
hmac_sha256_for_string(file->hash,
|
||||
(const unsigned char *)key,
|
||||
key_len,
|
||||
file->filename); //file->filename not filename
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* if we get here, this is a regular file */
|
||||
fl = fopen(filename, "r");
|
||||
if (!fl) {
|
||||
return -1;
|
||||
}
|
||||
blob = mmap(NULL, file->stat.st_size, PROT_READ, MAP_PRIVATE, fileno(fl), 0);
|
||||
if (blob == MAP_FAILED && file->stat.st_size != 0) {
|
||||
abort();
|
||||
}
|
||||
|
||||
hmac_compute_key(filename, &file->stat, key, &key_len, file->use_xattrs);
|
||||
hmac_sha256_for_data(file->hash,
|
||||
(const unsigned char *)key,
|
||||
key_len,
|
||||
blob,
|
||||
file->stat.st_size);
|
||||
munmap(blob, file->stat.st_size);
|
||||
fclose(fl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool verify_file(struct file *file, char *filename)
|
||||
{
|
||||
struct file *local = calloc(1, sizeof(struct file));
|
||||
|
||||
if (local == NULL) {
|
||||
abort();
|
||||
}
|
||||
|
||||
local->filename = file->filename;
|
||||
local->use_xattrs = true;
|
||||
|
||||
populate_file_struct(local, filename);
|
||||
if (compute_hash(local, filename) != 0) {
|
||||
free(local);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check if manifest hash matches local file hash */
|
||||
if (hash_compare(file->hash, local->hash)) {
|
||||
free(local);
|
||||
return true;
|
||||
} else {
|
||||
free(local);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Software Updater - client side
|
||||
*
|
||||
* Copyright © 2013-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:
|
||||
* Timothy C. Pepper <timothy.c.pepper@linux.intel.com>
|
||||
* cguiraud <christophe.guiraud@intel.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <getopt.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "swupd.h"
|
||||
|
||||
/* outputs the hash of a file */
|
||||
|
||||
static struct option opts[] = {
|
||||
{ "no-xattrs", 0, NULL, 'n' },
|
||||
{ "basepath", 1, NULL, 'b' },
|
||||
{ "help", 0, NULL, 'h' },
|
||||
{ 0, 0, NULL, 0 }
|
||||
};
|
||||
|
||||
static void usage(const char *name) {
|
||||
printf("Usage:\n");
|
||||
printf(" swupd %s [OPTION...] filename\n\n", basename((char*)name));
|
||||
printf("Help Options:\n");
|
||||
printf(" -h, --help Show help options\n\n");
|
||||
printf("Application Options:\n");
|
||||
printf(" -n, --no-xattrs Ignore extended attributes\n");
|
||||
printf(" -b, --basepath Optional argument for leading path to filename\n");
|
||||
printf("\n");
|
||||
printf("The filename is the name as it would appear in a Manifest file.\n");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
int hashdump_main(int argc, char **argv)
|
||||
{
|
||||
struct file* file;
|
||||
char *fullname;
|
||||
int ret;
|
||||
|
||||
file = calloc(1, sizeof(struct file));
|
||||
if (!file) {
|
||||
abort();
|
||||
}
|
||||
|
||||
file->use_xattrs = true;
|
||||
|
||||
while (1) {
|
||||
int c;
|
||||
int i;
|
||||
|
||||
c = getopt_long(argc, argv, "nb:h", opts, &i);
|
||||
if (c == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch(c) {
|
||||
case 'n':
|
||||
file->use_xattrs = false;
|
||||
break;
|
||||
case 'b':
|
||||
if (!optarg) {
|
||||
printf("Invalid --path argument\n\n");
|
||||
free(file);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (path_prefix) { /* multiple -p options */
|
||||
free(path_prefix);
|
||||
}
|
||||
string_or_die(&path_prefix, "%s", optarg);
|
||||
break;
|
||||
case 'h':
|
||||
usage(argv[0]);
|
||||
exit(0);
|
||||
break;
|
||||
default:
|
||||
usage(argv[0]);
|
||||
exit(-1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!init_globals()) {
|
||||
free_globals();
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (optind >= argc) {
|
||||
usage(argv[0]);
|
||||
free_globals();
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
// mk_full_filename expects absolute filenames (eg: from Manifest)
|
||||
if (argv[optind][0] == '/') {
|
||||
file->filename = strdup(argv[optind]);
|
||||
if (!file->filename) {
|
||||
abort();
|
||||
}
|
||||
} else {
|
||||
string_or_die(&file->filename, "/%s", argv[optind]);
|
||||
}
|
||||
|
||||
printf("Calculating hash %s xattrs for: (%s) ... %s\n",
|
||||
(file->use_xattrs ? "with":"without"), path_prefix, file->filename);
|
||||
fullname = mk_full_filename(path_prefix, file->filename);
|
||||
printf("fullname=%s\n", fullname);
|
||||
populate_file_struct(file, fullname);
|
||||
ret = compute_hash(file, fullname);
|
||||
if (ret != 0) {
|
||||
printf("compute_hash() failed\n");
|
||||
} else {
|
||||
printf("%s\n", file->hash);
|
||||
if (file->is_dir) {
|
||||
if (is_directory_mounted(fullname)) {
|
||||
printf("!! dumped hash might not match a manifest hash because a mount is active\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(fullname);
|
||||
free(file->filename);
|
||||
free(file);
|
||||
free_globals();
|
||||
return 0;
|
||||
}
|
||||
+666
@@ -0,0 +1,666 @@
|
||||
/*
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/statvfs.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "swupd.h"
|
||||
|
||||
|
||||
void check_root(void)
|
||||
{
|
||||
if (getuid() != 0) {
|
||||
printf("This program must be run as root..aborting.\n\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove the contents of a staging directory (eg: /mnt/swupd/update/780 or
|
||||
* /mnt/swupd/update/delta) which are not supposed to contain
|
||||
* subdirectories containing files, ie: no need for true recursive removal.
|
||||
* Just the relative path (et: "780" or "delta" is passed as a parameter).
|
||||
*
|
||||
* return: 0 on success, non-zero on error
|
||||
*/
|
||||
int rm_staging_dir_contents(const char *rel_path)
|
||||
{
|
||||
DIR *dir;
|
||||
struct dirent entry;
|
||||
struct dirent *result;
|
||||
char *filename;
|
||||
char *abs_path;
|
||||
int ret;
|
||||
|
||||
string_or_die(&abs_path, "%s/%s", STATE_DIR, rel_path);
|
||||
|
||||
dir = opendir(abs_path);
|
||||
if (dir == NULL) {
|
||||
free(abs_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
while ((ret = readdir_r(dir, &entry, &result)) == 0) {
|
||||
if (result == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!strcmp(entry.d_name, ".") ||
|
||||
!strcmp(entry.d_name, "..")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
string_or_die(&filename, "%s/%s", abs_path, entry.d_name);
|
||||
|
||||
ret = remove(filename);
|
||||
if (ret != 0) {
|
||||
free(filename);
|
||||
break;
|
||||
}
|
||||
free(filename);
|
||||
}
|
||||
|
||||
free(abs_path);
|
||||
closedir(dir);
|
||||
|
||||
sync();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void unlink_all_staged_content(struct file *file)
|
||||
{
|
||||
char *filename;
|
||||
|
||||
/* downloaded tar file */
|
||||
string_or_die(&filename, "%s/download/%s.tar", STATE_DIR, file->hash);
|
||||
unlink(filename);
|
||||
free(filename);
|
||||
string_or_die(&filename, "%s/download/.%s.tar", STATE_DIR, file->hash);
|
||||
unlink(filename);
|
||||
free(filename);
|
||||
|
||||
/* downloaded and un-tar'd file */
|
||||
string_or_die(&filename, "%s/staged/%s", STATE_DIR, file->hash);
|
||||
if (file->is_dir) {
|
||||
rmdir(filename);
|
||||
} else {
|
||||
unlink(filename);
|
||||
}
|
||||
free(filename);
|
||||
|
||||
/* delta file */
|
||||
if (file->peer) {
|
||||
string_or_die(&filename, "%s/delta/%i-%i-%s", STATE_DIR,
|
||||
file->peer->last_change, file->last_change, file->hash);
|
||||
unlink(filename);
|
||||
free(filename);
|
||||
}
|
||||
}
|
||||
|
||||
FILE * fopen_exclusive(const char *filename) /* no mode, opens for write only */
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = open(filename,O_CREAT | O_EXCL | O_RDWR , 00600);
|
||||
if (fd < 0) {
|
||||
return NULL;
|
||||
}
|
||||
return fdopen(fd, "w");
|
||||
}
|
||||
|
||||
int create_required_dirs(void)
|
||||
{
|
||||
int ret = 0;
|
||||
int i;
|
||||
char *dir;
|
||||
#define STATE_DIR_COUNT 3
|
||||
const char *dirs[] = {"delta","staged","download"};
|
||||
struct stat buf;
|
||||
bool missing = false;
|
||||
|
||||
// check for existance
|
||||
ret = stat(STATE_DIR, &buf);
|
||||
if (ret && (errno == ENOENT)) {
|
||||
missing = true;
|
||||
}
|
||||
for (i = 0; i < STATE_DIR_COUNT; i++) {
|
||||
string_or_die(&dir, "%s/%s", STATE_DIR, dirs[i]);
|
||||
ret = stat(dir, &buf);
|
||||
if (ret) {
|
||||
missing = true;
|
||||
}
|
||||
free(dir);
|
||||
}
|
||||
|
||||
if (missing) { // (re)create dirs
|
||||
char *cmd;
|
||||
|
||||
// laziness here for want of a simple "mkdir -p"
|
||||
string_or_die(&cmd, "mkdir -p %s/{delta,staged,download}", STATE_DIR);
|
||||
ret = system(cmd);
|
||||
if (ret) {
|
||||
return -1;
|
||||
}
|
||||
free(cmd);
|
||||
|
||||
// chmod 700
|
||||
ret = chmod(STATE_DIR, S_IRWXU);
|
||||
if (ret) {
|
||||
return -1;
|
||||
}
|
||||
for (i = 0; i < STATE_DIR_COUNT; i++) {
|
||||
string_or_die(&dir, "%s/%s", STATE_DIR, dirs[i]);
|
||||
ret = chmod(dir, S_IRWXU);
|
||||
if (ret) {
|
||||
return -1;
|
||||
}
|
||||
free(dir);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* store a colon separated list of current mountpoint into
|
||||
* variable mounted_dirs, this function do not return a value.
|
||||
*
|
||||
* e.g: :/proc:/mnt/acct:
|
||||
*/
|
||||
void get_mounted_directories(void)
|
||||
{
|
||||
FILE *file;
|
||||
char *line = NULL;
|
||||
char *mnt;
|
||||
char *tmp;
|
||||
ssize_t ret;
|
||||
char *c;
|
||||
size_t n;
|
||||
|
||||
file = fopen("/proc/self/mountinfo", "r");
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (!feof(file)) {
|
||||
ret = getline(&line, &n, file);
|
||||
if ((ret < 0) || (line == NULL)) {
|
||||
break;
|
||||
}
|
||||
|
||||
c = strchr(line, '\n');
|
||||
if (c) {
|
||||
*c = 0;
|
||||
}
|
||||
|
||||
n = 0;
|
||||
mnt = strtok(line, " ");
|
||||
while (mnt != NULL) {
|
||||
if (n == 4) {
|
||||
/* The "4" assumes today's mountinfo form of:
|
||||
* 16 36 0:3 / /proc rw,relatime master:7 - proc proc rw
|
||||
* where the fifth field is the mountpoint. */
|
||||
if (strcmp(mnt, "/") == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (mounted_dirs == NULL) {
|
||||
string_or_die(&mounted_dirs, "%s", ":");
|
||||
}
|
||||
tmp = mounted_dirs;
|
||||
string_or_die(&mounted_dirs, "%s%s:", tmp, mnt);
|
||||
free(tmp);
|
||||
break;
|
||||
}
|
||||
n++;
|
||||
mnt = strtok(NULL, " ");
|
||||
}
|
||||
free(line);
|
||||
line = NULL;
|
||||
}
|
||||
free(line);
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
// prepends prefix to an path (eg: the global path_prefix to a
|
||||
// file->filename or some other path prefix and path), insuring there
|
||||
// is no duplicate '/' at the strings' junction and no trailing '/'
|
||||
char *mk_full_filename(const char *prefix, const char *path)
|
||||
{
|
||||
char *fname = NULL;
|
||||
char *abspath;
|
||||
|
||||
if (path[0] == '/') {
|
||||
abspath = strdup(path);
|
||||
} else {
|
||||
string_or_die(&abspath, "/%s", path);
|
||||
}
|
||||
if (abspath == NULL) {
|
||||
abort();
|
||||
}
|
||||
|
||||
// The prefix is a minimum of "/" or "". If the prefix is only that,
|
||||
// just use abspath. If the prefix is longer than the minimal, insure
|
||||
// it ends in not "/" and append abspath.
|
||||
if ((strcmp(prefix, "/") == 0) ||
|
||||
(strcmp(prefix, "") == 0)) {
|
||||
// rootfs, use absolute path
|
||||
fname = strdup(abspath);
|
||||
if (fname == NULL) {
|
||||
abort();
|
||||
}
|
||||
} else if (strcmp(&prefix[strlen(prefix)-1], "/") == 0) {
|
||||
// chroot and need to strip trailing "/" from prefix
|
||||
char *tmp = strdup(prefix);
|
||||
if (tmp == NULL) {
|
||||
abort();
|
||||
}
|
||||
tmp[strlen(tmp) - 1] = '\0';
|
||||
|
||||
string_or_die(&fname, "%s%s", tmp, abspath);
|
||||
free(tmp);
|
||||
} else {
|
||||
// chroot and no need to strip trailing "/" from prefix
|
||||
string_or_die(&fname, "%s%s", prefix, abspath);
|
||||
}
|
||||
free(abspath);
|
||||
return fname;
|
||||
}
|
||||
|
||||
// expects filename w/o path_prefix prepended
|
||||
bool is_directory_mounted(const char *filename)
|
||||
{
|
||||
char *fname;
|
||||
bool ret = false;
|
||||
char *tmp;
|
||||
|
||||
if (mounted_dirs == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tmp = mk_full_filename(path_prefix, filename);
|
||||
string_or_die(&fname, ":%s:", tmp);
|
||||
free(tmp);
|
||||
|
||||
if (strstr(mounted_dirs, fname)) {
|
||||
ret = true;
|
||||
}
|
||||
|
||||
free(fname);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// expects filename w/o path_prefix prepended
|
||||
bool is_under_mounted_directory(const char *filename)
|
||||
{
|
||||
bool ret = false;
|
||||
int err;
|
||||
char *token;
|
||||
char *mountpoint;
|
||||
char *dir;
|
||||
char *fname;
|
||||
char *tmp;
|
||||
|
||||
if (mounted_dirs == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dir = strdup(mounted_dirs);
|
||||
if (dir == NULL) {
|
||||
abort();
|
||||
}
|
||||
|
||||
token = strtok(dir + 1, ":");
|
||||
while (token != NULL) {
|
||||
string_or_die(&mountpoint, "%s/", token);
|
||||
|
||||
tmp = mk_full_filename(path_prefix, filename);
|
||||
string_or_die(&fname, ":%s:", tmp);
|
||||
free(tmp);
|
||||
|
||||
err = strncmp(fname, mountpoint, strlen(mountpoint));
|
||||
free(fname);
|
||||
if (err == 0) {
|
||||
free(mountpoint);
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
|
||||
token = strtok(NULL, ":");
|
||||
|
||||
free(mountpoint);
|
||||
}
|
||||
|
||||
free(dir);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int swupd_rm_file(const char *path)
|
||||
{
|
||||
int err = unlink(path);
|
||||
if (err) {
|
||||
if (errno == ENOENT) {
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int swupd_rm_dir(const char *path)
|
||||
{
|
||||
DIR *dir;
|
||||
struct dirent entry;
|
||||
struct dirent *result;
|
||||
char *filename = NULL;
|
||||
int ret, err;
|
||||
|
||||
dir = opendir(path);
|
||||
if (dir == NULL) {
|
||||
if (errno == ENOENT) {
|
||||
ret = 0;
|
||||
goto exit;
|
||||
} else {
|
||||
ret = -1;
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
while ((ret = readdir_r(dir, &entry, &result)) == 0) {
|
||||
if (result == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!strcmp(entry.d_name, ".") ||
|
||||
!strcmp(entry.d_name, "..")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
free(filename);
|
||||
string_or_die(&filename, "%s/%s", path, entry.d_name);
|
||||
|
||||
err = swupd_rm(filename);
|
||||
if (err) {
|
||||
ret = -1;
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
/* Delete directory once it's empty */
|
||||
err = rmdir(path);
|
||||
if (err) {
|
||||
if (errno == ENOENT) {
|
||||
} else {
|
||||
ret = -1;
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
closedir(dir);
|
||||
free(filename);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int swupd_rm(const char *filename) {
|
||||
struct stat stat;
|
||||
int ret;
|
||||
|
||||
ret = lstat(filename, &stat);
|
||||
if (ret) {
|
||||
if (errno == ENOENT) {
|
||||
// Quiet, no real failure here
|
||||
return -ENOENT;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (S_ISDIR(stat.st_mode)) {
|
||||
ret = swupd_rm_dir(filename);
|
||||
} else {
|
||||
ret = swupd_rm_file(filename);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int rm_bundle_file(const char *bundle)
|
||||
{
|
||||
char *filename = NULL;
|
||||
int ret = 0;
|
||||
struct stat statb;
|
||||
|
||||
string_or_die(&filename, "%s/%s/%s", path_prefix, BUNDLES_DIR, bundle);
|
||||
|
||||
if (stat(filename, &statb) == -1) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (S_ISREG(statb.st_mode)) {
|
||||
if (unlink(filename) != 0) {
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
free(filename);
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
#if 0
|
||||
void dump_file_info(struct file *file)
|
||||
{
|
||||
printf("%s:\n", file->filename);
|
||||
printf("\t%s\n", file->hash);
|
||||
printf("\t%d\n", file->last_change);
|
||||
|
||||
if (file->use_xattrs) {
|
||||
printf("\tuse_xattrs\n");
|
||||
}
|
||||
if (file->is_dir) {
|
||||
printf("\tis_dir\n");
|
||||
}
|
||||
if (file->is_file) {
|
||||
printf("\tis_file\n");
|
||||
}
|
||||
if (file->is_link) {
|
||||
printf("\tis_link\n");
|
||||
}
|
||||
if (file->is_deleted) {
|
||||
printf("\tis_deleted\n");
|
||||
}
|
||||
if (file->is_manifest) {
|
||||
printf("\tis_manifest\n");
|
||||
}
|
||||
if (file->is_config) {
|
||||
printf("\tis_config\n");
|
||||
}
|
||||
if (file->is_state) {
|
||||
printf("\tis_state\n");
|
||||
}
|
||||
if (file->is_boot) {
|
||||
printf("\tis_boot\n");
|
||||
}
|
||||
if (file->is_rename) {
|
||||
printf("\tis_rename\n");
|
||||
}
|
||||
if (file->is_orphan) {
|
||||
printf("\tis_orphan\n");
|
||||
}
|
||||
if (file->do_not_update) {
|
||||
printf("\tdo_not_update\n");
|
||||
}
|
||||
|
||||
if (file->peer) {
|
||||
printf("\tpeer %s(%s)\n", file->peer->filename, file->peer->hash);
|
||||
}
|
||||
if (file->deltapeer) {
|
||||
printf("\tdeltapeer %s(%s)\n", file->deltapeer->filename, file->deltapeer->hash);
|
||||
}
|
||||
if (file->staging) {
|
||||
printf("\tstaging %s\n", file->staging);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void free_file_data(void *data)
|
||||
{
|
||||
struct file *file = (struct file *) data;
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* peer and deltapeer are pointers to files contained
|
||||
* in another list and must not be disposed */
|
||||
|
||||
if (file->filename) {
|
||||
free(file->filename);
|
||||
}
|
||||
|
||||
if (file->header) {
|
||||
free(file->header);
|
||||
}
|
||||
|
||||
if (file->staging) {
|
||||
free(file->staging);
|
||||
}
|
||||
|
||||
free(file);
|
||||
}
|
||||
|
||||
/* this function is intended to encapsulate the basic swupd
|
||||
* initializations for the majority of commands, that is:
|
||||
* - Make sure root is the user running the code
|
||||
* - Initialize log facility
|
||||
* - Get the lock
|
||||
* - initialize mounted directories
|
||||
* - Initialize curl
|
||||
*/
|
||||
int swupd_init(int *lock_fd)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
check_root();
|
||||
*lock_fd = p_lockfile();
|
||||
if (*lock_fd < 0) {
|
||||
ret = ELOCK_FILE;
|
||||
goto out_fds;
|
||||
}
|
||||
|
||||
get_mounted_directories();
|
||||
|
||||
if (swupd_curl_init() != 0) {
|
||||
ret = ECURL_INIT;
|
||||
goto out_close_lock;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
out_close_lock:
|
||||
v_lockfile(*lock_fd);
|
||||
out_fds:
|
||||
dump_file_descriptor_leaks();
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
/* this function prints the initial message for all utils
|
||||
*/
|
||||
void copyright_header(const char *name)
|
||||
{
|
||||
printf(PACKAGE " %s " VERSION "\n", name);
|
||||
printf(" Copyright (C) 2012-2016 Intel Corporation\n");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void string_or_die(char **strp, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
if (vasprintf(strp, fmt, ap) <= 0) {
|
||||
abort();
|
||||
}
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void update_motd(int new_release)
|
||||
{
|
||||
FILE *motd_fp = NULL;
|
||||
|
||||
motd_fp = fopen(MOTD_FILE, "w");
|
||||
|
||||
if (motd_fp != NULL) {
|
||||
fprintf(motd_fp, "There is a new OS version available: %d\n", new_release);
|
||||
fprintf(motd_fp, "Upgrade to the latest version using 'swupd update'\n");
|
||||
fclose(motd_fp);
|
||||
}
|
||||
}
|
||||
|
||||
void delete_motd(void)
|
||||
{
|
||||
if (unlink(MOTD_FILE) == ENOMEM) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
int is_dirname_link(const char *fullname)
|
||||
{
|
||||
int ret = -1;
|
||||
char *real_path = NULL;
|
||||
real_path = realpath(fullname, NULL);
|
||||
if (!real_path) {
|
||||
printf("Failed to get real path of %s\n", fullname);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (strcmp(real_path, fullname) != 0) {
|
||||
ret = 1;
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
free(real_path);
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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:
|
||||
* Timothy C. Pepper <timothy.c.pepper@linux.intel.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "swupd.h"
|
||||
|
||||
/* trailing slash is to indicate dir itself is expected to exist, but
|
||||
* contents are ignored */
|
||||
bool is_config(char *filename)
|
||||
{
|
||||
if (strncmp(filename, "/etc/", 5) == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void config_file_heuristics(struct file *file)
|
||||
{
|
||||
if (is_config(file->filename)) {
|
||||
file->is_config = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* trailing slash is to indicate dir itself is expected to exist, but
|
||||
* contents are ignored */
|
||||
bool is_state(char *filename)
|
||||
{
|
||||
if (is_directory_mounted(filename)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_under_mounted_directory(filename)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((strlen(filename) == 14) && (strncmp(filename, "/usr/src/debug", 14) == 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((strncmp(filename, "/data", 5) == 0) ||
|
||||
(strncmp(filename, "/dev/", 5) == 0) ||
|
||||
(strncmp(filename, "/home/", 6) == 0) ||
|
||||
(strncmp(filename, "/lost+found", 11) == 0) ||
|
||||
(strncmp(filename, "/proc/", 6) == 0) ||
|
||||
(strncmp(filename, "/root/", 6) == 0) ||
|
||||
(strncmp(filename, "/run/", 5) == 0) ||
|
||||
(strncmp(filename, "/sys/", 5) == 0) ||
|
||||
(strncmp(filename, "/tmp/", 5) == 0) ||
|
||||
(strncmp(filename, "/usr/src/", 9) == 0) ||
|
||||
(strncmp(filename, "/var/", 5) == 0)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void runtime_state_heuristics(struct file *file)
|
||||
{
|
||||
if (is_state(file->filename)) {
|
||||
file->is_state = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void boot_file_heuristics(struct file *file)
|
||||
{
|
||||
if ((strncmp(file->filename, "/boot/", 6) == 0) ||
|
||||
(strncmp(file->filename, "/usr/lib/modules/", 17) == 0)) {
|
||||
file->is_boot = 1;
|
||||
}
|
||||
|
||||
if (strncmp(file->filename, "/usr/lib/kernel/", 16) == 0) {
|
||||
file->is_boot = 1;
|
||||
need_update_boot = true;
|
||||
}
|
||||
|
||||
if ((strncmp(file->filename, "/usr/lib/gummiboot", 18) == 0) ||
|
||||
(strncmp(file->filename, "/usr/bin/gummiboot", 18) == 0) ||
|
||||
(strncmp(file->filename, "/usr/bin/bootctl", 16) == 0) ||
|
||||
(strncmp(file->filename, "/usr/lib/systemd/boot", 21) == 0)) {
|
||||
file->is_boot = 1;
|
||||
need_update_bootloader = true;
|
||||
}
|
||||
}
|
||||
|
||||
void apply_heuristics(struct file *file)
|
||||
{
|
||||
runtime_state_heuristics(file);
|
||||
boot_file_heuristics(file);
|
||||
config_file_heuristics(file);
|
||||
}
|
||||
|
||||
bool ignore(struct file *file)
|
||||
{
|
||||
if ((file->is_config) ||
|
||||
is_config(file->filename) || // ideally we trust the manifest but short term reapply check here
|
||||
(file->is_state) ||
|
||||
is_state(file->filename) || // ideally we trust the manifest but short term reapply check here
|
||||
(file->is_boot && fix && file->is_deleted) || // shouldn't happen
|
||||
(file->is_boot && !fix && !file->is_deleted) || // default ignore
|
||||
(ignore_orphans && file->is_orphan)) {
|
||||
update_skip++;
|
||||
file->do_not_update = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
+285
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* 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:
|
||||
* pplaquet <paul.plaquette@intel.com>
|
||||
* Eric Lapuyade <eric.lapuyade@intel.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include "list.h"
|
||||
|
||||
static struct list *list_append_item(struct list *list, struct list *item)
|
||||
{
|
||||
list = list_tail(list);
|
||||
if (list) {
|
||||
list->next = item;
|
||||
item->prev = list;
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
static struct list *list_prepend_item(struct list *list, struct list *item)
|
||||
{
|
||||
list = list_head(list);
|
||||
if (list) {
|
||||
list->prev = item;
|
||||
item->next = list;
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
static struct list *list_alloc_item(void *data)
|
||||
{
|
||||
struct list *item;
|
||||
|
||||
item = (struct list *)malloc(sizeof(struct list));
|
||||
if (item) {
|
||||
item->data = data;
|
||||
item->next = NULL;
|
||||
item->prev = NULL;
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
// Merges two sorted lists
|
||||
static struct list *list_merge(struct list *list1, struct list *list2, comparison_fn_t comparison_fn) {
|
||||
struct list *merged_list = NULL;
|
||||
struct list *merged_list_head = NULL;
|
||||
while (list1 && list2) {
|
||||
if (comparison_fn(list1->data, list2->data) < 0) {
|
||||
if (merged_list) {
|
||||
merged_list->next = list1;
|
||||
list1->prev = merged_list;
|
||||
}
|
||||
merged_list = list1;
|
||||
list1 = list1->next;
|
||||
} else {
|
||||
if (merged_list) {
|
||||
merged_list->next = list2;
|
||||
list2->prev = merged_list;
|
||||
}
|
||||
merged_list = list2;
|
||||
list2 = list2->next;
|
||||
}
|
||||
if (!merged_list_head) {
|
||||
merged_list_head = merged_list;
|
||||
}
|
||||
}
|
||||
if (list1 != NULL) {
|
||||
merged_list->next = list1;
|
||||
list1->prev = merged_list;
|
||||
}
|
||||
if (list2 != NULL) {
|
||||
merged_list->next = list2;
|
||||
list2->prev = merged_list;
|
||||
}
|
||||
return merged_list_head;
|
||||
}
|
||||
|
||||
/* Splits a list into two halves to be merged */
|
||||
static struct list *list_merge_sort(struct list *left, unsigned int len, comparison_fn_t comparison_fn)
|
||||
{
|
||||
struct list *right;
|
||||
unsigned int left_len = len / 2;
|
||||
unsigned int right_len = len / 2 + len % 2;
|
||||
unsigned int i;
|
||||
|
||||
if (!left) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (len == 1) {
|
||||
return left;
|
||||
}
|
||||
|
||||
right = left;
|
||||
|
||||
/* Split into left and right lists */
|
||||
for (i = 0; i < left_len; i++) {
|
||||
right = right->next;
|
||||
}
|
||||
right->prev->next = NULL;
|
||||
right->prev = NULL;
|
||||
|
||||
/* Recurse */
|
||||
left = list_merge_sort(left, left_len, comparison_fn);
|
||||
right = list_merge_sort(right, right_len, comparison_fn);
|
||||
return list_merge(left, right, comparison_fn);
|
||||
}
|
||||
|
||||
/* ------------ Public API ------------ */
|
||||
|
||||
struct list *list_append_data(struct list *list, void *data)
|
||||
{
|
||||
struct list *item = NULL;
|
||||
|
||||
item = list_alloc_item(data);
|
||||
if (item) {
|
||||
item = list_append_item(list, item);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
struct list *list_prepend_data(struct list *list, void *data)
|
||||
{
|
||||
struct list *item = NULL;
|
||||
|
||||
item = list_alloc_item(data);
|
||||
if (item) {
|
||||
item = list_prepend_item(list, item);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
struct list *list_head(struct list *item)
|
||||
{
|
||||
if (item) {
|
||||
while (item->prev) {
|
||||
item = item->prev;
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
struct list *list_tail(struct list *item)
|
||||
{
|
||||
if (item) {
|
||||
while (item->next) {
|
||||
item = item->next;
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
unsigned int list_len(struct list *list)
|
||||
{
|
||||
unsigned int len;
|
||||
struct list *item;
|
||||
|
||||
if (list == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
len = 1;
|
||||
|
||||
item = list;
|
||||
while ((item = item->next) != NULL) {
|
||||
len++;
|
||||
}
|
||||
|
||||
item = list;
|
||||
while ((item = item->prev) != NULL) {
|
||||
len++;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
#if 0
|
||||
struct list *list_find_data(struct list *list, void *data)
|
||||
{
|
||||
list = list_head(list);
|
||||
while (list) {
|
||||
if (list->data == data) {
|
||||
return list;
|
||||
}
|
||||
list = list->next;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct list *list_sort(struct list *list, comparison_fn_t comparison_fn)
|
||||
{
|
||||
list = list_head(list);
|
||||
unsigned int len = list_len(list);
|
||||
return list_merge_sort(list, len, comparison_fn);
|
||||
}
|
||||
|
||||
struct list *list_concat(struct list *list1, struct list *list2)
|
||||
{
|
||||
struct list *tail;
|
||||
|
||||
list2 = list_head(list2);
|
||||
|
||||
if (list1 == NULL) {
|
||||
return list2;
|
||||
}
|
||||
|
||||
list1 = list_head(list1);
|
||||
|
||||
if (list2) {
|
||||
tail = list_tail(list1);
|
||||
|
||||
tail->next = list2;
|
||||
list2->prev = tail;
|
||||
}
|
||||
|
||||
return list1;
|
||||
}
|
||||
|
||||
struct list *list_free_item(struct list *item, list_free_data_fn_t list_free_data_fn)
|
||||
{
|
||||
struct list *ret_item;
|
||||
|
||||
if (item->prev) {
|
||||
item->prev->next = item->next;
|
||||
ret_item = item->prev;
|
||||
} else {
|
||||
ret_item = item->next;
|
||||
}
|
||||
|
||||
if (item->next) {
|
||||
item->next->prev = item->prev;
|
||||
}
|
||||
|
||||
if (list_free_data_fn) {
|
||||
list_free_data_fn(item->data);
|
||||
}
|
||||
|
||||
free(item);
|
||||
|
||||
return ret_item;
|
||||
}
|
||||
|
||||
void list_free_list_and_data(struct list *list, list_free_data_fn_t list_free_data_fn)
|
||||
{
|
||||
struct list *item = list_head(list);
|
||||
|
||||
while (item) {
|
||||
item = list_free_item(item, list_free_data_fn);
|
||||
}
|
||||
}
|
||||
|
||||
void list_free_list(struct list *list)
|
||||
{
|
||||
list_free_list_and_data(list, NULL);
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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>
|
||||
* Eric Lapuyade <eric.lapuyade@intel.com>
|
||||
* Jim Kukunas <james.t.kukunas@linux.intel.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "swupd.h"
|
||||
|
||||
static int mklockdir(void)
|
||||
{
|
||||
char *cmd;
|
||||
int ret;
|
||||
struct stat buf;
|
||||
|
||||
memset(&buf, 0, sizeof(struct stat));
|
||||
ret = stat(LOCK_DIR, &buf);
|
||||
if ((ret == 0) && (S_ISDIR(buf.st_mode))) {
|
||||
return 0;
|
||||
} else if (force) {
|
||||
/* lock dir is not a directory, so with force, clean it up */
|
||||
unlink(LOCK_DIR);
|
||||
}
|
||||
|
||||
string_or_die(&cmd, "mkdir -m 755 -p %s", LOCK_DIR);
|
||||
|
||||
ret = system(cmd);
|
||||
free(cmd);
|
||||
if (ret) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Try to get a write lock region on the lock file. Returns:
|
||||
* >= 0 an fcntl region lock'd fd or exits with a positive error
|
||||
* code and a recommended course of action for user.
|
||||
*/
|
||||
int p_lockfile(void)
|
||||
{
|
||||
int lock_fd, ret;
|
||||
pid_t pid = getpid();
|
||||
struct flock fl = {
|
||||
.l_type = F_WRLCK,
|
||||
.l_whence = SEEK_SET,
|
||||
.l_start = 0,
|
||||
.l_len = 0,
|
||||
.l_pid = pid,
|
||||
};
|
||||
char *lockfile;
|
||||
|
||||
if (mklockdir() < 0) {
|
||||
printf("Error: Unable to create lock dir: '%s'\n", LOCK_DIR);
|
||||
if (!force) {
|
||||
printf(" Solution: Create manually or re-run with '--force'\n");
|
||||
printf("Operation Failed\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
printf(" --force used. Attempting to continue\n");
|
||||
}
|
||||
|
||||
string_or_die(&lockfile, "%s/swupd_lock", LOCK_DIR);
|
||||
|
||||
/* open lock file */
|
||||
lock_fd = open(lockfile, O_RDWR | O_CREAT | O_CLOEXEC, 0600);
|
||||
free(lockfile);
|
||||
|
||||
if (lock_fd < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* try to get an advisory region lock for the file */
|
||||
ret = fcntl(lock_fd, F_SETLK, &fl);
|
||||
if (ret == -1) {
|
||||
if ((errno == EAGAIN) || (errno == EACCES)) {
|
||||
ret = -EAGAIN;
|
||||
}
|
||||
close(lock_fd);
|
||||
return ret;
|
||||
} else {
|
||||
/* speculatively dump our pid in the file,
|
||||
* that may be useful for debug */
|
||||
ret = ftruncate(lock_fd, 0);
|
||||
ret = write(lock_fd, &pid, sizeof(pid));
|
||||
|
||||
/* our lock_fd represents the lock */
|
||||
return lock_fd;
|
||||
}
|
||||
}
|
||||
|
||||
/* closes lock fd and must not unlink lock file (else race allowed) */
|
||||
void v_lockfile(int fd)
|
||||
{
|
||||
close(fd);
|
||||
}
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* 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 <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <getopt.h>
|
||||
#include <libgen.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "swupd.h"
|
||||
|
||||
static bool cmd_line_status = false;
|
||||
|
||||
static const struct option prog_opts[] = {
|
||||
{"download", no_argument, 0, 'd'},
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"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'},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
static void print_help(const char *name) {
|
||||
printf("Usage:\n");
|
||||
printf(" swupd %s [OPTION...]\n\n", basename((char*)name));
|
||||
printf("Help Options:\n");
|
||||
printf(" -h, --help Show help options\n\n");
|
||||
printf("Application Options:\n");
|
||||
printf(" -d, --download Download all content, but do not actually install the update\n");
|
||||
#warning remove user configurable url when alternative exists
|
||||
printf(" -u, --url=[URL] RFC-3986 encoded url for version string and content file downloads\n");
|
||||
printf(" -P, --port=[port #] Port number to connect to at the url for version string and content file downloads\n");
|
||||
#warning remove user configurable content url when alternative exists
|
||||
printf(" -c, --contenturl=[URL] RFC-3986 encoded url for content file downloads\n");
|
||||
#warning remove user configurable version url when alternative exists
|
||||
printf(" -v, --versionurl=[URL] RFC-3986 encoded url for version string download\n");
|
||||
printf(" -s, --status Show current OS version and latest version available on server\n");
|
||||
printf(" -F, --format=[staging,1,2,etc.] the format suffix for version file downloads\n");
|
||||
printf(" -p, --path=[PATH...] Use [PATH...] as the path to verify (eg: a chroot or btrfs subvol\n");
|
||||
printf(" -x, --force Attempt to proceed even if non-critical errors found\n");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static bool parse_options(int argc, char **argv)
|
||||
{
|
||||
int opt;
|
||||
|
||||
//set default initial values
|
||||
set_format_string(NULL);
|
||||
|
||||
while ((opt = getopt_long(argc, argv, "hxdu:P:c:v:sF:p:", prog_opts, NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case '?':
|
||||
case 'h':
|
||||
print_help(argv[0]);
|
||||
exit(EXIT_SUCCESS);
|
||||
case 'd':
|
||||
download_only = true;
|
||||
break;
|
||||
case 'u':
|
||||
if (!optarg) {
|
||||
printf("Invalid --url argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
if (version_server_urls[0]) {
|
||||
free(version_server_urls[0]);
|
||||
}
|
||||
if (content_server_urls[0]) {
|
||||
free(content_server_urls[0]);
|
||||
}
|
||||
string_or_die(&version_server_urls[0], "%s", optarg);
|
||||
string_or_die(&content_server_urls[0], "%s", optarg);
|
||||
break;
|
||||
case 'P':
|
||||
if (sscanf(optarg, "%ld", &update_server_port) != 1) {
|
||||
printf("Invalid --port argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
break;
|
||||
case 'c':
|
||||
if (!optarg) {
|
||||
printf("Invalid --contenturl argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
if (content_server_urls[0]) {
|
||||
free(content_server_urls[0]);
|
||||
}
|
||||
string_or_die(&content_server_urls[0], "%s", optarg);
|
||||
break;
|
||||
case 'v':
|
||||
if (!optarg) {
|
||||
printf("Invalid --versionurl argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
if (version_server_urls[0]) {
|
||||
free(version_server_urls[0]);
|
||||
}
|
||||
string_or_die(&version_server_urls[0], "%s", optarg);
|
||||
break;
|
||||
case 's':
|
||||
cmd_line_status = true;
|
||||
break;
|
||||
case 'F':
|
||||
if (!optarg || !set_format_string(optarg)) {
|
||||
printf("Invalid --format argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
break;
|
||||
case 'p': /* default empty path_prefix updates the running OS */
|
||||
if (!optarg) {
|
||||
printf("Invalid --path argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
if (path_prefix) { /* multiple -p options */
|
||||
free(path_prefix);
|
||||
}
|
||||
string_or_die(&path_prefix, "%s", optarg);
|
||||
break;
|
||||
case 'x':
|
||||
force = true;
|
||||
break;
|
||||
default:
|
||||
printf("Unrecognized option\n\n");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if (!init_globals()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
err:
|
||||
print_help(argv[0]);
|
||||
return false;
|
||||
}
|
||||
|
||||
static void print_versions()
|
||||
{
|
||||
int current_version, server_version;
|
||||
|
||||
swupd_curl_init();
|
||||
read_versions(¤t_version, ¤t_version, &server_version, path_prefix);
|
||||
|
||||
if (current_version < 0) {
|
||||
printf("Cannot determine current OS version\n");
|
||||
} else {
|
||||
printf("Current OS version: %d\n", current_version);
|
||||
}
|
||||
|
||||
if (server_version < 0) {
|
||||
printf("Cannot get latest the server version.Could not reach server\n");
|
||||
} else {
|
||||
printf("Latest server version: %d\n", server_version);
|
||||
}
|
||||
}
|
||||
|
||||
int update_main(int argc, char **argv)
|
||||
{
|
||||
int ret = 0;
|
||||
copyright_header("software update");
|
||||
|
||||
if (!parse_options(argc, argv)) {
|
||||
free_globals();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (cmd_line_status) {
|
||||
print_versions();
|
||||
} else {
|
||||
ret = main_update();
|
||||
}
|
||||
free_globals();
|
||||
return ret;
|
||||
}
|
||||
+1130
File diff suppressed because it is too large
Load Diff
+134
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "swupd-build-variant.h"
|
||||
#include "swupd.h"
|
||||
#include "signature.h"
|
||||
|
||||
|
||||
static int download_pack(int oldversion, int newversion, char *module)
|
||||
{
|
||||
FILE *tarfile = NULL;
|
||||
char *tar = NULL;
|
||||
char *url = NULL;
|
||||
int err = -1;
|
||||
char *filename;
|
||||
struct stat stat;
|
||||
|
||||
string_or_die(&filename, "%s/pack-%s-from-%i-to-%i.tar", STATE_DIR, module, oldversion, newversion);
|
||||
|
||||
err = lstat(filename, &stat);
|
||||
if (err == 0 && stat.st_size == 0) {
|
||||
free(filename);
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("Downloading %s pack for version %i\n", module, newversion);
|
||||
|
||||
string_or_die(&url, "%s/%i/pack-%s-from-%i.tar", preferred_content_url, newversion, module, oldversion);
|
||||
|
||||
err = swupd_curl_get_file(url, filename, NULL, NULL, true);
|
||||
if (err) {
|
||||
free(url);
|
||||
if ((lstat(filename, &stat) == 0) && (stat.st_size == 0)) {
|
||||
unlink(filename);
|
||||
}
|
||||
free(filename);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!signature_download_and_verify(url, filename)) {
|
||||
free(url);
|
||||
unlink(filename);
|
||||
free(filename);
|
||||
return -1;
|
||||
}
|
||||
|
||||
free(url);
|
||||
|
||||
printf("Extracting pack.\n");
|
||||
string_or_die(&tar, "tar -C %s " TAR_PERM_ATTR_ARGS " -xf %s/pack-%s-from-%i-to-%i.tar 2> /dev/null",
|
||||
STATE_DIR, STATE_DIR, module, oldversion, newversion);
|
||||
|
||||
err = system(tar);
|
||||
if (WIFEXITED(err)) {
|
||||
err = WEXITSTATUS(err);
|
||||
}
|
||||
free(tar);
|
||||
unlink(filename);
|
||||
/* make a zero sized file to prevent redownload */
|
||||
tarfile = fopen(filename, "w");
|
||||
free(filename);
|
||||
if (tarfile) {
|
||||
fclose(tarfile);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/* pull in packs for base and any subscription */
|
||||
int download_subscribed_packs(int oldversion, int UNUSED_PARAM newversion, bool required)
|
||||
{
|
||||
struct list *iter;
|
||||
struct sub *sub = NULL;
|
||||
int err;
|
||||
|
||||
if (!check_network()) {
|
||||
return -ENOSWUPDSERVER;
|
||||
}
|
||||
|
||||
iter = list_head(subs);
|
||||
while (iter) {
|
||||
sub = iter->data;
|
||||
iter = iter->next;
|
||||
|
||||
if (sub->oldversion == sub->version) { // pack didn't change in this release
|
||||
continue;
|
||||
}
|
||||
|
||||
if (oldversion != 0) {
|
||||
oldversion = sub->oldversion;
|
||||
}
|
||||
|
||||
err = download_pack(oldversion, sub->version, sub->component);
|
||||
if (err < 0) {
|
||||
if (required) {
|
||||
return err;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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>
|
||||
* Timothy C. Pepper <timothy.c.pepper@linux.intel.com>
|
||||
* Tudor Marcu <tudor.marcu@intel.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "swupd.h"
|
||||
|
||||
static void update_kernel(void)
|
||||
{
|
||||
char *kernel_update_cmd = NULL;
|
||||
|
||||
if (strcmp("/", path_prefix) == 0) {
|
||||
string_or_die(&kernel_update_cmd, "/usr/bin/kernel_updater.sh");
|
||||
} else {
|
||||
string_or_die(&kernel_update_cmd, "%s/usr/bin/kernel_updater.sh --path %s", path_prefix, path_prefix);
|
||||
}
|
||||
|
||||
system(kernel_update_cmd);
|
||||
free(kernel_update_cmd);
|
||||
}
|
||||
|
||||
static void update_bootloader(void)
|
||||
{
|
||||
char *bootloader_update_cmd = NULL;
|
||||
|
||||
if (strcmp("/", path_prefix) == 0) {
|
||||
string_or_die(&bootloader_update_cmd, "/usr/bin/gummiboot_updaters.sh");
|
||||
} else {
|
||||
string_or_die(&bootloader_update_cmd, "%s/usr/bin/gummiboot_updaters.sh --path %s", path_prefix, path_prefix);
|
||||
}
|
||||
|
||||
system(bootloader_update_cmd);
|
||||
free(bootloader_update_cmd);
|
||||
|
||||
if (strcmp("/", path_prefix) == 0) {
|
||||
string_or_die(&bootloader_update_cmd, "/usr/bin/systemdboot_updater.sh");
|
||||
} else {
|
||||
string_or_die(&bootloader_update_cmd, "%s/usr/bin/systemdboot_updater.sh --path %s", path_prefix, path_prefix);
|
||||
}
|
||||
|
||||
system(bootloader_update_cmd);
|
||||
free(bootloader_update_cmd);
|
||||
}
|
||||
|
||||
static void update_triggers(void)
|
||||
{
|
||||
system("/usr/bin/systemctl daemon-reload");
|
||||
system("/usr/bin/systemctl restart update-triggers.target");
|
||||
}
|
||||
|
||||
void run_scripts(void)
|
||||
{
|
||||
printf("Calling post-update helper scripts.\n");
|
||||
|
||||
/* path_prefix aware helper */
|
||||
if (need_update_boot) {
|
||||
update_kernel();
|
||||
} else {
|
||||
printf("No kernel update needed, skipping helper call out.\n");
|
||||
}
|
||||
|
||||
if (need_update_bootloader) {
|
||||
update_bootloader();
|
||||
} else {
|
||||
printf("No bootloader update needed, skipping helper call out.\n");
|
||||
}
|
||||
|
||||
/* helpers which don't run when path_prefix is set */
|
||||
if (strcmp("/", path_prefix) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Crudely call post-update hooks after every update...FIXME */
|
||||
update_triggers();
|
||||
}
|
||||
|
||||
/* Run any "mandatory" pre-update scripts needed. In this case, mandatory
|
||||
* means the script must run, but it is not yet fatal if the script does not
|
||||
* return success */
|
||||
void run_preupdate_scripts(struct manifest *manifest)
|
||||
{
|
||||
struct list *iter = list_tail(manifest->files);
|
||||
struct file *file;
|
||||
struct stat sb;
|
||||
char *script;
|
||||
|
||||
string_or_die(&script, "/usr/bin/clr_pre_update.sh");
|
||||
|
||||
if (stat(script, &sb) == -1) {
|
||||
free(script);
|
||||
return;
|
||||
}
|
||||
|
||||
while (iter != NULL) {
|
||||
file = iter->data;
|
||||
iter = iter->prev;
|
||||
|
||||
if (strcmp(file->filename, script) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check that system file matches file in manifest */
|
||||
if (verify_file(file, script)) {
|
||||
system(script);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(script);
|
||||
}
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* 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:
|
||||
* Tom Keel <thomas.keel@intel.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/pem.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "signature.h"
|
||||
#include "swupd.h"
|
||||
|
||||
|
||||
/*
|
||||
* Implementation flavors:
|
||||
* FAKE ..... do nothing, always return success
|
||||
* FORGIVE .. do everything, always return success
|
||||
* REAL ..... do everything, return the real status
|
||||
*/
|
||||
#define IMPL_FAKE 0
|
||||
#define IMPL_FORGIVE 1
|
||||
#define IMPL_REAL 2
|
||||
|
||||
#warning TODO pick signing scheme
|
||||
#if defined(SWUPD_LINUX_ROOTFS)
|
||||
#define IMPL IMPL_FAKE
|
||||
#endif
|
||||
|
||||
#if IMPL != IMPL_FAKE
|
||||
|
||||
static X509_STORE *create_store(const char *, const char *, const char *);
|
||||
|
||||
static char *VERIF_FAIL = "Signature verification failed";
|
||||
static char *XSTORE_FAIL = "XSTORE creation failed";
|
||||
|
||||
static bool initialized = false;
|
||||
|
||||
static X509_STORE *x509_store = NULL;
|
||||
|
||||
bool signature_initialize(const char *ca_cert_filename)
|
||||
{
|
||||
if (initialized) {
|
||||
return true;
|
||||
}
|
||||
OpenSSL_add_all_algorithms();
|
||||
ERR_load_crypto_strings();
|
||||
x509_store = create_store(ca_cert_filename, NULL, NULL);
|
||||
if (x509_store == NULL) {
|
||||
ERR_free_strings(); // undoes ERR_load_crypto_strings
|
||||
EVP_cleanup(); // undoes OpenSSL_add_all_algorithms
|
||||
return false || (IMPL == IMPL_FORGIVE);
|
||||
}
|
||||
initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void signature_terminate(void)
|
||||
{
|
||||
if (initialized) {
|
||||
X509_STORE_free(x509_store); // undocumented...
|
||||
ERR_free_strings(); // undoes ERR_load_crypto_strings
|
||||
EVP_cleanup(); // undoes OpenSSL_add_all_algorithms
|
||||
initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool signature_verify(const char *data_filename, const char *sig_filename)
|
||||
{
|
||||
BIO *bio_data = NULL;
|
||||
BIO *bio_sig = NULL;
|
||||
PKCS7 *pkcs7 = NULL;
|
||||
int ret;
|
||||
bool result = false;
|
||||
|
||||
if (!initialized) {
|
||||
return false || (IMPL == IMPL_FORGIVE);
|
||||
}
|
||||
bio_data = BIO_new_file(data_filename, "r"); // i.e. fopen
|
||||
if (bio_data == NULL) {
|
||||
goto exit;
|
||||
}
|
||||
bio_sig = BIO_new_file(sig_filename, "r"); // i.e. fopen
|
||||
if (bio_sig == NULL) {
|
||||
goto exit;
|
||||
}
|
||||
pkcs7 = PEM_read_bio_PKCS7(bio_sig, NULL, NULL, NULL);
|
||||
if (pkcs7 == NULL) {
|
||||
goto exit;
|
||||
}
|
||||
ret = PKCS7_verify(pkcs7, NULL, x509_store, bio_data, NULL, 0);
|
||||
if (ret != 1) {
|
||||
goto exit;
|
||||
}
|
||||
result = true;
|
||||
exit:
|
||||
/*
|
||||
* The free functions below tolerate NULL arguments.
|
||||
* The documentation doesn't really say so, but both testing and
|
||||
* examination of openssl source code confirm that such is the case.
|
||||
*/
|
||||
PKCS7_free(pkcs7); // undocumented...
|
||||
BIO_free(bio_sig); // i.e. fclose
|
||||
BIO_free(bio_data); // i.e. fclose
|
||||
return result || (IMPL == IMPL_FORGIVE);
|
||||
}
|
||||
|
||||
static X509_STORE *create_store(const char *ca_filename, const char *ca_dirname,
|
||||
const char *crl_filename)
|
||||
{
|
||||
X509_STORE *store = X509_STORE_new();
|
||||
|
||||
if (!store) {
|
||||
return NULL;
|
||||
}
|
||||
if (X509_STORE_load_locations(store, ca_filename, ca_dirname) != 1) {
|
||||
goto err;
|
||||
}
|
||||
if (X509_STORE_set_default_paths(store) != 1) {
|
||||
goto err;
|
||||
}
|
||||
if (crl_filename) {
|
||||
X509_LOOKUP *lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
|
||||
if (!lookup) {
|
||||
goto err;
|
||||
}
|
||||
if (X509_load_crl_file(lookup, crl_filename, X509_FILETYPE_PEM) != 1) {
|
||||
goto err;
|
||||
}
|
||||
X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
|
||||
}
|
||||
return store;
|
||||
err:
|
||||
X509_STORE_free(x509_store);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool signature_download_and_verify(const char *data_url, const char *data_filename)
|
||||
{
|
||||
char *sig_url;
|
||||
char *sig_filename;
|
||||
int ret;
|
||||
bool result;
|
||||
|
||||
string_or_die(&sig_url, "%s.signed", data_url);
|
||||
|
||||
string_or_die(&sig_filename, "%s.signed", data_filename);
|
||||
|
||||
ret = swupd_curl_get_file(sig_url, sig_filename, NULL, NULL, false);
|
||||
if (ret) {
|
||||
result = false;
|
||||
} else {
|
||||
result = signature_verify(data_filename, sig_filename);
|
||||
}
|
||||
if (!result) {
|
||||
unlink(sig_filename);
|
||||
}
|
||||
free(sig_filename);
|
||||
free(sig_url);
|
||||
return result || (IMPL == IMPL_FORGIVE);
|
||||
}
|
||||
|
||||
void signature_delete(const char *data_filename)
|
||||
{
|
||||
char *sig_filename;
|
||||
|
||||
string_or_die(&sig_filename, "%s.signed", data_filename);
|
||||
|
||||
unlink(sig_filename);
|
||||
|
||||
free(sig_filename);
|
||||
}
|
||||
|
||||
#else // IMPL == IMPL_FAKE
|
||||
|
||||
bool signature_initialize(const char UNUSED_PARAM *ca_cert_filename)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void signature_terminate(void)
|
||||
{
|
||||
}
|
||||
|
||||
bool signature_verify(const char UNUSED_PARAM *data_filename, const char UNUSED_PARAM *sig_filename)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool signature_download_and_verify(const char UNUSED_PARAM *data_url, const char UNUSED_PARAM *data_filename)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void signature_delete(const char UNUSED_PARAM *data_filename)
|
||||
{
|
||||
}
|
||||
|
||||
#endif // IMPL == IMPL_FAKE
|
||||
+340
@@ -0,0 +1,340 @@
|
||||
/*
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <libgen.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "swupd-build-variant.h"
|
||||
#include "swupd.h"
|
||||
|
||||
/* clean then recreate temporary folder for tar renames */
|
||||
static int create_staging_renamedir(char *rename_tmpdir)
|
||||
{
|
||||
int ret;
|
||||
char *rmcommand = NULL;
|
||||
|
||||
string_or_die(&rmcommand, "rm -fr %s", rename_tmpdir);
|
||||
if (!system(rmcommand)) {
|
||||
/* Not fatal but pretty scary, likely to really fail at the
|
||||
* next command too. Pass for now as printing may just cause
|
||||
* confusion */
|
||||
;
|
||||
}
|
||||
free(rmcommand);
|
||||
|
||||
ret = mkdir(rename_tmpdir, S_IRWXU);
|
||||
if (ret == -1 && errno != EEXIST) {
|
||||
ret = -errno;
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Do the staging of new files into the filesystem */
|
||||
#warning do_staging is currently not able to be run in parallel
|
||||
/* Consider adding a remove_leftovers() that runs in verify/fix in order to
|
||||
* allow this function to mkdtemp create folders for parallel build */
|
||||
int do_staging(struct file *file)
|
||||
{
|
||||
char *statfile = NULL, *tmp = NULL, *tmp2 = NULL;
|
||||
char *dir, *base, *rel_dir;
|
||||
char *tarcommand = NULL;
|
||||
char *original = NULL;
|
||||
char *target = NULL;
|
||||
char *targetpath = NULL;
|
||||
char *symbase = NULL;
|
||||
char *rename_target = NULL;
|
||||
char *rename_tmpdir = NULL;
|
||||
int ret;
|
||||
struct stat s;
|
||||
|
||||
tmp = strdup(file->filename);
|
||||
tmp2 = strdup(file->filename);
|
||||
|
||||
dir = dirname(tmp);
|
||||
base = basename(tmp2);
|
||||
|
||||
rel_dir = dir;
|
||||
if (*dir == '/') {
|
||||
rel_dir = dir + 1;
|
||||
}
|
||||
|
||||
string_or_die(&original, "%s/staged/%s", STATE_DIR, file->hash);
|
||||
|
||||
string_or_die(&targetpath, "%s%s", path_prefix, rel_dir);
|
||||
ret = stat(targetpath, &s);
|
||||
|
||||
if (S_ISLNK(s.st_mode)) {
|
||||
/* Follow symlink to ultimate target and redo stat */
|
||||
symbase = realpath(targetpath, NULL);
|
||||
if (symbase != NULL) {
|
||||
free(targetpath);
|
||||
targetpath = strdup(symbase);
|
||||
ret = stat(targetpath, &s);
|
||||
free(symbase);
|
||||
}
|
||||
}
|
||||
|
||||
/* For now, just report on error conditions. Once we implement
|
||||
* verify_fix_path(char *path, int targetversion), we'll want to call it here */
|
||||
if ((ret == -1) && (errno == ENOENT)) {
|
||||
printf("Error: Update target directory does not exist: %s\n", targetpath);
|
||||
} else if (!S_ISDIR(s.st_mode)) {
|
||||
printf("Error: Update target exists but is NOT a directory: %s\n", targetpath);
|
||||
}
|
||||
|
||||
free(targetpath);
|
||||
string_or_die(&target, "%s%s/.update.%s", path_prefix, rel_dir, base);
|
||||
ret = swupd_rm(target);
|
||||
if (ret < 0 && ret != -ENOENT) {
|
||||
printf("Error: Failed to remove %s\n", target);
|
||||
}
|
||||
|
||||
string_or_die(&statfile, "%s%s", path_prefix, file->filename);
|
||||
|
||||
memset(&s, 0, sizeof(struct stat));
|
||||
ret = lstat(statfile, &s);
|
||||
if (ret == 0) {
|
||||
if ((file->is_dir && !S_ISDIR(s.st_mode)) ||
|
||||
(file->is_link && !S_ISLNK(s.st_mode)) ||
|
||||
(file->is_file && !S_ISREG(s.st_mode))) {
|
||||
//file type changed, move old out of the way for new
|
||||
ret = swupd_rm(statfile);
|
||||
if (ret < 0) {
|
||||
ret = -ETYPE_CHANGED_FILE_RM;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(statfile);
|
||||
|
||||
if (file->is_dir || S_ISDIR(s.st_mode)) {
|
||||
/* In the btrfs only scenario there was an implicit
|
||||
* "create_or_update_dir()" via un-tar-ing a directory.tar after
|
||||
* download and the untar happens in the staging subvolume which
|
||||
* then gets promoted to a "real" usable subvolume. But for
|
||||
* a live rootfs the directory needs copied out of staged
|
||||
* and into the rootfs. Tar is a way to copy with
|
||||
* attributes and it includes internal logic that does the
|
||||
* right thing to overlay a directory onto something
|
||||
* pre-existing: */
|
||||
/* In order to avoid tar transforms with directories, rename
|
||||
* the directory before and after the tar command */
|
||||
string_or_die(&rename_tmpdir, "%s/tmprenamedir", STATE_DIR);
|
||||
ret = create_staging_renamedir(rename_tmpdir);
|
||||
if (ret) {
|
||||
goto out;
|
||||
}
|
||||
string_or_die(&rename_target, "%s/%s", rename_tmpdir, base);
|
||||
if (rename(original, rename_target)) {
|
||||
ret = -errno;
|
||||
goto out;
|
||||
}
|
||||
string_or_die(&tarcommand, "tar -C %s " TAR_PERM_ATTR_ARGS " -cf - '%s' 2> /dev/null | "
|
||||
"tar -C %s%s " TAR_PERM_ATTR_ARGS " -xf - 2> /dev/null",
|
||||
rename_tmpdir, base, path_prefix, rel_dir);
|
||||
ret = system(tarcommand);
|
||||
if (WIFEXITED(ret)) {
|
||||
ret = WEXITSTATUS(ret);
|
||||
}
|
||||
free(tarcommand);
|
||||
if (rename(rename_target, original)) {
|
||||
ret = -errno;
|
||||
goto out;
|
||||
}
|
||||
if (ret < 0) {
|
||||
ret = -EDIR_OVERWRITE;
|
||||
goto out;
|
||||
}
|
||||
} else { /* (!file->is_dir && !S_ISDIR(stat.st_mode)) */
|
||||
/* can't naively hard link(): Non-read-only files with same hash must remain
|
||||
* separate copies otherwise modifications to one instance of the file
|
||||
* propagate to all instances of the file perhaps causing subtle data corruption from
|
||||
* a user's perspective. In practice the rootfs is stateless and owned by us.
|
||||
* Additionally cross-mount hardlinks fail and it's hard to know what an admin
|
||||
* might have for overlaid mounts. The use of tar is a simple way to copy, but
|
||||
* inefficient. So prefer hardlink and fall back if needed: */
|
||||
ret = -1;
|
||||
if (!file->is_config && !file->is_state && !file->use_xattrs) {
|
||||
ret = link(original, target);
|
||||
}
|
||||
if (ret < 0) {
|
||||
/* either the hardlink failed, or it was undesirable (config), do a tar-tar dance */
|
||||
/* In order to avoid tar transforms, rename the file
|
||||
* before and after the tar command */
|
||||
string_or_die(&rename_target, "%s/staged/.update.%s", STATE_DIR, base);
|
||||
ret = rename(original, rename_target);
|
||||
if (ret) {
|
||||
ret = -errno;
|
||||
goto out;
|
||||
}
|
||||
string_or_die(&tarcommand, "tar -C %s/staged " TAR_PERM_ATTR_ARGS " -cf - '.update.%s' 2> /dev/null | "
|
||||
"tar -C %s%s " TAR_PERM_ATTR_ARGS " -xf - 2> /dev/null",
|
||||
STATE_DIR, base, path_prefix, rel_dir);
|
||||
ret = system(tarcommand);
|
||||
if (WIFEXITED(ret)) {
|
||||
ret = WEXITSTATUS(ret);
|
||||
}
|
||||
free(tarcommand);
|
||||
ret = rename(rename_target, original);
|
||||
if (ret) {
|
||||
ret = -errno;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
struct stat buf;
|
||||
int err;
|
||||
|
||||
if (file->staging) {
|
||||
/* this must never happen...full file never finished download */
|
||||
free(file->staging);
|
||||
file->staging = NULL;
|
||||
}
|
||||
|
||||
string_or_die(&file->staging, "%s%s/.update.%s", path_prefix, rel_dir, base);
|
||||
|
||||
err = lstat(file->staging, &buf);
|
||||
if (err != 0) {
|
||||
free(file->staging);
|
||||
file->staging = NULL;
|
||||
ret = -EDOTFILE_WRITE;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
free(target);
|
||||
free(original);
|
||||
free(rename_target);
|
||||
free(rename_tmpdir);
|
||||
free(tmp);
|
||||
free(tmp2);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* caller should not call this function for do_not_update marked files */
|
||||
int rename_staged_file_to_final(struct file *file) {
|
||||
int ret;
|
||||
char *target;
|
||||
|
||||
string_or_die(&target, "%s%s", path_prefix, file->filename);
|
||||
|
||||
if (!file->staging && !file->is_deleted && !file->is_dir) {
|
||||
free(target);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (file->is_deleted) {
|
||||
ret = swupd_rm(target);
|
||||
|
||||
/* don't count missing ones as errors...
|
||||
* if somebody already deleted them for us then all is well */
|
||||
if ((ret == -ENOENT) || (ret == -ENOTDIR)) {
|
||||
ret = 0;
|
||||
}
|
||||
} else if (file->is_dir) {
|
||||
ret = 0;
|
||||
} else {
|
||||
struct stat stat;
|
||||
ret = lstat(target, &stat);
|
||||
|
||||
/* If the file was previously a directory but no longer, then
|
||||
* we need to move it out of the way.
|
||||
* This should not happen because the server side complains
|
||||
* when creating update content that includes such a state
|
||||
* change. But...you never know. */
|
||||
|
||||
if ((ret == 0) && (S_ISDIR(stat.st_mode))) {
|
||||
char *lostnfound;
|
||||
char *base;
|
||||
|
||||
string_or_die(&lostnfound, "%slost+found", path_prefix);
|
||||
ret = mkdir(lostnfound, S_IRWXU);
|
||||
if ((ret != 0) && (errno != EEXIST)) {
|
||||
free(lostnfound);
|
||||
free(target);
|
||||
return ret;
|
||||
}
|
||||
free(lostnfound);
|
||||
|
||||
base = basename(file->filename);
|
||||
string_or_die(&lostnfound, "%slost+found/%s", path_prefix, base);
|
||||
/* this will fail if the directory was not already emptied */
|
||||
ret = rename(target, lostnfound);
|
||||
if (ret < 0 && errno != ENOTEMPTY && errno != EEXIST) {
|
||||
printf("Error: failed to move %s to lost+found: %s\n",
|
||||
base, strerror(errno));
|
||||
}
|
||||
free(lostnfound);
|
||||
} else {
|
||||
ret = rename(file->staging, target);
|
||||
if (ret < 0) {
|
||||
printf("Error: failed to rename staged %s to final: %s\n",
|
||||
file->hash, strerror(errno));
|
||||
}
|
||||
unlink(file->staging);
|
||||
}
|
||||
}
|
||||
|
||||
free(target);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int rename_all_files_to_final(struct list *updates)
|
||||
{
|
||||
int ret, update_errs = 0, update_good = 0, skip = 0;
|
||||
struct list *list;
|
||||
|
||||
list = list_head(updates);
|
||||
while (list) {
|
||||
struct file *file;
|
||||
file = list->data;
|
||||
list = list->next;
|
||||
if (file->do_not_update) {
|
||||
skip += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = rename_staged_file_to_final(file);
|
||||
if (ret != 0) {
|
||||
update_errs += 1;
|
||||
} else {
|
||||
update_good += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return update_count - update_good - update_errs - (update_skip - skip);
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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>
|
||||
*
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "swupd.h"
|
||||
|
||||
|
||||
|
||||
|
||||
static int new_files;
|
||||
static int deleted_files;
|
||||
static int changed_files;
|
||||
static int new_manifests;
|
||||
static int deleted_manifests;
|
||||
static int changed_manifests;
|
||||
static int delta_miss;
|
||||
static int delta_hit;
|
||||
|
||||
|
||||
void account_new_file(void)
|
||||
{
|
||||
new_files++;
|
||||
}
|
||||
|
||||
void account_deleted_file(void)
|
||||
{
|
||||
deleted_files++;
|
||||
}
|
||||
|
||||
void account_changed_file(void)
|
||||
{
|
||||
changed_files++;
|
||||
}
|
||||
|
||||
void account_new_manifest(void)
|
||||
{
|
||||
new_manifests++;
|
||||
}
|
||||
|
||||
void account_deleted_manifest(void)
|
||||
{
|
||||
deleted_manifests++;
|
||||
}
|
||||
|
||||
void account_changed_manifest(void)
|
||||
{
|
||||
changed_manifests++;
|
||||
}
|
||||
|
||||
void account_delta_hit(void)
|
||||
{
|
||||
delta_hit++;
|
||||
}
|
||||
|
||||
|
||||
void account_delta_miss(void)
|
||||
{
|
||||
delta_miss++;
|
||||
}
|
||||
|
||||
|
||||
void print_statistics(int version1, int version2)
|
||||
{
|
||||
printf("\n");
|
||||
printf("Statistics for going from version %i to version %i:\n\n", version1, version2);
|
||||
printf(" changed manifests : %i\n", changed_manifests);
|
||||
printf(" new manifests : %i\n", new_manifests);
|
||||
printf(" deleted manifests : %i\n\n", deleted_manifests);
|
||||
printf(" changed files : %i\n", changed_files);
|
||||
printf(" new files : %i\n", new_files);
|
||||
printf(" deleted files : %i\n", deleted_files);
|
||||
printf("\n");
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "swupd.h"
|
||||
|
||||
struct list *subs;
|
||||
|
||||
static void free_subscription_data(void *data)
|
||||
{
|
||||
struct sub *sub = (struct sub *) data;
|
||||
|
||||
free(sub->component);
|
||||
free(sub);
|
||||
}
|
||||
|
||||
|
||||
void free_subscriptions(void)
|
||||
{
|
||||
list_free_list_and_data(subs, free_subscription_data);
|
||||
subs = NULL;
|
||||
}
|
||||
|
||||
struct list *free_bundle(struct list *item)
|
||||
{
|
||||
return list_free_item(item, free_subscription_data);
|
||||
}
|
||||
|
||||
struct list *free_list_file(struct list *item)
|
||||
{
|
||||
return list_free_item(item, free_file_data);
|
||||
}
|
||||
|
||||
void read_subscriptions_alt(void)
|
||||
{
|
||||
char *path = NULL;
|
||||
DIR *dir;
|
||||
struct dirent *ent;
|
||||
|
||||
string_or_die(&path, "%s/%s", path_prefix, BUNDLES_DIR);
|
||||
|
||||
dir = opendir(path);
|
||||
if (dir) {
|
||||
while ((ent = readdir(dir))) {
|
||||
if ((strcmp(ent->d_name, ".") == 0) || (strcmp(ent->d_name, "..") == 0)) {
|
||||
continue;
|
||||
}
|
||||
if (ent->d_type == DT_REG) {
|
||||
if (component_subscribed(ent->d_name)) {
|
||||
/* This is considered odd since means two files same name on same folder */
|
||||
continue;
|
||||
}
|
||||
|
||||
create_and_append_subscription(ent->d_name);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
free(path);
|
||||
|
||||
/* if nothing was picked up from bundles directory then add os-core by default */
|
||||
if (list_len(subs) == 0) {
|
||||
create_and_append_subscription("os-core");
|
||||
}
|
||||
}
|
||||
|
||||
int component_subscribed(char *component)
|
||||
{
|
||||
struct list *list;
|
||||
struct sub *sub;
|
||||
|
||||
list = list_head(subs);
|
||||
while (list) {
|
||||
sub = list->data;
|
||||
list = list->next;
|
||||
|
||||
if (strcmp(sub->component, component) == 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int subscription_versions_from_MoM(struct manifest *MoM, int is_old)
|
||||
{
|
||||
struct list *list;
|
||||
struct list *list2;
|
||||
struct file *file;
|
||||
struct sub *sub;
|
||||
bool bundle_found;
|
||||
int ret = 0;
|
||||
|
||||
list = list_head(subs);
|
||||
while (list) {
|
||||
bundle_found = false;
|
||||
sub = list->data;
|
||||
list = list->next;
|
||||
|
||||
list2 = MoM->manifests;
|
||||
while (list2 && !bundle_found) {
|
||||
file = list2->data;
|
||||
list2 = list2->next;
|
||||
|
||||
if (strcmp(sub->component, file->filename) == 0) {
|
||||
if (is_old) {
|
||||
sub->oldversion = file->last_change;
|
||||
} else {
|
||||
sub->version = file->last_change;
|
||||
}
|
||||
bundle_found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bundle_found) {
|
||||
sub->version = -1;
|
||||
printf("ERROR: Bundle not in MoM |\"component=%s\"\n", sub->component);
|
||||
ret = EBUNDLE_MISMATCH;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void create_and_append_subscription(const char *component)
|
||||
{
|
||||
struct sub *sub;
|
||||
|
||||
sub = calloc(1, sizeof(struct sub));
|
||||
if (!sub) {
|
||||
abort();
|
||||
}
|
||||
|
||||
sub->component = strdup(component);
|
||||
if (sub->component == NULL) {
|
||||
abort();
|
||||
}
|
||||
|
||||
sub->version = 0;
|
||||
sub->oldversion = 0;
|
||||
subs = list_prepend_data(subs, sub);
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Software Updater - client side
|
||||
*
|
||||
* Copyright © 2015-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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE // for basename()
|
||||
#include <getopt.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "swupd.h"
|
||||
#include "swupd-internal.h"
|
||||
|
||||
struct subcmd {
|
||||
char *name;
|
||||
char *doc;
|
||||
int (*mainfunc)(int, char **);
|
||||
};
|
||||
|
||||
static struct subcmd commands[] = {
|
||||
{ "bundle-add", "Install a new bundle", bundle_add_main },
|
||||
{ "bundle-remove", "Uninstall a bundle", bundle_remove_main },
|
||||
{ "hashdump", "Dumps the HMAC hash of a file", hashdump_main },
|
||||
{ "update", "Update to latest OS version", update_main },
|
||||
{ "verify", "Verify content for OS version", verify_main },
|
||||
{ "check-update", "Checks if a new OS version is available", check_update_main},
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
static const struct option prog_opts[] = {
|
||||
{ "help", no_argument, 0, 'h' },
|
||||
{ "version", no_argument, 0, 'v' },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
static void print_help(const char *name) {
|
||||
printf("Usage:\n");
|
||||
printf(" %s [OPTION...]\n", basename((char*)name));
|
||||
printf(" or %s [OPTION...] SUBCOMMAND [OPTION...]\n\n", basename((char*)name));
|
||||
printf("Help Options:\n");
|
||||
printf(" -h, --help Show help options\n");
|
||||
printf(" -v, --version Output version information and exit\n\n");
|
||||
printf("Subcommands:\n");
|
||||
|
||||
struct subcmd *entry = commands;
|
||||
|
||||
while (entry->name != NULL) {
|
||||
printf(" %-20s %-30s\n", entry->name, entry->doc);
|
||||
entry++;
|
||||
}
|
||||
printf("\n");
|
||||
printf("To view subcommand options, run `%s SUBCOMMAND --help'\n", basename((char*)name));
|
||||
}
|
||||
|
||||
static int subcmd_index(char *arg)
|
||||
{
|
||||
struct subcmd *entry = commands;
|
||||
int i = 0;
|
||||
size_t input_len = strlen(arg);
|
||||
size_t cmd_len;
|
||||
|
||||
while (entry->name != NULL) {
|
||||
cmd_len = strlen(entry->name);
|
||||
if (cmd_len == input_len && strcmp(arg, entry->name) == 0) {
|
||||
return i;
|
||||
}
|
||||
entry++;
|
||||
i++;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int parse_options(int argc, char **argv, int *index)
|
||||
{
|
||||
int opt;
|
||||
int ret;
|
||||
|
||||
/* The leading "-" in the optstring is required to preserve option parsing order */
|
||||
while ((opt = getopt_long(argc, argv, "-hv", prog_opts, NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case 'h':
|
||||
print_help(argv[0]);
|
||||
exit(EXIT_SUCCESS);
|
||||
case 'v':
|
||||
copyright_header("swupd");
|
||||
exit(EXIT_SUCCESS);
|
||||
case '\01':
|
||||
/* found a subcommand, or a random non-option argument */
|
||||
ret = subcmd_index(optarg);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "error: unrecognized subcommand `%s'\n\n",
|
||||
optarg);
|
||||
goto error;
|
||||
} else {
|
||||
*index = ret;
|
||||
return 0;
|
||||
}
|
||||
case '?':
|
||||
/* for unknown options, an error message is printed automatically */
|
||||
printf("\n");
|
||||
goto error;
|
||||
default:
|
||||
/* should be unreachable */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* no subcommands implies -h/--help */
|
||||
print_help(argv[0]);
|
||||
exit(EXIT_SUCCESS);
|
||||
error:
|
||||
print_help(argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int index;
|
||||
int ret;
|
||||
|
||||
if (parse_options(argc, argv, &index) < 0) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* Reset optind to 0 (instead of the default value, 1) at this point,
|
||||
* because option parsing is restarted for the given subcommand, and
|
||||
* subcommand optstrings are not prefixed with "-", which is a GNU
|
||||
* extension. See the getopt_long(3) NOTES section.
|
||||
*/
|
||||
optind = 0;
|
||||
|
||||
ret = commands[index].mainfunc(argc - 1, argv + 1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
+398
@@ -0,0 +1,398 @@
|
||||
/*
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
#include <libgen.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "swupd.h"
|
||||
#include "signature.h"
|
||||
|
||||
void increment_retries(int *retries, int *timeout)
|
||||
{
|
||||
(*retries)++;
|
||||
sleep(*timeout);
|
||||
*timeout *= 2;
|
||||
}
|
||||
|
||||
static void try_delta_loop(struct list *updates)
|
||||
{
|
||||
struct list *iter;
|
||||
struct file *file;
|
||||
|
||||
/* need update list in filename order to insure directories are
|
||||
* created before their contents */
|
||||
updates = list_sort(updates, file_sort_filename);
|
||||
|
||||
iter = list_head(updates);
|
||||
while (iter) {
|
||||
file = iter->data;
|
||||
iter = iter->next;
|
||||
|
||||
if (!file->is_file) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try_delta(file);
|
||||
}
|
||||
}
|
||||
|
||||
static struct list *full_download_loop(struct list *updates, int isfailed)
|
||||
{
|
||||
struct list *iter;
|
||||
struct file *file;
|
||||
|
||||
iter = list_head(updates);
|
||||
while (iter) {
|
||||
file = iter->data;
|
||||
iter = iter->next;
|
||||
|
||||
if (file->is_deleted) {
|
||||
continue;
|
||||
}
|
||||
|
||||
full_download(file);
|
||||
}
|
||||
|
||||
if (isfailed) {
|
||||
list_free_list(updates);
|
||||
}
|
||||
|
||||
return end_full_download();
|
||||
}
|
||||
|
||||
static int update_loop(struct list *updates)
|
||||
{
|
||||
int ret;
|
||||
struct file *file;
|
||||
struct list *iter;
|
||||
struct list *failed = NULL;
|
||||
int err;
|
||||
int retries = 0; /* We only want to go through the download loop once */
|
||||
int timeout = 10; /* Amount of seconds for first download retry */
|
||||
|
||||
TRY_DOWNLOAD:
|
||||
err = start_full_download(true);
|
||||
if (err != 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (failed != NULL) {
|
||||
try_delta_loop(failed);
|
||||
failed = full_download_loop(failed, 1);
|
||||
} else {
|
||||
try_delta_loop(updates);
|
||||
failed = full_download_loop(updates, 0);
|
||||
}
|
||||
|
||||
/* if (rm_staging_dir_contents("download")) {
|
||||
return -1;
|
||||
}
|
||||
*/
|
||||
|
||||
/* Set retries only if failed downloads exist, and only retry a fixed
|
||||
amount of times */
|
||||
if (list_head(failed) != NULL && retries < MAX_TRIES) {
|
||||
increment_retries(&retries, &timeout);
|
||||
printf("Starting download retry #%d\n", retries);
|
||||
clean_curl_multi_queue();
|
||||
goto TRY_DOWNLOAD;
|
||||
}
|
||||
|
||||
if (retries >= MAX_TRIES) {
|
||||
printf("ERROR: Could not download all files, aborting update\n");
|
||||
list_free_list(failed);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (download_only) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*********** 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 */
|
||||
printf("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);
|
||||
if (ret < 0) {
|
||||
printf("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 */
|
||||
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 main_update()
|
||||
{
|
||||
int current_version = -1, server_version = -1, latest_version = -1;
|
||||
struct manifest *current_manifest = NULL, *server_manifest = NULL;
|
||||
struct list *updates = NULL;
|
||||
int ret;
|
||||
int lock_fd;
|
||||
int retries = 0;
|
||||
int timeout = 10;
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
ret = swupd_init(&lock_fd);
|
||||
if (ret != 0) {
|
||||
/* being here means we already close log by a previously caught error */
|
||||
printf("Updater failed to initialize, exiting now.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!check_network()) {
|
||||
printf("Error: Network issue, unable to proceed with update\n");
|
||||
ret = EXIT_FAILURE;
|
||||
goto clean_curl;
|
||||
}
|
||||
|
||||
printf("Update started.\n");
|
||||
read_subscriptions_alt();
|
||||
|
||||
if (!signature_initialize(UPDATE_CA_CERTS_PATH "/" SIGNATURE_CA_CERT)) {
|
||||
goto clean_curl;
|
||||
}
|
||||
|
||||
/* Step 1: get versions */
|
||||
|
||||
ret = check_versions(¤t_version, &latest_version, &server_version, path_prefix);
|
||||
|
||||
if (ret < 0) {
|
||||
goto clean_curl;
|
||||
}
|
||||
if (server_version <= latest_version) {
|
||||
printf("Version on server (%i) is not newer than system version (%i)\n", server_version, latest_version);
|
||||
ret = EXIT_SUCCESS;
|
||||
goto clean_curl;
|
||||
}
|
||||
|
||||
printf("Preparing to update from %i to %i\n", latest_version, server_version);
|
||||
|
||||
/* Step 2: housekeeping */
|
||||
|
||||
if (create_required_dirs()) {
|
||||
goto clean_curl;
|
||||
}
|
||||
|
||||
if (rm_staging_dir_contents("download")) {
|
||||
goto clean_curl;
|
||||
}
|
||||
|
||||
/* Step 3: setup manifests */
|
||||
|
||||
load_current_manifests:
|
||||
/* get the from/to MoM manifests */
|
||||
printf("Querying current manifest.\n");
|
||||
ret = load_manifests(latest_version, latest_version, "MoM", NULL, ¤t_manifest);
|
||||
if (ret) {
|
||||
/* 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);
|
||||
printf("Retry #%d downloading from/to MoM Manifests\n", retries);
|
||||
goto load_current_manifests;
|
||||
}
|
||||
printf("Failure retrieving manifest from server\n");
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
/* Reset the retries and timeout for subsequent download calls */
|
||||
if (retries != 0) {
|
||||
retries = 0;
|
||||
timeout = 10;
|
||||
}
|
||||
|
||||
load_server_manifests:
|
||||
printf("Querying server manifest.\n");
|
||||
|
||||
ret = load_manifests(latest_version, server_version, "MoM", NULL, &server_manifest);
|
||||
if (ret) {
|
||||
if (retries < MAX_TRIES) {
|
||||
increment_retries(&retries, &timeout);
|
||||
printf("Retry #%d downloading server Manifests\n", retries);
|
||||
goto load_server_manifests;
|
||||
}
|
||||
printf("Failure retrieving manifest from server\n");
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
if (current_manifest == NULL || server_manifest == NULL) {
|
||||
printf("Unable to load manifest after retrying (config or network problem?)\n");
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
if (retries != 0) {
|
||||
retries = 0;
|
||||
timeout = 10;
|
||||
}
|
||||
|
||||
subscription_versions_from_MoM(current_manifest, 1);
|
||||
subscription_versions_from_MoM(server_manifest, 0);
|
||||
|
||||
link_submanifests(current_manifest, server_manifest);
|
||||
|
||||
/* updating subscribed manifests is done as part of recurse_manifest */
|
||||
|
||||
/* read the current collective of manifests that we are subscribed to */
|
||||
ret = recurse_manifest(current_manifest, NULL);
|
||||
if (ret != 0) {
|
||||
printf("Cannot load current MoM sub-manifests, ret = %d (%s), exiting\n", ret, strerror(errno));
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
/* consolidate the current collective manifests down into one in memory */
|
||||
consolidate_submanifests(current_manifest);
|
||||
|
||||
/* read the new collective of manifests that we are subscribed to */
|
||||
ret = recurse_manifest(server_manifest, NULL);
|
||||
if (ret != 0) {
|
||||
printf("Error: Cannot load server MoM sub-manifests, ret = %d (%s), exiting\n", ret, strerror(errno));
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
/* consolidate the new collective manifests down into one in memory */
|
||||
consolidate_submanifests(server_manifest);
|
||||
|
||||
/* prepare for an update process based on comparing two in memory manifests */
|
||||
link_manifests(current_manifest, server_manifest);
|
||||
#if 0
|
||||
debug_write_manifest(current_manifest, "debug_manifest_current.txt");
|
||||
debug_write_manifest(server_manifest, "debug_manifest_server.txt");
|
||||
#endif
|
||||
/* Step 4: check disk state before attempting update */
|
||||
|
||||
run_preupdate_scripts(server_manifest);
|
||||
|
||||
download_packs:
|
||||
/* Step 5: get the packs and untar */
|
||||
ret = download_subscribed_packs(latest_version, server_version, false);
|
||||
if (ret == -ENONET) {
|
||||
// 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("No network, or server unavailable for pack downloads\n");
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
/* Step 6: some more housekeeping */
|
||||
|
||||
/* TODO: consider trying to do less sorting of manifests */
|
||||
|
||||
updates = create_update_list(current_manifest, server_manifest);
|
||||
|
||||
link_renames(updates, current_manifest); /* TODO: Have special lists for candidate and renames */
|
||||
|
||||
print_statistics(latest_version, server_version);
|
||||
|
||||
/* Step 7: apply the update */
|
||||
|
||||
ret = update_loop(updates);
|
||||
if (ret == 0) {
|
||||
ret = update_device_latest_version(server_version);
|
||||
printf("Update was applied.\n");
|
||||
}
|
||||
|
||||
if ((latest_version < server_version) && (ret == 0)) {
|
||||
printf("Update successful. System updated from version %d to version %d\n",
|
||||
latest_version, server_version);
|
||||
} else if (ret == 0) {
|
||||
printf("Update complete. System already up-to-date at version %d\n", latest_version);
|
||||
}
|
||||
|
||||
delete_motd();
|
||||
|
||||
/* Run any scripts that are needed to complete update */
|
||||
run_scripts();
|
||||
|
||||
clean_exit:
|
||||
list_free_list(updates);
|
||||
free_manifest(current_manifest);
|
||||
free_manifest(server_manifest);
|
||||
|
||||
clean_curl:
|
||||
signature_terminate();
|
||||
swupd_curl_cleanup();
|
||||
free_subscriptions();
|
||||
|
||||
printf("Update exiting.\n");
|
||||
|
||||
v_lockfile(lock_fd);
|
||||
|
||||
dump_file_descriptor_leaks();
|
||||
|
||||
return ret;
|
||||
}
|
||||
+777
@@ -0,0 +1,777 @@
|
||||
/*
|
||||
* 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>
|
||||
* Timothy C. Pepper <timothy.c.pepper@linux.intel.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "swupd.h"
|
||||
#include "signature.h"
|
||||
|
||||
|
||||
static bool cmdline_option_fix = false;
|
||||
static bool cmdline_option_install = false;
|
||||
static bool cmdline_option_quick = false;
|
||||
|
||||
static int version;
|
||||
|
||||
/* Count of how many files we managed to not fix */
|
||||
static int file_checked_count;
|
||||
static int file_missing_count;
|
||||
static int file_replaced_count;
|
||||
static int file_not_replaced_count;
|
||||
static int file_mismatch_count;
|
||||
static int file_fixed_count;
|
||||
static int file_not_fixed_count;
|
||||
static int file_extraneous_count;
|
||||
static int file_deleted_count;
|
||||
static int file_not_deleted_count;
|
||||
|
||||
|
||||
static const struct option prog_opts[] = {
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"manifest", required_argument, 0, 'm'},
|
||||
{"path", required_argument, 0, 'p'},
|
||||
{"url", required_argument, 0, 'u'},
|
||||
{"port", required_argument, 0, 'P'},
|
||||
{"contenturl", required_argument, 0, 'c'},
|
||||
{"versionurl", required_argument, 0, 'v'},
|
||||
{"fix", no_argument, 0, 'f'},
|
||||
{"install", no_argument, 0, 'i'},
|
||||
{"format", required_argument, 0, 'F'},
|
||||
{"quick", no_argument, 0, 'q'},
|
||||
{"force", no_argument, 0, 'x'},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
static void print_help(const char *name) {
|
||||
printf("Usage:\n");
|
||||
printf(" swupd %s [OPTION...]\n\n", basename((char*)name));
|
||||
printf("Help Options:\n");
|
||||
printf(" -h, --help Show help options\n\n");
|
||||
printf("Application Options:\n");
|
||||
printf(" -m, --manifest=M Verify against manifest version M\n");
|
||||
printf(" -p, --path=[PATH...] Use [PATH...] as the path to verify (eg: a chroot or btrfs subvol\n");
|
||||
printf(" -u, --url=[URL] RFC-3986 encoded url for version string and content file downloads\n");
|
||||
printf(" -P, --port=[port #] Port number to connect to at the url for version string and content file downloads\n");
|
||||
printf(" -c, --contenturl=[URL] RFC-3986 encoded url for content file downloads\n");
|
||||
printf(" -v, --versionurl=[URL] RFC-3986 encoded url for version file downloads\n");
|
||||
printf(" -f, --fix Fix local issues relative to server manifest (will not modify ignored files)\n");
|
||||
printf(" -i, --install Similar to \"--fix\" but optimized for install all files to empty directory\n");
|
||||
printf(" -F, --format=[staging,1,2,etc.] the format suffix for version file downloads\n");
|
||||
printf(" -q, --quick Don't compare hashes, only fix missing files\n");
|
||||
printf(" -x, --force Attempt to proceed even if non-critical errors found\n");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static bool parse_options(int argc, char **argv)
|
||||
{
|
||||
int opt;
|
||||
|
||||
//set default initial values
|
||||
set_format_string(NULL);
|
||||
|
||||
while ((opt = getopt_long(argc, argv, "hxm:p:u:P:c:v:fiF:q", prog_opts, NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case '?':
|
||||
case 'h':
|
||||
print_help(argv[0]);
|
||||
exit(EXIT_SUCCESS);
|
||||
case 'm':
|
||||
if (sscanf(optarg, "%i", &version) != 1) {
|
||||
printf("Invalid --manifest argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
break;
|
||||
case 'p': /* default empty path_prefix verifies the running OS */
|
||||
if (!optarg) {
|
||||
printf("Invalid --path argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
if (path_prefix) { /* multiple -p options */
|
||||
free(path_prefix);
|
||||
}
|
||||
string_or_die(&path_prefix, "%s", optarg);
|
||||
break;
|
||||
case 'u':
|
||||
if (!optarg) {
|
||||
printf("Invalid --url argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
if (version_server_urls[0]) {
|
||||
free(version_server_urls[0]);
|
||||
}
|
||||
if (content_server_urls[0]) {
|
||||
free(content_server_urls[0]);
|
||||
}
|
||||
string_or_die(&version_server_urls[0], "%s", optarg);
|
||||
string_or_die(&content_server_urls[0], "%s", optarg);
|
||||
break;
|
||||
case 'P':
|
||||
if (sscanf(optarg, "%ld", &update_server_port) != 1) {
|
||||
printf("Invalid --port argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
break;
|
||||
case 'c':
|
||||
if (!optarg) {
|
||||
printf("Invalid --contenturl argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
if (content_server_urls[0]) {
|
||||
free(content_server_urls[0]);
|
||||
}
|
||||
string_or_die(&content_server_urls[0], "%s", optarg);
|
||||
break;
|
||||
case 'v':
|
||||
if (!optarg) {
|
||||
printf("Invalid --versionurl argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
if (version_server_urls[0]) {
|
||||
free(version_server_urls[0]);
|
||||
}
|
||||
string_or_die(&version_server_urls[0], "%s", optarg);
|
||||
break;
|
||||
case 'f':
|
||||
cmdline_option_fix = true;
|
||||
break;
|
||||
case 'i':
|
||||
cmdline_option_install = true;
|
||||
cmdline_option_quick = true;
|
||||
break;
|
||||
case 'F':
|
||||
if (!optarg || !set_format_string(optarg)) {
|
||||
printf("Invalid --format argument\n\n");
|
||||
goto err;
|
||||
}
|
||||
break;
|
||||
case 'q':
|
||||
cmdline_option_quick = true;
|
||||
break;
|
||||
case 'x':
|
||||
force = true;
|
||||
break;
|
||||
default:
|
||||
printf("Unrecognized option\n\n");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmdline_option_install) {
|
||||
if (version == 0) {
|
||||
printf("--install option requires -m version option\n");
|
||||
return false;
|
||||
}
|
||||
if (path_prefix == NULL) {
|
||||
printf("--install option requires --path option\n");
|
||||
return false;
|
||||
}
|
||||
if (cmdline_option_fix) {
|
||||
printf("--install and --fix options are mutually exclusive\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!init_globals()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
err:
|
||||
print_help(argv[0]);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool hash_needs_work(struct file *file, char *hash)
|
||||
{
|
||||
if (cmdline_option_quick) {
|
||||
if (hash_is_zeros(hash)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (hash_compare(file->hash, hash)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int get_all_files(int version, struct manifest *official_manifest)
|
||||
{
|
||||
int ret;
|
||||
struct list *iter;
|
||||
|
||||
/* for install we need everything so synchronously download zero packs */
|
||||
ret = download_subscribed_packs(0, version, true);
|
||||
if (ret < 0) { // require zero pack
|
||||
/* If we hit this point, we know we have a network connection, therefore
|
||||
* the error is server-side. This is also a critical error, so detailed
|
||||
* logging needed */
|
||||
printf("zero pack downloads failed. \n");
|
||||
printf("Failed - Server-side error, cannot download necessary files\n");
|
||||
return ret;
|
||||
}
|
||||
iter = list_head(official_manifest->files);
|
||||
while (iter) {
|
||||
struct file *file;
|
||||
|
||||
file = iter->data;
|
||||
iter = iter->next;
|
||||
|
||||
if (file->is_deleted) {
|
||||
continue;
|
||||
}
|
||||
|
||||
file_checked_count++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct list *download_loop(struct list *files, int isfailed)
|
||||
{
|
||||
int ret;
|
||||
struct file local;
|
||||
struct list *iter;
|
||||
|
||||
iter = list_head(files);
|
||||
while (iter) {
|
||||
struct file *file;
|
||||
char *fullname;
|
||||
|
||||
file = iter->data;
|
||||
iter = iter->next;
|
||||
|
||||
if (file->is_deleted) {
|
||||
continue;
|
||||
}
|
||||
|
||||
fullname = mk_full_filename(path_prefix, file->filename);
|
||||
if (fullname == NULL) {
|
||||
abort();
|
||||
}
|
||||
|
||||
memset(&local, 0, sizeof(struct file));
|
||||
local.filename = file->filename;
|
||||
populate_file_struct(&local, fullname);
|
||||
|
||||
if (cmdline_option_quick) {
|
||||
ret = compute_hash_lazy(&local, fullname);
|
||||
} else {
|
||||
ret = compute_hash(&local, fullname);
|
||||
}
|
||||
if (ret != 0) {
|
||||
free(fullname);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hash_needs_work(file, local.hash)) {
|
||||
full_download(file);
|
||||
} else {
|
||||
/* mark the file as good to save time later */
|
||||
file->do_not_update = 1;
|
||||
}
|
||||
free(fullname);
|
||||
}
|
||||
|
||||
if (isfailed) {
|
||||
list_free_list(files);
|
||||
}
|
||||
|
||||
return end_full_download();
|
||||
}
|
||||
|
||||
static int get_missing_files(struct manifest *official_manifest)
|
||||
{
|
||||
int ret;
|
||||
struct list *failed = NULL;
|
||||
int retries = 0; /* We only want to go through the download loop once */
|
||||
int timeout = 10; /* Amount of seconds for first download retry */
|
||||
|
||||
/* when fixing (not installing): queue download and mark any files
|
||||
* which are already verified OK */
|
||||
RETRY_DOWNLOADS:
|
||||
ret = start_full_download(true);
|
||||
if (ret != 0) {
|
||||
/* If we hit this point, the network is accessible but we were
|
||||
* unable to download the needed files. This is a terminal error
|
||||
* and we need good logging */
|
||||
printf("Error: Unable to download neccessary files for this OS release\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (failed != NULL) {
|
||||
failed = download_loop(failed, 1);
|
||||
} else {
|
||||
failed = download_loop(official_manifest->files, 0);
|
||||
}
|
||||
|
||||
/* Set retries only if failed downloads exist, and only retry a fixed
|
||||
amount of times */
|
||||
if (list_head(failed) != NULL && retries < MAX_TRIES) {
|
||||
increment_retries(&retries, &timeout);
|
||||
printf("Starting download retry #%d\n", retries);
|
||||
clean_curl_multi_queue();
|
||||
goto RETRY_DOWNLOADS;
|
||||
}
|
||||
|
||||
if (retries >= MAX_TRIES) {
|
||||
printf("ERROR: Could not download all files, aborting update\n");
|
||||
list_free_list(failed);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* allow optimization of install case */
|
||||
static int get_required_files(int version, struct manifest *official_manifest)
|
||||
{
|
||||
if (cmdline_option_install) {
|
||||
return get_all_files(version, official_manifest);
|
||||
}
|
||||
|
||||
if (cmdline_option_fix) {
|
||||
return get_missing_files(official_manifest);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* for each missing but expected file, (re)add the file */
|
||||
static void add_missing_files(struct manifest *official_manifest)
|
||||
{
|
||||
int ret;
|
||||
struct file local;
|
||||
struct list *iter;
|
||||
|
||||
iter = list_head(official_manifest->files);
|
||||
while (iter) {
|
||||
struct file *file;
|
||||
char *fullname;
|
||||
|
||||
file = iter->data;
|
||||
iter = iter->next;
|
||||
|
||||
if ((file->is_deleted) ||
|
||||
(file->do_not_update)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
fullname = mk_full_filename(path_prefix, file->filename);
|
||||
if (fullname == NULL) {
|
||||
abort();
|
||||
}
|
||||
memset(&local, 0, sizeof(struct file));
|
||||
local.filename = file->filename;
|
||||
populate_file_struct(&local, fullname);
|
||||
ret = compute_hash_lazy(&local, fullname);
|
||||
if (ret != 0) {
|
||||
file_not_replaced_count++;
|
||||
free(fullname);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* compare the hash and report mismatch */
|
||||
if (hash_is_zeros(local.hash)) {
|
||||
file_missing_count++;
|
||||
printf("Missing file: %s\n", fullname);
|
||||
} else {
|
||||
free(fullname);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* install the new file (on miscompare + fix) */
|
||||
ret = do_staging(file);
|
||||
if (ret == 0) {
|
||||
rename_staged_file_to_final(file);
|
||||
}
|
||||
|
||||
/* verify the hash again to judge success */
|
||||
populate_file_struct(&local, fullname);
|
||||
if (cmdline_option_quick) {
|
||||
ret = compute_hash_lazy(&local, fullname);
|
||||
} else {
|
||||
ret = compute_hash(&local, fullname);
|
||||
}
|
||||
if ((ret != 0) || hash_needs_work(file, local.hash)) {
|
||||
file_not_replaced_count++;
|
||||
printf("\tnot fixed\n");
|
||||
} else {
|
||||
file_replaced_count++;
|
||||
file->do_not_update = 1;
|
||||
printf("\tfixed\n");
|
||||
}
|
||||
free(fullname);
|
||||
}
|
||||
}
|
||||
|
||||
static void deal_with_hash_mismatches(struct manifest *official_manifest, bool repair)
|
||||
{
|
||||
int ret;
|
||||
struct list *iter;
|
||||
|
||||
/* for each expected and present file which hash-mismatches vs the manifest, replace the file */
|
||||
iter = list_head(official_manifest->files);
|
||||
while (iter) {
|
||||
struct file *file;
|
||||
char *fullname;
|
||||
|
||||
file = iter->data;
|
||||
iter = iter->next;
|
||||
|
||||
if (file->is_deleted ||
|
||||
ignore(file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
file_checked_count++;
|
||||
|
||||
// do_not_update set by earlier check, so account as checked
|
||||
if (file->do_not_update) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* compare the hash and report mismatch */
|
||||
fullname = mk_full_filename(path_prefix, file->filename);
|
||||
if (fullname == NULL) {
|
||||
abort();
|
||||
}
|
||||
if (verify_file(file, fullname)) {
|
||||
free(fullname);
|
||||
continue;
|
||||
} else {
|
||||
file_mismatch_count++;
|
||||
printf("Hash mismatch for file: %s\n", fullname);
|
||||
}
|
||||
|
||||
/* if not repairing, we're done */
|
||||
if (!repair) {
|
||||
free(fullname);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* install the new file (on miscompare + fix) */
|
||||
ret = do_staging(file);
|
||||
if (ret == 0) {
|
||||
rename_staged_file_to_final(file);
|
||||
}
|
||||
|
||||
/* at the end of all this, verify the hash again to judge success */
|
||||
if (verify_file(file, fullname)) {
|
||||
file_fixed_count++;
|
||||
printf("\tfixed\n");
|
||||
} else {
|
||||
file_not_fixed_count++;
|
||||
printf("\tnot fixed\n");
|
||||
}
|
||||
free(fullname);
|
||||
}
|
||||
}
|
||||
|
||||
static void remove_orphaned_files(struct manifest *official_manifest)
|
||||
{
|
||||
int ret;
|
||||
struct list *iter;
|
||||
|
||||
iter = list_head(official_manifest->files);
|
||||
while (iter) {
|
||||
struct file *file;
|
||||
char *fullname;
|
||||
struct stat sb;
|
||||
|
||||
file = iter->data;
|
||||
iter = iter->next;
|
||||
|
||||
if ((!file->is_deleted) ||
|
||||
(file->is_config)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
fullname = mk_full_filename(path_prefix, file->filename);
|
||||
if (fullname == NULL) {
|
||||
abort();
|
||||
}
|
||||
|
||||
if (lstat(fullname, &sb) != 0) {
|
||||
/* correctly, the file is not present */
|
||||
free(fullname);
|
||||
continue;
|
||||
}
|
||||
|
||||
file_extraneous_count++;
|
||||
if (is_dirname_link(fullname)) {
|
||||
free(fullname);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!file->is_dir) {
|
||||
ret = unlink(fullname);
|
||||
if (!ret && errno != ENOENT && errno != EISDIR) {
|
||||
printf("Failed to remove %s (%i: %s)\n", fullname, errno, strerror(errno));
|
||||
file_not_deleted_count++;
|
||||
} else {
|
||||
printf("Deleted %s \n", fullname);
|
||||
file_deleted_count++;
|
||||
}
|
||||
} else {
|
||||
ret = rmdir(fullname);
|
||||
if (ret) {
|
||||
file_not_deleted_count++;
|
||||
if (errno != ENOTEMPTY) {
|
||||
printf("Failed to remove empty folder %s (%i: %s)\n",
|
||||
fullname, errno, strerror(errno));
|
||||
} else {
|
||||
//FIXME: Add force removal option?
|
||||
printf("Couldn't remove directory containing untracked files: %s\n", fullname);
|
||||
}
|
||||
}
|
||||
}
|
||||
free(fullname);
|
||||
}
|
||||
}
|
||||
|
||||
/* This function does a simple verification of files listed in the
|
||||
* subscribed bundle manifests. If the optional "fix" or "install" parameter
|
||||
* is specified, the disk will be modified at each point during the
|
||||
* sequential comparison of manifest files to disk files, where the disk is
|
||||
* found to not match the manifest. This is notably different from update,
|
||||
* which attempts to atomically (or nearly atomically) activate a set of
|
||||
* pre-computed and validated staged changes as a group. */
|
||||
int verify_main(int argc, char **argv)
|
||||
{
|
||||
struct manifest *official_manifest = NULL;
|
||||
int ret;
|
||||
int lock_fd;
|
||||
|
||||
copyright_header("software verify");
|
||||
|
||||
if (!parse_options(argc, argv) ||
|
||||
create_required_dirs()) {
|
||||
free_globals();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* parse command line options */
|
||||
assert(argc >= 0);
|
||||
assert(argv != NULL);
|
||||
|
||||
/* Gather current manifests */
|
||||
if (!version) {
|
||||
version = read_version_from_subvol_file(path_prefix);
|
||||
if (!version) {
|
||||
printf("Cannot determine current version\n");
|
||||
free_globals();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
ret = swupd_init(&lock_fd);
|
||||
if (ret != 0) {
|
||||
printf("Failed verify initialization, exiting now.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
printf("Verifying version %i\n", version);
|
||||
|
||||
if (!check_network()) {
|
||||
printf("Error: Network issue, unable to download manifest\n");
|
||||
v_lockfile(lock_fd);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
read_subscriptions_alt();
|
||||
get_mounted_directories();
|
||||
|
||||
/*
|
||||
* FIXME: We need a command line option to override this in case the
|
||||
* certificate is hosed and the admin knows it and wants to recover.
|
||||
*/
|
||||
if (!signature_initialize(UPDATE_CA_CERTS_PATH "/" SIGNATURE_CA_CERT)) {
|
||||
printf("Can't initialize the SSL certificates\n");
|
||||
goto brick_the_system_and_clean_curl;
|
||||
}
|
||||
|
||||
ret = rm_staging_dir_contents("download");
|
||||
if (ret != 0) {
|
||||
printf("Failed to remove prior downloads, carrying on anyway\n");
|
||||
}
|
||||
|
||||
ret = load_manifests(version, version, "MoM", NULL, &official_manifest);
|
||||
|
||||
if (ret != 0) {
|
||||
/* This should never get hit, since network issues are identified by the
|
||||
* check_network() call earlier */
|
||||
printf("Unable to download %d Manifest.MoM\n", version);
|
||||
goto brick_the_system_and_clean_curl;
|
||||
}
|
||||
|
||||
subscription_versions_from_MoM(official_manifest, 0);
|
||||
recurse_manifest(official_manifest, NULL);
|
||||
consolidate_submanifests(official_manifest);
|
||||
|
||||
/* preparation work complete. */
|
||||
|
||||
/*
|
||||
* NOTHING ELSE IS ALLOWED TO FAIL/ABORT after this line.
|
||||
* This tool is there to recover a nearly-bricked system. Aborting
|
||||
* from this point forward, for any reason, will result in a bricked system.
|
||||
*
|
||||
* I don't care what your static analysis tools says
|
||||
* I don't care what valgrind tells you
|
||||
*
|
||||
* There shall be no "goto fail;" from this point on.
|
||||
*
|
||||
* *** THE SHOW MUST GO ON ***
|
||||
*/
|
||||
|
||||
if (cmdline_option_fix || cmdline_option_install) {
|
||||
/* when fixing or installing we need input files. */
|
||||
ret = get_required_files(version, official_manifest);
|
||||
if (ret != 0) {
|
||||
goto brick_the_system_and_clean_curl;
|
||||
}
|
||||
|
||||
/*
|
||||
* Next put the files in place that are missing completely.
|
||||
* This is to avoid updating a symlink to a library before the new full file
|
||||
* is already there. It's also the most safe operation, adding files rarely
|
||||
* has unintended side effect. So lets do the safest thing first.
|
||||
*/
|
||||
printf("Adding any missing files\n");
|
||||
add_missing_files(official_manifest);
|
||||
}
|
||||
|
||||
if (cmdline_option_quick) {
|
||||
/* quick only replaces missing files, so it is done here */
|
||||
goto brick_the_system_and_clean_curl;
|
||||
}
|
||||
|
||||
if (cmdline_option_fix) {
|
||||
bool repair = true;
|
||||
|
||||
printf("Fixing modified files\n");
|
||||
deal_with_hash_mismatches(official_manifest, repair);
|
||||
|
||||
/* removing files could be risky, so only do it if the
|
||||
* prior phases had no problems */
|
||||
if ((file_not_fixed_count == 0) && (file_not_replaced_count == 0)) {
|
||||
remove_orphaned_files(official_manifest);
|
||||
}
|
||||
} else {
|
||||
bool repair = false;
|
||||
|
||||
printf("Verifying files\n");
|
||||
deal_with_hash_mismatches(official_manifest, repair);
|
||||
}
|
||||
|
||||
/* clean up */
|
||||
|
||||
/*
|
||||
* naming convention: All exit goto labels must follow the "brick_the_system_and_FOO:" pattern
|
||||
*/
|
||||
|
||||
brick_the_system_and_clean_curl:
|
||||
|
||||
/* report a summary of what we managed to do and not do */
|
||||
printf("Inspected %i files\n", file_checked_count);
|
||||
|
||||
if (cmdline_option_fix || cmdline_option_install) {
|
||||
printf(" %i files were missing\n", file_missing_count);
|
||||
if (file_missing_count) {
|
||||
printf(" %i of %i missing files were replaced\n", file_replaced_count, file_missing_count);
|
||||
printf(" %i of %i missing files were not replaced\n", file_not_replaced_count, file_missing_count);
|
||||
}
|
||||
}
|
||||
|
||||
if (!cmdline_option_quick && file_mismatch_count > 0) {
|
||||
printf(" %i files did not match\n", file_mismatch_count);
|
||||
if (cmdline_option_fix) {
|
||||
printf(" %i of %i files were fixed\n", file_fixed_count, file_mismatch_count);
|
||||
printf(" %i of %i files were not fixed\n", file_not_fixed_count, file_mismatch_count);
|
||||
}
|
||||
}
|
||||
|
||||
if ((file_not_fixed_count == 0) && (file_not_replaced_count == 0) &&
|
||||
cmdline_option_fix && !cmdline_option_quick) {
|
||||
printf(" %i files found which should be deleted\n", file_extraneous_count);
|
||||
if (file_extraneous_count) {
|
||||
printf(" %i of %i files were deleted\n", file_deleted_count, file_extraneous_count);
|
||||
printf(" %i of %i files were not deleted\n", file_not_deleted_count, file_extraneous_count);
|
||||
}
|
||||
}
|
||||
|
||||
if (cmdline_option_fix || cmdline_option_install) {
|
||||
// always run in a fix or install case
|
||||
need_update_boot = true;
|
||||
need_update_bootloader = true;
|
||||
run_scripts();
|
||||
}
|
||||
|
||||
sync();
|
||||
|
||||
if ((file_not_fixed_count == 0) &&
|
||||
(file_not_replaced_count == 0) &&
|
||||
(file_not_deleted_count == 0)) {
|
||||
ret = EXIT_SUCCESS;
|
||||
} else {
|
||||
ret = EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* this concludes the critical section, after this point it's clean up time, the disk content is finished and final */
|
||||
|
||||
if (ret == EXIT_SUCCESS) {
|
||||
if (cmdline_option_fix || cmdline_option_install) {
|
||||
printf("Fix successful\n");
|
||||
} else {
|
||||
/* This is just a verification */
|
||||
printf("Verify successful\n");
|
||||
}
|
||||
} else {
|
||||
if (cmdline_option_fix || cmdline_option_install) {
|
||||
printf("Error: Fix did not fully succeed\n");
|
||||
} else {
|
||||
/* This is just a verification */
|
||||
printf("Error: Verify did not fully succeed\n");
|
||||
}
|
||||
}
|
||||
swupd_curl_cleanup();
|
||||
free_subscriptions();
|
||||
free_manifest(official_manifest);
|
||||
|
||||
v_lockfile(lock_fd);
|
||||
dump_file_descriptor_leaks();
|
||||
free_globals();
|
||||
return ret;
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "swupd.h"
|
||||
|
||||
/* 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 */
|
||||
static int try_version_download(void)
|
||||
{
|
||||
char *url = NULL;
|
||||
char *path = NULL;
|
||||
int ret = 0;
|
||||
char *tmp_version;
|
||||
|
||||
tmp_version = malloc(LINE_MAX);
|
||||
if (tmp_version == NULL) {
|
||||
abort();
|
||||
}
|
||||
|
||||
string_or_die(&url, "%s/version/format%s/latest", preferred_version_url, format_string);
|
||||
|
||||
string_or_die(&path, "%s/server_version", STATE_DIR);
|
||||
|
||||
unlink(path);
|
||||
|
||||
ret = swupd_curl_get_file(url, path, NULL, tmp_version, false);
|
||||
if (ret) {
|
||||
goto out;
|
||||
} else {
|
||||
ret = strtol(tmp_version, NULL, 10);
|
||||
}
|
||||
|
||||
out:
|
||||
free(path);
|
||||
free(url);
|
||||
free(tmp_version);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool check_network(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!have_network) {
|
||||
ret = try_version_download();
|
||||
if (ret < 0) {
|
||||
have_network = false;
|
||||
} else {
|
||||
have_network = true;
|
||||
}
|
||||
}
|
||||
|
||||
return have_network;
|
||||
}
|
||||
|
||||
int read_version_from_subvol_file(char *path_prefix)
|
||||
{
|
||||
char line[LINE_MAX];
|
||||
FILE *file;
|
||||
int v = -1;
|
||||
char *buildstamp;
|
||||
char *src, *dest;
|
||||
|
||||
string_or_die(&buildstamp, "%s/usr/lib/os-release", path_prefix);
|
||||
file = fopen(buildstamp, "rm");
|
||||
if (!file) {
|
||||
string_or_die(&buildstamp, "%s/etc/os-release", path_prefix);
|
||||
file = fopen(buildstamp, "rm");
|
||||
if (!file) {
|
||||
free(buildstamp);
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
while (!feof(file)) {
|
||||
line[0] = 0;
|
||||
if (fgets(line, LINE_MAX, file) == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (strncmp(line, "VERSION_ID=", 11) == 0) {
|
||||
src = &line[11];
|
||||
|
||||
/* Drop quotes and newline in value */
|
||||
dest = src;
|
||||
while (*src) {
|
||||
if (*src == '\'' || *src == '"' || *src == '\n') {
|
||||
++src;
|
||||
} else {
|
||||
*dest = *src;
|
||||
++dest;
|
||||
++src;
|
||||
}
|
||||
}
|
||||
*dest = 0;
|
||||
|
||||
v = strtoull(&line[11], NULL, 10);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(buildstamp);
|
||||
fclose(file);
|
||||
return v;
|
||||
}
|
||||
|
||||
void read_versions(int *current_version,
|
||||
int *latest_version,
|
||||
int *server_version,
|
||||
char *path_prefix)
|
||||
{
|
||||
*current_version = *latest_version = read_version_from_subvol_file(path_prefix);
|
||||
|
||||
printf("Querying server version.\n");
|
||||
*server_version = try_version_download();
|
||||
}
|
||||
|
||||
int check_versions(int *current_version,
|
||||
int *latest_version,
|
||||
int *server_version,
|
||||
char *path_prefix)
|
||||
{
|
||||
|
||||
read_versions(current_version, latest_version, server_version, path_prefix);
|
||||
|
||||
if (*latest_version < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (*latest_version == 0) {
|
||||
printf("Update from version 0 not supported yet.\n");
|
||||
return -1;
|
||||
}
|
||||
if (SWUPD_VERSION_IS_DEVEL(*current_version) || SWUPD_VERSION_IS_RESVD(*current_version)) {
|
||||
printf("Update of dev build not supported %d\n", *current_version);
|
||||
return -1;
|
||||
}
|
||||
swupd_curl_set_current_version(*latest_version);
|
||||
|
||||
/* set preferred version and content server urls */
|
||||
if (*server_version < 0) {
|
||||
have_network = false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
have_network = true;
|
||||
|
||||
//TODO allow policy layer to send us to intermediate version?
|
||||
|
||||
swupd_curl_set_requested_version(*server_version);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int update_device_latest_version(int version)
|
||||
{
|
||||
FILE *file = NULL;
|
||||
char *path = NULL;
|
||||
|
||||
string_or_die(&path, "%s/version", STATE_DIR);
|
||||
file = fopen(path, "w");
|
||||
if (!file) {
|
||||
free(path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fprintf(file, "%i\n", version);
|
||||
fflush(file);
|
||||
fdatasync(fileno(file));
|
||||
fclose(file);
|
||||
free(path);
|
||||
return 0;
|
||||
}
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
* Software Update - client side
|
||||
*
|
||||
* File Extended Attributes Helpers
|
||||
*
|
||||
* Copyright © 2014-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:
|
||||
* Christophe Guiraud <christophe.guiraud@intel.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/xattr.h>
|
||||
|
||||
#include "swupd.h"
|
||||
#include "xattrs.h"
|
||||
|
||||
enum xattrs_action_type_t_ {
|
||||
XATTRS_ACTION_COPY,
|
||||
XATTRS_ACTION_GET_BLOB
|
||||
};
|
||||
|
||||
typedef enum xattrs_action_type_t_ xattrs_action_type_t;
|
||||
|
||||
static int xattr_get_value(const char *path, const char *name, char **blob,
|
||||
size_t *blob_len, xattrs_action_type_t action)
|
||||
{
|
||||
char *value;
|
||||
ssize_t len;
|
||||
|
||||
len = lgetxattr(path, name, NULL, 0);
|
||||
if (len < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (*blob == NULL) {
|
||||
*blob_len = 0;
|
||||
}
|
||||
|
||||
/* realloc needed len + 1 in case we need to add final zero
|
||||
* to ensure consistent blob */
|
||||
value = realloc(*blob, *blob_len + len +
|
||||
(action == XATTRS_ACTION_GET_BLOB ? 1 : 0));
|
||||
if (!value) {
|
||||
abort();
|
||||
}
|
||||
|
||||
*blob = value;
|
||||
|
||||
value = value + *blob_len;
|
||||
len = lgetxattr(path, name, value, len);
|
||||
if (len < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* in the xattrs system, value is just an arbitrary binary blob. If it
|
||||
* is a string, it can already be null terminated or not, depending
|
||||
* on the len that was passed when the attribute was set. So, make
|
||||
* sure we use a consistent value by adding a final zero if not
|
||||
* already there.
|
||||
* note: this must not be done when copying attributes. In this case
|
||||
* we want to keep them unchanged. It must only be done when
|
||||
* getting the value blob to use for key computation
|
||||
*/
|
||||
if (action == XATTRS_ACTION_GET_BLOB && len && value[len - 1] != 0) {
|
||||
value[len] = 0;
|
||||
len++;
|
||||
}
|
||||
|
||||
*blob_len += len;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_xattr_name_count(const char *names_list, ssize_t len)
|
||||
{
|
||||
int count = 0;
|
||||
const char *name;
|
||||
|
||||
for (name = names_list; name < (names_list + len); name += strlen(name) + 1) {
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static int cmp_xattr_name_ptrs(const void *ptr1, const void *ptr2)
|
||||
{
|
||||
return strcmp(*(char * const *)ptr1, *(char * const *)ptr2);
|
||||
}
|
||||
|
||||
static const char **get_sorted_xattr_name_table(const char *names, int n)
|
||||
{
|
||||
const char **table;
|
||||
int i;
|
||||
|
||||
table = calloc(1, n * sizeof(char*));
|
||||
if (!table) {
|
||||
abort();
|
||||
}
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
table[i] = names;
|
||||
names += strlen(names) + 1;
|
||||
}
|
||||
|
||||
qsort(table, n, sizeof(char*), cmp_xattr_name_ptrs);
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
/* copy an xattr blob from a file to another file or to a buffer
|
||||
*
|
||||
* returned blob_len==0 indicates the blob pointer does not
|
||||
* contain valid data. Given quirks in xattr implemenations (or lack there
|
||||
* of on some kernels or filesystems or OS's) and SSL quirks (the blob and
|
||||
* blob length are passed to a hasing function) the blob pointer is set to a
|
||||
* canary instead of being left as NULL, simplifying code elsewhere. */
|
||||
static void xattrs_do_action(xattrs_action_type_t action,
|
||||
const char *src_filename,
|
||||
const char *dst_filename,
|
||||
char **blob, size_t *blob_len) {
|
||||
ssize_t len;
|
||||
char *list;
|
||||
int ret = 0;
|
||||
char *value = NULL;
|
||||
size_t value_len = 0;
|
||||
const char **sorted_list = NULL;
|
||||
int count;
|
||||
int i;
|
||||
int offset = 0;
|
||||
|
||||
len = llistxattr(src_filename, NULL, 0);
|
||||
if (len <= 0) {
|
||||
if (action == XATTRS_ACTION_GET_BLOB) {
|
||||
*blob_len = 0;
|
||||
*blob = (void *)0xdeadcafe;
|
||||
}
|
||||
return; // no xattrs, this is OK
|
||||
}
|
||||
|
||||
list = calloc(1, len);
|
||||
if (!list) {
|
||||
abort();
|
||||
}
|
||||
|
||||
len = llistxattr(src_filename, list, len);
|
||||
if (len <= 0) {
|
||||
if (action == XATTRS_ACTION_GET_BLOB) {
|
||||
*blob_len = 0;
|
||||
*blob = (void *)0xdeadcafe;
|
||||
}
|
||||
free(list);
|
||||
return; // no xattrs, this is OK
|
||||
}
|
||||
|
||||
count = get_xattr_name_count(list, len);
|
||||
sorted_list = get_sorted_xattr_name_table(list, count);
|
||||
|
||||
if (action == XATTRS_ACTION_GET_BLOB) {
|
||||
value = calloc(1, len);
|
||||
if (!value) {
|
||||
abort();
|
||||
}
|
||||
|
||||
value_len = len;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
len = strlen(sorted_list[i]) + 1;
|
||||
memcpy(value + offset, sorted_list[i], len);
|
||||
offset += len;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
|
||||
/* In the XATTRS_ACTION_COPY case the xattr_get_value(...) calls
|
||||
* are always performed with 'value = NULL' and 'value_len = 0'.
|
||||
*/
|
||||
ret = xattr_get_value(src_filename, sorted_list[i], &value, &value_len,
|
||||
action);
|
||||
if (ret < 0) {
|
||||
free(value);
|
||||
value_len = 0;
|
||||
value = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (action == XATTRS_ACTION_COPY) {
|
||||
/* 'value' contains only the attribute value in this
|
||||
* case. */
|
||||
ret = lsetxattr(dst_filename, sorted_list[i],
|
||||
value, value_len, 0);
|
||||
free(value);
|
||||
if (ret < 0) {
|
||||
break;
|
||||
}
|
||||
value_len = 0;
|
||||
value = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (action == XATTRS_ACTION_GET_BLOB) {
|
||||
if (value_len != 0) {
|
||||
*blob_len = value_len;
|
||||
*blob = value;
|
||||
} else {
|
||||
*blob_len = 0;
|
||||
*blob = (void *)0xdeadcafe;
|
||||
}
|
||||
}
|
||||
|
||||
free(list);
|
||||
free(sorted_list);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void xattrs_copy(const char *src_filename, const char *dst_filename)
|
||||
{
|
||||
xattrs_do_action(XATTRS_ACTION_COPY, src_filename, dst_filename,
|
||||
NULL, NULL);
|
||||
}
|
||||
|
||||
void xattrs_get_blob(const char *filename, char **blob, size_t *blob_len)
|
||||
{
|
||||
xattrs_do_action(XATTRS_ACTION_GET_BLOB, filename, NULL,
|
||||
blob, blob_len);
|
||||
}
|
||||
|
||||
int xattrs_compare(const char *filename1, const char *filename2)
|
||||
{
|
||||
char *new_xattrs;
|
||||
char *old_xattrs;
|
||||
size_t new_xattrs_len;
|
||||
size_t old_xattrs_len;
|
||||
int ret = 0;
|
||||
|
||||
xattrs_get_blob(filename1, &old_xattrs, &old_xattrs_len);
|
||||
xattrs_get_blob(filename2, &new_xattrs, &new_xattrs_len);
|
||||
|
||||
if ((old_xattrs_len == 0) && (new_xattrs_len == 0)) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (old_xattrs_len != new_xattrs_len) {
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (memcmp(old_xattrs, new_xattrs, old_xattrs_len) != 0) {
|
||||
ret = -1;
|
||||
}
|
||||
out:
|
||||
if (old_xattrs_len != 0) {
|
||||
free(old_xattrs);
|
||||
}
|
||||
|
||||
if (new_xattrs_len != 0) {
|
||||
free(new_xattrs);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
Reference in New Issue
Block a user