~ed.so/duplicity/reuse-passphrase-for-signing-fix

« back to all changes in this revision

Viewing changes to duplicity/tarfile.py.old

  • Committer: bescoto
  • Date: 2002-10-29 01:49:46 UTC
  • Revision ID: vcs-imports@canonical.com-20021029014946-3m4rmm5plom7pl6q
Initial checkin

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
#-------------------------------------------------------------------
 
3
# tarfile.py
 
4
#
 
5
# Module for reading and writing .tar and tar.gz files.
 
6
#
 
7
# Needs at least Python version 2.2.
 
8
#
 
9
# Please consult the html documentation in this distribution
 
10
# for further details on how to use tarfile.
 
11
#
 
12
#-------------------------------------------------------------------
 
13
# Copyright (C) 2002 Lars Gust�bel <lars@gustaebel.de>
 
14
# All rights reserved.
 
15
#
 
16
# Permission  is  hereby granted,  free  of charge,  to  any person
 
17
# obtaining a  copy of  this software  and associated documentation
 
18
# files  (the  "Software"),  to   deal  in  the  Software   without
 
19
# restriction,  including  without limitation  the  rights to  use,
 
20
# copy, modify, merge, publish, distribute, sublicense, and/or sell
 
21
# copies  of  the  Software,  and to  permit  persons  to  whom the
 
22
# Software  is  furnished  to  do  so,  subject  to  the  following
 
23
# conditions:
 
24
#
 
25
# The above copyright  notice and this  permission notice shall  be
 
26
# included in all copies or substantial portions of the Software.
 
27
#
 
28
# THE SOFTWARE IS PROVIDED "AS  IS", WITHOUT WARRANTY OF ANY  KIND,
 
29
# EXPRESS OR IMPLIED, INCLUDING  BUT NOT LIMITED TO  THE WARRANTIES
 
30
# OF  MERCHANTABILITY,  FITNESS   FOR  A  PARTICULAR   PURPOSE  AND
 
31
# NONINFRINGEMENT.  IN  NO  EVENT SHALL  THE  AUTHORS  OR COPYRIGHT
 
32
# HOLDERS  BE LIABLE  FOR ANY  CLAIM, DAMAGES  OR OTHER  LIABILITY,
 
33
# WHETHER  IN AN  ACTION OF  CONTRACT, TORT  OR OTHERWISE,  ARISING
 
34
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 
35
# OTHER DEALINGS IN THE SOFTWARE.
 
36
#
 
37
"""Read from and write to tar format archives.
 
38
"""
 
39
 
 
40
__version__ = "$Revision: 1.1 $"
 
41
# $Source: /sources/duplicity/duplicity/duplicity/Attic/tarfile.py.old,v $
 
42
 
 
43
version     = "0.4.9"
 
44
__author__  = "Lars Gust�bel (lars@gustaebel.de)"
 
45
__date__    = "$Date: 2002/10/29 01:51:36 $"
 
46
__cvsid__   = "$Id: tarfile.py.old,v 1.1 2002/10/29 01:51:36 bescoto Exp $"
 
47
__credits__ = "Gustavo Niemeyer for his support, " \
 
48
              "Detlef Lannert for some early contributions"
 
49
 
 
50
#---------
 
51
# Imports
 
52
#---------
 
53
import sys
 
54
import os
 
55
import __builtin__
 
56
import shutil
 
57
import stat
 
58
import errno
 
59
import time
 
60
 
 
61
try:
 
62
    import grp, pwd
 
63
except ImportError:
 
64
    grp = pwd = None
 
65
 
 
66
# We won't need this anymore in Python 2.3
 
67
#
 
68
# We import the _tarfile extension, that contains
 
69
# some useful functions to handle devices and symlinks.
 
70
# We inject them into os module, as if we were under 2.3.
 
71
#
 
72
try:
 
73
    import _tarfile
 
74
    if _tarfile.mknod is None:
 
75
        _tarfile = None
 
76
except ImportError:
 
77
    _tarfile = None
 
78
if _tarfile and not hasattr(os, "mknod"):
 
79
    os.mknod = _tarfile.mknod
 
80
if _tarfile and not hasattr(os, "major"):
 
81
    os.major = _tarfile.major
 
82
if _tarfile and not hasattr(os, "minor"):
 
83
    os.minor = _tarfile.minor
 
84
if _tarfile and not hasattr(os, "makedev"):
 
85
    os.makedev = _tarfile.makedev
 
86
if _tarfile and not hasattr(os, "lchown"):
 
87
    os.lchown = _tarfile.lchown
 
88
 
 
89
# XXX remove for release (2.3)
 
90
if sys.version_info[:2] < (2,3):
 
91
    True  = 1
 
92
    False = 0
 
93
 
 
94
#---------------------------------------------------------
 
95
# GNUtar constants
 
96
#---------------------------------------------------------
 
97
BLOCKSIZE  = 512                # length of processing blocks
 
98
RECORDSIZE = BLOCKSIZE * 20     # length of records
 
99
MAGIC      = "ustar"            # magic tar string
 
100
VERSION    = "00"               # version number
 
101
 
 
102
LENGTH_NAME = 100               # maximal length of a filename
 
103
LENGTH_LINK = 100               # maximal length of a linkname
 
104
 
 
105
REGTYPE  = "0"                  # regular file
 
106
AREGTYPE = "\0"                 # regular file
 
107
LNKTYPE  = "1"                  # link (inside tarfile)
 
108
SYMTYPE  = "2"                  # symbolic link
 
109
CHRTYPE  = "3"                  # character special device
 
110
BLKTYPE  = "4"                  # block special device
 
111
DIRTYPE  = "5"                  # directory
 
112
FIFOTYPE = "6"                  # fifo special device
 
113
CONTTYPE = "7"                  # contiguous file
 
114
 
 
115
GNUTYPE_LONGNAME = "L"          # GNU tar extension for longnames
 
116
GNUTYPE_LONGLINK = "K"          # GNU tar extension for longlink
 
117
GNUTYPE_SPARSE   = "S"          # GNU tar extension for sparse file
 
118
 
 
119
#---------------------------------------------------------
 
120
# tarfile constants
 
121
#---------------------------------------------------------
 
122
SUPPORTED_TYPES = (REGTYPE, AREGTYPE, LNKTYPE,  # file types that tarfile
 
123
                   SYMTYPE, DIRTYPE, FIFOTYPE,  # can cope with.
 
124
                   CONTTYPE, GNUTYPE_LONGNAME,
 
125
                   GNUTYPE_LONGLINK, GNUTYPE_SPARSE,
 
126
                   CHRTYPE, BLKTYPE)
 
127
 
 
128
REGULAR_TYPES = (REGTYPE, AREGTYPE,             # file types that somehow
 
129
                 CONTTYPE, GNUTYPE_SPARSE)      # represent regular files
 
130
 
 
131
#---------------------------------------------------------
 
132
# Bits used in the mode field, values in octal.
 
133
#---------------------------------------------------------
 
134
S_IFLNK = 0120000        # symbolic link
 
135
S_IFREG = 0100000        # regular file
 
136
S_IFBLK = 0060000        # block device
 
137
S_IFDIR = 0040000        # directory
 
138
S_IFCHR = 0020000        # character device
 
139
S_IFIFO = 0010000        # fifo
 
140
 
 
141
TSUID   = 04000          # set UID on execution
 
142
TSGID   = 02000          # set GID on execution
 
143
TSVTX   = 01000          # reserved
 
144
 
 
145
TUREAD  = 00400          # read by owner
 
146
TUWRITE = 00200          # write by owner
 
147
TUEXEC  = 00100          # execute/search by owner
 
148
TGREAD  = 00040          # read by group
 
149
TGWRITE = 00020          # write by group
 
150
TGEXEC  = 00010          # execute/search by group
 
151
TOREAD  = 00004          # read by other
 
152
TOWRITE = 00002          # write by other
 
153
TOEXEC  = 00001          # execute/search by other
 
154
 
 
155
#---------------------------------------------------------
 
156
# Some useful functions
 
157
#---------------------------------------------------------
 
158
def nts(s):
 
159
    """Convert a null-terminated string buffer to a python string.
 
160
    """
 
161
    return s.split("\0", 1)[0]
 
162
 
 
163
def calc_chksum(buf):
 
164
    """Calculate the checksum for a member's header. It's a simple addition
 
165
       of all bytes, treating the chksum field as if filled with spaces.
 
166
       buf is a 512 byte long string buffer which holds the header.
 
167
    """
 
168
    chk = 256                           # chksum field is treated as blanks,
 
169
                                        # so the initial value is 8 * ord(" ")
 
170
    for c in buf[:148]: chk += ord(c)   # sum up all bytes before chksum
 
171
    for c in buf[156:]: chk += ord(c)   # sum up all bytes after chksum
 
172
    return chk
 
173
 
 
174
def copyfileobj(src, dst, length=None):
 
175
    """Copy length bytes from fileobj src to fileobj dst.
 
176
       If length is None, copy the entire content.
 
177
    """
 
178
    if length == 0:
 
179
        return
 
180
    if length is None:
 
181
        shutil.copyfileobj(src, dst)
 
182
        return
 
183
 
 
184
    BUFSIZE = 16 * 1024
 
185
    blocks, remainder = divmod(length, BUFSIZE)
 
186
    for b in range(blocks):
 
187
        buf = src.read(BUFSIZE)
 
188
        if len(buf) < BUFSIZE:
 
189
            raise IOError, "end of file reached"
 
190
        dst.write(buf)
 
191
 
 
192
    if remainder != 0:
 
193
        buf = src.read(remainder)
 
194
        if len(buf) < remainder:
 
195
            raise IOError, "end of file reached"
 
196
        dst.write(buf)
 
197
    return
 
198
 
 
199
filemode_table = (
 
200
    (S_IFLNK, "l",
 
201
     S_IFREG, "-",
 
202
     S_IFBLK, "b",
 
203
     S_IFDIR, "d",
 
204
     S_IFCHR, "c",
 
205
     S_IFIFO, "p"),
 
206
    (TUREAD,  "r"),
 
207
    (TUWRITE, "w"),
 
208
    (TUEXEC,  "x", TSUID, "S", TUEXEC|TSUID, "s"),
 
209
    (TGREAD,  "r"),
 
210
    (TGWRITE, "w"),
 
211
    (TGEXEC,  "x", TSGID, "S", TGEXEC|TSGID, "s"),
 
212
    (TOREAD,  "r"),
 
213
    (TOWRITE, "w"),
 
214
    (TOEXEC,  "x", TSVTX, "T", TOEXEC|TSVTX, "t"))
 
215
 
 
216
def filemode(mode):
 
217
    """Convert a file's mode to a string of the form
 
218
       -rwxrwxrwx.
 
219
       Used by TarFile.list()
 
220
    """
 
221
    s = ""
 
222
    for t in filemode_table:
 
223
        while 1:
 
224
            if mode & t[0] == t[0]:
 
225
                s += t[1]
 
226
            elif len(t) > 2:
 
227
                t = t[2:]
 
228
                continue
 
229
            else:
 
230
                s += "-"
 
231
            break
 
232
    return s
 
233
 
 
234
if os.sep != "/":
 
235
    normpath = lambda path: os.path.normpath(path).replace(os.sep, "/")
 
236
else:
 
237
    normpath = os.path.normpath
 
238
 
 
239
class TarError(Exception):
 
240
    """Internally used exception"""
 
241
    pass
 
242
 
 
243
#--------------------
 
244
# exported functions
 
245
#--------------------
 
246
def open(name, mode="r", fileobj=None):
 
247
    """Open (uncompressed) tar archive name for reading, writing
 
248
       or appending.
 
249
    """
 
250
    return TarFile(name, mode, fileobj)
 
251
 
 
252
def gzopen(gzname, gzmode="r", compresslevel=9, fileobj=None):
 
253
    """Open gzip compressed tar archive name for reading or writing.
 
254
       Appending is not allowed.
 
255
    """
 
256
    if gzmode == "a":
 
257
        raise ValueError, "Appending to gzipped archive is not allowed"
 
258
    import gzip
 
259
    pre, ext = os.path.splitext(gzname)
 
260
    pre = os.path.basename(pre)
 
261
    if ext == ".tgz":
 
262
        ext = ".tar"
 
263
    if ext == ".gz":
 
264
        ext = ""
 
265
    tarname = pre + ext
 
266
    mode = gzmode
 
267
    if "b" not in gzmode:
 
268
        gzmode += "b"
 
269
    if mode[0:1] == "w":
 
270
        if not fileobj:
 
271
            fileobj = __builtin__.file(gzname, gzmode)
 
272
        t = TarFile(tarname, mode, gzip.GzipFile(tarname, gzmode,
 
273
                                                 compresslevel, fileobj))
 
274
    else:
 
275
        t = TarFile(tarname, mode, gzip.open(gzname, gzmode, compresslevel))
 
276
    t._extfileobj = 0
 
277
    return t
 
278
 
 
279
def is_tarfile(name):
 
280
    """Return True if name points to a tar archive that we
 
281
       are able to handle, else return False.
 
282
    """
 
283
 
 
284
    buftoinfo = TarFile.__dict__["_buftoinfo"]
 
285
    try:
 
286
        buf = __builtin__.open(name, "rb").read(BLOCKSIZE)
 
287
        buftoinfo(None, buf)
 
288
        return True
 
289
    except (ValueError, ImportError):
 
290
        pass
 
291
    try:
 
292
        import gzip
 
293
        buf = gzip.open(name, "rb").read(BLOCKSIZE)
 
294
        buftoinfo(None, buf)
 
295
        return True
 
296
    except (IOError, ValueError, ImportError):
 
297
        pass
 
298
    return False
 
299
 
 
300
#------------------
 
301
# Exported Classes
 
302
#------------------
 
303
class TarInfo:
 
304
    """Informational class which holds the details about an
 
305
       archive member given by a tar header block.
 
306
       TarInfo instances are returned by TarFile.getmember() and
 
307
       TarFile.getmembers() and are usually created internally.
 
308
       If you want to create a TarInfo instance from the outside,
 
309
       you should use TarFile.gettarinfo() if the file already exists,
 
310
       or you can instanciate the class yourself.
 
311
    """
 
312
 
 
313
    def __init__(self, name=""):
 
314
        """Construct a TarInfo instance. name is the optional name
 
315
           of the member.
 
316
        """
 
317
 
 
318
        self.name     = name       # member name (dirnames must end with '/')
 
319
        self.mode     = 0100666    # file permissions
 
320
        self.uid      = 0          # user id
 
321
        self.gid      = 0          # group id
 
322
        self.size     = 0          # file size
 
323
        self.mtime    = 0          # modification time
 
324
        self.chksum   = 0          # header checksum
 
325
        self.type     = REGTYPE    # member type
 
326
        self.linkname = ""         # link name
 
327
        self.uname    = "user"     # user name
 
328
        self.gname    = "group"    # group name
 
329
        self.devmajor = 0          #-
 
330
        self.devminor = 0          #-for use with CHRTYPE and BLKTYPE
 
331
        self.prefix   = ""         # prefix, holding information
 
332
                                   # about sparse files
 
333
 
 
334
        self.offset   = 0          # the tar header starts here
 
335
        self.offset_data = 0       # the optional file's data starts here
 
336
 
 
337
    def getheader(self):
 
338
        """Return a tar header block as a 512 byte string.
 
339
        """
 
340
        # The following code was contributed by Detlef Lannert.
 
341
        parts = []
 
342
        for value, fieldsize in (
 
343
                (self.name, 100),
 
344
                ("%07o" % self.mode, 8),
 
345
                ("%07o" % self.uid, 8),
 
346
                ("%07o" % self.gid, 8),
 
347
                ("%011o" % self.size, 12),
 
348
                ("%011o" % self.mtime, 12),
 
349
                ("        ", 8),
 
350
                (self.type, 1),
 
351
                (self.linkname, 100),
 
352
                (MAGIC, 6),
 
353
                (VERSION, 2),
 
354
                (self.uname, 32),
 
355
                (self.gname, 32),
 
356
                ("%07o" % self.devmajor, 8),
 
357
                ("%07o" % self.devminor, 8),
 
358
                (self.prefix, 155)
 
359
                ):
 
