aboutsummaryrefslogblamecommitdiffstats
path: root/sos/archive.py
blob: 74f932c83a24952e6eff255f4359e32f168b1e20 (plain) (tree)
















































































































































































































































































                                                                                           
## 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()