diff --git a/include/swupd.h b/include/swupd.h index 2b04c5ca..9b085841 100644 --- a/include/swupd.h +++ b/include/swupd.h @@ -169,8 +169,9 @@ extern void try_delta(struct file *file); extern void full_download(struct file *file); extern int start_full_download(bool pipelining); extern struct list *end_full_download(void); +extern int untar_full_download(void *data); -extern int do_staging(struct file *file); +extern int do_staging(struct file *file, struct manifest *manifest); extern int rename_all_files_to_final(struct list *updates); extern int rename_staged_file_to_final(struct file *file); @@ -219,6 +220,7 @@ void free_file_data(void *data); void remove_files_in_manifest_from_fs(struct manifest *m); void deduplicate_files_from_manifest(struct manifest **m1, struct manifest *m2); bool manifest_has_component(struct manifest *manifest, const char *component); +extern struct file *search_file_in_manifest(struct manifest *manifest, const char *filename); extern char *mounted_dirs; extern void get_mounted_directories(void); @@ -243,6 +245,7 @@ extern void string_or_die(char **strp, const char *fmt, ...); void update_motd(int new_release); void delete_motd(void); extern int is_dirname_link(const char *fullname); +extern int verify_fix_path(char* targetpath, struct manifest *manifest); /* subscription.c */ struct list *free_list_file(struct list *item); diff --git a/src/bundle.c b/src/bundle.c index 7b40906f..46e70517 100644 --- a/src/bundle.c +++ b/src/bundle.c @@ -417,7 +417,7 @@ int install_bundles(char **bundles) continue; } - ret = do_staging(file); + ret = do_staging(file, mom); if (ret == 0) { rename_staged_file_to_final(file); } diff --git a/src/download.c b/src/download.c index 19a9244b..4a6dbc3e 100644 --- a/src/download.c +++ b/src/download.c @@ -243,7 +243,7 @@ free_tarcommand: /* This function will break if the same HASH.tar full file is downloaded * multiple times in parallel. */ -static void untar_full_download(void *data) +int untar_full_download(void *data) { struct file *file = data; char *tarfile; @@ -267,7 +267,7 @@ static void untar_full_download(void *data) free(tar_dotfile); free(tarfile); free(targetfile); - return; + return 0; } else { unlink(tarfile); unlink(targetfile); @@ -323,6 +323,7 @@ exit: if (err) { unlink_all_staged_content(file); } + return err; } static int perform_curl_io_and_complete(int *left) diff --git a/src/helpers.c b/src/helpers.c index 4a009c1a..719ed7fa 100644 --- a/src/helpers.c +++ b/src/helpers.c @@ -34,6 +34,7 @@ #include #include #include +#include #include "config.h" #include "swupd.h" @@ -662,3 +663,122 @@ int is_dirname_link(const char *fullname) free(real_path); return ret; } + +void free_path_data(void *data) +{ + char *path = (char *) data; + free(path); +} + +/* This function is meant to be called while staging file to fix any missing/incorrect paths. + * While staging a file, if its parent directory is missing, this would try to create the path + * by breaking it into sub-paths and fixing them top down. + * Here, target_MoM is the consolidated manifest for the version you are trying to update/verify. + */ +int verify_fix_path(char* targetpath, struct manifest *target_MoM) +{ + struct list *path_list = NULL; /* path_list contains the subparts in the path */ + char *path; + char *tmp = NULL, *target = NULL; + char *url = NULL; + struct stat sb; + int ret = 0; + struct file *file; + char *tar_dotfile = NULL; + struct list *list1 = NULL; + + /* This shouldn't happen */ + if (strcmp(targetpath, "/") == 0) { + return ret; + } + + /* Removing trailing '/' from the path */ + path = strdup(targetpath); + if (path[strlen(path) - 1] == '/') { + path[strlen(path) - 1] = '\0'; + } + + /* Breaking down the path into parts. + * eg. Path /usr/bin/foo will be broken into /usr,/usr/bin and /usr/bin/foo + */ + while (strcmp(path, "/") != 0) { + path_list = list_prepend_data(path_list, strdup(path)); + tmp = strdup(dirname(path)); + free(path); + path = tmp; + } + free(path); + + list1 = list_head(path_list); + while(list1) { + path = list1->data; + list1 = list1->next; + + target = mk_full_filename(path_prefix, path); + + /* Search for the file in the manifest, to get the hash for the file */ + file = search_file_in_manifest(target_MoM, path); + if (file == NULL) { + printf("Error: Path %s not found in any of the subscribed manifests" + "in verify_fix_path for path_prefix %s\n", path, path_prefix); + ret = -1; + goto end; + } + + if (file->is_deleted) { + printf("Error: Path %s found deleted in verify_fix_path\n", path); + ret = -1; + goto end; + } + + ret = stat(target, &sb); + if (ret == 0) { + if (verify_file(file, target)) { + continue; + } + printf("Hash did not match for path : %s\n", path); + } else if (ret == -1 && errno == ENOENT) { + printf("Path %s is missing on the file system\n", path); + } else { + goto end; + } + + string_or_die(&tar_dotfile, "%s/download/.%s.tar", STATE_DIR, file->hash); + + // clean up in case any prior download failed in a partial state + unlink(tar_dotfile); + + string_or_die(&url, "%s/%i/files/%s.tar", preferred_content_url, file->last_change, file->hash); + ret = swupd_curl_get_file(url, tar_dotfile, NULL, NULL, false); + + if (ret != 0) { + printf("Error: Failed to download file %s in verify_fix_path\n", file->filename); + unlink(tar_dotfile); + goto end; + } + if (untar_full_download(file) != 0) { + printf("Error: Failed to untar file %s\n", file->filename); + ret = -1; + goto end; + } + + ret = do_staging(file, target_MoM); + if ( ret != 0) { + printf("Error: Path %s failed to stage in verify_fix_path\n", path); + goto end; + } + + } +end: + if (target) { + free(target); + } + if (tar_dotfile) { + free(tar_dotfile); + } + if (url) { + free(url); + } + list_free_list_and_data(path_list, free_path_data); + return ret; +} diff --git a/src/manifest.c b/src/manifest.c index 6c756123..df4f8c54 100644 --- a/src/manifest.c +++ b/src/manifest.c @@ -1125,3 +1125,21 @@ bool manifest_has_component(struct manifest *manifest, const char *component) return false; } + +/* This performs a linear search through the files list. */ +struct file *search_file_in_manifest(struct manifest *manifest, const char *filename) +{ + struct list *iter = NULL; + struct file *file; + + iter = manifest->files; + while(iter) { + file = iter->data; + iter = iter->next; + + if (strcmp(file->filename, filename) == 0) { + return file; + } + } + return NULL; +} diff --git a/src/staging.c b/src/staging.c index acc8a969..f35fc4a1 100644 --- a/src/staging.c +++ b/src/staging.c @@ -65,7 +65,7 @@ static int create_staging_renamedir(char *rename_tmpdir) #warning "do_staging is currently not able to be run in parallel" /* Consider adding a remove_leftovers() that runs in verify/fix in order to * allow this function to mkdtemp create folders for parallel build */ -int do_staging(struct file *file) +int do_staging(struct file *file, struct manifest *MoM) { char *statfile = NULL, *tmp = NULL, *tmp2 = NULL; char *dir, *base, *rel_dir; @@ -94,10 +94,9 @@ int do_staging(struct file *file) string_or_die(&targetpath, "%s%s", path_prefix, rel_dir); ret = stat(targetpath, &s); - /* For now, just report on error conditions. Once we implement - * verify_fix_path(char *path, int targetversion), we'll want to call it here */ if ((ret == -1) && (errno == ENOENT)) { - printf("Error: Update target directory does not exist: %s\n", targetpath); + printf("Update target directory does not exist: %s. Trying to fix it\n", targetpath); + verify_fix_path(dir, MoM); } else if (!S_ISDIR(s.st_mode)) { printf("Error: Update target exists but is NOT a directory: %s\n", targetpath); } diff --git a/src/update.c b/src/update.c index 1c885fdf..5598db4c 100644 --- a/src/update.c +++ b/src/update.c @@ -89,7 +89,7 @@ static struct list *full_download_loop(struct list *updates, int isfailed) return end_full_download(); } -static int update_loop(struct list *updates) +static int update_loop(struct list *updates, struct manifest *server_manifest) { int ret; struct file *file; @@ -161,7 +161,7 @@ TRY_DOWNLOAD: /* for each file: check hash value; on mismatch delete and queue full download */ /* todo: hash check */ - ret = do_staging(file); + ret = do_staging(file, server_manifest); if (ret < 0) { printf("File staging failed: %s\n", file->filename); return ret; @@ -360,7 +360,7 @@ download_packs: /* Step 7: apply the update */ - ret = update_loop(updates); + ret = update_loop(updates, server_manifest); if (ret == 0) { ret = update_device_latest_version(server_version); printf("Update was applied.\n"); diff --git a/src/verify.c b/src/verify.c index 690eb27e..c435afb8 100644 --- a/src/verify.c +++ b/src/verify.c @@ -414,7 +414,7 @@ static void add_missing_files(struct manifest *official_manifest) } /* install the new file (on miscompare + fix) */ - ret = do_staging(file); + ret = do_staging(file, official_manifest); if (ret == 0) { rename_staged_file_to_final(file); } @@ -486,7 +486,7 @@ static void deal_with_hash_mismatches(struct manifest *official_manifest, bool r } /* install the new file (on miscompare + fix) */ - ret = do_staging(file); + ret = do_staging(file, official_manifest); if (ret == 0) { rename_staged_file_to_final(file); }