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/O
  • copy_file(), move_file_or_folder(), remove_file() – For filesystem manipulation
  • make_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:

  1. Checks if the platform is hashable against NON_HASHABLE_PLATFORMS
  2. Computes CRC, MD5, and SHA-1 (and optionally CHD-SHA-1) via _calculate_rom_hashes() executed in background threads using asyncio.to_thread
  3. Handles archive formats (.zip, .tar, .7z, etc.) through the ARCHIVE_READERS map
  4. Constructs RomFile objects 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 directory
  • iter_directories(path, recursive) – Yields directory names for traversal
  • link_or_copy_file(source, dest) – Attempts hard-linking first; falls back to shutil.copy2 on cross-device errors while preserving metadata
  • sanitize_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.Lock objects ensures thread-safe concurrent access to the same files or folders.
  • Atomic writes through _atomic_write() and os.replace() prevent data corruption during interrupted operations.
  • ROM-specific handling in FSRomsHandler supports dynamic directory structures, archive extraction, and multi-hash calculation (CRC/MD5/SHA-1) using background threads.
  • Firmware management in FSFirmwareHandler provides streaming hash calculation and configurable folder structures for BIOS files.
  • Shared utilities in backend/utils/filesystem.py provide 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →