diff options
Diffstat (limited to 'sos/utilities.py')
-rw-r--r-- | sos/utilities.py | 445 |
1 files changed, 445 insertions, 0 deletions
diff --git a/sos/utilities.py b/sos/utilities.py new file mode 100644 index 00000000..e91586de --- /dev/null +++ b/sos/utilities.py @@ -0,0 +1,445 @@ +### This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. + +## This program 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 Public License for more details. + +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +# pylint: disable-msg = R0902 +# pylint: disable-msg = R0904 +# pylint: disable-msg = W0702 +# pylint: disable-msg = W0703 +# pylint: disable-msg = R0201 +# pylint: disable-msg = W0611 +# pylint: disable-msg = W0613 + +import os +import re +import sys +import string +import fnmatch +import inspect +from stat import * +from itertools import * +from subprocess import Popen, PIPE +import shlex +import logging +import zipfile +import tarfile +import hashlib +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO +import time + +def checksum(filename, chunk_size=128): + """Returns the checksum of the supplied filename. The file is read in + chunk_size blocks""" + name = get_hash_name() + digest = hashlib.new(name) + fd = open(filename, 'rb') + data = fd.read(chunk_size) + while data: + digest.update(data) + data = fd.read(chunk_size) + return digest.hexdigest() + +def get_hash_name(): + """Returns the algorithm used when computing a hash""" + import sos.policies + policy = sos.policies.load() + try: + name = policy.getPreferredHashAlgorithm() + hashlib.new(name) + return name + except: + return 'sha256' + +class DirTree(object): + """Builds an ascii representation of a directory structure""" + + def __init__(self, top_directory): + self.directory_count = 0 + self.file_count = 0 + self.buffer = [] + self.top_directory = top_directory + self._build_tree() + + def buf(self, s): + self.buffer.append(s) + + def printtree(self): + print self.as_string() + + def as_string(self): + return "\n".join(self.buffer) + + def _build_tree(self): + self.buf(os.path.abspath(self.top_directory)) + self.tree_i(self.top_directory, first=True) + + def _convert_bytes(self, n): + K, M, G, T = 1 << 10, 1 << 20, 1 << 30, 1 << 40 + if n >= T: + return '%.1fT' % (float(n) / T) + elif n >= G: + return '%.1fG' % (float(n) / G) + elif n >= M: + return '%.1fM' % (float(n) / M) + elif n >= K: + return '%.1fK' % (float(n) / K) + else: + return '%d' % n + + def _get_user(self, stats): + try: + import pwd + return pwd.getpwuid(stats.st_uid)[0] + except ImportError: + return str(stats.st_uid) + + def _get_group(self, stats): + try: + import grp + return grp.getgrgid(stats.st_gid)[0] + except ImportError: + return str(stats.st_uid) + + def _format(self, path): + """Conditionally adds detail to paths""" + stats = os.stat(path) + details = { + "filename": os.path.basename(path), + "user": self._get_user(stats), + "group": self._get_group(stats), + "filesize": self._convert_bytes(stats.st_size), + } + return ("[%(user)s %(group)s %(filesize)s] " % details, "%(filename)s" % details) + + def tree_i(self, dir_, padding='', first=False, fmt="%-30s %s%s%s"): + if not first: + details, filename = self._format(os.path.abspath(dir_)) + line = fmt % (details, padding[:-1], "+-- ", filename) + self.buf(line) + padding += ' ' + + count = 0 + files = os.listdir(dir_) + files.sort(key=string.lower) + for f in files: + count += 1 + path = os.path.join(dir_, f) + + if f.startswith("."): + pass + elif os.path.isfile(path): + self.file_count += 1 + details, filename = self._format(path) + line = fmt % (details, padding, "+-- ", filename) + self.buf(line) + elif os.path.islink(path): + self.buf(padding + + '+-- ' + + f + + ' -> ' + os.path.basename(os.path.realpath(path))) + if os.path.isdir(path): + self.directory_count += 1 + else: + self.file_count += 1 + elif os.path.isdir(path): + self.directory_count += 1 + if count == len(files): + self.tree_i(path, padding + ' ') + else: + self.tree_i(path, padding + '|') + + +class ImporterHelper(object): + """Provides a list of modules that can be imported in a package. + Importable modules are located along the module __path__ list and modules + are files that end in .py. This class will read from PKZip archives as well + for listing out jar and egg contents.""" + + def __init__(self, package): + """package is a package module + import my.package.module + helper = ImporterHelper(my.package.module)""" + self.package = package + + def _plugin_name(self, path): + "Returns the plugin module name given the path" + base = os.path.basename(path) + name, ext = os.path.splitext(base) + return name + + def _get_plugins_from_list(self, list_): + plugins = [self._plugin_name(plugin) + for plugin in list_ + if "__init__" not in plugin + and plugin.endswith(".py")] + plugins.sort() + return plugins + + def _find_plugins_in_dir(self, path): + if os.path.exists(path): + py_files = list(find("*.py", path)) + pnames = self._get_plugins_from_list(py_files) + if pnames: + return pnames + else: + return [] + + def _get_path_to_zip(self, path, tail_list=None): + if not tail_list: + tail_list = [''] + + if path.endswith(('.jar', '.zip', '.egg')): + return path, os.path.join(*tail_list) + + head, tail = os.path.split(path) + tail_list.insert(0, tail) + + if head == path: + raise Exception("not a zip file") + else: + return self._get_path_to_zip(head, tail_list) + + + def _find_plugins_in_zipfile(self, path): + try: + path_to_zip, tail = self._get_path_to_zip(path) + zf = zipfile.ZipFile(path_to_zip) + root_names = [name for name in zf.namelist() if tail in name] + candidates = self._get_plugins_from_list(root_names) + zf.close() + if candidates: + return candidates + else: + return [] + except (IOError, Exception): + return [] + + def get_modules(self): + "Returns the list of importable modules in the configured python package." + plugins = [] + for path in self.package.__path__: + if os.path.isdir(path) or path == '': + plugins.extend(self._find_plugins_in_dir(path)) + else: + plugins.extend(self._find_plugins_in_zipfile(path)) + + return plugins + + +def find(file_pattern, top_dir, max_depth=None, path_pattern=None): + """generator function to find files recursively. Usage: + + for filename in find("*.properties", "/var/log/foobar"): + print filename + """ + if max_depth: + base_depth = os.path.dirname(top_dir).count(os.path.sep) + max_depth += base_depth + + for path, dirlist, filelist in os.walk(top_dir): + if max_depth and path.count(os.path.sep) >= max_depth: + del dirlist[:] + + if path_pattern and not fnmatch.fnmatch(path, path_pattern): + continue + + for name in fnmatch.filter(filelist, file_pattern): + yield os.path.join(path, name) + + +def sosGetCommandOutput(command, timeout=300): + """Execute a command through the system shell. First checks to see if the + requested command is executable. Returns (returncode, stdout, 0)""" + # XXX: what is this doing this for? + cmdfile = command.strip("(").split()[0] + + possibles = [cmdfile] + [os.path.join(path, cmdfile) for path in os.environ.get("PATH", "").split(":")] + + if any(os.access(path, os.X_OK) for path in possibles): + p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, bufsize=-1) + stdout, stderr = p.communicate() + return (p.returncode, stdout.strip(), 0) + else: + return (127, "", 0) + + +def import_module(module_fqname, superclass=None): + module_name = module_fqname.rpartition(".")[-1] + module = __import__(module_fqname, globals(), locals(), [module_name]) + modules = [class_ for cname, class_ in + inspect.getmembers(module, inspect.isclass) + if class_.__module__ == module_fqname] + if superclass: + modules = [m for m in modules if issubclass(m, superclass)] + + return modules + +class Archive(object): + + _name = "unset" + + def prepend(self, src): + if src: + name = os.path.split(self._name)[-1] + return os.path.join(name, src.lstrip(os.sep)) + + +class TarFileArchive(Archive): + + def __init__(self, name): + self._name = name + self.tarfile = tarfile.open(self.name(), mode="w") + + def name(self): + return "%s.tar" % self._name + + def add_file(self, src, dest=None): + if dest: + dest = self.prepend(dest) + else: + dest = self.prepend(src) + + fp = open(src, 'rb') + content = fp.read() + fp.close() + + tar_info = tarfile.TarInfo(name=dest) + tar_info.size = len(content) + tar_info.mtime = os.stat(src).st_mtime + + self.tarfile.addfile(tar_info, StringIO(content)) + + def add_string(self, content, dest): + dest = self.prepend(dest) + tar_info = tarfile.TarInfo(name=dest) + tar_info.size = len(content) + tar_info.mtime = time.time() + self.tarfile.addfile(tar_info, StringIO(content)) + + def open_file(self, name): + try: + self.tarfile.close() + self.tarfile = tarfile.open(self.name(), mode="r") + name = self.prepend(name) + file_obj = self.tarfile.extractfile(name) + file_obj = StringIO(file_obj.read()) + return file_obj + finally: + self.tarfile.close() + self.tarfile = tarfile.open(self.name(), mode="a") + + def close(self): + self.tarfile.close() + + +class ZipFileArchive(Archive): + + def __init__(self, name): + self._name = name + try: + import zlib + self.compression = zipfile.ZIP_DEFLATED + except: + self.compression = zipfile.ZIP_STORED + + self.zipfile = zipfile.ZipFile(self.name(), mode="w", compression=self.compression) + + def name(self): + return "%s.zip" % self._name + + def add_file(self, src, dest=None): + if os.path.isdir(src): + # We may not need, this, but if we do I only want to do it + # one time + regex = re.compile(r"^" + src) + for path, dirnames, filenames in os.walk(src): + for filename in filenames: + filename = path + filename + if dest: + self.zipfile.write(filename, + self.prepend(re.sub(regex, dest, filename))) + else: + self.zipfile.write(filename, self.prepend(filename)) + else: + if dest: + self.zipfile.write(src, self.prepend(dest)) + else: + self.zipfile.write(src, self.prepend(src)) + + def add_string(self, content, dest): + info = zipfile.ZipInfo(self.prepend(dest), + date_time=time.localtime(time.time())) + info.compress_type = self.compression + info.external_attr = 0400 << 16L + self.zipfile.writestr(info, content) + + def open_file(self, name): + try: + self.zipfile.close() + self.zipfile = zipfile.ZipFile(self.name(), mode="r") + name = self.prepend(name) + file_obj = self.zipfile.open(name) + return file_obj + finally: + self.zipfile.close() + self.zipfile = zipfile.ZipFile(self.name(), mode="a") + + def close(self): + self.zipfile.close() + + +def compress(archive, method): + """Compress an archive object via method. ZIP archives are ignored. If + method is automatic then the following technologies are tried in order: xz, + bz2 and gzip""" + + if method == "zip": + return archive.name() + + methods = ['xz', 'bzip2', 'gzip'] + + if method in ('xz', 'bzip2', 'gzip'): + methods = [method] + + compressed = False + last_error = None + for cmd in ('xz', 'bzip2', 'gzip'): + if compressed: + break + try: + command = shlex.split("%s %s" % (cmd,archive.name())) + p = Popen(command, stdout=PIPE, stderr=PIPE, bufsize=-1) + stdout, stderr = p.communicate() + log = logging.getLogger('sos') + if stdout: + log.info(stdout) + if stderr: + log.error(stderr) + compressed = True + return archive.name() + "." + cmd.replace('ip','') + except Exception, e: + last_error = e + + if not compressed: + raise last_error + + +def shell_out(cmd): + """Uses subprocess.Popen to make a system call and returns stdout. + Does not handle exceptions.""" + p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE) + return p.communicate()[0] +# vim:ts=4 sw=4 et |