|
| 1 | +from typing import BinaryIO, Iterator, Optional |
| 2 | + |
| 3 | +from dissect.jffs import jffs2 |
| 4 | +from dissect.jffs.c_jffs2 import c_jffs2 |
| 5 | + |
| 6 | +from dissect.target.exceptions import ( |
| 7 | + FileNotFoundError, |
| 8 | + FilesystemError, |
| 9 | + IsADirectoryError, |
| 10 | + NotADirectoryError, |
| 11 | + NotASymlinkError, |
| 12 | +) |
| 13 | +from dissect.target.filesystem import Filesystem, FilesystemEntry |
| 14 | +from dissect.target.helpers import fsutil |
| 15 | + |
| 16 | + |
| 17 | +class JFFSFilesystem(Filesystem): |
| 18 | + __type__ = "jffs" |
| 19 | + |
| 20 | + def __init__(self, fh: BinaryIO, *args, **kwargs): |
| 21 | + super().__init__(fh, *args, **kwargs) |
| 22 | + self.jffs2 = jffs2.JFFS2(fh) |
| 23 | + |
| 24 | + @staticmethod |
| 25 | + def _detect(fh: BinaryIO) -> bool: |
| 26 | + return int.from_bytes(fh.read(2), "little") in ( |
| 27 | + c_jffs2.JFFS2_MAGIC_BITMASK, |
| 28 | + c_jffs2.JFFS2_OLD_MAGIC_BITMASK, |
| 29 | + ) |
| 30 | + |
| 31 | + def get(self, path: str) -> FilesystemEntry: |
| 32 | + return JFFSFilesystemEntry(self, path, self._get_node(path)) |
| 33 | + |
| 34 | + def _get_node(self, path: str, node: Optional[jffs2.INode] = None) -> jffs2.INode: |
| 35 | + try: |
| 36 | + return self.jffs2.get(path, node) |
| 37 | + except jffs2.FileNotFoundError as e: |
| 38 | + raise FileNotFoundError(path, cause=e) |
| 39 | + except jffs2.NotADirectoryError as e: |
| 40 | + raise NotADirectoryError(path, cause=e) |
| 41 | + except jffs2.NotASymlinkError as e: |
| 42 | + raise NotASymlinkError(path, cause=e) |
| 43 | + except jffs2.Error as e: |
| 44 | + raise FileNotFoundError(path, cause=e) |
| 45 | + |
| 46 | + |
| 47 | +class JFFSFilesystemEntry(FilesystemEntry): |
| 48 | + fs: JFFSFilesystem |
| 49 | + entry: jffs2.INode |
| 50 | + |
| 51 | + def get(self, path: str) -> FilesystemEntry: |
| 52 | + entry_path = fsutil.join(self.path, path, alt_separator=self.fs.alt_separator) |
| 53 | + entry = self.fs._get_node(path, self.entry) |
| 54 | + return JFFSFilesystemEntry(self.fs, entry_path, entry) |
| 55 | + |
| 56 | + def open(self) -> BinaryIO: |
| 57 | + if self.is_dir(): |
| 58 | + raise IsADirectoryError(self.path) |
| 59 | + return self._resolve().entry.open() |
| 60 | + |
| 61 | + def _iterdir(self) -> Iterator[tuple[str, jffs2.INode]]: |
| 62 | + if not self.is_dir(): |
| 63 | + raise NotADirectoryError(self.path) |
| 64 | + |
| 65 | + if self.is_symlink(): |
| 66 | + yield from self.readlink_ext().iterdir() |
| 67 | + else: |
| 68 | + yield from self.entry.iterdir() |
| 69 | + |
| 70 | + def iterdir(self) -> Iterator[str]: |
| 71 | + for name, _ in self._iterdir(): |
| 72 | + yield name |
| 73 | + |
| 74 | + def scandir(self) -> Iterator[FilesystemEntry]: |
| 75 | + for name, entry in self._iterdir(): |
| 76 | + entry_path = fsutil.join(self.path, name, alt_separator=self.fs.alt_separator) |
| 77 | + yield JFFSFilesystemEntry(self.fs, entry_path, entry) |
| 78 | + |
| 79 | + def is_dir(self, follow_symlinks: bool = False) -> bool: |
| 80 | + try: |
| 81 | + return self._resolve(follow_symlinks).entry.is_dir() |
| 82 | + except FilesystemError: |
| 83 | + return False |
| 84 | + |
| 85 | + def is_file(self, follow_symlinks: bool = False) -> bool: |
| 86 | + try: |
| 87 | + return self._resolve(follow_symlinks).entry.is_file() |
| 88 | + except FilesystemError: |
| 89 | + return False |
| 90 | + |
| 91 | + def is_symlink(self) -> bool: |
| 92 | + return self.entry.is_symlink() |
| 93 | + |
| 94 | + def readlink(self) -> str: |
| 95 | + if not self.is_symlink(): |
| 96 | + raise NotASymlinkError() |
| 97 | + |
| 98 | + return self.entry.link |
| 99 | + |
| 100 | + def stat(self, follow_symlinks: bool = False) -> fsutil.stat_result: |
| 101 | + return self._resolve(follow_symlinks).lstat() |
| 102 | + |
| 103 | + def lstat(self) -> fsutil.stat_result: |
| 104 | + node = self.entry.inode |
| 105 | + |
| 106 | + # mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime |
| 107 | + st_info = fsutil.stat_result( |
| 108 | + [ |
| 109 | + self.entry.mode, |
| 110 | + self.entry.inum, |
| 111 | + id(self.fs), |
| 112 | + 1, # TODO: properly calculate nlink in dissect.jffs |
| 113 | + node.uid, |
| 114 | + node.gid, |
| 115 | + node.isize, |
| 116 | + self.entry.atime.timestamp(), |
| 117 | + self.entry.mtime.timestamp(), |
| 118 | + self.entry.ctime.timestamp(), |
| 119 | + ] |
| 120 | + ) |
| 121 | + |
| 122 | + return st_info |
0 commit comments