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(); }
// expects filename w/o path_prefix prepended bool is_under_mounted_directory(const char *filename) { bool ret = false; int err; char *token; char *mountpoint; char *dir; char *fname; char *tmp; if (mounted_dirs == NULL) { return false; } dir = strdup(mounted_dirs); if (dir == NULL) { abort(); } token = strtok(dir + 1, ":"); while (token != NULL) { string_or_die(&mountpoint, "%s/", token); tmp = mk_full_filename(path_prefix, filename); string_or_die(&fname, ":%s:", tmp); free(tmp); err = strncmp(fname, mountpoint, strlen(mountpoint)); free(fname); if (err == 0) { free(mountpoint); ret = true; break; } token = strtok(NULL, ":"); free(mountpoint); } free(dir); return ret; }
// expects filename w/o path_prefix prepended bool is_directory_mounted(const char *filename) { char *fname; bool ret = false; char *tmp; if (mounted_dirs == NULL) { return false; } tmp = mk_full_filename(path_prefix, filename); string_or_die(&fname, ":%s:", tmp); free(tmp); if (strstr(mounted_dirs, fname)) { ret = true; } free(fname); return ret; }
/* 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", 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; }
/* 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_or_die(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_or_die(path)); tmp = strdup_or_die(dirname(path)); free_string(&path); path = tmp; } free_string(&path); list1 = list_head(path_list); while (list1) { path = list1->data; list1 = list1->next; free_string(&target); free_string(&tar_dotfile); free_string(&url); 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) { fprintf(stderr, "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) { fprintf(stderr, "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; } fprintf(stderr, "Hash did not match for path : %s ... fixing\n", path); } else if (ret == -1 && errno == ENOENT) { fprintf(stderr, "Path %s is missing on the file system ... fixing\n", path); } else { goto end; } /* In some circumstances (Docker using layers between updates/bundle adds, * corrupt staging content) we could have content which fails to stage. * In order to avoid this causing failure in verify_fix_path, remove the * staging content before proceeding. This also cleans up in case any prior * download failed in a partial state. */ unlink_all_staged_content(file); string_or_die(&tar_dotfile, "%s/download/.%s.tar", state_dir, file->hash); string_or_die(&url, "%s/%i/files/%s.tar", content_url, file->last_change, file->hash); ret = swupd_curl_get_file(url, tar_dotfile); if (ret != 0) { fprintf(stderr, "Error: Failed to download file %s in verify_fix_path\n", file->filename); unlink(tar_dotfile); goto end; } if (untar_full_download(file) != 0) { fprintf(stderr, "Error: Failed to untar file %s\n", file->filename); ret = -1; goto end; } ret = do_staging(file, target_MoM); if (ret != 0) { fprintf(stderr, "Error: Path %s failed to stage in verify_fix_path\n", path); goto end; } } end: free_string(&target); free_string(&tar_dotfile); free_string(&url); list_free_list_and_data(path_list, free_path_data); return ret; }
static void remove_orphaned_files(struct manifest *official_manifest) { int ret; struct list *iter; official_manifest->files = list_sort(official_manifest->files, file_sort_filename_reverse); iter = list_head(official_manifest->files); while (iter) { struct file *file; char *fullname; char *base; struct stat sb; int fd; file = iter->data; iter = iter->next; if ((!file->is_deleted) || (file->is_config)) { continue; } /* Note: boot files marked as deleted should not be deleted by * verify/fix; this task is delegated to an external program * (currently /usr/bin/kernel_updater.sh). */ if (ignore(file)) { continue; } fullname = mk_full_filename(path_prefix, file->filename); if (fullname == NULL) { abort(); } if (lstat(fullname, &sb) != 0) { /* correctly, the file is not present */ free(fullname); continue; } file_extraneous_count++; fd = get_dirfd_path(fullname); if (fd < 0) { printf("Not safe to delete: %s\n", fullname); free(fullname); file_not_deleted_count++; continue; } base = basename(fullname); if (!S_ISDIR(sb.st_mode)) { ret = unlinkat(fd, base, 0); if (ret && errno != ENOENT) { printf("Failed to remove %s (%i: %s)\n", fullname, errno, strerror(errno)); file_not_deleted_count++; } else { printf("Deleted %s\n", fullname); file_deleted_count++; } } else { ret = unlinkat(fd, base, AT_REMOVEDIR); if (ret) { file_not_deleted_count++; if (errno != ENOTEMPTY) { printf("Failed to remove empty folder %s (%i: %s)\n", fullname, errno, strerror(errno)); } else { //FIXME: Add force removal option? printf("Couldn't remove directory containing untracked files: %s\n", fullname); } } else { printf("Deleted %s\n", fullname); file_deleted_count++; } } free(fullname); close(fd); } }
static void deal_with_hash_mismatches(struct manifest *official_manifest, bool repair) { int ret; struct list *iter; /* for each expected and present file which hash-mismatches vs the manifest, replace the file */ iter = list_head(official_manifest->files); while (iter) { struct file *file; char *fullname; file = iter->data; iter = iter->next; // Note: boot files not marked as deleted are candidates for verify/fix if (file->is_deleted || ignore(file)) { continue; } file_checked_count++; // do_not_update set by earlier check, so account as checked if (file->do_not_update) { continue; } /* compare the hash and report mismatch */ fullname = mk_full_filename(path_prefix, file->filename); if (fullname == NULL) { abort(); } if (verify_file(file, fullname)) { free(fullname); continue; } else { file_mismatch_count++; printf("Hash mismatch for file: %s\n", fullname); } /* if not repairing, we're done */ if (!repair) { free(fullname); continue; } /* install the new file (on miscompare + fix) */ ret = do_staging(file, official_manifest); if (ret == 0) { rename_staged_file_to_final(file); } /* at the end of all this, verify the hash again to judge success */ if (verify_file(file, fullname)) { file_fixed_count++; printf("\tfixed\n"); } else { file_not_fixed_count++; printf("\tnot fixed\n"); } free(fullname); } }
/* for each missing but expected file, (re)add the file */ static void add_missing_files(struct manifest *official_manifest) { int ret; struct file local; struct list *iter; iter = list_head(official_manifest->files); while (iter) { struct file *file; char *fullname; file = iter->data; iter = iter->next; if ((file->is_deleted) || (file->do_not_update)) { 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); ret = compute_hash_lazy(&local, fullname); if (ret != 0) { file_not_replaced_count++; free(fullname); continue; } /* compare the hash and report mismatch */ if (hash_is_zeros(local.hash)) { file_missing_count++; if (cmdline_option_install == false) { printf("Missing file: %s\n", fullname); } } else { free(fullname); continue; } /* install the new file (on miscompare + fix) */ ret = do_staging(file, official_manifest); if (ret == 0) { rename_staged_file_to_final(file); } /* verify the hash again to judge success */ populate_file_struct(&local, fullname); if (cmdline_option_quick) { ret = compute_hash_lazy(&local, fullname); } else { ret = compute_hash(&local, fullname); } if ((ret != 0) || hash_needs_work(file, local.hash)) { file_not_replaced_count++; printf("\tnot fixed\n"); } else { file_replaced_count++; file->do_not_update = 1; if (cmdline_option_install == false) { printf("\tfixed\n"); } } free(fullname); } }