360
            l = len(value)
 
361
            parts.append(value + (fieldsize - l) * "\0")
 
362
 
 
363
        buf = "".join(parts)
 
364
        chksum = calc_chksum(buf)
 
365
        buf = buf[:148] + "%06o\0" % chksum + buf[155:]
 
366
        buf += (512 - len(buf)) * "\0"
 
367
        self.buf = buf
 
368
        return buf
 
369
 
 
370
    def isreg(self):
 
371
        return self.type in REGULAR_TYPES
 
372
    def isfile(self):
 
373
        return self.isreg()
 
374
    def isdir(self):
 
375
        return self.type == DIRTYPE
 
376
    def issym(self):
 
377
        return self.type == SYMTYPE
 
378
    def islnk(self):
 
379
        return self.type == LNKTYPE
 
380
    def ischr(self):
 
381
        return self.type == CHRTYPE
 
382
    def isblk(self):
 
383
        return self.type == BLKTYPE
 
384
    def isfifo(self):
 
385
        return self.type == FIFOTYPE
 
386
    def issparse(self):
 
387
        return self.type == GNUTYPE_SPARSE
 
388
    def isdev(self):
 
389
        return self.type in (CHRTYPE, BLKTYPE, FIFOTYPE)
 
390
# class TarInfo
 
391
 
 
392
 
 
393
class TarFile:
 
394
    """Class representing a TAR archive file on disk.
 
395
    """
 
396
    debug = 0                   # May be set from 0 (no msgs) to 3 (all msgs)
 
397
 
 
398
    dereference = False         # If true, add content of linked file to the
 
399
                                # tar file, else the link.
 
400
 
 
401
    ignore_zeros = False        # If true, skips empty or invalid blocks and
 
402
                                # continues processing.
 
403
 
 
404
    errorlevel = 0              # If 0, fatal errors only appear in debug
 
405
                                # messages (if debug >= 0). If > 0, errors
 
406
                                # are passed to the caller as exceptions.
 
407
 
 
408
    def __init__(self, name=None, mode="r", fileobj=None):
 
409
        self.name = name
 
410
 
 
411
        if len(mode) > 1 or mode not in "raw":
 
412
            raise ValueError, "mode must be either 'r', 'a' or 'w', " \
 
413
                                "not '%s'" % mode
 
414
        self._mode = mode
 
415
        self.mode = {"r": "rb", "a": "r+b", "w": "wb"}[mode]
 
416
 
 
417
        if not fileobj:
 
418
            fileobj = __builtin__.file(self.name, self.mode)
 
419
            self._extfileobj = 0
 
420
        else:
 
421
            if self.name is None and hasattr(fileobj, "name"):
 
422
                self.name = fileobj.name
 
423
            if hasattr(fileobj, "mode"):
 
424
                self.mode = fileobj.mode
 
425
            self._extfileobj = 1
 
426
        self.fileobj = fileobj
 
427
 
 
428
        # Init datastructures
 
429
        self.members     = []       # list of members as TarInfo instances
 
430
        self.membernames = []       # names of members
 
431
        self.chunks      = [0]      # chunk cache
 
432
        self._loaded     = 0        # flag if all members have been read
 
433
        self.offset      = 0l       # current position in the archive file
 
434
        self.inodes      = {}       # dictionary caching the inodes of
 
435
                                    # archive members already added
 
436
        if self._mode == "a":
 
437
            self.fileobj.seek(0)
 
438
            self._load()
 
439
 
 
440
    def close(self):
 
441
        """Close the TarFile instance and do some cleanup.
 
442
        """
 
443
        if self.fileobj:
 
444
            if self._mode in "aw":
 
445
                # fill up the end with zero-blocks
 
446
                # (like option -b20 for tar does)
 
447
                blocks, remainder = divmod(self.offset, RECORDSIZE)
 
448
                if remainder > 0:
 
449
                    self.fileobj.write("\0" * (RECORDSIZE - remainder))
 
450
 
 
451
            if not self._extfileobj:
 
452
                self.fileobj.close()
 
453
            self.fileobj = None
 
454
 
 
455
    def next(self):
 
456
        """Return the next member from the archive.
 
457
           Return None if the end is reached.
 
458
           Can be used in a while statement, is used
 
459
           for Iteration (see __iter__()) and internally.
 
460
        """
 
461
        if not self.fileobj:
 
462
            raise ValueError, "I/O operation on closed file"
 
463
        if self._mode not in "ra":
 
464
            raise ValueError, "reading from a write-mode file"
 
465
 
 
466
        # Read the next block.
 
467
        self.fileobj.seek(self.chunks[-1])
 
468
        while 1:
 
469
            buf = self.fileobj.read(BLOCKSIZE)
 
470
            if not buf:
 
471
                return None
 
472
            try:
 
473
                tarinfo = self._buftoinfo(buf)
 
474
            except ValueError:
 
475
                if self.ignore_zeros:
 
476
                    if buf.count("\0") == BLOCKSIZE:
 
477
                        adj = "empty"
 
478
                    else:
 
479
                        adj = "invalid"
 
480
                    self._dbg(2, "0x%X: %s block\n" % (self.offset, adj))
 
481
                    self.offset += BLOCKSIZE
 
482
                    continue
 
483
                else:
 
484
                    return None
 
485
            break
 
486
 
 
487
        # If the TarInfo instance contains a GNUTYPE longname or longlink
 
488
        # statement, we must process this first.
 
489
        if tarinfo.type in (GNUTYPE_LONGLINK, GNUTYPE_LONGNAME):
 
490
            tarinfo = self._proc_gnulong(tarinfo, tarinfo.type)
 
491
 
 
492
        if tarinfo.issparse():
 
493
            # Sparse files need some care,
 
494
            # due to the possible extra headers.
 
495
            tarinfo.offset = self.offset
 
496
            self.offset += BLOCKSIZE
 
497
            origsize = self._proc_sparse(tarinfo)
 
498
            tarinfo.offset_data = self.offset
 
499
            blocks, remainder = divmod(tarinfo.size, BLOCKSIZE)
 
500
            if remainder:
 
501
                blocks += 1
 
502
            self.offset += blocks * BLOCKSIZE
 
503
            tarinfo.size = origsize
 
504
        else:
 
505
            tarinfo.offset = self.offset
 
506
            self.offset += BLOCKSIZE
 
507
            tarinfo.offset_data = self.offset
 
508
            if tarinfo.isreg():
 
509
                # Skip the following data blocks.
 
510
                blocks, remainder = divmod(tarinfo.size, BLOCKSIZE)
 
511
                if remainder:
 
512
                    blocks += 1
 
513
                self.offset += blocks * BLOCKSIZE
 
514
 
 
515
        self.members.append(tarinfo)
 
516
        self.membernames.append(tarinfo.name)
 
517
        self.chunks.append(self.offset)
 
518
        return tarinfo
 
519
 
 
520
    def getmember(self, name):
 
521
        """Return a TarInfo instance for member name.
 
522
        """
 
523
        if name not in self.membernames and not self._loaded:
 
524
            self._load()
 
525
        if name not in self.membernames:
 
526
            raise KeyError, "filename `%s' not found in tar archive" % name
 
527
        return self._getmember(name)
 
528
 
 
529
    def getinfo(self, name):
 
530
        """Return a TarInfo instance for member name.
 
531
           This method will be deprecated in 0.6,
 
532
           use getmember() instead.
 
533
        """
 
534
        # XXX kick this out in 0.6
 
535
        import warnings
 
536
        warnings.warn("use getmember() instead", DeprecationWarning)
 
537
        return self.getmember(name)
 
538
 
 
539
    def getmembers(self):
 
540
        """Return a list of all members in the archive
 
541
           (as TarInfo instances).
 
542
        """
 
543
        if not self._loaded:    # if we want to obtain a list of
 
