Skip to content

Commit

Permalink
Collect empty directories when the source is volatile
Browse files Browse the repository at this point in the history
(DIS-1931)
  • Loading branch information
pyrco committed Aug 22, 2023
1 parent 27ee680 commit 5395f5a
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 40 deletions.
4 changes: 2 additions & 2 deletions acquire/acquire.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,8 +550,8 @@ def _run(cls, target: Target, cli_args: argparse.Namespace, collector: Collector
)
return

collector.output.write_entry(mem_dump_output_path, entry=mem_dump_path)
collector.output.write_entry(mem_dump_errors_output_path, entry=mem_dump_errors_path)
collector.output.write_entry(mem_dump_output_path, mem_dump_path)
collector.output.write_entry(mem_dump_errors_output_path, mem_dump_errors_path)

Check warning on line 554 in acquire/acquire.py

View check run for this annotation

Codecov / codecov/patch

acquire/acquire.py#L553-L554

Added lines #L553 - L554 were not covered by tests
collector.report.add_command_collected(cls.__name__, command_parts)
mem_dump_path.unlink()
mem_dump_errors_path.unlink()
Expand Down
20 changes: 13 additions & 7 deletions acquire/collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ def unbind(self) -> None:
def close(self) -> None:
self.output.close()

def _create_output_path(self, path: Path, base: Optional[str] = None) -> str:
def _get_output_path(self, path: Path, base: Optional[str] = None) -> str:
base = base or self.base
outpath = str(path)

