/* * Software Updater - client side * * Copyright © 2012-2016 Intel Corporation. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 or later of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: * Otavio Pontes * */ #define _GNU_SOURCE #include #include #include "swupd.h" /* hysteresis thresholds */ //FIXME #562 #define MAX_XFER 15 static void download_mix_file(struct file *file) { char *url, *filename; string_or_die(&url, "%s/%i/files/%s.tar", MIX_STATE_DIR, file->last_change, file->hash); string_or_die(&filename, "%s/download/.%s.tar", globals.state_dir, file->hash); /* Mix content is local, so don't queue files up for curl downloads */ if (link_or_rename(url, filename) == 0) { untar_full_download(file); } else { warn("Failed to copy local mix file: %s\n", file->staging); } free_string(&url); free_string(&filename); } static void download_file(struct swupd_curl_parallel_handle *download_handle, struct file *file) { char *url, *filename; string_or_die(&filename, "%s/download/.%s.tar", globals.state_dir, file->hash); string_or_die(&url, "%s/%i/files/%s.tar", globals.content_url, file->last_change, file->hash); swupd_curl_parallel_download_enqueue(download_handle, url, filename, file->hash, file); free_string(&url); free_string(&filename); } static bool download_successful(void *data) { if (!data) { return false; } if (untar_full_download(data) != 0) { warn("Error for %s tarfile extraction, (check free space for %s?)\n", ((struct file *)data)->hash, globals.state_dir); } return true; } static double fullfile_query_total_download_size(struct list *files) { long size = 0; long total_size = 0; struct file *file = NULL; struct list *list = NULL; char *url = NULL; int count = 0; for (list = list_head(files); list; list = list->next) { file = list->data; /* if it is a file from a mix, we won't download it */ if (file->is_mix) { continue; } string_or_die(&url, "%s/%i/files/%s.tar", globals.content_url, file->last_change, file->hash); size = swupd_curl_query_content_size(url); if (size != -1) { total_size += size; } else { debug("The header for file %s could not be downloaded\n", file->filename); free_string(&url); return -SWUPD_COULDNT_DOWNLOAD_FILE; } count++; debug("File: %s (%.2lf MB)\n", url, (double)size / 1000000); free_string(&url); } debug("Number of files to download: %d\n", count); debug("Total size of files to be downloaded: %.2lf MB\n", (double)total_size / 1000000); return total_size; } static int get_cached_fullfile(struct file *file) { char *targetfile; char *targetfile_cache; struct stat stat; int ret = 0; /* Check the statedir and statedir-cache for the expected fullfile. When the * fullfile exists in the statedir-cache, it is copied to the statedir. */ string_or_die(&targetfile, "%s/staged/%s", globals.state_dir, file->hash); if (lstat(targetfile, &stat) != 0 || !verify_file(file, targetfile)) { ret = 1; if (globals.state_dir_cache != NULL) { string_or_die(&targetfile_cache, "%s/staged/%s", globals.state_dir_cache, file->hash); if (lstat(targetfile_cache, &stat) == 0 && verify_file(file, targetfile_cache)) { if (link_or_copy_all(targetfile_cache, targetfile) == 0) { ret = 0; } } free_string(&targetfile_cache); } } free_string(&targetfile); return ret; } /* * 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 download_progress download_progress = { 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) { file = iter->data; if (file->is_deleted || file->do_not_update) { continue; } /* download the fullfile when it is missing from the statedir and statedir-cache */ if (get_cached_fullfile(file) != 0) { need_download = list_append_data(need_download, file); } } 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, NULL, 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 */ list_free_list(need_download); 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) { /* remove tar duplicates from the list first */ need_download = list_sort(need_download, file_sort_hash); for (iter = list_head(need_download); iter; iter = iter->next) { if (iter->next == NULL) { break; } struct file *file1, *file2; file1 = iter->data; file2 = iter->next->data; /* different directories may need the same tar, in those cases it needs * to be downloaded only once, the tar for each file is downloaded from * /files/.tar */ if (strcmp(file1->hash, file2->hash) == 0 && (file1->last_change == file2->last_change)) { iter = list_free_item(iter, NULL); } } need_download = list_head(iter); 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); } } list_free_list(need_download); return swupd_curl_parallel_download_end(download_handle, num_downloads); }