SylFS 仕様書 (Sylph Filesystem Specification)

最終更新: 2026-11-10

概要

SylFS(Sylph Filesystem / Sylphid FS) は、AtmOS のためにゼロから設計・実装された高性能・高信頼性を誇る独自ファイルシステムです。

組み込み環境(Raspberry Pi 3B+ / ARMv8 64-bit)における極めて過酷な運用条件(突然の電源断、microSD カードのビット腐敗、マルチコアからの並行アクセス、フラッシュメモリの摩耗)に耐えうる堅牢性と、Web ブラウザのマルチメディアキャッシュやシステムフォント(数 MB 〜 数十 MB)をストレスなく扱う高速性を両立させる設計思想を持っています。

設計思想

  1. Copy-on-Write (COW) による完全な電源断耐性: 既存のデータやメタデータ、インデックスノードをインプレース(同一箇所)で上書きすることは一切ありません。すべての変更は空きセクタへ新しく書き出され、最上位のスーパーブロックがアトミックに更新された瞬間にはじめて新バージョンが確定します。書き込み処理のどの瞬間で電源が切れても、直前の整合した状態に安全に復帰します。
  2. 2重化(Dual Redundancy)と自動自己修復(Self-Healing): 不揮発性ストレージ(microSD カード)上の全セクタは物理的にミラーリング(二重化)され、セクタ末尾に付与された CRC32 チェックサムにより読み出し時に整合性が検証されます。プライマリセクタがビット腐敗や書き込み不良で破損した場合、ミラーセクタから健全なデータを自動取得し、さらに破損したプライマリセクタを即座に書き直して修復(Self-Healing)します。
  3. B+Tree / B-Tree ハイブリッドインデックス: ファイルパスや名前の検索には、FNV-1a 128-bit ハッシュをキーとしたディスク上の COW B-Tree を採用しています。大量のファイルが存在するディレクトリでも O(log N) の極めて高速な探索速度を実現します。
  4. エクステント方式(Extent-based Allocation)による大容量対応: NTFS の “runs” や exFAT と同様に、ファイルを「開始セクタ番号+連続セクタ数」のエクステント列で表現します。連続領域であれば 1 つのエントリで巨大な領域を表現でき、単一ファイルの容量上限を実質的に撤廃しています。
  5. ストレージ階層・用途別振り分け(Storage Policy): microSD カード(フラッシュメモリ)の書き換え寿命を守るため、ブラウザの一時キャッシュ(/cache/)やテンポラリ(/tmp/)は専用の広大な RAMDisk(揮発性メモリ、最大 332MB)へ自動的にルーティングされます。
  6. マルチコア SMP 安全性 & パニック耐性: マルチコア環境において複数のワーカースレッドが同時にファイルシステムへアクセスしてもメタデータやビットマップが破壊されないよう、スピンロック RecoverableMutex(FS_LOCK)で保護されています。万一いずれかのコアがパニックした場合でも、該当コアが保持していたロックのみを強制回収(force release)し、OS 全体のハングアップを回避します。

1. 基本概念 & アーキテクチャ階層

SylFS は、アプリケーション層から物理ストレージハードウェアまで、明確に分離された多層アーキテクチャで構成されています。

アーキテクチャ全体図

+-----------------------------------------------------------------------+
|                アプリケーション層 (Web Browser, Desk, Shell)             |
|                                                                       |
|   Aura スクリプト (fs.*, ls, cat, save, show, find, cp, mv, rm...)     |
+-----------------------------------------------------------------------+
                                   |
                                   v
+-----------------------------------------------------------------------+
|                    VFS / SylFS Core インターフェース                   |
|                                                                       |
|   mount() / save_file() / read_file_checked() / delete_file()         |
|   find_by_label() / list_files() / list_dir()                         |
|   FS_LOCK (RecoverableMutex) 排他制御 & パニック時ロック自動回収         |
+-----------------------------------------------------------------------+
           |                                               |
           v (通常パス / 設定 / アセット)                    v (一時パス: /cache/, /tmp/)
+------------------------------------+          +-----------------------+
|   Persistent SylFS (不揮発性)      |          |  Volatile SylFS (RAM) |
|   - COW B-Tree インデックス         |          |  - 専用 RAMDisk       |
|   - エクステントアロケータ         |          |  - 寿命保護・高速処理 |
|   - 空きセクタビットマップ         |          +-----------------------+
+------------------------------------+                      |
           |                                                |
           v                                                |
+-----------------------------------------------------------v-----------+
|              MirroredBlockDevice (2重化 & 自己修復レイヤ)              |
|                                                                       |
|   - 508B データ + 4B CRC32 チェックサム付与・検証                     |
|   - SD カード: ミラーリング有効 (Primary + Mirror, 容量 50%)          |
|   - RAMDisk: ミラーリング無効 (単一領域, 容量 100% 活用)             |
|   - 読み出し時 CRC 不一致の検知とプライマリ自動書き戻し (Healing)     |
+-----------------------------------------------------------------------+
                                   |
                                   v
+-----------------------------------------------------------------------+
|                    BlockDevice (ハードウェア抽象化層)                  |
|                                                                       |
|   - SDCard: EMMC/SDHOST コントローラ (オフセット 256MiB, FAT32 保護)   |
|   - RAMDisk: 物理連続メモリ直接マッピング (ヒープ後方 332MB)           |
+-----------------------------------------------------------------------+

セクタとデータ境界

