feat(phase110): FileHandleBox実装(ハンドルベース複数回アクセス)
Phase 110 完全実装完了: - FileHandleBox struct で open → read/write → close の複数回アクセスをサポート - 各インスタンスが独立した Ring0FsFileIo を保持 - Fail-Fast 原則:二重 open、close後アクセス、NoFsプロファイル全て即座にエラー - RuntimeProfile 対応:Default で使用可能、NoFs で禁止 実装ファイル: - src/boxes/file/handle_box.rs (新規, 450行) - src/boxes/file/mod.rs (修正, export追加) テスト: 7/7 PASS ✅ - test_filehandlebox_basic_write_read - test_filehandlebox_double_open_error - test_filehandlebox_closed_access_error - test_filehandlebox_write_wrong_mode - test_filehandlebox_multiple_writes - test_filehandlebox_unsupported_mode - test_filehandlebox_independent_instances ドキュメント更新: - core_boxes_design.md (Section 16 追加) - ring0-inventory.md (Phase 110完了) - CURRENT_TASK.md (Phase 110完了セクション) 設計原則: - FileBox: ワンショット I/O(open/close隠蔽) - FileHandleBox: ハンドルベース I/O(複数回アクセス対応) - Ring0FsFileIo を内部で再利用 次フェーズ: Phase 111(append mode + metadata) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -424,7 +424,97 @@ CURRENT_TASK.md 自体は「いまどこを触っているか」と「次に何
|
||||
- Embedded: 組み込みプロファイル
|
||||
|
||||
**次のタスク候補**:
|
||||
- Phase 110: FileHandleBox(複数ファイル同時アクセス)
|
||||
- Phase 111: append mode 追加
|
||||
- ✅ Phase 110: FileHandleBox(複数ファイル同時アクセス) — **完了** (2025-12-03)
|
||||
- Phase 111: append mode 追加 + FileHandleBox metadata
|
||||
- Phase 112: Ring0 service registry 統一化
|
||||
|
||||
---
|
||||
|
||||
## Phase 110: FileHandleBox 実装完了 ✅ (2025-12-03)
|
||||
|
||||
### 実装内容
|
||||
|
||||
**FileHandleBox(ハンドルベース複数回アクセス I/O)の完全実装**
|
||||
|
||||
**ゴール**: FileBox(1ショット I/O)を補完するハンドルベースのファイル I/O を実装
|
||||
|
||||
**実装完了項目**:
|
||||
1. ✅ FileHandleBox struct + NyashBox trait 実装
|
||||
- `src/boxes/file/handle_box.rs`: 新規作成(450行)
|
||||
- 独立インスタンス設計(各ハンドルが独自の Ring0FsFileIo を保持)
|
||||
2. ✅ API 実装
|
||||
- `new()` - ハンドル作成(ファイル未open)
|
||||
- `open(path, mode)` - ファイルを開く("r" or "w")
|
||||
- `read_to_string()` - ファイル内容読み込み
|
||||
- `write_all(content)` - ファイル書き込み
|
||||
- `close()` - ファイルを閉じる
|
||||
- `is_open()` - open 状態確認
|
||||
3. ✅ プロファイル対応
|
||||
- Default ✅: Ring0FsFileIo 経由で完全なファイル I/O
|
||||
- NoFs ❌: open() が即座にエラー
|
||||
4. ✅ テスト実装(7テスト全PASS)
|
||||
- 基本的な書き込み・読み込み
|
||||
- 二重 open エラー
|
||||
- close 後アクセスエラー
|
||||
- read mode で write エラー
|
||||
- 複数回書き込み
|
||||
- 未サポートモードエラー
|
||||
- 独立インスタンス動作確認
|
||||
5. ✅ ドキュメント更新
|
||||
- `core_boxes_design.md`: Section 16 追加
|
||||
- `ring0-inventory.md`: Phase 110 完了マーク
|
||||
- `phase110_filehandlebox_design.md`: 完全仕様(既存)
|
||||
|
||||
**テスト結果**:
|
||||
- ✅ test_filehandlebox_basic_write_read
|
||||
- ✅ test_filehandlebox_double_open_error
|
||||
- ✅ test_filehandlebox_closed_access_error
|
||||
- ✅ test_filehandlebox_write_wrong_mode
|
||||
- ✅ test_filehandlebox_multiple_writes
|
||||
- ✅ test_filehandlebox_unsupported_mode
|
||||
- ✅ test_filehandlebox_independent_instances
|
||||
- ✅ cargo build --release: 成功
|
||||
- ✅ cargo test --release: 7/7 PASS
|
||||
|
||||
**設計原則**:
|
||||
- **Fail-Fast**: 二重 open、close 後アクセス、NoFs profile で即座にエラー
|
||||
- **独立インスタンス**: 各 FileHandleBox が独自の Ring0FsFileIo を保持(複数ファイル同時アクセス可能)
|
||||
- **Ring0 再利用**: Ring0FsFileIo を内部で使用(レイヤー統一)
|
||||
- **後方互換性**: FileBox の既存 API 変更なし
|
||||
|
||||
**FileBox との違い**:
|
||||
- FileBox: 1ショット I/O(read/write 1回ずつ、ファイルを開いて→読む/書く→閉じるを隠す)
|
||||
- FileHandleBox: 複数回アクセス I/O(open → read/write(複数回可)→ close を明示的に制御)
|
||||
|
||||
**実装詳細**:
|
||||
```rust
|
||||
// 各 FileHandleBox が独自の Ring0FsFileIo を作成
|
||||
let ring0 = get_global_ring0();
|
||||
let io: Arc<dyn FileIo> = Arc::new(Ring0FsFileIo::new(ring0.clone()));
|
||||
|
||||
// Write mode の処理
|
||||
if mode == "w" {
|
||||
// ファイルが存在しない場合は空ファイルを作成
|
||||
if !ring0.fs.exists(path_obj) {
|
||||
ring0.fs.write_all(path_obj, &[])?;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**将来拡張予定**:
|
||||
- Phase 111: append mode ("a") サポート
|
||||
- Phase 112: file metadata / stat(size, mtime 等)
|
||||
- Phase 113: Ring0 service registry 統一化
|
||||
- Phase 114: 並行アクセス安全性(Arc<Mutex<...>>)
|
||||
- Phase 115: file encoding explicit 指定(UTF-8 以外)
|
||||
|
||||
**関連ドキュメント**:
|
||||
- [Phase 110 設計書](docs/development/current/main/phase110_filehandlebox_design.md)
|
||||
- [Core Boxes Design](docs/development/current/main/core_boxes_design.md#section-16-phase-110---filehandlebox)
|
||||
- [Ring0 Inventory](docs/development/current/main/ring0-inventory.md)
|
||||
|
||||
**次のタスク候補**:
|
||||
- Phase 111: FileHandleBox append mode 追加 + metadata サポート
|
||||
- Phase 112: Ring0 service registry 統一化
|
||||
- Phase 113: FileIo trait 拡張(exists/stat/canonicalize)
|
||||
|
||||
|
||||
@ -1730,3 +1730,117 @@ NYASH_USE_PLUGIN_HOST=1 ./target/release/hakorune test.hako
|
||||
**Phase 98 実装完了日**: 2025-12-03
|
||||
|
||||
**Phase 99 ポリシー確定日**: 2025-12-03
|
||||
|
||||
---
|
||||
|
||||
## Section 16: Phase 110 - FileHandleBox(ハンドルベース複数回アクセス I/O)
|
||||
|
||||
### 概要
|
||||
|
||||
FileBox(ワンショット I/O)を補完するハンドルベースのファイル I/O。
|
||||
|
||||
- **位置づけ**: core_optional(future で core_required に昇格の可能性)
|
||||
- **API**: open(path, mode) → read/write → close()
|
||||
- **プロファイル対応**: Default ✅、NoFs ❌
|
||||
- **実装**: Ring0FsFileIo を内部で再利用
|
||||
|
||||
### 設計原則
|
||||
|
||||
**FileBox(Phase 108)との違い**:
|
||||
- FileBox: 1ショット I/O(read/write を1回ずつ、ファイルを開いて→読む/書く→閉じるを隠す)
|
||||
- FileHandleBox: 複数回アクセス I/O(open → read/write(複数回可)→ close を明示的に制御)
|
||||
|
||||
**Fail-Fast 原則**:
|
||||
- open() 呼び出し時に既に open 済み → 即座に Err
|
||||
- close() 後の read/write → 即座に Err
|
||||
- NoFs profile で open → 即座に Err
|
||||
|
||||
**独立インスタンス設計**:
|
||||
- 各 FileHandleBox インスタンスが独立した Ring0FsFileIo を保持
|
||||
- 複数の FileHandleBox が同時に異なるファイルを open 可能
|
||||
|
||||
### API
|
||||
|
||||
```rust
|
||||
pub struct FileHandleBox {
|
||||
base: BoxBase,
|
||||
path: String,
|
||||
mode: String,
|
||||
io: Option<Arc<dyn FileIo>>,
|
||||
}
|
||||
|
||||
impl FileHandleBox {
|
||||
pub fn new() -> Self;
|
||||
pub fn open(&mut self, path: &str, mode: &str) -> Result<(), String>;
|
||||
pub fn read_to_string(&self) -> Result<String, String>;
|
||||
pub fn write_all(&self, content: &str) -> Result<(), String>;
|
||||
pub fn close(&mut self) -> Result<(), String>;
|
||||
pub fn is_open(&self) -> bool;
|
||||
}
|
||||
```
|
||||
|
||||
### サポートモード
|
||||
|
||||
**Phase 110**:
|
||||
- "r" (read): ファイル読み込み専用
|
||||
- "w" (write): ファイル上書き書き込み(truncate mode)
|
||||
|
||||
**Phase 111 予定**:
|
||||
- "a" (append): ファイル追記モード
|
||||
|
||||
### プロファイル対応
|
||||
|
||||
| Profile | FileHandleBox | 動作 |
|
||||
|----------|---------------|------|
|
||||
| Default | ✅ | Ring0FsFileIo を使用(完全なファイル I/O) |
|
||||
| NoFs | ❌ | open() が Err を返す("File I/O disabled in no-fs profile") |
|
||||
| TestMock (TBD) | ✅ mock | テスト用ダミー |
|
||||
| Sandbox (TBD) | ✅ dir限定 | サンドボックス内のみアクセス可能 |
|
||||
|
||||
### 実装詳細
|
||||
|
||||
**ファイル**: `src/boxes/file/handle_box.rs`
|
||||
|
||||
**主要機能**:
|
||||
1. new() - ハンドル作成(ファイルは未open)
|
||||
2. open(path, mode) - ファイルを開く
|
||||
- 二重 open チェック(Fail-Fast)
|
||||
- mode 検証("r" or "w")
|
||||
- NoFs profile チェック
|
||||
- Ring0FsFileIo インスタンス作成
|
||||
3. read_to_string() - ファイル内容を読み込み
|
||||
4. write_all(content) - ファイルに書き込み
|
||||
- write mode チェック
|
||||
5. close() - ファイルを閉じる
|
||||
- FileIo インスタンスを drop
|
||||
6. is_open() - ファイルが open されているかチェック
|
||||
|
||||
### テスト
|
||||
|
||||
**実装済みテスト** (7個):
|
||||
1. test_filehandlebox_basic_write_read - 基本的な書き込み・読み込み
|
||||
2. test_filehandlebox_double_open_error - 二重 open エラー
|
||||
3. test_filehandlebox_closed_access_error - close 後アクセスエラー
|
||||
4. test_filehandlebox_write_wrong_mode - read mode で write エラー
|
||||
5. test_filehandlebox_multiple_writes - 複数回書き込み
|
||||
6. test_filehandlebox_unsupported_mode - 未サポートモードエラー
|
||||
7. test_filehandlebox_independent_instances - 独立インスタンス動作確認
|
||||
|
||||
**テスト結果**: ✅ 7/7 PASS
|
||||
|
||||
### 将来の拡張ポイント
|
||||
|
||||
- **Phase 111**: append mode (`mode = "a"`)
|
||||
- **Phase 112**: file metadata / stat(size, mtime 等)
|
||||
- **Phase 113**: Ring0 service registry 統一化
|
||||
- **Phase 114**: 並行アクセス安全性(Arc<Mutex<...>>)
|
||||
- **Phase 115**: file encoding explicit 指定(UTF-8 以外)
|
||||
|
||||
### 関連ドキュメント
|
||||
|
||||
- [Phase 110 設計書](phase110_filehandlebox_design.md) - 完全仕様
|
||||
- [Ring0 Inventory](ring0-inventory.md) - FileIo/FsApi レイヤー設計
|
||||
|
||||
---
|
||||
|
||||
**Phase 110 実装完了日**: 2025-12-03
|
||||
|
||||
@ -579,13 +579,18 @@ Phase 106–108 では FileBox provider_lock / Ring0FsFileIo / write/write_all
|
||||
|
||||
今後の候補フェーズ(優先度の高い順):
|
||||
|
||||
- **Phase 109: runtime profiles(default/no-fs)**
|
||||
- ✅ **Phase 109: runtime profiles(default/no-fs)** (COMPLETED)
|
||||
- `NYASH_PROFILE={default|no-fs}` などのプロファイル導入
|
||||
- default では FileBox 必須、no-fs では FileBox provider 未登録(エラー文字列で通知)
|
||||
- **Phase 110: FileHandleBox**
|
||||
- ✅ **Phase 110: FileHandleBox** (COMPLETED - 2025-12-03)
|
||||
- FileBox を「1ファイル専用」に保ちつつ、複数ファイル同時アクセスは FileHandleBox 側に切り出す
|
||||
- Ring0FsFileIo を再利用してハンドル単位の管理を行う
|
||||
- **Phase 111: Fs metadata 拡張**
|
||||
- ハンドルベース複数回アクセス I/O 完全実装
|
||||
- 実装: `src/boxes/file/handle_box.rs` (7テスト全PASS)
|
||||
- API: open(path, mode) → read/write → close()
|
||||
- プロファイル対応: Default ✅、NoFs ❌
|
||||
- **Phase 111: Fs metadata 拡張 + append mode**
|
||||
- FileHandleBox に append mode ("a") を追加
|
||||
- `exists/metadata/canonicalize` を FileIo / FileBox 側にきちんとエクスポート
|
||||
- Ring0.FsApi の stat 情報を Nyash 側から扱えるようにする
|
||||
|
||||
|
||||
477
src/boxes/file/handle_box.rs
Normal file
477
src/boxes/file/handle_box.rs
Normal file
@ -0,0 +1,477 @@
|
||||
//! Phase 110: FileHandleBox - Handle-based file I/O
|
||||
//!
|
||||
//! Provides multiple-access file I/O pattern:
|
||||
//! - open(path, mode) → read/write (multiple times) → close()
|
||||
//!
|
||||
//! Complements FileBox (one-shot I/O) by allowing multiple reads/writes
|
||||
//! to the same file handle.
|
||||
|
||||
use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
|
||||
use crate::boxes::file::provider::FileIo;
|
||||
use crate::runtime::provider_lock;
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Phase 110: FileHandleBox
|
||||
///
|
||||
/// Handle-based file I/O for multiple-access patterns.
|
||||
///
|
||||
/// # Lifecycle
|
||||
///
|
||||
/// 1. new() - Create handle (file not yet opened)
|
||||
/// 2. open(path, mode) - Open file (stores FileIo instance)
|
||||
/// 3. read_to_string() / write_all() - Multiple accesses allowed
|
||||
/// 4. close() - Close file (resets FileIo)
|
||||
///
|
||||
/// # Design Principles
|
||||
///
|
||||
/// - **Fail-Fast**: Double open() returns Err
|
||||
/// - **Independent instances**: Each FileHandleBox has its own FileIo
|
||||
/// - **Profile-aware**: NoFs profile → open() returns Err
|
||||
/// - **Ring0 reuse**: Uses Ring0FsFileIo internally
|
||||
pub struct FileHandleBox {
|
||||
base: BoxBase,
|
||||
/// Current file path (empty if not open)
|
||||
path: String,
|
||||
/// Current file mode ("r" or "w", empty if not open)
|
||||
mode: String,
|
||||
/// FileIo instance (None if not open)
|
||||
io: Option<Arc<dyn FileIo>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for FileHandleBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("FileHandleBox")
|
||||
.field("path", &self.path)
|
||||
.field("mode", &self.mode)
|
||||
.field("is_open", &self.is_open())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for FileHandleBox {
|
||||
fn clone(&self) -> Self {
|
||||
// Clone creates a new independent handle (not open)
|
||||
// Design decision: Cloning an open handle creates a closed handle
|
||||
// Rationale: Prevents accidental file handle leaks
|
||||
FileHandleBox {
|
||||
base: BoxBase::new(),
|
||||
path: String::new(),
|
||||
mode: String::new(),
|
||||
io: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileHandleBox {
|
||||
/// Create new FileHandleBox (file not yet opened)
|
||||
pub fn new() -> Self {
|
||||
FileHandleBox {
|
||||
base: BoxBase::new(),
|
||||
path: String::new(),
|
||||
mode: String::new(),
|
||||
io: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Open a file
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - path: File path to open
|
||||
/// - mode: "r" (read) or "w" (write)
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - Already open: "FileHandleBox is already open. Call close() first."
|
||||
/// - Unsupported mode: "Unsupported mode: X. Use 'r' or 'w'"
|
||||
/// - NoFs profile: "File I/O disabled in no-fs profile. FileHandleBox is not available."
|
||||
/// - File not found (mode="r"): "File not found: PATH"
|
||||
///
|
||||
/// # Design Notes
|
||||
///
|
||||
/// - Phase 110: Supports "r" and "w" only
|
||||
/// - Phase 111: Will add "a" (append) mode
|
||||
/// - Each FileHandleBox instance gets its own FileIo (independent)
|
||||
pub fn open(&mut self, path: &str, mode: &str) -> Result<(), String> {
|
||||
// Fail-Fast: Check for double open
|
||||
if self.io.is_some() {
|
||||
return Err("FileHandleBox is already open. Call close() first.".to_string());
|
||||
}
|
||||
|
||||
// Validate mode
|
||||
if mode != "r" && mode != "w" {
|
||||
return Err(format!("Unsupported mode: {}. Use 'r' or 'w'", mode));
|
||||
}
|
||||
|
||||
// Get FileIo provider to check capabilities
|
||||
let provider = provider_lock::get_filebox_provider()
|
||||
.ok_or("FileBox provider not initialized")?;
|
||||
|
||||
// NoFs profile check (Fail-Fast)
|
||||
let caps = provider.caps();
|
||||
if !caps.read && !caps.write {
|
||||
return Err("File I/O disabled in no-fs profile. FileHandleBox is not available."
|
||||
.to_string());
|
||||
}
|
||||
|
||||
// Mode-specific capability check
|
||||
if mode == "r" && !caps.read {
|
||||
return Err("Read not supported by FileBox provider".to_string());
|
||||
}
|
||||
if mode == "w" && !caps.write {
|
||||
return Err("Write not supported by FileBox provider".to_string());
|
||||
}
|
||||
|
||||
// Create NEW independent Ring0FsFileIo instance for this handle
|
||||
// IMPORTANT: We must create a new instance, not clone the Arc
|
||||
// because Ring0FsFileIo has internal state (path field)
|
||||
use crate::runtime::get_global_ring0;
|
||||
use crate::providers::ring1::file::ring0_fs_fileio::Ring0FsFileIo;
|
||||
|
||||
let ring0 = get_global_ring0();
|
||||
|
||||
// For write mode, create the file if it doesn't exist
|
||||
// Ring0FsFileIo expects the file to exist, so we create it first
|
||||
if mode == "w" {
|
||||
use std::path::Path;
|
||||
let path_obj = Path::new(path);
|
||||
if !ring0.fs.exists(path_obj) {
|
||||
// Create empty file for write mode
|
||||
ring0
|
||||
.fs
|
||||
.write_all(path_obj, &[])
|
||||
.map_err(|e| format!("Failed to create file: {}", e))?;
|
||||
}
|
||||
}
|
||||
|
||||
let io: Arc<dyn FileIo> = Arc::new(Ring0FsFileIo::new(ring0.clone()));
|
||||
|
||||
// Now open the file with the new instance
|
||||
io.open(path)
|
||||
.map_err(|e| format!("Failed to open file: {}", e))?;
|
||||
|
||||
// Store state
|
||||
self.path = path.to_string();
|
||||
self.mode = mode.to_string();
|
||||
self.io = Some(io);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read file contents to string
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - Not open: "FileHandleBox is not open"
|
||||
/// - Read failed: "Read failed: ERROR"
|
||||
pub fn read_to_string(&self) -> Result<String, String> {
|
||||
self.io
|
||||
.as_ref()
|
||||
.ok_or("FileHandleBox is not open".to_string())?
|
||||
.read()
|
||||
.map_err(|e| format!("Read failed: {}", e))
|
||||
}
|
||||
|
||||
/// Write content to file
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - Not open: "FileHandleBox is not open"
|
||||
/// - Wrong mode: "FileHandleBox opened in read mode"
|
||||
/// - Write failed: "Write failed: ERROR"
|
||||
pub fn write_all(&self, content: &str) -> Result<(), String> {
|
||||
// Fail-Fast: Check mode
|
||||
if self.mode != "w" {
|
||||
return Err("FileHandleBox opened in read mode".to_string());
|
||||
}
|
||||
|
||||
self.io
|
||||
.as_ref()
|
||||
.ok_or("FileHandleBox is not open".to_string())?
|
||||
.write(content)
|
||||
.map_err(|e| format!("Write failed: {}", e))
|
||||
}
|
||||
|
||||
/// Close the file
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - Not open: "FileHandleBox is not open"
|
||||
///
|
||||
/// # Post-condition
|
||||
///
|
||||
/// After close(), is_open() returns false and read/write will fail.
|
||||
pub fn close(&mut self) -> Result<(), String> {
|
||||
if self.io.is_none() {
|
||||
return Err("FileHandleBox is not open".to_string());
|
||||
}
|
||||
|
||||
// Drop the FileIo instance
|
||||
self.io.take();
|
||||
self.path.clear();
|
||||
self.mode.clear();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if file is currently open
|
||||
pub fn is_open(&self) -> bool {
|
||||
self.io.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl BoxCore for FileHandleBox {
|
||||
fn box_id(&self) -> u64 {
|
||||
self.base.id
|
||||
}
|
||||
|
||||
fn parent_type_id(&self) -> Option<std::any::TypeId> {
|
||||
self.base.parent_type_id
|
||||
}
|
||||
|
||||
fn fmt_box(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"FileHandleBox(path={}, mode={}, open={})",
|
||||
if self.path.is_empty() {
|
||||
"<none>"
|
||||
} else {
|
||||
&self.path
|
||||
},
|
||||
if self.mode.is_empty() {
|
||||
"<none>"
|
||||
} else {
|
||||
&self.mode
|
||||
},
|
||||
self.is_open()
|
||||
)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for FileHandleBox {
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn share_box(&self) -> Box<dyn NyashBox> {
|
||||
self.clone_box()
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(format!(
|
||||
"FileHandleBox(path={}, mode={}, open={})",
|
||||
self.path, self.mode, self.is_open()
|
||||
))
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"FileHandleBox"
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_handle) = other.as_any().downcast_ref::<FileHandleBox>() {
|
||||
// Equality: Same path and mode
|
||||
// Note: Two independent handles to same file are equal if path/mode match
|
||||
BoolBox::new(self.path == other_handle.path && self.mode == other_handle.mode)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FileHandleBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.fmt_box(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::runtime::ring0::default_ring0;
|
||||
use crate::providers::ring1::file::ring0_fs_fileio::Ring0FsFileIo;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn setup_test_file(path: &str, content: &str) {
|
||||
let mut file = fs::File::create(path).unwrap();
|
||||
file.write_all(content.as_bytes()).unwrap();
|
||||
}
|
||||
|
||||
fn cleanup_test_file(path: &str) {
|
||||
let _ = fs::remove_file(path);
|
||||
}
|
||||
|
||||
/// Helper: Initialize FileBox provider for tests
|
||||
fn init_test_provider() {
|
||||
// Try to initialize Ring0 (ignore if already initialized)
|
||||
use crate::runtime::ring0::init_global_ring0;
|
||||
use std::panic;
|
||||
|
||||
let _ = panic::catch_unwind(|| {
|
||||
let ring0 = default_ring0();
|
||||
init_global_ring0(ring0);
|
||||
});
|
||||
|
||||
// Set provider if not already set (ignore errors from re-initialization)
|
||||
let ring0_arc = Arc::new(default_ring0());
|
||||
let provider = Arc::new(Ring0FsFileIo::new(ring0_arc));
|
||||
let _ = provider_lock::set_filebox_provider(provider);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filehandlebox_basic_write_read() {
|
||||
init_test_provider();
|
||||
|
||||
let tmp_path = "/tmp/phase110_test_write_read.txt";
|
||||
|
||||
// Write to file
|
||||
let mut h = FileHandleBox::new();
|
||||
assert!(!h.is_open());
|
||||
|
||||
h.open(tmp_path, "w").expect("open for write failed");
|
||||
assert!(h.is_open());
|
||||
|
||||
h.write_all("hello world").expect("write failed");
|
||||
h.close().expect("close failed");
|
||||
assert!(!h.is_open());
|
||||
|
||||
// Read from file (separate instance)
|
||||
let mut h2 = FileHandleBox::new();
|
||||
h2.open(tmp_path, "r").expect("open for read failed");
|
||||
let content = h2.read_to_string().expect("read failed");
|
||||
assert_eq!(content, "hello world");
|
||||
h2.close().expect("close failed");
|
||||
|
||||
cleanup_test_file(tmp_path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filehandlebox_double_open_error() {
|
||||
init_test_provider();
|
||||
|
||||
let tmp_path = "/tmp/phase110_test_double_open.txt";
|
||||
setup_test_file(tmp_path, "test");
|
||||
|
||||
let mut h = FileHandleBox::new();
|
||||
h.open(tmp_path, "r").expect("first open");
|
||||
|
||||
// Second open should fail
|
||||
let result = h.open(tmp_path, "r");
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("already open"));
|
||||
|
||||
h.close().expect("close");
|
||||
cleanup_test_file(tmp_path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filehandlebox_closed_access_error() {
|
||||
init_test_provider();
|
||||
|
||||
let tmp_path = "/tmp/phase110_test_closed_access.txt";
|
||||
setup_test_file(tmp_path, "test");
|
||||
|
||||
let mut h = FileHandleBox::new();
|
||||
h.open(tmp_path, "r").expect("open");
|
||||
h.close().expect("close");
|
||||
|
||||
// Read after close should fail
|
||||
let result = h.read_to_string();
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("not open"));
|
||||
|
||||
cleanup_test_file(tmp_path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filehandlebox_write_wrong_mode() {
|
||||
init_test_provider();
|
||||
|
||||
let tmp_path = "/tmp/phase110_test_write_wrong_mode.txt";
|
||||
setup_test_file(tmp_path, "test");
|
||||
|
||||
let mut h = FileHandleBox::new();
|
||||
h.open(tmp_path, "r").expect("open for read");
|
||||
|
||||
// Write in read mode should fail
|
||||
let result = h.write_all("data");
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("read mode"));
|
||||
|
||||
h.close().expect("close");
|
||||
cleanup_test_file(tmp_path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filehandlebox_multiple_writes() {
|
||||
init_test_provider();
|
||||
|
||||
let tmp_path = "/tmp/phase110_test_multiple_writes.txt";
|
||||
|
||||
let mut h = FileHandleBox::new();
|
||||
h.open(tmp_path, "w").expect("open");
|
||||
|
||||
// Multiple writes (note: current design is truncate mode)
|
||||
h.write_all("first write").expect("first write");
|
||||
// Second write will overwrite (truncate mode)
|
||||
h.write_all("second write").expect("second write");
|
||||
|
||||
h.close().expect("close");
|
||||
|
||||
// Verify final content
|
||||
let content = fs::read_to_string(tmp_path).unwrap();
|
||||
assert_eq!(content, "second write");
|
||||
|
||||
cleanup_test_file(tmp_path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filehandlebox_unsupported_mode() {
|
||||
init_test_provider();
|
||||
|
||||
let mut h = FileHandleBox::new();
|
||||
let result = h.open("/tmp/test.txt", "a");
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("Unsupported mode"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filehandlebox_independent_instances() {
|
||||
init_test_provider();
|
||||
|
||||
let tmp_path1 = "/tmp/phase110_test_independent1.txt";
|
||||
let tmp_path2 = "/tmp/phase110_test_independent2.txt";
|
||||
setup_test_file(tmp_path1, "file1");
|
||||
setup_test_file(tmp_path2, "file2");
|
||||
|
||||
// Two independent handles
|
||||
let mut h1 = FileHandleBox::new();
|
||||
let mut h2 = FileHandleBox::new();
|
||||
|
||||
h1.open(tmp_path1, "r").expect("h1 open");
|
||||
h2.open(tmp_path2, "r").expect("h2 open");
|
||||
|
||||
let content1 = h1.read_to_string().expect("h1 read");
|
||||
let content2 = h2.read_to_string().expect("h2 read");
|
||||
|
||||
assert_eq!(content1, "file1");
|
||||
assert_eq!(content2, "file2");
|
||||
|
||||
h1.close().expect("h1 close");
|
||||
h2.close().expect("h2 close");
|
||||
|
||||
cleanup_test_file(tmp_path1);
|
||||
cleanup_test_file(tmp_path2);
|
||||
}
|
||||
}
|
||||
@ -10,8 +10,12 @@
|
||||
pub mod box_shim; // Thin delegating shim
|
||||
pub mod builtin_factory;
|
||||
pub mod core_ro; // Core read‑only provider
|
||||
pub mod handle_box; // Phase 110: FileHandleBox (handle-based multiple-access I/O)
|
||||
pub mod provider; // trait FileIo / FileCaps / FileError // Builtin FileBox ProviderFactory
|
||||
|
||||
// Re-export FileHandleBox for easier access
|
||||
pub use handle_box::FileHandleBox;
|
||||
|
||||
use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
|
||||
use crate::runtime::provider_lock;
|
||||
use std::any::Any;
|
||||
|
||||
Reference in New Issue
Block a user