static int clean_swupd_state_data(int UNUSED_PARAM current_version, int UNUSED_PARAM latest_version) { #warning should handle other data files and consider current and latest version LOG_INFO(NULL, "Cleaning swupd state files", class_disk_sp, ""); if (rm_staging_dir_contents("delta")) return -1; if (rm_staging_dir_contents("download")) return -1; if (rm_staging_dir_contents("staged")) return -1; return 0; }
int remove_snapshot(const char *version) { int ret; uint64_t before, after; int attempts = 2; LOG_INFO(NULL, "Removing snapshot files", class_disk_sp, "%s", version); #warning TODO remove when repair OS updates are available /* Right now repairOS and OS can change in the same build. * That should be disallowed in the future. * Until then, remove the repair OS config files */ ret = remove_esp_repairOS_boot_config_files(version); if (ret != 0) return ret; ret = remove_esp_OS_boot_config_files(version); if (ret != 0) return ret; before = get_available_btrfs_space(); ret = remove_btrfs_snapshot(version); if (ret != 0) return ret; LOG_INFO(NULL, "Removing snapshot OS tmp files", class_disk_sp, ""); ret = rm_staging_dir_contents(version); if (ret != 0) return ret; /* Give time to btrfs to update its disk space numbers */ after = get_available_btrfs_space(); while (after <= before) { LOG_ERROR(NULL, "btrfs free space did not change", class_disk_sp, ""); sleep(BTRFS_SLEEP_TIME); attempts--; if (!attempts) { break; } after = get_available_btrfs_space(); } 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; }
int install_bundles(struct list *bundles, int current_version, struct manifest *mom) { int ret; struct file *file; struct list *iter; struct list *to_install_bundles, *to_install_files; /* step 1: check bundle args are valid if so populate subs struct */ ret = add_subscriptions(bundles, current_version, mom); if (ret) { if (ret == 1) { printf("bundle(s) already installed, exiting now\n"); } ret = EBUNDLE_INSTALL; goto out; } subscription_versions_from_MoM(mom, 0); to_install_bundles = recurse_manifest(mom, NULL); if (!to_install_bundles) { printf("Error: Cannot load to install bundles\n"); ret = ERECURSE_MANIFEST; goto out; } to_install_files = files_from_bundles(to_install_bundles); to_install_files = consolidate_files(to_install_files); /* step 2: download neccessary packs */ (void)rm_staging_dir_contents("download"); printf("Downloading packs...\n"); (void)download_subscribed_packs(true); /* step 3: Add tracked bundles */ read_subscriptions_alt(); subscription_versions_from_MoM(mom, 0); mom->submanifests = recurse_manifest(mom, NULL); if (!mom->submanifests) { printf("Error: Cannot load installed bundles\n"); ret = ERECURSE_MANIFEST; goto out; } mom->files = files_from_bundles(mom->submanifests); mom->files = consolidate_files(mom->files); /* step 4: Install all bundle(s) files into the fs */ printf("Installing bundle(s) files...\n"); iter = list_head(to_install_files); while (iter) { file = iter->data; iter = iter->next; if (file->is_deleted || file->do_not_update || ignore(file)) { continue; } ret = do_staging(file, mom); if (ret) { ret = verify_fix_path(file->filename, mom); } if (ret) { ret = EBUNDLE_INSTALL; goto out; } } iter = list_head(to_install_files); while (iter) { file = iter->data; iter = iter->next; if (file->is_deleted || file->do_not_update || ignore(file)) { continue; } /* This was staged by verify_fix_path */ if (!file->staging) { file = search_file_in_manifest(mom, file->filename); } rename_staged_file_to_final(file); } sync(); /* step 5: Run any scripts that are needed to complete update */ run_scripts(); ret = 0; printf("Bundle(s) installation done.\n"); out: free_subscriptions(); return ret; }
static int update_loop(struct list *updates, struct manifest *server_manifest) { 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 0 if (rm_staging_dir_contents("download")) { return -1; } #endif /* 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, server_manifest); 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; struct manifest *current_manifest = NULL, *server_manifest = NULL; struct list *updates = NULL; int ret; int lock_fd; int retries = 0; int timeout = 10; srand(time(NULL)); ret = swupd_init(&lock_fd); if (ret != 0) { /* being here means we already close log by a previously caught error */ printf("Updater failed to initialize, exiting now.\n"); return ret; } if (!check_network()) { printf("Error: Network issue, unable to proceed with update\n"); ret = EXIT_FAILURE; goto clean_curl; } printf("Update started.\n"); read_subscriptions_alt(); if (!signature_initialize(UPDATE_CA_CERTS_PATH "/" SIGNATURE_CA_CERT)) { goto clean_curl; } /* Step 1: get versions */ ret = check_versions(¤t_version, &server_version, path_prefix); if (ret < 0) { goto clean_curl; } if (server_version <= current_version) { printf("Version on server (%i) is not newer than system version (%i)\n", server_version, current_version); ret = EXIT_SUCCESS; goto clean_curl; } printf("Preparing to update from %i to %i\n", current_version, server_version); /* Step 2: housekeeping */ if (rm_staging_dir_contents("download")) { goto clean_curl; } load_current_manifests: /* Step 3: setup manifests */ /* get the from/to MoM manifests */ printf("Querying current manifest.\n"); ret = load_manifests(current_version, current_version, "MoM", NULL, ¤t_manifest); if (ret) { /* TODO: possibly remove this as not getting a "from" manifest is not fatal * - we just don't apply deltas */ if (retries < MAX_TRIES) { increment_retries(&retries, &timeout); printf("Retry #%d downloading from/to MoM Manifests\n", retries); goto load_current_manifests; } printf("Failure retrieving manifest from server\n"); goto clean_exit; } /* Reset the retries and timeout for subsequent download calls */ retries = 0; timeout = 10; load_server_manifests: printf("Querying server manifest.\n"); ret = load_manifests(current_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; } retries = 0; timeout = 10; ret = add_included_manifests(server_manifest); if (ret) { goto clean_exit; } 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 */ current_manifest->submanifests = recurse_manifest(current_manifest, NULL); if (!current_manifest->submanifests) { printf("Cannot load current MoM sub-manifests, (%s), exiting\n", strerror(errno)); goto clean_exit; } /* consolidate the current collective manifests down into one in memory */ current_manifest->files = files_from_bundles(current_manifest->submanifests); current_manifest->files = consolidate_files(current_manifest->files); /* read the new collective of manifests that we are subscribed to */ server_manifest->submanifests = recurse_manifest(server_manifest, NULL); if (!server_manifest->submanifests) { printf("Error: Cannot load server MoM sub-manifests, (%s), exiting\n", strerror(errno)); goto clean_exit; } /* consolidate the new collective manifests down into one in memory */ server_manifest->files = files_from_bundles(server_manifest->submanifests); server_manifest->files = consolidate_files(server_manifest->files); /* 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(false); if (ret) { // packs don't always exist, tolerate that but not ENONET if (retries < MAX_TRIES) { increment_retries(&retries, &timeout); printf("Retry #%d downloading packs\n", retries); goto download_packs; } printf("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(current_version, server_version); /* Step 7: apply the update */ ret = update_loop(updates, server_manifest); if (ret == 0) { ret = update_device_latest_version(server_version); printf("Update was applied.\n"); } 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(); free_globals(); v_lockfile(lock_fd); dump_file_descriptor_leaks(); if ((current_version < server_version) && (ret == 0)) { printf("Update successful. System updated from version %d to version %d\n", current_version, server_version); } else if (ret == 0) { printf("Update complete. System already up-to-date at version %d\n", current_version); } return ret; }
int install_bundles(struct list *bundles, int current_version, struct manifest *mom) { int ret; struct file *file; struct list *iter; struct sub *sub; /* step 1: check bundle args are valid if so populate subs struct */ ret = add_subscriptions(bundles, current_version, mom); if (ret) { if (ret == 1) { printf("bundle(s) already installed, exiting now\n"); } ret = EBUNDLE_INSTALL; goto out; } subscription_versions_from_MoM(mom, 0); ret = recurse_manifest(mom, NULL); if (ret != 0) { printf("Error: Cannot load MoM sub-manifests (ret = %d)\n", ret); ret = ERECURSE_MANIFEST; goto out; } consolidate_submanifests(mom); /* step 2: download neccessary packs */ (void)rm_staging_dir_contents("download"); printf("Downloading required packs...\n"); ret = download_subscribed_packs(true); if (ret != 0) { printf("pack downloads failed, cannot proceed with the installation, exiting.\n"); goto out; } /* step 3: 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, mom); if (ret == 0) { rename_staged_file_to_final(file); } } sync(); /* step 4: 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); } } /* step 5: Run any scripts that are needed to complete update */ run_scripts(); ret = 0; printf("Bundle(s) installation done.\n"); out: free_subscriptions(); 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; }