在不写入磁盘的情况下提取 lzma 压缩 tar 存档成员
Extract lzma compressed tar archive members without writing to disk
我有一个嵌套的 tar 文件 2 tar 深。最外面的 tar 是 gpg 加密的,没有压缩。里面的tar是lzma。使用磁盘最里面的 tar 我没有任何问题。将最里面的 tar.xz 文件直接传递给 with tarfile.open() as get_lzma
是可行的。该行后面的代码执行时没有错误。我可以提取 tar 成员和 json.load()
数据。
这是一个小文件,数据很敏感。当我使用它时,它必须位于磁盘上,所以我不想解密它并将最里面的 tar 提取到磁盘。所以我想访问内存中的成员。我可以解密到 gpg 文件和 for member in get_lzma.getmembers():
returns 我期望的 tarinfo 对象,所以该成员似乎在那里,我只是不能用它做任何事情。当我 运行 extractfile()
我不能 .read()
结果是 returns <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)
离开这个问题的一天似乎已经弄清了我对这个问题的理解。我将解释我的想法和我的发现。
tldr 我把我的 tarinfo 和 tarfile 和 exfileobjects 都搞混了。
根据 OP,传递给 open()
的文件在幕后表示为 <_io.BufferedReader name='myfile.tar.xz'>
。所以让代码与未加密的文件和 open()
一起工作,实际上只能证明文件没有损坏。所以没有问题,我不会再提了。
按照 OP 中的代码示例,从 decrypted_data
对象调用 decrypted_data.data
returns 原始字节字符串。这些原始字节是我的 2 tar 个文件。解压缩 tar 和压缩 tar 嵌套结构。字符串 starts b'myfile.tar.xz\x00\x00\x00\......
等。我们将其包装为一个字节对象 io.BytesIO(decrypted_data.data)
以便我们有一个可以使用的接口,因此它可以传递给 tarfile.open()
最初。到目前为止一切顺利,但这正是事情tar注定会出错的地方。
我决定在以下代码中使用 2 个上下文管理器。在 OP 中我调用了 tarfile.open()
两次,我想我一定假设有一个提取操作以及 mode='r'
。您可以看到我现在在每个上下文管理器的 member
上对 extractfile()
进行了 2 次调用。第一个 extractfile()
从未压缩的 tar 中提取 tar.xz。这是我最终作为 mode=r:xz
传递给下一个上下文管理器的 ExFileObject
"file"。在 OP 中,它是一个 TarInfo
对象,而不是提取的数据,这是错误的。
对 extractfile()
的第二次调用是在第二个上下文管理器的 member
上完成的,以便从 myfile.tar.xz
.
获取 readable_members_value
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()
.
我有一个嵌套的 tar 文件 2 tar 深。最外面的 tar 是 gpg 加密的,没有压缩。里面的tar是lzma。使用磁盘最里面的 tar 我没有任何问题。将最里面的 tar.xz 文件直接传递给 with tarfile.open() as get_lzma
是可行的。该行后面的代码执行时没有错误。我可以提取 tar 成员和 json.load()
数据。
这是一个小文件,数据很敏感。当我使用它时,它必须位于磁盘上,所以我不想解密它并将最里面的 tar 提取到磁盘。所以我想访问内存中的成员。我可以解密到 gpg 文件和 for member in get_lzma.getmembers():
returns 我期望的 tarinfo 对象,所以该成员似乎在那里,我只是不能用它做任何事情。当我 运行 extractfile()
我不能 .read()
结果是 returns <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)
离开这个问题的一天似乎已经弄清了我对这个问题的理解。我将解释我的想法和我的发现。
tldr 我把我的 tarinfo 和 tarfile 和 exfileobjects 都搞混了。
根据 OP,传递给 open()
的文件在幕后表示为 <_io.BufferedReader name='myfile.tar.xz'>
。所以让代码与未加密的文件和 open()
一起工作,实际上只能证明文件没有损坏。所以没有问题,我不会再提了。
按照 OP 中的代码示例,从 decrypted_data
对象调用 decrypted_data.data
returns 原始字节字符串。这些原始字节是我的 2 tar 个文件。解压缩 tar 和压缩 tar 嵌套结构。字符串 starts b'myfile.tar.xz\x00\x00\x00\......
等。我们将其包装为一个字节对象 io.BytesIO(decrypted_data.data)
以便我们有一个可以使用的接口,因此它可以传递给 tarfile.open()
最初。到目前为止一切顺利,但这正是事情tar注定会出错的地方。
我决定在以下代码中使用 2 个上下文管理器。在 OP 中我调用了 tarfile.open()
两次,我想我一定假设有一个提取操作以及 mode='r'
。您可以看到我现在在每个上下文管理器的 member
上对 extractfile()
进行了 2 次调用。第一个 extractfile()
从未压缩的 tar 中提取 tar.xz。这是我最终作为 mode=r:xz
传递给下一个上下文管理器的 ExFileObject
"file"。在 OP 中,它是一个 TarInfo
对象,而不是提取的数据,这是错误的。
对 extractfile()
的第二次调用是在第二个上下文管理器的 member
上完成的,以便从 myfile.tar.xz
.
readable_members_value
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()
.