544
            self._load()        # all members, we first have to
 
545
                                # scan the whole archive.
 
546
        return self.members
 
547
 
 
548
    def getnames(self):
 
549
        """Return a list of names of all members in the
 
550
           archive.
 
551
        """
 
552
        if not self._loaded:
 
553
            self._load()
 
554
        return self.membernames
 
555
 
 
556
    def gettarinfo(self, name, arcname=None):
 
557
        """Create a TarInfo instance from an existing file.
 
558
           Optional arcname defines the name under which the file
 
559
           shall be stored in the archive.
 
560
        """
 
561
        # Building the name of the member in the archive.
 
562
        # Backward slashes are converted to forward slashes,
 
563
        # Absolute paths are turned to relative paths.
 
564
        if arcname is None:
 
565
            arcname = name
 
566
        arcname = normpath(arcname)
 
567
        drv, arcname = os.path.splitdrive(arcname)
 
568
        while arcname[0:1] == "/":
 
569
            arcname = arcname[1:]
 
570
 
 
571
        # Now, fill the TarInfo instance with
 
572
        # information specific for the file.
 
573
        tarinfo = TarInfo()
 
574
 
 
575
        # Use os.stat or os.lstat, depending on platform
 
576
        # and if symlinks shall be resolved.
 
577
        if hasattr(os, "lstat") and not self.dereference:
 
578
            statres = os.lstat(name)
 
579
        else:
 
580
            statres = os.stat(name)
 
581
 
 
582
        linkname = ""
 
583
 
 
584
        stmd = statres.st_mode
 
585
        if stat.S_ISREG(stmd):
 
586
            inode = (statres.st_ino, statres.st_dev,
 
587
                     statres.st_mtime)
 
588
            if inode in self.inodes.keys() and not self.dereference:
 
589
                # Is it a hardlink to an already
 
590
                # archived file?
 
591
                type = LNKTYPE
 
592
                linkname = self.inodes[inode]
 
593
            else:
 
594
                # The inode is added only if its valid.
 
595
                # For win32 it is always 0.
 
596
                type = REGTYPE
 
597
                if inode[0]: self.inodes[inode] = arcname
 
598
        elif stat.S_ISDIR(stmd):
 
599
            type = DIRTYPE
 
600
            if arcname[-1:] != "/": arcname += "/"
 
601
        elif stat.S_ISFIFO(stmd):
 
602
            type = FIFOTYPE
 
603
        elif stat.S_ISLNK(stmd):
 
604
            type = SYMTYPE
 
605
            linkname = os.readlink(name)
 
606
        elif stat.S_ISCHR(stmd):
 
607
            type = CHRTYPE
 
608
        elif stat.S_ISBLK(stmd):
 
609
            type = BLKTYPE
 
610
        else:
 
611
            return None
 
612
 
 
613
        # Fill the TarInfo instance with all
 
614
        # information we can get.
 
615
        tarinfo.name  = arcname
 
616
        tarinfo.mode  = stmd
 
617
        tarinfo.uid   = statres.st_uid
 
618
        tarinfo.gid   = statres.st_gid
 
619
        tarinfo.size  = statres.st_size
 
620
        tarinfo.mtime = statres.st_mtime
 
621
        tarinfo.type  = type
 
622
        tarinfo.linkname = linkname
 
623
        if pwd:
 
624
            try:
 
625
                tarinfo.uname = pwd.getpwuid(tarinfo.uid)[0]
 
626
            except KeyError:
 
627
                pass
 
628
        if grp:
 
629
            try:
 
630
                tarinfo.gname = grp.getgrgid(tarinfo.gid)[0]
 
631
            except KeyError:
 
632
                pass
 
633
 
 
634
        if type in (CHRTYPE, BLKTYPE):
 
635
            if hasattr(os, "major") and hasattr(os, "minor"):
 
636
                tarinfo.devmajor = os.major(statres.st_rdev)
 
637
                tarinfo.devminor = os.minor(statres.st_rdev)
 
638
        return tarinfo
 
639
 
 
640
    def list(self, verbose=1):
 
641
        """Print a formatted listing of the archive's
 
642
           contents to stdout.
 
643
        """
 
644
        for tarinfo in self:
 
645
            if verbose:
 
646
                print filemode(tarinfo.mode),
 
647
                print tarinfo.uname + "/" + tarinfo.gname,
 
648
                if tarinfo.ischr() or tarinfo.isblk():
 
649
                    print "%10s" % (str(tarinfo.devmajor) + "," + str(tarinfo.devminor)),
 
650
                else:
 
651
                    print "%10d" % tarinfo.size,
 
652
                print "%d-%02d-%02d %02d:%02d:%02d" \
 
653
                      % time.gmtime(tarinfo.mtime)[:6],
 
654
 
 
655
            print tarinfo.name,
 
656
 
 
657
            if verbose:
 
658
                if tarinfo.issym():
 
659
                    print "->", tarinfo.linkname,
 
660
                if tarinfo.islnk():
 
661
                    print "link to", tarinfo.linkname,
 
662
            print
 
663
 
 
664
    def add(self, name, arcname=None, recursive=1):
 
665
        """Add a file or a directory to the archive.
 
666
           Directory addition is recursive by default.
 
667
        """
 
668
        if not self.fileobj:
 
669
            raise ValueError, "I/O operation on closed file"
 
670
        if self._mode == "r":
 
671
            raise ValueError, "writing to a read-mode file"
 
672
 
 
673
        if arcname is None:
 
674
            arcname = name
 
675
 
 
676
        # Skip if somebody tries to archive the archive...
 
677
        if os.path.abspath(name) == os.path.abspath(self.name):
 
678
            self._dbg(2, "tarfile: Skipped `%s'\n" % name)
 
679
            return
 
680
 
 
681
        # Special case: The user wants to add the current
 
682
        # working directory.
 
683
        if name == ".":
 
684
            if recursive:
 
685
                if arcname == ".":
 
686
                    arcname = ""
 
687
                for f in os.listdir("."):
 
688
                    self.add(f, os.path.join(arcname, f))
 
689
            return
 
690
 
 
691
        self._dbg(1, "%s\n" % name)
 
692
 
 
693
        # Create a TarInfo instance from the file.
 
694
        tarinfo = self.gettarinfo(name, arcname)
 
695
 
 
696
        if tarinfo is None:
 
697
            self._dbg(1, "tarfile: Unsupported type `%s'\n" % name)
 
698
 
 
699
 
 
700
        # Append the tar header and data to the archive.
 
701
        if tarinfo.isreg():
 
702
            f = __builtin__.file(name, "rb")
 
703
            self.addfile(tarinfo, fileobj = f)
 
704
            f.close()
 
705
 
 
706
        if tarinfo.type in (LNKTYPE, SYMTYPE, FIFOTYPE, CHRTYPE, BLKTYPE):
 
707
            tarinfo.size = 0l
 
708
            self.addfile(tarinfo)
 
709
 
 
710
        if tarinfo.isdir():
 
711
            self.addfile(tarinfo)
 
712
            if recursive:
 
713
                for f in os.listdir(name):
 
714
                    self.add(os.path.join(name, f), os.path.join(arcname, f))
 
715
 
 
716
    def addfile(self, tarinfo, fileobj=None):
 
717
        """Add the content of fileobj to the tarfile.
 
718
           The amount of bytes to read is determined by
 
719
           the size attribute in the tarinfo instance.
 
720
        """
 
721
        if not self.fileobj:
 
722
            raise ValueError, "I/O operation on closed file"
 
723
        if self._mode == "r":
 
724
            raise ValueError, "writing to a read-mode file"
 
725
 
 
726
        # XXX What was this good for again?
 
727
        try:
 
728
            self.fileobj.seek(self.chunks[-1])
 
729
        except IOError:
 
730
            pass
 
731
 
 
732
        # Now we must check if the strings for filename
 
733
        # and linkname fit into the posix header.
 
734
        # (99 chars + "\0" for each)
 
735
        # If not, we must create GNU extension headers.
 
736
        # If both filename and linkname are too long,
 