物理ストレージの最小読み書き単位は 512 バイト です。SylFS ではデータの信頼性を物理ブロックレベルで担保するため、セクタの末尾 4 バイトを CRC32 チェックサム領域として予約しています。

+---------------------------------------------------------------+
|                      物理セクタ (512 Bytes)                    |
+-----------------------------------------------+---------------+
|           有効データ領域 (DATA_SIZE_PER_SECTOR) | CRC32 チェック |
|                     508 Bytes                 |    4 Bytes    |
+-----------------------------------------------+---------------+
0                                             508             512

すべてのデータブロック、メタデータブロック、インデックスノード、ビットマップブロックは、この 508 バイト境界内に完全に収まるよう #[repr(C, packed)] かつコンパイル時アサーション(assert!(size_of::<T>() <= DATA_SIZE_PER_SECTOR))により保証されています。


2. オンディスクフォーマット(バイナリレイアウト詳細)

ボリューム全体レイアウト

SylFS の物理デバイス(またはパーティション)上のレイアウトは以下のとおりです。

Sector 0        Sector 1..N           Sector N+1..
+---------------+---------------------+-----------------------------------------+
|  Superblock   |  Allocation Bitmap  |  Data Blocks / Metadata / B-Tree Nodes  |
|  (1 Sector)   |  (動的セクタ数)      |  (COW 動的割り当て領域)                 |
+---------------+---------------------+-----------------------------------------+
  1. セクタ 0 (SUPERBLOCK_SECTOR): ファイルシステムの根本情報(マジック、バージョン、B-Tree ルートセクタ、ビットマップ開始セクタ・セクタ長)を保持。
  2. セクタ 1 〜 N (BITMAP_START_SECTOR 〜 BITMAP_START_SECTOR + bitmap_sectors - 1): 全セクタの割当状態を 1 ビット単位で管理するビットマップ。
  3. セクタ N+1 以降: ファイルデータ、ファイルメタデータ(FileMetadata)、インデックスノード(BTreeNode)、溢れエクステントセクタが混在して動的に確保されるプール。

Superblock 構造体

ファイルシステムの最重要ヘッダであり、常にセクタ 0 に配置されます。変更のコミット時に最後に書き込まれます。

#[repr(C, packed)]
#[derive(Clone, Copy)]
pub struct Superblock {
    pub magic: [u8; 8],        // 0x00..0x08: "SYLFS\0\0\0"
    pub version: u32,          // 0x08..0x0C: バージョン番号 (現在 1)
    pub root_sector: u32,      // 0x0C..0x10: B-Tree インデックスのルートセクタ番号
    pub bitmap_start: u32,     // 0x10..0x14: ビットマップ領域の開始セクタ (通常 1)
    pub bitmap_sectors: u32,   // 0x14..0x18: ビットマップ領域が占有するセクタ数
}
オフセット フィールド名 型 説明
0x000 magic [u8; 8] SylFS 識別マジック (SYLFS\0\0\0 = [0x53, 0x59, 0x4C, 0x46, 0x53, 0x00, 0x00, 0x00])
0x008 version u32 フォーマットバージョン(現行バージョンは 1)
0x00C root_sector u32 ファイル名/ラベル探索用 B-Tree の根ノードが格納されているセクタ番号(空の場合は 0)
0x010 bitmap_start u32 空き領域管理ビットマップの開始セクタ(1)
0x014 bitmap_sectors u32 ビットマップが占めるセクタ数(ボリューム容量から動的算出)

[!NOTE] Superblock の構造体サイズは 24 バイトであり、セクタの先頭 24 バイトに書き込まれます。残りの 484 バイトは予約領域(将来の機能拡張用パディング)となります。


FileMetadata 構造体(i-node)

各ファイルの属性、名前、ラベル、サイズ、およびデータブロックを指すエクステントリストを格納します。1 ファイルにつき 1 つのセクタを消費します。

#[repr(C, packed)]
#[derive(Clone, Copy)]
pub struct FileMetadata {
    pub filename: [u8; 128],                  // 0x000..0x080: パス名 (UTF-8, NUL終端)
    pub label_data: [u8; 64],                 // 0x080..0x0C0: ラベル一覧 (カンマ区切り, UTF-8, NUL終端)
    pub size: u64,                            // 0x0C0..0x0C8: ファイルサイズ (バイト単位, 最大 16EB)
    pub extent_count: u32,                    // 0x0C8..0x0CC: 有効エクステントの総数
    pub overflow_sector: u64,                 // 0x0CC..0x0D4: 溢れエクステントセクタへのポインタ (0なら無し)
    pub extents: [Extent; MAX_INLINE_EXTENTS],// 0x0D4..0x1F4: インラインエクステント配列 (18エントリ)
}
オフセット フィールド名 サイズ 説明
0x000 filename 128 バイト ファイルの絶対パスまたは名前(UTF-8、最大 127 文字+NUL終端)。先頭バイトが 0 の場合は「削除済み」とみなす
0x080 label_data 64 バイト カンマ区切りのラベル文字列(例: "sys,font,cache"、最大 63 文字+NUL終端)
0x0C0 size 8 バイト (u64) ファイルの正確な実データ長(バイト数)。264 − 1 バイトまでサポート
0x0C8 extent_count 4 バイト (u32) ファイル全体を構成するエクステント(連続領域)の総数
0x0CC overflow_sector 8 バイト (u64) インライン(18本)に収まらなかったエクステントを連鎖格納するセクタ番号(不要時は 0)
0x0D4 extents 288 バイト Extent 構造体(16 バイト)× 18 エントリ(インラインエクステント)

