diff options
-rw-r--r-- | sos/archive.py | 273 | ||||
-rw-r--r-- | sos/policies/__init__.py | 2 | ||||
-rw-r--r-- | sos/policies/windows.py | 2 | ||||
-rw-r--r-- | sos/sosreport.py | 2 | ||||
-rw-r--r-- | sos/utilities.py | 241 |
5 files changed, 276 insertions, 244 deletions
diff --git a/sos/archive.py b/sos/archive.py new file mode 100644 index 00000000..74f932c8 --- /dev/null +++ b/sos/archive.py @@ -0,0 +1,273 @@ +## Copyright (C) 2012 Red Hat, Inc., +## Jesse Jaggars <jjaggars@redhat.com> +## Bryn M. Reeves <bmr@redhat.com> +## +### 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. +import os +import time +import tempfile +import tarfile +import zipfile +import shutil +import logging +import shlex +# required for compression callout (FIXME: move to policy?) +from subprocess import Popen, PIPE, STDOUT + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +class Archive(object): + + _name = "unset" + + def prepend(self, src): + if src: + name = os.path.split(self._name)[-1] + renamed = os.path.join(name, src.lstrip(os.sep)) + return renamed + + def add_link(self, dest, link_name): + pass + + def compress(self, 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""" + + self.close() + +class TarFileArchive(Archive): + + def __init__(self, name): + self._name = name + self._suffix = "tar" + self.tarfile = tarfile.open(self.name(), + mode="w", format=tarfile.PAX_FORMAT) + + # this can be used to set permissions if using the + # tarfile.add() interface to add directory trees. + def copy_permissions_filter(self, tar_info): + orig_path = tar_info.name[len(os.path.split(self._name)[-1]):] + fstat = os.stat(orig_path) + context = self.get_selinux_context(orig_path) + if(context): + tar_info.pax_headers['RHT.security.selinux'] = context + self.set_tar_info_from_stat(tar_info,fstat) + return tar_info + + def get_selinux_context(self, path): + try: + (rc, c) = selinux.getfilecon(path) + return c + except: + return None + + def set_tar_info_from_stat(self, tar_info, fstat, mode=None): + tar_info.mtime = fstat.st_mtime + tar_info.pax_headers['atime'] = "%.9f" % fstat.st_atime + tar_info.pax_headers['ctime'] = "%.9f" % fstat.st_ctime + if mode: + tar_info.mode = mode + else: + tar_info.mode = fstat.st_mode + tar_info.uid = fstat.st_uid + tar_info.gid = fstat.st_gid + + def name(self): + return "%s.%s" % (self._name, self._suffix) + + def add_parent(self, path): + path = os.path.split(path)[0] + self.add_file(path) + + def add_file(self, src, dest=None): + if dest: + dest = self.prepend(dest) + else: + dest = self.prepend(src) + + if dest in self.tarfile.getnames(): + return + if src != '/': + self.add_parent(src) + + tar_info = tarfile.TarInfo(name=dest) + + if os.path.isdir(src): + tar_info.type = tarfile.DIRTYPE + fileobj = None + else: + try: + fp = open(src, 'rb') + content = fp.read() + fp.close() + except: + # files with read permissions that cannot be read may exist + # in /proc, /sys and other virtual file systems. + content = "" + tar_info.size = len(content) + fileobj = StringIO(content) + + # FIXME: handle this at a higher level? + if src.startswith("/sys/") or src.startswith ("/proc/"): + context = None + else: + context = self.get_selinux_context(src) + if context: + tar_info.pax_headers['RHT.security.selinux'] = context + + fstat = os.stat(src) + if os.path.isdir(src) and not (fstat.st_mode & 000200): + # directories not writable by their owner are a world of pain + # in tar archives. Do not allow them (see Issue #85). + mode = fstat.st_mode | 000200 + else: + mode = None + self.set_tar_info_from_stat(tar_info,fstat, mode) + self.tarfile.addfile(tar_info, fileobj) + + def add_string(self, content, dest): + fstat = None + if os.path.exists(dest): + fstat = os.stat(dest) + dest = self.prepend(dest) + tar_info = tarfile.TarInfo(name=dest) + tar_info.size = len(content) + if fstat: + context = self.get_selinux_context(dest) + if context: + tar_info.pax_headers['RHT.security.selinux'] = context + self.set_tar_info_from_stat(tar_info, fstat) + else: + tar_info.mtime = time.time() + self.tarfile.addfile(tar_info, StringIO(content)) + + def add_link(self, dest, link_name): + tar_info = tarfile.TarInfo(name=self.prepend(link_name)) + tar_info.type = tarfile.SYMTYPE + tar_info.linkname = dest + tar_info.mtime = time.time() + self.tarfile.addfile(tar_info, None) + + 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() + + def compress(self, method): + super(TarFileArchive, self).compress(method) + + methods = ['xz', 'bzip2', 'gzip'] + + if method in methods: + methods = [method] + + last_error = Exception("compression failed for an unknown reason") + log = logging.getLogger('sos') + + for cmd in methods: + try: + command = shlex.split("%s %s" % (cmd,self.name())) + p = Popen(command, stdout=PIPE, stderr=PIPE, bufsize=-1) + stdout, stderr = p.communicate() + if stdout: + log.info(stdout) + if stderr: + log.error(stderr) + self._suffix += "." + cmd.replace('ip', '') + return self.name() + except Exception, e: + last_error = e + else: + raise last_error + + +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 compress(self, method): + super(ZipFileArchive, self).compress(method) + return self.name() + + def add_file(self, src, dest=None): + src = str(src) + if dest: + dest = str(dest) + + 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 = "/".join((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() + + diff --git a/sos/policies/__init__.py b/sos/policies/__init__.py index f245ac2f..4a4623aa 100644 --- a/sos/policies/__init__.py +++ b/sos/policies/__init__.py @@ -160,7 +160,7 @@ No changes will be made to your system. """ Return the class object of the prefered archive format for this platform """ - from sos.utilities import TarFileArchive + from sos.archive import TarFileArchive return TarFileArchive def getArchiveName(self): diff --git a/sos/policies/windows.py b/sos/policies/windows.py index abb46494..6b7b0e00 100644 --- a/sos/policies/windows.py +++ b/sos/policies/windows.py @@ -41,5 +41,5 @@ class WindowsPolicy(Policy): return username.strip() in admins def preferedArchive(self): - from sos.utilities import ZipFileArchive + from sos.archive import ZipFileArchive return ZipFileArchive diff --git a/sos/sosreport.py b/sos/sosreport.py index 6c0e8433..ec943988 100644 --- a/sos/sosreport.py +++ b/sos/sosreport.py @@ -50,7 +50,7 @@ import tempfile from sos import _sos as _ from sos import __version__ import sos.policies -from sos.utilities import TarFileArchive, ZipFileArchive +from sos.archive import TarFileArchive, ZipFileArchive from sos.reporting import Report, Section, Command, CopiedFile, CreatedFile, Alert, Note, PlainTextReport class TempFileUtil(object): diff --git a/sos/utilities.py b/sos/utilities.py index cf079ff5..d287768e 100644 --- a/sos/utilities.py +++ b/sos/utilities.py @@ -29,7 +29,6 @@ import inspect from stat import * #from itertools import * from subprocess import Popen, PIPE, STDOUT -import shlex import logging import zipfile import tarfile @@ -182,246 +181,6 @@ def shell_out(cmd): Does not handle exceptions.""" return sosGetCommandOutput(cmd)[1] -class Archive(object): - - _name = "unset" - - def prepend(self, src): - if src: - name = os.path.split(self._name)[-1] - renamed = os.path.join(name, src.lstrip(os.sep)) - return renamed - - def add_link(self, dest, link_name): - pass - - def compress(self, 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""" - - self.close() - -class TarFileArchive(Archive): - - def __init__(self, name): - self._name = name - self._suffix = "tar" - self.tarfile = tarfile.open(self.name(), - mode="w", format=tarfile.PAX_FORMAT) - - # this can be used to set permissions if using the - # tarfile.add() interface to add directory trees. - def copy_permissions_filter(self, tar_info): - orig_path = tar_info.name[len(os.path.split(self._name)[-1]):] - fstat = os.stat(orig_path) - context = self.get_selinux_context(orig_path) - if(context): - tar_info.pax_headers['RHT.security.selinux'] = context - self.set_tar_info_from_stat(tar_info,fstat) - return tar_info - - def get_selinux_context(self, path): - try: - (rc, c) = selinux.getfilecon(path) - return c - except: - return None - - def set_tar_info_from_stat(self, tar_info, fstat, mode=None): - tar_info.mtime = fstat.st_mtime - tar_info.pax_headers['atime'] = "%.9f" % fstat.st_atime - tar_info.pax_headers['ctime'] = "%.9f" % fstat.st_ctime - if mode: - tar_info.mode = mode - else: - tar_info.mode = fstat.st_mode - tar_info.uid = fstat.st_uid - tar_info.gid = fstat.st_gid - - def name(self): - return "%s.%s" % (self._name, self._suffix) - - def add_parent(self, path): - path = os.path.split(path)[0] - self.add_file(path) - - def add_file(self, src, dest=None): - if dest: - dest = self.prepend(dest) - else: - dest = self.prepend(src) - - if dest in self.tarfile.getnames(): - return - if src != '/': - self.add_parent(src) - - tar_info = tarfile.TarInfo(name=dest) - - if os.path.isdir(src): - tar_info.type = tarfile.DIRTYPE - fileobj = None - else: - try: - fp = open(src, 'rb') - content = fp.read() - fp.close() - except: - # files with read permissions that cannot be read may exist - # in /proc, /sys and other virtual file systems. - content = "" - tar_info.size = len(content) - fileobj = StringIO(content) - - # FIXME: handle this at a higher level? - if src.startswith("/sys/") or src.startswith ("/proc/"): - context = None - else: - context = self.get_selinux_context(src) - if context: - tar_info.pax_headers['RHT.security.selinux'] = context - - fstat = os.stat(src) - if os.path.isdir(src) and not (fstat.st_mode & 000200): - # directories not writable by their owner are a world of pain - # in tar archives. Do not allow them (see Issue #85). - mode = fstat.st_mode | 000200 - else: - mode = None - self.set_tar_info_from_stat(tar_info,fstat, mode) - self.tarfile.addfile(tar_info, fileobj) - - def add_string(self, content, dest): - fstat = None - if os.path.exists(dest): - fstat = os.stat(dest) - dest = self.prepend(dest) - tar_info = tarfile.TarInfo(name=dest) - tar_info.size = len(content) - if fstat: - context = self.get_selinux_context(dest) - if context: - tar_info.pax_headers['RHT.security.selinux'] = context - self.set_tar_info_from_stat(tar_info, fstat) - else: - tar_info.mtime = time.time() - self.tarfile.addfile(tar_info, StringIO(content)) - - def add_link(self, dest, link_name): - tar_info = tarfile.TarInfo(name=self.prepend(link_name)) - tar_info.type = tarfile.SYMTYPE - tar_info.linkname = dest - tar_info.mtime = time.time() - self.tarfile.addfile(tar_info, None) - - 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() - - def compress(self, method): - super(TarFileArchive, self).compress(method) - - methods = ['xz', 'bzip2', 'gzip'] - - if method in methods: - methods = [method] - - last_error = Exception("compression failed for an unknown reason") - log = logging.getLogger('sos') - - for cmd in methods: - try: - command = shlex.split("%s %s" % (cmd,self.name())) - p = Popen(command, stdout=PIPE, stderr=PIPE, bufsize=-1) - stdout, stderr = p.communicate() - if stdout: - log.info(stdout) - if stderr: - log.error(stderr) - self._suffix += "." + cmd.replace('ip', '') - return self.name() - except Exception, e: - last_error = e - else: - raise last_error - - -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 compress(self, method): - super(ZipFileArchive, self).compress(method) - return self.name() - - def add_file(self, src, dest=None): - src = str(src) - if dest: - dest = str(dest) - - 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 = "/".join((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() - - class DirTree(object): """Builds an ascii representation of a directory structure""" |