2013-04-15 2 views
0

Я работаю над преобразованием моего сценария резервного копирования из оболочки в Python. Одна из особенностей моего старого скрипта заключалась в том, чтобы проверить созданный tarfile на целостность, выполнив: gzip -t.Проверка целостности tarfile в Python

В Python это кажется немного сложным.

Кажется, что единственный способ сделать это - прочитать каждый из сжатых объектов TarInfo в tarfile.

Есть ли способ проверить tarfile для целостности, без извлечения на диск или сохранения его в памяти (в полном объеме)?

Хорошие люди на #python on freenode предположили, что я должен прочитать каждый объект TarInfo, кусок по куску, отбросив каждый кусок, прочитанный.

Должен признаться, что я понятия не имею, как это сделать, поскольку я только что начал Python.

Представьте себе, что у меня есть файл обработан в 30GB, который содержит файлы, начиная от 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() 

Этот код далек от завершения. Я бы не осмелился запустить это на огромном архиве tar-архива на 30 ГБ, потому что в какой-то момент проверка будет объектом 10 + GB (если у меня есть такие огромные файлы в архиве tar)

Бонус: Я пробовал вручную развращать zero.tar.gz (hex editor - редактирование среднего байта). Первый кроме не поймать 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 
+0

Я пробовал модуль tarfile с большим количеством файлов, дело в том, что модуль tarfile.TarFile хранит все члены чтения (или записи) в своих «членах», , Так что это займет много памяти, когда вы намеренно читаете tarbomb с партиями, много маленьких файлов. – tdihp

ответ

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+

1

Вы можете использовать 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) 
+0

Извините, я забыл упомянуть, что хочу использовать питонический подход, не прибегая к подпроцессу. Спасибо за ваш ответ, хотя! – Kaurin

1

Я попытался вручную развращает zero.tar.gz (шестнадцатеричный редактор - редактировать несколько байт midfile). Первый кроме не поймать IOError ...

Если вы посмотрите на TRACEBACK, вы увидите это, появляющейся при вызове 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() 

... в противном случае вы увидите, что более трудно обнаружить ошибки в коде.

+0

О, отлично! Заменяя «за исключением:» материал ... Я знаю об этом ... У меня обычно есть «кроме этого:» «кроме этого:« ... »кроме:», но это было только для тестирования: D – Kaurin

+0

Я сделал следующее: http://pastie.org/7585277. Как вы можете видеть, есть check member_info.isfile, потому что разбор каталогов всегда дает ошибку. Я также хотел бы пропустить синтаксический анализ всего, кроме простых файлов. – Kaurin

+1

Вам нужно будет проверить объект 'member_info' внутри цикла for. Что-то вроде 'if not member_info.isfile(): continue' должно работать. – Aya

Смежные вопросы