合計サイズ: 128 + 64 + 8 + 4 + 8 + 288 = 500 バイト( ≤ 508 バイトで 1 セクタに完全に収まる)。


Extent 構造体 & 溢れエクステントチェーン

ファイルを構成するデータセクタの連続領域を効率的に表現します。

#[repr(C, packed)]
#[derive(Clone, Copy, Default)]
pub struct Extent {
    pub start: u64, // 開始セクタ番号
    pub count: u64, // 連続セクタ数 (0なら未使用)
}

エクステントの利点

従来のブロックポインタ列挙方式では、1 セクタあたり数十〜数百個のセクタ番号しか保持できず、間接ブロックを用いても数 MB で容量上限に達していました。SylFS では連続したセクタ確保が行われた場合、どれほど巨大なファイルであっても単一のエクステント(例: start: 1000, count: 20480 = 10MB)で表現できます。

溢れエクステントセクタ (overflow_sector)

ファイルの断片化(フラグメンテーション)が進み、エクステント数が 18 個を超えた場合、残りのエクステントは溢れセクタのリンクリストへ書き出されます。

FileMetadata
  overflow_sector ──> [ Overflow Sector 1 ]
                      ├── next_sector: 1542 ──> [ Overflow Sector 2 ]
                      ├── Extent 19             ├── next_sector: 0 (終端)
                      ├── Extent 20             ├── Extent 50
                      └── ... (最大31エントリ)    └── ...

BTreeNode 構造体

ディレクトリ探索および高速ファイル検索を行う COW B-Tree ノードのバイナリ表現です。

pub const MAX_KEYS: usize = 24;
pub const KEY_LEN: usize = 16; // 128-bit FNV-1a ハッシュ

#[repr(C, packed)]
#[derive(Clone, Copy)]
pub struct BTreeNode {
    pub node_type: u8,                       // 0: Leaf, 1: Internal
    pub num_keys: u8,                        // 現在格納されているキー数 (最大 24)
    pub padding: [u8; 2],                    // 4バイト境界アライメントパディング
    pub keys: [[u8; KEY_LEN]; MAX_KEYS + 1], // キー配列 (25エントリ: 分割時の一時オーバーフロー用+1)
    pub values: [u32; MAX_KEYS + 2],         // 値配列 (26エントリ: 葉=Metadataセクタ, 内部=子ノードセクタ)
}

3. 内部アーキテクチャとアルゴリズム

Copy-on-Write (COW) トランザクション & アトミックコミット

SylFS におけるファイルの保存・更新は、厳密な COW 手順に従って実行されます。途中で電源が断たれた場合でも、ディスク上の旧構造は一切破壊されず残存します。

sequenceDiagram
    autonumber
    participant App as 呼び出し元 (Aura / Kernel)
    participant FS as SylFS Core
    participant Bmp as 空きビットマップ
    participant Dev as MirroredBlockDevice
    participant Disk as 物理ストレージ (SD / RAM)

    App->>FS: save_file("sample.txt", data, "text")
    Note over FS: 1. データセクタの確保と書き込み
    FS->>Bmp: allocate_sector() × 必要数
    Bmp-->>FS: セクタ番号 (連続ならエクステント集約)
    FS->>Dev: write_sector(data_sec, data_chunk)
    Dev->>Disk: プライマリ & ミラー書き込み (CRC32付与)

    Note over FS: 2. メタデータの作成と書き込み
    FS->>Bmp: allocate_sector() (メタデータ用)
    Bmp-->>FS: meta_sec
    FS->>Dev: write_sector(meta_sec, FileMetadata)

    Note over FS: 3. B-Tree インデックスへの COW 再帰挿入
    FS->>FS: insert_recursive() (根から葉までのパスを複製)
    FS->>Bmp: allocate_sector() (更新されたノード用)
    FS->>Dev: write_sector(new_node_sec, new_node)
    Note over FS: 古いノードは replaced リストへ保持

    Note over FS: 4. Superblock 更新によるアトミックコミット
    FS->>Dev: write_sector(0, NewSuperblock { root: new_root })
    Dev->>Disk: 【ここで状態が確定】

    Note over FS: 5. 不要となった旧セクタの安全解放
    FS->>Bmp: deallocate_sector() (replaced 節点)
    FS->>Bmp: free_file_sectors() (上書き前の旧メタデータ&データ)
    FS-->>App: Ok(())

コミット前電源断に対する安全設計

手順 4(Superblock の更新)が完了する前にクラッシュが発生した場合、セクタ 0 は古い root_sector を指したままです。新しく書き込まれたデータセクタや節点セクタは孤立しますが、次回の通常操作やファイルシステムチェック時に安全に再利用され、既存ファイルシステムが不整合になることは構造上あり得ません。

同じパスへ保存し直すとき・ハッシュが衝突したとき(2026-11-10)

B-tree の値は、同じハッシュのメタデータの連鎖(collision_next_sector)の先頭を指す。保存し直すときは連鎖をたどって、次のように扱う。

連鎖の中の状況 メタデータ 前の版の解放
同じパスが先頭(衝突が無い普通の場合) 新しいセクタに書き、木の値を差し替える 確定の後でデータ・溢れ・メタデータを全部解放
同じパスが途中(衝突。まれ) そのセクタをその場で書き換える(前の節を書き換えないため)。木は先頭を指したまま 確定の後でデータ・溢れだけ解放
削除済みの節がある それを使い直す。途中なら木は先頭を指したまま なし(削除のときに解放済み)
どれも無い 新しいセクタに書き、連鎖の先頭につなぐ なし(連鎖の別のファイルは解放しない)

前の版のデータは、確定より前には決して解放しない。確定の前に電源が切れても、前の版のメタデータが解放済みのセクタを指すことは無い。 2026-11-10 まではこの順序が崩れていた。前の版のデータを確定の前に解放してメタデータをその場で書き換えていたほか、衝突した新しいファイルを保存すると、連鎖の先頭の別のファイルを「前の版」として解放していた。 途中の節をその場で書き換える場合だけは、確定の前に電源が切れると新しい版が先に見えることがある(データは書き終えてから書き換えるので、壊れはしない)。


電源断への備え(2026-11-10)

この OS では、電源はいつでもぶつ切りされる前提で書く。

切り替えの単位は 1 セクタの書き込み。 ミラーのある記憶装置(microSD)では、1 セクタを主 → ミラーの順に書き、読むときは CRC の合わない方を捨てる。主を書いている途中で切れると主の CRC が合わず、ミラー(古い内容)が使われて主も直る。主を書き終えた後に切れると、主(新しい内容)が使われる。したがって 1 セクタの書き込みは、電源断でも「古い内容か新しい内容のどちらか」になる(論理的な判断。中途半端な内容は読まれない)。RAMDisk は揮発なので、電源断の後に残る物が無い。

すべての操作は次の順に書く。

  1. 新しい物(データ・溢れのチェーン・メタデータ・木の節点の写し)を空きセクタに書く。生きている物はその場で書き換えない。
  2. 1 セクタの書き込みで、指す先を切り替える(スーパーブロックの根、または 1 個のメタデータ)。
  3. 届かなくなった古い物を解放する。

ビットマップの確保は 1 の前に書くので、電源断で残るのは「確保したが切り替える前」と「切り替えた後で解放する前」の漏れだけになる。

操作 新しい物を書く 切り替え(1 セクタ) 後で返す物
保存(save_file) データ・溢れ・メタデータ・木の写し スーパーブロック(連鎖の途中の上書きは、そのメタデータ 1 セクタ) 前の版のデータ・溢れ・メタデータ・写す前の木
削除(delete_file) 木の写し(連鎖の先頭のとき) スーパーブロック、または手前の節 1 セクタ データ・溢れ・メタデータ・写す前の木
名前の変更(rename_file) 新しいメタデータ・連鎖の手前の写し・木の写し(2 つのキー) スーパーブロック 古いメタデータ・写す前の節と木
切り詰め(truncate_file) 新しい溢れのチェーン そのメタデータ 1 セクタ 要らなくなったデータ・古い溢れのチェーン
伸長 (保存と同じ)
ラベルの更新 なし そのメタデータ 1 セクタ なし

起動時の回収(recover_after_power_loss、mount で呼ぶ)。 スーパーブロックから届くセクタ(木の節点・メタデータの連鎖・データ・溢れ・ビットマップ)に印を付け、ビットマップを「届くセクタだけ使用中」に書き直す。電源断で漏れたセクタはここで戻る。届くのに空きのセクタ(破損)は使用中に直す。読めないセクタがあるときは、その先に印を付けられない恐れがあるので、漏れは解放しない(データを壊す側に倒さない)。

試験(arm/v1/tests/src/test_fs_power_loss.rs)。 RAMDisk の write_budget で「n 回書いた後の書き込みを全部失敗させる」(電源断と同じく、以後ディスクは変わらない)。保存・上書き・削除・名前の変更(溢れのある大きいファイルも)・切り詰め(溢れが残る/無くなる)・伸長・ラベルの更新のそれぞれで、切れ目 n を 0 から最後まで全部試し、開き直して回収した後に、fsck が健全で漏れが 0、ファイルの集合と中身が操作の「前」か「後」のどちらかにそっくり一致することを確かめる。修正前は、名前の変更(2 つの名前が同じデータを指す)と切り詰め(古い版の末尾をその場で 0 で埋めていたため、前とも後とも違う中身)が失敗した。

2重化 (Mirroring) と自動自己修復 (Self-Healing)

MirroredBlockDevice は、ブロックデバイス層の上位に位置し、ストレージの障害耐性を提供します。

flowchart TD
    Start([セクタ読み込み要求: sector]) --> ReadP[プライマリセクタ読み込み]
    ReadP --> CheckP{プライマリの<br/>CRC32 は正常か?}
    
    CheckP -- 一致 (健全) --> ReturnData[バッファへデータ返却: 成功]
    
    CheckP -- 不一致 (破損検知) --> IsMirrored{デバイスは<br/>二重化対象か?}
    
    IsMirrored -- No (RAMDisk) --> ErrRAM[CRC mismatch エラー返却]
    
    IsMirrored -- Yes (SDCard) --> WarnP[警告ログ出力 & ミラー探索開始]
    WarnP --> ReadM[ミラーセクタ読み込み<br/>sector + usable_sectors]
    ReadM --> CheckM{ミラーの<br/>CRC32 は正常か?}
    
    CheckM -- 一致 (健全) --> Heal[★ 自己修復: Self-Healing<br/>ミラーの健全データを<br/>プライマリセクタへ書き戻し]
    Heal --> ReturnData
    
    CheckM -- 不一致 (両系破損) --> FatalErr[二重系全損エラー: Dual-redundancy failure<br/>データ復旧不能]

B+Tree / B-Tree インデックスアルゴリズム

