synology_apm_repo.sdk.units.content.disk_fs package

DiskFilesystem parses the partition table and filesystem(s) inside a VM/PC/PS disk image via the Dissect framework (Fox-IT/NCC Group’s DFIR toolkit, dissect.* PyPI packages), so units/device.py can offer a read-only, per-file browsing/export view alongside the whole-image cat/export it already supports, wired in as a new, additional sibling node next to each disk-image leaf — existing disk-image refs are completely unaffected.

Dissect’s packages are pure Python (py3-none-any wheels, no per-platform native build) and cover partition tables (dissect.volume, auto-detecting MBR/GPT/Apple Partition Map/BSD disklabel) plus NTFS/ext2-4/XFS/Btrfs/FAT/APFS content (HFS+ and ISO9660 disks show the same “no filesystem recognized” diagnostic as any other unsupported format). A disk with no recognized partition table (Disk(...) raises) falls back to treating the whole image as one filesystem candidate — a bare, unpartitioned APFS container is the common real-world case. Each format’s own detection/listing/sizing lives in its own sibling module (_ntfs, _apfs, _posix_formats for ext2/3/4+XFS+Btrfs+FAT); _disk_filesystem.py holds the shared partition-table/APFS-container wiring (DiskFilesystem), and _content_source.py holds the two ContentSource classes every format’s own opened file reads through.

Every Dissect filesystem object exposes get(path) (NTFS via its root MftRecord; ext/FAT/APFS directly on the opened volume object) that re-resolves an absolute, forward-slash path from scratch — so a node’s own stable identifier is simply that path string, resolved fresh whenever DiskFilesystem.list_dir/open_file needs it, including a canonical ref pasted into a brand new process with no prior listing in this instance.

The whole dissect.* stack is always installed (a required dependency of this SDK, not split per format since every package involved is equally well-packaged — pure-Python universal wheels; dissect.apfs’s one native dependency, pycryptodome for FileVault decryption, ships broad prebuilt wheels of its own) but imported lazily, so importing units/device.py doesn’t pay the real cost of importing seven packages for a caller who never browses into a disk’s filesystem.

Each format also has its own content_unavailable check for a file whose real content this SDK cannot produce, rather than genuinely corrupt: a cloud-sync placeholder with no local data (e.g. an evicted OneDrive/iCloud file), or (NTFS only) an EFS-encrypted file this SDK has no key material for.

Every dissect.* call is synchronous while this SDK is async-native throughout, so every call here runs inside asyncio.to_thread — except _disk_filesystem._build_bridge’s stream adapter, whose worker thread briefly reads back across the loop boundary to fetch real bytes.

class synology_apm_repo.sdk.units.content.disk_fs.DiskFilesystem(content, loop)

Bases: object

One already-open disk image’s own partition table and filesystem(s)/volume(s), lazily parsed. Build with open, never the constructor directly.

A disk image can (and typically does) contain more than one partition/volume (a Windows “System Reserved” boot partition alongside the real NTFS volume, for instance) — every one Dissect can open is kept, not just the first/largest, matching what a real disk-mount tool would show. A partition/volume Dissect can’t open (unsupported format, encrypted without a key, or genuinely not a filesystem at all) is silently skipped, not reported as an error — this mirrors the project’s existing principle of resolving real state lazily and only reporting what was actually found, not guessing at why something wasn’t.

Not a UnitProvider — like VirtualDiskContentSource, it is a disk-content helper DeviceProvider drives directly, building its own Node/RestorableUnit objects from the plain tuples this class returns. device.py only ever talks to this class’s three public methods (partitions, list_dir, open_file).

async classmethod open(content)

Returns None when no partition/volume on this disk yields anything Dissect can open at all (encrypted, unsupported, or genuinely not a partitioned/filesystem-bearing image) — callers show a diagnostic node for that case, the same shape units/device_pcps.py’s own _diagnostic_node already uses for other “resolvable in principle, nothing found” cases.

Raises:

DiskFilesystemUnavailableError – None of this package’s dissect.* dependencies are importable — call disk_fs_available first to avoid this in the common case (nothing installed); this path exists for the rarer “find_spec found something, but the real import still fails” case (a broken/partial install).

partitions()

(partition_addr, human_label) for every filesystem/volume successfully opened — stable ordering (insertion order: dissect.volume’s own partition-table order, each APFS container’s volumes in their own container order).

async list_dir(partition_addr, path)

One _DirEntry per directory’s real entries — path="/" means the partition/volume’s own filesystem root. path is resolved fresh via the underlying format’s own get(path) every call (paths, not inode numbers, are the stable identifier here) — dot/dot-dot and each format’s own synthetic bookkeeping entries are filtered out by that format’s own _Format.iterdir, never surfaced as browsable content. file_state is always FileState.NORMAL for a directory, or for a format with no cloud-sync/encryption concept. mtime is None when a format/entry has no reliable value.

async open_file(partition_addr, path)

Opens path on the filesystem mounted at partition_addr.

Raises:

ContentUnavailableError – this file is a cloud-sync placeholder with no local data, or (NTFS only) is EFS-encrypted with no key material available.

exception synology_apm_repo.sdk.units.content.disk_fs.DiskFilesystemUnavailableError

Bases: Exception

Raised by DiskFilesystem.open when none of this package’s dissect.* dependencies are importable in the current environment. Deliberately not UnsupportedDataFormatError (that means “this repository’s own data doesn’t support this” — here the repository data may be perfectly fine, it’s this environment’s own dissect.* install that’s broken or missing).

class synology_apm_repo.sdk.units.content.disk_fs.DissectFileContentSource(entry, size)

Bases: object

Backed by one already-resolved Dissect filesystem entry (an NTFS MftRecord, an ext INode, a FAT DirectoryEntry, or an APFS DirectoryEntry) — reads through that entry’s own .open() stream rather than a format-specific random-access method, since that’s the one thing all four formats’ entries agree on (unlike read-at-offset method names/arg orders, which differ per format).

property size: int | None
property supports_concurrent_export: bool
async read(offset=0, length=None)
stream(block=8388608)
async export_to(dst, *, sparse=True, progress=None)
synology_apm_repo.sdk.units.content.disk_fs.disk_fs_available()

Cheap presence check for a working Dissect install — checks the import system’s own module registry, never actually imports any of them. True if at least one of the packages in _DISSECT_PACKAGES is present; callers use this to decide whether to even offer the “(filesystem)” sibling node at all.