737
        # the longlink is first to be written out.
 
738
        if len(tarinfo.linkname) >= LENGTH_LINK - 1:
 
739
            self._create_gnulong(tarinfo.linkname, GNUTYPE_LONGLINK)
 
740
            tarinfo.linkname = tarinfo.linkname[:LENGTH_LINK -1]
 
741
        if len(tarinfo.name) >= LENGTH_NAME - 1:
 
742
            self._create_gnulong(tarinfo.name, GNUTYPE_LONGNAME)
 
743
            tarinfo.name = tarinfo.name[:LENGTH_NAME - 1]
 
744
 
 
745
        header = tarinfo.getheader()
 
746
        self.fileobj.write(header)
 
747
        self.offset += BLOCKSIZE
 
748
 
 
749
        # If there's data to follow, append it.
 
750
        if fileobj is not None:
 
751
            copyfileobj(fileobj, self.fileobj, tarinfo.size)
 
752
            blocks, remainder = divmod(tarinfo.size, BLOCKSIZE)
 
753
            if remainder > 0:
 
754
                self.fileobj.write("\0" * (BLOCKSIZE - remainder))
 
755
                blocks += 1
 
756
            self.offset += blocks * BLOCKSIZE
 
757
 
 
758
        self.members.append(tarinfo)
 
759
        self.membernames.append(tarinfo.name)
 
760
        self.chunks.append(self.offset)
 
761
 
 
762
#    def untar(self, path):
 
763
#        """Untar the whole archive to path.
 
764
#        """
 
765
#        later = []
 
766
#        for tarinfo in self:
 
767
#            if tarinfo.isdir():
 
768
#                later.append(tarinfo)
 
769
#            self.extract(tarinfo, path)
 
770
#        for tarinfo in later:
 
771
#            self._utime(tarinfo, os.path.join(path, tarinfo.name))
 
772
 
 
773
    def extractfile(self, member):
 
774
        """Extract member from the archive and return a file-like
 
775
           object. member may be a name or a TarInfo instance.
 
776
        """
 
777
        if not self.fileobj:
 
778
            raise ValueError, "I/O operation on closed file"
 
779
        if self._mode != "r":
 
780
            raise ValueError, "reading from a write-mode file"
 
781
 
 
782
        if isinstance(member, TarInfo):
 
783
            tarinfo = member
 
784
        else:
 
785
            tarinfo = self.getmember(member)
 
786
 
 
787
        if tarinfo.isreg() or tarinfo.type not in SUPPORTED_TYPES:
 
788
            return _FileObject(self, tarinfo)
 
789
        elif tarinfo.islnk() or tarinfo.issym():
 
790
            return self.extractfile(self._getmember(tarinfo.linkname, tarinfo))
 
791
        else:
 
792
            return None
 
793
 
 
794
    def extract(self, member, path=""):
 
795
        """Extract member from the archive and write it to
 
796
           current working directory using its full pathname.
 
797
           If optional path is given, it is attached before the
 
798
           pathname.
 
799
           member may be a name or a TarInfo instance.
 
800
        """
 
801
        if not self.fileobj:
 
802
            raise ValueError, "I/O operation on closed file"
 
803
        if self._mode != "r":
 
804
            raise ValueError, "reading from a write-mode file"
 
805
 
 
806
        if isinstance(member, TarInfo):
 
807
            tarinfo = member
 
808
        else:
 
809
            tarinfo = self.getmember(member)
 
810
 
 
811
        self._dbg(1, tarinfo.name)
 
812
        try:
 
813
            self._extract_member(tarinfo, os.path.join(path, tarinfo.name))
 
814
        except EnvironmentError, e:
 
815
            if self.errorlevel > 0:
 
816
                raise
 
817
            else:
 
818
                self._dbg(1, "\ntarfile: %s `%s'" % (e.strerror, e.filename))
 
819
        except TarError, e:
 
820
            if self.errorlevel > 1:
 
821
                raise
 
822
            else:
 
823
                self._dbg(1, "\ntarfile: %s" % e)
 
824
        self._dbg(1, "\n")
 
825
 
 
826
    def _extract_member(self, tarinfo, targetpath):
 
827
        """Extract the TarInfo instance tarinfo to a physical
 
828
           file called targetpath.
 
829
        """
 
830
        # Fetch the TarInfo instance for the given name
 
831
        # and build the destination pathname, replacing
 
832
        # forward slashes to platform specific separators.
 
833
        if targetpath[-1:] == "/":
 
834
            targetpath = targetpath[:-1]
 
835
        targetpath = os.path.normpath(targetpath)
 
836
 
 
837
        # Create all upper directories.
 
838
        upperdirs = os.path.dirname(targetpath)
 
839
        if upperdirs and not os.path.exists(upperdirs):
 
840
            ti = TarInfo()
 
841
            ti.name  = ""
 
842
            ti.type  = DIRTYPE
 
843
            ti.mode  = 0777
 
844
            ti.mtime = tarinfo.mtime
 
845
            ti.uid   = tarinfo.uid
 
846
            ti.gid   = tarinfo.gid
 
847
            ti.uname = tarinfo.uname
 
848
            ti.gname = tarinfo.gname
 
849
            for d in os.path.split(os.path.splitdrive(upperdirs)[1]):
 
850
                ti.name = os.path.join(ti.name, d)
 
851
                self._extract_member(ti, ti.name)
 
852
 
 
853
        if tarinfo.isreg():
 
854
            self._makefile(tarinfo, targetpath)
 
855
        elif tarinfo.isdir():
 
856
            self._makedir(tarinfo, targetpath)
 
857
        elif tarinfo.isfifo():
 
858
            self._makefifo(tarinfo, targetpath)
 
859
        elif tarinfo.ischr() or tarinfo.isblk():
 
860
            self._makedev(tarinfo, targetpath)
 
861
        elif tarinfo.islnk() or tarinfo.issym():
 
862
            self._makelink(tarinfo, targetpath)
 
863
        else:
 
864
            self._makefile(tarinfo, targetpath)
 
865
            if tarinfo.type not in SUPPORTED_TYPES:
 
866
                self._dbg(1, "\ntarfile: Unknown file type '%s', " \
 
867
                             "extracted as regular file." % tarinfo.type)
 
868
 
 
869
        if not tarinfo.issym():
 
870
            self._chown(tarinfo, targetpath)
 
871
            self._chmod(tarinfo, targetpath)
 
872
            if not tarinfo.isdir():
 
873
                self._utime(tarinfo, targetpath)
 
874
 
 
875
    def _makedir(self, tarinfo, targetpath):
 
876
        """Make a directory called targetpath out of tarinfo.
 
877
        """
 
878
        try:
 
879
            os.mkdir(targetpath)
 
880
        except EnvironmentError, e:
 
881
            if e.errno != errno.EEXIST:
 
882
                raise
 
883
 
 
884
    def _makefile(self, tarinfo, targetpath):
 
885
        """Make a file called targetpath out of tarinfo.
 
886
        """
 
887
        source = self.extractfile(tarinfo)
 
888
        target = __builtin__.file(targetpath, "wb")
 
889
        copyfileobj(source, target)
 
890
        source.close()
 
891
        target.close()
 
892
 
 
893
    def _makefifo(self, tarinfo, targetpath):
 
894
        """Make a fifo called targetpath out of tarinfo.
 
895
        """
 
896
        if hasattr(os, "mkfifo"):
 
897
            os.mkfifo(targetpath)
 
898
        else:
 
899
            raise TarError, "Fifo not supported by system"
 
900
 
 
901
    def _makedev(self, tarinfo, targetpath):
 
902
        """Make a character or block device called targetpath out of tarinfo.
 
903
        """
 
904
        if not hasattr(os, "mknod"):
 
905
            raise TarError, "Special devices not supported by system"
 
906
 
 
907
        mode = tarinfo.mode
 
908
        if tarinfo.isblk():
 
909
            mode |= stat.S_IFBLK
 
910
        else:
 
911
            mode |= stat.S_IFCHR
 
912
 
 
913
        # This if statement should go away when python-2.3a0-devicemacros
 