SylFS では、単一の木構造の中で「葉ノード」と「内部ノード」で異なる分割規則を採用したハイブリッド方式を用いています。

  1. 葉ノード(B+Tree 方式):
  2. 内部ノード(B-Tree 方式):
  3. キー比較: UTF-8 ファイル名の FNV-1a 128-bit ハッシュ値(16 バイト)のバイト単位昇順比較。
pub fn fnv1a_128(data: &[u8]) -> [u8; 16] {
    let mut hash: u128 = 0x6c62272e07bb014262b821756295c58d;
    for &b in data {
        hash ^= b as u128;
        hash = hash.wrapping_mul(0x0000000001000000000000000000013B);
    }
    hash.to_le_bytes()
}

空きセクタビットマップアロケータ

ビットマップセクタ数の動的算出

ファイルシステム全体のセクタ数に応じて、必要なビットマップセクタ数が自動算出されます。

$$\text{bitmap\_sectors} = \left\lceil \frac{\text{total\_usable\_sectors}}{508 \times 8} \right\rceil$$

1 つのビットマップセクタ(508 バイト)で 508 × 8 = 4, 064 セクタ(約 2MB)を管理できます。1GB のストレージであっても約 515 セクタ(約 260KB)のビットマップで全域を完全にカバーします。

O(1) 近傍アロケーションヒント (next_bitmap_hint)

ファイルを保存する際、毎回ビットマップの先頭(セクタ 1)から走査すると、ディスクが使用されるにつれて走査コストが O(N2) に悪化し、メガバイト単位のファイル書き込み時に数秒のレイテンシが発生します。 SylFS では next_bitmap_hint に前回確保したビットマップセクタ番号を記憶させ、次回の探索をその位置から開始します。これにより連続確保が事実上 O(1) で完了し、ディスク満杯時にはリング状に一巡して全空き領域を漏れなく検知します。

余剰ビット(境界外)の保護

ビットマップセクタの総ビット数は通常、デバイスの有効セクタ数よりわずかに多くなります(端数)。SylFS は allocated_sector >= total_usable_sectors を厳格に検査し、実在しない架空のセクタ番号が払い出されるのを防ぎます。


ストレージ階層ポリシー(Storage Policy)

pub const TEMP_PREFIXES: &[&str] = &["/cache/", "/tmp/"];

pub fn target_for(path: &str) -> StorageTarget {
    let p = path.trim();
    for prefix in TEMP_PREFIXES {
        if p.starts_with(prefix) {
            return StorageTarget::Volatile; // RAM 上。電源断で消えてよい
        }
    }
    StorageTarget::Persistent // SD カード。電源断後も残す
}

4. 排他制御と並行性(SMP Safety)

SylFS は、マルチコア並行アクセスおよびパニック時の耐障害性を確保するため、以下の同期機構を備えています。

FS_LOCK (RecoverableMutex<()>)

pub static FS_LOCK: crate::kernel::reclock::RecoverableMutex<()> =
    crate::kernel::reclock::RecoverableMutex::new(());

5. API リファレンス

カーネル内部 Rust API

初期化・マウント

pub fn mount() -> Result<(), &'static str>

ファイルシステムをマウントします。microSD カード(SDHOST)の初期化を試行し、検出できた場合は SDCard バックエンドでマウントします。初期化に失敗した場合は Dedicated RAMDisk へ自動フォールバックします。未フォーマットの場合は自動的にフォーマットを実行します。また、/cache/ 等の一時ファイル用 RAMDisk の紐付けと、プリインストールアプリ(APP_ASSETS)の整合性確認を行います。

pub fn format_device(device: &mut MirroredBlockDevice<block::BackendDevice>) -> Result<Superblock, &'static str>

ブロックデバイスを SylFS 形式で初期化(フォーマット)します。ビットマップ領域およびセクタ 0 のスーパーブロックを正しく書き込みます。

pub fn is_mounted() -> bool

SylFS がマウント済みであるかを返します。

pub fn get_fs() -> &'static mut SylFS

グローバルな SylFS インスタンスへの可変参照を取得します。呼び出し時は必ず FS_LOCK を保持している必要があります。


ファイル操作 API

pub fn save_file(&mut self, filename: &str, content: &[u8], labels: &str) -> Result<(), &'static str>

