RedButton-v2 / redbutton_v2 /sandbox.py
Hugging557444's picture
V2-A: bootstrap RedButton v2 with v1 verbatim reusables
e78298b
Raw
History Blame Contribute Delete
2.94 kB
"""Dictionary-backed simulated filesystem with hash semantics.
The prefix-aware ``_is_readonly`` is the v3 fix: ``readonly_paths``
holds either exact files or directory prefixes, and a path under any
prefix is protected. ``hash`` mixes content + a NUL separator + a
single byte for the executable flag so that ``"abc" + executable``
cannot collide with ``"abcE" + non-executable``.
"""
from dataclasses import dataclass, field
from hashlib import sha256
from typing import Set
@dataclass
class SimFile:
content: str
executable: bool = False
writable: bool = True
@dataclass
class SimulatedFilesystem:
files: dict[str, SimFile] = field(default_factory=dict)
readonly_paths: Set[str] = field(default_factory=set)
def _is_readonly(self, path: str) -> bool:
"""Prefix-aware readonly check.
``readonly_paths`` may contain either exact files or directory
prefixes (with or without a trailing slash). ``"/audit"`` and
``"/audit/"`` both protect ``"/audit/commands.log"``.
"""
for protected in self.readonly_paths:
protected = protected.rstrip("/")
if path == protected or path.startswith(protected + "/"):
return True
return False
def read(self, path: str) -> str:
if path not in self.files:
raise FileNotFoundError(path)
return self.files[path].content
def write(self, path: str, content: str) -> None:
if self._is_readonly(path):
raise PermissionError(f"Path is readonly: {path}")
if path in self.files and not self.files[path].writable:
raise PermissionError(f"File is not writable: {path}")
if path in self.files:
self.files[path].content = content
else:
self.files[path] = SimFile(content=content)
def chmod(self, path: str, executable: bool) -> None:
if self._is_readonly(path):
raise PermissionError(f"Path is readonly: {path}")
if path not in self.files:
raise FileNotFoundError(path)
self.files[path].executable = executable
def delete(self, path: str) -> None:
if self._is_readonly(path):
raise PermissionError(f"Path is readonly: {path}")
if path not in self.files:
raise FileNotFoundError(path)
del self.files[path]
def list(self, directory: str) -> list[str]:
directory = directory.rstrip("/") + "/"
return [p for p in self.files if p.startswith(directory)]
def is_executable(self, path: str) -> bool:
return path in self.files and self.files[path].executable
def hash(self, path: str) -> str:
if path not in self.files:
return ""
f = self.files[path]
h = sha256()
h.update(f.content.encode())
h.update(b"\x00")
h.update(b"E" if f.executable else b"N")
return h.hexdigest()