914
        # patch succeeds.
 
915
        if hasattr(os, "makedev"):
 
916
            os.mknod(targetpath, mode,
 
917
                     os.makedev(tarinfo.devmajor, tarinfo.devminor))
 
918
        else:
 
919
            os.mknod(targetpath, mode,
 
920
                     tarinfo.devmajor, tarinfo.devminor)
 
921
 
 
922
    def _makelink(self, tarinfo, targetpath):
 
923
        """Make a (symbolic) link called targetpath out of tarinfo.
 
924
           If it cannot be made (due to platform or failure), we try
 
925
           to make a copy of the referenced file instead of a link.
 
926
        """
 
927
        linkpath = tarinfo.linkname
 
928
        self._dbg(1, " -> %s" % linkpath)
 
929
        try:
 
930
            if tarinfo.issym():
 
931
                os.symlink(linkpath, targetpath)
 
932
            else:
 
933
                linkpath = os.path.join(os.path.dirname(targetpath),
 
934
                                        linkpath)
 
935
                os.link(linkpath, targetpath)
 
936
        except AttributeError:
 
937
            linkpath = os.path.join(os.path.dirname(tarinfo.name),
 
938
                                    tarinfo.linkname)
 
939
            linkpath = normpath(linkpath)
 
940
            try:
 
941
                self._extract_member(self.getmember(linkpath), targetpath)
 
942
            except (IOError, OSError, KeyError), e:
 
943
                linkpath = os.path.normpath(linkpath)
 
944
                try:
 
945
                    shutil.copy2(linkpath, targetpath)
 
946
                except EnvironmentError, e:
 
947
                    raise TarError, "Link could not be created"
 
948
 
 
949
    def _chown(self, tarinfo, targetpath):
 
950
        """Set owner of targetpath according to tarinfo.
 
951
        """
 
952
        if pwd and os.geteuid() == 0:
 
953
            # We have to be root to do so.
 
954
            try:
 
955
                g = grp.getgrnam(tarinfo.gname)[2]
 
956
            except KeyError:
 
957
                try:
 
958
                    g = grp.getgrgid(tarinfo.gid)[2]
 
959
                except KeyError:
 
960
                    g = os.getgid()
 
961
            try:
 
962
                u = pwd.getpwnam(tarinfo.uname)[2]
 
963
            except KeyError:
 
964
                try:
 
965
                    u = pwd.getpwuid(tarinfo.uid)[2]
 
966
                except KeyError:
 
967
                    u = os.getuid()
 
968
            try:
 
969
                if tarinfo.issym() and hasattr(os, "lchown"):
 
970
                    os.lchown(targetpath, u, g)
 
971
                else:
 
972
                    os.chown(targetpath, u, g)
 
973
            except EnvironmentError, e:
 
974
                self._dbg(2, "\ntarfile: (chown failed), %s `%s'"
 
975
                             % (e.strerror, e.filename))
 
976
 
 
977
    def _chmod(self, tarinfo, targetpath):
 
978
        """Set file permissions of targetpath according to tarinfo.
 
979
        """
 
980
        try:
 
981
            os.chmod(targetpath, tarinfo.mode)
 
982
        except EnvironmentError, e:
 
983
            self._dbg(2, "\ntarfile: (chmod failed), %s `%s'"
 
984
                         % (e.strerror, e.filename))
 
985
 
 
986
    def _utime(self, tarinfo, targetpath):
 
987
        """Set modification time of targetpath according to tarinfo.
 
988
        """
 
989
        try:
 
990
            os.utime(targetpath, (tarinfo.mtime, tarinfo.mtime))
 
991
        except EnvironmentError, e:
 
992
            self._dbg(2, "\ntarfile: (utime failed), %s `%s'"
 
993
                         % (e.strerror, e.filename))
 
994
 
 
995
    def _getmember(self, name, tarinfo=None):
 
996
        """Find an archive member by name from bottom to top.
 
997
           If tarinfo is given, it is used as the starting point.
 
998
        """
 
999
        if tarinfo is None:
 
1000
            end = len(self.members)
 
1001
        else:
 
1002
            end = self.members.index(tarinfo)
 
1003
 
 
1004
        for i in xrange(end - 1, -1, -1):
 
1005
            if name == self.membernames[i]:
 
1006
                return self.members[i]
 
1007
 
 
1008
    def _load(self):
 
1009
        """Read through the entire archive file and look for readable
 
1010
           members.
 
1011
        """
 
1012
        while 1:
 
1013
            tarinfo = self.next()
 
1014
            if tarinfo is None:
 
1015
                break
 
1016
        self._loaded = 1
 
1017
        return
 
1018
 
 
1019
    def __iter__(self):
 
1020
        """Provide an iterator object.
 
1021
        """
 
1022
        if self._loaded:
 
1023
            return iter(self.members)
 
1024
        else:
 
1025
            return TarIter(self)
 
1026
 
 
1027
    def _buftoinfo(self, buf):
 
1028
        """Transform a 512 byte block to a TarInfo instance.
 
1029
        """
 
1030
        tarinfo = TarInfo()
 
1031
        tarinfo.name = nts(buf[0:100])
 
1032
        tarinfo.mode = int(buf[100:108], 8)
 
1033
        tarinfo.uid = int(buf[108:116],8)
 
1034
        tarinfo.gid = int(buf[116:124],8)
 
1035
        tarinfo.size = long(buf[124:136], 8)
 
1036
        tarinfo.mtime = long(buf[136:148], 8)
 
1037
        tarinfo.chksum = int(buf[148:156], 8)
 
1038
        tarinfo.type = buf[156:157]
 
1039
        tarinfo.linkname = nts(buf[157:257])
 
1040
        tarinfo.uname = nts(buf[265:297])
 
1041
        tarinfo.gname = nts(buf[297:329])
 
1042
        try:
 
1043
            tarinfo.devmajor = int(buf[329:337], 8)
 
1044
            tarinfo.devminor = int(buf[337:345], 8)
 
1045
        except ValueError:
 
1046
            tarinfo.devmajor = tarinfo.devmajor = 0
 
1047
        tarinfo.prefix = buf[345:500]
 
1048
        if tarinfo.chksum != calc_chksum(buf):
 
1049
            self._dbg(1, "tarfile: Bad Checksum\n")
 
1050
        return tarinfo
 
1051
 
 
1052
    def _proc_gnulong(self, tarinfo, type):
 
1053
        """Evaluate the two blocks that hold a GNU longname
 
1054
           or longlink member.
 
1055
        """
 
1056
        name = None
 
1057
        linkname = None
 
1058
        buf = self.fileobj.read(BLOCKSIZE)
 
1059
        if not buf:
 
1060
            return None
 
1061
        self.offset += BLOCKSIZE
 
1062
        if type == GNUTYPE_LONGNAME:
 
1063
            name = nts(buf)
 
1064
        if type == GNUTYPE_LONGLINK:
 
1065
            linkname = nts(buf)
 
1066
 
 
1067
        buf = self.fileobj.read(BLOCKSIZE)
 
1068
        if not buf:
 
1069
            return None
 
1070
        tarinfo = self._buftoinfo(buf)
 
1071
        if name is not None:
 
1072
            tarinfo.name = name
 
1073
        if linkname is not None:
 
1074
            tarinfo.linkname = linkname
 
1075
        self.offset += BLOCKSIZE
 
1076
        return tarinfo
 
1077
 
 
1078
    def _create_gnulong(self, name, type):
 
1079
        """Insert a GNU longname/longlink member into the archive.
 
1080
           It consists of a common tar header, with the length
 
1081
           of the longname as size, followed by a data block,
 
1082
           which contains the longname as a null terminated string.
 
1083
        """
 
1084
        tarinfo = TarInfo()
 
1085
        tarinfo.name = "././@LongLink"
 
1086
        tarinfo.type = type
 
1087
        tarinfo.mode = 0
 
1088
        tarinfo.size = len(name)
 
1089
 
 
1090
        self.fileobj.write(tarinfo.getheader())
 
1091
        self.fileobj.write(name + "\0" * (512 - len(name)))
 
