File: //usr/lib/Acronis/BackupAndRecovery/sysinfo.py
# @copyright (c) 2002-2016 Acronis International GmbH. All rights reserved.
"""Acronis System Info Report Utility."""
import os, sys
import argparse
import subprocess
import shutil
import hashlib
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:
return ["/etc/Acronis", "/usr/lib/Acronis", "/var/lib/Acronis"]
elif self.platform == self._PLATFORM_WIN:
return self._get_install_paths_windows()
else:
return [
"/Library/Application Support/Acronis",
"/Library/Application Support/BackupClient"]
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 _iter_files(top_dir, ignore_dir, file_extentions=[], ignore_file_extentions=[]):
# recursively yield (dir_name, file_name) for files from specified directory
#
# Arguments:
# - top_dir: top-level directory to yield files from
# - ignore_dir: ignore files in this dir (usefull if report directory
# is inside top_dir).
# - file_extentions: (optional) only yield files matching the extentions
# - ignore_file_extentions: (optional) ignore files with these extentions
ignore_dir = os.path.normpath(ignore_dir)
for dir_name, _sub_dirs, file_names in os.walk(top_dir):
if ignore_dir and os.path.commonpath([dir_name, ignore_dir]) == ignore_dir:
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_file_extentions:
if any(file_name.endswith(ext) for ext in ignore_file_extentions):
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"]
install_paths = self._get_install_paths()
src_2_tgt_dirs = {} # {conf_file_dir: dir_in_report}
for top_dir in install_paths:
for dir_name, file_name in self._iter_files(top_dir, self.report_dir, file_extentions):
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]
shutil.copy(os.path.join(dir_name, file_name), os.path.join(tgt_dir, file_name))
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
for top_loc in self._get_install_paths():
for dir_name, file_name in self._iter_files(top_loc, self.report_dir,
ignore_file_extentions=[".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 = "-nap" if self.platform == self._PLATFORM_LINUX else "-nab"
with open(rep_file_path, "w+") as outfile:
subprocess.call(["netstat", 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(
"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()
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)