Patch #1583880: fix tarfile's problems with long names and posix/

GNU modes.
 (backport from rev. 52524)
This commit is contained in:
Georg Brandl 2006-10-29 09:16:15 +00:00
parent 0d3de7612c
commit 2527f7fee0
2 changed files with 84 additions and 88 deletions

View File

@ -49,6 +49,7 @@ import stat
import errno import errno
import time import time
import struct import struct
import copy
if sys.platform == 'mac': if sys.platform == 'mac':
# This module needs work for MacOS9, especially in the area of pathname # This module needs work for MacOS9, especially in the area of pathname
@ -793,7 +794,6 @@ class TarInfo(object):
"""Construct a TarInfo object. name is the optional name """Construct a TarInfo object. name is the optional name
of the member. of the member.
""" """
self.name = name # member name (dirnames must end with '/') self.name = name # member name (dirnames must end with '/')
self.mode = 0666 # file permissions self.mode = 0666 # file permissions
self.uid = 0 # user id self.uid = 0 # user id
@ -807,8 +807,6 @@ class TarInfo(object):
self.gname = "group" # group name self.gname = "group" # group name
self.devmajor = 0 # device major number self.devmajor = 0 # device major number
self.devminor = 0 # device minor number self.devminor = 0 # device minor number
self.prefix = "" # prefix to filename or information
# about sparse files
self.offset = 0 # the tar header starts here self.offset = 0 # the tar header starts here
self.offset_data = 0 # the file's data starts here self.offset_data = 0 # the file's data starts here
@ -840,24 +838,70 @@ class TarInfo(object):
tarinfo.gname = buf[297:329].rstrip(NUL) tarinfo.gname = buf[297:329].rstrip(NUL)
tarinfo.devmajor = nti(buf[329:337]) tarinfo.devmajor = nti(buf[329:337])
tarinfo.devminor = nti(buf[337:345]) tarinfo.devminor = nti(buf[337:345])
tarinfo.prefix = buf[345:500] prefix = buf[345:500].rstrip(NUL)
if prefix and not tarinfo.issparse():
tarinfo.name = prefix + "/" + tarinfo.name
if tarinfo.chksum not in calc_chksums(buf): if tarinfo.chksum not in calc_chksums(buf):
raise ValueError("invalid header") raise ValueError("invalid header")
return tarinfo return tarinfo
def tobuf(self, posix=False): def tobuf(self, posix=False):
"""Return a tar header block as a 512 byte string. """Return a tar header as a string of 512 byte blocks.
""" """
buf = ""
type = self.type
prefix = ""
if self.name.endswith("/"):
type = DIRTYPE
name = normpath(self.name)
if type == DIRTYPE:
# directories should end with '/'
name += "/"
linkname = self.linkname
if linkname:
# if linkname is empty we end up with a '.'
linkname = normpath(linkname)
if posix:
if self.size > MAXSIZE_MEMBER:
raise ValueError("file is too large (>= 8 GB)")
if len(self.linkname) > LENGTH_LINK:
raise ValueError("linkname is too long (>%d)" % (LENGTH_LINK))
if len(name) > LENGTH_NAME:
prefix = name[:LENGTH_PREFIX + 1]
while prefix and prefix[-1] != "/":
prefix = prefix[:-1]
name = name[len(prefix):]
prefix = prefix[:-1]
if not prefix or len(name) > LENGTH_NAME:
raise ValueError("name is too long")
else:
if len(self.linkname) > LENGTH_LINK:
buf += self._create_gnulong(self.linkname, GNUTYPE_LONGLINK)
if len(name) > LENGTH_NAME:
buf += self._create_gnulong(name, GNUTYPE_LONGNAME)
parts = [ parts = [
stn(self.name, 100), stn(name, 100),
itn(self.mode & 07777, 8, posix), itn(self.mode & 07777, 8, posix),
itn(self.uid, 8, posix), itn(self.uid, 8, posix),
itn(self.gid, 8, posix), itn(self.gid, 8, posix),
itn(self.size, 12, posix), itn(self.size, 12, posix),
itn(self.mtime, 12, posix), itn(self.mtime, 12, posix),
" ", # checksum field " ", # checksum field
self.type, type,
stn(self.linkname, 100), stn(self.linkname, 100),
stn(MAGIC, 6), stn(MAGIC, 6),
stn(VERSION, 2), stn(VERSION, 2),
@ -865,15 +909,38 @@ class TarInfo(object):
stn(self.gname, 32), stn(self.gname, 32),
itn(self.devmajor, 8, posix), itn(self.devmajor, 8, posix),
itn(self.devminor, 8, posix), itn(self.devminor, 8, posix),
stn(self.prefix, 155) stn(prefix, 155)
] ]
buf = struct.pack("%ds" % BLOCKSIZE, "".join(parts)) buf += struct.pack("%ds" % BLOCKSIZE, "".join(parts))
chksum = calc_chksums(buf)[0] chksum = calc_chksums(buf)[0]
buf = buf[:148] + "%06o\0" % chksum + buf[155:] buf = buf[:-364] + "%06o\0" % chksum + buf[-357:]
self.buf = buf self.buf = buf
return buf return buf
def _create_gnulong(self, name, type):
"""Create a GNU longname/longlink header from name.
It consists of an extended tar header, with the length
of the longname as size, followed by data blocks,
which contain the longname as a null terminated string.
"""
name += NUL
tarinfo = self.__class__()
tarinfo.name = "././@LongLink"
tarinfo.type = type
tarinfo.mode = 0
tarinfo.size = len(name)
# create extended header
buf = tarinfo.tobuf()
# create name blocks
buf += name
blocks, remainder = divmod(len(name), BLOCKSIZE)
if remainder > 0:
buf += (BLOCKSIZE - remainder) * NUL
return buf
def isreg(self): def isreg(self):
return self.type in REGULAR_TYPES return self.type in REGULAR_TYPES
def isfile(self): def isfile(self):
@ -1377,50 +1444,11 @@ class TarFile(object):
""" """
self._check("aw") self._check("aw")
tarinfo.name = normpath(tarinfo.name) tarinfo = copy.copy(tarinfo)
if tarinfo.isdir():
# directories should end with '/'
tarinfo.name += "/"
if tarinfo.linkname: buf = tarinfo.tobuf(self.posix)
tarinfo.linkname = normpath(tarinfo.linkname) self.fileobj.write(buf)
self.offset += len(buf)
if tarinfo.size > MAXSIZE_MEMBER:
if self.posix:
raise ValueError("file is too large (>= 8 GB)")
else:
self._dbg(2, "tarfile: Created GNU tar largefile header")
if len(tarinfo.linkname) > LENGTH_LINK:
if self.posix:
raise ValueError("linkname is too long (>%d)" % (LENGTH_LINK))
else:
self._create_gnulong(tarinfo.linkname, GNUTYPE_LONGLINK)
tarinfo.linkname = tarinfo.linkname[:LENGTH_LINK -1]
self._dbg(2, "tarfile: Created GNU tar extension LONGLINK")
if len(tarinfo.name) > LENGTH_NAME:
if self.posix:
prefix = tarinfo.name[:LENGTH_PREFIX + 1]
while prefix and prefix[-1] != "/":
prefix = prefix[:-1]
name = tarinfo.name[len(prefix):]
prefix = prefix[:-1]
if not prefix or len(name) > LENGTH_NAME:
raise ValueError("name is too long (>%d)" % (LENGTH_NAME))
tarinfo.name = name
tarinfo.prefix = prefix
else:
self._create_gnulong(tarinfo.name, GNUTYPE_LONGNAME)
tarinfo.name = tarinfo.name[:LENGTH_NAME - 1]
self._dbg(2, "tarfile: Created GNU tar extension LONGNAME")
self.fileobj.write(tarinfo.tobuf(self.posix))
self.offset += BLOCKSIZE
# If there's data to follow, append it. # If there's data to follow, append it.
if fileobj is not None: if fileobj is not None:
@ -1779,12 +1807,6 @@ class TarFile(object):
if tarinfo.isreg() and tarinfo.name.endswith("/"): if tarinfo.isreg() and tarinfo.name.endswith("/"):
tarinfo.type = DIRTYPE tarinfo.type = DIRTYPE
# The prefix field is used for filenames > 100 in
# the POSIX standard.
# name = prefix + '/' + name
tarinfo.name = normpath(os.path.join(tarinfo.prefix.rstrip(NUL),
tarinfo.name))
# Directory names should have a '/' at the end. # Directory names should have a '/' at the end.
if tarinfo.isdir(): if tarinfo.isdir():
tarinfo.name += "/" tarinfo.name += "/"
@ -1909,10 +1931,6 @@ class TarFile(object):
self.offset += self._block(tarinfo.size) self.offset += self._block(tarinfo.size)
tarinfo.size = origsize tarinfo.size = origsize
# Clear the prefix field so that it is not used
# as a pathname in next().
tarinfo.prefix = ""
return tarinfo return tarinfo
#-------------------------------------------------------------------------- #--------------------------------------------------------------------------
@ -1970,31 +1988,6 @@ class TarFile(object):
else: else:
return TarIter(self) return TarIter(self)
def _create_gnulong(self, name, type):
"""Write a GNU longname/longlink member to the TarFile.
It consists of an extended tar header, with the length
of the longname as size, followed by data blocks,
which contain the longname as a null terminated string.
"""
name += NUL
tarinfo = TarInfo()
tarinfo.name = "././@LongLink"
tarinfo.type = type
tarinfo.mode = 0
tarinfo.size = len(name)
# write extended header
self.fileobj.write(tarinfo.tobuf())
self.offset += BLOCKSIZE
# write name blocks
self.fileobj.write(name)
blocks, remainder = divmod(tarinfo.size, BLOCKSIZE)
if remainder > 0:
self.fileobj.write(NUL * (BLOCKSIZE - remainder))
blocks += 1
self.offset += blocks * BLOCKSIZE
def _dbg(self, level, msg): def _dbg(self, level, msg):
"""Write debugging output to sys.stderr. """Write debugging output to sys.stderr.
""" """

View File

@ -90,6 +90,9 @@ Extension Modules
Library Library
------- -------
- Patch #1583880: fix tarfile's problems with long names and posix/
GNU modes.
- Fix codecs.EncodedFile which did not use file_encoding in 2.5.0, and - Fix codecs.EncodedFile which did not use file_encoding in 2.5.0, and
fix all codecs file wrappers to work correctly with the "with" fix all codecs file wrappers to work correctly with the "with"
statement (bug #1586513). statement (bug #1586513).