Skip to main content

atmos/kernel/fs/
mod.rs

1//! SylFS: Copy-on-Write・ラベル指向の頑健なファイルシステム。
2//!
3//! 特徴(`spec/README.md`「ファイル管理」参照):
4//! - **Copy-on-Write**: 書き込みは新しいセクタへ行い、電源断でも最後の整合状態へ戻れる
5//! - **2 重化**: 全データをミラーリングし、片系故障時は自動復旧([`block::MirroredBlockDevice`])
6//! - **B-tree インデックス**([`btree::CowBTree`]): ラベル・名前・日時の高速検索
7//! - **エクステント方式**([`Extent`]): NTFS の "runs" 同様、連続領域を1エントリで表現し
8//!   ファイルサイズの実質上限を撤廃(2026-07-26 導入)
9//! - **チェックサム**([`checksum`])による自己修復
10//!
11//! サブモジュール: [`storage_policy`](保存方針), [`block`](ミラー化ブロックデバイス),
12//! [`btree`](COW B-tree), [`checksum`](CRC 検証)。
13// fs/mod.rs - SylFS: Copy-on-Write Label-based Robust File System
14#![allow(dead_code)]
15#![allow(static_mut_refs)]
16use crate::println;
17
18pub mod storage_policy;
19pub mod block;
20pub mod btree;
21pub mod checksum;
22
23extern crate alloc;
24use alloc::boxed::Box;
25use alloc::vec::Vec;
26
27use block::{MirroredBlockDevice, RAMDisk, DATA_SIZE_PER_SECTOR};
28use btree::CowBTree;
29use core::ptr::null_mut;
30
31pub fn fnv1a_128(data: &[u8]) -> [u8; 16] {
32    let mut hash: u128 = 0x6c62272e07bb014262b821756295c58d;
33    for &b in data {
34        hash ^= b as u128;
35        hash = hash.wrapping_mul(0x0000000001000000000000000000013B);
36    }
37    hash.to_le_bytes()
38}
39
40/// ファイルが占めるセクタ範囲を「開始セクタ + 連続数」で表す。
41///
42/// 【2026-07-26 設計変更】従来はセクタ番号を 1 個ずつ列挙する方式
43/// (直接ポインタ 44 本 ≈ 22KB、多段間接を足しても 8.3MB)だったため、
44/// ファイルサイズに厳しい上限があった。NTFS の "runs" / exFAT と同じ
45/// **エクステント方式**へ変更する。連続領域なら 1 エントリで
46/// 最大 2^64 セクタを表現できるため、ポインタ列挙のような容量上限が消える。
47#[repr(C, packed)]
48#[derive(Clone, Copy, Default)]
49pub struct Extent {
50    /// 開始セクタ番号。
51    pub start: u64,
52    /// 連続セクタ数(0 なら未使用エントリ)。
53    pub count: u64,
54}
55
56/// メタデータ 1 セクタに直接持てるエクステント数。
57/// filename(128)+label(64)+size(8)+extent_count(4)+overflow(8)+16*N <= 508 → N <= 18。
58const MAX_INLINE_EXTENTS: usize = 18;
59
60/// 溢れたエクステントを格納する 1 セクタに入るエントリ数。
61/// 先頭 8 バイトを「次の溢れセクタ番号」に使い、残りをエントリに充てる。
62const EXTENTS_PER_OVERFLOW_SECTOR: usize = (DATA_SIZE_PER_SECTOR - 8) / 16;
63
64const SUPERBLOCK_SECTOR: usize = 0;
65const BITMAP_START_SECTOR: u32 = 1;
66/// 1 ビットマップセクタが管理できるセクタ数(508 バイト × 8 ビット)。
67const BITS_PER_BITMAP_SECTOR: u64 = (DATA_SIZE_PER_SECTOR as u64) * 8;
68
69/// フォーマット時のビットマップ領域サイズ(セクタ数)を、実際のデバイス容量から求める。
70///
71/// 【2026-07-26】従来は `const BITMAP_SECTORS: u32 = 4` とハードコードされており、
72/// 管理できるのは 4 × 508 × 8 = 16,256 セクタ ≈ **8.3MB だけ**だった。
73/// `sdcard.img` が 1GB あってもその 99% 以上が使われず、
74/// 「ファイル 1 個が 22KB まで」という制限以前に**ファイルシステム全体が
75/// 8MB しか無い**という状態だった。デバイスの総セクタ数から必要な
76/// ビットマップ長を算出することで、ボリューム容量の上限を撤廃する。
77fn bitmap_sectors_for(total_sectors: u64) -> u64 {
78    total_sectors.div_ceil(BITS_PER_BITMAP_SECTOR).max(1)
79}
80
81pub const SYLFS_MAGIC: &[u8; 8] = b"SYLFS\0\0\0";
82
83/// ディスク上の Superblock 構造 (サイズ 24 バイト)
84#[repr(C, packed)]
85#[derive(Clone, Copy)]
86pub struct Superblock {
87    pub magic: [u8; 8],
88    pub version: u32,
89    pub root_sector: u32, // B-Tree インデックスのルート
90    pub bitmap_start: u32,
91    pub bitmap_sectors: u32,
92}
93
94/// ディスク上のファイルメタデータ (サイズ 168 バイト)
95#[repr(C, packed)]
96#[derive(Clone, Copy)]
97pub struct FileMetadata {
98    /// ファイル名/パス (UTF-8, NUL終端)。
99    /// 【2026-07-26】エクステント領域を確保するため 256→128 バイトへ縮小した。
100    /// 本 FS のパスは `/cache/browser/<host>/<hash>` 形式などで 128 バイトあれば足りる。
101    pub filename: [u8; 128],
102    pub label_data: [u8; 64], // ラベル一覧(カンマ区切り, UTF-8, NUL終端)
103    /// ファイルサイズ(バイト)。u32 では 4GB で頭打ちになるため u64 とした。
104    pub size: u64,
105    /// 有効なエクステントの総数(インライン+溢れ分の合計)。
106    pub extent_count: u32,
107    /// 溢れたエクステントを格納するセクタのチェーン先頭(0 なら無し)。
108    /// 断片化が激しくインライン 18 本に収まらない場合のみ使う。
109    pub overflow_sector: u64,
110    /// インラインのエクステント配列。
111    pub extents: [Extent; MAX_INLINE_EXTENTS],
112}
113
114// 【2026-07-26】`FileMetadata` は 1 セクタ(508B)へ丸ごと書き込む前提の構造体。
115// フィールドを足して 508 バイトを超えると、書き込み時に末尾が切り捨てられて
116// データセクタ番号が失われ、**ファイルが静かに壊れる**(実行時まで気付けない)。
117// 間接ブロック用の `indirect_sector` 追加のような変更で容易に起こりうるため、
118// コンパイル時に検査する。
119const _: () = assert!(
120    core::mem::size_of::<FileMetadata>() <= DATA_SIZE_PER_SECTOR,
121    "FileMetadata must fit within a single sector"
122);
123
124impl FileMetadata {
125    pub fn new(name: &str, labels: &str, size: u64) -> Self {
126        let mut fm = Self {
127            filename: [0; 128],
128            label_data: [0; 64],
129            size,
130            extent_count: 0,
131            overflow_sector: 0,
132            extents: [Extent::default(); MAX_INLINE_EXTENTS],
133        };
134
135        // UTF-8 セーフな切り捨て処理 (最大 127 バイト)
136        let mut name_valid_len = 0;
137        for (i, c) in name.char_indices() {
138            if i + c.len_utf8() > 127 {
139                break;
140            }
141            name_valid_len = i + c.len_utf8();
142        }
143        let name_bytes = name.as_bytes();
144        fm.filename[..name_valid_len].copy_from_slice(&name_bytes[..name_valid_len]);
145
146        let mut label_valid_len = 0;
147        for (i, c) in labels.char_indices() {
148            if i + c.len_utf8() > 63 {
149                break;
150            }
151            label_valid_len = i + c.len_utf8();
152        }
153        let label_bytes = labels.as_bytes();
154        fm.label_data[..label_valid_len].copy_from_slice(&label_bytes[..label_valid_len]);
155
156        fm
157    }
158
159    pub fn get_filename(&self) -> &str {
160        let len = self.filename.iter().position(|&x| x == 0).unwrap_or(128);
161        unsafe { core::str::from_utf8_unchecked(&self.filename[..len]) }
162    }
163
164    pub fn get_labels(&self) -> &str {
165        let len = self.label_data.iter().position(|&x| x == 0).unwrap_or(64);
166        unsafe { core::str::from_utf8_unchecked(&self.label_data[..len]) }
167    }
168}
169
170pub struct SylFS {
171    pub device: MirroredBlockDevice<block::BackendDevice>,
172    pub superblock: Superblock,
173    /// 次に空きを探し始めるビットマップセクタ(`allocate_sector` 用)。
174    ///
175    /// 【2026-08-05】以前は毎回**先頭から**走査していた。
176    /// 5.3MB のフォントを保存すると 1 万回以上 `allocate_sector` を呼ぶが、
177    /// 前の方はもう埋まっているので、埋まるほど走査が長くなる。**O(n²)**。
178    ///
179    /// 実測: `cache書=3636tick/14回`(1 回 260 tick=2.6 秒)。
180    ///
181    /// 前回見つけた位置から再開すれば、連続確保が O(1) 近くになる。
182    /// 見つからなければ 0 へ戻して**一巡**するので取りこぼしは無い。
183    next_bitmap_hint: u32,
184}
185
186impl SylFS {
187    /// フォーマット済みのデバイスから `SylFS` を組み立てる。
188    ///
189    /// 内部状態(`next_bitmap_hint`)を持つようになったので、
190    /// 構造体リテラルではなくこれを使う。
191    /// 試験からも同じ経路で作れる(`tests/src/test_fs_error_propagation.rs`)。
192    pub fn from_parts(
193        device: MirroredBlockDevice<block::BackendDevice>,
194        superblock: Superblock,
195    ) -> Self {
196        Self {
197            device,
198            superblock,
199            next_bitmap_hint: 0,
200        }
201    }
202}
203
204static mut FS_INSTANCE: *mut SylFS = null_mut();
205
206// build.rs が自動生成したプリインストールアプリのアセット(AppAsset構造体とAPP_ASSETS配列)を読み込む
207include!(concat!(env!("OUT_DIR"), "/app_assets.rs"));
208
209/// デバイスを SylFS としてフォーマットし、書き込んだスーパーブロックを返します。
210///
211/// 【2026-07-28】従来はビットマップ領域とスーパーブロックの書き込みを
212/// `let _ = device.write_sector(...)` で捨てたうえ、無条件に
213/// 「Format completed successfully!」と出力していた。スーパーブロックが
214/// 書けていなければ次回起動でも「未フォーマット」と判定されるため、
215/// **毎回フォーマットし直して保存内容が全部消えるのに、ログ上は毎回成功**
216/// という最悪の隠蔽になっていた。1 セクタでも書けなければエラーを返す。
217pub fn format_device(
218    device: &mut MirroredBlockDevice<block::BackendDevice>,
219) -> Result<Superblock, &'static str> {
220    let total = device.total_usable_sectors() as u64;
221    let bitmap_sectors = bitmap_sectors_for(total);
222
223    let superblock = Superblock {
224        magic: *SYLFS_MAGIC,
225        version: 1,
226        root_sector: 0, // 初期ツリーは空
227        bitmap_start: BITMAP_START_SECTOR,
228        bitmap_sectors: bitmap_sectors as u32,
229    };
230
231    crate::info!(
232        "[FS] Formatting: total_sectors={} bitmap_sectors={} (capacity {} MiB)",
233        total,
234        bitmap_sectors,
235        total * DATA_SIZE_PER_SECTOR as u64 / (1024 * 1024)
236    );
237
238    // 空きセクタビットマップの初期化。
239    // 先頭のスーパーブロック(セクタ0)とビットマップ領域自身を使用済みにする。
240    let reserved = 1 + bitmap_sectors; // sector 0 + bitmap
241    let mut first = [0u8; DATA_SIZE_PER_SECTOR];
242    for s in 0..reserved.min(BITS_PER_BITMAP_SECTOR) {
243        let byte = (s / 8) as usize;
244        let bit = (s % 8) as u8;
245        first[byte] |= 1 << bit;
246    }
247
248    for i in 0..bitmap_sectors {
249        let sec = BITMAP_START_SECTOR as u64 + i;
250        let data_to_write = if i == 0 {
251            &first[..]
252        } else {
253            &[0u8; DATA_SIZE_PER_SECTOR][..]
254        };
255        if let Err(e) = device.write_sector(sec as usize, data_to_write) {
256            crate::error!("[FS] Format failed: bitmap sector {} not written: {}", sec, e);
257            return Err(e);
258        }
259    }
260
261    // Superblockをコミット
262    let mut sb_buf = [0u8; DATA_SIZE_PER_SECTOR];
263    unsafe {
264        core::ptr::copy_nonoverlapping(
265            &superblock as *const Superblock as *const u8,
266            sb_buf.as_mut_ptr(),
267            core::mem::size_of::<Superblock>(),
268        );
269    }
270    if let Err(e) = device.write_sector(SUPERBLOCK_SECTOR, &sb_buf) {
271        crate::error!("[FS] Format failed: superblock not written: {}", e);
272        return Err(e);
273    }
274
275    crate::info!("[FS] Format completed successfully!");
276    Ok(superblock)
277}
278
279/// ファイルシステムを microSD または RAMDisk 上にマウント(初期化)します。
280/// 未フォーマットの場合は自動的にフォーマットを実行します。
281///
282/// 失敗しても起動を止めないため `FS_INSTANCE` は必ず設定するが、
283/// **フォーマット失敗・プリインストールアプリの書き込み失敗は握り潰さず
284/// 呼び出し側へ返す**(従来はどちらも `let _ =` で捨てていた)。
285pub fn mount() -> Result<(), &'static str> {
286    crate::info!("[FS] Mounting SylFS (COW Label-based File System)...");
287
288    // まずオンボードの microSD カード (SDHOST 制御) の初期化を試みます
289    let backend = match block::SDCard::new() {
290        Ok(sd_card) => {
291            crate::info!("[FS] microSD card backend successfully initialized.");
292            block::BackendDevice::SD(sd_card)
293        }
294        Err(e) => {
295            crate::warn!("[FS] Failed to initialize microSD card: {}. Falling back to RAMDisk...", e);
296            // フォールバック: 8MBのRAMディスクを確保 (512B * 16,384セクタ)
297            // 【2026-07-31】RAMDisk のサイズ根拠。
298            //
299            // 前提(いずれも実測・コード確認済み):
300            //  - `RAMDisk::new` は `vec![0u8; size]` で**起動時に全量を即確保**する。
301            //    遅延確保ではないので、指定した分だけヒープを最初から消費する。
302            //  - カーネルヒープは 512MB(`main.rs` の `heap_size`)。
303            //  - `MirroredBlockDevice` は自己修復のため **容量を半分**にする
304            //    (`block.rs` の `half_sectors = total_sectors() / 2`)。
305            //    → 指定 N バイトの実効容量は N/2。
306            //  - Raspberry Pi 3B+ の搭載 RAM は 1GB。
307            //
308            // 必要量の見積り(www.sugi-lab.net 1 ページ分の実測値):
309            //    Noto Sans JP  5.3MB × 4 ウェイト = 21.2MB
310            //    背景写真                          =  3.7MB
311            //    Font Awesome woff2 0.1MB × 4      =  0.4MB
312            //    CSS / JS / HTML                   =  0.2MB
313            //                                  計 ≒ 26MB / サイト
314            //
315            // 数サイト分を保持したいので実効 64MB を確保する。
316            // ミラーで半分になるため指定値は 128MB。
317            // ヒープ 512MB の 25% で、1GB 搭載機なら妥当な範囲。
318            //
319            // なお RAMDisk は「SD が使えないときのフォールバック」だけでなく、
320            // **一時ファイル(/cache, /tmp)の正規の置き場**でもある。
321            // キャッシュを SD へ書くとフラッシュの書き換え回数を消費して
322            // 寿命を縮めるため、一時ファイルは常に RAM 上に置く
323            // (`fs/storage_policy.rs` の方針、単体試験 6 件で固定)。
324            // 【2026-08-25】ヒープ外の物理空き RAM をそのまま記憶装置に使う。
325            // 実測では 0x2331_19E0 〜 0x3800_0000(332MB)。
326            // 開始はヒープ末尾、終端は `mmu::DEVICE_REGION_START`。
327            // 専用RAMDiskとして直接マッピング。ヒープを1バイトも消費せず、広大な空きRAMを100%有効活用する。
328            extern "C" {
329                static __cpu_stacks_end: u8;
330            }
331            let heap_start = core::ptr::addr_of!(__cpu_stacks_end) as usize;
332            // `main.rs` と同じ値でなければヒープに重なる。定義は 1 箇所。
333            let heap_size = crate::kernel::allocator::KERNEL_HEAP_SIZE;
334            let ramdisk_start = heap_start + heap_size;
335            // 境界の定義は `mmu.rs`(マップを決めている側)が権威。
336            // ここで別の値を書くと、RAMDisk がフレームバッファを踏む。
337            let gpu_boundary = crate::kernel::mmu::DEVICE_REGION_START;
338            let ramdisk_size = if gpu_boundary > ramdisk_start {
339                gpu_boundary - ramdisk_start
340            } else {
341                64 * 1024 * 1024
342            };
343
344            let ram_disk = unsafe { block::RAMDisk::from_raw_memory(ramdisk_start, ramdisk_size) };
345            // 【2026-08-25】`info!` は既定のログ水準(Warn)では**出ない**。
346            // ここは「どの物理範囲を記憶装置に使ったか」という
347            // 起動時 1 回だけの重要な事実で、範囲を誤れば GPU 領域を踏んで
348            // 画面が壊れる。確認できるよう必ず記録する(L-1)。
349            crate::warn!(
350                "[FS] Dedicated RAMDisk allocated from 0x{:08X} to 0x{:08X} ({} MB)",
351                ramdisk_start,
352                ramdisk_start + ramdisk_size,
353                ramdisk_size / (1024 * 1024)
354            );
355            block::BackendDevice::RAM(ram_disk)
356        }
357    };
358
359    let mut device = MirroredBlockDevice::new(backend);
360
361    let mut superblock = Superblock {
362        magic: [0; 8],
363        version: 0,
364        root_sector: 0,
365        bitmap_start: BITMAP_START_SECTOR,
366        bitmap_sectors: 4, // 実際の値はマウント時に読み込むか、フォーマット時に算出する
367    };
368
369    let mut is_formatted = false;
370    let mut buf = [0u8; DATA_SIZE_PER_SECTOR];
371
372    // Superblock (セクタ0) の読み込み試行。未フォーマット時の警告出力をサイレント化する。
373    crate::kernel::uart::set_silent(true);
374    let read_res = device.read_sector(SUPERBLOCK_SECTOR, &mut buf);
375    crate::kernel::uart::set_silent(false);
376
377    if read_res.is_ok() {
378        unsafe {
379            core::ptr::copy_nonoverlapping(
380                buf.as_ptr(),
381                &mut superblock as *mut Superblock as *mut u8,
382                core::mem::size_of::<Superblock>(),
383            );
384        }
385        if &superblock.magic == SYLFS_MAGIC {
386            is_formatted = true;
387            let root_sec = superblock.root_sector;
388            crate::info!(
389                "[FS] SylFS successfully mounted! Root B-Tree root at Sector {}",
390                root_sec
391            );
392        }
393    }
394
395    // 最初に検出した失敗を保持する(起動は続けるが、必ず呼び出し側へ返す)。
396    let mut status: Result<(), &'static str> = Ok(());
397
398    if !is_formatted {
399        crate::info!("[FS] SylFS not detected. Formatting disk...");
400        match format_device(&mut device) {
401            Ok(sb) => superblock = sb,
402            Err(e) => {
403                // フォーマットできていないので、この後のファイル操作はほぼ
404                // すべて失敗する。黙って「成功」にせず記録して先へ進む。
405                crate::error!("[FS] Format failed: {}", e);
406                status = Err(e);
407            }
408        }
409    }
410
411    let fs = Box::new(SylFS::from_parts(device, superblock));
412    unsafe {
413        FS_INSTANCE = Box::into_raw(fs);
414    }
415
416    // デフォルトアプリは毎起動時に最新の状態で上書き(パーミッションの更新などのため)
417    let fs_ref = get_fs();
418
419    let mut asset_failures = 0usize;
420    for asset in APP_ASSETS.iter() {
421        if let Err(e) = fs_ref.save_file(asset.path, asset.data, "sys") {
422            // 書けなかったアプリは起動できない。どのアプリがなぜ入らなかったかを
423            // 残さないと「アプリが無い」としか分からなくなる。
424            crate::error!("[FS] Failed to install default app '{}': {}", asset.path, e);
425            asset_failures += 1;
426        }
427    }
428
429    if asset_failures > 0 {
430        crate::error!(
431            "[FS] {} of {} pre-installed apps could not be written.",
432            asset_failures,
433            APP_ASSETS.len()
434        );
435        if status.is_ok() {
436            status = Err("failed to install pre-installed apps");
437        }
438    } else {
439        crate::info!("[FS] Pre-installed default apps updated.");
440    }
441
442    status
443}
444
445/// ファイルを読み込んでバイト列を返すヘルパー (WiFiファームウェアロード等に使用)
446pub fn read_file(path: &str) -> Option<alloc::vec::Vec<u8>> {
447    let fs = get_fs();
448    let (_meta, data) = fs.read_file(path)?;
449    Some(data)
450}
451
452/// ファイルシステムがマウント済みかどうか。
453pub fn is_mounted() -> bool {
454    unsafe { !FS_INSTANCE.is_null() }
455}
456
457/// グローバルなファイルシステムインスタンスを取得
458///
459/// # SMP 安全性についての注記(2026-07-14 調査)
460/// この関数は `static mut` の生ポインタから `&'static mut` を無条件に発行
461/// するため、型システム上はマルチコアからの並行呼び出しに対して無防備
462/// (排他ロックを一切持たない)。`spec/TODO.md`「デバッグ・検証候補」節の
463/// P1 疑いとして監査した結果、`get_fs()`(および `read_file`/`write_file`
464/// 等)を実際に呼ぶコードは、`kernel/fs/mod.rs` 以外の21ファイル
465/// (JS/Aura ビルトイン・各アプリ・`web_engine`・`cloud.rs` 等)を含め
466/// 全て `Core 0` に `spawn_on_core` で固定された単一プロセス
467/// `gui_shell_process`(`main.rs`)の実行文脈内でのみ呼ばれており、
468/// `Core 1`(`usb_poll_thread`。USB/HID ポーリングのみ)・`Core 2`
469/// (`net_service_thread`。NTP 同期のみ)はいずれもファイルシステムへ
470/// 一切アクセスしないことを確認済み。したがって**現状のアーキテクチャでは
471/// 実際の並行アクセスは発生せず実害は無い**。ただし、この安全性は
472/// コンパイラ/型システムが保証するものではなく「今のところ誰も他コアから
473/// 呼んでいない」という運用上の不変条件に過ぎないため、**今後 FS へ
474/// アクセスするコードを Core 1〜3 側(`spawn_on_core` の新規呼び出し等)に
475/// 追加する場合は、先に排他機構(`spin::Mutex` 等)の導入を検討すること**。
476/// ファイルシステム全体を直列化するロック。
477///
478/// 【2026-08-05】上の注記が予告していた事態が実際に起きた。
479/// リソース取得を 4 並列にしたことで、複数のワーカースレッドが
480/// 同時に `web_engine/cache.rs` 経由で FS を触るようになり、
481/// **空きセクタビットマップが壊れた**。実測の症状:
482///
483/// ```text
484/// [SylFS] Failed to read bitmap sector 10
485/// read '...': data sector 19997 unreadable: both primary and mirror corrupted
486/// read_file('...') failed: extent list unreadable
487/// [CACHE] 保存は成功したのに読み返せない key=... bytes=5327688
488/// ```
489///
490/// 結果として 5.3MB のフォントも 3.7MB の画像もキャッシュに残らず、
491/// **毎回 14MB を取り直していた**(速度にも直結する)。
492///
493/// `get_fs()` は `&'static mut` を返すので、ロックを関数の中へ隠せない
494/// (借用が呼び出し側へ渡るため)。呼び出し側が操作の間だけ保持する。
495///
496/// **今後 FS を触るコードを増やすときは必ずこのロックを取ること。**
497///
498/// 【2026-08-25】以前ここに「現状の取得者は `cache.rs` だけ」と書いてあり、
499/// その記述が古いまま放置された結果、番人なしで FS を触る箇所が
500/// 4 つ増えていた(`layout/values.rs` の画像サイズ解決、
501/// `apps/browser/mod.rs` のローカルパス読み込み 2 箇所、
502/// `js/builtins/web_forms.rs` の localStorage 書き出し)。
503/// とくに最後のひとつは**書き込み**で、ワーカーの書き込みと重なると
504/// 空きセクタビットマップが二重確保され、無関係なファイルが壊れる。
505///
506/// **判定の基準は「ブラウザのワーカースレッドと同時に走りうるか」**。
507/// ブラウザが動いている間、フォント・画像・ローダの各ワーカーは
508/// 常時 FS へ書いている。UI スレッドだから安全、とは言えない。
509///
510/// 取得者を個別に数え上げる書き方はまた古くなるので、ここには書かない。
511/// 探すときは `FS_LOCK` を grep し、`get_fs()` の呼び出しと突き合わせること。
512/// 【2026-08-25 バグ修正】ここは素の `spin::Mutex` だった。
513///
514/// AtmOS は `panic = "abort"` で巻き戻しが無いので、ロックを持ったまま
515/// パニックすると**そのロックは永久に解放されない**。だから
516/// `#[panic_handler]` はヒープ・ネットワーク・USB のロックを
517/// 死んだコアから強制的に取り上げている。
518///
519/// FS_LOCK は後から足したためその手当てが漏れていた。
520/// 画像ワーカーは `os_lib/webp`(panic 禁止ポリシーの**対象外**と
521/// 明記されたベンダリング済みデコーダ)を回しながら FS_LOCK を取る。
522/// 壊れた WebP 一枚で番人が居座り、以後 OS 全体の FS 操作が
523/// 永久に止まる。ネットワークや USB と同じ扱いにする。
524pub static FS_LOCK: crate::kernel::reclock::RecoverableMutex<()> =
525    crate::kernel::reclock::RecoverableMutex::new(());
526
527/// パニックしたコアが保持していた FS ロックを取り上げる。
528/// 他コアが正当に持っているロックには触れない。
529pub fn force_release_locks(cid: i16) {
530    unsafe {
531        FS_LOCK.force_release_if_core(cid);
532    }
533}
534
535pub fn get_fs() -> &'static mut SylFS {
536    unsafe {
537        assert!(!FS_INSTANCE.is_null(), "SylFS not mounted!");
538        &mut *FS_INSTANCE
539    }
540}
541
542impl SylFS {
543    /// 空きセクタをビットマップから探索・確保します (COWトランザクション用)
544    pub fn allocate_sector(&mut self) -> u32 {
545        let mut buf = [0u8; DATA_SIZE_PER_SECTOR];
546
547        // ビットマップ領域を順次走査(長さはフォーマット時にデバイス容量から
548        // 算出してスーパーブロックへ記録済み。固定 4 セクタではない)。
549        //
550        // 【2026-08-05】開始位置を前回の続きにする(`next_bitmap_hint`)。
551        // 毎回先頭からだと、埋まるほど走査が伸びて O(n²) になっていた。
552        // 見つからなければそのまま一巡するので取りこぼしは無い。
553        let total = self.superblock.bitmap_sectors;
554        for step in 0..total {
555            let i = (self.next_bitmap_hint + step) % total.max(1);
556            let sec = self.superblock.bitmap_start + i;
557            if self.device.read_sector(sec as usize, &mut buf).is_err() {
558                println!("[SylFS] Failed to read bitmap sector {}", sec);
559                continue;
560            }
561
562            for byte_idx in 0..DATA_SIZE_PER_SECTOR {
563                let byte = buf[byte_idx];
564                if byte != 0xFF {
565                    // 空きビット(0)を発見
566                    for bit in 0..8 {
567                        if (byte & (1 << bit)) == 0 {
568                            // 使用済みに設定 (1に書き換え)
569                            buf[byte_idx] |= 1 << bit;
570
571                            // ビットマップセクタを更新 (COWトランザクション)
572                            // 【2026-07-27】ここの失敗を握り潰すと、確保したつもりの
573                            // セクタがビットマップ上は空きのままになり、**別のファイルへ
574                            // 二重割り当てされてデータが破壊される**。確保失敗として
575                            // 0 を返し、呼び出し側にエラーを伝える。
576                            if let Err(e) = self.device.write_sector(sec as usize, &buf) {
577                                crate::error!(
578                                    "[SylFS] allocate_sector: bitmap write failed on sector {}: {}",
579                                    sec,
580                                    e
581                                );
582                                return 0;
583                            }
584
585                            // 次の確保はこのセクタから探し始める。
586                            self.next_bitmap_hint = i;
587                            let allocated_sector = (i * DATA_SIZE_PER_SECTOR as u32 * 8)
588                                + (byte_idx as u32 * 8)
589                                + bit as u32;
590                            // 【2026-08-05】ビットマップは**デバイスより多くのビットを持つ**。
591                            //
592                            // ビットマップ長は `total.div_ceil(4064)` で切り上げるので、
593                            // 最後のセクタには実在しないセクタに対応する余剰ビットが残る
594                            // (実効 64MB = 131,072 セクタなら 33 × 4,064 = 134,112 ビットで
595                            //   3,040 ビットが余る)。
596                            //
597                            // 余剰ビットは空き(0)のままなので、ディスクが埋まってくると
598                            // ここが**存在しないセクタ番号**を返し、以後の読み書きが
599                            // `read_sector: sector out of range` で失敗していた。
600                            // 「満杯」ではなく意味不明なエラーとして表面化するため、
601                            // 実測では 5.3MB のフォントが「保存に失敗」と出るだけで
602                            // 原因がまったく分からない状態だった。
603                            //
604                            // 範囲外なら確保せず、満杯として扱う。
605                            if allocated_sector as usize >= self.device.total_usable_sectors() {
606                                // 立てたビットを戻す(このセクタは使えない)。
607                                buf[byte_idx] &= !(1 << bit);
608                                // 【2026-08-05】戻し書きの失敗を握り潰していた。
609                                // 失敗すると**ビットが立ったまま**になり、
610                                // 実在しないセクタが恒久的に「使用中」として
611                                // 残る(セクタリーク)。必ず記録する(L-1)。
612                                if let Err(e) = self.device.write_sector(sec as usize, &buf) {
613                                    crate::error!(
614                                        "[SylFS] allocate_sector: 余剰ビットの戻し書きに失敗 sector={} err={}(ビットが漏れる)",
615                                        sec,
616                                        e
617                                    );
618                                }
619                                crate::warn!(
620                                    "[SylFS] allocate_sector: ビットマップの余剰ビットに到達 (sector={} usable={})。満杯として扱う",
621                                    allocated_sector,
622                                    self.device.total_usable_sectors()
623                                );
624                                return 0;
625                            }
626                            return allocated_sector;
627                        }
628                    }
629                }
630            }
631        }
632        println!("[SylFS] allocate_sector failed (Disk Full)");
633        0 // ディスク満杯
634    }
635
636    /// 指定したセクタをビットマップで解放します
637    pub fn deallocate_sector(&mut self, sector: u32) {
638        let i = sector / (DATA_SIZE_PER_SECTOR as u32 * 8);
639        let rem = sector % (DATA_SIZE_PER_SECTOR as u32 * 8);
640        let byte_idx = (rem / 8) as usize;
641        let bit = rem % 8;
642
643        let sec = self.superblock.bitmap_start + i;
644        let mut buf = [0u8; DATA_SIZE_PER_SECTOR];
645        if self.device.read_sector(sec as usize, &mut buf).is_ok() {
646            buf[byte_idx] &= !(1 << bit); // ビットを0 (空き) に設定
647            // 【2026-07-27】ビットマップ更新の失敗を握り潰すと、解放したはずの
648            // セクタが使用中のまま残り続け(セクタリーク)、やがて Disk Full に
649            // なる。原因を追えるようログを出す。
650            if let Err(e) = self.device.write_sector(sec as usize, &buf) {
651                crate::error!(
652                    "[SylFS] deallocate_sector({}): bitmap write failed: {} (sector leaked)",
653                    sector,
654                    e
655                );
656            }
657        } else {
658            crate::error!(
659                "[SylFS] deallocate_sector({}): bitmap read failed (sector leaked)",
660                sector
661            );
662        }
663    }
664
665    /// ファイルを新しく保存(または上書き)します (COWによる完全保護)
666    pub fn save_file(
667        &mut self,
668        filename: &str,
669        content: &[u8],
670        labels: &str,
671    ) -> Result<(), &'static str> {
672        crate::debug!("[FS] Saving file '{}' with labels [{}]...", filename, labels);
673
674        // 1. ファイルデータ用の空きセクタを確保し、データを書き込む。
675        //    確保したセクタが連続していれば 1 本のエクステントへまとめる
676        //    (NTFS の run / exFAT と同じ考え方)。これにより
677        //    「セクタ番号を 1 個ずつ並べる」方式にあったサイズ上限が無くなる。
678        let needed_sectors = content.len().div_ceil(DATA_SIZE_PER_SECTOR);
679
680        let mut extents: Vec<Extent> = Vec::new();
681        for i in 0..needed_sectors {
682            let sec = self.allocate_sector() as u64;
683            if sec == 0 {
684                return Err("save_file: no free data sector");
685            }
686
687            match extents.last_mut() {
688                // 直前のエクステントの続きなら伸ばすだけ
689                Some(last) if last.start + last.count == sec => last.count += 1,
690                _ => extents.push(Extent {
691                    start: sec,
692                    count: 1,
693                }),
694            }
695
696            let offset = i * DATA_SIZE_PER_SECTOR;
697            let end = (offset + DATA_SIZE_PER_SECTOR).min(content.len());
698            // 【2026-07-27】データ書き込みの失敗を `let _ =` で握り潰していたため、
699            // **1 セクタも書けていなくても save_file が成功を返す**(=ファイルが
700            // 静かに壊れる)状態だった。失敗はログを出したうえでエラーとして返す。
701            if let Err(e) = self
702                .device
703                .write_sector(sec as usize, &content[offset..end])
704            {
705                crate::error!(
706                    "[SylFS] save_file('{}'): data sector {} write failed: {}",
707                    filename,
708                    sec,
709                    e
710                );
711                return Err("save_file: data sector write failed");
712            }
713        }
714
715        // インラインに収まらない分は溢れセクタのチェーンへ書き出す。
716        let inline_count = extents.len().min(MAX_INLINE_EXTENTS);
717        let mut inline_extents = [Extent::default(); MAX_INLINE_EXTENTS];
718        inline_extents[..inline_count].copy_from_slice(&extents[..inline_count]);
719        let overflow_sector = self.write_overflow_extents(&extents[inline_count..])?;
720
721        // 2. メタデータオブジェクトを作成し、空きセクタに書き込み
722        let meta_sector = self.allocate_sector();
723        if meta_sector == 0 {
724            return Err("save_file: no free metadata sector");
725        }
726        let mut meta = FileMetadata::new(filename, labels, content.len() as u64);
727        meta.extents = inline_extents;
728        meta.extent_count = extents.len() as u32;
729        meta.overflow_sector = overflow_sector;
730
731        let mut meta_buf = [0u8; DATA_SIZE_PER_SECTOR];
732        unsafe {
733            core::ptr::copy_nonoverlapping(
734                &meta as *const FileMetadata as *const u8,
735                meta_buf.as_mut_ptr(),
736                core::mem::size_of::<FileMetadata>(),
737            );
738        }
739        self.device.write_sector(meta_sector as usize, &meta_buf)?;
740
741        // 3. B-Tree インデックスへの登録
742        let mut btree = CowBTree::new(self.superblock.root_sector);
743
744        // ラムダ式の借用問題を避けるため、セクタ割り当て用の関数をローカルクロージャで構成
745        let self_ptr = self as *mut Self;
746        let mut alloc_fn = || unsafe { (*self_ptr).allocate_sector() };
747
748        // 3-1. ファイル名インデックスへの登録 (128-bitハッシュ)
749        let hash_key = fnv1a_128(filename.as_bytes());
750        let new_root = btree.insert(&mut self.device, &hash_key, meta_sector, &mut alloc_fn)?;
751        self.superblock.root_sector = new_root;
752
753        // 4. Superblock を更新して、一括コミット (アトミック・整合状態確定)
754        let mut sb_buf = [0u8; DATA_SIZE_PER_SECTOR];
755        unsafe {
756            core::ptr::copy_nonoverlapping(
757                &self.superblock as *const Superblock as *const u8,
758                sb_buf.as_mut_ptr(),
759                core::mem::size_of::<Superblock>(),
760            );
761        }
762        self.device.write_sector(SUPERBLOCK_SECTOR, &sb_buf)?;
763
764        crate::debug!(
765            "  File '{}' successfully saved! Metadata stored at Sector {}",
766            filename,
767            meta_sector
768        );
769        Ok(())
770    }
771
772    /// 指定した名前のファイルの内容をロードして返します
773    /// (チェックサム自動検証 & 自己修復付き、Overlay ROM フォールバック対応)。
774    ///
775    /// # 【2026-07-28】互換用のラッパーであること
776    /// 戻り値が `Option` のため、**「本当に存在しない」と「存在するが読めない」を
777    /// 区別できない**。とくに設定ファイルの読み出しでこれを「存在しない」と
778    /// 解釈すると既定値で上書きしてしまい、**読めなかっただけのファイルを
779    /// 破棄する**ことになる(実際に `config.rs` がその経路を持っていた)。
780    /// 新規のコードは区別できる [`SylFS::read_file_checked`] を使うこと。
781    /// ここでは少なくとも失敗を無言にしないため、エラーをログへ出す。
782    pub fn read_file(&mut self, filename: &str) -> Option<(FileMetadata, Vec<u8>)> {
783        match self.read_file_checked(filename) {
784            Ok(v) => v,
785            Err(e) => {
786                crate::error!("[SylFS] read_file('{}') failed: {}", filename, e);
787                None
788            }
789        }
790    }
791
792    /// ファイルを読み出す。**「存在しない」と「読めない」を区別する**版。
793    ///
794    /// - `Ok(None)` … ファイルが存在しない(正常な結果)
795    /// - `Ok(Some(..))` … 正常に読めた
796    /// - `Err(e)` … 存在するはずなのに読めない(デバイス異常・両系破損・
797    ///   ハッシュ衝突・実体セクタの欠落)。**呼び出し側はこれを「無い」と
798    ///   扱ってはならない**(上書きするとデータを失う)。
799    pub fn read_file_checked(
800        &mut self,
801        filename: &str,
802    ) -> Result<Option<(FileMetadata, Vec<u8>)>, &'static str> {
803        let btree = CowBTree::new(self.superblock.root_sector);
804
805        // 1. B-Treeからファイル名のハッシュでメタデータセクタを検索
806        let hash_key = fnv1a_128(filename.as_bytes());
807        let meta_sector = match btree.search(&mut self.device, &hash_key) {
808            Some(sec) => sec,
809            None => {
810                // Overlay/ROM フォールバック: SD カード上にファイルが存在しない、または削除された場合は
811                // コンパイル時に埋め込まれた APP_ASSETS (ROM) から透過的にデータを返します。
812                for asset in APP_ASSETS.iter() {
813                    if asset.path == filename {
814                        let dummy_meta = FileMetadata::new(filename, "rom", asset.data.len() as u64);
815                        return Ok(Some((dummy_meta, asset.data.to_vec())));
816                    }
817                }
818                // ここだけが「本当に存在しない」正常な結果。
819                return Ok(None);
820            }
821        };
822
823        // 2. メタデータブロックを読み込み
824        let mut buf = [0u8; DATA_SIZE_PER_SECTOR];
825        if let Err(e) = self.device.read_sector(meta_sector as usize, &mut buf) {
826            crate::error!(
827                "[SylFS] read '{}': metadata sector {} unreadable: {}",
828                filename,
829                meta_sector,
830                e
831            );
832            return Err("metadata sector unreadable");
833        }
834        let meta = unsafe { *(buf.as_ptr() as *const FileMetadata) };
835        if meta.get_filename() != filename {
836            // Hash collision (very rare)。「無い」ではなく異常として返す。
837            crate::error!(
838                "[SylFS] read '{}': hash collision with '{}'",
839                filename,
840                meta.get_filename()
841            );
842            return Err("filename hash collision");
843        }
844
845        // 3. データセクタから実体を結合・読み出し
846        let mut content = Vec::new();
847        let mut total_read = 0;
848        let mut i = 0;
849
850        // エクステントを辿って全データセクタ番号を論理順に得る。
851        let extents = self
852            .collect_extents(&meta)
853            .ok_or("extent list unreadable")?;
854        let all_secs = Self::extents_to_sectors(&extents);
855
856        while total_read < meta.size && i < all_secs.len() {
857            let sec = all_secs[i];
858            let mut data_buf = [0u8; DATA_SIZE_PER_SECTOR];
859            if let Err(e) = self.device.read_sector(sec as usize, &mut data_buf) {
860                crate::error!(
861                    "[SylFS] read '{}': data sector {} unreadable: {}",
862                    filename,
863                    sec,
864                    e
865                );
866                return Err("data sector unreadable");
867            }
868
869            let remain = (meta.size - total_read) as usize;
870            let chunk_size = remain.min(DATA_SIZE_PER_SECTOR);
871            content.extend_from_slice(&data_buf[..chunk_size]);
872
873            total_read += chunk_size as u64;
874            i += 1;
875        }
876
877        // 実体が足りないまま終わった場合も「読めた」と言ってはいけない
878        // (途中までのデータを正常な内容として返すと、静かにファイルが壊れる)。
879        // `FileMetadata` は packed なのでフィールドへの参照を作らずコピーする。
880        let expected_size = meta.size;
881        if total_read < expected_size {
882            crate::error!(
883                "[SylFS] read '{}': truncated ({} of {} bytes)",
884                filename,
885                total_read,
886                expected_size
887            );
888            return Err("file data is truncated");
889        }
890
891        Ok(Some((meta, content)))
892    }
893
894    /// インラインに収まらないエクステント群を、セクタのチェーンへ書き出す。
895    /// 各セクタは先頭 8 バイトが「次のセクタ番号(0 で終端)」、以降が
896    /// `Extent` の並び。戻り値はチェーン先頭のセクタ番号(不要なら 0)。
897    fn write_overflow_extents(&mut self, rest: &[Extent]) -> Result<u64, &'static str> {
898        if rest.is_empty() {
899            return Ok(0);
900        }
901        // 後ろのチャンクから作って「次」を繋いでいく。
902        let mut next: u64 = 0;
903        let mut head: u64 = 0;
904        for chunk in rest.chunks(EXTENTS_PER_OVERFLOW_SECTOR).rev() {
905            let sec = self.allocate_sector() as u64;
906            if sec == 0 {
907                return Err("save_file: no free overflow sector");
908            }
909            let mut buf = [0u8; DATA_SIZE_PER_SECTOR];
910            buf[..8].copy_from_slice(&next.to_le_bytes());
911            for (i, e) in chunk.iter().enumerate() {
912                let off = 8 + i * 16;
913                buf[off..off + 8].copy_from_slice(&e.start.to_le_bytes());
914                buf[off + 8..off + 16].copy_from_slice(&e.count.to_le_bytes());
915            }
916            // 【2026-07-27】溢れエクステント表の書き込み失敗を握り潰すと、
917            // メタデータは「続きがある」と記録しているのに実体が無い状態になり、
918            // ファイルが途中までしか読めなくなる。エラーとして返す。
919            if let Err(e) = self.device.write_sector(sec as usize, &buf) {
920                crate::error!(
921                    "[SylFS] write_overflow_extents: sector {} write failed: {}",
922                    sec,
923                    e
924                );
925                return Err("save_file: overflow extent write failed");
926            }
927            next = sec;
928            head = sec;
929        }
930        Ok(head)
931    }
932
933    /// メタデータから全エクステントを論理順に集める(インライン+溢れチェーン)。
934    fn collect_extents(&mut self, meta: &FileMetadata) -> Option<Vec<Extent>> {
935        let total = meta.extent_count as usize;
936        let mut out: Vec<Extent> = Vec::with_capacity(total);
937
938        let inline_n = total.min(MAX_INLINE_EXTENTS);
939        for e in meta.extents.iter().take(inline_n) {
940            out.push(*e);
941        }
942
943        let mut next = meta.overflow_sector;
944        while next != 0 && out.len() < total {
945            let mut buf = [0u8; DATA_SIZE_PER_SECTOR];
946            if self.device.read_sector(next as usize, &mut buf).is_err() {
947                return None;
948            }
949            let following = u64::from_le_bytes([
950                buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
951            ]);
952            for i in 0..EXTENTS_PER_OVERFLOW_SECTOR {
953                if out.len() >= total {
954                    break;
955                }
956                let off = 8 + i * 16;
957                let start = u64::from_le_bytes([
958                    buf[off], buf[off + 1], buf[off + 2], buf[off + 3],
959                    buf[off + 4], buf[off + 5], buf[off + 6], buf[off + 7],
960                ]);
961                let count = u64::from_le_bytes([
962                    buf[off + 8], buf[off + 9], buf[off + 10], buf[off + 11],
963                    buf[off + 12], buf[off + 13], buf[off + 14], buf[off + 15],
964                ]);
965                if count == 0 {
966                    continue;
967                }
968                out.push(Extent { start, count });
969            }
970            next = following;
971        }
972        Some(out)
973    }
974
975    /// エクステント列を、論理順のセクタ番号列へ展開する。
976    fn extents_to_sectors(extents: &[Extent]) -> Vec<u64> {
977        let mut secs = Vec::new();
978        for e in extents {
979            for k in 0..e.count {
980                secs.push(e.start + k);
981            }
982        }
983        secs
984    }
985
986    /// 特定のラベルが割り当てられているファイルをすべて列挙して検索します (メタデータ走査)
987    pub fn find_by_label(&mut self, label: &str) -> Vec<FileMetadata> {
988        let mut results = Vec::new();
989        let all_files = self.list_files();
990        for meta in all_files {
991            if meta.get_labels().split(',').any(|l| l.trim() == label) {
992                results.push(meta);
993            }
994        }
995        results
996    }
997
998    /// すべての通常ファイルを列挙します
999    pub fn list_files(&mut self) -> Vec<FileMetadata> {
1000        let btree = CowBTree::new(self.superblock.root_sector);
1001        let mut entries = Vec::new();
1002        let mut results = Vec::new();
1003        let mut seen_meta_sectors = Vec::new();
1004
1005        btree.collect_leaf_entries(&mut self.device, &mut entries);
1006
1007        for (_, meta_sector) in entries {
1008            if seen_meta_sectors.contains(&meta_sector) {
1009                continue;
1010            }
1011            seen_meta_sectors.push(meta_sector);
1012
1013            let mut buf = [0u8; DATA_SIZE_PER_SECTOR];
1014            if self
1015                .device
1016                .read_sector(meta_sector as usize, &mut buf)
1017                .is_ok()
1018            {
1019                let meta = unsafe { *(buf.as_ptr() as *const FileMetadata) };
1020                // 削除済みファイル (ファイル名の先頭がNULバイト) は除外
1021                if meta.filename[0] != 0 {
1022                    results.push(meta);
1023                }
1024            }
1025        }
1026
1027        results
1028    }
1029
1030    /// 指定した名前のファイルを削除します (メタデータとデータセクタの安全な解放)
1031    pub fn delete_file(&mut self, filename: &str) -> Result<(), &'static str> {
1032        let btree = CowBTree::new(self.superblock.root_sector);
1033        let hash_key = fnv1a_128(filename.as_bytes());
1034
1035        if let Some(meta_sector) = btree.search(&mut self.device, &hash_key) {
1036            let mut buf = [0u8; DATA_SIZE_PER_SECTOR];
1037            if self
1038                .device
1039                .read_sector(meta_sector as usize, &mut buf)
1040                .is_ok()
1041            {
1042                let mut meta = unsafe { *(buf.as_ptr() as *const FileMetadata) };
1043
1044                // 1. メタデータ内のファイル名先頭を0にして「削除済み」とマーク
1045                meta.filename[0] = 0;
1046
1047                let mut meta_buf = [0u8; DATA_SIZE_PER_SECTOR];
1048                unsafe {
1049                    core::ptr::copy_nonoverlapping(
1050                        &meta as *const FileMetadata as *const u8,
1051                        meta_buf.as_mut_ptr(),
1052                        core::mem::size_of::<FileMetadata>(),
1053                    );
1054                }
1055                self.device.write_sector(meta_sector as usize, &meta_buf)?;
1056
1057                // 2. エクステントが指す全データセクタを解放する。
1058                if let Some(extents) = self.collect_extents(&meta) {
1059                    for sec in Self::extents_to_sectors(&extents) {
1060                        self.deallocate_sector(sec as u32);
1061                    }
1062                }
1063
1064                // 2b. 溢れエクステントのチェーン自体も解放する
1065                //     (漏らすとセクタリークになる)。
1066                let mut next = meta.overflow_sector;
1067                while next != 0 {
1068                    let mut buf = [0u8; DATA_SIZE_PER_SECTOR];
1069                    let following = if self.device.read_sector(next as usize, &mut buf).is_ok() {
1070                        u64::from_le_bytes([
1071                            buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
1072                        ])
1073                    } else {
1074                        0
1075                    };
1076                    self.deallocate_sector(next as u32);
1077                    next = following;
1078                }
1079
1080                // 3. メタデータセクタ自身の deallocate (B-Treeからキーが削除されないため、dangling pointer化を防ぐ目的でメタデータセクタは解放せずにゴミとして残す)
1081                // self.deallocate_sector(meta_sector);
1082
1083                println!(
1084                    "  SylFS: File '{}' deallocated and deleted successfully.",
1085                    filename
1086                );
1087                return Ok(());
1088            }
1089        }
1090        Err("delete_file: file not found")
1091    }
1092
1093    /// プレフィックス(ディレクトリパス等)で始まるファイルを一覧取得します
1094    pub fn list_dir(&mut self, prefix: &str) -> Vec<FileMetadata> {
1095        let mut results = Vec::new();
1096        let all_files = self.list_files();
1097        for meta in all_files {
1098            if meta.get_filename().starts_with(prefix) {
1099                results.push(meta);
1100            }
1101        }
1102        results
1103    }
1104}
1105
1106/// SylFS 自己テスト(CI スモークの判定マーカー `FS_SELFTEST`)。
1107/// グローバル FS には一切触れず、専用の RAM デバイス上で
1108/// ①COW B-Tree が分割後も全キー上書き整合を保つこと(分離キー上書きで部分木が壊れる回帰の捕捉)、
1109/// ②二重化レイヤがプライマリ破損をミラーから自己修復し、両系破損時のみ失敗すること、を検証する。
1110pub fn selftest() -> (u32, u32) {
1111    let mut pass = 0u32;
1112    let mut total = 0u32;
1113
1114    // --- 1. COW B-Tree: 分割を強制し、全キーを上書きしても全件正しく引けること ---
1115    {
1116        let mut dev = MirroredBlockDevice::new(RAMDisk::new(1024 * 1024));
1117        let mut next_sec: u32 = 10; // ビットマップを介さない単純払い出し(テスト専用)
1118        let mut btree = CowBTree::new(0);
1119        const N: u32 = 40; // MAX_KEYS(24) を超え、複数回の分割を誘発する
1120
1121        let make_key = |i: u32| -> [u8; 16] {
1122            let mut k = [0u8; 16];
1123            k[..4].copy_from_slice(&i.to_be_bytes());
1124            k
1125        };
1126
1127        // 初回挿入(値 = 2000+i)
1128        let mut ok = true;
1129        for i in 0..N {
1130            let key = make_key(i);
1131            let mut alloc = || {
1132                let s = next_sec;
1133                next_sec += 1;
1134                s
1135            };
1136            if btree.insert(&mut dev, &key, 2000 + i, &mut alloc).is_err() {
1137                ok = false;
1138                break;
1139            }
1140        }
1141        total += 1;
1142        if ok {
1143            pass += 1;
1144        }
1145
1146        // 全キーを上書き(値 = 3000+i)。分離キーへ昇格したキーの上書きを含む。
1147        let mut ok2 = true;
1148        for i in 0..N {
1149            let key = make_key(i);
1150            let mut alloc = || {
1151                let s = next_sec;
1152                next_sec += 1;
1153                s
1154            };
1155            if btree.insert(&mut dev, &key, 3000 + i, &mut alloc).is_err() {
1156                ok2 = false;
1157                break;
1158            }
1159        }
1160        total += 1;
1161        if ok2 {
1162            pass += 1;
1163        }
1164
1165        // 全キーが「更新後の値」で引けること(部分木喪失が無いこと)
1166        let mut all_ok = true;
1167        for i in 0..N {
1168            let key = make_key(i);
1169            if btree.search(&mut dev, &key) != Some(3000 + i) {
1170                all_ok = false;
1171            }
1172        }
1173        total += 1;
1174        if all_ok {
1175            pass += 1;
1176        }
1177    }
1178
1179    // --- 2. 二重化レイヤ: 自己修復と両系破損検知 ---
1180    {
1181        let mut dev = MirroredBlockDevice::new(RAMDisk::new(64 * 1024));
1182        let sector = 5usize;
1183        let payload = b"SylFS dual-redundancy selftest payload";
1184        // 準備の書き込み自体が失敗していると、以降の判定は「何を測ったのか
1185        // 分からない失敗」になる。準備段階も 1 項目として数える。
1186        total += 1;
1187        if dev.write_sector(sector, payload).is_ok() {
1188            pass += 1;
1189        }
1190
1191        // プライマリのみ破損 → ミラーから読み出し成功&内容一致(自己修復)
1192        let _ = dev.force_corrupt_raw_sector(sector);
1193        let mut buf = [0u8; DATA_SIZE_PER_SECTOR];
1194        let healed = dev.read_sector(sector, &mut buf).is_ok() && &buf[..payload.len()] == payload;
1195        total += 1;
1196        if healed {
1197            pass += 1;
1198        }
1199
1200        // 修復後はプライマリ単体でも再読込できること
1201        let mut buf2 = [0u8; DATA_SIZE_PER_SECTOR];
1202        let reread =
1203            dev.read_sector(sector, &mut buf2).is_ok() && &buf2[..payload.len()] == payload;
1204        total += 1;
1205        if reread {
1206            pass += 1;
1207        }
1208
1209        // 両系破損は復旧不能(Err)であること
1210        let mirror = sector + dev.total_usable_sectors();
1211        let _ = dev.force_corrupt_raw_sector(sector);
1212        let _ = dev.force_corrupt_raw_sector(mirror);
1213        let mut buf3 = [0u8; DATA_SIZE_PER_SECTOR];
1214        total += 1;
1215        if dev.read_sector(sector, &mut buf3).is_err() {
1216            pass += 1;
1217        }
1218    }
1219
1220    (pass, total)
1221}