# How RomM Manages File System Operations for ROMs, Assets, and Firmware/BIOS Files

> RomM centralizes file system operations with FSHandler, ensuring secure path validation, file locking, and atomic writes for ROMs, assets, and firmware/BIOS files. Configure your library directory easily.

- Repository: [The RomM Project/romm](https://github.com/rommapp/romm)
- Tags: internals
- Published: 2026-07-06

---

**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`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/backend/utils/filesystem.py) and [`backend/utils/hashing.py`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/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`.