| // -*- Mode: C++ -*- |
| // |
| // Copyright (C) 2015-2020 Red Hat, Inc. |
| // |
| // This file is part of the GNU Application Binary Interface Generic |
| // Analysis and Instrumentation Library (libabigail). This library is |
| // free software; you can redistribute it and/or modify it under the |
| // terms of the GNU Lesser General Public License as published by the |
| // Free Software Foundation; either version 3, or (at your option) any |
| // later version. |
| |
| // This library 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 Lesser Public License for more details. |
| |
| // You should have received a copy of the GNU Lesser General Public |
| // License along with this program; see the file COPYING-LGPLV3. If |
| // not, see <http://www.gnu.org/licenses/>. |
| // |
| // Author: Sinny Kumari |
| |
| /// @file |
| |
| /// This program compares the ABIs of binaries inside two packages. |
| /// |
| /// For now, the supported package formats are Deb and RPM, but |
| /// support for other formats would be greatly appreciated. |
| /// |
| /// The program takes the two packages to compare as well as their |
| /// associated debug info packages. |
| /// |
| /// The program extracts the content of the two packages into a |
| /// temporary directory , looks for the ELF binaries in there, |
| /// compares their ABIs and emit a report about the changes. |
| /// As this program uses libpthread to perform several tasks |
| /// concurrently, here is a coarse grain description of the sequence |
| /// of actions performed, including where things are done |
| /// concurrently. |
| /// |
| /// (steps 1/ and 2/ are performed concurrently. Then steps 3/ and 4/ |
| /// are performed in sequence) |
| /// |
| /// 1/ the first package and its ancillary packages (debug info and |
| /// devel packages) are extracted concurrently. |
| /// There is one thread per package being extracted. So if there are |
| /// 3 thread packages (one package, one debug info package and one |
| /// devel package), then there are 3 threads to extracts them. Then |
| /// when the extracting is done, another thread performs the analysis |
| /// of th1 extracted content. |
| /// |
| /// 2/ A similar thing is done for the second package. |
| /// |
| /// 3/ comparisons are performed concurrently. |
| /// |
| /// 4/ the reports are then emitted to standard output, always in the same |
| /// order. |
| |
| |
| // In case we have a bad fts we include this before config.h because |
| // it can't handle _FILE_OFFSET_BITS. Everything we need here is fine |
| // if its declarations just come first. Also, include sys/types.h |
| // before fts. On some systems fts.h is not self contained. |
| #ifdef BAD_FTS |
| #include <sys/types.h> |
| #include <fts.h> |
| #endif |
| |
| // For package configuration macros. |
| #include "config.h" |
| |
| #include <assert.h> |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| |
| // If fts.h is included before config.h, its indirect inclusions may |
| // not give us the right LFS aliases of these functions, so map them |
| // manually. |
| #ifdef BAD_FTS |
| #ifdef _FILE_OFFSET_BITS |
| #define open open64 |
| #define fopen fopen64 |
| #endif |
| #else |
| #include <sys/types.h> |
| #include <fts.h> |
| #endif |
| |
| #include <iostream> |
| #include <string> |
| #include <cstring> |
| #include <cstdlib> |
| #include <vector> |
| #include <algorithm> |
| #include <map> |
| |
| #include "abg-cxx-compat.h" |
| #include "abg-workers.h" |
| #include "abg-config.h" |
| #include "abg-tools-utils.h" |
| #include "abg-comparison.h" |
| #include "abg-suppression.h" |
| #include "abg-dwarf-reader.h" |
| |
| using std::cout; |
| using std::cerr; |
| using std::string; |
| using std::ostream; |
| using std::vector; |
| using std::map; |
| using abg_compat::unordered_set; |
| using std::set; |
| using std::ostringstream; |
| using abg_compat::shared_ptr; |
| using abg_compat::dynamic_pointer_cast; |
| using abigail::workers::task; |
| using abigail::workers::task_sptr; |
| using abigail::workers::queue; |
| using abigail::tools_utils::maybe_get_symlink_target_file_path; |
| using abigail::tools_utils::file_exists; |
| using abigail::tools_utils::is_dir; |
| using abigail::tools_utils::emit_prefix; |
| using abigail::tools_utils::check_file; |
| using abigail::tools_utils::ensure_dir_path_created; |
| using abigail::tools_utils::guess_file_type; |
| using abigail::tools_utils::string_ends_with; |
| using abigail::tools_utils::dir_name; |
| using abigail::tools_utils::real_path; |
| using abigail::tools_utils::string_suffix; |
| using abigail::tools_utils::sorted_strings_common_prefix; |
| using abigail::tools_utils::file_type; |
| using abigail::tools_utils::make_path_absolute; |
| using abigail::tools_utils::base_name; |
| using abigail::tools_utils::get_rpm_arch; |
| using abigail::tools_utils::file_is_kernel_package; |
| using abigail::tools_utils::gen_suppr_spec_from_headers; |
| using abigail::tools_utils::get_default_system_suppression_file_path; |
| using abigail::tools_utils::get_default_user_suppression_file_path; |
| using abigail::tools_utils::get_vmlinux_path_from_kernel_dist; |
| using abigail::tools_utils::get_dsos_provided_by_rpm; |
| using abigail::tools_utils::build_corpus_group_from_kernel_dist_under; |
| using abigail::tools_utils::load_default_system_suppressions; |
| using abigail::tools_utils::load_default_user_suppressions; |
| using abigail::tools_utils::abidiff_status; |
| using abigail::ir::corpus_sptr; |
| using abigail::ir::corpus_group_sptr; |
| using abigail::comparison::diff_context; |
| using abigail::comparison::diff_context_sptr; |
| using abigail::comparison::compute_diff; |
| using abigail::comparison::corpus_diff_sptr; |
| using abigail::comparison::get_default_harmless_categories_bitmap; |
| using abigail::comparison::get_default_harmful_categories_bitmap; |
| using abigail::suppr::suppression_sptr; |
| using abigail::suppr::suppressions_type; |
| using abigail::suppr::read_suppressions; |
| using abigail::dwarf_reader::read_context_sptr; |
| using abigail::dwarf_reader::create_read_context; |
| using abigail::dwarf_reader::get_soname_of_elf_file; |
| using abigail::dwarf_reader::get_type_of_elf_file; |
| using abigail::dwarf_reader::read_corpus_from_elf; |
| |
| /// The options passed to the current program. |
| class options |
| { |
| options(); |
| |
| public: |
| string wrong_option; |
| string wrong_arg; |
| string prog_name; |
| bool display_usage; |
| bool display_version; |
| bool missing_operand; |
| bool nonexistent_file; |
| bool abignore; |
| bool parallel; |
| string package1; |
| string package2; |
| vector<string> debug_packages1; |
| vector<string> debug_packages2; |
| string devel_package1; |
| string devel_package2; |
| size_t num_workers; |
| bool verbose; |
| bool drop_private_types; |
| bool show_relative_offset_changes; |
| bool no_default_suppression; |
| bool keep_tmp_files; |
| bool compare_dso_only; |
| bool compare_private_dsos; |
| bool leaf_changes_only; |
| bool show_all_types; |
| bool show_hexadecimal_values; |
| bool show_offsets_sizes_in_bits; |
| bool show_impacted_interfaces; |
| bool show_full_impact_report; |
| bool show_linkage_names; |
| bool show_redundant_changes; |
| bool show_harmless_changes; |
| bool show_locs; |
| bool show_added_syms; |
| bool show_symbols_not_referenced_by_debug_info; |
| bool show_added_binaries; |
| bool fail_if_no_debug_info; |
| bool show_identical_binaries; |
| vector<string> kabi_whitelist_packages; |
| vector<string> suppression_paths; |
| vector<string> kabi_whitelist_paths; |
| suppressions_type kabi_suppressions; |
| |
| options(const string& program_name) |
| : prog_name(program_name), |
| display_usage(), |
| display_version(), |
| missing_operand(), |
| nonexistent_file(), |
| abignore(true), |
| parallel(true), |
| verbose(false), |
| drop_private_types(false), |
| show_relative_offset_changes(true), |
| no_default_suppression(), |
| keep_tmp_files(), |
| compare_dso_only(), |
| compare_private_dsos(), |
| leaf_changes_only(), |
| show_all_types(), |
| show_hexadecimal_values(), |
| show_offsets_sizes_in_bits(true), |
| show_impacted_interfaces(), |
| show_full_impact_report(), |
| show_linkage_names(true), |
| show_redundant_changes(), |
| show_harmless_changes(), |
| show_locs(true), |
| show_added_syms(true), |
| show_symbols_not_referenced_by_debug_info(true), |
| show_added_binaries(true), |
| fail_if_no_debug_info(), |
| show_identical_binaries() |
| { |
| // set num_workers to the default number of threads of the |
| // underlying maching. This is the default value for the number |
| // of workers to use in workers queues throughout the code. |
| num_workers = abigail::workers::get_number_of_threads(); |
| } |
| }; |
| |
| static bool |
| get_interesting_files_under_dir(const string dir, |
| const string& file_name_to_look_for, |
| options& opts, |
| vector<string>& interesting_files); |
| |
| /// Abstract ELF files from the packages which ABIs ought to be |
| /// compared |
| class elf_file |
| { |
| private: |
| elf_file(); |
| |
| public: |
| string path; |
| string name; |
| string soname; |
| off_t size; |
| abigail::dwarf_reader::elf_type type; |
| |
| /// The path to the elf file. |
| /// |
| /// @param path the path to the elf file. |
| elf_file(const string& path) |
| : path(path) |
| { |
| abigail::tools_utils::base_name(path, name); |
| get_soname_of_elf_file(path, soname); |
| get_type_of_elf_file(path, type); |
| struct stat estat; |
| stat(path.c_str(), &estat); |
| size = estat.st_size; |
| } |
| }; |
| |
| /// A convenience typedef for a shared pointer to elf_file. |
| typedef shared_ptr<elf_file> elf_file_sptr; |
| |
| /// Abstract the result of comparing two packages. |
| /// |
| /// This contains the the paths of the set of added binaries, removed |
| /// binaries, and binaries whic ABI changed. |
| struct abi_diff |
| { |
| vector<elf_file_sptr> added_binaries; |
| vector<elf_file_sptr> removed_binaries; |
| vector<string> changed_binaries; |
| |
| /// Test if the current diff carries changes. |
| /// |
| /// @return true iff the current diff carries changes. |
| bool |
| has_changes() |
| { |
| return (!added_binaries.empty() |
| || !removed_binaries.empty() |
| ||!changed_binaries.empty()); |
| } |
| }; |
| |
| class package; |
| |
| /// Convenience typedef for a shared pointer to a @ref package. |
| typedef shared_ptr<package> package_sptr; |
| |
| /// Abstracts a package. |
| class package |
| { |
| public: |
| |
| /// The kind of package we are looking at. |
| enum kind |
| { |
| /// Main package. Contains binaries to ABI-compare. |
| KIND_MAIN = 0, |
| /// Devel package. Contains public headers files in which public |
| /// types are defined. |
| KIND_DEVEL, |
| /// Debug info package. Contains the debug info for the binaries |
| /// int he main packge. |
| KIND_DEBUG_INFO, |
| /// Contains kernel ABI whitelists |
| KIND_KABI_WHITELISTS, |
| /// Source package. Contains the source of the binaries in the |
| /// main package. |
| KIND_SRC |
| }; |
| |
| private: |
| string path_; |
| string extracted_dir_path_; |
| string common_paths_prefix_; |
| abigail::tools_utils::file_type type_; |
| kind kind_; |
| map<string, elf_file_sptr> path_elf_file_sptr_map_; |
| vector<package_sptr> debug_info_packages_; |
| package_sptr devel_package_; |
| package_sptr kabi_whitelist_package_; |
| vector<string> elf_file_paths_; |
| set<string> public_dso_sonames_; |
| |
| public: |
| /// Constructor for the @ref package type. |
| /// |
| /// @param path the path to the package. |
| /// |
| /// @parm dir the temporary directory where to extract the content |
| /// of the package. |
| /// |
| /// @param pkg_kind the kind of package. |
| package(const string& path, |
| const string& dir, |
| kind pkg_kind = package::KIND_MAIN) |
| : path_(path), |
| kind_(pkg_kind) |
| { |
| type_ = guess_file_type(path); |
| if (type_ == abigail::tools_utils::FILE_TYPE_DIR) |
| extracted_dir_path_ = path; |
| else |
| extracted_dir_path_ = extracted_packages_parent_dir() + "/" + dir; |
| } |
| |
| /// Getter of the path of the package. |
| /// |
| /// @return the path of the package. |
| const string& |
| path() const |
| {return path_;} |
| |
| /// Setter of the path of the package. |
| /// |
| /// @param s the new path. |
| void |
| path(const string& s) |
| {path_ = s;} |
| |
| /// Getter of the base name of the package. |
| /// |
| /// @return the base name of the package. |
| string |
| base_name() const |
| { |
| string name; |
| abigail::tools_utils::base_name(path(), name); |
| return name; |
| } |
| |
| /// Getter for the path to the root dir where the packages are |
| /// extracted. |
| /// |
| /// @return the path to the root dir where the packages are |
| /// extracted. |
| static const string& |
| extracted_packages_parent_dir(); |
| |
| /// Getter for the path to the directory where the packages are |
| /// extracted for the current thread. |
| /// |
| /// @return the path to the directory where the packages are |
| /// extracted for the current thread. |
| const string& |
| extracted_dir_path() const |
| {return extracted_dir_path_;} |
| |
| /// Setter for the path to the directory where the packages are |
| /// extracted for the current thread. |
| /// |
| /// @param p the new path. |
| void |
| extracted_dir_path(const string& p) |
| {extracted_dir_path_ = p;} |
| |
| /// Getter of the the prefix that is common to all the paths of all |
| /// the elements of the package. |
| /// |
| /// @return the common path prefix of package elements. |
| const string& |
| common_paths_prefix() const |
| {return common_paths_prefix_;} |
| |
| /// Getter of the the prefix that is common to all the paths of all |
| /// the elements of the package. |
| /// |
| /// @return the common path prefix of package elements. |
| string& |
| common_paths_prefix() |
| {return common_paths_prefix_;} |
| |
| /// Setter of the the prefix that is common to all the paths of all |
| /// the elements of the package. |
| /// |
| /// |
| ///@param p the new prefix. |
| void |
| common_paths_prefix(const string& p) |
| {common_paths_prefix_ = p;} |
| |
| /// Getter for the file type of the current package. |
| /// |
| /// @return the file type of the current package. |
| abigail::tools_utils::file_type |
| type() const |
| {return type_;} |
| |
| /// Setter for the file type of the current package. |
| /// |
| /// @param t the new file type. |
| void type(abigail::tools_utils::file_type t) |
| {type_ = t;} |
| |
| /// Get the package kind |
| /// |
| /// @return the package kind |
| kind |
| get_kind() const |
| {return kind_;} |
| |
| /// Set the package kind |
| /// |
| /// @param k the package kind. |
| void |
| set_kind(kind k) |
| {kind_ = k;} |
| |
| /// Getter for the path <-> elf_file map. |
| /// |
| /// @return the the path <-> elf_file map. |
| const map<string, elf_file_sptr>& |
| path_elf_file_sptr_map() const |
| {return path_elf_file_sptr_map_;} |
| |
| /// Getter for the path <-> elf_file map. |
| /// |
| /// @return the the path <-> elf_file map. |
| map<string, elf_file_sptr>& |
| path_elf_file_sptr_map() |
| {return path_elf_file_sptr_map_;} |
| |
| /// Getter for the debug info packages associated to the current |
| /// package. |
| /// |
| /// There can indeed be several debug info packages needed for one |
| /// input package, as the debug info for that input package can be |
| /// split across several debuginfo packages. |
| /// |
| /// @return the debug info packages associated to the current |
| /// package. |
| const vector<package_sptr>& |
| debug_info_packages() const |
| {return debug_info_packages_;} |
| |
| /// Getter for the debug info packages associated to the current |
| /// package. |
| /// |
| /// There can indeed be several debug info packages needed for one |
| /// input package, as the debug info for that input package can be |
| /// split across several debuginfo packages. |
| /// |
| /// @return the debug info packages associated to the current |
| /// package. |
| vector<package_sptr>& |
| debug_info_packages() |
| {return debug_info_packages_;} |
| |
| /// Setter for the debug info packages associated to the current |
| /// package. |
| /// |
| /// There can indeed be several debug info packages needed for one |
| /// input package, as the debug info for that input package can be |
| /// split across several debuginfo packages. |
| /// |
| /// @param p the new debug info package. |
| void |
| debug_info_packages(const vector<package_sptr> &p) |
| {debug_info_packages_ = p;} |
| |
| /// Getter for the devel package associated to the current package. |
| /// |
| /// @return the devel package associated to the current package. |
| const package_sptr& |
| devel_package() const |
| {return devel_package_;} |
| |
| /// Setter of the devel package associated to the current package. |
| /// |
| /// @param p the new devel package associated to the current package. |
| void |
| devel_package(const package_sptr& p) |
| {devel_package_ = p;} |
| |
| /// Getter of the associated kernel abi whitelist package, if any. |
| /// |
| /// @return the associated kernel abi whitelist package. |
| const package_sptr |
| kabi_whitelist_package() const |
| {return kabi_whitelist_package_;} |
| |
| /// Setter of the associated kernel abi whitelist package. |
| /// |
| /// @param p the new kernel abi whitelist package. |
| void |
| kabi_whitelist_package(const package_sptr& p) |
| {kabi_whitelist_package_ = p;} |
| |
| /// Getter of the path to the elf files of the package. |
| /// |
| /// @return the path tothe elf files of the package. |
| const vector<string>& |
| elf_file_paths() const |
| {return elf_file_paths_;} |
| |
| /// Getter of the path to the elf files of the package. |
| /// |
| /// @return the path tothe elf files of the package. |
| vector<string>& |
| elf_file_paths() |
| {return elf_file_paths_;} |
| |
| /// Getter of the SONAMEs of the public DSOs carried by this |
| /// package. |
| /// |
| /// This is relevant only if the --private-dso option was *NOT* |
| /// provided. |
| /// |
| /// @return the SONAMEs of the public DSOs carried by this package. |
| const set<string>& |
| public_dso_sonames() const |
| {return public_dso_sonames_;} |
| |
| /// Getter of the SONAMEs of the public DSOs carried by this |
| /// package. |
| /// |
| /// This is relevant only if the --private-dso option was *NOT* |
| /// provided. |
| /// |
| /// @return the SONAMEs of the public DSOs carried by this package. |
| set<string>& |
| public_dso_sonames() |
| {return public_dso_sonames_;} |
| |
| /// Convert the absolute path of an element of this package into a |
| /// path relative to the root path pointing to this package. |
| /// |
| /// That is, suppose the content of a package named 'pkg' is located |
| /// at /root/path/pkg. Suppose an element of that package is named |
| /// is at '/root/path/pkg/somewhere/inside/element'. |
| /// |
| /// This function will return the path: |
| /// /pkg/somewhere/inside/element. |
| /// |
| /// @param path the path to consider. |
| /// |
| /// @param converted_path the resulting converted path. This is set |
| /// iff the function returns true. |
| /// |
| /// @return true if the path could be converted to being relative to |
| /// the extracted directory. |
| bool |
| convert_path_to_relative(const string& path, string& converted_path) const |
| { |
| string root = extracted_dir_path_; |
| real_path(root, root); |
| string p = path; |
| real_path(p, p); |
| return string_suffix(p, root, converted_path); |
| } |
| |
| // Convert the absolute path of an element of this package into a |
| // path relative to the prefix common to the paths of all elements |
| // of the package. |
| // |
| // @param path the path to conver. |
| // |
| // @param converted_path the resulting converted path. This is set |
| // iff the function returns true. |
| // |
| // @return true iff the function could successfully convert @p path |
| // and put the result into @p converted_path. |
| bool |
| convert_path_to_unique_suffix(const string& path, |
| string& converted_path) const |
| {return string_suffix(path, common_paths_prefix(), converted_path);} |
| |
| /// Retrieve the set of "interesting" package element paths by |
| /// walking the package. |
| /// |
| /// And then compute the path prefix that is common to all the |
| /// collected elements. |
| /// |
| /// @param the options of this application. |
| void |
| load_elf_file_paths(options& opts) |
| { |
| if (!common_paths_prefix().empty() |
| || !elf_file_paths().empty()) |
| // We have already loaded the elf file paths, don't do it again. |
| return; |
| |
| get_interesting_files_under_dir(extracted_dir_path(), |
| /*file_name_to_look_for=*/"", |
| opts, elf_file_paths()); |
| std::sort(elf_file_paths().begin(), elf_file_paths().end()); |
| string common_prefix; |
| sorted_strings_common_prefix(elf_file_paths(), common_paths_prefix()); |
| } |
| |
| /// Erase the content of the temporary extraction directory that has |
| /// been populated by the @ref extract_package() function; |
| /// |
| /// @param opts the options passed to the current program. |
| void |
| erase_extraction_directory(const options &opts) const |
| { |
| if (type() == abigail::tools_utils::FILE_TYPE_DIR) |
| // If we are comparing two directories, do not erase the |
| // directory as it was provided by the user; it's not a |
| // temporary directory we created ourselves. |
| return; |
| |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << "Erasing temporary extraction directory " |
| << extracted_dir_path() |
| << " ..."; |
| |
| string cmd = "rm -rf " + extracted_dir_path(); |
| if (system(cmd.c_str())) |
| { |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) << " FAILED\n"; |
| } |
| else |
| { |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) << " DONE\n"; |
| } |
| } |
| |
| /// Erase the content of all the temporary extraction directories. |
| /// |
| /// @param opts the options passed to the current program. |
| void |
| erase_extraction_directories(const options &opts) const |
| { |
| erase_extraction_directory(opts); |
| if (!debug_info_packages().empty()) |
| debug_info_packages().front()->erase_extraction_directory(opts); |
| if (devel_package()) |
| devel_package()->erase_extraction_directory(opts); |
| if (kabi_whitelist_package()) |
| kabi_whitelist_package()->erase_extraction_directory(opts); |
| } |
| }; // end class package. |
| |
| /// Arguments passed to the comparison tasks. |
| struct compare_args |
| { |
| const elf_file elf1; |
| const string& debug_dir1; |
| const suppressions_type private_types_suppr1; |
| const elf_file elf2; |
| const string& debug_dir2; |
| const suppressions_type private_types_suppr2; |
| const options& opts; |
| |
| /// Constructor for compare_args, which is used to pass |
| /// information to the comparison threads. |
| /// |
| /// @param elf1 the first elf file to consider. |
| /// |
| /// @param debug_dir1 the directory where the debug info file for @p |
| /// elf1 is stored. |
| /// |
| /// @param elf2 the second elf file to consider. |
| /// |
| /// @param debug_dir2 the directory where the debug info file for @p |
| /// elf2 is stored. |
| /// |
| /// @param opts the options the current program has been called with. |
| compare_args(const elf_file &elf1, const string& debug_dir1, |
| const suppressions_type& priv_types_suppr1, |
| const elf_file &elf2, const string& debug_dir2, |
| const suppressions_type& priv_types_suppr2, |
| const options& opts) |
| : elf1(elf1), debug_dir1(debug_dir1), |
| private_types_suppr1(priv_types_suppr1), |
| elf2(elf2), debug_dir2(debug_dir2), |
| private_types_suppr2(priv_types_suppr2), |
| opts(opts) |
| {} |
| }; // end struct compare_args |
| |
| /// A convenience typedef for arguments passed to the comparison workers. |
| typedef shared_ptr<compare_args> compare_args_sptr; |
| |
| static bool extract_package_and_map_its_content(const package_sptr &pkg, |
| options &opts); |
| |
| /// Getter for the path to the parent directory under which packages |
| /// extracted by the current thread are placed. |
| /// |
| /// @return the path to the parent directory under which packages |
| /// extracted by the current thread are placed. |
| const string& |
| package::extracted_packages_parent_dir() |
| { |
| // I tried to declare this in thread-local storage, but GCC 4.4.7 |
| // won't let me. So for now, I am just making it static. I'll deal |
| // with this later when I have to. |
| |
| //static __thread string p; |
| static string p; |
| |
| if (p.empty()) |
| { |
| const char *cachedir = getenv("XDG_CACHE_HOME"); |
| |
| if (cachedir != NULL) |
| p = cachedir; |
| else |
| { |
| const char* s = getenv("HOME"); |
| if (s != NULL) |
| p = s; |
| if (p.empty()) |
| { |
| s = getenv("TMPDIR"); |
| if (s != NULL) |
| p = s; |
| else |
| p = "/tmp"; |
| } |
| p += "/.cache/libabigail"; |
| } |
| |
| // Create the cache directory if it doesn't exist |
| ABG_ASSERT(ensure_dir_path_created(p)); |
| |
| string libabigail_tmp_dir_template = p; |
| libabigail_tmp_dir_template += "/abipkgdiff-tmp-dir-XXXXXX"; |
| |
| if (!mkdtemp(const_cast<char*>(libabigail_tmp_dir_template.c_str()))) |
| abort(); |
| |
| p = libabigail_tmp_dir_template; |
| } |
| |
| return p; |
| } |
| |
| /// A convenience typedef for shared_ptr of package. |
| typedef shared_ptr<package> package_sptr; |
| |
| /// Show the usage of this program. |
| /// |
| /// @param prog_name the name of the program. |
| /// |
| /// @param out the output stream to emit the usage to . |
| static void |
| display_usage(const string& prog_name, ostream& out) |
| { |
| emit_prefix(prog_name, out) |
| << "usage: " << prog_name << " [options] <package1> <package2>\n" |
| << " where options can be:\n" |
| << " --debug-info-pkg1|--d1 <path> path of debug-info package of package1\n" |
| << " --debug-info-pkg2|--d2 <path> path of debug-info package of package2\n" |
| << " --devel-pkg1|--devel1 <path> path of devel package of pakage1\n" |
| << " --devel-pkg2|--devel2 <path> path of devel package of pakage1\n" |
| << " --drop-private-types drop private types from " |
| "internal representation\n" |
| << " --no-default-suppression don't load any default " |
| "suppression specifications\n" |
| << " --suppressions|--suppr <path> specify supression specification path\n" |
| << " --linux-kernel-abi-whitelist|-w path to a " |
| "linux kernel abi whitelist\n" |
| << " --wp <path> path to a linux kernel abi whitelist package\n" |
| << " --keep-tmp-files don't erase created temporary files\n" |
| << " --dso-only compare shared libraries only\n" |
| << " --private-dso compare DSOs that are private " |
| "to the package as well\n" |
| << " --leaf-changes-only|-l only show leaf changes, " |
| "so no change impact analysis (implies --redundant)\n" |
| << " --impacted-interfaces|-i display interfaces impacted by leaf changes\n" |
| << " --full-impact|-f when comparing kernel packages, show the " |
| "full impact analysis report rather than the default leaf changes reports\n" |
| << " --non-reachable-types|-t consider types non reachable" |
| " from public interfaces\n" |
| << " --no-linkage-name do not display linkage names of " |
| "added/removed/changed\n" |
| << " --redundant display redundant changes\n" |
| << " --harmless display the harmless changes\n" |
| << " --no-show-locs do not show location information\n" |
| << " --show-bytes show size and offsets in bytes\n" |
| << " --show-bits show size and offsets in bits\n" |
| << " --show-hex show size and offset in hexadecimal\n" |
| << " --show-dec show size and offset in decimal\n" |
| << " --no-show-relative-offset-changes do not show relative" |
| " offset changes\n" |
| << " --no-added-syms do not display added functions or variables\n" |
| << " --no-unreferenced-symbols do not display changes " |
| "about symbols not referenced by debug info\n" |
| << " --no-added-binaries do not display added binaries\n" |
| << " --no-abignore do not look for *.abignore files\n" |
| << " --no-parallel do not execute in parallel\n" |
| << " --fail-no-dbg fail if no debug info was found\n" |
| << " --show-identical-binaries show the names of identical binaries\n" |
| << " --verbose emit verbose progress messages\n" |
| << " --help|-h display this help message\n" |
| << " --version|-v display program version information" |
| " and exit\n"; |
| } |
| |
| #ifdef WITH_RPM |
| |
| /// Extract an RPM package. |
| /// |
| /// @param package_path the path to the package to extract. |
| /// |
| /// @param extracted_package_dir_path the path where to extract the |
| /// package to. |
| /// |
| /// @param opts the options passed to the current program. |
| /// |
| /// @return true upon successful completion, false otherwise. |
| static bool |
| extract_rpm(const string& package_path, |
| const string& extracted_package_dir_path, |
| const options &opts) |
| { |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << "Extracting package " |
| << package_path |
| << " to " |
| << extracted_package_dir_path |
| << " ..."; |
| |
| string cmd = "test -d " + extracted_package_dir_path |
| + " || mkdir -p " + extracted_package_dir_path + " ; cd " + |
| extracted_package_dir_path + " && rpm2cpio " + package_path + |
| " | cpio -dium --quiet"; |
| |
| if (system(cmd.c_str())) |
| { |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) << " FAILED\n"; |
| return false; |
| } |
| |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) << " DONE\n"; |
| |
| return true; |
| } |
| |
| #endif // WITH_RPM |
| |
| #ifdef WITH_DEB |
| |
| /// Extract a Debian binary package. |
| /// |
| /// @param package_path the path to the package to extract. |
| /// |
| /// @param extracted_package_dir_path the path where to extract the |
| /// package to. |
| /// |
| /// @param opts the options passed to the current program. |
| /// |
| /// @return true upon successful completion, false otherwise. |
| static bool |
| extract_deb(const string& package_path, |
| const string& extracted_package_dir_path, |
| const options &opts) |
| { |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << "Extracting package " |
| << package_path |
| << " to " |
| << extracted_package_dir_path |
| << " ..."; |
| |
| string cmd = "mkdir -p " + extracted_package_dir_path + " && dpkg -x " + |
| package_path + " " + extracted_package_dir_path; |
| |
| if (system(cmd.c_str())) |
| { |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) << " FAILED\n"; |
| return false; |
| } |
| |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) << " DONE\n"; |
| |
| return true; |
| } |
| |
| #endif // WITH_DEB |
| |
| #ifdef WITH_TAR |
| |
| /// Extract a GNU Tar archive. |
| /// |
| /// @param package_path the path to the archive to extract. |
| /// |
| /// @param extracted_package_dir_path the path where to extract the |
| /// archive to. |
| /// |
| /// @param opts the options passed to the current program. |
| /// |
| /// @return true upon successful completion, false otherwise. |
| static bool |
| extract_tar(const string& package_path, |
| const string& extracted_package_dir_path, |
| const options &opts) |
| { |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << "Extracting tar archive " |
| << package_path |
| << " to " |
| << extracted_package_dir_path |
| << " ..."; |
| |
| string cmd = "test -d " + |
| extracted_package_dir_path + |
| " && rm -rf " + extracted_package_dir_path; |
| |
| if (system(cmd.c_str())) |
| { |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) << "command " << cmd << " FAILED\n"; |
| } |
| |
| cmd = "mkdir -p " + extracted_package_dir_path + " && cd " + |
| extracted_package_dir_path + " && tar -xf " + package_path; |
| |
| if (system(cmd.c_str())) |
| { |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) << " FAILED\n"; |
| return false; |
| } |
| |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) << " DONE\n"; |
| |
| return true; |
| } |
| |
| #endif // WITH_TAR |
| |
| /// Erase the temporary directories created for the extraction of two |
| /// packages. |
| /// |
| /// @param first_package the first package to consider. |
| /// |
| /// @param opts the options passed to the current program. |
| /// |
| /// @param second_package the second package to consider. |
| static void |
| erase_created_temporary_directories(const package& first_package, |
| const package& second_package, |
| const options &opts) |
| { |
| first_package.erase_extraction_directories(opts); |
| second_package.erase_extraction_directories(opts); |
| } |
| |
| /// Erase the root of all the temporary directories created by the |
| /// current thread. |
| static void |
| erase_created_temporary_directories_parent(const options &opts) |
| { |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << "Erasing temporary extraction parent directory " |
| << package::extracted_packages_parent_dir() |
| << " ..."; |
| |
| string cmd = "rm -rf " + package::extracted_packages_parent_dir(); |
| if (system(cmd.c_str())) |
| { |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) << "FAILED\n"; |
| } |
| else |
| { |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) << "DONE\n"; |
| } |
| } |
| |
| /// Extract the content of a package. |
| /// |
| /// @param package the package we are looking at. |
| /// |
| /// @param opts the options passed to the current program. |
| static bool |
| extract_package(const package& package, |
| const options &opts) |
| { |
| switch(package.type()) |
| { |
| case abigail::tools_utils::FILE_TYPE_RPM: |
| #ifdef WITH_RPM |
| if (!extract_rpm(package.path(), package.extracted_dir_path(), opts)) |
| { |
| emit_prefix("abipkgdiff", cerr) |
| << "Error while extracting package " << package.path() << "\n"; |
| return false; |
| } |
| return true; |
| #else |
| emit_prefix("abipkgdiff", cerr) |
| << "Support for rpm hasn't been enabled. Please consider " |
| "enabling it at package configure time\n"; |
| return false; |
| #endif // WITH_RPM |
| break; |
| case abigail::tools_utils::FILE_TYPE_DEB: |
| #ifdef WITH_DEB |
| if (!extract_deb(package.path(), package.extracted_dir_path(), opts)) |
| { |
| emit_prefix("abipkgdiff", cerr) |
| << "Error while extracting package" << package.path() << "\n"; |
| return false; |
| } |
| return true; |
| #else |
| emit_prefix("abipkgdiff", cerr) |
| << "Support for deb hasn't been enabled. Please consider " |
| "enabling it at package configure time\n"; |
| return false; |
| #endif // WITH_DEB |
| break; |
| |
| case abigail::tools_utils::FILE_TYPE_DIR: |
| // The input package is just a directory that contains binaries, |
| // there is nothing to extract. |
| break; |
| |
| case abigail::tools_utils::FILE_TYPE_TAR: |
| #ifdef WITH_TAR |
| if (!extract_tar(package.path(), package.extracted_dir_path(), opts)) |
| { |
| emit_prefix("abipkgdiff", cerr) |
| << "Error while extracting GNU tar archive " |
| << package.path() << "\n"; |
| return false; |
| } |
| return true; |
| #else |
| emit_prefix("abipkgdiff", cerr) |
| << "Support for GNU tar hasn't been enabled. Please consider " |
| "enabling it at package configure time\n"; |
| return false; |
| #endif // WITH_TAR |
| break; |
| |
| default: |
| return false; |
| } |
| return true; |
| } |
| |
| /// Check that the suppression specification files supplied are |
| /// present. If not, emit an error on stderr. |
| /// |
| /// @param opts the options instance to use. |
| /// |
| /// @return true if all suppression specification files are present, |
| /// false otherwise. |
| static bool |
| maybe_check_suppression_files(const options& opts) |
| { |
| for (vector<string>::const_iterator i = opts.suppression_paths.begin(); |
| i != opts.suppression_paths.end(); |
| ++i) |
| if (!check_file(*i, cerr, opts.prog_name)) |
| return false; |
| |
| for (vector<string>::const_iterator i = |
| opts.kabi_whitelist_paths.begin(); |
| i != opts.kabi_whitelist_paths.end(); |
| ++i) |
| if (!check_file(*i, cerr, "abidiff")) |
| return false; |
| |
| return true; |
| } |
| |
| /// Update the diff context from the @ref options data structure. |
| /// |
| /// @param ctxt the diff context to update. |
| /// |
| /// @param opts the instance of @ref options to consider. |
| static void |
| set_diff_context_from_opts(diff_context_sptr ctxt, |
| const options& opts) |
| { |
| ctxt->default_output_stream(&cout); |
| ctxt->error_output_stream(&cerr); |
| // See comment in abidiff.cc's set_diff_context_from_opts. |
| ctxt->show_redundant_changes(opts.show_redundant_changes |
| || opts.leaf_changes_only); |
| ctxt->show_leaf_changes_only(opts.leaf_changes_only); |
| ctxt->show_impacted_interfaces(opts.show_impacted_interfaces); |
| ctxt->show_unreachable_types(opts.show_all_types); |
| ctxt->show_hex_values(opts.show_hexadecimal_values); |
| ctxt->show_offsets_sizes_in_bits(opts.show_offsets_sizes_in_bits); |
| ctxt->show_relative_offset_changes(opts.show_relative_offset_changes); |
| ctxt->show_locs(opts.show_locs); |
| ctxt->show_linkage_names(opts.show_linkage_names); |
| ctxt->show_added_fns(opts.show_added_syms); |
| ctxt->show_added_vars(opts.show_added_syms); |
| ctxt->show_added_symbols_unreferenced_by_debug_info |
| (opts.show_added_syms); |
| ctxt->show_symbols_unreferenced_by_debug_info |
| (opts.show_symbols_not_referenced_by_debug_info); |
| |
| if (!opts.show_harmless_changes) |
| ctxt->switch_categories_off(get_default_harmless_categories_bitmap()); |
| |
| suppressions_type supprs; |
| for (vector<string>::const_iterator i = opts.suppression_paths.begin(); |
| i != opts.suppression_paths.end(); |
| ++i) |
| read_suppressions(*i, supprs); |
| ctxt->add_suppressions(supprs); |
| } |
| |
| /// Compare the ABI two elf files, using their associated debug info. |
| /// |
| /// The result of the comparison is emitted to standard output. |
| /// |
| /// @param elf1 the first elf file to consider. |
| /// |
| /// @param debug_dir1 the directory where the debug info file for @p |
| /// elf1 is stored. |
| /// The result of the comparison is saved to a global corpus map. |
| /// |
| /// @param elf2 the second eld file to consider. |
| /// @args the list of argument sets used for comparison |
| /// |
| /// @param debug_dir2 the directory where the debug info file for @p |
| /// elf2 is stored. |
| /// |
| /// @param opts the options the current program has been called with. |
| /// |
| /// @param env the environment encapsulating the entire comparison. |
| /// |
| /// @param diff the shared pointer to be set to the result of the comparison. |
| /// |
| /// @param detailed_error_status is this pointer is non-null and if |
| /// the function returns ABIDIFF_ERROR, then the function sets the |
| /// pointed-to parameter to the abigail::dwarf_reader::status value |
| /// that gives details about the rror. |
| /// |
| /// @return the status of the comparison. |
| static abidiff_status |
| compare(const elf_file& elf1, |
| const string& debug_dir1, |
| const suppressions_type& priv_types_supprs1, |
| const elf_file& elf2, |
| const string& debug_dir2, |
| const suppressions_type& priv_types_supprs2, |
| const options& opts, |
| abigail::ir::environment_sptr &env, |
| corpus_diff_sptr &diff, |
| diff_context_sptr &ctxt, |
| abigail::dwarf_reader::status *detailed_error_status = 0) |
| { |
| char *di_dir1 = (char*) debug_dir1.c_str(), |
| *di_dir2 = (char*) debug_dir2.c_str(); |
| |
| vector<char**> di_dirs1, di_dirs2; |
| di_dirs1.push_back(&di_dir1); |
| di_dirs2.push_back(&di_dir2); |
| |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << "Comparing the ABIs of file " |
| << elf1.path |
| << " and " |
| << elf2.path |
| << "...\n"; |
| |
| abigail::dwarf_reader::status c1_status = abigail::dwarf_reader::STATUS_OK, |
| c2_status = abigail::dwarf_reader::STATUS_OK; |
| |
| ctxt.reset(new diff_context); |
| set_diff_context_from_opts(ctxt, opts); |
| suppressions_type& supprs = ctxt->suppressions(); |
| bool files_suppressed = (file_is_suppressed(elf1.path, supprs) |
| ||file_is_suppressed(elf2.path, supprs)); |
| |
| if (files_suppressed) |
| { |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << " input file " |
| << elf1.path << " or " << elf2.path |
| << " has been suppressed by a suppression specification.\n" |
| << " Not reading any of them\n"; |
| return abigail::tools_utils::ABIDIFF_OK; |
| } |
| |
| // Add the first private type suppressions set to the set of |
| // suppressions. |
| for (suppressions_type::const_iterator i = priv_types_supprs1.begin(); |
| i != priv_types_supprs1.end(); |
| ++i) |
| supprs.push_back(*i); |
| |
| // Add the second private type suppressions set to the set of |
| // suppressions. |
| for (suppressions_type::const_iterator i = priv_types_supprs2.begin(); |
| i != priv_types_supprs2.end(); |
| ++i) |
| supprs.push_back(*i); |
| |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << " Reading file " |
| << elf1.path |
| << " ...\n"; |
| |
| corpus_sptr corpus1; |
| { |
| read_context_sptr c = |
| create_read_context(elf1.path, di_dirs1, env.get(), |
| /*load_all_types=*/opts.show_all_types); |
| add_read_context_suppressions(*c, priv_types_supprs1); |
| if (!opts.kabi_suppressions.empty()) |
| add_read_context_suppressions(*c, opts.kabi_suppressions); |
| |
| corpus1 = read_corpus_from_elf(*c, c1_status); |
| |
| bool bail_out = false; |
| if (!(c1_status & abigail::dwarf_reader::STATUS_OK)) |
| { |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << "Could not read file '" |
| << elf1.path |
| << "' properly\n"; |
| |
| if (detailed_error_status) |
| *detailed_error_status = c1_status; |
| |
| bail_out = true; |
| } |
| |
| if (opts.fail_if_no_debug_info) |
| { |
| bool debug_info_error = false; |
| if (c1_status & abigail::dwarf_reader::STATUS_DEBUG_INFO_NOT_FOUND) |
| { |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << "while reading file" << elf1.path << "\n"; |
| |
| emit_prefix("abipkgdiff", cerr) << "Could not find debug info file"; |
| if (di_dir1 && strcmp(di_dir1, "")) |
| cerr << " under " << di_dir1 << "\n"; |
| else |
| cerr << "\n"; |
| |
| if (detailed_error_status) |
| *detailed_error_status = c1_status; |
| debug_info_error = true; |
| } |
| |
| if (c1_status & abigail::dwarf_reader::STATUS_ALT_DEBUG_INFO_NOT_FOUND) |
| { |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << "while reading file" << elf1.path << "\n"; |
| |
| emit_prefix("abipkgdiff", cerr) |
| << "Could not find alternate debug info file"; |
| string alt_di_path; |
| abigail::dwarf_reader::refers_to_alt_debug_info(*c, alt_di_path); |
| if (!alt_di_path.empty()) |
| cerr << ": " << alt_di_path << "\n"; |
| else |
| cerr << "\n"; |
| |
| if (detailed_error_status) |
| *detailed_error_status = c1_status; |
| debug_info_error = true; |
| } |
| |
| if (debug_info_error) |
| bail_out = true; |
| } |
| |
| if (bail_out) |
| return abigail::tools_utils::ABIDIFF_ERROR; |
| } |
| |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << " DONE reading file " |
| << elf1.path |
| << "\n"; |
| |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << " Reading file " |
| << elf2.path |
| << " ...\n"; |
| |
| corpus_sptr corpus2; |
| { |
| read_context_sptr c = |
| create_read_context(elf2.path, di_dirs2, env.get(), |
| /*load_all_types=*/opts.show_all_types); |
| add_read_context_suppressions(*c, priv_types_supprs2); |
| |
| if (!opts.kabi_suppressions.empty()) |
| add_read_context_suppressions(*c, opts.kabi_suppressions); |
| |
| corpus2 = read_corpus_from_elf(*c, c2_status); |
| |
| bool bail_out = false; |
| if (!(c2_status & abigail::dwarf_reader::STATUS_OK)) |
| { |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << "Could not find the read file '" |
| << elf2.path |
| << "' properly\n"; |
| |
| if (detailed_error_status) |
| *detailed_error_status = c2_status; |
| |
| bail_out = true; |
| } |
| |
| if (opts.fail_if_no_debug_info) |
| { |
| bool debug_info_error = false; |
| if (c2_status & abigail::dwarf_reader::STATUS_DEBUG_INFO_NOT_FOUND) |
| { |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << "while reading file" << elf2.path << "\n"; |
| |
| emit_prefix("abipkgdiff", cerr) << "Could not find debug info file"; |
| if (di_dir2 && strcmp(di_dir2, "")) |
| cerr << " under " << di_dir2 << "\n"; |
| else |
| cerr << "\n"; |
| |
| if (detailed_error_status) |
| *detailed_error_status = c2_status; |
| debug_info_error = true; |
| } |
| |
| if (c2_status & abigail::dwarf_reader::STATUS_ALT_DEBUG_INFO_NOT_FOUND) |
| { |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << "while reading file" << elf2.path << "\n"; |
| |
| emit_prefix("abipkgdiff", cerr) |
| << "Could not find alternate debug info file"; |
| string alt_di_path; |
| abigail::dwarf_reader::refers_to_alt_debug_info(*c, alt_di_path); |
| if (!alt_di_path.empty()) |
| cerr << ": " << alt_di_path << "\n"; |
| else |
| cerr << "\n"; |
| |
| if (detailed_error_status) |
| *detailed_error_status = c2_status; |
| debug_info_error = true; |
| } |
| |
| if (debug_info_error) |
| bail_out = true; |
| } |
| |
| if (bail_out) |
| return abigail::tools_utils::ABIDIFF_ERROR; |
| } |
| |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << " DONE reading file " << elf2.path << "\n"; |
| |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << " Comparing the ABIs of: \n" |
| << " " << elf1.path << "\n" |
| << " " << elf2.path << "\n"; |
| |
| diff = compute_diff(corpus1, corpus2, ctxt); |
| |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << "Comparing the ABIs of file " |
| << elf1.path |
| << " and " |
| << elf2.path |
| << " is DONE\n"; |
| |
| abidiff_status s = abigail::tools_utils::ABIDIFF_OK; |
| if (diff->has_net_changes()) |
| s |= abigail::tools_utils::ABIDIFF_ABI_CHANGE; |
| if (diff->has_incompatible_changes()) |
| s |= abigail::tools_utils::ABIDIFF_ABI_INCOMPATIBLE_CHANGE; |
| |
| return s; |
| } |
| |
| /// If devel packages were associated to the main package we are |
| /// looking at, use the names of the header files (extracted from the |
| /// package) to generate suppression specification to filter out types |
| /// that are not defined in those header files. |
| /// |
| /// Filtering out types not defined in publi headers amounts to filter |
| /// out types that are deemed private to the package we are looking |
| /// at. |
| /// |
| /// If the function succeeds, it returns a non-empty vector of |
| /// suppression specifications. |
| /// |
| /// @param pkg the main package we are looking at. |
| /// |
| /// @param opts the options of the current program. |
| /// |
| /// @return a vector of suppression_sptr. If no suppressions |
| /// specification were constructed, the returned vector is empty. |
| static suppressions_type |
| create_private_types_suppressions(const package& pkg, const options &opts) |
| { |
| suppressions_type supprs; |
| |
| package_sptr devel_pkg = pkg.devel_package(); |
| if (!devel_pkg |
| || !file_exists(devel_pkg->extracted_dir_path()) |
| || !is_dir(devel_pkg->extracted_dir_path())) |
| return supprs; |
| |
| string headers_path = devel_pkg->extracted_dir_path(); |
| if (devel_pkg->type() == abigail::tools_utils::FILE_TYPE_RPM |
| ||devel_pkg->type() == abigail::tools_utils::FILE_TYPE_DEB) |
| // For RPM and DEB packages, header files are under the |
| // /usr/include sub-directories. |
| headers_path += "/usr/include"; |
| |
| if (!is_dir(headers_path)) |
| return supprs; |
| |
| suppression_sptr suppr = |
| gen_suppr_spec_from_headers(headers_path); |
| |
| if (suppr) |
| { |
| if (opts.drop_private_types) |
| suppr->set_drops_artifact_from_ir(true); |
| supprs.push_back(suppr); |
| } |
| |
| return supprs; |
| } |
| |
| /// If the user wants to avoid comparing DSOs that are private to this |
| /// package, then we build the set of public DSOs as advertised in the |
| /// package's "provides" property. |
| /// |
| /// Note that at the moment this function only works for RPMs. It |
| /// doesn't yet support other packaging formats. |
| /// |
| /// @param pkg the package to consider. |
| /// |
| /// @param opts the options of this program. |
| /// |
| /// @return true iff the set of public DSOs was built. |
| static bool |
| maybe_create_public_dso_sonames_set(package& pkg, const options &opts) |
| { |
| if (opts.compare_private_dsos || !pkg.public_dso_sonames().empty()) |
| return false; |
| |
| if (pkg.type() == abigail::tools_utils::FILE_TYPE_RPM) |
| return get_dsos_provided_by_rpm(pkg.path(), pkg.public_dso_sonames()); |
| |
| // We don't support this yet for non-RPM packages. |
| return false; |
| } |
| |
| /// Test if we should only compare the public DSOs of a given package. |
| /// |
| /// @param pkg the package to consider. |
| /// |
| /// @param opts the options of this program |
| static bool |
| must_compare_public_dso_only(package& pkg, options& opts) |
| { |
| if (pkg.type() == abigail::tools_utils::FILE_TYPE_RPM |
| && !opts.compare_private_dsos) |
| return true; |
| |
| return false; |
| } |
| |
| /// While walking a file directory, check if a directory entry is a |
| /// kabi whitelist of a particular architecture. |
| /// |
| /// If it is, then save its file path in a vector of whitelists. |
| /// |
| /// @param entry the directory entry to consider. |
| /// |
| /// @param arch the architecture to consider. |
| /// |
| /// @param whitelists out parameter. If @p entry is the whitelist we |
| /// are looking for, add its path to this output parameter. |
| static void |
| maybe_collect_kabi_whitelists(const FTSENT *entry, |
| const string arch, |
| vector<string> &whitelists) |
| { |
| if (entry == NULL |
| || (entry->fts_info != FTS_F && entry->fts_info != FTS_SL) |
| || entry->fts_info == FTS_ERR |
| || entry->fts_info == FTS_NS) |
| return; |
| |
| string path = entry->fts_path; |
| maybe_get_symlink_target_file_path(path, path); |
| |
| string kabi_whitelist_name = "kabi_whitelist_" + arch; |
| |
| if (string_ends_with(path, kabi_whitelist_name)) |
| whitelists.push_back(path); |
| } |
| |
| /// Get the kabi whitelist for a particular architecture under a given |
| /// directory. |
| /// |
| /// @param dir the directory to look at. |
| /// |
| /// @param arch the architecture to consider. |
| /// |
| /// @param whitelist_paths the vector where to add the whitelists |
| /// found. Note that a whitelist is added to this parameter iff the |
| /// function returns true. |
| /// |
| /// @return true iff the function found a whitelist at least. |
| static bool |
| get_kabi_whitelists_from_arch_under_dir(const string& dir, |
| const string& arch, |
| vector<string>& whitelist_paths) |
| { |
| bool is_ok = false; |
| char* paths[] = {const_cast<char*>(dir.c_str()), 0}; |
| |
| FTS *file_hierarchy = fts_open(paths, FTS_LOGICAL|FTS_NOCHDIR, NULL); |
| if (!file_hierarchy) |
| return is_ok; |
| |
| FTSENT *entry; |
| while ((entry = fts_read(file_hierarchy))) |
| maybe_collect_kabi_whitelists(entry, arch, whitelist_paths); |
| |
| fts_close(file_hierarchy); |
| |
| return true; |
| } |
| |
| /// Find a kabi whitelist in a linux kernel RPM package. |
| /// |
| /// Note that the linux kernel RPM package must have been extracted |
| /// somewhere already. |
| /// |
| /// This function then looks for the whitelist under the /lib/modules |
| /// directory inside the extracted content of the package. If it |
| /// finds it and saves its file path in the |
| /// options::kabi_whitelist_paths data member. |
| /// |
| /// @param pkg the linux kernel package to consider. |
| /// |
| /// @param opts the options the program was invoked with. |
| static bool |
| maybe_handle_kabi_whitelist_pkg(const package& pkg, options &opts) |
| { |
| if (opts.kabi_whitelist_packages.empty() |
| || !opts.kabi_whitelist_paths.empty() |
| || !pkg.kabi_whitelist_package()) |
| return false; |
| |
| if (pkg.type() != abigail::tools_utils::FILE_TYPE_RPM) |
| return false; |
| |
| string pkg_name = pkg.base_name(); |
| bool is_linux_kernel_package = file_is_kernel_package(pkg_name, pkg.type()); |
| |
| if (!is_linux_kernel_package) |
| return false; |
| |
| package_sptr kabi_wl_pkg = pkg.kabi_whitelist_package(); |
| assert(kabi_wl_pkg); |
| |
| if (!file_exists(kabi_wl_pkg->extracted_dir_path()) |
| || !is_dir(kabi_wl_pkg->extracted_dir_path())) |
| return false; |
| |
| string rpm_arch; |
| if (!get_rpm_arch(pkg_name, rpm_arch)) |
| return false; |
| |
| string kabi_wl_path = kabi_wl_pkg->extracted_dir_path(); |
| kabi_wl_path += "/lib/modules"; |
| vector<string> whitelist_paths; |
| |
| get_kabi_whitelists_from_arch_under_dir(kabi_wl_path, rpm_arch, |
| whitelist_paths); |
| |
| if (!whitelist_paths.empty()) |
| { |
| std::sort(whitelist_paths.begin(), whitelist_paths.end()); |
| opts.kabi_whitelist_paths.push_back(whitelist_paths.back()); |
| } |
| |
| return true; |
| } |
| |
| /// The task that performs the extraction of the content of several |
| /// packages into a temporary directory. |
| /// |
| /// If this task has several packages to extract, then it extracts |
| /// them in sequence. |
| /// |
| /// Note that several instances of tasks can perform their jobs (i.e |
| /// extract packages in sequence) in parallel. |
| class pkg_extraction_task : public task |
| { |
| pkg_extraction_task(); |
| |
| public: |
| vector<package_sptr> pkgs; |
| const options &opts; |
| bool is_ok; |
| |
| pkg_extraction_task(const package_sptr &p, const options &o) |
| : opts(o), is_ok(true) |
| {pkgs.push_back(p);} |
| |
| pkg_extraction_task(const vector<package_sptr> &packages, const options &o) |
| : pkgs(packages), opts(o), is_ok(true) |
| {} |
| |
| /// The job performed by the current task, which is to extract its |
| /// packages in sequence. This is job is to be performed in |
| /// parallel with other jobs of other tasks. |
| virtual void |
| perform() |
| { |
| for (vector<package_sptr>::const_iterator p = pkgs.begin(); |
| p != pkgs.end(); |
| ++p) |
| is_ok &= extract_package(**p, opts); |
| } |
| }; //end class pkg_extraction_task |
| |
| /// A convenience typedef for a shared pointer to @f pkg_extraction_task. |
| typedef shared_ptr<pkg_extraction_task> pkg_extraction_task_sptr; |
| |
| /// The worker task which job is to prepares a package. |
| /// |
| /// Preparing a package means: |
| /// |
| /// 1/ Extract the package and its ancillary packages. |
| /// |
| /// 2/ Analyze the extracted content, map that content so that we |
| /// determine what the ELF files to be analyze are. |
| class pkg_prepare_task : public abigail::workers::task |
| { |
| pkg_prepare_task(); |
| |
| public: |
| package_sptr pkg; |
| options &opts; |
| bool is_ok; |
| |
| pkg_prepare_task(package_sptr &p, options &o) |
| : pkg(p), opts(o), is_ok(false) |
| {} |
| |
| /// The job performed by this task. |
| virtual void |
| perform() |
| { |
| is_ok = pkg && extract_package_and_map_its_content(pkg, opts); |
| } |
| }; //end class pkg_prepare_task |
| |
| /// A convenience typedef for a shared_ptr to @ref pkg_prepare_task |
| typedef shared_ptr<pkg_prepare_task> pkg_prepare_task_sptr; |
| |
| /// The worker task which job is to compare two ELF binaries |
| class compare_task : public abigail::workers::task |
| { |
| public: |
| |
| compare_args_sptr args; |
| abidiff_status status; |
| ostringstream out; |
| string pretty_output; |
| |
| compare_task() |
| : status(abigail::tools_utils::ABIDIFF_OK) |
| {} |
| |
| compare_task(const compare_args_sptr& a) |
| : args(a), |
| status(abigail::tools_utils::ABIDIFF_OK) |
| {} |
| |
| /// The job performed by the task. |
| /// |
| /// This compares two ELF files, gets the resulting test report and |
| /// stores it in an output stream. |
| virtual void |
| perform() |
| { |
| abigail::ir::environment_sptr env(new abigail::ir::environment); |
| diff_context_sptr ctxt; |
| corpus_diff_sptr diff; |
| |
| abigail::dwarf_reader::status detailed_status = |
| abigail::dwarf_reader::STATUS_UNKNOWN; |
| |
| status |= compare(args->elf1, args->debug_dir1, args->private_types_suppr1, |
| args->elf2, args->debug_dir2, args->private_types_suppr2, |
| args->opts, env, diff, ctxt, &detailed_status); |
| |
| // If there is an ABI change, tell the user about it. |
| if ((status & abigail::tools_utils::ABIDIFF_ABI_CHANGE) |
| ||( diff && diff->has_net_changes())) |
| { |
| diff->report(out, /*prefix=*/" "); |
| string name = args->elf1.name; |
| |
| pretty_output += |
| string("================ changes of '") + name + "'===============\n" |
| + out.str() |
| + "================ end of changes of '" |
| + name + "'===============\n\n"; |
| } |
| else |
| { |
| if (args->opts.show_identical_binaries) |
| out << "No ABI change detected\n"; |
| } |
| |
| // If an error happened while comparing the two binaries, tell the |
| // user about it. |
| if (status & abigail::tools_utils::ABIDIFF_ERROR) |
| { |
| string diagnostic = |
| abigail::dwarf_reader::status_to_diagnostic_string(detailed_status); |
| |
| if (!diagnostic.empty()) |
| { |
| string name = args->elf1.name; |
| |
| pretty_output += |
| "==== Error happened during processing of " + name + ": ====\n"; |
| |
| pretty_output += diagnostic; |
| |
| pretty_output += |
| "==== End of error for " + name + " ====\n"; |
| } |
| } |
| } |
| }; // end class compare_task |
| |
| /// Convenience typedef for a shared_ptr of @ref compare_task. |
| typedef shared_ptr<compare_task> compare_task_sptr; |
| |
| /// This function is a sub-routine of create_maps_of_package_content. |
| /// |
| /// It's called during the walking of the directory tree containing |
| /// the extracted content of package. It's called with an entry of |
| /// that directory tree. |
| /// |
| /// Depending on the kind of file this function is called on, it |
| /// updates the vector of paths of the directory and the set of |
| /// suppression paths found. |
| /// |
| /// @param entry the directory entry to analyze. |
| /// |
| /// @param opts the options of the current program. |
| /// |
| /// @param file_name_to_look_for if this parameter is set, the |
| /// function only looks for a file name which name is the same as the |
| /// value of this parameter. |
| /// |
| /// @param paths out parameter. This is the set of meaningful paths |
| /// of the current directory tree being analyzed. These paths are |
| /// those that are going to be involved in ABI comparison. |
| static void |
| maybe_update_package_content(const FTSENT *entry, |
| options &opts, |
| const string& file_name_to_look_for, |
| unordered_set<string>& paths) |
| { |
| if (entry == NULL |
| || (entry->fts_info != FTS_F && entry->fts_info != FTS_SL) |
| || entry->fts_info == FTS_ERR |
| || entry->fts_info == FTS_NS) |
| return; |
| |
| string path = entry->fts_path; |
| maybe_get_symlink_target_file_path(path, path); |
| |
| if (!file_name_to_look_for.empty()) |
| { |
| string name; |
| abigail::tools_utils::base_name(path, name); |
| if (name == file_name_to_look_for) |
| paths.insert(path); |
| return; |
| } |
| |
| if (guess_file_type(path) == abigail::tools_utils::FILE_TYPE_ELF) |
| paths.insert(path); |
| else if (opts.abignore && string_ends_with(path, ".abignore")) |
| opts.suppression_paths.push_back(path); |
| } |
| |
| /// Walk a given directory to collect files that are "interesting" to |
| /// analyze. By default, "interesting" means interesting from either |
| /// a kernel package or a userspace binary analysis point of view. |
| /// |
| /// @param dir the directory to walk. |
| /// |
| /// @param file_name_to_look_for if this parameter is set, only a file |
| /// with this name is going to be collected. |
| /// |
| /// @param interesting_files out parameter. This parameter is |
| /// populated with the interesting files found by the function iff the |
| /// function returns true. |
| /// |
| /// @return true iff the function completed successfully. |
| static bool |
| get_interesting_files_under_dir(const string dir, |
| const string& file_name_to_look_for, |
| options& opts, |
| vector<string>& interesting_files) |
| { |
| bool is_ok = false; |
| string root; |
| real_path(dir, root); |
| if (root.empty()) |
| root = dir; |
| |
| char* paths[] = {const_cast<char*>(root.c_str()), 0}; |
| |
| FTS *file_hierarchy = fts_open(paths, FTS_LOGICAL|FTS_NOCHDIR, NULL); |
| if (!file_hierarchy) |
| return is_ok; |
| |
| FTSENT *entry; |
| unordered_set<string> files; |
| while ((entry = fts_read(file_hierarchy))) |
| maybe_update_package_content(entry, opts, file_name_to_look_for, files); |
| |
| for (unordered_set<string>::const_iterator i = files.begin(); |
| i != files.end(); |
| ++i) |
| interesting_files.push_back(*i); |
| |
| fts_close(file_hierarchy); |
| |
| is_ok = true; |
| |
| return is_ok; |
| } |
| |
| /// Create maps of the content of a given package. |
| /// |
| /// The maps contain relevant metadata about the content of the |
| /// files. These maps are used afterwards during the comparison of |
| /// the content of the package. Note that the maps are stored in the |
| /// object that represents that package. |
| /// |
| /// @param package the package to consider. |
| /// |
| /// @param opts the options the current program has been called with. |
| /// |
| /// @param true upon successful completion, false otherwise. |
| static bool |
| create_maps_of_package_content(package& package, options& opts) |
| { |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << "Analyzing the content of package " |
| << package.path() |
| << " extracted to " |
| << package.extracted_dir_path() |
| << " ...\n"; |
| |
| bool is_ok = true; |
| vector<string> elf_file_paths; |
| |
| // if package is linux kernel package and its associated debug |
| // info package looks like a kernel debuginfo package, then try to |
| // go find the vmlinux file in that debug info file. |
| string pkg_name = package.base_name(); |
| bool is_linux_kernel_package = file_is_kernel_package(pkg_name, |
| package.type()); |
| if (is_linux_kernel_package) |
| { |
| // For a linux kernel package, no analysis is done. It'll be |
| // done later at comparison time by |
| // compare_prepared_linux_kernel_packages |
| is_ok = true; |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << " Analysis of " << package.path() << " DONE\n"; |
| return is_ok; |
| } |
| |
| is_ok &= get_interesting_files_under_dir(package.extracted_dir_path(), |
| /*file_name_to_look_for=*/"", |
| opts, elf_file_paths); |
| |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << "Found " << elf_file_paths.size() << " files in " |
| << package.extracted_dir_path() << "\n"; |
| |
| // determine if all files have the same prefix. Compute that prefix |
| // and stick it into the package! That prefix is going to be used |
| // later by the package::convert_path_to_unique_suffix method. |
| package.load_elf_file_paths(opts); |
| |
| maybe_create_public_dso_sonames_set(package, opts); |
| |
| for (vector<string>::const_iterator file = elf_file_paths.begin(); |
| file != elf_file_paths.end(); |
| ++file) |
| { |
| elf_file_sptr e (new elf_file(*file)); |
| if (opts.compare_dso_only) |
| { |
| if (e->type != abigail::dwarf_reader::ELF_TYPE_DSO) |
| { |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << "skipping non-DSO file " << e->path << "\n"; |
| continue; |
| } |
| } |
| else |
| { |
| if (e->type != abigail::dwarf_reader::ELF_TYPE_DSO |
| && e->type != abigail::dwarf_reader::ELF_TYPE_EXEC |
| && e->type != abigail::dwarf_reader::ELF_TYPE_PI_EXEC) |
| { |
| if (is_linux_kernel_package) |
| { |
| if (e->type == abigail::dwarf_reader::ELF_TYPE_RELOCATABLE) |
| { |
| // This is a Linux Kernel module. |
| ; |
| } |
| } |
| else if (opts.verbose) |
| { |
| emit_prefix("abipkgdiff", cerr) |
| << "skipping non-DSO non-executable file " |
| << e->path |
| << "\n"; |
| continue; |
| } |
| } |
| } |
| |
| if (e->soname.empty()) |
| { |
| if (e->type == abigail::dwarf_reader::ELF_TYPE_DSO |
| && must_compare_public_dso_only(package, opts)) |
| { |
| // We are instructed to compare public DSOs only. Yet |
| // this DSO does not have a soname. so it can not be a |
| // public DSO. Let's skip it. |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << "DSO " << e->path |
| << " does not have a soname so it's private. Skipping it\n"; |
| continue; |
| } |
| |
| // Several binaries at different paths can have the same |
| // base name. So let's consider the full path of the binary |
| // inside the extracted directory. |
| string key = e->name; |
| package.convert_path_to_unique_suffix(e->path, key); |
| package.path_elf_file_sptr_map()[key] = e; |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << "mapped binary with key '" << key << "'" |
| << "\n"; |
| } |
| else |
| { |
| // Several binaries at different paths can have the same |
| // soname. So let's *also* consider the full path of the |
| // binary inside the extracted directory, not just the |
| // soname. |
| string key = e->soname; |
| |
| if (must_compare_public_dso_only(package, opts)) |
| { |
| if (package.public_dso_sonames().find(key) |
| == package.public_dso_sonames().end()) |
| { |
| // We are instructed to compare public DSOs only and |
| // this one seems to be private. So skip it. |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << "DSO " << e->path << " of soname " << key |
| << " seems to be private. Skipping it\n"; |
| continue; |
| } |
| } |
| |
| if (package.convert_path_to_unique_suffix(e->path, key)) |
| { |
| dir_name(key, key); |
| key += string("/@soname:") + e->soname; |
| } |
| package.path_elf_file_sptr_map()[key] = e; |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << "mapped binary with key '" << key << "'" |
| << "\n"; |
| } |
| } |
| |
| if (opts.verbose) |
| emit_prefix("abipkgdiff", cerr) |
| << " Analysis of " << package.path() << " DONE\n"; |
| |
| is_ok = true; |
| |
| return is_ok; |
| } |
| |
| /// Extract the content of a package (and its ancillary packages) and |
| /// map its content. |
| /// |
| /// First, the content of the package and its ancillary packages are |
| /// extracted, in parallel. |
| /// |
| /// Then, after that extraction is done, the content of the package if |
| /// walked and analyzed. |
| /// |
| /// @param pkg the package to extract and to analyze. |
| /// |
| /// @param opts the options of the current program. |
| /// |
| /// @return true iff the extraction and analyzing went well. |
| static bool |
| extract_package_and_map_its_content(const package_sptr &pkg, options &opts) |
| { |
| assert(pkg); |
| |
| pkg_extraction_task_sptr main_pkg_extraction; |
| pkg_extraction_task_sptr dbg_extraction; |
| pkg_extraction_task_sptr devel_extraction; |
| pkg_extraction_task_sptr kabi_whitelist_extraction; |
| |
| size_t NUM_EXTRACTIONS = 1; |
| |
| main_pkg_extraction.reset(new pkg_extraction_task(pkg, opts)); |
| |
| if (!pkg->debug_info_packages().empty()) |
| { |
| dbg_extraction.reset(new pkg_extraction_task(pkg->debug_info_packages(), |
| opts)); |
| ++NUM_EXTRACTIONS; |
| } |
| |
| if (package_sptr devel_pkg = pkg->devel_package()) |
| { |
| devel_extraction.reset(new pkg_extraction_task(devel_pkg, opts)); |
| ++NUM_EXTRACTIONS; |
| } |
| |
| if (package_sptr kabi_wl_pkg = pkg->kabi_whitelist_package()) |
| { |
| kabi_whitelist_extraction.reset(new pkg_extraction_task(kabi_wl_pkg, |
| opts)); |
| ++NUM_EXTRACTIONS; |
| } |
| |
| size_t num_workers = (opts.parallel |
| ? std::min(opts.num_workers, NUM_EXTRACTIONS) |
| : 1); |
| abigail::workers::queue extraction_queue(num_workers); |
| |
| // Perform the extraction of the NUM_WORKERS packages in parallel. |
| extraction_queue.schedule_task(dbg_extraction); |
| extraction_queue.schedule_task(main_pkg_extraction); |
| extraction_queue.schedule_task(devel_extraction); |
| extraction_queue.schedule_task(kabi_whitelist_extraction); |
| |
| // Wait for the extraction to be done. |
| extraction_queue.wait_for_workers_to_complete(); |
| |
| // Analyze and map the content of the extracted package. |
| bool is_ok = false; |
| if (main_pkg_extraction->is_ok) |
| is_ok = create_maps_of_package_content(*pkg, opts); |
| |
| if (is_ok) |
| maybe_handle_kabi_whitelist_pkg(*pkg, opts); |
| |
| return is_ok; |
| } |
| |
| /// Extract the two packages (and their ancillary packages) and |
| /// analyze their content, so that we later know what files from the |
| /// first package to compare against what files from the second |
| /// package. |
| /// |
| /// Note that preparing the first package and its ancillary packages |
| /// happens in parallel with preparing the second package and its |
| /// ancillary packages. The function then waits for the two |
| /// preparations to complete before returning. |
| /// |
| /// @param first_package the first package to consider. |
| /// |
| /// @param second_package the second package to consider. |
| /// |
| /// @param opts the options of the current program. |
| /// |
| /// @return true iff the preparation went well. |
| static bool |
| prepare_packages(package_sptr &first_package, |
| package_sptr &second_package, |
| options &opts) |
| { |
| pkg_prepare_task_sptr first_pkg_prepare; |
| pkg_prepare_task_sptr second_pkg_prepare; |
| size_t NUM_PREPARATIONS = 2; |
| |
| first_pkg_prepare.reset(new pkg_prepare_task(first_package, opts)); |
| second_pkg_prepare.reset(new pkg_prepare_task(second_package, opts)); |
| |
| size_t num_workers = (opts.parallel |
| ? std::min(opts.num_workers, NUM_PREPARATIONS) |
| : 1); |
| abigail::workers::queue preparation_queue(num_workers); |
| |
| preparation_queue.schedule_task(first_pkg_prepare); |
| preparation_queue.schedule_task(second_pkg_prepare); |
| |
| preparation_queue.wait_for_workers_to_complete(); |
| |
| return first_pkg_prepare->is_ok && second_pkg_prepare->is_ok; |
| } |
| |
| /// Compare the added sizes of an ELF pair (specified by a comparison |
| /// task that compares two ELF files) against the added sizes of a |
| /// second ELF pair. |
| /// |
| /// Larger filesize strongly raises the possibility of larger debug-info, |
| /// hence longer diff time. For a package containing several relatively |
| /// large and small ELFs, it is often more efficient to start working on |
| /// the larger ones first. This function is used to order the pairs by |
| /// size, starting from the largest. |
| /// |
| /// @param t1 the first comparison task that compares a pair of ELF |
| /// files. |
| /// |
| /// @param t2 the second comparison task that compares a pair of ELF |
| /// files. |
| /// |
| /// @return true if @p task1 is greater than @p task2. |
| bool |
| elf_size_is_greater(const task_sptr &task1, |
| const task_sptr &task2) |
| { |
| compare_task_sptr t1 = dynamic_pointer_cast<compare_task>(task1); |
| compare_task_sptr t2 = dynamic_pointer_cast<compare_task>(task2); |
| |
| off_t s1 = t1->args->elf1.size + t1->args->elf2.size; |
| off_t s2 = t2->args->elf1.size + t2->args->elf2.size; |
| |
| return s1 > s2; |
| } |
| |
| /// This type is used to notify the calling thread that the comparison |
| /// of two ELF files is done. |
| class comparison_done_notify : public abigail::workers::queue::task_done_notify |
| { |
| comparison_done_notify(); |
| |
| public: |
| abi_diff& diff; |
| abidiff_status status; |
| |
| comparison_done_notify(abi_diff &d) |
| : diff(d), |
| status(abigail::tools_utils::ABIDIFF_OK) |
| {} |
| |
| /// This operator is invoked by the worker queue whenever a |
| /// comparison task is done. |
| /// |
| /// The operator collects the status of the job of the task and also |
| /// updates the the count of binaries that have ABI changes. |
| /// |
| /// @param task_done the task that is done. |
| virtual void |
| operator()(const task_sptr& task_done) |
| { |
| compare_task_sptr comp_task = dynamic_pointer_cast<compare_task>(task_done); |
| assert(comp_task); |
| |
| status |= comp_task->status; |
| |
| if (status != abigail::tools_utils::ABIDIFF_OK) |
| { |
| string name = comp_task->args->elf1.name; |
| |
| if (status & abigail::tools_utils::ABIDIFF_ABI_CHANGE) |
| diff.changed_binaries.push_back(name); |
| } |
| } |
| }; // end struct comparison_done_notify |
| |
| /// Erase the temporary directories that might have been created while |
| /// handling two packages, unless the user asked to keep the temporary |
| /// directories around. |
| /// |
| /// @param first_package the first package to consider. |
| /// |
| /// @param second_package the second package to consider. |
| /// |
| /// @param opts the options passed to the program. |
| static void |
| maybe_erase_temp_dirs(package& first_package, package& second_package, |
| options& opts) |
| { |
| if (opts.keep_tmp_files) |
| return; |
| |
| erase_created_temporary_directories(first_package, second_package, opts); |
| erase_created_temporary_directories_parent(opts); |
| } |
| |
| /// Compare the ABI of two prepared packages that contain userspace |
| /// binaries. |
| /// |
| /// A prepared package is a package which content has been extracted |
| /// and mapped. |
| /// |
| /// @param first_package the first package to consider. |
| /// |
| /// @param second_package the second package to consider. |
| /// |
| /// @param options the options the current program has been called |
| /// with. |
| /// |
| /// @param diff out parameter. If this function returns true, then |
| /// this parameter is set to the result of the comparison. |
| /// |
| /// @param opts the options of the current program. |
| /// |
| /// @return the status of the comparison. |
| static abidiff_status |
| compare_prepared_userspace_packages(package& first_package, |
| package& second_package, |
| abi_diff& diff, options& opts) |
| { |
| abidiff_status status = abigail::tools_utils::ABIDIFF_OK; |
| abigail::workers::queue::tasks_type compare_tasks; |
| string pkg_name = first_package.base_name(); |
| |
| // Setting debug-info path of libraries |
| string debug_dir1, debug_dir2, relative_debug_path = "/usr/lib/debug/"; |
| if (!first_package.debug_info_packages().empty() |
| && !second_package.debug_info_packages().empty()) |
| { |
| debug_dir1 = |
| first_package.debug_info_packages().front()->extracted_dir_path() + |
| relative_debug_path; |
| debug_dir2 = |
| second_package.debug_info_packages().front()->extracted_dir_path() + |
| relative_debug_path; |
| } |
| |
| for (map<string, elf_file_sptr>::iterator it = |
| first_package.path_elf_file_sptr_map().begin(); |
| it != first_package.path_elf_file_sptr_map().end(); |
| ++it) |
| { |
| map<string, elf_file_sptr>::iterator iter = |
| second_package.path_elf_file_sptr_map().find(it->first); |
| |
| if (iter != second_package.path_elf_file_sptr_map().end() |
| && (iter->second->type == abigail::dwarf_reader::ELF_TYPE_DSO |
| || iter->second->type == abigail::dwarf_reader::ELF_TYPE_EXEC |
| || iter->second->type == abigail::dwarf_reader::ELF_TYPE_PI_EXEC |
| || iter->second->type == abigail::dwarf_reader::ELF_TYPE_RELOCATABLE)) |
| { |
| if (iter->second->type != abigail::dwarf_reader::ELF_TYPE_RELOCATABLE) |
| { |
| compare_args_sptr args |
| (new compare_args(*it->second, |
| debug_dir1, |
| create_private_types_suppressions |
| (first_package, opts), |
| *iter->second, |
| debug_dir2, |
| create_private_types_suppressions |
| (second_package, opts), opts)); |
| compare_task_sptr t(new compare_task(args)); |
| compare_tasks.push_back(t); |
| } |
| second_package.path_elf_file_sptr_map().erase(iter); |
| } |
| else if (iter == second_package.path_elf_file_sptr_map().end()) |
| { |
| diff.removed_binaries.push_back(it->second); |
| status |= abigail::tools_utils::ABIDIFF_ABI_INCOMPATIBLE_CHANGE; |
| status |= abigail::tools_utils::ABIDIFF_ABI_CHANGE; |
| } |
| } |
| |
| if (compare_tasks.empty()) |
| { |
| maybe_erase_temp_dirs(first_package, second_package, opts); |
| return abigail::tools_utils::ABIDIFF_OK; |
| } |
| |
| // Larger elfs are processed first, since it's usually safe to assume |
| // their debug-info is larger as well, but the results are still |
| // in a map ordered by looked up in elf.name order. |
| std::sort(compare_tasks.begin(), compare_tasks.end(), elf_size_is_greater); |
| |
| // There's no reason to spawn more workers than there are ELF pairs |
| // to be compared. |
| size_t num_workers = (opts.parallel |
| ? std::min(opts.num_workers, compare_tasks.size()) |
| : 1); |
| assert(num_workers >= 1); |
| |
| comparison_done_notify notifier(diff); |
| abigail::workers::queue comparison_queue(num_workers, notifier); |
| |
| // Compare all the binaries, in parallel and then wait for the |
| // comparisons to complete. |
| comparison_queue.schedule_tasks(compare_tasks); |
| comparison_queue.wait_for_workers_to_complete(); |
| |
| // Get the set of comparison tasks that were perform and sort them. |
| queue::tasks_type& done_tasks = comparison_queue.get_completed_tasks(); |
| std::sort(done_tasks.begin(), done_tasks.end(), elf_size_is_greater); |
| |
| // Print the reports of the comparison to standard output. |
| for (queue::tasks_type::const_iterator i = done_tasks.begin(); |
| i != done_tasks.end(); |
| ++i) |
| { |
| compare_task_sptr t = dynamic_pointer_cast<compare_task>(*i); |
| cout << t->pretty_output; |
| } |
| |
| // Update the count of added binaries. |
| for (map<string, elf_file_sptr>::iterator it = |
| second_package.path_elf_file_sptr_map().begin(); |
| it != second_package.path_elf_file_sptr_map().end(); |
| ++it) |
| diff.added_binaries.push_back(it->second); |
| |
| // Print information about removed binaries on standard output. |
| if (diff.removed_binaries.size()) |
| { |
| cout << "Removed binaries:\n"; |
| for (vector<elf_file_sptr>::iterator it = diff.removed_binaries.begin(); |
| it != diff.removed_binaries.end(); ++it) |
| { |
| string relative_path; |
| first_package.convert_path_to_relative((*it)->path, relative_path); |
| cout << " [D] " << relative_path << ", "; |
| string soname; |
| get_soname_of_elf_file((*it)->path, soname); |
| if (!soname.empty()) |
| cout << "SONAME: " << soname; |
| else |
| cout << "no SONAME"; |
| cout << "\n"; |
| } |
| } |
| |
| // Print information about added binaries on standard output. |
| if (opts.show_added_binaries && diff.added_binaries.size()) |
| { |
| cout << "Added binaries:\n"; |
| for (vector<elf_file_sptr>::iterator it = diff.added_binaries.begin(); |
| it != diff.added_binaries.end(); ++it) |
| { |
| string relative_path; |
| second_package.convert_path_to_relative((*it)->path, relative_path); |
| cout << " [A] " << relative_path << ", "; |
| string soname; |
| get_soname_of_elf_file((*it)->path, soname); |
| if (!soname.empty()) |
| cout << "SONAME: " << soname; |
| else |
| cout << "no SONAME"; |
| cout << "\n"; |
| } |
| } |
| |
| // Erase temporary directory tree we might have left behind. |
| maybe_erase_temp_dirs(first_package, second_package, opts); |
| |
| status = notifier.status; |
| |
| return status; |
| } |
| |
| /// Compare the ABI of two prepared packages that contain linux kernel |
| /// binaries. |
| /// |
| /// A prepared package is a package which content has been extracted |
| /// and mapped. |
| /// |
| /// @param first_package the first package to consider. |
| /// |
| /// @param second_package the second package to consider. |
| /// |
| /// @param options the options the current program has been called |
| /// with. |
| /// |
| /// @param diff out parameter. If this function returns true, then |
| /// this parameter is set to the result of the comparison. |
| /// |
| /// @param opts the options of the current program. |
| /// |
| /// @return the status of the comparison. |
| static abidiff_status |
| compare_prepared_linux_kernel_packages(package& first_package, |
| package& second_package, |
| options& opts) |
| { |
| abidiff_status status = abigail::tools_utils::ABIDIFF_OK; |
| string pkg_name = first_package.base_name(); |
| |
| // Setting debug-info path of binaries |
| string debug_dir1, debug_dir2, relative_debug_path = "/usr/lib/debug/"; |
| if (!first_package.debug_info_packages().empty() |
| && !second_package.debug_info_packages().empty()) |
| { |
| debug_dir1 = |
| first_package.debug_info_packages().front()->extracted_dir_path() + |
| relative_debug_path; |
| debug_dir2 = |
| second_package.debug_info_packages().front()->extracted_dir_path() + |
| relative_debug_path; |
| } |
| |
| string vmlinux_path1, vmlinux_path2; |
| |
| if (!get_vmlinux_path_from_kernel_dist(debug_dir1, vmlinux_path1)) |
| return abigail::tools_utils::ABIDIFF_ERROR; |
| |
| if (!get_vmlinux_path_from_kernel_dist(debug_dir2, vmlinux_path2)) |
| return abigail::tools_utils::ABIDIFF_ERROR; |
| |
| string dist_root1 = first_package.extracted_dir_path(); |
| string dist_root2 = second_package.extracted_dir_path(); |
| |
| abigail::ir::environment_sptr env(new abigail::ir::environment); |
| suppressions_type supprs; |
| corpus_group_sptr corpus1, corpus2; |
| corpus1 = build_corpus_group_from_kernel_dist_under(dist_root1, |
| debug_dir1, |
| vmlinux_path1, |
| opts.suppression_paths, |
| opts.kabi_whitelist_paths, |
| supprs, |
| opts.verbose, |
| env); |
| |
| if (!corpus1) |
| return abigail::tools_utils::ABIDIFF_ERROR; |
| |
| corpus2 = build_corpus_group_from_kernel_dist_under(dist_root2, |
| debug_dir2, |
| vmlinux_path2, |
| opts.suppression_paths, |
| opts.kabi_whitelist_paths, |
| supprs, |
| opts.verbose, |
| env); |
| |
| if (!corpus2) |
| return abigail::tools_utils::ABIDIFF_ERROR; |
| |
| diff_context_sptr diff_ctxt(new diff_context); |
| set_diff_context_from_opts(diff_ctxt, opts); |
| |
| corpus_diff_sptr diff = compute_diff(corpus1, corpus2, diff_ctxt); |
| |
| if (diff->has_net_changes()) |
| status |= abigail::tools_utils::ABIDIFF_ABI_CHANGE; |
| if (diff->has_incompatible_changes()) |
| status |= abigail::tools_utils::ABIDIFF_ABI_INCOMPATIBLE_CHANGE; |
| |
| if (status & abigail::tools_utils::ABIDIFF_ABI_CHANGE) |
| { |
| cout << "== Kernel ABI changes between packages '" |
| << first_package.path() << "' and '" |
| << second_package.path() << "' are: ===\n"; |
| diff->report(cout); |
| cout << "== End of kernel ABI changes between packages '" |
| << first_package.path() |
| << "' and '" |
| << second_package.path() << "' ===\n\n"; |
| } |
| |
| return status; |
| } |
| |
| /// Compare the ABI of two prepared packages. |
| /// |
| /// A prepared package is a package which content has been extracted |
| /// and mapped. |
| /// |
| /// @param first_package the first package to consider. |
| /// |
| /// @param second_package the second package to consider. |
| /// |
| /// @param options the options the current program has been called |
| /// with. |
| /// |
| /// @param diff out parameter. If this function returns true, then |
| /// this parameter is set to the result of the comparison. |
| /// |
| /// @param opts the options of the current program. |
| /// |
| /// @return the status of the comparison. |
| static abidiff_status |
| compare_prepared_package(package& first_package, package& second_package, |
| abi_diff& diff, options& opts) |
| { |
| abidiff_status status = abigail::tools_utils::ABIDIFF_OK; |
| |
| if (abigail::tools_utils::file_is_kernel_package(first_package.base_name(), |
| first_package.type())) |
| { |
| opts.show_symbols_not_referenced_by_debug_info = false; |
| status = compare_prepared_linux_kernel_packages(first_package, |
| second_package, |
| opts); |
| } |
| else |
| status = compare_prepared_userspace_packages(first_package, |
| second_package, |
| diff, opts); |
| |
| return status; |
| } |
| |
| /// Compare the ABI of two packages |
| /// |
| /// @param first_package the first package to consider. |
| /// |
| /// @param second_package the second package to consider. |
| /// |
| /// @param options the options the current program has been called |
| /// with. |
| /// |
| /// @param diff out parameter. If this function returns true, then |
| /// this parameter is set to the result of the comparison. |
| /// |
| /// @param opts the options of the current program. |
| /// |
| /// @return the status of the comparison. |
| static abidiff_status |
| compare(package_sptr& first_package, package_sptr& second_package, |
| abi_diff& diff, options& opts) |
| { |
| // Prepare (extract and analyze the contents) the packages and their |
| // ancillary packages. |
| // |
| // Note that the package preparations happens in parallel. |
| if (!prepare_packages(first_package, second_package, opts)) |
| { |
| maybe_erase_temp_dirs(*first_package, *second_package, opts); |
| return abigail::tools_utils::ABIDIFF_ERROR; |
| } |
| |
| return compare_prepared_package(*first_package, *second_package, diff, opts); |
| } |
| |
| /// Compare the ABI of two packages. |
| /// |
| /// @param first_package the first package to consider. |
| /// |
| /// @param second_package the second package to consider. |
| /// |
| /// @param opts the options the current program has been called with. |
| /// |
| /// @return the status of the comparison. |
| static abidiff_status |
| compare(package_sptr& first_package, |
| package_sptr& second_package, |
| options& opts) |
| { |
| abi_diff diff; |
| return compare(first_package, second_package, diff, opts); |
| } |
| |
| /// Parse the command line of the current program. |
| /// |
| /// @param argc the number of arguments in the @p argv parameter. |
| /// |
| /// @param argv the array of arguemnts passed to the function. The |
| /// first argument is the name of this program. |
| /// |
| /// @param opts the resulting options. |
| /// |
| /// @return true upon successful parsing. |
| static bool |
| parse_command_line(int argc, char* argv[], options& opts) |
| { |
| if (argc < 2) |
| return false; |
| |
| for (int i = 1; i < argc; ++i) |
| { |
| if (argv[i][0] != '-') |
| { |
| if (opts.package1.empty()) |
| { |
| opts.package1 = make_path_absolute(argv[i]).get(); |
| opts.nonexistent_file = !file_exists(opts.package1); |
| } |
| else if (opts.package2.empty()) |
| { |
| opts.package2 = make_path_absolute(argv[i]).get(); |
| opts.nonexistent_file = !file_exists(opts.package2); |
| } |
| else |
| { |
| opts.wrong_arg = argv[i]; |
| return false; |
| } |
| |
| if (opts.nonexistent_file) |
| { |
| opts.wrong_option = argv[i]; |
| return true; |
| } |
| } |
| else if (!strcmp(argv[i], "--debug-info-pkg1") |
| || !strcmp(argv[i], "--d1")) |
| { |
| int j = i + 1; |
| if (j >= argc) |
| { |
| opts.missing_operand = true; |
| opts.wrong_option = argv[i]; |
| return true; |
| } |
| opts.debug_packages1.push_back |
| (abigail::tools_utils::make_path_absolute(argv[j]).get()); |
| ++i; |
| } |
| else if (!strcmp(argv[i], "--debug-info-pkg2") |
| || !strcmp(argv[i], "--d2")) |
| { |
| int j = i + 1; |
| if (j >= argc) |
| { |
| opts.missing_operand = true; |
| opts.wrong_option = argv[i]; |
| return true; |
| } |
| opts.debug_packages2.push_back |
| (abigail::tools_utils::make_path_absolute(argv[j]).get()); |
| ++i; |
| } |
| else if (!strcmp(argv[i], "--devel-pkg1") |
| || !strcmp(argv[i], "--devel1")) |
| { |
| int j = i + 1; |
| if (j >= argc) |
| { |
| opts.missing_operand = true; |
| opts.wrong_option = argv[i]; |
| return true; |
| } |
| opts.devel_package1 = |
| abigail::tools_utils::make_path_absolute(argv[j]).get(); |
| ++i; |
| } |
| else if (!strcmp(argv[i], "--devel-pkg2") |
| || !strcmp(argv[i], "--devel2")) |
| { |
| int j = i + 1; |
| if (j >= argc) |
| { |
| opts.missing_operand = true; |
| opts.wrong_option = argv[i]; |
| return true; |
| } |
| opts.devel_package2 = |
| abigail::tools_utils::make_path_absolute(argv[j]).get(); |
| ++i; |
| } |
| else if (!strcmp(argv[i], "--drop-private-types")) |
| opts.drop_private_types = true; |
| else if (!strcmp(argv[i], "--no-default-suppression")) |
| opts.no_default_suppression = true; |
| else if (!strcmp(argv[i], "--keep-tmp-files")) |
| opts.keep_tmp_files = true; |
| else if (!strcmp(argv[i], "--dso-only")) |
| opts.compare_dso_only = true; |
| else if (!strcmp(argv[i], "--private-dso")) |
| opts.compare_private_dsos = true; |
| else if (!strcmp(argv[i], "--leaf-changes-only") |
| ||!strcmp(argv[i], "-l")) |
| opts.leaf_changes_only = true; |
| else if (!strcmp(argv[i], "--impacted-interfaces") |
| ||!strcmp(argv[i], "-i")) |
| opts.show_impacted_interfaces = true; |
| else if (!strcmp(argv[i], "--non-reachable-types") |
| ||!strcmp(argv[i], "-t")) |
| opts.show_all_types = true; |
| else if (!strcmp(argv[i], "--full-impact") |
| ||!strcmp(argv[i], "-f")) |
| opts.show_full_impact_report = true; |
| else if (!strcmp(argv[i], "--no-linkage-name")) |
| opts.show_linkage_names = false; |
| else if (!strcmp(argv[i], "--redundant")) |
| opts.show_redundant_changes = true; |
| else if (!strcmp(argv[i], "--harmless")) |
| opts.show_harmless_changes = true; |
| else if (!strcmp(argv[i], "--no-show-locs")) |
| opts.show_locs = false; |
| else if (!strcmp(argv[i], "--show-bytes")) |
| opts.show_offsets_sizes_in_bits = false; |
| else if (!strcmp(argv[i], "--show-bits")) |
| opts.show_offsets_sizes_in_bits = true; |
| else if (!strcmp(argv[i], "--show-hex")) |
| opts.show_hexadecimal_values = true; |
| else if (!strcmp(argv[i], "--show-dec")) |
| opts.show_hexadecimal_values = false; |
| else if (!strcmp(argv[i], "--no-show-relative-offset-changes")) |
| opts.show_relative_offset_changes = false; |
| else if (!strcmp(argv[i], "--no-added-syms")) |
| opts.show_added_syms = false; |
| else if (!strcmp(argv[i], "--no-unreferenced-symbols")) |
| opts.show_symbols_not_referenced_by_debug_info = false; |
| else if (!strcmp(argv[i], "--no-added-binaries")) |
| opts.show_added_binaries = false; |
| else if (!strcmp(argv[i], "--fail-no-dbg")) |
| opts.fail_if_no_debug_info = true; |
| else if (!strcmp(argv[i], "--verbose")) |
| opts.verbose = true; |
| else if (!strcmp(argv[i], "--no-abignore")) |
| opts.abignore = false; |
| else if (!strcmp(argv[i], "--no-parallel")) |
| opts.parallel = false; |
| else if (!strcmp(argv[i], "--show-identical-binaries")) |
| opts.show_identical_binaries = true; |
| else if (!strcmp(argv[i], "--suppressions") |
| || !strcmp(argv[i], "--suppr")) |
| { |
| int j = i + 1; |
| if (j >= argc) |
| return false; |
| opts.suppression_paths.push_back(argv[j]); |
| ++i; |
| } |
| else if (!strcmp(argv[i], "--linux-kernel-abi-whitelist") |
| || !strcmp(argv[i], "-w")) |
| { |
| int j = i + 1; |
| if (j >= argc) |
| { |
| opts.missing_operand = true; |
| opts.wrong_option = argv[i]; |
| return true; |
| } |
| if (guess_file_type(argv[j]) == abigail::tools_utils::FILE_TYPE_RPM) |
| // The kernel abi whitelist is actually a whitelist |
| // *package*. Take that into account. |
| opts.kabi_whitelist_packages.push_back |
| (make_path_absolute(argv[j]).get()); |
| else |
| // We assume the kernel abi whitelist is a white list |
| // file. |
| opts.kabi_whitelist_paths.push_back(argv[j]); |
| ++i; |
| } |
| else if (!strcmp(argv[i], "--wp")) |
| { |
| int j = i + 1; |
| if (j >= argc) |
| { |
| opts.missing_operand = true; |
| opts.wrong_option = argv[i]; |
| return true; |
| } |
| opts.kabi_whitelist_packages.push_back |
| (make_path_absolute(argv[j]).get()); |
| ++i; |
| } |
| else if (!strcmp(argv[i], "--help") |
| || !strcmp(argv[i], "-h")) |
| { |
| opts.display_usage = true; |
| return true; |
| } |
| else if (!strcmp(argv[i], "--version") |
| || !strcmp(argv[i], "-v")) |
| { |
| opts.display_version = true; |
| return true; |
| } |
| else |
| { |
| if (strlen(argv[i]) >= 2 && argv[i][0] == '-' && argv[i][1] == '-') |
| opts.wrong_option = argv[i]; |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| int |
| main(int argc, char* argv[]) |
| { |
| options opts(argv[0]); |
| |
| if (!parse_command_line(argc, argv, opts)) |
| { |
| if (!opts.wrong_option.empty()) |
| emit_prefix("abipkgdiff", cerr) |
| << "unrecognized option: " << opts.wrong_option |
| << "\ntry the --help option for more information\n"; |
| else |
| emit_prefix("abipkgdiff", cerr) |
| << "unrecognized argument: " << opts.wrong_arg |
| << "\ntry the --help option for more information\n"; |
| return (abigail::tools_utils::ABIDIFF_USAGE_ERROR |
| | abigail::tools_utils::ABIDIFF_ERROR); |
| } |
| |
| if (opts.missing_operand) |
| { |
| emit_prefix("abipkgdiff", cerr) |
| << "missing operand\n" |
| "try the --help option for more information\n"; |
| return (abigail::tools_utils::ABIDIFF_USAGE_ERROR |
| | abigail::tools_utils::ABIDIFF_ERROR); |
| } |
| |
| if (opts.nonexistent_file) |
| { |
| string input_file; |
| base_name(opts.wrong_option, input_file); |
| emit_prefix("abipkgdiff", cerr) |
| << "The input file " << input_file << " doesn't exist\n" |
| "try the --help option for more information\n"; |
| return (abigail::tools_utils::ABIDIFF_USAGE_ERROR |
| | abigail::tools_utils::ABIDIFF_ERROR); |
| } |
| |
| if (opts.kabi_whitelist_packages.size() > 2) |
| { |
| emit_prefix("abipkgdiff", cerr) |
| << "no more than 2 Linux kernel white list packages can be provided\n"; |
| return (abigail::tools_utils::ABIDIFF_USAGE_ERROR |
| | abigail::tools_utils::ABIDIFF_ERROR); |
| } |
| |
| if (opts.display_usage) |
| { |
| display_usage(argv[0], cout); |
| return (abigail::tools_utils::ABIDIFF_USAGE_ERROR |
| | abigail::tools_utils::ABIDIFF_ERROR); |
| } |
| |
| if (opts.display_version) |
| { |
| emit_prefix(argv[0], cout) |
| << abigail::tools_utils::get_library_version_string() |
| << "\n"; |
| return 0; |
| } |
| |
| if (!opts.no_default_suppression && opts.suppression_paths.empty()) |
| { |
| // Load the default system and user suppressions. |
| string default_system_suppr_file = |
| get_default_system_suppression_file_path(); |
| if (file_exists(default_system_suppr_file)) |
| opts.suppression_paths.push_back(default_system_suppr_file); |
| |
| string default_user_suppr_file = |
| get_default_user_suppression_file_path(); |
| if (file_exists(default_user_suppr_file)) |
| opts.suppression_paths.push_back(default_user_suppr_file); |
| } |
| |
| if (!maybe_check_suppression_files(opts)) |
| return (abigail::tools_utils::ABIDIFF_USAGE_ERROR |
| | abigail::tools_utils::ABIDIFF_ERROR); |
| |
| if (opts.package1.empty() || opts.package2.empty()) |
| { |
| emit_prefix("abipkgdiff", cerr) |
| << "Please enter two packages to compare" << "\n"; |
| return (abigail::tools_utils::ABIDIFF_USAGE_ERROR |
| | abigail::tools_utils::ABIDIFF_ERROR); |
| } |
| |
| package_sptr first_package(new package(opts.package1, "package1")); |
| |
| package_sptr second_package(new package(opts.package2, "package2")); |
| |
| for (vector<string>::const_iterator p = opts.debug_packages1.begin(); |
| p != opts.debug_packages1.end(); |
| ++p) |
| first_package->debug_info_packages().push_back |
| (package_sptr(new package(*p, |
| "debug_package1", |
| /*pkg_kind=*/package::KIND_DEBUG_INFO))); |
| |
| for (vector<string>::const_iterator p = opts.debug_packages2.begin(); |
| p != opts.debug_packages2.end(); |
| ++p) |
| second_package->debug_info_packages().push_back |
| (package_sptr(new package(*p, |
| "debug_package2", |
| /*pkg_kind=*/package::KIND_DEBUG_INFO))); |
| |
| if (!opts.devel_package1.empty()) |
| first_package->devel_package |
| (package_sptr(new package(opts.devel_package1, |
| "devel_package1", |
| /*pkg_kind=*/package::KIND_DEVEL))); |
| ; |
| |
| if (!opts.devel_package2.empty()) |
| second_package->devel_package |
| (package_sptr(new package(opts.devel_package2, |
| "devel_package2", |
| /*pkg_kind=*/package::KIND_DEVEL))); |
| |
| if (!opts.kabi_whitelist_packages.empty()) |
| { |
| first_package->kabi_whitelist_package |
| (package_sptr(new package |
| (opts.kabi_whitelist_packages[0], |
| "kabi_whitelist_package1", |
| /*pkg_kind=*/package::KIND_KABI_WHITELISTS))); |
| if (opts.kabi_whitelist_packages.size() >= 2) |
| second_package->kabi_whitelist_package |
| (package_sptr(new package |
| (opts.kabi_whitelist_packages[1], |
| "kabi_whitelist_package2", |
| /*pkg_kind=*/package::KIND_KABI_WHITELISTS))); |
| } |
| |
| string package_name; |
| switch (first_package->type()) |
| { |
| case abigail::tools_utils::FILE_TYPE_RPM: |
| if (second_package->type() != abigail::tools_utils::FILE_TYPE_RPM) |
| { |
| base_name(opts.package2, package_name); |
| emit_prefix("abipkgdiff", cerr) |
| << package_name << " should be an RPM file\n"; |
| return (abigail::tools_utils::ABIDIFF_USAGE_ERROR |
| | abigail::tools_utils::ABIDIFF_ERROR); |
| } |
| |
| if (file_is_kernel_package(first_package->base_name(), |
| abigail::tools_utils::FILE_TYPE_RPM) |
| || file_is_kernel_package(second_package->base_name(), |
| abigail::tools_utils::FILE_TYPE_RPM)) |
| { |
| if (file_is_kernel_package(first_package->base_name(), |
| abigail::tools_utils::FILE_TYPE_RPM) |
| != file_is_kernel_package(second_package->base_name(), |
| abigail::tools_utils::FILE_TYPE_RPM)) |
| { |
| emit_prefix("abipkgdiff", cerr) |
| << "a Linux kernel package can only be compared to another " |
| "Linux kernel package\n"; |
| return (abigail::tools_utils::ABIDIFF_USAGE_ERROR |
| | abigail::tools_utils::ABIDIFF_ERROR); |
| } |
| |
| if (first_package->debug_info_packages().empty() |
| || second_package->debug_info_packages().empty()) |
| { |
| emit_prefix("abipkgdiff", cerr) |
| << "a Linux Kernel package must be accompanied with its " |
| "debug info package\n"; |
| return (abigail::tools_utils::ABIDIFF_USAGE_ERROR |
| | abigail::tools_utils::ABIDIFF_ERROR); |
| } |
| // We are looking at kernel packages. If the user provided |
| // the --full-impact option then it means we want to display |
| // the default libabigail report format where a full impact |
| // analysis is done for each ABI change. |
| // |
| // Otherwise, let's just emit the leaf change report. |
| if (opts.show_full_impact_report) |
| opts.leaf_changes_only = false; |
| else |
| opts.leaf_changes_only = true; |
| } |
| |
| break; |
| |
| case abigail::tools_utils::FILE_TYPE_DEB: |
| if (second_package->type() != abigail::tools_utils::FILE_TYPE_DEB) |
| { |
| base_name(opts.package2, package_name); |
| emit_prefix("abipkgdiff", cerr) |
| << package_name << " should be a DEB file\n"; |
| return (abigail::tools_utils::ABIDIFF_USAGE_ERROR |
| | abigail::tools_utils::ABIDIFF_ERROR); |
| } |
| break; |
| |
| case abigail::tools_utils::FILE_TYPE_DIR: |
| if (second_package->type() != abigail::tools_utils::FILE_TYPE_DIR) |
| { |
| base_name(opts.package2, package_name); |
| emit_prefix("abipkgdiff", cerr) |
| << package_name << " should be a directory\n"; |
| return (abigail::tools_utils::ABIDIFF_USAGE_ERROR |
| | abigail::tools_utils::ABIDIFF_ERROR); |
| } |
| break; |
| |
| case abigail::tools_utils::FILE_TYPE_TAR: |
| if (second_package->type() != abigail::tools_utils::FILE_TYPE_TAR) |
| { |
| base_name(opts.package2, package_name); |
| emit_prefix("abipkgdiff", cerr) |
| << package_name << " should be a GNU tar archive\n"; |
| return (abigail::tools_utils::ABIDIFF_USAGE_ERROR |
| | abigail::tools_utils::ABIDIFF_ERROR); |
| } |
| break; |
| |
| default: |
| base_name(opts.package1, package_name); |
| emit_prefix("abipkgdiff", cerr) |
| << package_name << " should be a valid package file \n"; |
| return (abigail::tools_utils::ABIDIFF_USAGE_ERROR |
| | abigail::tools_utils::ABIDIFF_ERROR); |
| } |
| |
| return compare(first_package, second_package, opts); |
| } |