Comprobando la integridad del archivo tar en Python

Estoy trabajando para convertir mi script de copia de seguridad de shell a Python. Una de las características de mi script anterior era verificar la integridad del archivo tar creado: gzip -t.

Esto parece ser un poco complicado en Python.

Parece que la única forma de hacerlo es leyendo cada uno de los objetos TarInfo comprimidos dentro del archivo tar.

¿Hay alguna forma de verificar la integridad de un archivo tar, sin extraerlo en el disco o guardarlo en la memoria (en su totalidad)?

La gente buena en #python en freenode sugirió que debería leer cada objeto TarInfo parte por parte, descartando cada parte leída.

    Debo admitir que no tengo idea de cómo hacer esto, ya que acabo de iniciar Python.

    Imagina que tengo un archivo tar de 30 GB que contiene archivos de 1kb a 10GB …

    Esta es la solución que empecé a escribir:

    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() 

    Este código está lejos de ser terminado. No me atrevería a ejecutar esto en un enorme archivo tar de 30 GB, porque en un momento dado, el control sería un objeto de más de 10 GB (si tengo archivos tan grandes dentro del archivo tar)

    Bono: Intenté corromper manualmente zero.tar.gz (editor hexadecimal: edito algunos bytes en el archivo intermedio). La primera, excepto que no captura IOError … Aquí está la salida:

     Traceback (most recent call last): File "./test.py", line 31, in  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 

    Solo una pequeña mejora en la respuesta de Aya para hacer las cosas un poco más idiomáticas (aunque estoy eliminando algunas de las comprobaciones de errores para hacer que la mecánica sea más visible):

     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 

    Esto realmente solo elimina el while 1: (a veces se considera un olor de código menor) y la comprobación, if not data: También tenga en cuenta que el uso de with restringe esto a Python 2.7+

    Intenté corromper manualmente zero.tar.gz (editor hexadecimal: editar unos pocos bytes en midfile). El primero excepto que no captura IOError …

    Si miras el rastro, verás que se lanza cuando llamas a tardude.getmembers() , por lo que necesitarás algo como …

     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() 

    En cuanto al problema original, ya casi llegas. Solo necesita leer los datos de su objeto de check con algo como …

     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() 

    … lo que debería garantizar que nunca use más de BLOCK_SIZE bytes de memoria a la vez.

    Además, debes intentar evitar usar …

     try: do_something() except: do_something_else() 

    … porque enmascarará excepciones inesperadas. Intente capturar solo la excepción que realmente pretende manejar, como …

     try: do_something() except IOError: do_something_else() 

    … de lo contrario, será más difícil detectar errores en su código.

    Puede usar el módulo de subprocess para llamar a gzip -t en el archivo …

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

    Si el result no es 0, algo anda mal. Sin embargo, es posible que desee comprobar si gzip está disponible. Escribí una función de utilidad para eso;

     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)