ファイルを保存または完全上書きします(COW 方式)。 - 引数: - filename: 保存先のパス(例: "/user/memo.txt"、最大 127 バイト) - content: ファイルデータの生バイト列 - labels: 検索用ラベル(カンマ区切り、例: "user,doc"、最大 63 バイト) - 戻り値: 成功時は Ok(())、容量不足やハードウェア書き込み失敗時は Err(&'static str)

pub fn read_file_checked(&mut self, filename: &str) -> Result<Option<(FileMetadata, Vec<u8>)>, &'static str>

指定されたパスのファイルを読み込みます。「存在しない」ことと「破損等の異常で読めない」ことを明確に区別して返します。 - 引数: - filename: 読み出すファイルのパス - 戻り値: - Ok(Some((meta, data))): 正常に読み出し完了 - Ok(None): ファイルが存在しない(正常) - Err("metadata sector unreadable"): メタデータが破損しており読み込めない - Err("filename hash collision"): ハッシュ衝突が発生した - Err("extent list unreadable"): エクステント情報が読み込めない - Err("data sector unreadable"): データブロックが破損しており両系復旧も失敗した - Err("file data is truncated"): 実データが途中で途切れている

pub fn read_file(&mut self, filename: &str) -> Option<(FileMetadata, Vec<u8>)>

read_file_checked の簡易ラッパーです。エラー発生時はカーネルエラーログを出力し None を返します。

pub fn delete_file(&mut self, filename: &str) -> Result<(), &'static str>

ファイルを削除します。 - メタデータセクタ内の filename[0] を 0(NUL)に書き換えて論理削除マークを付与します。 - 当該ファイルが占有していた全データセクタおよび溢れエクステントセクタをビットマップ上で即座に解放(deallocate_sector)します。 - B-Tree のダングリングポインタ化を防ぐため、メタデータセクタ自身は予約セクタとして残します。

pub fn list_files(&mut self) -> Vec<FileMetadata>

ファイルシステム内に存在するすべての有効な通常ファイル(削除済みを除く)のメタデータ一覧を返します。SD カードおよび RAMDisk(一時領域)の双方が結合されます。

pub fn list_dir(&mut self, prefix: &str) -> Vec<FileMetadata>

指定されたディレクトリパス(接頭辞 prefix)で始まるファイルをすべて列挙します。

pub fn find_by_label(&mut self, label: &str) -> Vec<FileMetadata>

指定されたラベル(例: "font")を持つすべてのファイルをメタデータ走査により高速抽出します。

pub fn fsck(&mut self) -> Result<FsckReport, &'static str>

ファイルシステム全体の整合性検査(fsck)を実行し、統計情報と健全性フラグを返します。 - 検査項目(2026-11-10 に到達性の検査へ強化): - スーパーブロック・ビットマップ・B-tree の全節点・メタデータ(衝突の連鎖の 2 個目以降も)・データ・溢れのチェーンから実際に届くセクタに印を付ける。 - 印とビットマップを突き合わせる: 使用中だが届かない=漏れ(leaked_sectors)、届くのに空き=破損(unallocated_refs。次の確保で上書きされる)、2 か所から届く=二重参照(double_refs)。範囲外の番号・読めないセクタは corrupted_sectors。 - is_healthy は破損・二重参照・範囲外が 0 のとき真。漏れはデータを壊さないので健全のまま、数だけ報告する。 - 戻り値: 成功時は Ok(FsckReport)(is_healthy, total_sectors, used_sectors, file_count, extent_count, btree_node_count, corrupted_sectors, leaked_sectors, unallocated_refs, double_refs) - 以前はビットマップの使用数を数え、木の葉が指す先頭のメタデータだけを見ていたので、漏れも解放済みのセクタを指す破損も、衝突の連鎖の 2 個目以降のファイルも見えなかった。 - 試験 arm/v1/tests/src/test_fs_fsck_random.rs は、断片化させた上で保存・上書き・削除・名前の変更・切り詰め・伸長を無作為に約 3,000 回繰り返し、毎回この fsck(漏れも 0)と中身を確かめる。


Aura スクリプト組み込みコマンド / システムコール

Aura シェルおよびスクリプト環境から直接呼び出し可能なファイルシステム関連の組み込み関数一覧です。

コマンド構文 引数 説明 実行例
ls [options] [path] options: "-a", "-l", path: ディレクトリ ファイル・ディレクトリの一覧を表示 ls("-la" "/user/")
cd path path: 移動先ディレクトリパス カレントワーキングディレクトリ (cwd) を変更 cd("/user/docs")
pwd なし 現在のカレントワーキングディレクトリを表示 pwd()
mkdir path path: 作成するディレクトリ名 ディレクトリを作成(内部で .keep ファイルを生成) mkdir("/user/music")
cp src dst src: コピー元, dst: コピー先 ファイルを複製 cp("a.txt" "b.txt")
mv src dst src: 移動元, dst: 移動先 ファイルを移動(リネーム) mv("old.txt" "new.txt")
rm path path: 削除対象ファイルパス ファイルを削除し、データセクタを解放 rm("temp.log")
cat path path: ファイルパス テキストファイルの内容を文字列として取得・表示 cat("/sys/config.json")
grep pattern path pattern: 検索文字列, path: ファイル ファイル内を行単位で部分一致検索 grep("ERROR" "/tmp/log")
head path [lines] path: ファイル, lines: 行数 (省略時10) ファイルの先頭 N 行を取得 head("/sys/hosts" 5)
tail path [lines] path: ファイル, lines: 行数 (省略時10) ファイルの末尾 N 行を取得 tail("/tmp/log" 20)
save name data [labels] name: パス, data: 文字列, labels: タグ ファイルを新規保存または完全上書き save("m.txt" "hello" "memo")
show filename filename: パス メタ情報(ラベル、サイズ)とともにファイル内容を表示 show("m.txt")
find label label: 検索タグ文字列 指定ラベルを持つ全ファイルを一覧表示 find("memo")
addlabel filename label filename: パス, label: 追加タグ 既存ファイルに新しいラベルを付与 addlabel("m.txt" "work")
fsck なし ファイルシステム整合性検査: ディスクの健全性・使用セクタ・ファイル統計を表示 fsck()
corrupt sector sector: 破壊するセクタ番号 (整数) デバッグ専用: 指定セクタをゼロクリア破壊(自己修復テスト用) corrupt(120)

6. エラーハンドリング・耐障害性・デバッグ

主要エラーコードと原因・対策

エラーメッセージ 発生原因 システムの動作・対処法
read_sector: both primary and mirror corrupted プライマリセクタ・ミラーセクタの双方が物理的または電気的に破損し、CRC32 検証に失敗した 復旧不能エラー。データ損失を防止するため不正な値は返さず即座にエラーを伝播する
save_file: no free data sector (Disk Full) ストレージの空きセクタビットマップに 0(空き)が存在しない 保存処理を中断し、それまでに確保したデータセクタを直ちに全解放してセクタリークを防ぐ
filename hash collision 異なるファイル名から同一の FNV-1a 128-bit ハッシュ値が生成された 誤ったファイル内容を返さないよう安全に中断。128-bit ハッシュのため実用上の発生確率は約 10−19 以下
metadata sector unreadable ファイルのメタデータセクタの読み込みに失敗した(CRC 不一致等) ファイルが存在しないのではなくストレージ障害と判定。誤って既定値で上書き破棄しないよう上位層にエラー通知
btree: node sector out of range B-Tree ノード内に記録されていた子セクタ番号がデバイスの有効セクタ数を超えている インデックス破損を検知。クラッシュを未然に防ぎ、不正な探索ループを打ち切る
file data is truncated メタデータに記録されたファイルサイズに達する前にセクタチェーンが途切れた 不完全なデータを正常として返さず、データ破損として呼び出し元へ報告する

自己修復のテスト手法(フォールトインジェクション)

SylFS では、Aura の corrupt コマンドおよびカーネル単体テストを通じて、意図的に特定セクタの生データを破壊し、自己修復機能が正しく動作するかを検証できます。

;; 1. テストファイルを保存
(save "/user/test.txt" "AtmOS Resilient Storage Data" "test")

;; 2. ファイルのメタ情報とセクタを確認して生セクタを意図的に破壊
(corrupt 5)

;; 3. 再度読み出しを試みる
;; カーネルは CRC 不一致を検知し、ミラーセクタから自動修復して正常に内容を表示する
(cat "/user/test.txt")
;; ログ出力:
;; [WARN ] CRC mismatch on Primary Sector 5. Initiating Self-Healing...
;; [INFO ] Mirror Sector 524293 is healthy. Healing Primary Sector 5...
;; => "AtmOS Resilient Storage Data"

7. 実装ファイル構成 & 単体テスト体系

カーネル実装ファイル一覧

相対パス 主要責務
arm/v1/src/kernel/fs/mod.rs SylFS コア実装、Superblock/FileMetadata 定義、VFS API、空きセクタビットマップアロケータ、マウント処理、自己テスト
arm/v1/src/kernel/fs/block.rs BlockDevice トレイト、RAMDisk(ヒープ/物理RAM)、SDCard(SDHOST/FAT保護)、MirroredBlockDevice(二重化・自己修復・CRC32)
arm/v1/src/kernel/fs/btree.rs ディスクベースの COW B-Tree インデックス、BTreeNode、ノード分割・再帰挿入・探索アルゴリズム
arm/v1/src/kernel/fs/checksum.rs CRC32(IEEE 802.3 多項式: 0xEDB88320)高速計算エンジン
arm/v1/src/kernel/fs/storage_policy.rs パス別ストレージ振り分けポリシー(/cache/・/tmp/ の揮発性 RAMDisk 誘導)
arm/v1/src/os_lib/aura/builtins/os_fs.rs Aura スクリプト環境向け組み込みコマンドバインディング(ls, cat, save, find 等)

ホスト完全隔離テスト体系

カーネルのソースコード(src/)に一切テストコードを混入させず、arm/v1/tests/ ディレクトリ配下の独立テストクレートから検証されます。ホスト環境(macOS / Linux)上で make test により数ミリ秒で全件実行されます。

テストファイル テスト項目数 主な検証内容
arm/v1/tests/src/test_fs.rs 24 件 基本的な保存・読み出し、上書き、削除、ラベル検索、ディレクトリ列挙、ROM フォールバック
arm/v1/tests/src/test_fs_space_reuse.rs 18 件 ファイル上書き・削除時におけるデータセクタ、溢れエクステント、旧 B-Tree 節点の確実な解放とセクタ再利用(セクタリーク防止)
arm/v1/tests/src/test_fs_error_propagation.rs 15 件 セクタ読み込み失敗・破損時における「存在しない」と「読めない」の厳格な区別、エラーの握り潰し防止
arm/v1/tests/src/kernel/sylfs/ 32 件 B-Tree の大規模ノード分割(1,000キー挿入)、自己修復(プライマリ破壊からの書き戻し復旧)、両系破損検知

8. 実践コード例

1. カーネル内部(Rust)からの利用例

設定ファイルの安全な読み書き

use crate::kernel::fs::{get_fs, FS_LOCK};

pub fn save_system_config(config_json: &str) -> Result<(), &'static str> {
    // 1. マルチコア排他ロックを取得
    let _guard = FS_LOCK.lock();
    let fs = get_fs();

    // 2. ラベル "sys,config" を付与してアトミック保存
    fs.save_file("/sys/config.json", config_json.as_bytes(), "sys,config")?;
    crate::info!("[CONFIG] System configuration successfully updated.");
    Ok(())
}

