diff --git a/src/3rd_party_bundle_add.c b/src/3rd_party_bundle_add.c index 810273dc..ff558ea9 100644 --- a/src/3rd_party_bundle_add.c +++ b/src/3rd_party_bundle_add.c @@ -19,6 +19,8 @@ #define _GNU_SOURCE +#include + #include "3rd_party_repos.h" #include "swupd.h" @@ -27,6 +29,11 @@ #define FLAG_SKIP_OPTIONAL 2000 #define FLAG_SKIP_DISKSPACE_CHECK 2001 +#define SCRIPT_TEMPLATE "#!/bin/bash\n\n" \ + "export PATH=%s:$PATH\n" \ + "export LD_LIBRARY_PATH=%s:$LD_LIBRARY_PATH\n\n" \ + "%s \"$@\"\n" + static char **cmdline_bundles; static char *cmdline_repo = NULL; @@ -94,18 +101,121 @@ static bool parse_options(int argc, char **argv) return true; } -static enum swupd_code add_bundle(char *bundle) +static enum swupd_code create_wrapper_script(char *filename) +{ + enum swupd_code ret_code = 0; + int fd; + FILE *fp = NULL; + char *bin_directory = NULL; + char *script = NULL; + char *binary = NULL; + char *third_party_bin_path = NULL; + char *third_party_ld_path = NULL; + mode_t mode = 0755; + + bin_directory = third_party_get_bin_dir(); + script = third_party_get_binary_path(sys_basename(filename)); + binary = sys_path_join(globals.path_prefix, filename); + + if (!sys_filelink_is_executable(binary)) { + goto close_and_exit; + } + + if (sys_file_exists(script)) { + /* the binary already exists, this condition should never happen + * since we checked before installing */ + ret_code = SWUPD_UNEXPECTED_CONDITION; + error("There is already a binary called %s in %s, it will be skipped\n", sys_basename(filename), bin_directory); + goto close_and_exit; + } + + /* if the SWUPD_3RD_PARTY_BIN_DIR does not exist, attempt to create it */ + if (mkdir_p(bin_directory)) { + ret_code = SWUPD_COULDNT_CREATE_DIR; + goto close_and_exit; + } + + if (!is_dir(bin_directory)) { + error("The path %s for 3rd-party content exists but is not a directory\n", bin_directory); + ret_code = SWUPD_COULDNT_CREATE_DIR; + goto close_and_exit; + } + + /* open the file with mode set to 0755 */ + fd = open(script, O_RDWR | O_CREAT, mode); + if (fd < 0) { + error("The file %s failed to be created\n", script); + ret_code = SWUPD_COULDNT_CREATE_FILE; + goto close_and_exit; + } + fp = fdopen(fd, "w"); + if (!fp) { + error("The file %s failed to be created\n", script); + ret_code = SWUPD_COULDNT_CREATE_FILE; + goto close_and_exit; + } + + /* get the path for the 3rd-party content */ + third_party_bin_path = str_or_die("%sbin:%susr/bin:%susr/local/bin", globals.path_prefix, globals.path_prefix, globals.path_prefix); + third_party_ld_path = str_or_die("%susr/lib64:%susr/local/lib64", globals.path_prefix, globals.path_prefix); + + fprintf(fp, SCRIPT_TEMPLATE, third_party_bin_path, third_party_ld_path, binary); + +close_and_exit: + if (fp) { + fclose(fp); + } + free_string(&binary); + free_string(&script); + free_string(&bin_directory); + free_string(&third_party_bin_path); + free_string(&third_party_ld_path); + + return ret_code; +} + +static enum swupd_code validate_binary(char *filename) +{ + enum swupd_code ret_code = SWUPD_OK; + char *bin_directory = NULL; + char *script = NULL; + + bin_directory = third_party_get_bin_dir(); + script = third_party_get_binary_path(sys_basename(filename)); + + /* if a file already exists, report the problem to the user */ + if (sys_file_exists(script)) { + error("There is already a binary called %s in %s\n", sys_basename(filename), bin_directory); + ret_code = SWUPD_COULDNT_CREATE_FILE; + } + free_string(&script); + free_string(&bin_directory); + + return ret_code; +} + +static enum swupd_code export_bundle_binaries(struct list *installed_files) +{ + return third_party_process_binaries(installed_files, "\nExporting 3rd-party bundle binaries...\n", "export_binaries", create_wrapper_script); +} + +static enum swupd_code validate_bundle_binaries(struct list *files_to_be_installed) +{ + return third_party_process_binaries(files_to_be_installed, "\nValidating 3rd-party bundle binaries...\n", "validate_binaries", validate_binary); +} + +static enum swupd_code add_bundle(char *bundle_name) { struct list *bundle_to_install = NULL; enum swupd_code ret = SWUPD_OK; /* execute_bundle_add expects a list */ - bundle_to_install = list_append_data(bundle_to_install, bundle); + bundle_to_install = list_append_data(bundle_to_install, bundle_name); info("\nBundles added from a 3rd-party repository are forced to run with the --no-scripts flag for security reasons\n\n"); globals.no_scripts = true; - ret = execute_bundle_add(bundle_to_install); + ret = execute_bundle_add_extra(bundle_to_install, validate_bundle_binaries, export_bundle_binaries); list_free_list(bundle_to_install); return ret; @@ -115,7 +225,7 @@ enum swupd_code third_party_bundle_add_main(int argc, char **argv) { struct list *bundles = NULL; enum swupd_code ret_code = SWUPD_OK; - const int steps_in_bundleadd = 8; + const int steps_in_bundleadd = 10; if (!parse_options(argc, argv)) { print("\n"); @@ -133,13 +243,15 @@ enum swupd_code third_party_bundle_add_main(int argc, char **argv) /* * Steps for 3rd-party bundle-add: * 1) load_manifests - * 2) download_packs - * 3) extract_packs - * 4) validate_fullfiles - * 5) download_fullfiles - * 6) extract_fullfiles - * 7) install_files - * 8) run_postupdate_scripts + * 2) validate_binaries + * 3) download_packs + * 4) extract_packs + * 5) validate_fullfiles + * 6) download_fullfiles + * 7) extract_fullfiles + * 8) install_files + * 9) run_postupdate_scripts + * 10) export_binaries */ progress_init_steps("3rd-party-bundle-add", steps_in_bundleadd); diff --git a/src/3rd_party_repos.c b/src/3rd_party_repos.c index 81b54a1b..babe0f8a 100644 --- a/src/3rd_party_repos.c +++ b/src/3rd_party_repos.c @@ -375,7 +375,7 @@ clean_and_exit: return ret_code; } -enum swupd_code third_party_run_operation(struct list *bundles, const char *repo, run_operation_fn_t run_operation_fn) +enum swupd_code third_party_run_operation(struct list *bundles, const char *repo, process_data_fn_t process_bundle_fn) { struct list *repos = NULL; struct list *iter = NULL; @@ -423,7 +423,7 @@ enum swupd_code third_party_run_operation(struct list *bundles, const char *repo } /* perform appropriate action on the bundle */ - ret = run_operation_fn(bundle); + ret = process_bundle_fn(bundle); if (ret) { /* if we have an error here it is ok to overwrite any previous error */ ret_code = ret; @@ -446,7 +446,7 @@ clean_and_exit: return ret_code; } -enum swupd_code third_party_run_operation_multirepo(const char *repo, run_operation_fn_t run_operation_fn, enum swupd_code expected_ret_code, const char *op_name, int op_steps) +enum swupd_code third_party_run_operation_multirepo(const char *repo, process_data_fn_t process_bundle_fn, enum swupd_code expected_ret_code, const char *op_name, int op_steps) { enum swupd_code ret_code = SWUPD_OK; enum swupd_code ret; @@ -482,7 +482,7 @@ enum swupd_code third_party_run_operation_multirepo(const char *repo, run_operat goto clean_and_exit; } - ret_code = run_operation_fn(NULL); + ret_code = process_bundle_fn(NULL); } else { @@ -499,7 +499,7 @@ enum swupd_code third_party_run_operation_multirepo(const char *repo, run_operat /* set the repo's header */ third_party_repo_header(selected_repo->name); - ret = run_operation_fn(NULL); + ret = process_bundle_fn(NULL); if (ret != expected_ret_code) { /* if the operation failed in any of the repos, * keep the error */ @@ -526,4 +526,56 @@ void third_party_repo_header(const char *repo_name) free_string(&header); } +static bool is_binary(const char *filename) +{ + const char *binary_paths[] = { "/bin", "/usr/bin", "/usr/local/bin", NULL }; + int i = 0; + + while (binary_paths[i]) { + if (strncmp(filename, binary_paths[i], strlen(binary_paths[i])) == 0) { + return true; + } + i++; + } + + return false; +} + +bool third_party_file_is_binary(struct file *file) +{ + if (file->is_exported && is_binary(file->filename)) { + return true; + } else + return false; +} + +enum swupd_code third_party_process_binaries(struct list *files, const char *msg, const char *step, process_data_fn_t proc_binary_fn) +{ + enum swupd_code ret, ret_code = SWUPD_OK; + struct list *iter = NULL; + struct file *file = NULL; + char *filename = NULL; + int count = 0; + int number_of_files = list_len(files); + + info("%s", msg); + + progress_next_step(step, PROGRESS_BAR); + for (iter = list_head(files); iter; iter = iter->next) { + file = iter->data; + filename = file->filename; + if (third_party_file_is_binary(file)) { + ret = proc_binary_fn(filename); + if (ret) { + /* at least one file failed to process */ + ret_code = ret; + } + } + count++; + progress_report(count, number_of_files); + } + + return ret_code; +} + #endif diff --git a/src/3rd_party_repos.h b/src/3rd_party_repos.h index 2cbe0555..16e7bac3 100644 --- a/src/3rd_party_repos.h +++ b/src/3rd_party_repos.h @@ -34,8 +34,8 @@ struct repo { char *url; }; -/** @brief Definition of a function that performs an action on a given bundle */ -typedef enum swupd_code (*run_operation_fn_t)(char *bundle); +/** @brief Definition of a function that performs some processing on a given data */ +typedef enum swupd_code (*process_data_fn_t)(char *data_name); /** @brief Function that returns the path to the 3rd-party bin directory */ char *third_party_get_bin_dir(void); @@ -112,24 +112,24 @@ enum swupd_code third_party_set_repo(struct repo *repo, bool sigcheck); * @param repo the name of the 3rd-party repository where the bundles * should be looked for. If no repo is specified the bundles are searched for * in all existing repos. - * @param run_operation_fn the function to be performed + * @param process_bundle_fn the bundle processing to be performed * * @returns a swupd_code */ -enum swupd_code third_party_run_operation(struct list *bundles, const char *repo, run_operation_fn_t run_operation_fn); +enum swupd_code third_party_run_operation(struct list *bundles, const char *repo, process_data_fn_t process_bundle_fn); /** * @brief Performs an operation on 3rd-party repos. * * @param repo the name of the 3rd-party repository where the operation will be run - * @param run_operation_fn the function to be performed + * @param process_bundle_fn the bundle processing to be performed * @param expected_ret_code the expected return code from the operation * @param op_name the name of the operation to be run in all repos * @param op_steps the number of steps involved in the operation * * @returns a swupd_code */ -enum swupd_code third_party_run_operation_multirepo(const char *repo, run_operation_fn_t run_operation_fn, enum swupd_code expected_ret_code, const char *op_name, int op_steps); +enum swupd_code third_party_run_operation_multirepo(const char *repo, process_data_fn_t process_bundle_fn, enum swupd_code expected_ret_code, const char *op_name, int op_steps); /** * @brief Prints a header with the repository name, useful when showing info from multiple repos. @@ -138,6 +138,23 @@ enum swupd_code third_party_run_operation_multirepo(const char *repo, run_operat */ void third_party_repo_header(const char *repo_name); +/** + * @brief Determines if a file from the manifest is 3rd-party exported binary + * + * @returns true if the file is an exported binary, false otherwise + */ +bool third_party_file_is_binary(struct file *file); + +/** + * @brief Function that perform some processing to binaries from a 3rd-party bundle + * + * @param files is the list of files that are part of the bundle and its dependencies + * @param msg the message to be printed at the beginning of the processing + * @param step the name of the step to show during the reporting of progress + * @param proc_binary_fn the function with the processing to be performed on each binary file + */ +enum swupd_code third_party_process_binaries(struct list *files, const char *msg, const char *step, process_data_fn_t proc_binary_fn); + #endif #ifdef __cplusplus diff --git a/src/bundle_add.c b/src/bundle_add.c index 0d619169..1ddd8333 100644 --- a/src/bundle_add.c +++ b/src/bundle_add.c @@ -319,7 +319,7 @@ static enum swupd_code download_content(struct manifest *mom, struct list *to_in /* Bundle install one ore more bundles passed in bundles * param as a null terminated array of strings */ -enum swupd_code bundle_add(struct list *bundles_list, int version) +enum swupd_code bundle_add_extra(struct list *bundles_list, int version, extra_proc_fn_t pre_add_fn, extra_proc_fn_t post_add_fn) { int ret = 0; struct manifest *mom; @@ -391,6 +391,15 @@ enum swupd_code bundle_add(struct list *bundles_list, int version) goto clean_and_exit; } + /* execute pre-add processing (if any) */ + if (pre_add_fn) { + ret = pre_add_fn(to_install_files); + if (ret != SWUPD_OK) { + info("Aborting bundle installation...\n\n"); + goto clean_and_exit; + } + } + /* Check if we have enough space */ ret = check_disk_space_availability(to_install_bundles); if (ret) { @@ -406,11 +415,19 @@ enum swupd_code bundle_add(struct list *bundles_list, int version) mom->files = installed_files; ret = install_files(mom, to_install_files); + if (ret) { + goto clean_and_exit; + } timelist_timer_stop(globals.global_times); // closing: Install bundles timelist_print_stats(globals.global_times); + /* execute post-add processing (if any) */ + if (post_add_fn) { + ret = post_add_fn(to_install_files); + } + clean_and_exit: iter = list_head(to_install_bundles); /* count how many of the requested bundles were actually installed, note that the @@ -462,7 +479,12 @@ clean_and_exit: return ret; } -enum swupd_code execute_bundle_add(struct list *bundles_list) +enum swupd_code bundle_add(struct list *bundles_list, int version) +{ + return bundle_add_extra(bundles_list, version, NULL, NULL); +} + +enum swupd_code execute_bundle_add_extra(struct list *bundles_list, extra_proc_fn_t pre_add_fn, extra_proc_fn_t post_add_fn) { int version; @@ -473,7 +495,12 @@ enum swupd_code execute_bundle_add(struct list *bundles_list) return SWUPD_CURRENT_VERSION_UNKNOWN; } - return bundle_add(bundles_list, version); + return bundle_add_extra(bundles_list, version, pre_add_fn, post_add_fn); +} + +enum swupd_code execute_bundle_add(struct list *bundles_list) +{ + return execute_bundle_add_extra(bundles_list, NULL, NULL); } enum swupd_code bundle_add_main(int argc, char **argv) diff --git a/src/swupd.h b/src/swupd.h index b7f64447..07463d06 100644 --- a/src/swupd.h +++ b/src/swupd.h @@ -329,8 +329,11 @@ extern int add_subscriptions(struct list *bundles, struct list **subs, struct ma extern int subscription_get_tree(struct list *bundles, struct list **subs, struct manifest *mom, bool find_all, int recursion); /* bundle_add.c */ -extern enum swupd_code execute_bundle_add(struct list *bundles_list); +typedef enum swupd_code (*extra_proc_fn_t)(struct list *files); extern enum swupd_code bundle_add(struct list *bundles_list, int version); +extern enum swupd_code bundle_add_extra(struct list *bundles_list, int version, extra_proc_fn_t pre_add_fn, extra_proc_fn_t post_add_fn); +extern enum swupd_code execute_bundle_add(struct list *bundles_list); +extern enum swupd_code execute_bundle_add_extra(struct list *bundles_list, extra_proc_fn_t pre_add_fn, extra_proc_fn_t post_add_fn); /* bundle_remove.c */ extern enum swupd_code execute_remove_bundles(struct list *bundles); diff --git a/test/functional/3rd-party/3rd-party-bundle-add-basic.bats b/test/functional/3rd-party/3rd-party-bundle-add-basic.bats index c06862a7..616b057e 100755 --- a/test/functional/3rd-party/3rd-party-bundle-add-basic.bats +++ b/test/functional/3rd-party/3rd-party-bundle-add-basic.bats @@ -33,11 +33,13 @@ test_setup() { Bundle test-bundle2 found in 3rd-party repository test-repo2 Bundles added from a 3rd-party repository are forced to run with the --no-scripts flag for security reasons Loading required manifests... + Validating 3rd-party bundle binaries... No packs need to be downloaded Validate downloaded files Starting download of remaining update content. This may take a while... Installing files... Warning: post-update helper scripts skipped due to --no-scripts argument + Exporting 3rd-party bundle binaries... Successfully installed 1 bundle EOM ) @@ -89,11 +91,13 @@ test_setup() { Bundle test-bundle2 found in 3rd-party repository test-repo2 Bundles added from a 3rd-party repository are forced to run with the --no-scripts flag for security reasons Loading required manifests... + Validating 3rd-party bundle binaries... No packs need to be downloaded Validate downloaded files Starting download of remaining update content. This may take a while... Installing files... Warning: post-update helper scripts skipped due to --no-scripts argument + Exporting 3rd-party bundle binaries... Successfully installed 1 bundle EOM ) @@ -116,11 +120,13 @@ test_setup() { Bundle test-bundle2 found in 3rd-party repository test-repo2 Bundles added from a 3rd-party repository are forced to run with the --no-scripts flag for security reasons Loading required manifests... + Validating 3rd-party bundle binaries... No packs need to be downloaded Validate downloaded files Starting download of remaining update content. This may take a while... Installing files... Warning: post-update helper scripts skipped due to --no-scripts argument + Exporting 3rd-party bundle binaries... Successfully installed 1 bundle Searching for bundle upstream-bundle in the 3rd-party repositories... Error: bundle upstream-bundle was not found in any 3rd-party repository diff --git a/test/functional/3rd-party/3rd-party-bundle-add-config-file.bats b/test/functional/3rd-party/3rd-party-bundle-add-config-file.bats index 1493021c..d68cdde8 100755 --- a/test/functional/3rd-party/3rd-party-bundle-add-config-file.bats +++ b/test/functional/3rd-party/3rd-party-bundle-add-config-file.bats @@ -42,11 +42,13 @@ test_setup() { Bundle test-bundle1 found in 3rd-party repository test-repo Bundles added from a 3rd-party repository are forced to run with the --no-scripts flag for security reasons Loading required manifests... + Validating 3rd-party bundle binaries... No packs need to be downloaded Validate downloaded files Starting download of remaining update content. This may take a while... Installing files... Warning: post-update helper scripts skipped due to --no-scripts argument + Exporting 3rd-party bundle binaries... Successfully installed 1 bundle 1 bundle was installed as dependency EOM diff --git a/test/functional/3rd-party/3rd-party-bundle-add-export-bin.bats b/test/functional/3rd-party/3rd-party-bundle-add-export-bin.bats new file mode 100755 index 00000000..25975594 --- /dev/null +++ b/test/functional/3rd-party/3rd-party-bundle-add-export-bin.bats @@ -0,0 +1,86 @@ +#!/usr/bin/env bats + +# Author: Castulo Martinez +# Email: castulo.martinez@intel.com + +load "../testlib" + +test_setup() { + + create_test_environment "$TEST_NAME" + # create a 3rd-party bundle that has a couple of binaries + add_third_party_repo "$TEST_NAME" 10 1 test-repo1 + create_bundle -n test-bundle1 -f /file1,/foo/file_2,/usr/bin/file_3,/bin/file_4 -u test-repo1 "$TEST_NAME" + +} + +@test "TPR056: Adding one bundle from a third party repo that will export binaries" { + + # If a 3rd-party bundle has binaries to be exported (marked with 'x' in the + # manifest and within /bin, /usr/bin, or /usr/local/bin) they should have a + # script created in /opt/3rd-party/bin to make the binary available to the user + + run sudo sh -c "$SWUPD 3rd-party bundle-add $SWUPD_OPTS test-bundle1" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + Searching for bundle test-bundle1 in the 3rd-party repositories... + Bundle test-bundle1 found in 3rd-party repository test-repo1 + Bundles added from a 3rd-party repository are forced to run with the --no-scripts flag for security reasons + Loading required manifests... + Validating 3rd-party bundle binaries... + Downloading packs for: + - test-bundle1 + Finishing packs extraction... + Validate downloaded files + No extra files need to be downloaded + Installing files... + Warning: post-update helper scripts skipped due to --no-scripts argument + Exporting 3rd-party bundle binaries... + Successfully installed 1 bundle + EOM + ) + assert_is_output "$expected_output" + + # the script files should have been generated for the exported files + assert_file_exists "$TARGETDIR"/"$THIRD_PARTY_BIN_DIR"/file_3 + assert_file_exists "$TARGETDIR"/"$THIRD_PARTY_BIN_DIR"/file_4 + + # verify the content of the scripts is correct + run sudo sh -c "cat $TARGETDIR/$THIRD_PARTY_BIN_DIR/file_3" + expected_output=$(cat <<-EOM + #!/bin/bash + export PATH=.* + export LD_LIBRARY_PATH=.* + $PATH_PREFIX/$THIRD_PARTY_BUNDLES_DIR/test-repo1/usr/bin/file_3 .* + EOM + ) + assert_regex_is_output "$expected_output" + +} + +@test "TPR057: Try adding one bundle from a third party repo that will export conflicting binaries" { + + # If a 3rd-party bundle has binaries to be exported but the binary already exists + # in the target system, the bundle installation should be aborted + + sudo mkdir -p "$TARGETDIR"/"$THIRD_PARTY_BIN_DIR" + sudo touch "$TARGETDIR"/"$THIRD_PARTY_BIN_DIR"/file_3 + + run sudo sh -c "$SWUPD 3rd-party bundle-add $SWUPD_OPTS test-bundle1" + + assert_status_is "$SWUPD_COULDNT_CREATE_FILE" + expected_output=$(cat <<-EOM + Searching for bundle test-bundle1 in the 3rd-party repositories... + Bundle test-bundle1 found in 3rd-party repository test-repo1 + Bundles added from a 3rd-party repository are forced to run with the --no-scripts flag for security reasons + Loading required manifests... + Validating 3rd-party bundle binaries... + Error: There is already a binary called file_3 in $PATH_PREFIX/opt/3rd-party/bin + Aborting bundle installation... + Failed to install 1 of 1 bundles + EOM + ) + assert_is_output "$expected_output" + +} diff --git a/test/functional/3rd-party/3rd-party-bundle-add-multi-repo.bats b/test/functional/3rd-party/3rd-party-bundle-add-multi-repo.bats index aad3c39a..b4e9c6f7 100755 --- a/test/functional/3rd-party/3rd-party-bundle-add-multi-repo.bats +++ b/test/functional/3rd-party/3rd-party-bundle-add-multi-repo.bats @@ -56,11 +56,13 @@ test_setup() { expected_output=$(cat <<-EOM Bundles added from a 3rd-party repository are forced to run with the --no-scripts flag for security reasons Loading required manifests... + Validating 3rd-party bundle binaries... No packs need to be downloaded Validate downloaded files Starting download of remaining update content. This may take a while... Installing files... Warning: post-update helper scripts skipped due to --no-scripts argument + Exporting 3rd-party bundle binaries... Successfully installed 1 bundle EOM )