Skip to content

Commit 1ef8168

Browse files
authored
Add vmtar tool (#51)
1 parent 317023b commit 1ef8168

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+200
-57
lines changed

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ dist/
66
*.pyc
77
__pycache__/
88
.pytest_cache/
9-
tests/docs/api
10-
tests/docs/build
9+
tests/_docs/api
10+
tests/_docs/build
1111
.tox/

dissect/hypervisor/tools/vmtar.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import tarfile
2+
3+
from dissect.hypervisor.util import vmtar
4+
5+
6+
def main() -> None:
7+
# We just want to run the main function of the tarfile module, but with our VisorTarFile and is_tarfile functions
8+
type(tarfile.main)(
9+
tarfile.main.__code__,
10+
tarfile.main.__globals__
11+
| {
12+
"TarFile": vmtar.VisorTarFile,
13+
"is_tarfile": vmtar.is_tarfile,
14+
"open": vmtar.open,
15+
},
16+
)()
17+
18+
19+
if __name__ == "__main__":
20+
main()

dissect/hypervisor/util/vmtar.py

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
import struct
77
import tarfile
8+
from io import BytesIO
9+
from typing import BinaryIO, Final
810

911

1012
class VisorTarInfo(tarfile.TarInfo):
@@ -49,9 +51,72 @@ def _proc_member(self, tarfile: tarfile.TarFile) -> VisorTarInfo | tarfile.TarIn
4951
return super()._proc_member(tarfile)
5052

5153

52-
def VisorTarFile(*args, **kwargs) -> tarfile.TarFile:
53-
return tarfile.TarFile(*args, **kwargs, tarinfo=VisorTarInfo)
54+
class VisorTarFile(tarfile.TarFile):
55+
def __init__(self, *args, **kwargs) -> None:
56+
super().__init__(*args, **kwargs, tarinfo=VisorTarInfo)
5457

55-
56-
def open(*args, **kwargs) -> tarfile.TarFile:
57-
return tarfile.open(*args, **kwargs, tarinfo=VisorTarInfo)
58+
@classmethod
59+
def visoropen(cls, name: str, mode: str = "r", fileobj: BinaryIO | None = None, **kwargs) -> VisorTarFile:
60+
"""Open a visor tar file for reading. Supports gzip and lzma compression."""
61+
if mode not in ("r",):
62+
raise tarfile.TarError("visor currently only supports read mode")
63+
64+
try:
65+
from gzip import GzipFile
66+
except ImportError:
67+
raise tarfile.CompressionError("gzip module is not available") from None
68+
69+
try:
70+
from lzma import LZMAError, LZMAFile
71+
except ImportError:
72+
raise tarfile.CompressionError("lzma module is not available") from None
73+
74+
compressed = False
75+
76+
try:
77+
t = cls.taropen(name, mode, fileobj, **kwargs)
78+
except Exception:
79+
try:
80+
fileobj = GzipFile(name, mode + "b", fileobj=fileobj)
81+
except OSError as e:
82+
if fileobj is not None and mode == "r":
83+
raise tarfile.ReadError("not a visor file") from e
84+
raise
85+
86+
try:
87+
t = cls.taropen(name, mode, fileobj, **kwargs)
88+
except Exception:
89+
fileobj.seek(0)
90+
fileobj = LZMAFile(fileobj or name, mode) # noqa: SIM115
91+
92+
try:
93+
t = cls.taropen(name, mode, fileobj, **kwargs)
94+
except (LZMAError, EOFError, OSError) as e:
95+
fileobj.close()
96+
if mode == "r":
97+
raise tarfile.ReadError("not a visor file") from e
98+
raise
99+
except:
100+
fileobj.close()
101+
raise
102+
103+
compressed = True
104+
105+
# If we get here, we have a valid visor tar file
106+
if fileobj is not None and compressed:
107+
# Just read the entire file into memory, it's probably small
108+
fileobj.seek(0)
109+
fileobj = BytesIO(fileobj.read())
110+
111+
t = cls.taropen(name, mode, fileobj, **kwargs)
112+
113+
t._extfileobj = False
114+
return t
115+
116+
# Only allow opening visor tar files
117+
OPEN_METH: Final[dict[str, str]] = {"visor": "visoropen"}
118+
119+
120+
open = VisorTarFile.open
121+
122+
is_tarfile = type(tarfile.is_tarfile)(tarfile.is_tarfile.__code__, tarfile.is_tarfile.__globals__ | {"open": open})

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,11 @@ dev = [
4848

4949
[project.scripts]
5050
envelope-decrypt = "dissect.hypervisor.tools.envelope:main"
51+
vmtar = "dissect.hypervisor.tools.vmtar:main"
5152

5253
[tool.ruff]
5354
line-length = 120
54-
required-version = ">=0.9.0"
55+
required-version = ">=0.11.0"
5556

5657
[tool.ruff.format]
5758
docstring-code-format = true
@@ -94,7 +95,7 @@ select = [
9495
ignore = ["E203", "B904", "UP024", "ANN002", "ANN003", "ANN204", "ANN401", "SIM105", "TRY003"]
9596

9697
[tool.ruff.lint.per-file-ignores]
97-
"tests/docs/**" = ["INP001"]
98+
"tests/_docs/**" = ["INP001"]
9899

99100
[tool.ruff.lint.isort]
100101
known-first-party = ["dissect.hypervisor"]
File renamed without changes.
File renamed without changes.

tests/data/expanding.hdd/expanding.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz renamed to tests/_data/disk/hdd/expanding.hdd/expanding.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz

File renamed without changes.

0 commit comments

Comments
 (0)