Skip to main content

Filesystem information

Windows 93 uses IndexedDB to store files. There exist two buckets, each with one object store called store. fileindex is first initialized as a dump of /files.cbor. fs is all .desktop files, etc. Each file is given a unique identifier, and is stored as a File object.

Creating a driver

Driver architecture​

The runtime selects a storage backend from mount points and delegates file operations to a driver.

Core flow:

  1. FileSystem resolves a path to a mounted driver name in 42/api/fs.js#L76.
  2. Driver is loaded lazily through 42/api/fs/getDriverLazy.js#L5.
  3. Files are represented by inodes in FileIndex.
  4. Inode format for stored files is [id, mask, times] (written in 42/api/fs/class/BrowserDriver.js#L196).
  5. mask identifies which backend owns that file. Cross-driver delegation is handled in 42/api/fs/class/BrowserDriver.js#L141 and 42/api/fs/class/BrowserDriver.js#L247.

mask​

A mask is a numeric backend identifier stored in each inode.

Mask mapping is defined in 42/api/fs/FileIndex.js#L7.

Current builtins:

  • 0x10 -> memory
  • 0x12 -> localstorage
  • 0x13 -> indexeddb
  • 0x14 -> opfs

A new driver should have a unique mask value. Do not follow the sequential order above.

Simple implementation​

The simplest path is to extend BrowserDriver in 42/api/fs/class/BrowserDriver.js.

tip

Don't extend Driver.js

You must provide:

  • mask (number)
  • store object with storage primitives compatible with BrowserDriver

Expected store methods:

  • has(id) -> boolean | Promise<boolean>
  • get(id) -> Blob | File | undefined | Promise<...>
  • set(id, data) -> void | Promise<void> where data is usually File/Blob
  • delete(id) -> void | Promise<void>

BrowserDriver handles:

  • inode metadata updates (b/a/c/m times)
  • symlink and directory checks
  • cross-driver delegation by mask
  • stream wrappers (sink/source)

Example​

Create a file under 42/api/fs/driver, named using the <name>Driver.js convention.

The lazy loader imports by name pattern in 42/api/fs/getDriverLazy.js#L13, so naming must match.

Example skeleton:

import { BrowserDriver } from "../class/BrowserDriver.js"

class MyBackendDriver extends BrowserDriver {
mask = 0x19

async init() {
// optional: feature detection or fallback
// if unsupported: return this.getDriver("indexeddb")

this.store = {
has: async (id) => {
// return true/false
},
get: async (id) => {
// return File/Blob or undefined
},
set: async (id, data) => {
// persist data
},
delete: async (id) => {
// remove entry
},
}

return super.init()
}
}

export const driver = (...args) => new MyBackendDriver(...args).init()

Driver Registration​

Add your new mask entry in 42/api/fs/FileIndex.js#L7.

Example:

0x15: "mybackend",

Rules:

  • mask must be unique
  • name should match your filename prefix (mybackendDriver.js)

Eager loading vs lazy loading​

Eager loading imports all drivers up front - basically, faster first-use access at the cost of higher startup time/memory. Lazy loading loads driver code only when a mounted path first needs it.

If code paths use eager loading via 42/api/fs/getDriver.js, also:

  1. import the driver module
  2. add it to modules

If your runtime always uses lazy loading, this is optional, but keeping both loaders aligned would be recommended.

Mounting​

Mount points are configured in 42/api/fs.js#L15.

Default root mount currently points to IndexedDB in 42/api/fs.js#L17.

Options:

  • global mount: set places: { "/": "mybackend" } in fs.js
  • scoped mount: call fs.mount("/some/path", "mybackend")

Longest matching mount path wins (see sort logic in 42/api/fs.js#L104).

For browser-dependent APIs, implement feature detection in init() and fallback to IndexedDB when unavailable.

Reference behavior in 42/api/fs/driver/opfsDriver.js#L8.

misc info​

Your backend must follow these expectations:

  • IDs are opaque and generated by uid() in BrowserDriver.
  • Data written through write() is a File object
  • get() should return an object compatible with Blob APIs (arrayBuffer(), stream()).
  • Missing entries should return undefined where possible. DO NOT THROW!!
  • delete() should be able to tolerate missing keys.

If things go wrong, here's where you messed up probably:

  • mask not registered in 42/api/fs/FileIndex.js#L7
  • returning non-Blob-compatible objects from store.get
  • forgetting return super.init() after setting up store

Refer to these files​

  • 42/api/fs.js
  • 42/api/fs/class/BrowserDriver.js
  • 42/api/fs/getDriverLazy.js
  • 42/api/fs/getDriver.js
  • 42/api/fs/FileIndex.js
  • 42/api/fs/driver/indexeddbDriver.js
  • 42/api/fs/driver/opfsDriver.js
  • 42/api/fs/driver/localstorageDriver.js
  • 42/api/fs/driver/memoryDriver.js