class TarFile(object):
"""
The TarFile Class provides an interface to tar archives.
"""
debug = 0
#
May be set from 0 (no msgs) to 3 (all msgs)
dereference = False
#
If true, add content of linked file to the
#
tar file, else the link.
ignore_zeros = False
#
If true, skips empty or invalid blocks and
#
continues processing.
errorlevel = 1
#
If 0, fatal errors only appear in debug
#
messages (if debug >= 0). If > 0, errors
#
are passed to the caller as exceptions.
format = DEFAULT_FORMAT
#
The format to use when creating an archive.
encoding = ENCODING
#
Encoding for 8-bit character strings.
errors = None
#
Error handler for unicode conversion.
tarinfo = TarInfo
#
The default TarInfo class to use.
fileobject = ExFileObject
#
The default ExFileObject class to use.
def
__init__(self, name=None, mode=
"
r
", fileobj=None, format=None,
tarinfo=None, dereference=None, ignore_zeros=None, encoding=None,
errors=None, pax_headers=None, debug=None, errorlevel=None):
"""
Open an (uncompressed) tar archive `name'. `mode' is either 'r' to
read from an existing archive, 'a' to append data to an existing
file or 'w' to create a new file overwriting an existing one. `mode'
defaults to 'r'.
If `fileobj' is given, it is used for reading or writing data. If it
can be determined, `mode' is overridden by `fileobj's mode.
`fileobj' is not closed, when TarFile is closed.
"""
modes = {
"
r
":
"
rb
",
"
a
":
"
r+b
",
"
w
":
"
wb
"}
if mode
not
in modes:
raise ValueError(
"
mode must be 'r', 'a' or 'w'
")
self.mode = mode
self._mode = modes[mode]
if
not fileobj:
if self.mode ==
"
a
"
and
not os.path.exists(name):
#
Create nonexistent files in append mode.
self.mode =
"
w
"
self._mode =
"
wb
"
fileobj = bltn_open(name, self._mode)
self._extfileobj = False
else:
if name
is None
and hasattr(fileobj,
"
name
"):
name = fileobj.name
if hasattr(fileobj,
"
mode
"):
self._mode = fileobj.mode
self._extfileobj = True
self.name = os.path.abspath(name)
if name
else None
self.fileobj = fileobj
#
Init attributes.
if format
is
not None:
self.format = format
if tarinfo
is
not None:
self.tarinfo = tarinfo
if dereference
is
not None:
self.dereference = dereference
if ignore_zeros
is
not None:
self.ignore_zeros = ignore_zeros
if encoding
is
not None:
self.encoding = encoding
if errors
is
not None:
self.errors = errors
elif mode ==
"
r
":
self.errors =
"
utf-8
"
else:
self.errors =
"
strict
"
if pax_headers
is
not None
and self.format == PAX_FORMAT:
self.pax_headers = pax_headers
else:
self.pax_headers = {}
if debug
is
not None:
self.debug = debug
if errorlevel
is
not None:
self.errorlevel = errorlevel
#
Init datastructures.
self.closed = False
self.members = []
#
list of members as TarInfo objects
self._loaded = False
#
flag if all members have been read
self.offset = self.fileobj.tell()
#
current position in the archive file
self.inodes = {}
#
dictionary caching the inodes of
#
archive members already added
try:
if self.mode ==
"
r
":
self.firstmember = None
self.firstmember = self.next()
if self.mode ==
"
a
":
#
Move to the end of the archive,
#
before the first empty block.
while True:
self.fileobj.seek(self.offset)
try:
tarinfo = self.tarinfo.fromtarfile(self)
self.members.append(tarinfo)
except EOFHeaderError:
self.fileobj.seek(self.offset)
break
except HeaderError, e:
raise ReadError(str(e))
if self.mode
in
"
aw
":
self._loaded = True
if self.pax_headers:
buf = self.tarinfo.create_pax_global_header(self.pax_headers.copy())
self.fileobj.write(buf)
self.offset += len(buf)
except:
if
not self._extfileobj:
self.fileobj.close()
self.closed = True
raise
def _getposix(self):
return self.format == USTAR_FORMAT
def _setposix(self, value):
import warnings
warnings.warn(
"
use the format attribute instead
", DeprecationWarning,
2)
if value:
self.format = USTAR_FORMAT
else:
self.format = GNU_FORMAT
posix = property(_getposix, _setposix)
#
--------------------------------------------------------------------------
#
Below are the classmethods which act as alternate constructors to the
#
TarFile class. The open() method is the only one that is needed for
#
public use; it is the "super"-constructor and is able to select an
#
adequate "sub"-constructor for a particular compression using the mapping
#
from OPEN_METH.
#
#
This concept allows one to subclass TarFile without losing the comfort of
#
the super-constructor. A sub-constructor is registered and made available
#
by adding it to the mapping in OPEN_METH.
@classmethod
def open(cls, name=None, mode=
"
r
", fileobj=None, bufsize=RECORDSIZE, **kwargs):
"""
Open a tar archive for reading, writing or appending. Return
an appropriate TarFile class.
mode:
'r' or 'r:*' open for reading with transparent compression
'r:' open for reading exclusively uncompressed
'r:gz' open for reading with gzip compression
'r:bz2' open for reading with bzip2 compression
'a' or 'a:' open for appending, creating the file if necessary
'w' or 'w:' open for writing without compression
'w:gz' open for writing with gzip compression
'w:bz2' open for writing with bzip2 compression
'r|*' open a stream of tar blocks with transparent compression
'r|' open an uncompressed stream of tar blocks for reading
'r|gz' open a gzip compressed stream of tar blocks
'r|bz2' open a bzip2 compressed stream of tar blocks
'w|' open an uncompressed stream for writing
'w|gz' open a gzip compressed stream for writing
'w|bz2' open a bzip2 compressed stream for writing
"""
if
not name
and
not fileobj:
raise ValueError(
"
nothing to open
")
if mode
in (
"
r
",
"
r:*
"):
#
Find out which *open() is appropriate for opening the file.
for comptype
in cls.OPEN_METH:
func = getattr(cls, cls.OPEN_METH[comptype])
if fileobj
is
not None:
saved_pos = fileobj.tell()
try:
return func(name,
"
r
", fileobj, **kwargs)
except (ReadError, CompressionError), e:
if fileobj
is
not None:
fileobj.seek(saved_pos)
continue
raise ReadError(
"
file could not be opened successfully
")
elif
"
:
"
in mode:
filemode, comptype = mode.split(
"
:
", 1)
filemode = filemode
or
"
r
"
comptype = comptype
or
"
tar
"
#
Select the *open() function according to
#
given compression.
if comptype
in cls.OPEN_METH:
func = getattr(cls, cls.OPEN_METH[comptype])
else:
raise CompressionError(
"
unknown compression type %r
" % comptype)
return func(name, filemode, fileobj, **kwargs)
elif
"
|
"
in mode:
filemode, comptype = mode.split(
"
|
", 1)
filemode = filemode
or
"
r
"
comptype = comptype
or
"
tar
"
if filemode
not
in (
"
r
",
"
w
"):
raise ValueError(
"
mode must be 'r' or 'w'
")
stream = _Stream(name, filemode, comptype, fileobj, bufsize)
try:
t = cls(name, filemode, stream, **kwargs)
except:
stream.close()
raise
t._extfileobj = False
return t
elif mode
in (
"
a
",
"
w
"):
return cls.taropen(name, mode, fileobj, **kwargs)
raise ValueError(
"
undiscernible mode
")
@classmethod
def taropen(cls, name, mode=
"
r
", fileobj=None, **kwargs):
"""
Open uncompressed tar archive name for reading or writing.
"""
if mode
not
in (
"
r
",
"
a
",
"
w
"):
raise ValueError(
"
mode must be 'r', 'a' or 'w'
")
return cls(name, mode, fileobj, **kwargs)
@classmethod
def gzopen(cls, name, mode=
"
r
", fileobj=None, compresslevel=9, **kwargs):
"""
Open gzip compressed tar archive name for reading or writing.
Appending is not allowed.
"""
if mode
not
in (
"
r
",
"
w
"):
raise ValueError(
"
mode must be 'r' or 'w'
")
try:
import gzip
gzip.GzipFile
except (ImportError, AttributeError):
raise CompressionError(
"
gzip module is not available
")
try:
fileobj = gzip.GzipFile(name, mode, compresslevel, fileobj)
except OSError:
if fileobj
is
not None
and mode ==
'
r
':
raise ReadError(
"
not a gzip file
")
raise
try:
t = cls.taropen(name, mode, fileobj, **kwargs)
except IOError:
fileobj.close()
if mode ==
'
r
':
raise ReadError(
"
not a gzip file
")
raise
except:
fileobj.close()
raise
t._extfileobj = False
return t
@classmethod
def bz2open(cls, name, mode=
"
r
", fileobj=None, compresslevel=9, **kwargs):
"""
Open bzip2 compressed tar archive name for reading or writing.
Appending is not allowed.
"""
if mode
not
in (
"
r
",
"
w
"):
raise ValueError(
"
mode must be 'r' or 'w'.
")
try:
import bz2
except ImportError:
raise CompressionError(
"
bz2 module is not available
")
if fileobj
is
not None:
fileobj = _BZ2Proxy(fileobj, mode)
else:
fileobj = bz2.BZ2File(name, mode, compresslevel=compresslevel)
try:
t = cls.taropen(name, mode, fileobj, **kwargs)
except (IOError, EOFError):
fileobj.close()
if mode ==
'
r
':
raise ReadError(
"
not a bzip2 file
")
raise
except:
fileobj.close()
raise
t._extfileobj = False
return t
#
All *open() methods are registered here.
OPEN_METH = {
"
tar
":
"
taropen
",
#
uncompressed tar
"
gz
":
"
gzopen
",
#
gzip compressed tar
"
bz2
":
"
bz2open
"
#
bzip2 compressed tar
}
#
--------------------------------------------------------------------------
#
The public methods which TarFile provides:
def close(self):
"""
Close the TarFile. In write-mode, two finishing zero blocks are
appended to the archive.
"""
if self.closed:
return
if self.mode
in
"
aw
":
self.fileobj.write(NUL * (BLOCKSIZE * 2))
self.offset += (BLOCKSIZE * 2)
#
fill up the end with zero-blocks
#
(like option -b20 for tar does)
blocks, remainder = divmod(self.offset, RECORDSIZE)
if remainder > 0:
self.fileobj.write(NUL * (RECORDSIZE - remainder))
if
not self._extfileobj:
self.fileobj.close()
self.closed = True
def getmember(self, name):
"""
Return a TarInfo object for member `name'. If `name' can not be
found in the archive, KeyError is raised. If a member occurs more
than once in the archive, its last occurrence is assumed to be the
most up-to-date version.
"""
tarinfo = self._getmember(name)
if tarinfo
is None:
raise KeyError(
"
filename %r not found
" % name)
return tarinfo
def getmembers(self):
"""
Return the members of the archive as a list of TarInfo objects. The
list has the same order as the members in the archive.
"""
self._check()
if
not self._loaded:
#
if we want to obtain a list of
self._load()
#
all members, we first have to
#
scan the whole archive.
return self.members
def getnames(self):
"""
Return the members of the archive as a list of their names. It has
the same order as the list returned by getmembers().
"""
return [tarinfo.name
for tarinfo
in self.getmembers()]
def gettarinfo(self, name=None, arcname=None, fileobj=None):
"""
Create a TarInfo object for either the file `name' or the file
object `fileobj' (using os.fstat on its file descriptor). You can
modify some of the TarInfo's attributes before you add it using
addfile(). If given, `arcname' specifies an alternative name for the
file in the archive.
"""
self._check(
"
aw
")
#
When fileobj is given, replace name by
#
fileobj's real name.
if fileobj
is
not None:
name = fileobj.name
#
Building the name of the member in the archive.
#
Backward slashes are converted to forward slashes,
#
Absolute paths are turned to relative paths.
if arcname
is None:
arcname = name
drv, arcname = os.path.splitdrive(arcname)
arcname = arcname.replace(os.sep,
"
/
")
arcname = arcname.lstrip(
"
/
")
#
Now, fill the TarInfo object with
#
information specific for the file.
tarinfo = self.tarinfo()
tarinfo.tarfile = self
#
Use os.stat or os.lstat, depending on platform
#
and if symlinks shall be resolved.
if fileobj
is None:
if hasattr(os,
"
lstat
")
and
not self.dereference:
statres = os.lstat(name)
else:
statres = os.stat(name)
else:
statres = os.fstat(fileobj.fileno())
linkname =
""
stmd = statres.st_mode
if stat.S_ISREG(stmd):
inode = (statres.st_ino, statres.st_dev)
if
not self.dereference
and statres.st_nlink > 1
and \
inode
in self.inodes
and arcname != self.inodes[inode]:
#
Is it a hardlink to an already
#
archived file?
type = LNKTYPE
linkname = self.inodes[inode]
else:
#
The inode is added only if its valid.
#
For win32 it is always 0.
type = REGTYPE
if inode[0]:
self.inodes[inode] = arcname
elif stat.S_ISDIR(stmd):
type = DIRTYPE
elif stat.S_ISFIFO(stmd):
type = FIFOTYPE
elif stat.S_ISLNK(stmd):
type = SYMTYPE
linkname = os.readlink(name)
elif stat.S_ISCHR(stmd):
type = CHRTYPE
elif stat.S_ISBLK(stmd):
type = BLKTYPE
else:
return None
#
Fill the TarInfo object with all
#
information we can get.
tarinfo.name = arcname
tarinfo.mode = stmd
tarinfo.uid = statres.st_uid
tarinfo.gid = statres.st_gid
if type == REGTYPE:
tarinfo.size = statres.st_size
else:
tarinfo.size = 0L
tarinfo.mtime = statres.st_mtime
tarinfo.type = type
tarinfo.linkname = linkname
if pwd:
try:
tarinfo.uname = pwd.getpwuid(tarinfo.uid)[0]
except KeyError:
pass
if grp:
try:
tarinfo.gname = grp.getgrgid(tarinfo.gid)[0]
except KeyError:
pass
if type
in (CHRTYPE, BLKTYPE):
if hasattr(os,
"
major
")
and hasattr(os,
"
minor
"):
tarinfo.devmajor = os.major(statres.st_rdev)
tarinfo.devminor = os.minor(statres.st_rdev)
return tarinfo
def list(self, verbose=True):
"""
Print a table of contents to sys.stdout. If `verbose' is False, only
the names of the members are printed. If it is True, an `ls -l'-like
output is produced.
"""
self._check()
for tarinfo
in self:
if verbose:
print filemode(tarinfo.mode),
print
"
%s/%s
" % (tarinfo.uname
or tarinfo.uid,
tarinfo.gname
or tarinfo.gid),
if tarinfo.ischr()
or tarinfo.isblk():
print
"
%10s
" % (
"
%d,%d
" \
% (tarinfo.devmajor, tarinfo.devminor)),
else:
print
"
%10d
" % tarinfo.size,
print
"
%d-%02d-%02d %02d:%02d:%02d
" \
% time.localtime(tarinfo.mtime)[:6],
print tarinfo.name + (
"
/
"
if tarinfo.isdir()
else
""),
if verbose:
if tarinfo.issym():
print
"
->
", tarinfo.linkname,
if tarinfo.islnk():
print
"
link to
", tarinfo.linkname,
print
def add(self, name, arcname=None, recursive=True, exclude=None, filter=None):
"""
Add the file `name' to the archive. `name' may be any type of file
(directory, fifo, symbolic link, etc.). If given, `arcname'
specifies an alternative name for the file in the archive.
Directories are added recursively by default. This can be avoided by
setting `recursive' to False. `exclude' is a function that should
return True for each filename to be excluded. `filter' is a function
that expects a TarInfo object argument and returns the changed
TarInfo object, if it returns None the TarInfo object will be
excluded from the archive.
"""
self._check(
"
aw
")
if arcname
is None:
arcname = name
#
Exclude pathnames.
if exclude
is
not None:
import warnings
warnings.warn(
"
use the filter argument instead
",
DeprecationWarning, 2)
if exclude(name):
self._dbg(2,
"
tarfile: Excluded %r
" % name)
return
#
Skip if somebody tries to archive the archive...
if self.name
is
not None
and os.path.abspath(name) == self.name:
self._dbg(2,
"
tarfile: Skipped %r
" % name)
return
self._dbg(1, name)
#
Create a TarInfo object from the file.
tarinfo = self.gettarinfo(name, arcname)
if tarinfo
is None:
self._dbg(1,
"
tarfile: Unsupported type %r
" % name)
return
#
Change or exclude the TarInfo object.
if filter
is
not None:
tarinfo = filter(tarinfo)
if tarinfo
is None:
self._dbg(2,
"
tarfile: Excluded %r
" % name)
return
#
Append the tar header and data to the archive.
if tarinfo.isreg():
with bltn_open(name,
"
rb
") as f:
self.addfile(tarinfo, f)
elif tarinfo.isdir():
self.addfile(tarinfo)
if recursive:
for f
in os.listdir(name):
self.add(os.path.join(name, f), os.path.join(arcname, f),
recursive, exclude, filter)
else:
self.addfile(tarinfo)
def addfile(self, tarinfo, fileobj=None):
"""
Add the TarInfo object `tarinfo' to the archive. If `fileobj' is
given, tarinfo.size bytes are read from it and added to the archive.
You can create TarInfo objects using gettarinfo().
On Windows platforms, `fileobj' should always be opened with mode
'rb' to avoid irritation about the file size.
"""
self._check(
"
aw
")
tarinfo = copy.copy(tarinfo)
buf = tarinfo.tobuf(self.format, self.encoding, self.errors)
self.fileobj.write(buf)
self.offset += len(buf)
#
If there's data to follow, append it.
if fileobj
is
not None:
copyfileobj(fileobj, self.fileobj, tarinfo.size)
blocks, remainder = divmod(tarinfo.size, BLOCKSIZE)
if remainder > 0:
self.fileobj.write(NUL * (BLOCKSIZE - remainder))
blocks += 1
self.offset += blocks * BLOCKSIZE
self.members.append(tarinfo)
def extractall(self, path=
"
.
", members=None):
"""
Extract all members from the archive to the current working
directory and set owner, modification time and permissions on
directories afterwards. `path' specifies a different directory
to extract to. `members' is optional and must be a subset of the
list returned by getmembers().
"""
directories = []
if members
is None:
members = self
for tarinfo
in members:
if tarinfo.isdir():
#
Extract directories with a safe mode.
directories.append(tarinfo)
tarinfo = copy.copy(tarinfo)
tarinfo.mode = 0700
self.extract(tarinfo, path)
#
Reverse sort directories.
directories.sort(key=operator.attrgetter(
'
name
'))
directories.reverse()
#
Set correct owner, mtime and filemode on directories.
for tarinfo
in directories:
dirpath = os.path.join(path, tarinfo.name)
try:
self.chown(tarinfo, dirpath)
self.utime(tarinfo, dirpath)
self.chmod(tarinfo, dirpath)
except ExtractError, e:
if self.errorlevel > 1:
raise
else:
self._dbg(1,
"
tarfile: %s
" % e)
def extract(self, member, path=
""):
"""
Extract a member from the archive to the current working directory,
using its full name. Its file information is extracted as accurately
as possible. `member' may be a filename or a TarInfo object. You can
specify a different directory using `path'.
"""
self._check(
"
r
")
if isinstance(member, basestring):
tarinfo = self.getmember(member)
else:
tarinfo = member
#
Prepare the link target for makelink().
if tarinfo.islnk():
tarinfo._link_target = os.path.join(path, tarinfo.linkname)
try:
self._extract_member(tarinfo, os.path.join(path, tarinfo.name))
except EnvironmentError, e:
if self.errorlevel > 0:
raise
else:
if e.filename
is None:
self._dbg(1,
"
tarfile: %s
" % e.strerror)
else:
self._dbg(1,
"
tarfile: %s %r
" % (e.strerror, e.filename))
except ExtractError, e:
if self.errorlevel > 1:
raise
else:
self._dbg(1,
"
tarfile: %s
" % e)
def extractfile(self, member):
"""
Extract a member from the archive as a file object. `member' may be
a filename or a TarInfo object. If `member' is a regular file, a
file-like object is returned. If `member' is a link, a file-like
object is constructed from the link's target. If `member' is none of
the above, None is returned.
The file-like object is read-only and provides the following
methods: read(), readline(), readlines(), seek() and tell()
"""
self._check(
"
r
")
if isinstance(member, basestring):
tarinfo = self.getmember(member)
else:
tarinfo = member
if tarinfo.isreg():
return self.fileobject(self, tarinfo)
elif tarinfo.type
not
in SUPPORTED_TYPES:
#
If a member's type is unknown, it is treated as a
#
regular file.
return self.fileobject(self, tarinfo)
elif tarinfo.islnk()
or tarinfo.issym():
if isinstance(self.fileobj, _Stream):
#
A small but ugly workaround for the case that someone tries
#
to extract a (sym)link as a file-object from a non-seekable
#
stream of tar blocks.
raise StreamError(
"
cannot extract (sym)link as file object
")
else:
#
A (sym)link's file object is its target's file object.
return self.extractfile(self._find_link_target(tarinfo))
else:
#
If there's no data associated with the member (directory, chrdev,
#
blkdev, etc.), return None instead of a file object.
return None
def _extract_member(self, tarinfo, targetpath):
"""
Extract the TarInfo object tarinfo to a physical
file called targetpath.
"""
#
Fetch the TarInfo object for the given name
#
and build the destination pathname, replacing
#
forward slashes to platform specific separators.
targetpath = targetpath.rstrip(
"
/
")
targetpath = targetpath.replace(
"
/
", os.sep)
#
Create all upper directories.
upperdirs = os.path.dirname(targetpath)
if upperdirs
and
not os.path.exists(upperdirs):
#
Create directories that are not part of the archive with
#
default permissions.
os.makedirs(upperdirs)
if tarinfo.islnk()
or tarinfo.issym():
self._dbg(1,
"
%s -> %s
" % (tarinfo.name, tarinfo.linkname))
else:
self._dbg(1, tarinfo.name)
if tarinfo.isreg():
self.makefile(tarinfo, targetpath)
elif tarinfo.isdir():
self.makedir(tarinfo, targetpath)
elif tarinfo.isfifo():
self.makefifo(tarinfo, targetpath)
elif tarinfo.ischr()
or tarinfo.isblk():
self.makedev(tarinfo, targetpath)
elif tarinfo.islnk()
or tarinfo.issym():
self.makelink(tarinfo, targetpath)
elif tarinfo.type
not
in SUPPORTED_TYPES:
self.makeunknown(tarinfo, targetpath)
else:
self.makefile(tarinfo, targetpath)
self.chown(tarinfo, targetpath)
if
not tarinfo.issym():
self.chmod(tarinfo, targetpath)
self.utime(tarinfo, targetpath)
#
--------------------------------------------------------------------------
#
Below are the different file methods. They are called via
#
_extract_member() when extract() is called. They can be replaced in a
#
subclass to implement other functionality.
def makedir(self, tarinfo, targetpath):
"""
Make a directory called targetpath.
"""
try:
#
Use a safe mode for the directory, the real mode is set
#
later in _extract_member().
os.mkdir(targetpath, 0700)
except EnvironmentError, e:
if e.errno != errno.EEXIST:
raise
def makefile(self, tarinfo, targetpath):
"""
Make a file called targetpath.
"""
source = self.extractfile(tarinfo)
try:
with bltn_open(targetpath,
"
wb
") as target:
copyfileobj(source, target)
finally:
source.close()
def makeunknown(self, tarinfo, targetpath):
"""
Make a file from a TarInfo object with an unknown type
at targetpath.
"""
self.makefile(tarinfo, targetpath)
self._dbg(1,
"
tarfile: Unknown file type %r,
" \
"
extracted as regular file.
" % tarinfo.type)
def makefifo(self, tarinfo, targetpath):
"""
Make a fifo called targetpath.
"""
if hasattr(os,
"
mkfifo
"):
os.mkfifo(targetpath)
else:
raise ExtractError(
"
fifo not supported by system
")
def makedev(self, tarinfo, targetpath):
"""
Make a character or block device called targetpath.
"""
if
not hasattr(os,
"
mknod
")
or
not hasattr(os,
"
makedev
"):
raise ExtractError(
"
special devices not supported by system
")
mode = tarinfo.mode
if tarinfo.isblk():
mode |= stat.S_IFBLK
else:
mode |= stat.S_IFCHR
os.mknod(targetpath, mode,
os.makedev(tarinfo.devmajor, tarinfo.devminor))
def makelink(self, tarinfo, targetpath):
"""
Make a (symbolic) link called targetpath. If it cannot be created
(platform limitation), we try to make a copy of the referenced file
instead of a link.
"""
if hasattr(os,
"
symlink
")
and hasattr(os,
"
link
"):
#
For systems that support symbolic and hard links.
if tarinfo.issym():
if os.path.lexists(targetpath):
os.unlink(targetpath)
os.symlink(tarinfo.linkname, targetpath)
else:
#
See extract().
if os.path.exists(tarinfo._link_target):
if os.path.lexists(targetpath):
os.unlink(targetpath)
os.link(tarinfo._link_target, targetpath)
else:
self._extract_member(self._find_link_target(tarinfo), targetpath)
else:
try:
self._extract_member(self._find_link_target(tarinfo), targetpath)
except KeyError:
raise ExtractError(
"
unable to resolve link inside archive
")
def chown(self, tarinfo, targetpath):
"""
Set owner of targetpath according to tarinfo.
"""
if pwd
and hasattr(os,
"
geteuid
")
and os.geteuid() == 0:
#
We have to be root to do so.
try:
g = grp.getgrnam(tarinfo.gname)[2]
except KeyError:
g = tarinfo.gid
try:
u = pwd.getpwnam(tarinfo.uname)[2]
except KeyError:
u = tarinfo.uid
try:
if tarinfo.issym()
and hasattr(os,
"
lchown
"):
os.lchown(targetpath, u, g)
else:
if sys.platform !=
"
os2emx
":
os.chown(targetpath, u, g)
except EnvironmentError, e:
raise ExtractError(
"
could not change owner
")
def chmod(self, tarinfo, targetpath):
"""
Set file permissions of targetpath according to tarinfo.
"""
if hasattr(os,
'
chmod
'):
try:
os.chmod(targetpath, tarinfo.mode)
except EnvironmentError, e:
raise ExtractError(
"
could not change mode
")
def utime(self, tarinfo, targetpath):
"""
Set modification time of targetpath according to tarinfo.
"""
if
not hasattr(os,
'
utime
'):
return
try:
os.utime(targetpath, (tarinfo.mtime, tarinfo.mtime))
except EnvironmentError, e:
raise ExtractError(
"
could not change modification time
")
#
--------------------------------------------------------------------------
def next(self):
"""
Return the next member of the archive as a TarInfo object, when
TarFile is opened for reading. Return None if there is no more
available.
"""
self._check(
"
ra
")
if self.firstmember
is
not None:
m = self.firstmember
self.firstmember = None
return m
#
Read the next block.
self.fileobj.seek(self.offset)
tarinfo = None
while True:
try:
tarinfo = self.tarinfo.fromtarfile(self)
except EOFHeaderError, e:
if self.ignore_zeros:
self._dbg(2,
"
0x%X: %s
" % (self.offset, e))
self.offset += BLOCKSIZE
continue
except InvalidHeaderError, e:
if self.ignore_zeros:
self._dbg(2,
"
0x%X: %s
" % (self.offset, e))
self.offset += BLOCKSIZE
continue
elif self.offset == 0:
raise ReadError(str(e))
except EmptyHeaderError:
if self.offset == 0:
raise ReadError(
"
empty file
")
except TruncatedHeaderError, e:
if self.offset == 0:
raise ReadError(str(e))
except SubsequentHeaderError, e:
raise ReadError(str(e))
break
if tarinfo
is
not None:
self.members.append(tarinfo)
else:
self._loaded = True
return tarinfo
#
--------------------------------------------------------------------------
#
Little helper methods:
def _getmember(self, name, tarinfo=None, normalize=False):
"""
Find an archive member by name from bottom to top.
If tarinfo is given, it is used as the starting point.
"""
#
Ensure that all members have been loaded.
members = self.getmembers()
#
Limit the member search list up to tarinfo.
if tarinfo
is
not None:
members = members[:members.index(tarinfo)]
if normalize:
name = os.path.normpath(name)
for member
in reversed(members):
if normalize:
member_name = os.path.normpath(member.name)
else:
member_name = member.name
if name == member_name:
return member
def _load(self):
"""
Read through the entire archive file and look for readable
members.
"""
while True:
tarinfo = self.next()
if tarinfo
is None:
break
self._loaded = True
def _check(self, mode=None):
"""
Check if TarFile is still open, and if the operation's mode
corresponds to TarFile's mode.
"""
if self.closed:
raise IOError(
"
%s is closed
" % self.
__class__.
__name__)
if mode
is
not None
and self.mode
not
in mode:
raise IOError(
"
bad operation for mode %r
" % self.mode)
def _find_link_target(self, tarinfo):
"""
Find the target member of a symlink or hardlink member in the
archive.
"""
if tarinfo.issym():
#
Always search the entire archive.
linkname =
"
/
".join(filter(None, (os.path.dirname(tarinfo.name), tarinfo.linkname)))
limit = None
else:
#
Search the archive before the link, because a hard link is
#
just a reference to an already archived file.
linkname = tarinfo.linkname
limit = tarinfo
member = self._getmember(linkname, tarinfo=limit, normalize=True)
if member
is None:
raise KeyError(
"
linkname %r not found
" % linkname)
return member
def
__iter__(self):
"""
Provide an iterator object.
"""
if self._loaded:
return iter(self.members)
else:
return TarIter(self)
def _dbg(self, level, msg):
"""
Write debugging output to sys.stderr.
"""
if level <= self.debug:
print >> sys.stderr, msg
def
__enter__(self):
self._check()
return self
def
__exit__(self, type, value, traceback):
if type
is None:
self.close()
else:
#
An exception occurred. We must not call close() because
#
it would try to write end-of-archive blocks and padding.
if
not self._extfileobj:
self.fileobj.close()
self.closed = True
#
class TarFile