????
Current Path : /usr/lib/Acronis/BackupAndRecovery/ |
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)