Извлечение сжатых элементов tar-архива lzma без записи на диск - PullRequest
0 голосов
/ 05 мая 2020

У меня есть вложенный tar-файл глубиной в 2 цента. Самый внешний tar зашифрован и не сжат. Внутренний tar - это lzma. Работая с самым внутренним tar с диска, проблем не возникает. Передача самого внутреннего файла tar.xz напрямую в with tarfile.open() as get_lzma работает. Код, следующий за этой строкой, выполняется без ошибок. Я могу извлечь члены tar и json.load() данные.

Это небольшой файл, данные конфиденциальны. Он должен находиться на диске, пока я работаю с ним, поэтому я не хочу его расшифровывать и извлекать на диск самый внутренний tar. Итак, я хотел бы получить доступ к членам в памяти. Я могу расшифровать файл gpg, и for member in get_lzma.getmembers(): вернет объекты tarinfo, которые я ожидал, так что член оказывается там, я просто ничего не могу с ним сделать. Когда я запускаю extractfile(), я не могу .read() результат, поскольку он возвращает <ExFileObject name=None>.

На данный момент мне просто любопытно, почему это не работает.

В случае, если файловая структура неясна, вот как он находится на диске:

file.tar.gpg <- is a tar file
 file.tar.xz <- is a compressed tar file
   member1
   memberN
   json.load(file_o)
  File "/home/user/.pyenv/versions/3.8.2/lib/python3.8/json/__init__.py", line 293, in load
    return loads(fp.read(),
  File "/home/user/.pyenv/versions/3.8.2/lib/python3.8/tarfile.py", line 681, in read
    self.fileobj.seek(offset + (self.position - start))
  File "/home/user/.pyenv/versions/3.8.2/lib/python3.8/lzma.py", line 253, in seek
    return self._buffer.seek(offset, whence)
  File "/home/user/.pyenv/versions/3.8.2/lib/python3.8/_compression.py", line 143, in seek
    data = self.read(min(io.DEFAULT_BUFFER_SIZE, offset))
  File "/home/user/.pyenv/versions/3.8.2/lib/python3.8/_compression.py", line 103, in read
    data = self._decompressor.decompress(rawblock, size)
_lzma.LZMAError: Input format not supported by decoder
   with open(gpg_encrypted_tar_archive, 'rb') as f: 
        try:
            decrypted_data = gpg.decrypt_file(f, passphrase=passph)
            assert decrypted_data.ok
        except AssertionError:
            print(f"Decryption failed with message '{decrypted_data.status}' and status '{decrypted_data.ok}'")

        io_bytes_file_like_object = io.BytesIO(decrypted_data.data)

        # untar the parent archive
        tarfile.open(fileobj=io_bytes_file_like_object, mode='r')

        with tarfile.open(fileobj=io_bytes_file_like_object, mode='r:xz', debug=3, errorlevel=2) as get_lzma:

            for member in get_lzma.getmembers():

                if member.isfile():
                    file_o = get_lzma.extractfile(member)
                    json.load(file_o)

1 Ответ

0 голосов
/ 06 мая 2020

Кажется, день, оставшийся до этой проблемы, прояснил мое понимание проблемы. Я объясню свое мышление и то, что я нашел.

tldr Я перепутал tarinfo, tarfile и exfileobjects.

Согласно OP файлы, переданные в open(), под обложками представлены как <_io.BufferedReader name='myfile.tar.xz'>. Таким образом, получение кода для работы с незашифрованным файлом и open() на самом деле только доказывает, что файл не был поврежден. Так что никаких проблем, я не буду повторять это снова.

Следуя примеру кода в OP, вызов decrypted_data.data возвращает необработанную байтовую строку из объекта decrypted_data. Эти необработанные байты - мои 2 файла tar. Несжатый tar и вложенная структура сжатого tar. Строка начинается с b'myfile.tar.xz\x00\x00\x00\...... et c. Мы оборачиваем это нас как байтовый объект io.BytesIO(decrypted_data.data), чтобы у нас был интерфейс для работы, и чтобы его можно было передать в tarfile.open() изначально. Пока все хорошо, но тут все начало go неправильно.

Я решил go с двумя менеджерами контекста в следующем коде. В OP я дважды звонил по номеру tarfile.open(), наверное, я предполагал, что была операция извлечения вместе с mode='r'. Вы можете видеть, что теперь я делаю 2 вызова extractfile() на member каждого диспетчера контекста. Первый extractfile() извлекает tar.xz из несжатого tar. Это «файл» ExFileObject, который я в конечном итоге передаю следующему диспетчеру контекста как mode=r:xz. В OP это был бы объект TarInfo, а не извлеченные данные, что неверно.

Второй вызов extractfile() выполняется на member второго диспетчера контекста, чтобы получить readable_members_value от myfile.tar.xz.

with tarfile.open(fileobj=io_bytes_file_like_object, mode='r') as uncompressed_tar_file:

    # uncompressed_tar_file is tarfile.TarFile object
    for member in uncompressed_tar_file.getmembers():

      # member is TarInfo object
        tar_file_object = uncompressed_tar_file.extractfile(member)

        # tar_file_object is ExFileObject
        with tarfile.open(fileobj=tar_file_object, mode='r:xz', debug=3, errorlevel=2) as lzma_compressed_tar_file:
            for member in lzma_compressed_tar_file.getmembers():
                if member.isfile():
                    readable_members_value = lzma_compressed_tar_file.extractfile(member)

                    # now works where it failed before equivalent to something like file_o.read() in OP
                    print(readable_members_value.read())

                    decoded_readable_member_value = readable_member_value.read().decode("utf-8")
                    json_data = json.loads(decoded_readable_member_value)
                    print(json_data)

Остальная часть кода практически идентичен, за исключением последних нескольких строк. Вы можете видеть по именам переменных в OP, я ожидал, что file_o будет файловым объектом. json.load(file_o) будет работать с указателем файла, но в этом случае readable_member.read() возвращает литерал байтов b'', поэтому на самом деле json.loads() мне не нужно json.load().

...