【问题标题】:Checking tarfile integrity in Python在 Python 中检查 tarfile 的完整性
【发布时间】:2013-04-15 10:48:07
【问题描述】:

我正在将我的备份脚本从 shell 转换为 Python。我的旧脚本的功能之一是通过执行以下操作检查创建的 tarfile 的完整性: gzip -t 。

这在 Python 中似乎有点棘手。

似乎唯一的方法是读取 tar 文件中的每个压缩的 TarInfo 对象。

有没有一种方法可以检查 tar 文件的完整性,而无需解压到磁盘或将其保存在内存中(完整地)?

freenode 上#python 上的好人建议我应该逐块读取每个 TarInfo 对象,丢弃读取的每个块。

我必须承认我不知道该怎么做,因为我刚刚开始使用 Python。

假设我有一个 30GB 的 tarfile,其中包含从 1kb 到 10GB 的文件...

这是我开始写的解决方案:

try:
    tardude = tarfile.open("zero.tar.gz")
except:
    print "There was an error opening tarfile. The file might be corrupt or missing."

for member_info in tardude.getmembers():
    try:
        check = tardude.extractfile(member_info.name)
    except:
        print "File: %r is corrupt." % member_info.name

tardude.close()

这段代码远未完成。我不敢在一个巨大的 30GB tar 存档上运行它,因为在某一时刻,检查将是 10+GB 的对象(如果我在 tar 存档中有这么大的文件)

奖金: 我尝试手动破坏 zero.tar.gz(十六进制编辑器 - 编辑几个字节的中间文件)。第一个except没有捕获IOError...这是输出:

Traceback (most recent call last):
  File "./test.py", line 31, in <module>
    for member_info in tardude.getmembers():
  File "/usr/lib/python2.7/tarfile.py", line 1805, in getmembers
    self._load()        # all members, we first have to
  File "/usr/lib/python2.7/tarfile.py", line 2380, in _load
    tarinfo = self.next()
  File "/usr/lib/python2.7/tarfile.py", line 2315, in next
    self.fileobj.seek(self.offset)
  File "/usr/lib/python2.7/gzip.py", line 429, in seek
    self.read(1024)
  File "/usr/lib/python2.7/gzip.py", line 256, in read
    self._read(readsize)
  File "/usr/lib/python2.7/gzip.py", line 320, in _read
    self._read_eof()
  File "/usr/lib/python2.7/gzip.py", line 342, in _read_eof
    hex(self.crc)))
IOError: CRC check failed 0xe5384b87 != 0xdfe91e1L

【问题讨论】:

  • 我已经尝试了包含大量文件的 tarfile 模块,问题是 tarfile.TarFile 模块将所有读取(或写入)成员存储到它的“成员”中。因此,当您有意读取包含大量小文件的 tarbomb 时,会占用大量内存。

标签: python error-handling integrity tarfile


【解决方案1】:

Aya's 的回答稍作改进,让事情变得更加地道(尽管我删除了一些错误检查以使机制更加明显):

BLOCK_SIZE = 1024

with tarfile.open("zero.tar.gz") as tardude:
    for member in tardude.getmembers():
        with tardude.extractfile(member.name) as target:
            for chunk in iter(lambda: target.read(BLOCK_SIZE), b''):
                pass

这实际上只是删除了while 1:(有时被认为是轻微的代码异味)和if not data: 检查。另请注意,with 的使用将其限制为 Python 2.7+

【讨论】:

    【解决方案2】:

    我尝试手动破坏 zero.tar.gz(十六进制编辑器 - 编辑几个字节 中间文件)。第一个 except 没有捕获 IOError...

    如果您查看回溯,当您调用 tardude.getmembers() 时,您会看到它被抛出,因此您需要类似...

    try:
        tardude = tarfile.open("zero.tar.gz")
    except:
        print "There was an error opening tarfile. The file might be corrupt or missing."
    
    try:
        members = tardude.getmembers()
    except:
        print "There was an error reading tarfile members."
    
    for member_info in members:
        try:
            check = tardude.extractfile(member_info.name)
        except:
            print "File: %r is corrupt." % member_info.name
    
    tardude.close()
    

    至于最初的问题,你几乎就在那里。您只需要从您的 check 对象中读取数据,例如...

    BLOCK_SIZE = 1024
    
    try:
        tardude = tarfile.open("zero.tar.gz")
    except:
        print "There was an error opening tarfile. The file might be corrupt or missing."
    
    try:
        members = tardude.getmembers()
    except:
        print "There was an error reading tarfile members."
    
    for member_info in members:
        try:            
            check = tardude.extractfile(member_info.name)
            while 1:
                data = check.read(BLOCK_SIZE)
                if not data:
                    break
        except:
            print "File: %r is corrupt." % member_info.name
    
    tardude.close()
    

    ...这应该确保您一次使用的内存永远不会超过 BLOCK_SIZE 字节。

    另外,你应该尽量避免使用...

    try:
        do_something()
    except:
        do_something_else()
    

    ...因为它会掩盖意外的异常。尝试仅捕获您实际打算处理的异常,例如...

    try:
        do_something()
    except IOError:
        do_something_else()
    

    ...否则您会发现更难检测代码中的错误。

    【讨论】:

    • 太棒了!重新审视“除了:”的东西......我知道......我通常有“除了这个:”“除了那个:”......“除了:”,但这只是为了测试:D
    • 我做了以下事情:pastie.org/7585277。如您所见,有一个检查 member_info.isfile,因为解析目录总是会出错。除了普通文件,我还想跳过解析任何内容。
    • 您需要检查 for 循环内的 member_info 对象。像if not member_info.isfile(): continue 这样的东西应该可以工作。
    • 这有点帮助,但也没有(由于 except: 和 tarfile 问题,您能否从上面的代码示例中编辑它们?):我 仍然得到坏焦油以通过此测试 - 唯一 确保焦油正确的方法是调用 tardude.extractall("/some/tmp/dir")
    【解决方案3】:

    您可以使用subprocess 模块在文件上调用gzip -t...

    from subprocess import call
    import os
    
    with open(os.devnull, 'w') as bb:
        result = call(['gzip', '-t', "zero.tar.gz"], stdout=bb, stderr=bb)
    

    如果result 不为 0,则有问题。不过,您可能想检查 gzip 是否可用。我为此写了一个实用函数;

    import subprocess
    import sys
    import os
    
    def checkfor(args, rv = 0):
        """Make sure that a program necessary for using this script is
        available.
    
        Arguments:
        args  -- string or list of strings of commands. A single string may
                 not contain spaces.
        rv    -- expected return value from evoking the command.
        """
        if isinstance(args, str):
            if ' ' in args:
                raise ValueError('no spaces in single command allowed')
            args = [args]
        try:
            with open(os.devnull, 'w') as bb:
                rc = subprocess.call(args, stdout=bb, stderr=bb)
            if rc != rv:
                raise OSError
        except OSError as oops:
            outs = "Required program '{}' not found: {}."
            print(outs.format(args[0], oops.strerror))
            sys.exit(1)
    

    【讨论】:

    • 对不起,我忘了说我想使用pythonic方法,而不求助于子进程。不过,谢谢你的回答!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-12
    • 2013-12-17
    • 1970-01-01
    • 1970-01-01
    • 2011-05-16
    • 1970-01-01
    相关资源
    最近更新 更多