How RomM Manages File System Operations for ROMs, Assets, and Firmware/BIOS Files
RomM manages all file system operations through a centralized FSHandler abstraction that enforces path validation, per-file locking, and atomic writes to securely handle ROMs, assets, and firmware/BIOS files within a configurable library directory.
RomM is a self-hosted ROM manager that organizes gaming libraries through a secure, async-first file system layer. According to the RomM source code, all interactions with ROM files, save states, screenshots, and firmware binaries are mediated through specialized handlers built on a common base class. This architecture ensures that every file operation within the LIBRARY_BASE_PATH directory is validated, locked, and performed atomically.
The FSHandler Base Abstraction: Security and Atomicity
At the core of RomM's file system management is FSHandler (backend/handler/filesystem/base_handler.py), a security-focused abstraction that all concrete handlers extend. This base class provides async utilities that prevent common filesystem vulnerabilities while ensuring data integrity.
Path Validation and Sanitization
The validate_path() method in backend/handler/filesystem/base_handler.py prevents directory traversal, absolute-path abuse, and symlink hijacking. Before any operation, the handler checks for .. sequences, absolute paths, and symbolic links in the path chain, then normalizes the result against the library root.
Per-File Locking
To guarantee thread-safe operations, FSHandler maintains a dictionary of asyncio.Lock objects (self._locks) created on-demand via _get_file_lock(). This ensures that concurrent operations on the same file or folder are serialized without blocking unrelated paths.
Atomic Write Operations
The _atomic_write() context manager prevents partial writes on crash or interruption. It creates a temporary file in the same directory and finalizes the operation using os.replace() to atomically swap the target file, ensuring that readers never see incomplete data.
Unified I/O Primitives
All high-level operations delegate to secure primitives:
read_file(),stream_file(),write_file(),write_file_streamed()– For content I/Ocopy_file(),move_file_or_folder(),remove_file()– For filesystem manipulationmake_directory(),list_directories(),remove_directory()– For directory management
Each method calls validate_path() and acquires the appropriate lock before delegating to anyio or shutil.
ROM File Operations with FSRomsHandler
The FSRomsHandler (backend/handler/filesystem/roms_handler.py) extends FSHandler to manage the ROM sub-tree, handling everything from directory structure to hash calculation.
Dynamic Directory Structure
RomM supports two file system layouts controlled by the has_structure_path_b flag in config_manager. The get_roms_fs_structure() method returns either:
<platform>/<roms_folder><roms_folder>/<platform>
Listing and Filtering ROMs
The handler combines list_files() for single-file ROMs and list_directories() for multi-file ROM folders, filtering entries against exclusion patterns (exclude_single_files, exclude_multi_roms) defined in the configuration.
Reading and Hashing ROM Content
The get_rom_files() method walks the ROM directory using iter_files() from backend/utils/filesystem.py. For each file, it:
- Checks if the platform is hashable against
NON_HASHABLE_PLATFORMS - Computes CRC, MD5, and SHA-1 (and optionally CHD-SHA-1) via
_calculate_rom_hashes()executed in background threads usingasyncio.to_thread - Handles archive formats (
.zip,.tar,.7z, etc.) through theARCHIVE_READERSmap - Constructs
RomFileobjects containing metadata, size, timestamps, and track information for audio files
ROM Renaming and Metadata
The rename_fs_rom() method validates new names, checks for conflicts, and executes moves via move_file_or_folder(). For PICO-8 cartridges, get_pico8_cover_url() returns file:// URIs for .p8.png images.
Firmware and BIOS Management
Firmware and BIOS files are managed by FSFirmwareHandler (backend/handler/filesystem/firmware_handler.py), which mirrors the ROM handler's security model with simplified workflows.
Path Resolution
The get_firmware_fs_structure() method constructs paths as <platform>/<firmware_folder> (or the inverse order), using the configurable FIRMWARE_FOLDER_NAME value from backend/config/config_manager.py.
Firmware Listing and Hashing
The get_firmware() method calls list_files() on the firmware path and filters excluded entries via exclude_single_files(). The calculate_file_hashes() method streams files in 8 KB chunks using stream_file(), updating CRC, MD5, and SHA-1 digests without loading entire binaries into memory.
Shared Utilities and Helpers
Common functionality resides in backend/utils/filesystem.py and backend/utils/hashing.py:
iter_files(path, recursive)– Yields(Path, filename)pairs for all files under a directoryiter_directories(path, recursive)– Yields directory names for traversallink_or_copy_file(source, dest)– Attempts hard-linking first; falls back toshutil.copy2on cross-device errors while preserving metadatasanitize_filename()– Removes illegal characters, trims whitespace, and guarantees non-empty results
Summary
- RomM uses a centralized
FSHandler(backend/handler/filesystem/base_handler.py) to mediate all file system operations with built-in security and atomicity guarantees. - Path validation via
validate_path()prevents directory traversal and symlink attacks by normalizing paths against the library root. - Per-file locking using
asyncio.Lockobjects ensures thread-safe concurrent access to the same files or folders. - Atomic writes through
_atomic_write()andos.replace()prevent data corruption during interrupted operations. - ROM-specific handling in
FSRomsHandlersupports dynamic directory structures, archive extraction, and multi-hash calculation (CRC/MD5/SHA-1) using background threads. - Firmware management in
FSFirmwareHandlerprovides streaming hash calculation and configurable folder structures for BIOS files. - Shared utilities in
backend/utils/filesystem.pyprovide efficient file iteration, hard-link optimization, and filename sanitization.
Frequently Asked Questions
How does RomM prevent directory traversal attacks?
RomM prevents directory traversal through the validate_path() method in backend/handler/filesystem/base_handler.py, which checks for .. sequences, absolute paths, and symbolic links before normalizing any path against the configured LIBRARY_BASE_PATH. This ensures handlers can only access files within the designated library directory.
What makes RomM's file writes atomic?
RomM implements atomic writes via the _atomic_write() context manager, which creates a temporary file in the target directory and uses os.replace() to perform an atomic rename operation. This guarantees that readers never see partially written files, even if the process crashes during the write operation.
How does RomM handle hashing for large ROM files?
Large ROM files are hashed using asyncio.to_thread to execute _calculate_rom_hashes() in background threads, keeping the async API responsive. For firmware files, calculate_file_hashes() streams content in 8 KB chunks using stream_file(), updating CRC, MD5, and SHA-1 digests without loading entire files into memory.
Can RomM handle multi-file ROMs and archived games?
Yes, FSRomsHandler distinguishes between single-file ROMs and multi-file ROM folders using list_files() and list_directories() respectively. It also supports archive formats (.zip, .tar, .7z) through the ARCHIVE_READERS map, extracting and hashing contents while respecting exclusion patterns configured in config_manager.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →