????

Your IP : 18.191.118.36


Current Path : /usr/lib/Acronis/BackupAndRecovery/
Upload File :
Current File : //usr/lib/Acronis/BackupAndRecovery/sysinfo.py

# @copyright (c) 2002-2016 Acronis International GmbH. All rights reserved.

"""Acronis System Info Report Utility."""

import glob, os, sys
import argparse
import subprocess
import shutil
import hashlib
import sqlite3


class _SysInfoReportBase():
    # Base class for sys-info reports

    _PLATFORM_LINUX, _PLATFORM_WIN, _PLATFORM_MACOS = range(3)

    def __init__(self, platform, report_dir, cmd_name):
        # Arguments
        #   - platform: (optional) one of _PLATFORM_* constants
        #   - report_dir: path to report directory
        #   - cmd_name: name of report command (f.e. 'collect_configs')
        #     Used for diagnostic purposes only
        self.cmd_name = cmd_name
        self.report_dir = os.path.abspath(report_dir)
        self.platform = platform if platform is not None else self._detect_platform()

    @classmethod
    def _detect_platform(cls):
        if sys.platform.startswith('win32'):
            return cls._PLATFORM_WIN
        elif sys.platform.startswith('linux'):
            return cls._PLATFORM_LINUX
        elif sys.platform.startswith('darwin'):
            return cls._PLATFORM_MACOS
        assert False, "Unexpected sys.platform name: {}".format(sys.platform)

    def _get_install_paths(self):
        # get list of Acronis installation locations
        if self.platform == self._PLATFORM_LINUX:
            self._ETC_DIR = "/etc/Acronis"
            self._USR_LIB_DIR = "/usr/lib/Acronis"
            self._VAR_LIB_DIR = "/var/lib/Acronis"
            self._OPT_DIR = "/opt/acronis"
            return [self._ETC_DIR, self._USR_LIB_DIR, self._VAR_LIB_DIR, self._OPT_DIR]
        elif self.platform == self._PLATFORM_WIN:
            return self._get_install_paths_windows()
        else:
            self._ACRONIS_DIR = "/Library/Application Support/Acronis"
            return [
                self._ACRONIS_DIR,
                "/Library/Application Support/BackupClient",
                "/Library/Logs/Acronis"
            ]

    def _get_install_paths_windows(self):
        # on windows product installation path should be taken from registry
        import acrobind
        import acrort

        install_paths = set([])

        brand_name = acrort.common.BRAND_NAME

        for path_id in ('COMMONPROGRAMFILES', 'COMMONPROGRAMFILES(x86)',
                        'PROGRAMDATA', 'ALLUSERSPROFILE'):
            # paths like "C:\Program Files\Common Files\Acronis"
            #
            # %PROGRAMDATA% and %ALLUSERSPROFILE% reference the
            # same dir: usually "C:\ProgramData". But one of these variables
            # may be not present depending on Windows version.
            if path_id in os.environ:
                install_paths.add(os.path.join(os.environ[path_id], brand_name))

        key_path = r"SOFTWARE\{}\Installer".format(brand_name)
        val_name = "TargetDir"
        product_install_path = acrobind.registry_read_string(key_path, val_name)

        if product_install_path:
            install_paths.add(product_install_path)
        else:
            print(
                "Warning: Processing '{0}' report command. "
                "Product installation dir not found in registry. "
                "key_path: {1}, val_name {2}".format(self.cmd_name, key_path, val_name))

        return sorted(install_paths)

    @staticmethod
    def _dump_sqlite3_db(db_path, output_csv, exclude_tables_list):
        with sqlite3.connect(db_path) as conn:
            cursor = conn.cursor()
            tables = cursor.execute("SELECT name FROM sqlite_master WHERE type='table';").fetchall()
            if exclude_tables_list is None:
                table_names = [table[0] for table in tables]
            else:
                table_names = [table[0] for table in tables if table[0] not in exclude_tables_list]
            with open(output_csv, "w", encoding='utf-8') as csvfile:
                for table_name in table_names:
                    rows = cursor.execute("SELECT * FROM {};".format(table_name)).fetchall()
                    headers = cursor.execute("PRAGMA table_info('{}');".format(table_name)).fetchall()
                    header_names = [header[1] for header in headers]
                    csvfile.write("Table: {}\n".format(table_name))
                    csvfile.write(",".join(header_names) + "\r\n")
                    for row in rows:
                        csvfile.write(",".join(map(str, row)) + "\r\n")

    @staticmethod
    def _iter_files(top_dir, ignore_dirs, file_extentions=[], ignore_files=[]):
        # recursively yield (dir_name, file_name) for files from specified directory
        #
        # Arguments:
        #   - top_dir: top-level directory to yield files from
        #   - ignore_dirs: ignore files in this dir (usefull if report directory
        #       is inside top_dir or when you want to skip the "mount" dir where
        #       backups are mounted).
        #   - file_extentions: (optional) only yield files matching the extentions
        #   - ignore_files: (optional) ignore files ending with the given paths

        for i in range(len(ignore_dirs)):
            ignore_dirs[i] = os.path.normpath(ignore_dirs[i])
        for i in range(len(ignore_files)):
            ignore_files[i] = os.path.normpath(ignore_files[i])

        for dir_name, _sub_dirs, file_names in os.walk(top_dir):
            if ignore_dirs and \
                any(os.path.commonpath([dir_name, ignore_dir]) == ignore_dir
                    for ignore_dir in ignore_dirs):
                continue
            for file_name in file_names:
                if file_extentions:
                    if not any(file_name.endswith(ext) for ext in file_extentions):
                        continue
                if ignore_files:
                    if any(os.path.join(dir_name, file_name).endswith(ignore_file) for ignore_file in ignore_files):
                        continue
                yield (dir_name, file_name)


#########################
# collect conf files

class _CollectConfigFilesReport(_SysInfoReportBase):
    # inclde all the Acronis configuration files into the report

    def run_report(self):
        configs_report_subdir = os.path.join(self.report_dir, "configs")

        file_extentions = [".config", ".cfg", ".conf", ".xml", ".json", ".ini", ".yml", ".yaml", ".db"]
        ignore_files = ["ml_analysis.xml", "AccessVault/config/preferred.json", "MMS/user.config", "Agent/var/credentials-store/credentials_store.db"]
        install_paths = self._get_install_paths()
        always_ignore_relative_dirs = ["/atp-downloader/Cache"]
        for i in range(len(always_ignore_relative_dirs)):
            always_ignore_relative_dirs[i] = os.path.normpath(always_ignore_relative_dirs[i])

        src_2_tgt_dirs = {}  # {conf_file_dir: dir_in_report}

        ignore_dirs = [self.report_dir]
        if self.platform == self._PLATFORM_LINUX:
            ignore_dirs.append(os.path.join(self._VAR_LIB_DIR, "mount"))
            ignore_dirs.append(os.path.join(self._VAR_LIB_DIR, "NGMP"))
            ignore_dirs.extend(glob.glob(os.path.join(self._VAR_LIB_DIR, "sysinfo*")))
        if self.platform == self._PLATFORM_MACOS:
            ignore_dirs.extend(glob.glob(os.path.join(self._ACRONIS_DIR, "sysinfo*")))


        for top_dir in install_paths:
            for dir_name, file_name in self._iter_files(top_dir, ignore_dirs, file_extentions, ignore_files):
                
                if any(os.path.normpath(rel_dir) in dir_name for rel_dir in always_ignore_relative_dirs):
                    continue

                if dir_name not in src_2_tgt_dirs:
                    src_2_tgt_dirs[dir_name] = self._make_tgt_dir_for_configs_report(
                        dir_name, configs_report_subdir)
                tgt_dir = src_2_tgt_dirs[dir_name]
                tgt_file = os.path.join(tgt_dir, file_name)
                src_file = os.path.join(dir_name, file_name)
                if file_name.endswith("account_server.db"):
                    tgt_file = tgt_file + '.txt'
                    tables_2_exclude = [
                        'clients',
                        'rsa_keys',
                        'keys_table',
                        '__client_old',
                        'backup_servers',
                        'identity_providers',
                        'identities',
                        'refresh_tokens',
                        'opaque_tokens']
                    acc_srv_dir = os.path.join(self.report_dir, "AccountServer")
                    if not os.path.exists(acc_srv_dir):
                        os.mkdir(acc_srv_dir)
                    self._dump_sqlite3_db(src_file, os.path.join(acc_srv_dir, "db_dump.txt"), tables_2_exclude)
                elif file_name.endswith("api_gateway.json"):
                    if self.platform in (self._PLATFORM_LINUX, self._PLATFORM_MACOS):
                        os.system('grep -vwE "{0}" "{1}" > "{2}"'.format('passphrase', src_file, tgt_file))
                    else:
                        os.system('findstr -V "{0}" "{1}" > "{2}"'.format('passphrase', src_file, tgt_file))
                elif file_name.endswith("Global.config"):
                    if self.platform in (self._PLATFORM_LINUX, self._PLATFORM_MACOS):
                        os.system('grep -vwE "{0}" "{1}" > "{2}"'.format('(Username|Password)', src_file, tgt_file))
                else:
                    shutil.copy(src_file, tgt_file)

    def _make_tgt_dir_for_configs_report(self, config_dir_name, configs_report_subdir):
        # returns abs path of dir in the report to copy the config file to.
        # Create the dir if not exist yet.
        if self.platform in (self._PLATFORM_LINUX, self._PLATFORM_MACOS):
            tgt_file_rel_path = os.path.relpath(config_dir_name, "/")
        else:  # self.platform == _PLATFORM_WIN
            drive = os.path.splitdrive(config_dir_name)[0]  # "C:"
            drive = os.path.join(drive, os.sep)             # "C:\\"
            tgt_file_rel_path = os.path.relpath(config_dir_name, drive)
        tgt_file_location = os.path.join(configs_report_subdir, tgt_file_rel_path)
        os.makedirs(tgt_file_location, exist_ok=True)
        return tgt_file_location


#########################
# report Acronis files hashes

class _CollectFileHashes(_SysInfoReportBase):
    # calculate hashes of all the Acronis files

    def run_report(self):

        no_hash_for_exts = [".log", ]

        with open(os.path.join(self.report_dir, "file_hashes.txt"), "w+") as out_file:
            for file_path in self._iter_installed_files():
                skip_hash = (
                    any(file_path.endswith(ext) for ext in no_hash_for_exts)
                    or not os.path.isfile(file_path))

                if skip_hash:
                    hexdigest = "n/a"
                else:
                    with open(file_path, "rb") as file_data:
                        hexdigest = hashlib.md5(file_data.read()).hexdigest()
                out_file.write("{0}\t{1}\n".format(file_path, hexdigest))

    def _iter_installed_files(self):
        # yields all the files in Acronis installation directories

        ignore_dirs = [self.report_dir]
        if self.platform == self._PLATFORM_LINUX:
            ignore_dirs.append(os.path.join(self._VAR_LIB_DIR, "mount"))
            ignore_dirs.append(os.path.join(self._VAR_LIB_DIR, "NGMP"))
            ignore_dirs.extend(glob.glob(os.path.join(self._VAR_LIB_DIR, "sysinfo*")))
        if self.platform == self._PLATFORM_MACOS:
            ignore_dirs.extend(glob.glob(os.path.join(self._ACRONIS_DIR, "sysinfo*")))

        for top_loc in self._get_install_paths():
            for dir_name, file_name in self._iter_files(top_loc, ignore_dirs,
                                                        ignore_files=[".pyc", ]):
                yield os.path.join(dir_name, file_name)


#########################
# report netstat

class _CollectNetstat(_SysInfoReportBase):
    # just report 'netstat -a' output

    def run_report(self):
        rep_file_path = os.path.join(self.report_dir, "netstat.txt")

        options = "-nab"
        if self.platform == self._PLATFORM_LINUX:
            if os.path.isfile("/bin/acronis"):
                options = "-na"
            else:
                options = "-nap"

        netstat_executable = shutil.which("netstat")
        if netstat_executable is not None and len(netstat_executable) > 0:
          with open(rep_file_path, "w+") as outfile:
              subprocess.call([netstat_executable, options], stdout=outfile)


#########################
# common functionality

_REPORT_CLASSES = {
    'collect_configs': _CollectConfigFilesReport,
# Disable hash collection because perfomance degradation  ABR-121489: Collecting sysinfo loads 100% CPU and woks too long ~ 5 min
#    'collect_filehashes': _CollectFileHashes,
    'netstat': _CollectNetstat,
}


def _parse_arguments():
    parser = argparse.ArgumentParser(
        description=("Part of Acronis sysinfo utility. "
                     "!!! Not intended to be executed directly !!!"))

    parser.add_argument(
        "-o", "--output-dir",
        dest="output_dir",
        help=("(optional) Path to output report directory. "
              "Default is current directory."))

    platform_names = {
        'linux': _SysInfoReportBase._PLATFORM_LINUX,
        'macos': _SysInfoReportBase._PLATFORM_MACOS,
        'win': _SysInfoReportBase._PLATFORM_WIN}

    parser.add_argument(
        "-p", "--platform",
        dest="platform_name",
        choices=sorted(platform_names.keys()))

    parser.add_argument(
        "--optimized",
        dest="optimized",
        default=False,
        action='store_true',
        help='(optional) Optimize data collection.')

    parser.add_argument(
        "--days ",
        dest="days",
        type=int,
        help='(optional) Collect data for the last <DAYS> prior to current date.')

    parser.add_argument(
        "commands", nargs='*', metavar='command',
        choices=[[]] + sorted(_REPORT_CLASSES.keys()),
        help=("(optional) Data collection command. "
              "If not specified all commands will be executed."))

    args = parser.parse_args()

    if args.days and args.days < 1:
        raise argparse.ArgumentTypeError("{0} days number is wrong. Minimum days number is 1.".format(args.days))

    platform = platform_names.get(args.platform_name)

    output_dir = args.output_dir if args.output_dir is not None else os.getcwd()

    commands_to_execute = args.commands if args.commands else sorted(_REPORT_CLASSES.keys())

    return platform, output_dir, commands_to_execute


if __name__ == '__main__':

    platform, output_dir, commands_to_execute = _parse_arguments()

    for cmd_name in commands_to_execute:
        try:
            cmd_report = _REPORT_CLASSES[cmd_name](platform, output_dir, cmd_name)
            cmd_report.run_report()
        except:
            print("Warning: error processing '{0}' report command.".format(cmd_name))
            import traceback
            traceback.print_exc(file=sys.stdout)