diff --git a/advisors/check_abi.py b/advisors/check_abi.py new file mode 100755 index 0000000000000000000000000000000000000000..2ca05a32246b265c2b095c0ee658f47163092c1e --- /dev/null +++ b/advisors/check_abi.py @@ -0,0 +1,396 @@ +#!/usr/bin/python3 +#****************************************************************************** +# Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved. +# licensed under the Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +# PURPOSE. +# See the Mulan PSL v2 for more details. +# Author: wangchuangGG +# Create: 2020-07-20 +# ******************************************************************************/ + +""" +(1) This script is used to check the interface changes between the old + and new versions of the C/C++ language pack. + The final difference information result is saved in the xxx_all_abidiff.out file in the working directory + default path: /var/tmp/xxx_all_abidiff.out + +(2) To use this script, you must first install the Libabigail service, + you can download and install libabigail-xxx.rpm in the community. + +(3) Command parameters + Required parameters: + -rpm1 Specify the old version rpm path or url + (e.g. /home/test-1.0.rpm or https://example.com/test-1.0.rpm) + + -rpm2 Specify the new version rpm path or url + (e.g. /home/test-2.0.rpm or https://example.com/test-2.0.rpm) + + Or: + -so1 Specify the old version .so file + (e.g /home/test-1.0.so) + + -so2 Specify the old version .so file + (e.g /home/test-2.0.so) + + Optional parameters: + + -d_rpm1 Specify the old version debuginfo_rpm path or url + (e.g. /home/test-debuginfo-1.0.rpm or https://example.com/test-debuginfo-1.0.rpm) + + -d_rpm2 Specify the new version debuginfo_rpm path or url + (e.g. /home/test-debuginfo-2.0.rpm or https://example.com/test-debuginfo-2.0.rpm) + + Or: + -d_path1 Specify the old version debuginfo file path + (e.g. /home/test-1.0/lib/debug/) + + -d_path2 Specify the new version debuginfo file path + (e.g. /home/test-2.0/lib/debug/) + + -d Specify the work path of the rpm2cpio + default: /var/tmp/ + + -all Show all infos includ changes in member name + default: False +""" +import argparse +import subprocess +import sys +import os +import logging +#import pycurl +import io +logging.basicConfig(format='%(message)s', level=logging.INFO) + +all_so_pair = {} + +def parse_command_line(): + """Parse the command line arguments.""" + parser = argparse.ArgumentParser() + + ex_group1 = parser.add_mutually_exclusive_group(required=True) + ex_group1.add_argument("-rpm1", "--old_rpm", default="", + help = "the old version rpm path or url") + ex_group1.add_argument("-so1", "--old_so", default="", + help = "the old version .so file") + + ex_group2 = parser.add_mutually_exclusive_group(required=True) + ex_group2.add_argument("-rpm2", "--new_rpm", default="", + help = "the new version rpm path or url") + ex_group2.add_argument("-so2", "--new_so", default="", + help = "the new version .so file") + + ex_group3 = parser.add_mutually_exclusive_group() + ex_group3.add_argument("-d_rpm1", "--old_debuginfo_rpm", + default="", nargs="?", + help = "the old version debuginfo_rpm path or url") + ex_group3.add_argument("-d_path1", "--old_debuginfo_path", + default="", nargs="?", + help = "the old version debuginfo file path") + + ex_group4 = parser.add_mutually_exclusive_group() + ex_group4.add_argument("-d_rpm2", "--new_debuginfo_rpm", + default="", nargs="?", + help = "the new version debuginfo_rpm path or url") + ex_group4.add_argument("-d_path2", "--new_debuginfo_path", default="", + nargs="?", + help = "the new version debuginfo file path") + + parser.add_argument("-d", "--work_path", default="", nargs="?", + help="The work path to put rpm2cpio files and results" + " (e.g. /home/tmp_abidiff default: /var/tmp/)") + parser.add_argument("-all", "--show_all_info", action="store_true", default=False, + help="show all infos includ changes in member name") + parser.add_argument("-rpms", "--rpms_path", default="", nargs="?", + help="Find the rpm packages in this path that calls this change interfaces" + " (e.g. /home/rpms)") + return parser.parse_args() + + +def find_so_file(path): + """ + Try to find .so files in path. + return .so files. + """ + so_files = [] + for dirpath, dirnames, files in os.walk(path): + for filename in files: + fp = os.path.join(dirpath, filename) + if ".so" in filename and not os.path.islink(fp): + #fp = os.path.join(dirpath, filename) + so_files.append(fp) + return so_files + + +def find_all_so_file(path1, path2): + """ + Try to find all .so files. + """ + old_so = find_so_file(path1) + new_so = find_so_file(path2) + logging.debug("old_so:%s\n", old_so) + logging.debug("new_so:%s\n", new_so) + if old_so and new_so: + for so_file1 in old_so: + for so_file2 in new_so: + base_name1 = (os.path.basename(so_file1)).split('.')[0] + base_name2 = (os.path.basename(so_file2)).split('.')[0] + if base_name1 == base_name2: + all_so_pair[so_file1] = so_file2 + else: + logging.info("Not found so files") + sys.exit(0) + logging.debug("all so files:%s\n", all_so_pair) + + +def get_rpm2cpio_path(work_path, abipath): + """ + Get the path to put so file from rpm + return the path. + """ + fp = os.path.join(work_path, abipath) + if os.path.isdir(fp): + subprocess.run("rm -rf {}".format(fp), shell=True) + subprocess.run("mkdir {}".format(fp), shell=True) + return fp + + +def get_rpm_path(rpm_url, dest): + """Get the path of rpm package""" + if os.path.isfile(rpm_url): + abs_rpmpath = os.path.abspath(rpm_url) + logging.debug("rpm exists:%s", abs_rpmpath) + return abs_rpmpath + else: + rpm_name = os.path.basename(rpm_url) + rpm_path = os.path.join(dest, rpm_name) + logging.debug("downloading %s......", rpm_name) + subprocess.call(["curl", rpm_url, "-L", + "--connect-timeout", "10", + "--max-time", "600", + "-sS", "-o", rpm_path]) + return rpm_path + + +def get_work_path(config): + """ + Get the work path + return the path. + """ + work_path = "/var/tmp" + if config.work_path: + work_path = os.path.abspath(config.work_path) + logging.debug("work_path assigned:%s", work_path) + return work_path + + +def do_rpm2cpio(rpm2cpio_path, rpm_file): + """ + Exec the rpm2cpio at rpm2cpio_path. + """ + os.chdir(rpm2cpio_path) + logging.debug("\n----working in path:%s----", os.getcwd()) + logging.debug("rpm2cpio %s", rpm_file) + subprocess.run("rpm2cpio {} | cpio -id".format(rpm_file), shell=True) + + +def merge_all_abidiff_files(all_abidiff_files, work_path, rpm_base_name): + """ + Merge the all diff files to merged_file. + return the merged_file. + """ + merged_file = os.path.join(work_path, "{}_all_abidiff.out".format(rpm_base_name)) + if os.path.exists(merged_file): + subprocess.run("rm -rf {}".format(merged_file), shell=True) + + ofile = open(merged_file, "a+") + for diff_file in all_abidiff_files: + diff_file_name = os.path.basename(diff_file) + ofile.write("---------------diffs in {}:----------------\n".format(diff_file_name)) + for txt in open(diff_file, "r"): + ofile.write(txt) + ofile.close() + return merged_file + + +def do_abidiff(config, work_path, base_name, old_debuginfo_path, new_debuginfo_path): + """ + Exec the abidiff and write result to files. + return the abidiff returncode. + """ + if not all_so_pair: + logging.info("There has no so file") + sys.exit(0) + else: + return_code = 0 + all_abidiff_files = [] + for old_so_file in all_so_pair: + new_so_file = all_so_pair[old_so_file] + logging.debug("begin abidiff between %s and %s", old_so_file, new_so_file) + + abidiff_file = os.path.join(work_path, + "{}_{}_abidiff.out".format( + base_name, os.path.basename(new_so_file))) + + if config.show_all_info: + if old_debuginfo_path and new_debuginfo_path: + logging.debug("old_debuginfo_path:%s\nnew_debuginfo_path:%s", + old_debuginfo_path, new_debuginfo_path) + + ret = subprocess.run("abidiff {} {} --d1 {} --d2 {} " + "--harmless > {}".format( + old_so_file, + new_so_file, + old_debuginfo_path, + new_debuginfo_path, + abidiff_file), + shell=True) + else: + ret = subprocess.run("abidiff {} {} --harmless > {}".format( + old_so_file, new_so_file, + abidiff_file), shell=True) + else: + if old_debuginfo_path and new_debuginfo_path: + logging.debug("old_debuginfo_path:%s\nnew_debuginfo_path:%s", + old_debuginfo_path, new_debuginfo_path) + + ret = subprocess.run("abidiff {} {} --d1 {} --d2 {} > {} " + "--changed-fns --deleted-fns --added-fns".format( + old_so_file, new_so_file, + old_debuginfo_path, new_debuginfo_path, + abidiff_file), shell=True) + else: + ret = subprocess.run("abidiff {} {} > {} --changed-fns" + " --deleted-fns --added-fns".format( + old_so_file, new_so_file, + abidiff_file), shell=True) + + all_abidiff_files.append(abidiff_file) + logging.info("result write in: %s", abidiff_file) + return_code |= ret.returncode + merged_file = merge_all_abidiff_files(all_abidiff_files, work_path, base_name) + logging.info("all results writed in: %s", merged_file) + return return_code + + +def check_command(config): + """ + Check the command arguments + """ + if config.old_rpm or config.new_rpm or config.old_debuginfo_rpm or config.new_debuginfo_rpm: + if config.old_so or config.new_so or config.old_debuginfo_path or config.new_debuginfo_path: + logging.error("-rpm and -so cannot required at the same time") + sys.exit(0) + + if config.old_so or config.new_so: + if not os.path.isfile(config.old_so) or ".so" not in config.old_so: + logging.error("-so1 not exists or not a .so file") + sys.exit(0) + if not os.path.isfile(config.new_so) or ".so" not in config.new_so: + logging.error("-so2 not exists or not a .so file") + sys.exit(0) + + if config.old_debuginfo_path and not os.path.exists(config.old_debuginfo_path): + logging.error("-d_path1 the path not exists") + sys.exit(0) + + if config.new_debuginfo_path and not os.path.exists(config.new_debuginfo_path): + logging.error("-d_path2 the path not exists") + sys.exit(0) + + +def check_result(returncode): + """ + Check the result of abidiff + """ + ABIDIFF_ERROR_BIT = 1 + if returncode == 0: + logging.info("No abidiff found") + elif returncode & ABIDIFF_ERROR_BIT: + logging.info("An unexpected error happened") + else: + logging.info("Found abidiffs") + + +def process_with_rpm(config, work_path): + """ + Process the file with type of rpm. + """ + old_rpm2cpio_path = get_rpm2cpio_path(work_path, "old_abi") + new_rpm2cpio_path = get_rpm2cpio_path(work_path, "new_abi") + logging.debug("old_rpm2cpio_path:%s\nnew_rpm2cpio_path:%s", + old_rpm2cpio_path, new_rpm2cpio_path) + + old_rpm = get_rpm_path(config.old_rpm, old_rpm2cpio_path) + old_debuginfo_rpm = get_rpm_path(config.old_debuginfo_rpm, old_rpm2cpio_path) + + new_rpm = get_rpm_path(config.new_rpm, new_rpm2cpio_path) + new_debuginfo_rpm = get_rpm_path(config.new_debuginfo_rpm, new_rpm2cpio_path) + + logging.debug("old_rpm:%s\nold_debuginfo_rpm:%s\n" + "new_rpm:%s\nnew_debuginfo_rpm:%s", + old_rpm, old_debuginfo_rpm, + new_rpm, new_debuginfo_rpm) + + do_rpm2cpio(old_rpm2cpio_path, old_rpm) + do_rpm2cpio(old_rpm2cpio_path, old_debuginfo_rpm) + do_rpm2cpio(new_rpm2cpio_path, new_rpm) + do_rpm2cpio(new_rpm2cpio_path, new_debuginfo_rpm) + + os.chdir(work_path) + logging.debug("\n----begin abidiff working in path:%s----", os.getcwd()) + + old_so_path = os.path.join(old_rpm2cpio_path, "usr/lib64") + new_so_path = os.path.join(new_rpm2cpio_path, "usr/lib64") + find_all_so_file(old_so_path, new_so_path) + + old_debuginfo_path = os.path.join(old_rpm2cpio_path, "usr/lib/debug") + new_debuginfo_path = os.path.join(new_rpm2cpio_path, "usr/lib/debug") + rpm_base_name = os.path.basename(new_rpm).split('.')[0] + returncode = do_abidiff(config, work_path, rpm_base_name, + old_debuginfo_path, new_debuginfo_path) + check_result(returncode) + + +def process_with_so(config, work_path): + """ + Process the file with type of .so. + """ + old_so_path = os.path.abspath(config.old_so) + new_so_path = os.path.abspath(config.new_so) + all_so_pair[old_so_path] = new_so_path + os.chdir(work_path) + logging.debug("\n----begin abidiff with .so working in path:%s----", os.getcwd()) + + so_base_name = os.path.basename(old_so_path).split('.')[0] + if config.old_debuginfo_path: + old_debuginfo_path = os.path.abspath(config.old_debuginfo_path) + new_debuginfo_path = os.path.abspath(config.new_debuginfo_path) + returncode = do_abidiff(config, work_path, so_base_name, + old_debuginfo_path, new_debuginfo_path) + else: + returncode = do_abidiff(config, work_path, so_base_name, None, None) + + check_result(returncode) + + +def main(): + """Entry point for check_abi""" + config = parse_command_line() + check_command(config) + work_path = get_work_path(config) + + if config.old_rpm: + process_with_rpm(config, work_path) + if config.old_so: + process_with_so(config, work_path) + sys.exit(0) + + +if __name__ == "__main__": + main()