1092
        self.offset += BLOCKSIZE * 2
 
1093
 
 
1094
    def _proc_sparse(self, tarinfo):
 
1095
        """Analyze a GNU sparse header plus extra headers.
 
1096
        """
 
1097
        buf = tarinfo.getheader()
 
1098
        sp = _ringbuffer()
 
1099
        pos = 386
 
1100
        lastpos = 0l
 
1101
        realpos = 0l
 
1102
        try:
 
1103
            # There are 4 possible sparse structs in the
 
1104
            # first header.
 
1105
            for i in range(4):
 
1106
                offset = int(buf[pos:pos + 12], 8)
 
1107
                numbytes = int(buf[pos + 12:pos + 24], 8)
 
1108
                if offset > lastpos:
 
1109
                    sp.append(_hole(lastpos, offset - lastpos))
 
1110
                sp.append(_data(offset, numbytes, realpos))
 
1111
                realpos += numbytes
 
1112
                lastpos = offset + numbytes
 
1113
                pos += 24
 
1114
 
 
1115
            isextended = ord(buf[482])
 
1116
            origsize = int(buf[483:495], 8)
 
1117
 
 
1118
            # If the isextended flag is given,
 
1119
            # there are extra headers to process.
 
1120
            while isextended == 1:
 
1121
                buf = self.fileobj.read(BLOCKSIZE)
 
1122
                self.offset += BLOCKSIZE
 
1123
                pos = 0
 
1124
                for i in range(21):
 
1125
                    offset = int(buf[pos:pos + 12], 8)
 
1126
                    numbytes = int(buf[pos + 12:pos + 24], 8)
 
1127
                    if offset > lastpos:
 
1128
                        sp.append(_hole(lastpos, offset - lastpos))
 
1129
                    sp.append(_data(offset, numbytes, realpos))
 
1130
                    realpos += numbytes
 
1131
                    lastpos = offset + numbytes
 
1132
                    pos += 24
 
1133
                isextended = ord(buf[504])
 
1134
        except ValueError:
 
1135
            pass
 
1136
        if lastpos < origsize:
 
1137
            sp.append(_hole(lastpos, origsize - lastpos))
 
1138
 
 
1139
        tarinfo.sparse = sp
 
1140
        return origsize
 
1141
 
 
1142
    def _dbg(self, level, msg):
 
1143
        if level <= self.debug:
 
1144
            sys.stdout.write(msg)
 
1145
# class TarFile
 
1146
 
 
1147
class TarIter:
 
1148
    """Iterator Class.
 
1149
 
 
1150
       for tarinfo in TarFile(...):
 
1151
           suite...
 
1152
    """
 
1153
 
 
1154
    def __init__(self, tarfile):
 
1155
        """Construct a TarIter instance.
 
1156
        """
 
1157
        self.tarfile = tarfile
 
1158
    def __iter__(self):
 
1159
        """Return iterator object.
 
1160
        """
 
1161
        return self
 
1162
    def next(self):
 
1163
        """Return the next item using TarFile's next() method.
 
1164
           When all members have been read, set TarFile as _loaded.
 
1165
        """
 
1166
        tarinfo = self.tarfile.next()
 
1167
        if not tarinfo:
 
1168
            self.tarfile._loaded = 1
 
1169
            raise StopIteration
 
1170
        return tarinfo
 
1171
# class TarIter
 
1172
 
 
1173
# Helper classes for sparse file support
 
1174
class _section:
 
1175
    """Base class for _data and _hole.
 
1176
    """
 
1177
    def __init__(self, offset, size):
 
1178
        self.offset = offset
 
1179
        self.size = size
 
1180
    def __contains__(self, offset):
 
1181
        return self.offset <= offset < self.offset + self.size
 
1182
 
 
1183
class _data(_section):
 
1184
    """Represent a data section in a sparse file.
 
1185
    """
 
1186
    def __init__(self, offset, size, realpos):
 
1187
        _section.__init__(self, offset, size)
 
1188
        self.realpos = realpos
 
1189
 
 
1190
class _hole(_section):
 
1191
    """Represent a hole section in a sparse file.
 
1192
    """
 
1193
    pass
 
1194
 
 
1195
class _ringbuffer(list):
 
1196
    """Ringbuffer class which increases performance
 
1197
       over a regular list.
 
1198
    """
 
1199
    def __init__(self):
 
1200
        self.idx = 0
 
1201
    def find(self, offset):
 
1202
        idx = self.idx
 
1203
        while 1:
 
1204
            item = self[idx]
 
1205
            if offset in item:
 
1206
                break
 
1207
            idx += 1
 
1208
            if idx == len(self):
 
1209
                idx = 0
 
1210
            if idx == self.idx:
 
1211
                # End of File
 
1212
                return None
 
1213
        self.idx = idx
 
1214
        return item
 
1215
 
 
1216
class _FileObject:
 
1217
    """File-like object for reading an archive member,
 
1218
       is returned by TarFile.extractfile().
 
1219
       Support for sparse files included.
 
1220
    """
 
1221
 
 
1222
    def __init__(self, tarfile, tarinfo):
 
1223
        self.fileobj = tarfile.fileobj
 
1224
        self.name    = tarinfo.name
 
1225
        self.mode    = "r"
 
1226
        self.closed  = 0
 
1227
        self.offset  = tarinfo.offset_data
 
1228
        self.size    = tarinfo.size
 
1229
        self.pos     = 0l
 
1230
        self.linebuffer = ""
 
1231
        if tarinfo.issparse():
 
1232
            self.sparse = tarinfo.sparse
 
1233
            self.read = self._readsparse
 
1234
        else:
 
1235
            self.read = self._readnormal
 
1236
 
 
1237
    def readline(self, size=-1):
 
1238
        """Read a line with approx. size.
 
1239
           If size is negative, read a whole line.
 
1240
           readline() and read() must not be mixed up (!).
 
1241
        """
 
1242
        if size < 0:
 
1243
            size = sys.maxint
 
1244
 
 
1245
        nl = self.linebuffer.find("\n")
 
1246
        if nl >= 0:
 
1247
            nl = min(nl, size)
 
1248
        else:
 
1249
            size -= len(self.linebuffer)
 
1250
            while nl < 0:
 
1251
                buf = self.read(min(size, 100))
 
1252
                if not buf:
 
1253
                    break
 
1254
                self.linebuffer += buf
 
1255
                size -= len(buf)
 
1256
                if size <= 0:
 
1257
                    break
 
1258
                nl = self.linebuffer.find("\n")
 
1259
            if nl == -1:
 
1260
                s = self.linebuffer
 
1261
                self.linebuffer = ""
 
1262
                return s
 
1263
        buf = self.linebuffer[:nl]
 
1264
        self.linebuffer = self.linebuffer[nl + 1:]
 
1265
        while buf[-1:] == "\r":
 
1266
            buf = buf[:-1]
 
1267
        return buf + "\n"
 
1268
 
 
1269
    def readlines(self):
 
1270
        """Return a list with all (following) lines.
 
1271
        """
 
1272
        result = []
 
1273
        while 1:
 
1274
            line = self.readline()
 
1275
            if not line: break
 
1276
            result.append(line)
 
1277
        return result
 
1278
 
 
1279
    def _readnormal(self, size=None):
 
1280
        """Read operation for regular files.
 
1281
        """
 
1282
        if self.closed:
 
1283
            raise ValueError, "I/O operation on closed file"
 
1284
        self.fileobj.seek(self.offset + self.pos)
 
1285
        bytesleft = self.size - self.pos
 
1286
        if size is None:
 
1287
            bytestoread = bytesleft
 
1288
        else:
 
1289
            bytestoread = min(size, bytesleft)
 
1290
        self.pos += bytestoread
 
1291
        return self.fileobj.read(bytestoread)
 
1292
 
 
1293
    def _readsparse(self, size=None):
 
1294
        """Read operation for sparse files.
 
1295
        """
 
1296
        if self.closed:
 
1297
            raise ValueError, "I/O operation on closed file"
 
1298
 
 
1299
        if size is None:
 
