diff --git a/config b/config index 8cebfdc4..e7179150 100644 --- a/config +++ b/config @@ -282,6 +282,9 @@ # Like --picky, but it only searches for extra files (boolean value) #extra_files_only= +# Forces swupd to only diagnose the specified file or directory (recursively) +#file=[PATH] + [repair] @@ -319,6 +322,9 @@ # Like --picky, but it only removes extra files (boolean value) #extra_files_only= +# Forces swupd to only repair the specified file or directory (recursively) +#file=[PATH] + [os-install] diff --git a/docs/RELEASE_NOTES_DRAFT b/docs/RELEASE_NOTES_DRAFT index 8e3f8da3..4dcecd1a 100644 --- a/docs/RELEASE_NOTES_DRAFT +++ b/docs/RELEASE_NOTES_DRAFT @@ -5,7 +5,7 @@ Release vX.XX.XX Bug Fixes: Features: - - Add a bundle-remove --recursive flag to remove a bundle and all its dependencies from - the system (with some exceptions, e.g. dependencies required by other installed bundles). + - Add the --file flag to diagnose and repair to allow users to diagnose/repair a specific + file or directory (recursively). Tests: diff --git a/docs/swupd.1.rst b/docs/swupd.1.rst index ab391067..333744eb 100644 --- a/docs/swupd.1.rst +++ b/docs/swupd.1.rst @@ -483,6 +483,11 @@ SUBCOMMANDS Like ``--picky``, but it only looks for extra files. It omits checking hash values, and for missing files, directories and/or symlinks. + - ``--file`` + + Forces swupd to only diagnose the specified file or directory + (recursively). + ``repair`` Correct any issues found. This will overwrite incorrect file content, @@ -558,6 +563,11 @@ SUBCOMMANDS Like ``--picky``, but it only removes extra files. It omits repairing corrupt files, and adding missing files, directories and/or symlinks. + - ``--file`` + + Forces swupd to only repair the specified file or directory + (recursively). + ``os-install`` Perform system software installation in the specified location. Install diff --git a/src/helpers.c b/src/helpers.c index fd1d6c31..e726cfc1 100644 --- a/src/helpers.c +++ b/src/helpers.c @@ -672,9 +672,9 @@ enum swupd_code verify_fix_path(char *targetpath, struct manifest *target_MoM) /* this subpart of the path does exist, nothing to be done */ continue; } - info("Hash did not match for path : %s ... fixing\n", path); + warn_unlabeled(" -> Corrupt directory: %s", target); } else if (ret == -1 && errno == ENOENT) { - info("Path %s is missing on the file system ... fixing\n", path); + warn_unlabeled(" -> Missing directory: %s", target); } else { goto end; } @@ -692,6 +692,7 @@ enum swupd_code verify_fix_path(char *targetpath, struct manifest *target_MoM) string_or_die(&url, "%s/%i/files/%s.tar", globals.content_url, file->last_change, file->hash); ret = swupd_curl_get_file(url, tar_dotfile); if (ret != 0) { + warn_unlabeled(" -> not fixed\n"); error("Failed to download file %s in verify_fix_path\n", file->filename); ret = SWUPD_COULDNT_DOWNLOAD_FILE; unlink(tar_dotfile); @@ -699,6 +700,7 @@ enum swupd_code verify_fix_path(char *targetpath, struct manifest *target_MoM) } if (untar_full_download(file) != 0) { + warn_unlabeled(" -> not fixed\n"); error("Failed to untar file %s\n", file->filename); ret = SWUPD_COULDNT_UNTAR_FILE; goto end; @@ -708,9 +710,11 @@ enum swupd_code verify_fix_path(char *targetpath, struct manifest *target_MoM) if (ret != 0) { /* do_staging returns a swupd_code on error, * just propagate the error */ + warn_unlabeled(" -> not fixed\n"); error("Path %s failed to stage in verify_fix_path\n", path); goto end; } + warn_unlabeled(" -> fixed\n"); } end: free_string(&target); diff --git a/src/lib/sys.h b/src/lib/sys.h index ced3f310..1c29f241 100644 --- a/src/lib/sys.h +++ b/src/lib/sys.h @@ -190,6 +190,11 @@ char *sys_path_join(const char *prefix, const char *path); */ bool is_root(void); +/** + * @brief Check if the provided path is a directory + */ +bool is_dir(const char *path); + /** * @brief Remove file or directory. * diff --git a/src/repair.c b/src/repair.c index cef35363..7628196f 100644 --- a/src/repair.c +++ b/src/repair.c @@ -24,6 +24,7 @@ #include "swupd.h" #define FLAG_EXTRA_FILES_ONLY 2000 +#define FLAG_FILE 2001 static const char picky_tree_default[] = "/usr"; static const char picky_whitelist_default[] = "/usr/lib/modules|/usr/lib/kernel|/usr/local|/usr/src"; @@ -33,6 +34,7 @@ static bool cmdline_option_picky = false; static bool cmdline_option_quick = false; static int cmdline_option_version = 0; static char *cmdline_option_picky_tree = NULL; +static char *cmdline_option_file = NULL; static const char *cmdline_option_picky_whitelist = picky_whitelist_default; static bool cmdline_option_extra_files_only = false; static struct list *cmdline_bundles = NULL; @@ -51,6 +53,7 @@ static const struct option prog_opts[] = { { "quick", no_argument, 0, 'q' }, { "extra-files-only", no_argument, 0, FLAG_EXTRA_FILES_ONLY }, { "bundles", required_argument, 0, 'B' }, + { "file", required_argument, 0, FLAG_FILE }, }; static void print_help(void) @@ -71,6 +74,7 @@ static void print_help(void) print(" -X, --picky-tree=[PATH] Changes the path where --picky and --extra-files-only look for extra files\n"); print(" -w, --picky-whitelist=[RE] Directories that match the regex get skipped during --picky. Example: /usr/man|/usr/doc\n"); print(" --extra-files-only Like --picky, but it only performs this task\n"); + print(" --file Forces swupd to only repair the specified file or directory (recursively)\n"); print("\n"); } @@ -122,6 +126,11 @@ static bool parse_opt(int opt, char *optarg) return false; } return true; + case FLAG_FILE: + cmdline_option_file = strdup_or_die(optarg); + /* Remove trailing '/' at the end of file/dir if any */ + remove_trailing_slash(cmdline_option_file); + return true; default: return false; } @@ -211,7 +220,6 @@ enum swupd_code repair_main(int argc, char **argv) { enum swupd_code ret = SWUPD_OK; int steps_in_repair; - string_or_die(&cmdline_option_picky_tree, "%s", picky_tree_default); if (!parse_options(argc, argv)) { print("\n"); @@ -222,10 +230,17 @@ enum swupd_code repair_main(int argc, char **argv) ret = swupd_init(SWUPD_ALL); if (ret != SWUPD_OK) { error("Failed swupd initialization, exiting now\n"); - free_string(&cmdline_option_picky_tree); return ret; } + /* if the --file flag was used, use that path for --picky as well + * unless --picky-tree was also specified, if none use default */ + if (!cmdline_option_picky_tree && !cmdline_option_file) { + string_or_die(&cmdline_option_picky_tree, "%s", picky_tree_default); + } else if (!cmdline_option_picky_tree) { + cmdline_option_picky_tree = cmdline_option_file; + } + /* set options needed for the verify --fix command */ verify_set_option_install(false); verify_set_option_fix(true); @@ -237,6 +252,7 @@ enum swupd_code repair_main(int argc, char **argv) verify_set_picky_tree(cmdline_option_picky_tree); verify_set_extra_files_only(cmdline_option_extra_files_only); verify_set_option_bundles(cmdline_bundles); + verify_set_option_file(cmdline_option_file); /* * Steps for repair: @@ -265,6 +281,9 @@ enum swupd_code repair_main(int argc, char **argv) /* run verify --fix */ ret = execute_verify(); + if (cmdline_option_picky_tree != cmdline_option_file) { + free_string(&cmdline_option_file); + } free_string(&cmdline_option_picky_tree); if (picky_whitelist) { regfree(picky_whitelist); diff --git a/src/staging.c b/src/staging.c index c2eb656f..da6c2672 100644 --- a/src/staging.c +++ b/src/staging.c @@ -97,13 +97,12 @@ enum swupd_code do_staging(struct file *file, struct manifest *MoM) ret = stat(targetpath, &s); if ((ret == -1) && (errno == ENOENT)) { if (MoM) { - warn("Update target directory does not exist: %s. Trying to fix it\n", targetpath); verify_fix_path(dir, MoM); } else { - debug("Update target directory does not exist: %s. Auto-fix disabled\n", targetpath); + debug("Target directory does not exist: %s. Auto-fix disabled\n", targetpath); } } else if (!S_ISDIR(s.st_mode)) { - error("Update target exists but is NOT a directory: %s\n", targetpath); + error("Target exists but is NOT a directory: %s\n", targetpath); } if (!realpath(targetpath, real_path)) { diff --git a/src/swupd.h b/src/swupd.h index 92179a89..d6c20ed3 100644 --- a/src/swupd.h +++ b/src/swupd.h @@ -363,6 +363,7 @@ extern void verify_set_option_picky(bool opt); extern void verify_set_picky_whitelist(regex_t *whitelist); extern void verify_set_picky_tree(char *picky_tree); extern void verify_set_extra_files_only(bool opt); +extern void verify_set_option_file(char *path); /* repair.c */ extern regex_t *compile_whitelist(const char *whitelist_pattern); diff --git a/src/verify.c b/src/verify.c index 3c05f0c8..e3fec3f0 100644 --- a/src/verify.c +++ b/src/verify.c @@ -38,7 +38,9 @@ #include "swupd.h" #define FLAG_EXTRA_FILES_ONLY 2000 +#define FLAG_FILE 2001 +static const char picky_tree_default[] = "/usr"; static const char picky_whitelist_default[] = "/usr/lib/modules|/usr/lib/kernel|/usr/local|/usr/src"; static bool warning_printed = false; @@ -50,6 +52,7 @@ static bool cmdline_option_fix = false; static bool cmdline_option_picky = false; static char *cmdline_option_statedir_cache = NULL; static char *cmdline_option_picky_tree = NULL; +static char *cmdline_option_file = NULL; static const char *cmdline_option_picky_whitelist = picky_whitelist_default; static bool cmdline_option_install = false; static bool cmdline_option_quick = false; @@ -76,6 +79,7 @@ static const struct option prog_opts[] = { { "quick", no_argument, 0, 'q' }, { "bundles", required_argument, 0, 'B' }, { "extra-files-only", no_argument, 0, FLAG_EXTRA_FILES_ONLY }, + { "file", required_argument, 0, FLAG_FILE }, }; /* setter functions */ @@ -152,17 +156,34 @@ void verify_set_extra_files_only(bool opt) cmdline_option_extra_files_only = opt; } +void verify_set_option_file(char *path) +{ + free_string(&cmdline_option_file); + cmdline_option_file = path; +} + +static void print_if_verify(const char *option) +{ + if (cmdline_command_verify) { + print("%s", option); + } +} + +static void print_if_diagnose(const char *option) +{ + if (!cmdline_command_verify) { + print("%s", option); + } +} + static void print_help(void) { print("Performs a system software installation verification\n\n"); print("Usage:\n"); - if (cmdline_command_verify) { - print(" swupd verify [OPTION...]\n\n"); - print("Warning: The \"swupd verify\" command has been superseded\n"); - print("Please consider using \"swupd diagnose\", \"swupd repair\" or \"swupd os-install\" instead\n\n"); - } else { - print(" swupd diagnose [OPTION...]\n\n"); - } + print_if_verify(" swupd verify [OPTION...]\n\n"); + print_if_verify("Warning: The \"swupd verify\" command has been superseded\n"); + print_if_verify("Please consider using \"swupd diagnose\", \"swupd repair\" or \"swupd os-install\" instead\n\n"); + print_if_diagnose(" swupd diagnose [OPTION...]\n\n"); global_print_help(); @@ -171,18 +192,16 @@ static void print_help(void) print(" -x, --force Attempt to proceed even if non-critical errors found\n"); print(" -q, --quick Don't check for corrupt files, only fix missing files\n"); print(" -B, --bundles=[BUNDLES] Forces swupd to only %s the specified BUNDLES. Example: --bundles=os-core,vi\n", cmdline_command_verify ? "verify" : "diagnose"); - if (cmdline_command_verify) { - print(" -m, --manifest=[VER] This option has been superseded. Please consider using the -V option instead\n"); - print(" -f, --fix This option has been superseded, please consider using \"swupd repair\" instead\n"); - print(" -i, --install This option has been superseded, please consider using \"swupd os-install\" instead\n"); - print(" -Y, --picky Also list (without --fix) or remove (with --fix) files which should not exist\n"); - } else { - print(" -Y, --picky Also list files which should not exist. By default swupd only looks for them at /usr\n"); - print(" skipping /usr/lib/modules, /usr/lib/kernel, /usr/local, and /usr/src\n"); - } + print_if_verify(" -m, --manifest=[VER] This option has been superseded. Please consider using the -V option instead\n"); + print_if_verify(" -f, --fix This option has been superseded, please consider using \"swupd repair\" instead\n"); + print_if_verify(" -i, --install This option has been superseded, please consider using \"swupd os-install\" instead\n"); + print_if_verify(" -Y, --picky Also list (without --fix) or remove (with --fix) files which should not exist\n"); + print_if_diagnose(" -Y, --picky Also list files which should not exist. By default swupd only looks for them at /usr\n"); + print_if_diagnose(" skipping /usr/lib/modules, /usr/lib/kernel, /usr/local, and /usr/src\n"); print(" -X, --picky-tree=[PATH] Changes the path where --picky and --extra-files-only look for extra files\n"); print(" -w, --picky-whitelist=[RE] Directories that match the regex get skipped during --picky. Example: /usr/bin|/usr/doc\n"); print(" --extra-files-only Like --picky, but it only performs this task\n"); + print_if_diagnose(" --file Forces swupd to only diagnose the specified file or directory (recursively)\n"); print("\n"); } @@ -254,7 +273,7 @@ static int check_files_hash(struct list *files) } /* allow optimization of install case */ -static int get_required_files(struct manifest *official_manifest, struct list *subs) +static int get_required_files(struct manifest *official_manifest, struct list *required_files, struct list *subs) { int ret; @@ -265,7 +284,7 @@ static int get_required_files(struct manifest *official_manifest, struct list *s progress_next_step("check_files_hash", PROGRESS_BAR); print("\n"); - if (check_files_hash(official_manifest->files)) { + if (check_files_hash(required_files)) { /* we don't need to do these steps, we already have the files, * just complete the steps */ progress_next_step("validate_fullfiles", PROGRESS_BAR); @@ -274,7 +293,7 @@ static int get_required_files(struct manifest *official_manifest, struct list *s return 0; } - ret = download_fullfiles(official_manifest->files, NULL); + ret = download_fullfiles(required_files, NULL); if (ret) { error("Unable to download necessary files for this OS release\n"); } @@ -325,15 +344,15 @@ out: } /* for each missing but expected file, (re)add the file */ -static void add_missing_files(struct manifest *official_manifest, bool repair) +static void add_missing_files(struct manifest *official_manifest, struct list *files_to_verify, bool repair) { int ret; struct file local; struct list *iter; - int list_length = list_len(official_manifest->files); + int list_length = list_len(files_to_verify); int complete = 0; - iter = list_head(official_manifest->files); + iter = list_head(files_to_verify); while (iter) { struct file *file; char *fullname; @@ -452,7 +471,7 @@ end: free_string(&fullname); } -static void deal_with_hash_mismatches(struct manifest *official_manifest, bool repair) +static void deal_with_hash_mismatches(struct manifest *official_manifest, struct list *files_to_verify, bool repair) { struct list *iter; int complete = 0; @@ -462,7 +481,7 @@ static void deal_with_hash_mismatches(struct manifest *official_manifest, bool r /* for each expected and present file which hash-mismatches vs * the manifest, replace the file */ - iter = list_head(official_manifest->files); + iter = list_head(files_to_verify); list_length = list_len(iter); while (iter) { @@ -476,18 +495,18 @@ static void deal_with_hash_mismatches(struct manifest *official_manifest, bool r } } -static void remove_orphaned_files(struct manifest *official_manifest, bool repair) +static void remove_orphaned_files(struct list *files_to_verify, bool repair) { int ret; struct list *iter; - int list_length = list_len(official_manifest->files); + int list_length = list_len(files_to_verify); int complete = 0; info("%s extraneous files\n", repair ? "Removing" : "Checking for"); - official_manifest->files = list_sort(official_manifest->files, cmp_file_filename_reverse); + files_to_verify = list_sort(files_to_verify, cmp_file_filename_reverse); - iter = list_head(official_manifest->files); + iter = list_head(files_to_verify); while (iter) { struct file *file; char *fullname; @@ -646,6 +665,11 @@ static bool parse_opt(int opt, char *optarg) case FLAG_EXTRA_FILES_ONLY: cmdline_option_extra_files_only = optarg_to_bool(optarg); return true; + case FLAG_FILE: + cmdline_option_file = strdup_or_die(optarg); + /* Remove trailing '/' at the end of file/dir if any */ + remove_trailing_slash(cmdline_option_file); + return true; default: return false; } @@ -718,6 +742,13 @@ static bool parse_options(int argc, char **argv) info("\n"); } + /* new options should not be allowed for the legacy verify command + * since it has been superseded, to motivate users to use diagnose/repair */ + if (cmdline_option_file) { + error("unrecognized option '--file'\n"); + return false; + } + } else { /* flag restrictions for "diagnose" */ @@ -725,6 +756,7 @@ static bool parse_options(int argc, char **argv) error("'latest' not supported for --version\n"); return false; } + if (cmdline_option_bundles) { if (cmdline_option_picky) { error("--bundles and --picky options are mutually exclusive\n"); @@ -799,6 +831,23 @@ static int filter_file_unsafe_to_delete(const void *a, const void *b) return -1; } +static struct list *keep_matching_path(struct list *all_files) +{ + struct list *matching_files = NULL; + struct list *iter = NULL; + struct file *file; + + for (iter = all_files; iter; iter = iter->next) { + file = iter->data; + if (strncmp(cmdline_option_file, file->filename, strlen(cmdline_option_file)) == 0) { + /* preserving the order is important */ + matching_files = list_append_data(matching_files, file); + } + } + + return list_head(matching_files); +} + /* 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 @@ -817,6 +866,7 @@ enum swupd_code execute_verify(void) struct list *bundles_submanifests = NULL; struct list *all_files = NULL; struct list *bundles_files = NULL; + struct list *files_to_verify = NULL; struct list *iter; bool use_latest = false; bool invalid_bundle = false; @@ -1036,6 +1086,7 @@ enum swupd_code execute_verify(void) goto clean_and_exit; } official_manifest->submanifests = all_submanifests; + if (cmdline_option_bundles) { bundles_submanifests = recurse_manifest(official_manifest, bundles_subs, NULL, false, NULL); if (!bundles_submanifests) { @@ -1066,6 +1117,23 @@ enum swupd_code execute_verify(void) } timelist_timer_stop(globals.global_times); + /* get the list of files to verify */ + files_to_verify = official_manifest->files; + if (cmdline_option_file) { + /* the user specified a file or path to verify, use that instead */ + files_to_verify = keep_matching_path(official_manifest->files); + info("\n"); + if (files_to_verify) { + char *file_path = sys_path_join(globals.path_prefix, cmdline_option_file); + info("Limiting diagnose to the following %s:\n", is_dir(file_path) ? "directory (recursively)" : "file"); + free_string(&file_path); + info(" - %s\n", cmdline_option_file); + } else { + /* we are done, nothing to be done */ + goto report_and_exit; + } + } + if (cmdline_option_extra_files_only) { /* user wants to deal only with the extra files, so skip everything else */ goto extra_files; @@ -1075,11 +1143,11 @@ enum swupd_code execute_verify(void) timelist_timer_start(globals.global_times, "Get required files"); /* get the initial number of files to be inspected */ - counts.checked = list_len(official_manifest->files); + counts.checked = list_len(files_to_verify); /* when fixing or installing we need input files */ if (cmdline_option_fix || cmdline_option_install) { - ret = get_required_files(official_manifest, selected_subs); + ret = get_required_files(official_manifest, files_to_verify, selected_subs); if (ret != 0) { ret = SWUPD_COULDNT_DOWNLOAD_FILE; goto clean_and_exit; @@ -1123,7 +1191,7 @@ enum swupd_code execute_verify(void) } else { info("\nChecking for missing files\n"); } - add_missing_files(official_manifest, cmdline_option_fix || cmdline_option_install); + add_missing_files(official_manifest, files_to_verify, cmdline_option_fix || cmdline_option_install); timelist_timer_stop(globals.global_times); if (cmdline_option_quick) { @@ -1134,7 +1202,7 @@ enum swupd_code execute_verify(void) /* repair corrupt files */ timelist_timer_start(globals.global_times, "Fixing modified files"); progress_next_step("fix_files", PROGRESS_BAR); - deal_with_hash_mismatches(official_manifest, cmdline_option_fix); + deal_with_hash_mismatches(official_manifest, files_to_verify, cmdline_option_fix); timelist_timer_stop(globals.global_times); /* remove orphaned files, removing files could be @@ -1142,7 +1210,7 @@ enum swupd_code execute_verify(void) timelist_timer_start(globals.global_times, "Removing orphaned files"); if ((counts.not_fixed == 0) && (counts.not_replaced == 0)) { progress_next_step("remove_extraneous_files", PROGRESS_BAR); - remove_orphaned_files(official_manifest, cmdline_option_fix); + remove_orphaned_files(files_to_verify, cmdline_option_fix); } timelist_timer_stop(globals.global_times); @@ -1179,6 +1247,7 @@ brick_the_system_and_clean_curl: } /* report a summary of what we managed to do and not do */ +report_and_exit: info("Inspected %i file%s\n", counts.checked, (counts.checked == 1 ? "" : "s")); if (counts.missing) { @@ -1236,6 +1305,9 @@ brick_the_system_and_clean_curl: /* this concludes the critical section, after this point it's clean up time, the disk content is finished and final */ clean_and_exit: + if (cmdline_option_file) { + list_free_list(files_to_verify); + } free_subscriptions(&all_subs); free_subscriptions(&bundles_subs); /* if the --bundles flag was used the official_manifest contains @@ -1331,7 +1403,7 @@ enum swupd_code verify_main(int argc, char **argv) { enum swupd_code ret = SWUPD_OK; const int steps_in_verify = 12; - string_or_die(&cmdline_option_picky_tree, "/usr"); + string_or_die(&cmdline_option_picky_tree, "%s", picky_tree_default); /* set option needed so we know the legacy "verify" command was used */ verify_set_command_verify(true); @@ -1388,7 +1460,6 @@ enum swupd_code diagnose_main(int argc, char **argv) { enum swupd_code ret = SWUPD_OK; int steps_in_diagnose; - string_or_die(&cmdline_option_picky_tree, "/usr"); if (!parse_options(argc, argv)) { print("\n"); @@ -1399,10 +1470,17 @@ enum swupd_code diagnose_main(int argc, char **argv) ret = swupd_init(SWUPD_ALL); if (ret != SWUPD_OK) { error("Failed swupd initialization, exiting now\n"); - free_string(&cmdline_option_picky_tree); return ret; } + /* if the --file flag was used, use that path for --picky as well + * unless --picky-tree was also specified, if none use default */ + if (!cmdline_option_picky_tree && !cmdline_option_file) { + string_or_die(&cmdline_option_picky_tree, "%s", picky_tree_default); + } else if (!cmdline_option_picky_tree) { + cmdline_option_picky_tree = cmdline_option_file; + } + /* * Steps for diagnose: * 1) load_manifests (with --extra-files-only jumps to step 5) @@ -1423,6 +1501,9 @@ enum swupd_code diagnose_main(int argc, char **argv) /* diagnose */ ret = execute_verify(); + if (cmdline_option_picky_tree != cmdline_option_file) { + free_string(&cmdline_option_file); + } free_string(&cmdline_option_picky_tree); if (picky_whitelist) { regfree(picky_whitelist); diff --git a/swupd.bash b/swupd.bash index 9285025c..4c74b21b 100644 --- a/swupd.bash +++ b/swupd.bash @@ -63,10 +63,10 @@ _swupd() opts="$global --version --library --binary --top --csv --init --order " break;; ("diagnose") - opts="$global --version --picky --picky-tree --picky-whitelist --quick --force --extra-files-only --bundles " + opts="$global --version --picky --picky-tree --picky-whitelist --quick --force --extra-files-only --bundles --file " break;; ("repair") - opts="$global --version --picky --picky-tree --picky-whitelist --quick --force --extra-files-only --bundles " + opts="$global --version --picky --picky-tree --picky-whitelist --quick --force --extra-files-only --bundles --file " break;; ("os-install") opts="$global --version --force --bundles --statedir-cache --download --skip-optional" diff --git a/swupd.zsh b/swupd.zsh index de40ff60..1711c2cd 100644 --- a/swupd.zsh +++ b/swupd.zsh @@ -324,6 +324,7 @@ if [[ -n "$state" ]]; then '(help -w --picky-whitelist)'{-w,--picky-whitelist=}'[Directories that match the regex get skipped. Example\: /var|/etc/machine-id. Default\: /usr/lib/modules|/usr/lib/kernel|/usr/local|/usr/src]:picky-whitelist:()' '(help)--extra-files-only[Only list files which should not exist]' '(help -B --bundles)'{-B,--bundles=}'[Forces swupd to only diagnose the specified BUNDLES. Example: --bundles=os-core,vi]:bundles:()' + '(help)--file[Forces swupd to only diagnose the specified file or directory (recursively)]' ) _arguments $diagnoses && ret=0 ;; @@ -338,6 +339,7 @@ if [[ -n "$state" ]]; then '(help -X --picky-tree)'{-X,--picky-tree=}'[Selects the sub-tree where --picky and --extra-files-only looks for extra files. Default\: /usr]:picky-tree: _path_files -/' '(help)--extra-files-only[Only remove files which should not exist]' '(help -B --bundles)'{-B,--bundles=}'[Forces swupd to only repair the specified BUNDLES. Example: --bundles=os-core,vi]:bundles:()' + '(help)--file[Forces swupd to only repair the specified file or directory (recursively)]' ) _arguments $repair && ret=0 ;; diff --git a/test/functional/bundleadd/add-verify-fix-path.bats b/test/functional/bundleadd/add-verify-fix-path.bats index 141f99e7..a5fa4859 100755 --- a/test/functional/bundleadd/add-verify-fix-path.bats +++ b/test/functional/bundleadd/add-verify-fix-path.bats @@ -22,15 +22,14 @@ test_setup() { @test "ADD022: When adding a bundle and a path is missing on the fs, bundle-add fixes it" { run sudo sh -c "$SWUPD bundle-add $SWUPD_OPTS test-bundle1" - assert_status_is 0 + assert_status_is "$SWUPD_OK" expected_output=$(cat <<-EOM Loading required manifests... No packs need to be downloaded Validate downloaded files Starting download of remaining update content. This may take a while... Installing files... - Warning: Update target directory does not exist: $TEST_DIRNAME/testfs/target-dir/foo/bar. Trying to fix it - Path /foo/bar is missing on the file system ... fixing + -> Missing directory: $PATH_PREFIX/foo/bar -> fixed Calling post-update helper scripts Successfully installed 1 bundle EOM diff --git a/test/functional/diagnose/diagnose-path.bats b/test/functional/diagnose/diagnose-path.bats new file mode 100755 index 00000000..1e34d767 --- /dev/null +++ b/test/functional/diagnose/diagnose-path.bats @@ -0,0 +1,208 @@ +#!/usr/bin/env bats + +# Author: Castulo Martinez +# Email: castulo.martinez@intel.com + +load "../testlib" + +test_setup() { + + create_test_environment -r "$TEST_NAME" 10 1 + create_bundle -L -n test-bundle1 -f /foo/file_1,/bar/file_2 "$TEST_NAME" + create_version "$TEST_NAME" 20 10 1 + update_bundle -p "$TEST_NAME" test-bundle1 --update /foo/file_1 + update_bundle -p "$TEST_NAME" test-bundle1 --delete /bar/file_2 + update_bundle "$TEST_NAME" test-bundle1 --add /baz/file_3 + update_bundle "$TEST_NAME" test-bundle1 --add /bar/file_4 + update_bundle "$TEST_NAME" test-bundle1 --add /bar/bat/file_5 + set_current_version "$TEST_NAME" 20 + # adding an untracked files into an untracked directory (/bat) + sudo mkdir "$TARGETDIR"/bat + sudo touch "$TARGETDIR"/bat/untracked_file1 + # adding an untracked file into tracked directory (/bar) + sudo touch "$TARGETDIR"/bar/untracked_file2 + # adding an untracked file into /usr + sudo touch "$TARGETDIR"/usr/untracked_file3 + +} + +@test "DIA022: Diagnose can be limited to a specific file" { + + # if the user uses the --file option, the diagnose is limited to the + # file/directory specified by the user + + run sudo sh -c "$SWUPD diagnose $SWUPD_OPTS --file /baz/file_3" + + assert_status_is "$SWUPD_NO" + expected_output=$(cat <<-EOM + Diagnosing version 20 + Downloading missing manifests... + Limiting diagnose to the following file: + - /baz/file_3 + Checking for missing files + -> Missing file: $PATH_PREFIX/baz/file_3 + Checking for corrupt files + Checking for extraneous files + Inspected 1 file + 1 file was missing + Use "swupd repair" to correct the problems in the system + Diagnose successful + EOM + ) + assert_is_output "$expected_output" + +} + +@test "DIA023: Diagnose picky can be limited to a specific file" { + + # if the user uses the --file + --picky options, the diagnose is limited to the + # file/directory specified by the user and looks for extra files in the + # same path only + + run sudo sh -c "$SWUPD diagnose $SWUPD_OPTS --file /baz/file_3 --picky" + + assert_status_is "$SWUPD_NO" + expected_output=$(cat <<-EOM + Diagnosing version 20 + Downloading missing manifests... + Limiting diagnose to the following file: + - /baz/file_3 + Checking for missing files + -> Missing file: $PATH_PREFIX/baz/file_3 + Checking for corrupt files + Checking for extraneous files + Checking for extra files under $PATH_PREFIX/baz/file_3 + Inspected 1 file + 1 file was missing + Use "swupd repair" to correct the problems in the system + Diagnose successful + EOM + ) + assert_is_output "$expected_output" + +} + +@test "DIA024: Diagnose can be limited to a specific path" { + + # if the user uses the --file option, the diagnose is limited to the + # file/directory specified by the user + + run sudo sh -c "$SWUPD diagnose $SWUPD_OPTS --file /bar" + + assert_status_is "$SWUPD_NO" + expected_output=$(cat <<-EOM + Diagnosing version 20 + Downloading missing manifests... + Limiting diagnose to the following directory (recursively): + - /bar + Checking for missing files + -> Missing file: $PATH_PREFIX/bar/bat + -> Missing file: $PATH_PREFIX/bar/bat/file_5 + -> Missing file: $PATH_PREFIX/bar/file_4 + Checking for corrupt files + Checking for extraneous files + -> File that should be deleted: $PATH_PREFIX/bar/file_2 + Inspected 5 files + 3 files were missing + 1 file found which should be deleted + Use "swupd repair" to correct the problems in the system + Diagnose successful + EOM + ) + assert_is_output "$expected_output" + +} + +@test "DIA025: Diagnose can be limited to a specific path to look for extra files only" { + + # if the user uses the --file + --extra-files-only option, swupd should only look for + # extra files in the path specified by the user + + run sudo sh -c "$SWUPD diagnose $SWUPD_OPTS --file /bar --extra-files-only" + + assert_status_is "$SWUPD_NO" + expected_output=$(cat <<-EOM + Diagnosing version 20 + Downloading missing manifests... + Limiting diagnose to the following directory (recursively): + - /bar + Checking for extra files under $PATH_PREFIX/bar + -> Extra file: $PATH_PREFIX/bar/untracked_file2 + -> Extra file: $PATH_PREFIX/bar/file_2 + Inspected 2 files + 2 files found which should be deleted + Use "swupd repair --picky" to correct the problems in the system + Diagnose successful + EOM + ) + assert_is_output "$expected_output" + +} + +@test "DIA026: Diagnose can be limited to a specific path and can look for extra files in another path" { + + # if the user uses the --file option with --picky-tree, + # the latter should take precedence + + run sudo sh -c "$SWUPD diagnose $SWUPD_OPTS --file /bar --picky --picky-tree /usr" + + assert_status_is "$SWUPD_NO" + expected_output=$(cat <<-EOM + Diagnosing version 20 + Downloading missing manifests... + Limiting diagnose to the following directory (recursively): + - /bar + Checking for missing files + -> Missing file: $PATH_PREFIX/bar/bat + -> Missing file: $PATH_PREFIX/bar/bat/file_5 + -> Missing file: $PATH_PREFIX/bar/file_4 + Checking for corrupt files + Checking for extraneous files + -> File that should be deleted: $PATH_PREFIX/bar/file_2 + Checking for extra files under $PATH_PREFIX/usr + -> Extra file: $PATH_PREFIX/usr/untracked_file3 + -> Extra file: $PATH_PREFIX/usr/share/defaults/swupd/versionurl + -> Extra file: $PATH_PREFIX/usr/share/defaults/swupd/contenturl + Inspected 8 files + 3 files were missing + 4 files found which should be deleted + Use "swupd repair --picky" to correct the problems in the system + Diagnose successful + EOM + ) + assert_is_output "$expected_output" + +} + +@test "DIA027: Diagnose can be limited to a specific path for a specific bundle" { + + # if the --bundles and --file options are used together, swupd should only + # look in the specified path and only for files that are part of the specified + # bundle + + write_to_protected_file -a "$PATH_PREFIX"/usr/share/clear/bundles/os-core "corrupting the file" + write_to_protected_file -a "$PATH_PREFIX"/usr/share/clear/bundles/test-bundle1 "corrupting the file" + + run sudo sh -c "$SWUPD diagnose $SWUPD_OPTS --bundle test-bundle1 --file /usr/share/clear/bundles" + + assert_status_is "$SWUPD_NO" + expected_output=$(cat <<-EOM + Diagnosing version 20 + Limiting diagnose to the following bundles: + - test-bundle1 + Downloading missing manifests... + Limiting diagnose to the following directory (recursively): + - /usr/share/clear/bundles + Checking for missing files + Checking for corrupt files + -> Hash mismatch for file: $PATH_PREFIX/usr/share/clear/bundles/test-bundle1 + Checking for extraneous files + Inspected 1 file + 1 file did not match + Use "swupd repair" to correct the problems in the system + Diagnose successful + EOM + ) + assert_is_output "$expected_output" + +} diff --git a/test/functional/repair/repair-path.bats b/test/functional/repair/repair-path.bats new file mode 100755 index 00000000..65925e7e --- /dev/null +++ b/test/functional/repair/repair-path.bats @@ -0,0 +1,279 @@ +#!/usr/bin/env bats + +# Author: Castulo Martinez +# Email: castulo.martinez@intel.com + +load "../testlib" + +test_setup() { + + create_test_environment -r "$TEST_NAME" 10 1 + create_bundle -L -n test-bundle1 -f /foo/file_1,/bar/file_2 -d /baz/testdir "$TEST_NAME" + create_version "$TEST_NAME" 20 10 1 + update_bundle -p "$TEST_NAME" test-bundle1 --update /foo/file_1 + update_bundle -p "$TEST_NAME" test-bundle1 --delete /bar/file_2 + update_bundle "$TEST_NAME" test-bundle1 --add /baz/testdir/file_3 + update_bundle "$TEST_NAME" test-bundle1 --add /bar/file_4 + update_bundle "$TEST_NAME" test-bundle1 --add /bar/bat/file_5 + set_current_version "$TEST_NAME" 20 + # adding an untracked files into an untracked directory (/bat) + sudo mkdir "$TARGETDIR"/bat + sudo touch "$TARGETDIR"/bat/untracked_file1 + # adding an untracked file into tracked directory (/bar) + sudo touch "$TARGETDIR"/bar/untracked_file2 + # adding an untracked file into /usr + sudo touch "$TARGETDIR"/usr/untracked_file3 + +} + +@test "REP038: Repair can be limited to a specific file" { + + # if the user uses the --file option, the repair is limited to the + # file/directory specified by the user + + run sudo sh -c "$SWUPD repair $SWUPD_OPTS --file /baz/testdir/file_3" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + Diagnosing version 20 + Downloading missing manifests... + Limiting diagnose to the following file: + - /baz/testdir/file_3 + Checking for corrupt files + Validate downloaded files + Starting download of remaining update content. This may take a while... + Adding any missing files + -> Missing file: $PATH_PREFIX/baz/testdir/file_3 -> fixed + Repairing corrupt files + Removing extraneous files + Inspected 1 file + 1 file was missing + 1 of 1 missing files were replaced + 0 of 1 missing files were not replaced + Calling post-update helper scripts + Repair successful + EOM + ) + assert_is_output "$expected_output" + +} + +@test "REP039: Repair extra files only can be limited to a specific file" { + + # if the user uses the --file + --picky options, the repair is limited to the + # file/directory specified by the user and looks for extra files in the + # same path only + + run sudo sh -c "$SWUPD repair $SWUPD_OPTS --file /foo/file_1 --extra-files-only" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + Diagnosing version 20 + Downloading missing manifests... + Limiting diagnose to the following file: + - /foo/file_1 + Removing extra files under $PATH_PREFIX/foo/file_1 + Inspected 0 files + Calling post-update helper scripts + Repair successful + EOM + ) + assert_is_output "$expected_output" + +} + +@test "REP040: Repair can fix a specific file even if its parent directory is missing" { + + # if the user uses the --file option, the repair is limited to the + # file/directory specified by the user. If a file was specified to be + # verified, but part of its path is missing or corrput, fix it anyway + + sudo rm -rf "$PATH_PREFIX"/baz + + run sudo sh -c "$SWUPD repair $SWUPD_OPTS --file /baz/testdir/file_3" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + Diagnosing version 20 + Downloading missing manifests... + Limiting diagnose to the following file: + - /baz/testdir/file_3 + Checking for corrupt files + Validate downloaded files + Starting download of remaining update content. This may take a while... + Adding any missing files + -> Missing directory: $PATH_PREFIX/baz -> fixed + -> Missing directory: $PATH_PREFIX/baz/testdir -> fixed + -> Missing file: $PATH_PREFIX/baz/testdir/file_3 -> fixed + Repairing corrupt files + Removing extraneous files + Inspected 1 file + 1 file was missing + 1 of 1 missing files were replaced + 0 of 1 missing files were not replaced + Calling post-update helper scripts + Repair successful + EOM + ) + assert_is_output "$expected_output" + +} + +@test "REP041: Repair can be limited to a specific path" { + + # if the user uses the --file option, the repair is limited to the + # file/directory specified by the user + + run sudo sh -c "$SWUPD repair $SWUPD_OPTS --file /bar" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + Diagnosing version 20 + Downloading missing manifests... + Limiting diagnose to the following directory (recursively): + - /bar + Checking for corrupt files + Validate downloaded files + Starting download of remaining update content. This may take a while... + Adding any missing files + -> Missing file: $PATH_PREFIX/bar/bat -> fixed + -> Missing file: $PATH_PREFIX/bar/bat/file_5 -> fixed + -> Missing file: $PATH_PREFIX/bar/file_4 -> fixed + Repairing corrupt files + Removing extraneous files + -> File that should be deleted: $PATH_PREFIX/bar/file_2 -> deleted + Inspected 5 files + 3 files were missing + 3 of 3 missing files were replaced + 0 of 3 missing files were not replaced + 1 file found which should be deleted + 1 of 1 files were deleted + 0 of 1 files were not deleted + Calling post-update helper scripts + Repair successful + EOM + ) + assert_is_output "$expected_output" + +} + +@test "REP042: Repair picky can be limited to a specific path" { + + # if the user uses the --file + --picky option, the repair is limited to the + # file/directory specified by the user and swupd will only look for extra files + # in the provided path + + run sudo sh -c "$SWUPD repair $SWUPD_OPTS --file /bar --picky" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + Diagnosing version 20 + Downloading missing manifests... + Limiting diagnose to the following directory (recursively): + - /bar + Checking for corrupt files + Validate downloaded files + Starting download of remaining update content. This may take a while... + Adding any missing files + -> Missing file: $PATH_PREFIX/bar/bat -> fixed + -> Missing file: $PATH_PREFIX/bar/bat/file_5 -> fixed + -> Missing file: $PATH_PREFIX/bar/file_4 -> fixed + Repairing corrupt files + Removing extraneous files + -> File that should be deleted: $PATH_PREFIX/bar/file_2 -> deleted + Removing extra files under $PATH_PREFIX/bar + -> Extra file: $PATH_PREFIX/bar/untracked_file2 -> deleted + Inspected 6 files + 3 files were missing + 3 of 3 missing files were replaced + 0 of 3 missing files were not replaced + 2 files found which should be deleted + 2 of 2 files were deleted + 0 of 2 files were not deleted + Calling post-update helper scripts + Repair successful + EOM + ) + assert_is_output "$expected_output" + +} + +@test "REP043: Repair can be limited to a specific path and can look for extra files in another path" { + + # if the user uses the --file option with --picky-tree, + # the latter should take precedence + + run sudo sh -c "$SWUPD repair $SWUPD_OPTS --file /bar --picky --picky-tree /usr" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + Diagnosing version 20 + Downloading missing manifests... + Limiting diagnose to the following directory (recursively): + - /bar + Checking for corrupt files + Validate downloaded files + Starting download of remaining update content. This may take a while... + Adding any missing files + -> Missing file: $PATH_PREFIX/bar/bat -> fixed + -> Missing file: $PATH_PREFIX/bar/bat/file_5 -> fixed + -> Missing file: $PATH_PREFIX/bar/file_4 -> fixed + Repairing corrupt files + Removing extraneous files + -> File that should be deleted: $PATH_PREFIX/bar/file_2 -> deleted + Removing extra files under $PATH_PREFIX/usr + -> Extra file: $PATH_PREFIX/usr/untracked_file3 -> deleted + -> Extra file: $PATH_PREFIX/usr/share/defaults/swupd/versionurl -> deleted + -> Extra file: $PATH_PREFIX/usr/share/defaults/swupd/contenturl -> deleted + Inspected 8 files + 3 files were missing + 3 of 3 missing files were replaced + 0 of 3 missing files were not replaced + 4 files found which should be deleted + 4 of 4 files were deleted + 0 of 4 files were not deleted + Calling post-update helper scripts + Repair successful + EOM + ) + assert_is_output "$expected_output" + +} + +@test "REP044: Repair can be limited to a specific path for a specific bundle" { + + # if the --bundles and --file options are used together, swupd should only + # look in the specified path and only for files that are part of the specified + # bundle + + write_to_protected_file -a "$PATH_PREFIX"/usr/share/clear/bundles/os-core "corrupting the file" + write_to_protected_file -a "$PATH_PREFIX"/usr/share/clear/bundles/test-bundle1 "corrupting the file" + + run sudo sh -c "$SWUPD repair $SWUPD_OPTS --bundle test-bundle1 --file /usr/share/clear/bundles" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + Diagnosing version 20 + Limiting diagnose to the following bundles: + - test-bundle1 + Downloading missing manifests... + Limiting diagnose to the following directory (recursively): + - /usr/share/clear/bundles + Checking for corrupt files + Validate downloaded files + Starting download of remaining update content. This may take a while... + Adding any missing files + Repairing corrupt files + -> Hash mismatch for file: $PATH_PREFIX/usr/share/clear/bundles/test-bundle1 -> fixed + Removing extraneous files + Inspected 1 file + 1 file did not match + 1 of 1 files were repaired + 0 of 1 files were not repaired + Calling post-update helper scripts + Repair successful + EOM + ) + assert_is_output "$expected_output" + +} diff --git a/test/functional/update/update-verify-fix-path-hash-mismatch.bats b/test/functional/update/update-verify-fix-path-hash-mismatch.bats index 1dd60f91..25b54eb7 100755 --- a/test/functional/update/update-verify-fix-path-hash-mismatch.bats +++ b/test/functional/update/update-verify-fix-path-hash-mismatch.bats @@ -38,9 +38,8 @@ test_setup() { Validate downloaded files Starting download of remaining update content. This may take a while... Installing files... - Warning: Update target directory does not exist: .*/target-dir/usr/foo. Trying to fix it - Hash did not match for path : /usr ... fixing - Path /usr/foo is missing on the file system ... fixing + -> Corrupt directory: $PATH_PREFIX/usr -> fixed + -> Missing directory: $PATH_PREFIX/usr/foo -> fixed Update was applied Calling post-update helper scripts 1 files were not in a pack diff --git a/test/functional/update/update-verify-fix-path-missing-dir.bats b/test/functional/update/update-verify-fix-path-missing-dir.bats index 88f6e5ea..b57f2eac 100755 --- a/test/functional/update/update-verify-fix-path-missing-dir.bats +++ b/test/functional/update/update-verify-fix-path-missing-dir.bats @@ -5,11 +5,11 @@ load "../testlib" test_setup() { create_test_environment "$TEST_NAME" - create_bundle -L -n test-bundle -f /usr/foo/test-file "$TEST_NAME" + create_bundle -L -n test-bundle -f /foo/bar/baz/test-file "$TEST_NAME" # remove the foo dir - sudo rm -rf "$TARGETDIR"/usr/foo + sudo rm -rf "$TARGETDIR"/foo create_version "$TEST_NAME" 100 10 - update_bundle "$TEST_NAME" test-bundle --update /usr/foo/test-file + update_bundle "$TEST_NAME" test-bundle --update /foo/bar/baz/test-file } @@ -35,8 +35,9 @@ test_setup() { Validate downloaded files Starting download of remaining update content. This may take a while... Installing files... - Warning: Update target directory does not exist: .*/target-dir/usr/foo. Trying to fix it - Path /usr/foo is missing on the file system ... fixing + -> Missing directory: $PATH_PREFIX/foo -> fixed + -> Missing directory: $PATH_PREFIX/foo/bar -> fixed + -> Missing directory: $PATH_PREFIX/foo/bar/baz -> fixed Update was applied Calling post-update helper scripts 1 files were not in a pack @@ -44,8 +45,8 @@ test_setup() { EOM ) assert_regex_is_output "$expected_output" - assert_dir_exists "$TARGETDIR"/usr/foo - assert_file_exists "$TARGETDIR"/usr/foo/test-file + assert_dir_exists "$TARGETDIR"/foo/bar/baz + assert_file_exists "$TARGETDIR"/foo/bar/baz/test-file }