/* 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); mom = load_mom(version); if (!mom) { ret = EMOM_NOTFOUND; goto out; } sub_list = recurse_manifest(mom, bundle_name); if (!sub_list) { ret = ERECURSE_MANIFEST; goto free_out; } *submanifest = sub_list->data; sub_list->data = NULL; ret = 0; free_out: free_manifest(mom); out: return ret; }
/* * 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; ret = swupd_init(&lock_fd); if (ret != 0) { printf("Error: Failed updater initialization. Exiting now\n"); return ret; } if (!check_network()) { printf("Error: Network issue, unable to download manifest\n"); v_lockfile(lock_fd); return EXIT_FAILURE; } current_version = get_current_version(path_prefix); if (current_version < 0) { printf("Error: Unable to determine current OS version\n"); v_lockfile(lock_fd); return ECURRENT_VERSION; } swupd_curl_set_current_version(current_version); MoM = load_mom(current_version); if (!MoM) { 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 install one ore more bundles passed in bundles * param as a null terminated array of strings */ int install_bundles_frontend(char **bundles) { int lock_fd; int ret = 0; int current_version; struct list *bundles_list = NULL; struct manifest *mom; /* initialize swupd and get current version from OS */ ret = swupd_init(&lock_fd); if (ret != 0) { printf("Failed updater initialization, exiting now.\n"); return ret; } current_version = get_current_version(path_prefix); if (current_version < 0) { printf("Error: Unable to determine current OS version\n"); ret = ECURRENT_VERSION; goto clean_and_exit; } swupd_curl_set_current_version(current_version); mom = load_mom(current_version); if (!mom) { printf("Cannot load official manifest MoM for version %i\n", current_version); ret = EMOM_NOTFOUND; goto clean_and_exit; } for (; *bundles; ++bundles) { bundles_list = list_prepend_data(bundles_list, *bundles); } ret = install_bundles(bundles_list, current_version, mom); list_free_list(bundles_list); free_manifest(mom); clean_and_exit: swupd_deinit(lock_fd); 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 = NULL; 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 = get_current_version(path_prefix); if (current_version < 0) { printf("Error: Unable to determine current OS version\n"); ret = ECURRENT_VERSION; goto out_free_curl; } swupd_curl_set_current_version(current_version); current_mom = load_mom(current_version); if (!current_mom) { printf("Unable to download/verify %d Manifest.MoM\n", current_version); ret = EMOM_NOTFOUND; goto out_free_curl; } if (!search_bundle_in_manifest(current_mom, bundle_name)) { printf("Bundle name is invalid, aborting removal\n"); ret = EBUNDLE_REMOVE; goto out_free_mom; } /* 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 */ current_mom->submanifests = recurse_manifest(current_mom, NULL); if (!current_mom->submanifests) { printf("Error: Cannot load MoM sub-manifests\n"); ret = ERECURSE_MANIFEST; goto out_free_mom; } if (is_included(bundle_name, current_mom)) { printf("Error: bundle requested to be removed is required by other installed bundles\n"); ret = EBUNDLE_REMOVE; goto out_free_mom; } current_mom->files = files_from_bundles(current_mom->submanifests); current_mom->files = consolidate_files(current_mom->files); /* 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 || !bundle_manifest) { printf("Error: Cannot load %s sub-manifest (ret = %d)\n", bundle_name, ret); 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_deinit(lock_fd); return ret; }
/* download_manifests() * Description: To search Clear bundles for a particular entry, a complete set of * manifests must be downloaded. This function does so, asynchronously, using * the curl_multi interface */ static int download_manifests(struct manifest **MoM, struct list **subs) { struct list *list = NULL; struct file *file = NULL; char *tarfile, *untard_file, *url = NULL; struct manifest *subMan = NULL; int current_version; int ret = 0; double size; bool did_download = false; current_version = get_current_version(path_prefix); if (current_version < 0) { fprintf(stderr, "Error: Unable to determine current OS version\n"); return ECURRENT_VERSION; } *MoM = load_mom(current_version, false, false, NULL); if (!(*MoM)) { fprintf(stderr, "Cannot load official manifest MoM for version %i\n", current_version); return EMOM_LOAD; } list = (*MoM)->manifests; size = query_total_download_size(list); if (size == -1) { fprintf(stderr, "Downloading manifests. Expect a delay, up to 100MB may be downloaded\n"); } else if (size > 0) { fprintf(stderr, "Downloading Clear Linux manifests\n"); fprintf(stderr, " %.2f MB total...\n\n", size); } while (list) { file = list->data; list = list->next; create_and_append_subscription(subs, file->filename); string_or_die(&untard_file, "%s/%i/Manifest.%s", state_dir, file->last_change, file->filename); string_or_die(&tarfile, "%s/%i/Manifest.%s.tar", state_dir, file->last_change, file->filename); if (access(untard_file, F_OK) == -1) { /* Do download */ subMan = load_manifest(file->last_change, file, *MoM, false); if (!subMan) { fprintf(stderr, "Cannot load official manifest MoM for version %i\n", current_version); } else { did_download = true; } free_manifest(subMan); } if (access(untard_file, F_OK) == -1) { string_or_die(&url, "%s/%i/Manifest.%s.tar", content_url, current_version, file->filename); fprintf(stderr, "Error: Failure reading from %s\n", url); free_string(&url); } unlink(tarfile); free_string(&untard_file); free_string(&tarfile); } if (did_download) { fprintf(stderr, "Completed manifests download.\n\n"); } return ret; }
/* 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; struct list *subs = NULL; copyright_header("software verify"); if (!parse_options(argc, argv)) { return EINVALID_OPTION; } /* parse command line options */ assert(argc >= 0); assert(argv != NULL); ret = swupd_init(&lock_fd); if (ret != 0) { printf("Failed verify initialization, exiting now.\n"); return ret; } /* Gather current manifests */ if (!version) { version = get_current_version(path_prefix); if (version < 0) { printf("Error: Unable to determine current OS version\n"); ret = ECURRENT_VERSION; goto clean_and_exit; } } if (version == -1) { version = get_latest_version(); if (version < 0) { printf("Unable to get latest version for install\n"); ret = EXIT_FAILURE; goto clean_and_exit; } } printf("Verifying version %i\n", version); if (!check_network()) { printf("Error: Network issue, unable to download manifest\n"); ret = ENOSWUPDSERVER; goto clean_and_exit; } read_subscriptions_alt(&subs); /* * 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. */ ret = rm_staging_dir_contents("download"); if (ret != 0) { printf("Failed to remove prior downloads, carrying on anyway\n"); } official_manifest = load_mom(version); if (!official_manifest) { /* This is hit when or if an OS version is specified for --fix which * is not available, or if there is a server error and a manifest is * not provided. */ printf("Unable to download/verify %d Manifest.MoM\n", version); ret = EMOM_NOTFOUND; /* No repair is possible without a manifest, nor is accurate reporting * of the state of the system. Therefore cleanup, report failure and exit */ goto clean_and_exit; } ret = add_included_manifests(official_manifest, &subs); if (ret) { ret = EMANIFEST_LOAD; goto clean_and_exit; } set_subscription_versions(official_manifest, NULL, &subs); official_manifest->submanifests = recurse_manifest(official_manifest, subs, NULL); if (!official_manifest->submanifests) { printf("Error: Cannot load MoM sub-manifests\n"); ret = ERECURSE_MANIFEST; goto clean_and_exit; } official_manifest->files = files_from_bundles(official_manifest->submanifests); official_manifest->files = consolidate_files(official_manifest->files); /* when fixing or installing we need input files. */ if (cmdline_option_fix || cmdline_option_install) { ret = get_required_files(official_manifest, subs); if (ret != 0) { ret = -ret; goto clean_and_exit; } } /* 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) { /* * 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); } free_manifest(official_manifest); brick_the_system_and_clean_curl: /* clean up */ /* * naming convention: All exit goto labels must follow the "brick_the_system_and_FOO:" pattern */ /* 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 */ clean_and_exit: telemetry(ret ? TELEMETRY_CRIT : TELEMETRY_INFO, "verify", "fix=%d\nret=%d\n" "current_version=%d\n" "file_replaced_count=%d\n" "file_not_replaced_count=%d\n" "file_missing_count=%d\n" "file_fixed_count=%d\n" "file_not_fixed_count=%d\n" "file_deleted_count=%d\n" "file_not_deleted_count=%d\n" "file_mismatch_count=%d\n" "file_extraneous_count=%d\n", cmdline_option_fix || cmdline_option_install, ret, version, file_replaced_count, file_not_replaced_count, file_missing_count, file_fixed_count, file_not_fixed_count, file_deleted_count, file_not_deleted_count, file_mismatch_count, file_extraneous_count); 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_deinit(lock_fd, &subs); return ret; }