1300
            size = self.size - self.pos
 
1301
 
 
1302
        data = ""
 
1303
        while size > 0:
 
1304
            buf = self._readsparsesection(size)
 
1305
            if not buf:
 
1306
                break
 
1307
            size -= len(buf)
 
1308
            data += buf
 
1309
        return data
 
1310
 
 
1311
    def _readsparsesection(self, size):
 
1312
        """Read a single section of a sparse file.
 
1313
        """
 
1314
        section = self.sparse.find(self.pos)
 
1315
 
 
1316
        if section is None:
 
1317
            return ""
 
1318
 
 
1319
        toread = min(size, section.offset + section.size - self.pos)
 
1320
        if isinstance(section, _data):
 
1321
            realpos = section.realpos + self.pos - section.offset
 
1322
            self.pos += toread
 
1323
            self.fileobj.seek(self.offset + realpos)
 
1324
            return self.fileobj.read(toread)
 
1325
        else:
 
1326
            self.pos += toread
 
1327
            return "\0" * toread
 
1328
 
 
1329
    def tell(self):
 
1330
        """Return the current file position.
 
1331
        """
 
1332
        return self.pos
 
1333
 
 
1334
    def seek(self, pos, whence=0):
 
1335
        """Seek to a position in the file.
 
1336
        """
 
1337
        self.linebuffer = ""
 
1338
        if whence == 0:
 
1339
            self.pos = min(max(pos, 0), self.size)
 
1340
        if whence == 1:
 
1341
            if pos < 0:
 
1342
                self.pos = max(self.pos + pos, 0)
 
1343
            else:
 
1344
                self.pos = min(self.pos + pos, self.size)
 
1345
        if whence == 2:
 
1346
            self.pos = max(min(self.size + pos, self.size), 0)
 
1347
 
 
1348
    def close(self):
 
1349
        """Close the file object.
 
1350
        """
 
1351
        self.closed = 1
 
1352
#class _FileObject
 
1353
 
 
1354
#---------------------------------------------
 
1355
# zipfile compatible TarFile class
 
1356
#
 
1357
# for details consult zipfile's documentation
 
1358
#---------------------------------------------
 
1359
import cStringIO
 
1360
 
 
1361
TAR_PLAIN = 0           # zipfile.ZIP_STORED
 
1362
TAR_GZIPPED = 8         # zipfile.ZIP_DEFLATED
 
1363
class TarFileCompat:
 
1364
    """TarFile class compatible with standard module zipfile's
 
1365
       ZipFile class.
 
1366
    """
 
1367
    def __init__(self, file, mode="r", compression=TAR_PLAIN):
 
1368
        if compression == TAR_PLAIN:
 
1369
            self.tarfile = open(file, mode)
 
1370
        elif compression == TAR_GZIPPED:
 
1371
            self.tarfile = gzopen(file, mode)
 
1372
        else:
 
1373
            raise ValueError, "unknown compression constant"
 
1374
        if mode[0:1] == "r":
 
1375
            import time
 
1376
            members = self.tarfile.getmembers()
 
1377
            for i in range(len(members)):
 
1378
                m = members[i]
 
1379
                m.filename = m.name
 
1380
                m.file_size = m.size
 
1381
                m.date_time = time.gmtime(m.mtime)[:6]
 
1382
    def namelist(self):
 
1383
        return map(lambda m: m.name, self.infolist())
 
1384
    def infolist(self):
 
1385
        return filter(lambda m: m.type in REGULAR_TYPES,
 
1386
                      self.tarfile.getmembers())
 
1387
    def printdir(self):
 
1388
        self.tarfile.list()
 
1389
    def testzip(self):
 
1390
        return
 
1391
    def getinfo(self, name):
 
1392
        return self.tarfile.getmember(name)
 
1393
    def read(self, name):
 
1394
        return self.tarfile.extractfile(self.tarfile.getmember(name)).read()
 
1395
    def write(self, filename, arcname=None, compress_type=None):
 
1396
        self.tarfile.add(filename, arcname)
 
1397
    def writestr(self, zinfo, bytes):
 
1398
        import calendar
 
1399
        zinfo.name = zinfo.filename
 
1400
        zinfo.size = zinfo.file_size
 
1401
        zinfo.mtime = calendar.timegm(zinfo.date_time)
 
1402
        self.tarfile.addfile(zinfo, cStringIO.StringIO(bytes))
 
1403
    def close(self):
 
1404
        self.tarfile.close()
 
1405
#class TarFileCompat
 
1406
 
 
1407
if __name__ == "__main__":
 
1408
    # a "light-weight" implementation of GNUtar ;-)
 
1409
    usage = """
 
1410
Usage: %s [options] [files]
 
1411
 
 
1412
-h      display this help message
 
1413
-c      create a tarfile
 
1414
-r      append to an existing archive
 
1415
-x      extract archive
 
1416
-t      list archive contents
 
1417
-f FILENAME
 
1418
        use archive FILENAME, else STDOUT (-c)
 
1419
-z      filter archive through gzip
 
1420
-C DIRNAME
 
1421
        with opt -x:     extract to directory DIRNAME
 
1422
        with opt -c, -r: put files to archive under DIRNAME
 
1423
-v      verbose output
 
1424
-q      quiet
 
1425
 
 
1426
wildcards *, ?, [seq], [!seq] are accepted.
 
1427
    """ % sys.argv[0]
 
1428
 
 
1429
    import getopt, glob
 
1430
    try:
 
1431
        opts, args = getopt.getopt(sys.argv[1:], "htcrzxf:C:qv")
 
1432
    except getopt.GetoptError, e:
 
1433
        print
 
1434
        print "ERROR:", e
 
1435
        print usage
 
1436
        sys.exit(0)
 
1437
 
 
1438
    file = None
 
1439
    mode = None
 
1440
    dir = None
 
1441
    comp = 0
 
1442
    debug = 0
 
1443
    for o, a in opts:
 
1444
        if o == "-t": mode = "l"        # list archive
 
1445
        if o == "-c": mode = "w"        # write to archive
 
1446
        if o == "-r": mode = "a"        # append to archive
 
1447
        if o == "-x": mode = "r"        # extract from archive
 
1448
        if o == "-f": file = a          # specify filename else use stdout
 
1449
        if o == "-C": dir = a           # change to dir
 
1450
        if o == "-z": comp = 1          # filter through gzip
 
1451
        if o == "-v": debug = 2         # verbose mode
 
1452
        if o == "-q": debug = 0         # quiet mode
 
1453
        if o == "-h":                   # help message
 
1454
            print usage
 
1455
            sys.exit(0)
 
1456
 
 
1457
    if not mode:
 
1458
        print usage
 
1459
        sys.exit(0)
 
1460
 
 
1461
    if comp:
 
1462
        func = gzopen
 
1463
    else:
 
1464
        func = open
 
1465
 
 
1466
    if not file or file == "-":
 
1467
        if mode != "w":
 
1468
            print usage
 
1469
            sys.exit(0)
 
1470
        debug = 0
 
1471
        # If under Win32, set stdout to binary.
 
1472
        try:
 
1473
            import msvcrt
 
1474
            msvcrt.setmode(1, os.O_BINARY)
 
1475
        except ImportError:
 
1476
            pass
 
1477
        tarfile = func("sys.stdout.tar", mode, 9, sys.stdout)
 
1478
    else:
 
1479
        if mode == "l":
 
1480
            tarfile = func(file, "r")
 
1481
        else:
 
1482
            tarfile = func(file, mode)
 
1483
 
 
1484
    tarfile.debug = debug
 
1485
 
 
1486
    if mode == "r":
 
1487
        if dir is None:
 
1488
            dir = ""
 
1489
        for tarinfo in tarfile:
 
1490
            tarfile.extract(tarinfo, dir)
 
1491
    elif mode == "l":
 
1492
        tarfile.list(debug)
 
1493
    else:
 
1494
        for arg in args:
 
1495
            files = glob.glob(arg)
 
1496
            for f in files:
 
1497
                tarfile.add(f, dir)
 
1498
    tarfile.close()