Expand Down Expand Up @@ -299,14 +299,14 @@ def collect_file(
log.info("- Collecting file %s: Skipped (DEDUP)", path)
return

outpath = self._create_output_path(outpath or path, base)
outpath = self._get_output_path(outpath or path, base)

try:
entry = path.get()
if volatile:
self.output.write_volatile(outpath, entry, size)
self.output.write_volatile(outpath, entry, size=size)

Check warning on line 307 in acquire/collector.py

View check run for this annotation

Codecov / codecov/patch

acquire/collector.py#L307

Added line #L307 was not covered by tests
else:
self.output.write_entry(outpath, entry, size)
self.output.write_entry(outpath, entry, size=size)

self.report.add_file_collected(module_name, path)
result = "OK"
Expand All @@ -322,8 +322,8 @@ def collect_file(

def collect_symlink(self, path: fsutil.TargetPath, module_name: Optional[str] = None) -> None:
try:
outpath = self._create_output_path(path)
self.output.write_bytes(outpath, b"", path.get(), 0)
outpath = self._get_output_path(path)
self.output.write_entry(outpath, path.get())

self.report.add_symlink_collected(module_name, path)
result = "OK"
Expand Down Expand Up @@ -359,11 +359,17 @@ def collect_dir(
return
seen_paths.add(resolved)

dir_is_empty = True
for entry in path.iterdir():
dir_is_empty = False
self.collect_path(
entry, seen_paths=seen_paths, module_name=module_name, follow=follow, volatile=volatile
)

if dir_is_empty and volatile:
outpath = self._get_output_path(path)
self.output.write_entry(outpath, entry)

Check warning on line 371 in acquire/collector.py

View check run for this annotation

Codecov / codecov/patch

acquire/collector.py#L370-L371

Added lines #L370 - L371 were not covered by tests

except OSError as error:
if error.errno == errno.ENOENT:
self.report.add_dir_missing(module_name, path)
Expand Down Expand Up @@ -485,7 +491,7 @@ def collect_command_output(
return

def write_bytes(self, destination_path: str, data: bytes) -> None:
self.output.write_bytes(destination_path, data, None)
self.output.write_bytes(destination_path, data)

Check warning on line 494 in acquire/collector.py

View check run for this annotation

Codecov / codecov/patch

acquire/collector.py#L494

Added line #L494 was not covered by tests
self.report.add_file_collected(self.bound_module_name, destination_path)


Expand Down
44 changes: 22 additions & 22 deletions acquire/outputs/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from pathlib import Path
from typing import BinaryIO, Optional, Union

from dissect.target import Target
from dissect.target.filesystem import FilesystemEntry

import acquire.utils
Expand All @@ -12,12 +11,9 @@ class Output:
"""Base class to implement acquire output formats with.
New output formats must sub-class this class.
Args:
target: The target that we're using acquire on.
"""

def init(self, target: Target):
def init(self, path: Path, **kwargs) -> None:
pass

def write(
Expand All @@ -27,31 +23,34 @@ def write(
entry: Optional[Union[FilesystemEntry, Path]],
size: Optional[int] = None,
) -> None:
"""Write a filesystem entry or file-like object to the implemented output type.
"""Write a file-like object to the output.
Args:
output_path: The path of the entry in the output format.
output_path: The path of the entry in the output.
fh: The file-like object of the entry to write.
entry: The optional filesystem entry of the entry to write.
entry: The optional filesystem entry to write.
size: The optional file size in bytes of the entry to write.
"""
raise NotImplementedError()

def write_entry(
self,
output_path: str,
entry: Optional[Union[FilesystemEntry, Path]],
entry: Union[FilesystemEntry, Path],
size: Optional[int] = None,
) -> None:
"""Write a filesystem entry to the output format.
"""Write a filesystem entry to the output.
Args:
output_path: The path of the entry in the output format.
entry: The optional filesystem entry of the entry to write.
output_path: The path of the entry in the output.
entry: The filesystem entry to write.
size: The optional file size in bytes of the entry to write.
"""
with entry.open() as fh:
self.write(output_path, fh, entry, size)
if entry.is_dir() or entry.is_symlink():
self.write_bytes(output_path, b"", entry, size=0)

Check warning on line 50 in acquire/outputs/base.py

View check run for this annotation

Codecov / codecov/patch

acquire/outputs/base.py#L49-L50

Added lines #L49 - L50 were not covered by tests
else:
with entry.open() as fh:
self.write(output_path, fh, entry, size=size)

Check warning on line 53 in acquire/outputs/base.py

View check run for this annotation

Codecov / codecov/patch

acquire/outputs/base.py#L52-L53

Added lines #L52 - L53 were not covered by tests

def write_bytes(
self,
Expand All @@ -63,27 +62,28 @@ def write_bytes(
"""Write raw bytes to the output format.
Args:
output_path: The path of the entry in the output format.
output_path: The path of the entry in the output.
data: The raw bytes to write.
entry: The optional filesystem entry of the entry to write.
entry: The optional filesystem entry to write.
size: The optional file size in bytes of the entry to write.
"""

stream = io.BytesIO(data)
self.write(output_path, stream, entry, size)
self.write(output_path, stream, entry, size=size)

Check warning on line 72 in acquire/outputs/base.py

View check run for this annotation

Codecov / codecov/patch

acquire/outputs/base.py#L72

Added line #L72 was not covered by tests

def write_volatile(
self,
output_path: str,
entry: Optional[Union[FilesystemEntry, Path]],
entry: Union[FilesystemEntry, Path],
size: Optional[int] = None,
) -> None:
"""Write specified path to the output format.
"""Write a filesystem entry to the output.
Handles files that live in volatile filesystems. Such as procfs and sysfs.
Args:
output_path: The path of the entry in the output format.
entry: The optional filesystem entry of the entry to write.
output_path: The path of the entry in the output.
entry: The filesystem entry to write.
size: The optional file size in bytes of the entry to write.
"""
try:
Expand All @@ -96,7 +96,7 @@ def write_volatile(
buf = b""
size = 0

self.write_bytes(output_path, buf, entry, size)
self.write_bytes(output_path, buf, entry, size=size)

Check warning on line 99 in acquire/outputs/base.py

View check run for this annotation

Codecov / codecov/patch

acquire/outputs/base.py#L99

Added line #L99 was not covered by tests

def close(self) -> None:
"""Closes the output."""
Expand Down
33 changes: 27 additions & 6 deletions acquire/outputs/dir.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,39 @@ def __init__(self, path: Path, **kwargs):
self.path = path

def write(
self, output_path: str, fh: BinaryIO, entry: Optional[Union[FilesystemEntry, Path]], size: Optional[int] = None
self,
output_path: str,
fh: BinaryIO,
entry: Optional[Union[FilesystemEntry, Path]],
size: Optional[int] = None,
) -> None:
"""Write a file-like object to a directory.
The data from ``fh`` is written, while ``entry`` is used to get some properties of the file.
On Windows platforms ``:`` is replaced with ``_`` in the output_path.
Args:
output_path: The path of the entry in the output.
fh: The file-like object of the entry to write.
entry: The optional filesystem entry to write.
size: The optional file size in bytes of the entry to write.
"""
if platform.system() == "Windows":
output_path = output_path.replace(":", "_")

out_path = self.path.joinpath(output_path)
out_dir = out_path.parent
if not out_dir.exists():
out_dir.mkdir(parents=True)

with out_path.open("wb") as fhout:
shutil.copyfileobj(fh, fhout)
if entry and entry.is_dir():
out_dir = out_path
out_dir.mkdir(parents=True, exist_ok=True)

Check warning on line 41 in acquire/outputs/dir.py

View check run for this annotation

Codecov / codecov/patch

acquire/outputs/dir.py#L39-L41

Added lines #L39 - L41 were not covered by tests

else:
out_dir = out_path.parent
out_dir.mkdir(parents=True, exist_ok=True)

Check warning on line 45 in acquire/outputs/dir.py

View check run for this annotation

Codecov / codecov/patch

acquire/outputs/dir.py#L44-L45

Added lines #L44 - L45 were not covered by tests

with out_path.open("wb") as fhout:
shutil.copyfileobj(fh, fhout)

Check warning on line 48 in acquire/outputs/dir.py

View check run for this annotation

Codecov / codecov/patch

acquire/outputs/dir.py#L47-L48

Added lines #L47 - L48 were not covered by tests

def close(self) -> None:
pass
10 changes: 7 additions & 3 deletions acquire/outputs/tar.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,14 @@ def write(
entry: Optional[Union[FilesystemEntry, Path]],
size: Optional[int] = None,
) -> None:
"""Write a filesystem entry or file-like object to a tar file.
"""Write a file-like object to a tar file.
The data from ``fh`` is written, while ``entry`` is used to get some properties of the file.
Args:
output_path: The path of the entry in the output format.
output_path: The path of the entry in the output.
fh: The file-like object of the entry to write.
entry: The optional filesystem entry of the entry to write.
entry: The optional filesystem entry to write.
size: The optional file size in bytes of the entry to write.
"""
stat = None
Expand All @@ -79,6 +81,8 @@ def write(
if entry.is_symlink():
info.type = tarfile.SYMTYPE
info.linkname = entry.readlink()
elif entry.is_dir():
info.type = tarfile.DIRTYPE

Check warning on line 85 in acquire/outputs/tar.py

View check run for this annotation

Codecov / codecov/patch

acquire/outputs/tar.py#L84-L85

Added lines #L84 - L85 were not covered by tests

stat = entry.lstat()

Expand Down

0 comments on commit 5395f5a

Please sign in to comment.