pub fn load_system_config() -> Result<String, &'static str> {
    let _guard = FS_LOCK.lock();
    let fs = get_fs();

    // 存在しないことと破損を厳格に区別
    match fs.read_file_checked("/sys/config.json")? {
        Some((_meta, data)) => {
            let text = alloc::string::String::from_utf8(data)
                .map_err(|_| "Invalid UTF-8 in config")?;
            Ok(text)
        }
        None => {
            crate::warn!("[CONFIG] Config not found, generating default.");
            Ok(alloc::string::String::from("{}"))
        }
    }
}

2. Aura スクリプトからの実用例

シナリオ A: 日常的なメモの作成とタグ検索

;; メモを作成して保存
(save "/user/notes/meeting.txt" "2026-11-09: SylFS 仕様策定ミーティング議事録" "work,meeting,spec")
(save "/user/notes/todo.txt" "1. ブラウザキャッシュ検証\n2. QEMU 音声出力テスト" "work,todo")

;; "meeting" タグがついたメモを一覧検索
(find "meeting")

;; 内容を確認
(cat "/user/notes/meeting.txt")

シナリオ B: ディレクトリ構造の操作とファイル整理

;; ディレクトリ作成
(mkdir "/user/archive")

;; ファイルをアーカイブへ移動
(mv "/user/notes/meeting.txt" "/user/archive/meeting_20261109.txt")

