Initial commit

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
This commit is contained in:
Patrick McCarty
2016-02-24 09:34:13 -08:00
commit c42f8a3aa1
236 changed files with 18402 additions and 0 deletions
+465
View File
@@ -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, &current_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;
}
+159
View File
@@ -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(&current_version, &current_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;
}
+154
View File
@@ -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);
}
}
+138
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+85
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+131
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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(&current_version, &current_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
View File
File diff suppressed because it is too large Load Diff
+134
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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");
}
+173
View File
@@ -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
View File
@@ -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
View File
@@ -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(&current_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, &current_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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}