219N/A#!/usr/bin/python
219N/A#
219N/A# Copyright (C) 2002 Lars Gustaebel <lars@gustaebel.de>
219N/A# All rights reserved.
219N/A#
219N/A# Permission is hereby granted, free of charge, to any person
219N/A# obtaining a copy of this software and associated documentation
219N/A# files (the "Software"), to deal in the Software without
219N/A# restriction, including without limitation the rights to use,
219N/A# copy, modify, merge, publish, distribute, sublicense, and/or sell
219N/A# copies of the Software, and to permit persons to whom the
219N/A# Software is furnished to do so, subject to the following
219N/A# conditions:
219N/A#
219N/A# The above copyright notice and this permission notice shall be
219N/A# included in all copies or substantial portions of the Software.
219N/A#
219N/A# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
219N/A# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
219N/A# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
219N/A# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
219N/A# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
219N/A# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
219N/A# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
219N/A# OTHER DEALINGS IN THE SOFTWARE.
219N/A#
219N/A"""Read from and write to cpio format archives.
219N/A"""
219N/A
219N/A#
219N/A# Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved.
219N/A#
219N/A
219N/Afrom __future__ import print_function
219N/A
219N/A#---------
219N/A# Imports
219N/A#---------
219N/Aimport sys
219N/Aif sys.version > '3':
219N/A long = int
219N/Aimport os
219N/Aimport stat
219N/Aimport time
219N/Aimport struct
219N/Afrom six.moves import range
219N/Aimport pkg.pkgsubprocess as subprocess
219N/A
219N/A# cpio magic numbers
219N/A# XXX matches actual cpio archives and /etc/magic, but not archives.h
219N/ACMN_ASC = 0o70701 # Cpio Magic Number for ASCII header
219N/ACMN_BIN = 0o70707 # Cpio Magic Number for Binary header
219N/ACMN_BBS = 0o143561 # Cpio Magic Number for Byte-Swap header
219N/ACMN_CRC = 0o70702 # Cpio Magic Number for CRC header
219N/ACMS_ASC = "070701" # Cpio Magic String for ASCII header
219N/ACMS_CHR = "070707" # Cpio Magic String for CHR (-c) header
219N/ACMS_CRC = "070702" # Cpio Magic String for CRC header
219N/ACMS_LEN = 6 # Cpio Magic String length
219N/A
219N/ABLOCKSIZE = 512
219N/A
219N/Aclass CpioError(Exception):
219N/A """Base exception."""
219N/A pass
219N/Aclass ExtractError(CpioError):
219N/A """General exception for extract errors."""
219N/A pass
219N/Aclass ReadError(CpioError):
219N/A """Exception for unreadble cpio archives."""
219N/A pass
219N/Aclass CompressionError(CpioError):
219N/A """Exception for unavailable compression methods."""
219N/A pass
219N/Aclass StreamError(CpioError):
219N/A """Exception for unsupported operations on stream-like CpioFiles."""
219N/A pass
219N/A
219N/A#---------------------------
219N/A# internal stream interface
219N/A#---------------------------
219N/Aclass _LowLevelFile:
219N/A """Low-level file object. Supports reading and writing.
219N/A It is used instead of a regular file object for streaming
219N/A access.
219N/A """
219N/A
219N/A def __init__(self, name, mode):
219N/A mode = {
219N/A "r": os.O_RDONLY,
219N/A "w": os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
219N/A }[mode]
219N/A if hasattr(os, "O_BINARY"):
219N/A mode |= os.O_BINARY
219N/A self.fd = os.open(name, mode)
219N/A
219N/A def close(self):
219N/A os.close(self.fd)
219N/A
219N/A def read(self, size):
219N/A return os.read(self.fd, size)
219N/A
219N/A def write(self, s):
219N/A os.write(self.fd, s)
219N/A
219N/Aclass _Stream:
219N/A """Class that serves as an adapter between CpioFile and
219N/A a stream-like object. The stream-like object only
219N/A needs to have a read() or write() method and is accessed
219N/A blockwise. Use of gzip or bzip2 compression is possible.
219N/A A stream-like object could be for example: sys.stdin,
219N/A sys.stdout, a socket, a tape device etc.
219N/A
219N/A _Stream is intended to be used only internally.
219N/A """
219N/A
219N/A def __init__(self, name, mode, type, fileobj, bufsize):
219N/A """Construct a _Stream object.
219N/A """
219N/A self._extfileobj = True
219N/A if fileobj is None:
219N/A fileobj = _LowLevelFile(name, mode)
219N/A self._extfileobj = False
219N/A
219N/A self.name = name or ""
219N/A self.mode = mode
219N/A self.type = type
219N/A self.fileobj = fileobj
219N/A self.bufsize = bufsize
219N/A self.buf = ""
219N/A self.pos = long(0)
219N/A self.closed = False
219N/A
219N/A if type == "gz":
219N/A try:
219N/A import zlib
219N/A except ImportError:
219N/A raise CompressionError("zlib module is not available")
219N/A self.zlib = zlib
219N/A self.crc = zlib.crc32("")
219N/A if mode == "r":
219N/A self._init_read_gz()
219N/A else:
219N/A self._init_write_gz()
219N/A
219N/A if type == "bz2":
219N/A try:
219N/A import bz2
219N/A except ImportError:
219N/A raise CompressionError("bz2 module is not available")
219N/A if mode == "r":
219N/A self.dbuf = ""
219N/A self.cmp = bz2.BZ2Decompressor()
219N/A else:
219N/A self.cmp = bz2.BZ2Compressor()
219N/A
219N/A def __del__(self):
219N/A if not self.closed:
219N/A self.close()
219N/A
219N/A def _init_write_gz(self):
219N/A """Initialize for writing with gzip compression.
219N/A """
219N/A self.cmp = self.zlib.compressobj(9, self.zlib.DEFLATED,
219N/A -self.zlib.MAX_WBITS, self.zlib.DEF_MEM_LEVEL, 0)
219N/A timestamp = struct.pack("<L", long(time.time()))
219N/A self.__write("\037\213\010\010{0}\002\377".format(timestamp))
219N/A if self.name.endswith(".gz"):
219N/A self.name = self.name[:-3]
219N/A self.__write(self.name + NUL)
219N/A
219N/A def write(self, s):
219N/A """Write string s to the stream.
219N/A """
219N/A if self.type == "gz":
219N/A self.crc = self.zlib.crc32(s, self.crc)
219N/A self.pos += len(s)
219N/A if self.type != "cpio":
219N/A s = self.cmp.compress(s)
219N/A self.__write(s)
219N/A
219N/A def __write(self, s):
219N/A """Write string s to the stream if a whole new block
219N/A is ready to be written.
219N/A """
219N/A self.buf += s
219N/A while len(self.buf) > self.bufsize:
219N/A self.fileobj.write(self.buf[:self.bufsize])
219N/A self.buf = self.buf[self.bufsize:]
219N/A
219N/A def close(self):
219N/A """Close the _Stream object. No operation should be
219N/A done on it afterwards.
219N/A """
219N/A if self.closed:
219N/A return
219N/A
219N/A if self.mode == "w" and self.type != "cpio":
219N/A self.buf += self.cmp.flush()
219N/A if self.mode == "w" and self.buf:
219N/A self.fileobj.write(self.buf)
219N/A self.buf = ""
219N/A if self.type == "gz":
219N/A self.fileobj.write(struct.pack("<l", self.crc))
219N/A self.fileobj.write(struct.pack("<L", self.pos &
219N/A long(0xffffFFFF)))
219N/A if not self._extfileobj:
219N/A self.fileobj.close()
219N/A
219N/A self.closed = True
219N/A
219N/A def _init_read_gz(self):
219N/A """Initialize for reading a gzip compressed fileobj.
219N/A """
219N/A self.cmp = self.zlib.decompressobj(-self.zlib.MAX_WBITS)
219N/A self.dbuf = ""
219N/A
219N/A # taken from gzip.GzipFile with some alterations
219N/A if self.__read(2) != "\037\213":
219N/A raise ReadError("not a gzip file")
219N/A if self.__read(1) != "\010":
219N/A raise CompressionError("unsupported compression method")
219N/A
219N/A flag = ord(self.__read(1))
219N/A self.__read(6)
219N/A
219N/A if flag & 4:
219N/A xlen = ord(self.__read(1)) + 256 * ord(self.__read(1))
219N/A self.read(xlen)
219N/A if flag & 8:
219N/A while True:
219N/A s = self.__read(1)
219N/A if not s or s == NUL:
219N/A break
219N/A if flag & 16:
219N/A while True:
219N/A s = self.__read(1)
219N/A if not s or s == NUL:
219N/A break
219N/A if flag & 2:
219N/A self._read(2)
219N/A
219N/A def tell(self):
219N/A """Return the stream's file pointer position.
219N/A """
219N/A return self.pos
219N/A
219N/A def seek(self, pos=0):
219N/A """Set the stream's file pointer to pos. Negative seeking
219N/A is forbidden.
219N/A """
219N/A if pos - self.pos >= 0:
219N/A blocks, remainder = divmod(pos - self.pos, self.bufsize)
219N/A for i in range(blocks):
219N/A self.read(self.bufsize)
219N/A self.read(remainder)
219N/A else:
219N/A raise StreamError("seeking backwards is not allowed")
219N/A return self.pos
219N/A
219N/A def read(self, size=None):
219N/A """Return the next size number of bytes from the stream.
219N/A If size is not defined, return all bytes of the stream
219N/A up to EOF.
219N/A """
219N/A if size is None:
219N/A t = []
219N/A while True:
219N/A buf = self._read(self.bufsize)
219N/A if not buf:
219N/A break
219N/A t.append(buf)
219N/A buf = "".join(t)
219N/A else:
219N/A buf = self._read(size)
219N/A self.pos += len(buf)
219N/A # print("reading {0} bytes to {1} ({2})".format(size, self.pos, self.fileobj.tell()))
219N/A return buf
219N/A
219N/A def _read(self, size):
219N/A """Return size bytes from the stream.
219N/A """
219N/A if self.type == "cpio":
219N/A return self.__read(size)
219N/A
219N/A c = len(self.dbuf)
219N/A t = [self.dbuf]
219N/A while c < size:
219N/A buf = self.__read(self.bufsize)
219N/A if not buf:
219N/A break
219N/A buf = self.cmp.decompress(buf)
219N/A t.append(buf)
219N/A c += len(buf)
219N/A t = "".join(t)
219N/A self.dbuf = t[size:]
219N/A return t[:size]
219N/A
219N/A def __read(self, size):
219N/A """Return size bytes from stream. If internal buffer is empty,
219N/A read another block from the stream.
219N/A """
219N/A c = len(self.buf)
219N/A t = [self.buf]
219N/A while c < size:
219N/A buf = self.fileobj.read(self.bufsize)
219N/A if not buf:
219N/A break
219N/A t.append(buf)
219N/A c += len(buf)
219N/A t = "".join(t)
219N/A self.buf = t[size:]
219N/A return t[:size]
219N/A# class _Stream
219N/A
219N/A#------------------------
219N/A# Extraction file object
219N/A#------------------------
219N/Aclass ExFileObject(object):
219N/A """File-like object for reading an archive member.
219N/A Is returned by CpioFile.extractfile().
219N/A """
219N/A
219N/A def __init__(self, cpiofile, cpioinfo):
219N/A self.fileobj = cpiofile.fileobj
219N/A self.name = cpioinfo.name
219N/A self.mode = "r"
219N/A self.closed = False
219N/A self.offset = cpioinfo.offset_data
219N/A self.size = cpioinfo.size
219N/A self.pos = long(0)
219N/A self.linebuffer = ""
219N/A
219N/A def read(self, size=None):
219N/A if self.closed:
219N/A raise ValueError("file is closed")
219N/A self.fileobj.seek(self.offset + self.pos)
219N/A bytesleft = self.size - self.pos
219N/A if size is None:
219N/A bytestoread = bytesleft
219N/A else:
219N/A bytestoread = min(size, bytesleft)
219N/A self.pos += bytestoread
219N/A return self.fileobj.read(bytestoread)
219N/A
219N/A def readline(self, size=-1):
219N/A """Read a line with approx. size. If size is negative,
219N/A read a whole line. readline() and read() must not
219N/A be mixed up (!).
219N/A """
219N/A if size < 0:
219N/A size = sys.maxsize
219N/A
219N/A nl = self.linebuffer.find("\n")
219N/A if nl >= 0:
219N/A nl = min(nl, size)
219N/A else:
219N/A size -= len(self.linebuffer)
219N/A while (nl < 0 and size > 0):
219N/A buf = self.read(min(size, 100))
219N/A if not buf:
219N/A break
219N/A self.linebuffer += buf
219N/A size -= len(buf)
219N/A nl = self.linebuffer.find("\n")
219N/A if nl == -1:
219N/A s = self.linebuffer
219N/A self.linebuffer = ""
219N/A return s
219N/A buf = self.linebuffer[:nl]
219N/A self.linebuffer = self.linebuffer[nl + 1:]
219N/A while buf[-1:] == "\r":
219N/A buf = buf[:-1]
219N/A return buf + "\n"
219N/A
219N/A def readlines(self):
219N/A """Return a list with all (following) lines.
219N/A """
219N/A result = []
219N/A while True:
219N/A line = self.readline()
219N/A if not line: break
219N/A result.append(line)
219N/A return result
219N/A
219N/A def tell(self):
219N/A """Return the current file position.
219N/A """
219N/A return self.pos
219N/A
219N/A def seek(self, pos, whence=0):
219N/A """Seek to a position in the file.
219N/A """
219N/A self.linebuffer = ""
219N/A if whence == 0:
219N/A self.pos = min(max(pos, 0), self.size)
219N/A elif whence == 1:
219N/A if pos < 0:
219N/A self.pos = max(self.pos + pos, 0)
219N/A else:
219N/A self.pos = min(self.pos + pos, self.size)
219N/A elif whence == 2:
219N/A self.pos = max(min(self.size + pos, self.size), 0)
219N/A
219N/A def close(self):
219N/A """Close the file object.
219N/A """
219N/A self.closed = True
219N/A#class ExFileObject
219N/A
219N/A#------------------
219N/A# Exported Classes
219N/A#------------------
219N/Aclass CpioInfo(object):
219N/A """Informational class which holds the details about an
219N/A archive member given by a cpio header block.
219N/A CpioInfo objects are returned by CpioFile.getmember(),
219N/A CpioFile.getmembers() and CpioFile.getcpioinfo() and are
219N/A usually created internally.
219N/A """
219N/A
219N/A def __init__(self, name="", cpiofile=None):
219N/A """Construct a CpioInfo object. name is the optional name
219N/A of the member.
219N/A """
219N/A
219N/A self.name = name
219N/A self.mode = 0o666
219N/A self.uid = 0
219N/A self.gid = 0
219N/A self.size = 0
219N/A self.mtime = 0
219N/A self.chksum = 0
219N/A self.type = "0"
219N/A self.linkname = ""
219N/A self.uname = "user"
219N/A self.gname = "group"
219N/A self.devmajor = 0
219N/A self.devminor = 0
219N/A self.prefix = ""
219N/A self.cpiofile = cpiofile
219N/A
219N/A self.offset = 0
219N/A self.offset_data = 0
219N/A self.padding = 1
219N/A
219N/A def __repr__(self):
219N/A return "<{0} {1!r} at {2:#x}>".format(
219N/A self.__class__.__name__, self.name, id(self))
219N/A
219N/A @classmethod
219N/A def frombuf(cls, buf, fileobj, cpiofile=None):
219N/A """Construct a CpioInfo object from a buffer. The buffer should
219N/A be at least 6 octets long to determine the type of archive. The
219N/A rest of the data will be read in on demand.
219N/A """
219N/A cpioinfo = cls(cpiofile=cpiofile)
219N/A
219N/A # Read enough for the ASCII magic
219N/A if buf[:6] == CMS_ASC:
219N/A hdrtype = "CMS_ASC"
219N/A elif buf[:6] == CMS_CHR:
219N/A hdrtype = "CMS_CHR"
219N/A elif buf[:6] == CMS_CRC:
219N/A hdrtype = "CMS_CRC"
219N/A else:
219N/A b = struct.unpack("h", buf[:2])[0]
219N/A if b == CMN_ASC:
219N/A hdrtype = "CMN_ASC"
219N/A elif b == CMN_BIN:
219N/A hdrtype = "CMN_BIN"
219N/A elif b == CMN_BBS:
219N/A hdrtype = "CMN_BBS"
219N/A elif b == CMN_CRC:
219N/A hdrtype = "CMN_CRC"
219N/A else:
219N/A raise ValueError("invalid cpio header")
219N/A
219N/A if hdrtype == "CMN_BIN":
219N/A buf += fileobj.read(26 - len(buf))
219N/A (magic, dev, inode, cpioinfo.mode, cpioinfo.uid,
219N/A cpioinfo.gid, nlink, rdev, cpioinfo.mtime, namesize,
219N/A cpioinfo.size) = struct.unpack("=hhHHHHhhihi", buf[:26])
219N/A buf += fileobj.read(namesize)
219N/A cpioinfo.name = buf[26:26 + namesize - 1]
219N/A # Header is padded to halfword boundaries
219N/A cpioinfo.padding = 2
219N/A cpioinfo.hdrsize = 26 + namesize + (namesize % 2)
219N/A buf += fileobj.read(namesize % 2)
219N/A elif hdrtype == "CMS_ASC":
219N/A buf += fileobj.read(110 - len(buf))
219N/A cpioinfo.mode = int(buf[14:22], 16)
219N/A cpioinfo.uid = int(buf[22:30], 16)
219N/A cpioinfo.gid = int(buf[30:38], 16)
219N/A cpioinfo.mtime = int(buf[46:54], 16)
219N/A cpioinfo.size = int(buf[54:62], 16)
219N/A cpioinfo.devmajor = int(buf[62:70], 16)
219N/A cpioinfo.devminor = int(buf[70:78], 16)
219N/A namesize = int(buf[94:102], 16)
219N/A cpioinfo.chksum = int(buf[102:110], 16)
219N/A buf += fileobj.read(namesize)
219N/A cpioinfo.name = buf[110:110 + namesize - 1]
219N/A cpioinfo.hdrsize = 110 + namesize
219N/A # Pad to the nearest 4 byte block, 0-3 bytes.
219N/A cpioinfo.hdrsize += 4 - ((cpioinfo.hdrsize - 1) % 4) - 1
219N/A buf += fileobj.read(cpioinfo.hdrsize - 110 - namesize)
219N/A cpioinfo.padding = 4
219N/A else:
219N/A raise ValueError("unsupported cpio header")
219N/A
219N/A return cpioinfo
219N/A
219N/A def isreg(self):
219N/A return stat.S_ISREG(self.mode)
219N/A
219N/A # This isn't in tarfile, but it's too useful. It's required
219N/A # modifications to frombuf(), as well as CpioFile.next() to pass the
219N/A # CpioFile object in. I'm not sure that isn't poor OO style.
219N/A def extractfile(self):
219N/A """Return a file-like object which can be read to extract the contents.
219N/A """
219N/A
219N/A if self.isreg():
219N/A return ExFileObject(self.cpiofile, self)
219N/A else:
219N/A return None
219N/A
219N/Aclass CpioFile(object):
219N/A """The CpioFile Class provides an interface to cpio archives.
219N/A """
219N/A
219N/A fileobject = ExFileObject
219N/A
219N/A def __init__(self, name=None, mode="r", fileobj=None, cfobj=None):
219N/A """Open an (uncompressed) cpio archive `name'. `mode' is either 'r' to
219N/A read from an existing archive, 'a' to append data to an existing
219N/A file or 'w' to create a new file overwriting an existing one. `mode'
219N/A defaults to 'r'.
219N/A If `fileobj' is given, it is used for reading or writing data. If it
219N/A can be determined, `mode' is overridden by `fileobj's mode.
219N/A `fileobj' is not closed, when CpioFile is closed.
219N/A """
219N/A self.name = name
219N/A
219N/A if len(mode) > 1 or mode not in "raw":
219N/A raise ValueError("mode must be 'r', 'a' or 'w'")
219N/A self._mode = mode
219N/A self.mode = {"r": "rb", "a": "r+b", "w": "wb"}[mode]
219N/A
219N/A if not fileobj and not cfobj:
219N/A fileobj = open(self.name, self.mode)
219N/A self._extfileobj = False
219N/A else:
219N/A # Copy constructor: just copy fileobj over and reset the
219N/A # _Stream object's idea of where we are back to the
219N/A # beginning. Everything else will be reset normally.
219N/A # XXX clear closed flag?
219N/A if cfobj:
219N/A fileobj = cfobj.fileobj
219N/A fileobj.pos = 0
219N/A if self.name is None and hasattr(fileobj, "name"):
219N/A self.name = fileobj.name
219N/A if hasattr(fileobj, "mode"):
219N/A self.mode = fileobj.mode
219N/A self._extfileobj = True
219N/A self.fileobj = fileobj
219N/A
219N/A # Init datastructures
219N/A self.closed = False
219N/A self.members = [] # list of members as CpioInfo objects
219N/A self._loaded = False # flag if all members have been read
219N/A self.offset = long(0) # current position in the archive file
219N/A
219N/A if self._mode == "r":
219N/A self.firstmember = None
219N/A self.firstmember = next(self)
219N/A
219N/A if self._mode == "a":
219N/A # Move to the end of the archive,
219N/A # before the first empty block.
219N/A self.firstmember = None
219N/A while True:
219N/A try:
219N/A cpioinfo = next(self)
219N/A except ReadError:
219N/A self.fileobj.seek(0)
219N/A break
219N/A if cpioinfo is None:
219N/A self.fileobj.seek(- BLOCKSIZE, 1)
219N/A break
219N/A
219N/A if self._mode in "aw":
219N/A self._loaded = True
219N/A
219N/A #--------------------------------------------------------------------------
219N/A # Below are the classmethods which act as alternate constructors to the
219N/A # CpioFile class. The open() method is the only one that is needed for
219N/A # public use; it is the "super"-constructor and is able to select an
219N/A # adequate "sub"-constructor for a particular compression using the mapping
219N/A # from OPEN_METH.
219N/A #
219N/A # This concept allows one to subclass CpioFile without losing the comfort of
219N/A # the super-constructor. A sub-constructor is registered and made available
219N/A # by adding it to the mapping in OPEN_METH.
219N/A @classmethod
219N/A def open(cls, name=None, mode="r", fileobj=None, bufsize=20*512):
219N/A """Open a cpio archive for reading, writing or appending. Return
219N/A an appropriate CpioFile class.
219N/A
219N/A mode:
219N/A 'r' open for reading with transparent compression
219N/A 'r:' open for reading exclusively uncompressed
219N/A 'r:gz' open for reading with gzip compression
219N/A 'r:bz2' open for reading with bzip2 compression
219N/A 'a' or 'a:' open for appending
219N/A 'w' or 'w:' open for writing without compression
219N/A 'w:gz' open for writing with gzip compression
219N/A 'w:bz2' open for writing with bzip2 compression
219N/A 'r|' open an uncompressed stream of cpio blocks for reading
219N/A 'r|gz' open a gzip compressed stream of cpio blocks
219N/A 'r|bz2' open a bzip2 compressed stream of cpio blocks
219N/A 'w|' open an uncompressed stream for writing
219N/A 'w|gz' open a gzip compressed stream for writing
219N/A 'w|bz2' open a bzip2 compressed stream for writing
219N/A """
219N/A
219N/A if not name and not fileobj:
219N/A raise ValueError("nothing to open")
219N/A
219N/A if ":" in mode:
219N/A filemode, comptype = mode.split(":", 1)
219N/A filemode = filemode or "r"
219N/A comptype = comptype or "cpio"
219N/A
219N/A # Select the *open() function according to
219N/A # given compression.
219N/A if comptype in cls.OPEN_METH:
219N/A func = getattr(cls, cls.OPEN_METH[comptype])
219N/A else:
219N/A raise CompressionError("unknown compression type {0!r}".format(comptype))
219N/A return func(name, filemode, fileobj)
219N/A
219N/A elif "|" in mode:
219N/A filemode, comptype = mode.split("|", 1)
219N/A filemode = filemode or "r"
219N/A comptype = comptype or "cpio"
219N/A
219N/A if filemode not in "rw":
219N/A raise ValueError("mode must be 'r' or 'w'")
219N/A
219N/A t = cls(name, filemode,
219N/A _Stream(name, filemode, comptype, fileobj, bufsize))
219N/A t._extfileobj = False
219N/A return t
219N/A
219N/A elif mode == "r":
219N/A # Find out which *open() is appropriate for opening the file.
219N/A for comptype in cls.OPEN_METH:
219N/A func = getattr(cls, cls.OPEN_METH[comptype])
219N/A try:
219N/A return func(name, "r", fileobj)
219N/A except (ReadError, CompressionError):
219N/A continue
219N/A raise ReadError("file could not be opened successfully")
219N/A
219N/A elif mode in "aw":
219N/A return cls.cpioopen(name, mode, fileobj)
219N/A
219N/A raise ValueError("undiscernible mode")
219N/A
219N/A @classmethod
219N/A def cpioopen(cls, name, mode="r", fileobj=None):
219N/A """Open uncompressed cpio archive name for reading or writing.
219N/A """
219N/A if len(mode) > 1 or mode not in "raw":
219N/A raise ValueError("mode must be 'r', 'a' or 'w'")
219N/A return cls(name, mode, fileobj)
219N/A
219N/A @classmethod
219N/A def gzopen(cls, name, mode="r", fileobj=None, compresslevel=9):
219N/A """Open gzip compressed cpio archive name for reading or writing.
219N/A Appending is not allowed.
219N/A """
219N/A if len(mode) > 1 or mode not in "rw":
219N/A raise ValueError("mode must be 'r' or 'w'")
219N/A
219N/A try:
219N/A import gzip
219N/A gzip.GzipFile
219N/A except (ImportError, AttributeError):
219N/A raise CompressionError("gzip module is not available")
219N/A
219N/A pre, ext = os.path.splitext(name)
219N/A pre = os.path.basename(pre)
219N/A if ext == ".gz":
219N/A ext = ""
219N/A cpioname = pre + ext
219N/A
219N/A if fileobj is None:
219N/A fileobj = open(name, mode + "b")
219N/A
219N/A if mode != "r":
219N/A name = tarname
219N/A
219N/A try:
219N/A t = cls.cpioopen(cpioname, mode,
219N/A gzip.GzipFile(name, mode, compresslevel,
219N/A fileobj))
219N/A except IOError:
219N/A raise ReadError("not a gzip file")
219N/A t._extfileobj = False
219N/A return t
219N/A
219N/A @classmethod
219N/A def bz2open(cls, name, mode="r", fileobj=None, compresslevel=9):
219N/A """Open bzip2 compressed cpio archive name for reading or writing.
219N/A Appending is not allowed.
219N/A """
219N/A if len(mode) > 1 or mode not in "rw":
219N/A raise ValueError("mode must be 'r' or 'w'.")
219N/A
219N/A try:
219N/A import bz2
219N/A except ImportError:
219N/A raise CompressionError("bz2 module is not available")
219N/A
219N/A pre, ext = os.path.splitext(name)
219N/A pre = os.path.basename(pre)
219N/A if ext == ".bz2":
219N/A ext = ""
219N/A cpioname = pre + ext
219N/A
219N/A if fileobj is not None:
219N/A raise ValueError("no support for external file objects")
219N/A
219N/A try:
219N/A t = cls.cpioopen(cpioname, mode,
219N/A bz2.BZ2File(name, mode, compresslevel=compresslevel))
219N/A except IOError:
219N/A raise ReadError("not a bzip2 file")
219N/A t._extfileobj = False
219N/A return t
219N/A
219N/A @classmethod
219N/A def p7zopen(cls, name, mode="r", fileobj=None):
219N/A """Open 7z compressed cpio archive name for reading, writing.
219N/A
219N/A Appending is not allowed
219N/A """
219N/A if len(mode) > 1 or mode not in "rw":
219N/A raise ValueError("mode must be 'r' or 'w'.")
219N/A
219N/A pre, ext = os.path.splitext(name)
219N/A pre = os.path.basename(pre)
219N/A if ext == ".7z":
219N/A ext = ""
219N/A cpioname = pre + ext
219N/A
219N/A try:
219N/A # To extract: 7z e -so <fname>
219N/A # To create an archive: 7z a -si <fname>
219N/A cmd = "7z {0} -{1} {2}".format(
219N/A {'r':'e', 'w':'a'}[mode],
219N/A {'r':'so', 'w':'si'}[mode],
219N/A name)
219N/A p = subprocess.Popen(cmd.split(),
219N/A stdin=subprocess.PIPE,
219N/A stdout=subprocess.PIPE,
219N/A stderr=subprocess.PIPE)
219N/A pobj = p.stdout
219N/A if mode == "w":
219N/A pobj = p.stdin
219N/A
219N/A comptype = "cpio"
219N/A bufsize = 20*512
219N/A
219N/A obj = _Stream(cpioname, mode, comptype, pobj, bufsize)
219N/A t = cls.cpioopen(cpioname, mode, obj)
219N/A except IOError:
219N/A raise ReadError("read/write via 7z failed")
219N/A t._extfileobj = False
219N/A return t
219N/A
219N/A # All *open() methods are registered here.
219N/A OPEN_METH = {
219N/A "cpio": "cpioopen", # uncompressed
219N/A "gz": "gzopen", # gzip compressed
219N/A "bz2": "bz2open", # bzip2 compressed
219N/A "p7z": "p7zopen" # 7z compressed
219N/A }
219N/A
219N/A def getmember(self, name):
219N/A """Return a CpioInfo object for member `name'. If `name' can not be
219N/A found in the archive, KeyError is raised. If a member occurs more
219N/A than once in the archive, its last occurence is assumed to be the
219N/A most up-to-date version.
219N/A """
219N/A cpioinfo = self._getmember(name)
219N/A if cpioinfo is None:
219N/A raise KeyError("filename {0!r} not found".format(name))
219N/A return cpioinfo
219N/A
219N/A def getmembers(self):
219N/A """Return the members of the archive as a list of CpioInfo objects. The
219N/A list has the same order as the members in the archive.
219N/A """
219N/A self._check()
219N/A if not self._loaded: # if we want to obtain a list of
219N/A self._load() # all members, we first have to
219N/A # scan the whole archive.
219N/A return self.members
219N/A
219N/A def __next__(self):
219N/A self._check("ra")
219N/A if self.firstmember is not None:
219N/A m = self.firstmember
219N/A self.firstmember = None
219N/A return m
219N/A
219N/A self.fileobj.seek(self.offset)
219N/A while True:
219N/A # Read in enough for frombuf() to be able to determine
219N/A # what kind of archive it is. It will have to read the
219N/A # rest of the header.
219N/A buf = self.fileobj.read(6)
219N/A if not buf:
219N/A return None
219N/A try:
219N/A cpioinfo = CpioInfo.frombuf(buf, self.fileobj, self)
219N/A except ValueError as e:
219N/A if self.offset == 0:
219N/A raise ReadError("empty, unreadable or compressed file")
219N/A return None
219N/A break
219N/A
219N/A # if cpioinfo.chksum != calc_chksum(buf):
219N/A # self._dbg(1, "cpiofile: Bad Checksum {0!r}".format(cpioinfo.name))
219N/A
219N/A cpioinfo.offset = self.offset
219N/A
219N/A cpioinfo.offset_data = self.offset + cpioinfo.hdrsize
219N/A if cpioinfo.isreg() or cpioinfo.type not in (0,): # XXX SUPPORTED_TYPES?
219N/A self.offset += cpioinfo.hdrsize + cpioinfo.size
219N/A if self.offset % cpioinfo.padding != 0:
219N/A self.offset += cpioinfo.padding - \
219N/A (self.offset % cpioinfo.padding)
219N/A
219N/A if cpioinfo.name == "TRAILER!!!":
219N/A return None
219N/A
219N/A self.members.append(cpioinfo)
219N/A return cpioinfo
219N/A
219N/A next = __next__
219N/A
219N/A def extractfile(self, member):
219N/A self._check("r")
219N/A
219N/A if isinstance(member, CpioInfo):
219N/A cpioinfo = member
219N/A else:
219N/A cpioinfo = self.getmember(member)
219N/A
219N/A if cpioinfo.isreg():
219N/A return self.fileobject(self, cpioinfo)
219N/A # XXX deal with other types
219N/A else:
219N/A return None
219N/A
219N/A def _block(self, count):
219N/A blocks, remainder = divmod(count, BLOCKSIZE)
219N/A if remainder:
219N/A blocks += 1
219N/A return blocks * BLOCKSIZE
219N/A
219N/A def _getmember(self, name, cpioinfo=None):
219N/A members = self.getmembers()
219N/A
219N/A if cpioinfo is None:
219N/A end = len(members)
219N/A else:
219N/A end = members.index(cpioinfo)
219N/A
219N/A for i in range(end - 1, -1, -1):
219N/A if name == members[i].name:
219N/A return members[i]
219N/A
219N/A def _load(self):
219N/A while True:
219N/A cpioinfo = next(self)
219N/A if cpioinfo is None:
219N/A break
219N/A self._loaded = True
219N/A
219N/A def _check(self, mode=None):
219N/A if self.closed:
219N/A raise IOError("{0} is closed".format(
219N/A self.__class__.__name__))
219N/A if mode is not None and self._mode not in mode:
219N/A raise IOError("bad operation for mode {0!r}".format(
219N/A self._mode))
219N/A
219N/A def __iter__(self):
219N/A if self._loaded:
219N/A return iter(self.members)
219N/A else:
219N/A return CpioIter(self)
219N/A
219N/A def find_next_archive(self, padding=512):
219N/A """Find the next cpio archive glommed on to the end of the current one.
219N/A
219N/A Some applications, like Solaris package datastreams, concatenate
219N/A multiple cpio archives together, separated by a bit of padding.
219N/A This routine puts all the file pointers in position to start
219N/A reading from the next archive, which can be done by creating a
219N/A new CpioFile object given the original one as an argument (after
219N/A this routine is called).
219N/A """
219N/A
219N/A bytes = 0
219N/A if self.fileobj.tell() % padding != 0:
219N/A bytes = padding - self.fileobj.tell() % padding
219N/A self.fileobj.seek(self.fileobj.tell() + bytes)
219N/A self.offset += bytes
219N/A
219N/A def get_next_archive(self, padding=512):
219N/A """Return the next cpio archive glommed on to the end of the current one.
219N/A
219N/A Return the CpioFile object based on the repositioning done by
219N/A find_next_archive().
219N/A """
219N/A
219N/A self.find_next_archive(padding)
219N/A return CpioFile(cfobj=self)
219N/A
219N/Aclass CpioIter:
219N/A def __init__(self, cpiofile):
219N/A self.cpiofile = cpiofile
219N/A self.index = 0
219N/A
219N/A def __iter__(self):
219N/A return self
219N/A
219N/A def __next__(self):
219N/A if not self.cpiofile._loaded:
219N/A cpioinfo = next(self.cpiofile)
219N/A if not cpioinfo:
219N/A self.cpiofile._loaded = True
219N/A raise StopIteration
219N/A else:
219N/A try:
219N/A cpioinfo = self.cpiofile.members[self.index]
219N/A except IndexError:
219N/A raise StopIteration
219N/A self.index += 1
219N/A return cpioinfo
219N/A
219N/A next = __next__
219N/A
219N/Adef is_cpiofile(name):
219N/A
219N/A magic = open(name).read(CMS_LEN)
219N/A
219N/A if magic in (CMS_ASC, CMS_CHR, CMS_CRC):
219N/A return True
219N/A elif struct.unpack("h", magic[:2])[0] in \
219N/A (CMN_ASC, CMN_BIN, CMN_BBS, CMN_CRC):
219N/A return True
219N/A
219N/A return False
219N/A
219N/Aif __name__ == "__main__":
219N/A print(is_cpiofile(sys.argv[1]))
219N/A
219N/A cf = CpioFile.open(sys.argv[1])
219N/A print("cpiofile is:", cf)
219N/A
219N/A for ci in cf:
219N/A print("cpioinfo is:", ci)
219N/A print(" mode:", oct(ci.mode))
219N/A print(" uid:", ci.uid)
219N/A print(" gid:", ci.gid)
219N/A print(" mtime:", ci.mtime, "({0})".format(
219N/A time.ctime(ci.mtime)))
219N/A print(" size:", ci.size)
219N/A print(" name:", ci.name)
219N/A # f = cf.extractfile(ci)
219N/A # for l in f.readlines():
219N/A # print(l, end=" ")
219N/A # f.close()
219N/A