From a696e46a37fd1b0234b83a16c4e347a6ad05bdac Mon Sep 17 00:00:00 2001 From: JSCU-CNI <121175071+JSCU-CNI@users.noreply.github.com> Date: Mon, 5 Aug 2024 14:15:19 +0200 Subject: [PATCH 1/4] add target-shell history --- dissect/target/helpers/config.py | 29 ++++++++++++++++++++++------- dissect/target/target.py | 5 +++-- dissect/target/tools/shell.py | 16 ++++++++++++++++ 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/dissect/target/helpers/config.py b/dissect/target/helpers/config.py index 8909012d1..7a1cc3faa 100644 --- a/dissect/target/helpers/config.py +++ b/dissect/target/helpers/config.py @@ -1,27 +1,35 @@ +from __future__ import annotations + import ast import importlib.machinery import importlib.util import logging from pathlib import Path from types import ModuleType -from typing import Optional, Union log = logging.getLogger(__name__) CONFIG_NAME = ".targetcfg.py" -def load(path: Optional[Union[Path, str]]) -> ModuleType: +def load(paths: list[Path | str] | Path | str | None) -> ModuleType: + """Attempt to load one configuration from the provided path(s).""" + + if isinstance(paths, Path) or isinstance(paths, str): + paths = [paths] + config_spec = importlib.machinery.ModuleSpec("config", None) config = importlib.util.module_from_spec(config_spec) - config_file = _find_config_file(path) + config_file = _find_config_file(paths) + if config_file: config_values = _parse_ast(config_file.read_bytes()) config.__dict__.update(config_values) + return config -def _parse_ast(code: str) -> dict[str, Union[str, int]]: +def _parse_ast(code: str) -> dict[str, str | int]: # Only allow basic value assignments for backwards compatibility obj = {} @@ -49,15 +57,19 @@ def _parse_ast(code: str) -> dict[str, Union[str, int]]: return obj -def _find_config_file(path: Optional[Union[Path, str]]) -> Optional[Path]: - """Find a config file anywhere in the given path and return it. +def _find_config_file(paths: list[Path | str] | None) -> Path | None: + """Find a config file anywhere in the given path(s) and return it. This algorithm allows parts of the path to not exist or the last part to be a filename. It also does not look in the root directory ('/') for config files. """ + if not paths: + return + config_file = None - if path: + + for path in paths: path = Path(path) cur_path = path.absolute() @@ -69,4 +81,7 @@ def _find_config_file(path: Optional[Union[Path, str]]) -> Optional[Path]: config_file = cur_config cur_path = cur_path.parent + if config_file: + break + return config_file diff --git a/dissect/target/target.py b/dissect/target/target.py index 7ef02b1b6..c4a03e1a0 100644 --- a/dissect/target/target.py +++ b/dissect/target/target.py @@ -87,9 +87,10 @@ def __init__(self, path: Union[str, Path] = None): self._applied = False try: - self._config = config.load(self.path) + self._config = config.load([self.path, os.getcwd()]) except Exception as e: - self.log.debug("Error loading config file", exc_info=e) + self.log.warning("Error loading config file: %s", self.path) + self.log.debug("", exc_info=e) self._config = config.load(None) # This loads an empty config. # Fill the disks and/or volumes and/or filesystems and apply() will diff --git a/dissect/target/tools/shell.py b/dissect/target/tools/shell.py index 0b46b63f6..1033f768f 100644 --- a/dissect/target/tools/shell.py +++ b/dissect/target/tools/shell.py @@ -58,6 +58,7 @@ except ImportError: # Readline is not available on Windows log.warning("Readline module is not available") + readline = None # ['mode', 'addr', 'dev', 'nlink', 'uid', 'gid', 'size', 'atime', 'mtime', 'ctime'] STAT_TEMPLATE = """ File: {path} {symlink} @@ -117,6 +118,21 @@ def __init__(self, target: Target): self.debug = False self.identchars += "." + self.DEFAULT_HISTFILE = "~/.dissect_history" + self.DEFAULT_HISTFILESIZE = 10_000 + + self.HISTFILE = pathlib.Path(getattr(target._config, "HISTFILE", self.DEFAULT_HISTFILE)).expanduser() + self.HISTFILESIZE = getattr(target._config, "HISTFILESIZE", self.DEFAULT_HISTFILESIZE) + + def preloop(self) -> None: + if readline and self.HISTFILE.exists(): + readline.read_history_file(self.HISTFILE) + + def postloop(self) -> None: + if readline: + readline.set_history_length(self.HISTFILESIZE) + readline.write_history_file(self.HISTFILE) + def __getattr__(self, attr: str) -> Any: if attr.startswith("help_"): _, _, command = attr.partition("_") From b67cc3937b64c646702d49615129b1d57ea206e4 Mon Sep 17 00:00:00 2001 From: JSCU-CNI <121175071+JSCU-CNI@users.noreply.github.com> Date: Tue, 6 Aug 2024 11:49:23 +0200 Subject: [PATCH 2/4] implement review comments --- dissect/target/tools/shell.py | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/dissect/target/tools/shell.py b/dissect/target/tools/shell.py index 1033f768f..b34856652 100644 --- a/dissect/target/tools/shell.py +++ b/dissect/target/tools/shell.py @@ -112,26 +112,37 @@ class TargetCmd(cmd.Cmd): CMD_PREFIX = "cmd_" + DEFAULT_HISTFILE = "~/.dissect_history" + DEFAULT_HISTFILESIZE = 10_000 + DEFAULT_HISTDIR = None + DEFAULT_HISTDIRFMT = ".dissect_history_{uid}_{target}" + def __init__(self, target: Target): cmd.Cmd.__init__(self) self.target = target self.debug = False self.identchars += "." - self.DEFAULT_HISTFILE = "~/.dissect_history" - self.DEFAULT_HISTFILESIZE = 10_000 + self.histfilesize = getattr(target._config, "HISTFILESIZE", self.DEFAULT_HISTFILESIZE) + self.histdir = getattr(target._config, "HISTDIR", self.DEFAULT_HISTDIR) - self.HISTFILE = pathlib.Path(getattr(target._config, "HISTFILE", self.DEFAULT_HISTFILE)).expanduser() - self.HISTFILESIZE = getattr(target._config, "HISTFILESIZE", self.DEFAULT_HISTFILESIZE) + if self.histdir: + self.histdirfmt = getattr(target._config, "HISTDIRFMT", self.DEFAULT_HISTDIRFMT) + self.histfile = pathlib.Path(self.histdir).resolve() / pathlib.Path( + self.histdirfmt.format(uid=os.getuid(), target=target.name) + ) + + else: + self.histfile = pathlib.Path(getattr(target._config, "HISTFILE", self.DEFAULT_HISTFILE)).expanduser() def preloop(self) -> None: - if readline and self.HISTFILE.exists(): - readline.read_history_file(self.HISTFILE) + if readline and self.histfile.exists(): + readline.read_history_file(self.histfile) def postloop(self) -> None: if readline: - readline.set_history_length(self.HISTFILESIZE) - readline.write_history_file(self.HISTFILE) + readline.set_history_length(self.histfilesize) + readline.write_history_file(self.histfile) def __getattr__(self, attr: str) -> Any: if attr.startswith("help_"): @@ -1257,10 +1268,15 @@ def run_cli(cli: cmd.Cmd) -> None: # Print an empty newline on exit print() return + except KeyboardInterrupt: # Add a line when pressing ctrl+c, so the next one starts at a new line print() - pass + + except PermissionError as e: + log.error(e) + break + except Exception as e: if cli.debug: log.exception(e) @@ -1268,7 +1284,6 @@ def run_cli(cli: cmd.Cmd) -> None: log.info(e) print(f"*** Unhandled error: {e}") print("If you wish to see the full debug trace, enable debug mode.") - pass @catch_sigpipe From a5cb97c782b8df304d7a688096830a82a150c89f Mon Sep 17 00:00:00 2001 From: Erik Schamper <1254028+Schamper@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:02:57 +0200 Subject: [PATCH 3/4] Update dissect/target/tools/shell.py --- dissect/target/tools/shell.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dissect/target/tools/shell.py b/dissect/target/tools/shell.py index b34856652..b4986d7c2 100644 --- a/dissect/target/tools/shell.py +++ b/dissect/target/tools/shell.py @@ -131,7 +131,6 @@ def __init__(self, target: Target): self.histfile = pathlib.Path(self.histdir).resolve() / pathlib.Path( self.histdirfmt.format(uid=os.getuid(), target=target.name) ) - else: self.histfile = pathlib.Path(getattr(target._config, "HISTFILE", self.DEFAULT_HISTFILE)).expanduser() From cca8f46d94b92b8fede543ca62ea4ccf9a578445 Mon Sep 17 00:00:00 2001 From: Computer Network Investigation <121175071+JSCU-CNI@users.noreply.github.com> Date: Wed, 7 Aug 2024 13:08:26 +0200 Subject: [PATCH 4/4] Apply suggestions from code review Co-authored-by: Erik Schamper <1254028+Schamper@users.noreply.github.com> --- dissect/target/tools/shell.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/dissect/target/tools/shell.py b/dissect/target/tools/shell.py index b4986d7c2..29b06781d 100644 --- a/dissect/target/tools/shell.py +++ b/dissect/target/tools/shell.py @@ -136,12 +136,18 @@ def __init__(self, target: Target): def preloop(self) -> None: if readline and self.histfile.exists(): - readline.read_history_file(self.histfile) + try: + readline.read_history_file(self.histfile) + except Exception as e: + log.debug("Error reading history file: %s", e) def postloop(self) -> None: if readline: readline.set_history_length(self.histfilesize) - readline.write_history_file(self.histfile) + try: + readline.write_history_file(self.histfile) + except Exception as e: + log.debug("Error writing history file: %s", e) def __getattr__(self, attr: str) -> Any: if attr.startswith("help_"): @@ -1272,10 +1278,6 @@ def run_cli(cli: cmd.Cmd) -> None: # Add a line when pressing ctrl+c, so the next one starts at a new line print() - except PermissionError as e: - log.error(e) - break - except Exception as e: if cli.debug: log.exception(e)