From 4339044267786406b132522dadaf89b14131df20 Mon Sep 17 00:00:00 2001 From: Castulo Martinez Date: Fri, 6 Sep 2019 14:30:39 -0700 Subject: [PATCH] Implement bundle-info command The bundle-info command shows detailed data related to a specified bundle. Among the data currently displayed it includes: - If the bundle is installed or not - If the bundle is experimental or not - If the bundle is installed, it shows if there is an existing update for the bundle - The latest available version of the bundle - The size of the bundle and all its dependencies - The size needed in disk to install a bundle if not installed This commit also adds the following command flags: --version: so a user can display information for a bundle in a specific version, not only the current version. --includes: this flag can be used to show all optional and required bundles that are directly and indirectly included by the specified bundle. --files: this flag can be used to show all files that are part of a given bundle. All flags can be combined to show specific data. This is the first of a series of PRs to implement the bundle-info features referred to at #461. Signed-off-by: Castulo Martinez --- Makefile.am | 4 + docs/swupd.1.rst | 18 +- src/bundle_info.c | 570 ++++++++++++++++++ src/helpers.c | 33 + src/lib/list.c | 54 +- src/lib/list.h | 13 + src/main.c | 1 + src/swupd.h | 3 +- src/swupd_internal.h | 1 + src/update.c | 2 +- swupd.bash | 3 + swupd.zsh | 10 + test/functional/bundleinfo/binfo-basic.bats | 168 ++++++ test/functional/bundleinfo/binfo-files.bats | 138 +++++ .../functional/bundleinfo/binfo-includes.bats | 103 ++++ test/functional/testlib.bash | 4 +- 16 files changed, 1118 insertions(+), 7 deletions(-) create mode 100644 src/bundle_info.c create mode 100755 test/functional/bundleinfo/binfo-basic.bats create mode 100755 test/functional/bundleinfo/binfo-files.bats create mode 100755 test/functional/bundleinfo/binfo-includes.bats diff --git a/Makefile.am b/Makefile.am index 553eab30d..f11def7da 100644 --- a/Makefile.am +++ b/Makefile.am @@ -37,6 +37,7 @@ swupd_SOURCES = \ src/autoupdate.c \ src/binary_loader.c \ src/bundle.c \ + src/bundle_info.c \ src/check_update.c \ src/clean.c \ src/clr_bundle_add.c \ @@ -187,6 +188,9 @@ BATS = \ test/functional/bundleadd/add-uses-fullfile.bats \ test/functional/bundleadd/add-uses-zeropack.bats \ test/functional/bundleadd/add-verify-fix-path.bats \ + test/functional/bundleinfo/binfo-basic.bats \ + test/functional/bundleinfo/binfo-files.bats \ + test/functional/bundleinfo/binfo-includes.bats \ test/functional/bundlelist/list-all.bats \ test/functional/bundlelist/list-client-certificate.bats \ test/functional/bundlelist/list-deps-flat.bats \ diff --git a/docs/swupd.1.rst b/docs/swupd.1.rst index 78bb882b4..b5666f6d9 100644 --- a/docs/swupd.1.rst +++ b/docs/swupd.1.rst @@ -214,7 +214,7 @@ SUBCOMMANDS List all installed software bundles in the local system. Bundles available can be listed with the `--all` option. - - `-a, --all` + - `-a, --all` Lists all available software bundles, either installed or not, that are available. It will return 0 with succeeded and a different value @@ -231,6 +231,22 @@ SUBCOMMANDS dependency. Combine with `--all` to report all bundles including those not installed on the system. +``bundle-info`` + + Display detailed information about a bundle. + + - `--includes` + + Show all bundles included by this bundle. + + - `--files` + + Show all files installed by this bundle. + + - `-V, --version` + + Show the bundle info for the specified version V, also accepts 'latest' and 'current' (default). + ``check-update`` Checks whether an update is available and prints out the information diff --git a/src/bundle_info.c b/src/bundle_info.c new file mode 100644 index 000000000..dd12b6b75 --- /dev/null +++ b/src/bundle_info.c @@ -0,0 +1,570 @@ +/* + * Software Updater - client side + * + * Copyright © 2012-2019 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 . + * + */ + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include "swupd.h" + +#define FLAG_INCLUDES 2000 +#define FLAG_FILES 2001 + +static bool cmdline_option_includes = false; +static bool cmdline_option_files = false; +static int cmdline_option_version = 0; + +static char *bundle; + +struct bundle_info { + struct manifest *manifest; + struct list *direct_includes; + struct list *indirect_includes; + struct list *includes_manifests; + struct list *files; + bool is_installed; + bool is_experimental; + int version_latest_available; + long size_total; + long size_required; +}; + +static void print_help(void) +{ + print("Usage:\n"); + print(" swupd bundle-info [OPTIONS...] BUNDLE\n\n"); + + global_print_help(); + + print("Options:\n"); + print(" -V, --version=V Show the bundle info for the specified version V, also accepts 'latest' and 'current' (default)\n"); + print(" --includes Show all bundles included by this bundle\n"); + print(" --files Show all files installed by this bundle\n"); + print("\n"); +} + +static const struct option prog_opts[] = { + { "version", required_argument, 0, 'V' }, + { "includes", no_argument, 0, FLAG_INCLUDES }, + { "files", no_argument, 0, FLAG_FILES }, +}; + +static bool parse_opt(int opt, UNUSED_PARAM char *optarg) +{ + int err; + + switch (opt) { + case 'V': + if (strcmp("current", optarg) == 0) { + cmdline_option_version = 0; + return true; + } else if (strcmp("latest", optarg) == 0) { + cmdline_option_version = -1; + return true; + } + err = strtoi_err(optarg, &cmdline_option_version); + if (err < 0 || cmdline_option_version < 0) { + error("Invalid --version argument: %s\n\n", optarg); + return false; + } + return true; + case FLAG_INCLUDES: + cmdline_option_includes = optarg_to_bool(optarg); + return true; + case FLAG_FILES: + cmdline_option_files = optarg_to_bool(optarg); + return true; + default: + return false; + } + return false; +} + +static const struct global_options opts = { + prog_opts, + sizeof(prog_opts) / sizeof(struct option), + parse_opt, + print_help, +}; + +static bool parse_options(int argc, char **argv) +{ + int ind = global_parse_options(argc, argv, &opts); + + if (ind < 0) { + return false; + } + + if (argc <= optind) { + error("Please specify the bundle you wish to display information from\n\n"); + return false; + } + + if (optind + 1 < argc) { + error("Please specify only one bundle at a time\n\n"); + return false; + } + + bundle = *(argv + optind); + + return true; +} + +static void free_bundle_data(struct bundle_info *bundle_info) +{ + if (bundle_info->direct_includes) { + list_free_list(bundle_info->direct_includes); + } + if (bundle_info->indirect_includes) { + list_free_list_and_data(bundle_info->indirect_includes, free); + } + if (bundle_info->files) { + list_free_list(bundle_info->files); + } + if (bundle_info->includes_manifests) { + list_free_list_and_data(bundle_info->includes_manifests, free_manifest_data); + } +} + +static void print_general_info(struct bundle_info *bundle_info) +{ + struct manifest *bundle = bundle_info->manifest; + char *info_header; + char *pretty_size; + int header_length; + + string_or_die(&info_header, " Info for bundle: %s ", bundle->component); + header_length = strlen(info_header); + + /* info header */ + print_pattern("_", header_length); + print("%s\n", info_header); + print_pattern("_", header_length); + + /* status info */ + print("\nStatus: %s%s\n", bundle_info->is_installed ? "Installed" : "Not installed", bundle_info->is_experimental ? " (experimental)" : ""); + + /* version info */ + if (bundle_info->is_installed) { + if (bundle_info->manifest->version < bundle_info->version_latest_available) { + print("\nThere is an update for bundle %s:\n", bundle->component); + } else { + print("Bundle %s is up to date:\n", bundle->component); + } + print(" - Last updated in version: %d\n", bundle->version); + print(" - "); + } + print("Latest available version: %d\n", bundle_info->version_latest_available); + + /* size info */ + print("\nBundle size:\n"); + prettify_size(bundle->contentsize, &pretty_size); + print(" - Size of bundle: %s\n", pretty_size); + free_string(&pretty_size); + if (bundle_info->is_installed) { + prettify_size(bundle_info->size_total, &pretty_size); + print(" - Size bundle takes on disk (includes dependencies): %s\n", pretty_size); + free_string(&pretty_size); + } else { + if (bundle_info->size_required >= 0) { + prettify_size(bundle_info->size_required, &pretty_size); + print(" - Size the bundle will take on disk if installed (includes dependencies): %s\n", pretty_size); + free_string(&pretty_size); + } else { + print(" - Size the bundle will take on disk if installed (includes dependencies): unknown\n"); + } + } + + free_string(&info_header); +} + +static void print_includes(struct bundle_info *bundle_info) +{ + struct manifest *bundle_manifest = bundle_info->manifest; + struct list *iter; + char *included_bundle; + + /* print direct includes */ + if (bundle_manifest->includes || bundle_manifest->optional) { + print("\nDirectly included bundles (%d):\n", list_len(bundle_manifest->includes) + list_len(bundle_manifest->optional)); + iter = list_head(bundle_manifest->includes); + if (iter) { + while (iter) { + included_bundle = iter->data; + print(" - %s\n", included_bundle); + iter = iter->next; + } + } + iter = list_head(bundle_manifest->optional); + if (iter) { + while (iter) { + included_bundle = iter->data; + if (is_installed_bundle(bundle_manifest->component)) { + /* it only makes sense to provide details about an optional include + * not being installed if the bundle that includes it is installed */ + print(" - %s (optional%s)\n", included_bundle, is_installed_bundle(included_bundle) ? "" : ", not installed"); + } else { + print(" - %s (optional)\n", included_bundle); + } + iter = iter->next; + } + } + } + + /* print indirect includes (if any) */ + iter = list_head(bundle_info->indirect_includes); + if (iter) { + print("\nIndirectly included bundles (%d):\n", list_len(bundle_info->indirect_includes)); + while (iter) { + included_bundle = iter->data; + print(" - %s\n", included_bundle); + iter = iter->next; + } + } +} + +static void print_files(struct bundle_info *bundle_info) +{ + struct manifest *bundle_manifest = bundle_info->manifest; + struct list *iter; + struct file *file; + long count = 0; + + print("\nFiles in bundle"); + + if (cmdline_option_includes) { + iter = list_head(bundle_info->files); + print(" (includes dependencies):\n"); + } else { + iter = list_head(bundle_manifest->files); + print(":\n"); + } + + while (iter) { + count++; + file = iter->data; + print(" - %s\n", file->filename); + iter = iter->next; + } + + print("\nTotal files: %d\n", count); +} + +static enum swupd_code get_indirect_includes(struct manifest *mom, struct list *recurse_includes, struct list *direct_includes, struct list **indirect_includes, bool from_recursion) +{ + struct list *iter; + struct file *file = NULL; + struct manifest *manifest = NULL; + char *included_bundle; + + iter = list_head(recurse_includes); + while (iter) { + + included_bundle = iter->data; + iter = iter->next; + + /* if the bundle has already been added to the direct includes list do not + * add it as indirect include */ + if (from_recursion) { + if (list_search(direct_includes, included_bundle, list_strcmp)) { + continue; + } + } + + file = search_bundle_in_manifest(mom, included_bundle); + if (!file) { + error("Bundle %s could not be found in the MoM\n", included_bundle); + return SWUPD_INVALID_BUNDLE; + } + + manifest = load_manifest(file->last_change, file, mom, true, NULL); + if (!manifest) { + error("Manifest for bundle %s could not be loaded\n", included_bundle); + return SWUPD_COULDNT_LOAD_MANIFEST; + } + + /* only add to the list those includes gotten by recursion */ + if (from_recursion) { + *indirect_includes = list_prepend_data(*indirect_includes, strdup_or_die(included_bundle)); + } + + if (manifest->includes) { + get_indirect_includes(mom, manifest->includes, direct_includes, indirect_includes, true); + } + + if (manifest->optional) { + get_indirect_includes(mom, manifest->optional, direct_includes, indirect_includes, true); + } + + free_manifest(manifest); + } + + return SWUPD_OK; +} + +static enum swupd_code get_included_bundles(struct bundle_info *bundle_info, struct manifest *mom) +{ + struct manifest *bundle_manifest = bundle_info->manifest; + struct list *direct_includes = NULL; + struct list *indirect_includes = NULL; + enum swupd_code ret; + + /* get a list of all includes (direct and indirect includes) */ + direct_includes = list_clone_and_concat(bundle_manifest->includes, bundle_manifest->optional); + direct_includes = list_sort(direct_includes, list_strcmp); + ret = get_indirect_includes(mom, direct_includes, direct_includes, &indirect_includes, false); + if (ret) { + list_free_list(direct_includes); + list_free_list(indirect_includes); + return ret; + } + + /* some bundles could have included bundles that had already + * been included by another bundle, so remove duplication */ + indirect_includes = list_string_deduplicate(indirect_includes); + + bundle_info->direct_includes = direct_includes; + bundle_info->indirect_includes = indirect_includes; + + return ret; +} + +static enum swupd_code get_bundle_size(struct bundle_info *bundle_info) +{ + struct file *file = NULL; + struct list *iter; + struct stat st; + enum swupd_code ret = SWUPD_OK; + long size_installed = 0; + char *file_path = NULL; + + /* there are two different sizes we are interested here: + * - the total size of the bundle including all its dependencies, + * - the size increase if the bundle gets installed (if already installed the increase is 0): + * increase = + - */ + bundle_info->size_total = get_manifest_list_contentsize(bundle_info->includes_manifests); + + /* subtract the size of the files already installed from the total size to + * figure out how much space is required for the bundle if not installed */ + iter = list_head(bundle_info->files); + while (iter) { + file = iter->data; + iter = iter->next; + + string_or_die(&file_path, "%s/%s", globals.path_prefix, file->filename); + if (lstat(file_path, &st) == 0) { + if (S_ISREG(st.st_mode)) { + size_installed += st.st_size; + } + } + free_string(&file_path); + } + if (bundle_info->size_total - size_installed < 0) { + ret = SWUPD_UNEXPECTED_CONDITION; + } + bundle_info->size_required = bundle_info->size_total - size_installed; + + return ret; +} + +enum swupd_code bundle_info(char *bundle) +{ + enum swupd_code ret = SWUPD_OK; + int requested_version, latest_version; + struct manifest *mom = NULL; + struct manifest *latest_mom = NULL; + struct manifest *latest_manifest = NULL; + struct file *file = NULL; + struct list *subs = NULL; + struct list *iter; + struct bundle_info bundle_info = { NULL, NULL, NULL, NULL, NULL, false, false, 0, 0, 0 }; + bool mix_exists; + bool system_up_to_date; + + /* get the very latest version available */ + latest_version = version_get_absolute_latest(); + if (latest_version < 0) { + error("Unable to determine the server version\n"); + ret = SWUPD_SERVER_CONNECTION_ERROR; + return ret; + } + + /* if no version was specified, or the user specified the "current" version, + * get the version the system is currently at */ + if (cmdline_option_version == 0) { + requested_version = get_current_version(globals.path_prefix); + if (requested_version < 0) { + error("Unable to determine current OS version\n"); + ret = SWUPD_CURRENT_VERSION_UNKNOWN; + return ret; + } + } else if (cmdline_option_version == -1) { + /* user selected "latest" */ + requested_version = latest_version; + } else { + /* user specified an arbitrary version */ + requested_version = cmdline_option_version; + } + + system_up_to_date = requested_version == latest_version; + mix_exists = (check_mix_exists() & system_on_mix()); + + /* get the MoM for the requested version */ + mom = load_mom(requested_version, false, mix_exists, NULL); + if (!mom) { + error("Cannot load official manifest MoM for version %i\n", requested_version); + ret = SWUPD_COULDNT_LOAD_MOM; + return ret; + } + + /* validate the bundle provided */ + file = search_bundle_in_manifest(mom, bundle); + if (!file) { + error("Bundle \"%s\" is invalid\n", bundle); + ret = SWUPD_INVALID_BUNDLE; + goto clean; + } + + /* if there is a newer version get the manifests */ + if (!system_up_to_date) { + + /* get mom from the latest version */ + latest_mom = load_mom(latest_version, false, mix_exists, NULL); + if (!latest_mom) { + error("Cannot load official manifest MoM for version %i\n", latest_version); + ret = SWUPD_COULDNT_LOAD_MOM; + goto clean; + } + + /* get the latest version of the bundle manifest */ + file = search_bundle_in_manifest(latest_mom, bundle); + if (file) { + latest_manifest = load_manifest(file->last_change, file, latest_mom, false, NULL); + if (!latest_manifest) { + error("Unable to download the manifest for %s version %d, exiting now\n", bundle, file->last_change); + ret = SWUPD_COULDNT_LOAD_MANIFEST; + goto clean; + } + } else { + debug("Bundle \"%s\" is no longer available at version %d\n", bundle, latest_version); + } + } + + /* get the manifest of the requested bundle along with all its dependencies */ + struct list *bundle_temp = NULL; + bundle_temp = list_prepend_data(bundle_temp, bundle); + ret = add_subscriptions(bundle_temp, &subs, mom, true, 0); + list_free_list(bundle_temp); + if (ret & add_sub_ERR) { + ret = SWUPD_COULDNT_LOAD_MANIFEST; + goto clean; + } else if (ret & add_sub_BADNAME) { + ret = SWUPD_INVALID_BUNDLE; + goto clean; + } else { + /* convert add_sub_NEW to a swupd_code */ + ret = SWUPD_OK; + } + set_subscription_versions(mom, NULL, &subs); + bundle_info.includes_manifests = recurse_manifest(mom, subs, NULL, false, NULL); + + /* get all files that are part of that bundle (including dependencies) */ + bundle_info.files = files_from_bundles(bundle_info.includes_manifests); + bundle_info.files = consolidate_files(bundle_info.files); + bundle_info.files = filter_out_deleted_files(bundle_info.files); + + /* collect bundle's data */ + iter = list_head(bundle_info.includes_manifests); + while (iter) { + struct manifest *manifest = iter->data; + iter = iter->next; + if (strcmp(manifest->component, bundle) == 0) { + bundle_info.manifest = manifest; + break; + } + } + file = search_bundle_in_manifest(mom, bundle); + bundle_info.is_experimental = file->is_experimental == 1 ? true : false; + bundle_info.is_installed = is_installed_bundle(bundle); + bundle_info.version_latest_available = latest_manifest ? latest_manifest->version : bundle_info.manifest->version; + + ret = get_included_bundles(&bundle_info, mom); + if (ret) { + error("Could not get all bundles included by %s\n", bundle); + goto clean; + } + + ret = get_bundle_size(&bundle_info); + if (ret) { + error("Could not get the size %s by %s\n", bundle_info.is_installed ? "used" : "required", bundle); + } + + /* always print the bundle's general info regardless of options selected */ + print_general_info(&bundle_info); + + /* optional info */ + if (cmdline_option_includes) { + print_includes(&bundle_info); + } + if (cmdline_option_files) { + print_files(&bundle_info); + } + + print("\n"); + +clean: + free_subscriptions(&subs); + free_bundle_data(&bundle_info); + free_manifest(latest_manifest); + free_manifest(latest_mom); + free_manifest(mom); + + return ret; +} + +enum swupd_code bundle_info_main(int argc, char **argv) +{ + int ret; + const int steps_in_bundleinfo = 1; + + /* there is no need to report in progress for bundle-info at this time */ + + if (!parse_options(argc, argv)) { + print_help(); + return SWUPD_INVALID_OPTION; + } + + progress_init_steps("bundle-info", steps_in_bundleinfo); + ret = swupd_init(SWUPD_ALL); + if (ret != 0) { + error("Failed swupd initialization. Exiting now\n"); + ret = SWUPD_CURL_INIT_FAILED; + goto exit; + } + + ret = bundle_info(bundle); + + swupd_deinit(); +exit: + progress_finish_steps("bundle-info", ret); + + return ret; +} diff --git a/src/helpers.c b/src/helpers.c index 655647ffb..b952f1a60 100644 --- a/src/helpers.c +++ b/src/helpers.c @@ -1170,3 +1170,36 @@ bool check_mix_exists(void) free_string(&fullpath); return ret; } + +void print_pattern(const char *pattern, int times) +{ + while (times > 0) { + info("%s", pattern); + times--; + } + info("\n"); +} + +void prettify_size(long size_in_bytes, char **pretty_size) +{ + double size; + + /* find the best way to show the provided size */ + size = (double)size_in_bytes; + if (size < 1000) { + string_or_die(pretty_size, "%.2lf Bytes", size); + return; + } + size = size / 1000; + if (size < 1000) { + string_or_die(pretty_size, "%.2lf KB", size); + return; + } + size = size / 1000; + if (size < 1000) { + string_or_die(pretty_size, "%.2lf MB", size); + return; + } + size = size / 1000; + string_or_die(pretty_size, "%.2lf GB", size); +} diff --git a/src/lib/list.c b/src/lib/list.c index e3c6c88f2..9cd256217 100644 --- a/src/lib/list.c +++ b/src/lib/list.c @@ -32,6 +32,13 @@ #include "macros.h" #include "strings.h" +int lex_sort(const void *a, const void *b) +{ + const char *name1 = (char *)a; + const char *name2 = (char *)b; + return strcmp(name1, name2); +} + static struct list *list_append_item(struct list *list, struct list *item) { list = list_tail(list); @@ -222,6 +229,14 @@ struct list *list_concat(struct list *list1, struct list *list2) return list1; } +struct list *list_clone_and_concat(struct list *list1, struct list *list2) +{ + struct list *list1_copy = list_clone(list1); + struct list *list2_copy = list_clone(list2); + + return list_concat(list1_copy, list2_copy); +} + struct list *list_free_item(struct list *item, list_free_data_fn_t list_free_data_fn) { struct list *ret_item; @@ -305,9 +320,11 @@ bool list_longer_than(struct list *list, int count) void *list_search(struct list *list, const void *item, comparison_fn_t comparison_fn) { - for (; list; list = list->next) { - if (comparison_fn(list->data, item) == 0) { - return list->data; + struct list *iter = list_head(list); + + for (; iter; iter = iter->next) { + if (comparison_fn(iter->data, item) == 0) { + return iter->data; } } @@ -318,3 +335,34 @@ int list_strcmp(const void *a, const void *b) { return strcmp((const char *)a, (const char *)b); } + +struct list *list_string_deduplicate(struct list *list) +{ + struct list *iter, *next, *preserver = NULL; + char *a, *b; + + list_sort(list, list_strcmp); + + preserver = iter = list_head(list); + while (iter) { + next = iter->next; + if (!next) { + /* we are done */ + break; + } + + a = iter->data; + b = iter->next->data; + + if (strcmp(a, b) == 0) { + /* duplicated item, remove one */ + iter = list_free_item(iter, free); + preserver = iter; + continue; + } + + iter = iter->next; + } + + return list_head(preserver); +} diff --git a/src/lib/list.h b/src/lib/list.h index 227da22b1..ff1fc4bb9 100644 --- a/src/lib/list.h +++ b/src/lib/list.h @@ -120,4 +120,17 @@ void *list_search(struct list *list, const void *item, comparison_fn_t compariso */ int list_strcmp(const void *a, const void *b); +/** + * @brief Creates a shallow copy of both lists and appends list2 at the tail of list1. + * Either list1 or list2 can be NULL. + * @returns The head of the resulting concatenation + */ +struct list *list_clone_and_concat(struct list *list1, struct list *list2); + +/** + * @brief Removes duplicated items from a list of strings. + * @returns The head of the filtered list. + */ +struct list *list_string_deduplicate(struct list *list); + #endif diff --git a/src/main.c b/src/main.c index 9df595cde..767b3a7ee 100644 --- a/src/main.c +++ b/src/main.c @@ -57,6 +57,7 @@ static struct subcmd commands[] = { { "bundle-add", "Install a new bundle", bundle_add_main }, { "bundle-remove", "Uninstall a bundle", bundle_remove_main }, { "bundle-list", "List installed bundles", bundle_list_main }, + { "bundle-info", "Display information about a bundle", bundle_info_main }, #ifdef EXTERNAL_MODULES_SUPPORT { "search", "Searches for the best bundle to install a binary or library (depends on os-core-search bundle)", external_search_main }, #endif diff --git a/src/swupd.h b/src/swupd.h index 32737a71b..eccf3992e 100644 --- a/src/swupd.h +++ b/src/swupd.h @@ -160,7 +160,6 @@ extern int main_verify(int current_version); extern enum swupd_code walk_tree(struct manifest *, const char *, bool, const regex_t *, struct file_counts *); extern int get_latest_version(char *v_url); -extern int get_absolute_latest_version(void); extern enum swupd_code read_versions(int *current_version, int *server_version, char *path_prefix); extern int get_current_version(char *path_prefix); extern bool get_distribution_string(char *path_prefix, char *dist); @@ -310,6 +309,8 @@ extern bool is_url_insecure(const char *url); extern void remove_trailing_slash(char *url); extern int link_or_copy(const char *orig, const char *dest); extern int link_or_copy_all(const char *orig, const char *dest); +extern void print_pattern(const char *pattern, int times); +extern void prettify_size(long size_in_bytes, char **pretty_size); /* subscription.c */ struct list *free_list_file(struct list *item); diff --git a/src/swupd_internal.h b/src/swupd_internal.h index 4cad0833c..3132cc516 100644 --- a/src/swupd_internal.h +++ b/src/swupd_internal.h @@ -16,5 +16,6 @@ extern enum swupd_code binary_loader_main(int argc, char **argv); extern enum swupd_code install_main(int argc, char **argv); extern enum swupd_code repair_main(int argc, char **argv); extern enum swupd_code diagnose_main(int argc, char **argv); +extern enum swupd_code bundle_info_main(int argc, char **argv); #endif diff --git a/src/update.c b/src/update.c index 31800d2d6..91a6bc691 100644 --- a/src/update.c +++ b/src/update.c @@ -649,7 +649,7 @@ static bool parse_opt(int opt, char *optarg) err = strtoi_err(optarg, &requested_version); if (err < 0 || requested_version < 0) { - error("Invalid --manifest argument: %s\n\n", optarg); + error("Invalid --version argument: %s\n\n", optarg); return false; } return true; diff --git a/swupd.bash b/swupd.bash index 6b9d1a1e9..33b1fb7ac 100644 --- a/swupd.bash +++ b/swupd.bash @@ -51,6 +51,9 @@ _swupd() ("bundle-list") opts="$global --all --deps --has-dep " break;; + ("bundle-info") + opts="$global --includes --files --version " + break;; ("search") opts="--help --all --quiet --verbose " break;; diff --git a/swupd.zsh b/swupd.zsh index 2f4a35221..28a10fb35 100644 --- a/swupd.zsh +++ b/swupd.zsh @@ -234,6 +234,16 @@ if [[ -n "$state" ]]; then ) _arguments $lsbundle && ret=0 ;; + bundle-info) + local -a infobundle; infobundle=( + $global_opts + '(help -V --version)'{-V,--version=}'[Show the bundle info for the specified version V, also accepts "latest" and "current" (default)]:version:()' + '(help)--includes[Show all bundles included by this bundle]' + '(help)--files[Show all files installed by this bundle]' + '*:bundle-info: _swupd_all_bundles -f' + ) + _arguments $infobundle && ret=0 + ;; search) local -a searches; searches=( '(help -v -vv --verbose -q --quiet)'{-v,-vv,--verbose}'[verbose mode]' diff --git a/test/functional/bundleinfo/binfo-basic.bats b/test/functional/bundleinfo/binfo-basic.bats new file mode 100755 index 000000000..63abcc181 --- /dev/null +++ b/test/functional/bundleinfo/binfo-basic.bats @@ -0,0 +1,168 @@ +#!/usr/bin/env bats + +# Author: Castulo Martinez +# Email: castulo.martinez@intel.com + +load "../testlib" + +global_setup() { + + create_test_environment "$TEST_NAME" + create_bundle -L -n test-bundle1 -f /file_1,/foo/file_2 "$TEST_NAME" + create_bundle -e -n test-bundle2 -f /file_3,/bar/file_4 "$TEST_NAME" + create_version "$TEST_NAME" 20 10 + update_bundle "$TEST_NAME" test-bundle1 --update /file_1 + create_version "$TEST_NAME" 30 20 + update_bundle "$TEST_NAME" test-bundle1 --add /foo/file_5 + +} + +test_setup() { + + # do nothing, just overwrite the lib test_setup + return + +} + +test_teardown() { + + # do nothing, just overwrite the lib test_setup + return + +} + +global_teardown() { + + destroy_test_environment "$TEST_NAME" + +} + +@test "BIN001: Show info about a bundle installed in the system" { + + # users can use the bundle-info command to see detailed info about a bundle + # if the bundle is installed it should show when it was last updated and + # how much disk space is taking + + run sudo sh -c "$SWUPD bundle-info $SWUPD_OPTS test-bundle1" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + _______________________________ + Info for bundle: test-bundle1 + _______________________________ + Status: Installed + There is an update for bundle test-bundle1: + - Last updated in version: 10 + - Latest available version: 30 + Bundle size: + - Size of bundle: .* KB + - Size bundle takes on disk \(includes dependencies\): .* KB + EOM + ) + assert_regex_in_output "$expected_output" + +} + +@test "BIN002: Show info about a bundle not installed in the system" { + + # users can use the bundle-info command to see detailed info about a bundle + # if the bundle is not installed it should show the latest available version + # and how much disk space it would take if installed + # also bundles that are experimental are marked as such in the status + + run sudo sh -c "$SWUPD bundle-info $SWUPD_OPTS test-bundle2" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + _______________________________ + Info for bundle: test-bundle2 + _______________________________ + Status: Not installed \(experimental\) + Latest available version: 10 + Bundle size: + - Size of bundle: .* KB + - Size the bundle will take on disk if installed \(includes dependencies\): .* KB + EOM + ) + assert_regex_is_output "$expected_output" + +} + +@test "BIN003: Show info about a bundle for a particular system version" { + + # a specific version can be specified to display info + + run sudo sh -c "$SWUPD bundle-info $SWUPD_OPTS test-bundle1 --version 20" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + _______________________________ + Info for bundle: test-bundle1 + _______________________________ + Status: Installed + There is an update for bundle test-bundle1: + - Last updated in version: 20 + - Latest available version: 30 + Bundle size: + - Size of bundle: .* KB + - Size bundle takes on disk \(includes dependencies\): .* KB + EOM + ) + assert_regex_in_output "$expected_output" + +} + +@test "BIN004: Show info about a bundle for a particular system version" { + + # version can take latest or current + + run sudo sh -c "$SWUPD bundle-info $SWUPD_OPTS test-bundle1 --version latest" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + _______________________________ + Info for bundle: test-bundle1 + _______________________________ + Status: Installed + Bundle test-bundle1 is up to date: + - Last updated in version: 30 + - Latest available version: 30 + Bundle size: + - Size of bundle: .* KB + - Size bundle takes on disk \(includes dependencies\): .* KB + EOM + ) + assert_regex_in_output "$expected_output" + + run sudo sh -c "$SWUPD bundle-info $SWUPD_OPTS test-bundle1 --version current" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + _______________________________ + Info for bundle: test-bundle1 + _______________________________ + Status: Installed + There is an update for bundle test-bundle1: + - Last updated in version: 10 + - Latest available version: 30 + Bundle size: + - Size of bundle: .* KB + - Size bundle takes on disk \(includes dependencies\): .* KB + EOM + ) + assert_regex_in_output "$expected_output" + +} + +@test "BIN005: Try fo show nfo about an invalid bundle" { + + run sudo sh -c "$SWUPD bundle-info $SWUPD_OPTS bad-bundle" + + assert_status_is "$SWUPD_INVALID_BUNDLE" + expected_output=$(cat <<-EOM + Error: Bundle "bad-bundle" is invalid + EOM + ) + assert_is_output "$expected_output" + +} diff --git a/test/functional/bundleinfo/binfo-files.bats b/test/functional/bundleinfo/binfo-files.bats new file mode 100755 index 000000000..685910a3d --- /dev/null +++ b/test/functional/bundleinfo/binfo-files.bats @@ -0,0 +1,138 @@ +#!/usr/bin/env bats + +# Author: Castulo Martinez +# Email: castulo.martinez@intel.com + +load "../testlib" + +global_setup() { + + create_test_environment "$TEST_NAME" + create_bundle -n test-bundle1 -f /file_1,/foo/file_2 "$TEST_NAME" + create_bundle -n test-bundle2 -f /file_3,/bar/file_4 "$TEST_NAME" + # add test-bundle2 as a dependency of test-bundle1 + add_dependency_to_manifest "$WEBDIR"/10/Manifest.test-bundle1 test-bundle2 + create_version "$TEST_NAME" 20 10 + update_bundle "$TEST_NAME" test-bundle1 --header-only + update_bundle "$TEST_NAME" test-bundle2 --add /file_5 + +} + +test_setup() { + + # do nothing, just overwrite the lib test_setup + return + +} + +test_teardown() { + + # do nothing, just overwrite the lib test_setup + return + +} + +global_teardown() { + + destroy_test_environment "$TEST_NAME" + +} + +@test "BIN008: Show the files that are part of a bundle" { + + # bundle-info --files show the files that are part of that bundle + # it doesn't include files from dependencies + + run sudo sh -c "$SWUPD bundle-info $SWUPD_OPTS test-bundle1 --files" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + _______________________________ + Info for bundle: test-bundle1 + _______________________________ + Status: Not installed + Latest available version: 20 + Bundle size: + - Size of bundle: .* KB + - Size the bundle will take on disk if installed \(includes dependencies\): .* KB + Files in bundle: + - /usr/share/clear/bundles/test-bundle1 + - /foo/file_2 + - /foo + - /file_1 + Total files: 4 + EOM + ) + assert_regex_is_output "$expected_output" + +} + +@test "BIN009: Show the files that are part of a bundle and its dependencies" { + + # bundle-info --files show the files that are part of that bundle, if + # the --includes flag is also used it doest include files from dependencies + + run sudo sh -c "$SWUPD bundle-info $SWUPD_OPTS test-bundle1 --files --includes" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + _______________________________ + Info for bundle: test-bundle1 + _______________________________ + Status: Not installed + Latest available version: 20 + Bundle size: + - Size of bundle: .* KB + - Size the bundle will take on disk if installed \(includes dependencies\): .* KB + Directly included bundles \(1\): + - test-bundle2 + Files in bundle \(includes dependencies\): + - /bar + - /bar/file_4 + - /file_1 + - /file_3 + - /foo + - /foo/file_2 + - /usr/share/clear/bundles/test-bundle1 + - /usr/share/clear/bundles/test-bundle2 + Total files: 8 + EOM + ) + assert_regex_is_output "$expected_output" + +} + +@test "BIN010: Show the files that are part of a bundle and its dependencies for a specific version" { + + # the --version flag can be appended to the command to select a specific version + + run sudo sh -c "$SWUPD bundle-info $SWUPD_OPTS test-bundle1 --files --includes --version latest" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + _______________________________ + Info for bundle: test-bundle1 + _______________________________ + Status: Not installed + Latest available version: 20 + Bundle size: + - Size of bundle: .* KB + - Size the bundle will take on disk if installed \(includes dependencies\): .* KB + Directly included bundles \(1\): + - test-bundle2 + Files in bundle \(includes dependencies\): + - /bar + - /bar/file_4 + - /file_1 + - /file_3 + - /file_5 + - /foo + - /foo/file_2 + - /usr/share/clear/bundles/test-bundle1 + - /usr/share/clear/bundles/test-bundle2 + Total files: 9 + EOM + ) + assert_regex_is_output "$expected_output" + +} diff --git a/test/functional/bundleinfo/binfo-includes.bats b/test/functional/bundleinfo/binfo-includes.bats new file mode 100755 index 000000000..6f943481b --- /dev/null +++ b/test/functional/bundleinfo/binfo-includes.bats @@ -0,0 +1,103 @@ +#!/usr/bin/env bats + +# Author: Castulo Martinez +# Email: castulo.martinez@intel.com + +load "../testlib" + +global_setup() { + + create_test_environment "$TEST_NAME" + create_bundle -n test-bundle1 -f /file_1 "$TEST_NAME" + create_bundle -n test-bundle2 -f /file_2 "$TEST_NAME" + create_bundle -n test-bundle3 -f /file_3 "$TEST_NAME" + create_bundle -n test-bundle4 -f /file_4 "$TEST_NAME" + # add test-bundle2 as a dependency of test-bundle1 and test-bundle3 as optional + add_dependency_to_manifest "$WEBDIR"/10/Manifest.test-bundle1 test-bundle2 + add_dependency_to_manifest -o "$WEBDIR"/10/Manifest.test-bundle1 test-bundle3 + # add test-bundle4 as a dependency of test-bundle2 + add_dependency_to_manifest "$WEBDIR"/10/Manifest.test-bundle2 test-bundle4 + # add one more dependency in a new version + create_version "$TEST_NAME" 20 10 + update_bundle "$TEST_NAME" test-bundle1 --header-only + create_bundle -n test-bundle5 -f /file_5 "$TEST_NAME" + add_dependency_to_manifest "$WEBDIR"/20/Manifest.test-bundle1 test-bundle5 + +} + +test_setup() { + + # do nothing, just overwrite the lib test_setup + return + +} + +test_teardown() { + + # do nothing, just overwrite the lib test_setup + return + +} + +global_teardown() { + + destroy_test_environment "$TEST_NAME" + +} + +@test "BIN006: Show info about a bundle including its dependencies" { + + # the --includes flag can be used to show all directly and indirectly + # included and optional bundles + + run sudo sh -c "$SWUPD bundle-info $SWUPD_OPTS test-bundle1 --includes" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + _______________________________ + Info for bundle: test-bundle1 + _______________________________ + Status: Not installed + Latest available version: 20 + Bundle size: + - Size of bundle: .* KB + - Size the bundle will take on disk if installed \(includes dependencies\): .* KB + Directly included bundles \(2\): + - test-bundle2 + - test-bundle3 \(optional\) + Indirectly included bundles \(1\): + - test-bundle4 + EOM + ) + assert_regex_is_output "$expected_output" + +} + +@test "BIN007: Show info about a specific version of bundle including its dependencies" { + + # the --includes flag can be used to show all directly and indirectly + # included and optional bundles algon with the --version flag + + run sudo sh -c "$SWUPD bundle-info $SWUPD_OPTS test-bundle1 --includes --version latest" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + _______________________________ + Info for bundle: test-bundle1 + _______________________________ + Status: Not installed + Latest available version: 20 + Bundle size: + - Size of bundle: .* KB + - Size the bundle will take on disk if installed \(includes dependencies\): .* KB + Directly included bundles \(3\): + - test-bundle2 + - test-bundle5 + - test-bundle3 \(optional\) + Indirectly included bundles \(1\): + - test-bundle4 + EOM + ) + assert_regex_is_output "$expected_output" + +} diff --git a/test/functional/testlib.bash b/test/functional/testlib.bash index 4b2fb6764..990ad196f 100644 --- a/test/functional/testlib.bash +++ b/test/functional/testlib.bash @@ -3018,7 +3018,8 @@ generate_test() { # swupd_function printf '\t# \n\n' # shellcheck disable=SC2016 printf '\trun sudo sh -c "$SWUPD $SWUPD_OPTS "\n\n' - printf '\t# assert_status_is 0\n' + # shellcheck disable=SC2016 + printf '\t# assert_status_is "$SWUPD_OK"\n' # shellcheck disable=SC2016 printf '\t# expected_output=$(cat <<-EOM\n' printf '\t# \t\n' @@ -3060,6 +3061,7 @@ get_next_available_id() { # swupd_function bundleadd) group=ADD;; bundleremove) group=REM;; bundlelist) group=LST;; + bundleinfo) group=BIN;; diagnose) group=DIA;; update) group=UPD;; checkupdate) group=CHK;;