void critical_verify_error(int version) { struct list *version_list = NULL; version_list = list_prepend_data(version_list, (void *)((intptr_t)version)); efivar_bootloader_set_next_boot_to_repair(repair_verify_failure, version_list); list_free_list(version_list); }
void list_free(list_T *l) { if (!in_free_unref_items) { list_free_contents(l); list_free_list(l); } }
void fatal_update_error(int from_version, int to_version) { struct list *version_list = NULL; version_list = list_prepend_data(version_list, (void *)((intptr_t)to_version)); version_list = list_prepend_data(version_list, (void *)((intptr_t)from_version)); efivar_bootloader_set_next_boot_to_repair(repair_update_failure, version_list); list_free_list(version_list); }
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(); }
void list_free_items(int copyID) { list_T *ll, *ll_next; for (ll = first_list; ll != NULL; ll = ll_next) { ll_next = ll->lv_used_next; if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) && ll->lv_watch == NULL) { /* Free the List and ordinary items it contains, but don't recurse * into Lists and Dictionaries, they will be in the list of dicts * or list of lists. */ list_free_list(ll); } } }
/* 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; }
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 */ RETRY_DOWNLOADS: /* when fixing (not installing): queue download and mark any files * which are already verified OK */ 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 -EFULLDOWNLOAD; } 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 -EFULLDOWNLOAD; } 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 = read_version_from_subvol_file(path_prefix); swupd_curl_set_current_version(current_version); 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; } 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_curl_cleanup(); v_lockfile(lock_fd); dump_file_descriptor_leaks(); free_globals(); return ret; }
int add_included_manifests(struct manifest *mom) { struct list *subbed = NULL; struct list *iter; int ret; iter = list_head(subs); while (iter) { subbed = list_prepend_data(subbed, ((struct sub *)iter->data)->component); iter = iter->next; } if (add_subscriptions(subbed, mom->version, mom) >= 0) { ret = 0; } else { ret = -1; } list_free_list(subbed); return ret; }
static struct list *parse_version_list(char *csv_version_str) { char *token; int version; struct list *list = NULL; if (!csv_version_str) return NULL; token = strtok(csv_version_str, ","); while (token != NULL) { if (sscanf(token, "%i", &version) != 1) { list_free_list(list); return NULL; } list = list_prepend_data(list, (void *)((intptr_t)version)); token = strtok(NULL, ","); } return list; }
static int do_update_bootloader_pref(void) { int result = 0; if (boot_prior) { try_log_first_boot(); result = efivar_bootloader_boot_check(current_version, false); } else if (boot_repair) { result = efivar_bootloader_boot_check(current_version, true); } else if (next_version != -1) { result = efivar_bootloader_set_next_boot_to_version(next_version); } else if (next_repair) { result = efivar_bootloader_set_next_boot_to_repair(repair_reason, next_repair); list_free_list(next_repair); } if (result < 0) return EXIT_FAILURE; return EXIT_SUCCESS; }
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, 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; }
static bool parse_options(int argc, char **argv) { int opt; int opt_count = 0; unsigned int count; int tmp_val; while ((opt = getopt_long(argc, argv, "hrpn:b:i:u:d", prog_opts, NULL)) != -1) { switch (opt) { case '?': case 'h': goto err; case 'r': boot_repair = true; break; case 'p': boot_prior = true; break; case 'n': if (sscanf(optarg, "%i", &next_version) != 1) { printf("Invalid --next_version argument\n\n"); goto err; } break; case 'b': next_repair = parse_version_list(optarg); count = list_len(next_repair); if (count != 1) { printf("Invalid --next_repair_boot_check_failed argument\n\n"); list_free_list(next_repair); goto err; } repair_reason = repair_boot_check_failure; break; case 'i': next_repair = parse_version_list(optarg); count = list_len(next_repair); if ((count == 0) || (count > VERIFY_FAILED_MAX_VERSIONS_COUNT)) { printf("Invalid --next_repair_integrity_check_failed argument\n\n"); list_free_list(next_repair); goto err; } repair_reason = repair_verify_failure; break; case 'u': next_repair = parse_version_list(optarg); count = list_len(next_repair); if (count != 2) { printf("Invalid --next_repair_update_failed argument\n\n"); list_free_list(next_repair); goto err; } /* switch values to have: to,from*/ tmp_val = (int)next_repair->data; next_repair->data = next_repair->next->data; next_repair->next->data = (void *)((intptr_t)tmp_val); repair_reason = repair_update_failure; break; case 'd': dump = true; break; default: printf("Unrecognized option\n\n"); goto err; } if (opt_count > 1) { printf("Invalid options\n\n"); goto err; } opt_count++; } if (opt_count == 0) goto err; return true; err: print_help(argv[0]); return false; }
/* * Download fullfiles from the list of files. * * Return 0 on success or a negative number or errors. */ int download_fullfiles(struct list *files, int *num_downloads) { struct swupd_curl_parallel_handle *download_handle; struct list *iter; struct list *need_download = NULL; struct file *file; struct stat stat; struct download_progress download_progress = { 0, 0, 0 }; unsigned int complete = 0; unsigned int list_length; const unsigned int MAX_FILES = 1000; if (!files) { /* nothing needs to be downloaded */ return SWUPD_OK; } /* make a new list with only the files we actually need to download */ for (iter = list_head(files); iter; iter = iter->next) { char *targetfile; file = iter->data; if (file->is_deleted || file->do_not_update) { continue; } string_or_die(&targetfile, "%s/staged/%s", state_dir, file->hash); if (lstat(targetfile, &stat) != 0 || !verify_file(file, targetfile)) { need_download = list_append_data(need_download, file); } free_string(&targetfile); } if (!need_download) { /* no file needs to be downloaded */ info("No extra files need to be downloaded\n"); progress_complete_step(); return 0; } /* we need to download some files, so set up curl */ download_handle = swupd_curl_parallel_download_start(get_max_xfer(MAX_XFER)); swupd_curl_parallel_download_set_callbacks(download_handle, download_successful, download_error, NULL); if (!download_handle) { /* 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 */ return -SWUPD_COULDNT_DOWNLOAD_FILE; } /* getting the size of many files can be very expensive, so if * the files are not too many, get their size, otherwise just use their count * to report progress */ list_length = list_len(need_download); if (list_length < MAX_FILES) { download_progress.total_download_size = fullfile_query_total_download_size(need_download); if (download_progress.total_download_size > 0) { /* enable the progress callback */ swupd_curl_parallel_download_set_progress_callbacks(download_handle, swupd_progress_callback, &download_progress); } else { debug("Couldn't get the size of the files to download, using number of files instead\n"); download_progress.total_download_size = 0; } } else { debug("Too many files to calculate download size (%d files), maximum is %d. Using number of files instead\n", list_length, MAX_FILES); } /* download loop */ info("Starting download of remaining update content. This may take a while...\n"); for (iter = list_head(need_download); iter; iter = iter->next) { file = iter->data; if (file->is_mix) { download_mix_file(file); } else { download_file(download_handle, file); } /* fall back for progress reporting when the download size * could not be determined */ if (download_progress.total_download_size == 0) { complete++; progress_report(complete, list_length); } } info("\n"); list_free_list(need_download); return swupd_curl_parallel_download_end(download_handle, num_downloads); }