;; 一覧確認 (詳細表示)
(ls "-l" "/user/archive/")

;; 不要になったファイルの削除
(rm "/user/notes/todo.txt")

シナリオ C: システムログの監視

;; 最新のログ末尾 15 行を確認
(tail "/tmp/system.log" 15)

;; エラー行を抽出
(grep "ERROR" "/tmp/system.log")

削除(delete_file、2026-11-10)

  1. 連鎖を先頭からたどり、消す節とその前の節を探す。
  2. 連鎖の途中なら、前の節の collision_next_sector を次の節へつなぎ替える。連鎖の先頭なら、木の値を次の節(無ければ 0 =「削除した」印)へ差し替えてスーパーブロックを書き、確定する。
  3. データ・溢れのチェーン・メタデータのセクタを返す。2 で木を写せなかった(満杯)ときは、メタデータに印(ファイル名の先頭を 0)を付けて残し、データだけ返す。

search と一覧(collect_leaf_entries)は値 0 のキーを「無い」として扱う。以前は印を付けたメタデータを必ず 1 セクタ残したので、別々のパスを作っては消すと空きが減り続けた(512 セクタで 473 個目に満杯)。

一覧(collect_leaf_entries)の打ち切りは、深さ 32 で行う。以前はたどった節点の総数が 100 を超えると打ち切っていたので、ファイルが約 1,200 個を超えると一覧・fsck から黙って漏れた(3000 個中 1483 個しか出なかった)。

名前の変更(rename_file、2026-11-10)

  1. 移動先の名前が既にあれば失敗する。
  2. 古い名前を連鎖から外す。連鎖の先頭なら木の値を次の節へ差し替える。途中なら、先頭からその手前までの節を空きセクタへ写し(つなぎ先だけ変える)、木の値を写しの先頭へ差し替える(生きている節はその場で書き換えない)。
  3. 新しいメタデータ(データ・溢れ・ラベルを引き継ぐ)を空きセクタに書き、移動先の連鎖の先頭につなぐ。
  4. 2 と 3 の木の変更を同じ写しの上で行い、スーパーブロック 1 回で同時に切り替える。
  5. 確定の後で、古いメタデータ・写す前の節・写す前の木の節点を返す。

以前は、新しい名前を確定した後に別の書き込みで古いメタデータへ削除の印を付けていたので、その間で電源が切れると 2 つの名前が同じデータを指したまま残った(電源断の試験で再現)。さらに以前は古い名前を先に削除済みにしていたので、木への登録に失敗するとファイルが消えた。

既知の制約(2026-11-10 時点)


付録: 定数・マジックナンバー一覧 & 限界値

定数定義テーブル

定数名 値 定義場所 説明
SECTOR_SIZE 512 block.rs 物理ストレージセクタサイズ(バイト)
DATA_SIZE_PER_SECTOR 508 block.rs セクタ内の有効データ領域長(512 - 4 バイト CRC)
SYLFS_MAGIC b"SYLFS\0\0\0" mod.rs スーパーブロック識別マジック(8 バイト)
SUPERBLOCK_SECTOR 0 mod.rs スーパーブロックが配置される固定セクタ番号
BITMAP_START_SECTOR 1 mod.rs 空きセクタビットマップの開始セクタ番号
MAX_INLINE_EXTENTS 18 mod.rs FileMetadata 内に直接保持可能なエクステント数
EXTENTS_PER_OVERFLOW_SECTOR 31 mod.rs 1 つの溢れセクタに格納可能なエクステント数 ((508 − 8)/16)
MAX_KEYS 24 btree.rs B-Tree ノード 1 つあたりに保持できる最大キー数
KEY_LEN 16 btree.rs B-Tree インデックスキーのバイト長(128-bit ハッシュ)
TEMP_PREFIXES &["/cache/", "/tmp/"] storage_policy.rs 揮発性 RAMDisk に自動退避するパス接頭辞

システム限界値

項目 限界値 根拠・制約要因
単一ファイル最大サイズ 264 − 1 バイト ( ≈ 18 EB) FileMetadata.size(u64)およびエクステント表現による
ボリューム最大容量 232 × 508 バイト ( ≈ 2 TB) セクタ番号の表現形式(u32)による物理限界
ファイルパス最大長 127 バイト (UTF-8) FileMetadata.filename(128 バイト、NUL終端必須)
ラベル文字列最大長 63 バイト (UTF-8) FileMetadata.label_data(64 バイト、NUL終端必須)
最大インラインエクステント数 18 エントリ メタデータを 508 バイト以内へ収めるためのコンパイル時制限
最大溢れエクステント数 実質無制限 64-bit リンクリストチェーン構造による動的拡張
B-Tree 最大分岐数 (Fan-out) 25 分岐 MAX_KEYS + 1 による(1 ノード 24 キー、25 ポインタ)