feat(phase111): FileHandleBox append+metadata実装(修正案統合版)
Task 2: FsApi / Ring0FsFileIo 拡張 - FsApi trait に append_all(path, data) メソッド追加 - StdFsApi で append_all() を std::fs::OpenOptions で実装 - Ring0FsFileIo に mode を保持、write() で truncate/append を切り替え - Ring0FsFileIo に内部 metadata() ヘルパ追加(FsApi.metadata() 呼び出し) Task 3: FileHandleBox API 実装 - open(path, mode) で "r"/"w"/"a" 3モードをサポート - write_all() で read-only mode チェック - 内部 Rust API:size / exists / is_file / is_dir メソッド実装 (NyashBox 公開は Phase 112+ に延期) Task 5: テスト + ドキュメント - 4つの新テスト PASS: - test_filehandlebox_append_mode(write→append→内容確認) - test_filehandlebox_metadata_size(size() 取得) - test_filehandlebox_metadata_is_file(is_file()/is_dir()) - test_filehandlebox_write_readonly_error("r"で write 拒否) 統計: - 9ファイル修正(+316行, -35行) - 4つの新テスト追加(既存15テスト全PASS) - cargo build --release: SUCCESS - 11個のチェックリスト: ✅ ALL PASS 次フェーズ(Phase 112-114)の backlog も指示書で整理済み 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -32,7 +32,7 @@ pub fn not_open() -> String {
|
||||
|
||||
/// Unsupported mode error (with mode name)
|
||||
pub fn unsupported_mode(mode: &str) -> String {
|
||||
format!("Unsupported mode: {}. Use 'r' or 'w'", mode)
|
||||
format!("Unsupported mode: {}. Use 'r', 'w', or 'a'", mode)
|
||||
}
|
||||
|
||||
/// Read not supported by provider
|
||||
|
||||
@ -80,19 +80,18 @@ impl FileHandleBox {
|
||||
/// # Arguments
|
||||
///
|
||||
/// - path: File path to open
|
||||
/// - mode: "r" (read) or "w" (write)
|
||||
/// - mode: "r" (read), "w" (write/truncate), or "a" (append)
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - Already open: "FileHandleBox is already open. Call close() first."
|
||||
/// - Unsupported mode: "Unsupported mode: X. Use 'r' or 'w'"
|
||||
/// - Unsupported mode: "Unsupported mode: X. Use 'r', 'w', or 'a'"
|
||||
/// - 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
|
||||
/// - Phase 111: Supports "r", "w", and "a" modes
|
||||
/// - 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
|
||||
@ -100,8 +99,8 @@ impl FileHandleBox {
|
||||
return Err(already_open());
|
||||
}
|
||||
|
||||
// Validate mode
|
||||
if mode != "r" && mode != "w" {
|
||||
// Validate mode (Phase 111: "a" added)
|
||||
if mode != "r" && mode != "w" && mode != "a" {
|
||||
return Err(unsupported_mode(mode));
|
||||
}
|
||||
|
||||
@ -126,13 +125,13 @@ impl FileHandleBox {
|
||||
|
||||
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" {
|
||||
// For write/append mode, create the file if it doesn't exist
|
||||
// Ring0FsFileIo expects the file to exist for open(), so we create it first
|
||||
if mode == "w" || mode == "a" {
|
||||
use std::path::Path;
|
||||
let path_obj = Path::new(path);
|
||||
if !ring0.fs.exists(path_obj) {
|
||||
// Create empty file for write mode
|
||||
// Create empty file for write/append mode
|
||||
ring0
|
||||
.fs
|
||||
.write_all(path_obj, &[])
|
||||
@ -140,7 +139,12 @@ impl FileHandleBox {
|
||||
}
|
||||
}
|
||||
|
||||
let io: Arc<dyn FileIo> = Arc::new(Ring0FsFileIo::new(ring0.clone()));
|
||||
let file_io = Ring0FsFileIo::new(ring0.clone());
|
||||
|
||||
// Set mode BEFORE opening (Phase 111)
|
||||
file_io.set_mode(mode.to_string());
|
||||
|
||||
let io: Arc<dyn FileIo> = Arc::new(file_io);
|
||||
|
||||
// Now open the file with the new instance
|
||||
io.open(path)
|
||||
@ -175,10 +179,15 @@ impl FileHandleBox {
|
||||
/// - Not open: "FileHandleBox is not open"
|
||||
/// - Wrong mode: "FileHandleBox opened in read mode"
|
||||
/// - Write failed: "Write failed: ERROR"
|
||||
///
|
||||
/// # Phase 111
|
||||
///
|
||||
/// Supports both "w" (truncate) and "a" (append) modes.
|
||||
/// Mode "r" returns error.
|
||||
pub fn write_all(&self, content: &str) -> Result<(), String> {
|
||||
// Fail-Fast: Check mode
|
||||
if self.mode != "w" {
|
||||
return Err(opened_in_read_mode());
|
||||
// Fail-Fast: Check mode (Phase 111: allow "w" and "a")
|
||||
if self.mode == "r" {
|
||||
return Err("FileHandleBox is opened in read-only mode".to_string());
|
||||
}
|
||||
|
||||
self.io
|
||||
@ -214,6 +223,75 @@ impl FileHandleBox {
|
||||
pub fn is_open(&self) -> bool {
|
||||
self.io.is_some()
|
||||
}
|
||||
|
||||
// ===== Phase 111: Metadata methods (internal Rust API) =====
|
||||
|
||||
/// Internal helper: Get FsMetadata from Ring0FsFileIo
|
||||
fn metadata_internal(&self) -> Result<crate::runtime::ring0::FsMetadata, String> {
|
||||
if self.path.is_empty() {
|
||||
return Err("FileHandleBox path not set".to_string());
|
||||
}
|
||||
|
||||
// Get Ring0FsFileIo and call metadata()
|
||||
self.io
|
||||
.as_ref()
|
||||
.ok_or_else(|| "FileHandleBox not open".to_string())?
|
||||
.as_any()
|
||||
.downcast_ref::<crate::providers::ring1::file::ring0_fs_fileio::Ring0FsFileIo>()
|
||||
.ok_or_else(|| "FileIo is not Ring0FsFileIo".to_string())?
|
||||
.metadata()
|
||||
.map_err(|e| format!("Metadata failed: {}", e))
|
||||
}
|
||||
|
||||
/// Get file size in bytes
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - Path not set: "FileHandleBox path not set"
|
||||
/// - Metadata failed: "Metadata failed: ERROR"
|
||||
pub fn size(&self) -> Result<u64, String> {
|
||||
self.metadata_internal().map(|meta| meta.len)
|
||||
}
|
||||
|
||||
/// Check if file exists
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - Path not set: "FileHandleBox path not set"
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Returns false if file not found (no error)
|
||||
pub fn exists(&self) -> Result<bool, String> {
|
||||
if self.path.is_empty() {
|
||||
return Err("FileHandleBox path not set".to_string());
|
||||
}
|
||||
|
||||
match self.metadata_internal() {
|
||||
Ok(_) => Ok(true),
|
||||
Err(_) => Ok(false), // not found → false
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if path is a file
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - Path not set: "FileHandleBox path not set"
|
||||
/// - Metadata failed: "Metadata failed: ERROR"
|
||||
pub fn is_file(&self) -> Result<bool, String> {
|
||||
self.metadata_internal().map(|meta| meta.is_file)
|
||||
}
|
||||
|
||||
/// Check if path is a directory
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - Path not set: "FileHandleBox path not set"
|
||||
/// - Metadata failed: "Metadata failed: ERROR"
|
||||
pub fn is_dir(&self) -> Result<bool, String> {
|
||||
self.metadata_internal().map(|meta| meta.is_dir)
|
||||
}
|
||||
}
|
||||
|
||||
impl BoxCore for FileHandleBox {
|
||||
@ -402,7 +480,7 @@ mod tests {
|
||||
// Write in read mode should fail
|
||||
let result = h.write_all("data");
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("read mode"));
|
||||
assert!(result.unwrap_err().contains("read-only"));
|
||||
|
||||
h.close().expect("close");
|
||||
cleanup_test_file(tmp_path);
|
||||
@ -436,7 +514,8 @@ mod tests {
|
||||
init_test_provider();
|
||||
|
||||
let mut h = FileHandleBox::new();
|
||||
let result = h.open("/tmp/test.txt", "a");
|
||||
// Phase 111: "a" is now supported, test with "x" instead
|
||||
let result = h.open("/tmp/test.txt", "x");
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("Unsupported mode"));
|
||||
}
|
||||
@ -575,4 +654,120 @@ mod tests {
|
||||
|
||||
cleanup_test_file(tmp_path);
|
||||
}
|
||||
|
||||
// ===== Phase 111: Append mode + metadata tests =====
|
||||
|
||||
#[test]
|
||||
fn test_filehandlebox_append_mode() {
|
||||
init_test_provider();
|
||||
|
||||
let path = "/tmp/phase111_append_test.txt";
|
||||
let _ = fs::remove_file(path); // cleanup
|
||||
|
||||
// First write (truncate)
|
||||
let mut handle = FileHandleBox::new();
|
||||
handle.open(path, "w").unwrap();
|
||||
handle.write_all("hello\n").unwrap();
|
||||
handle.close().unwrap();
|
||||
|
||||
// Append
|
||||
let mut handle = FileHandleBox::new();
|
||||
handle.open(path, "a").unwrap();
|
||||
handle.write_all("world\n").unwrap();
|
||||
handle.close().unwrap();
|
||||
|
||||
// Verify
|
||||
let content = fs::read_to_string(path).unwrap();
|
||||
assert_eq!(content, "hello\nworld\n");
|
||||
|
||||
let _ = fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filehandlebox_metadata_size() {
|
||||
init_test_provider();
|
||||
|
||||
let path = "/tmp/phase111_metadata_test.txt";
|
||||
let _ = fs::remove_file(path);
|
||||
|
||||
// Write test file
|
||||
let mut handle = FileHandleBox::new();
|
||||
handle.open(path, "w").unwrap();
|
||||
handle.write_all("hello").unwrap(); // 5 bytes
|
||||
handle.close().unwrap();
|
||||
|
||||
// Check size
|
||||
let mut handle = FileHandleBox::new();
|
||||
handle.open(path, "r").unwrap();
|
||||
let size = handle.size().unwrap();
|
||||
assert_eq!(size, 5);
|
||||
handle.close().unwrap();
|
||||
|
||||
let _ = fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filehandlebox_metadata_is_file() {
|
||||
init_test_provider();
|
||||
|
||||
let path = "/tmp/phase111_file_test.txt";
|
||||
let _ = fs::remove_file(path);
|
||||
|
||||
// Create file
|
||||
let mut handle = FileHandleBox::new();
|
||||
handle.open(path, "w").unwrap();
|
||||
handle.close().unwrap();
|
||||
|
||||
// Check is_file
|
||||
let mut handle = FileHandleBox::new();
|
||||
handle.open(path, "r").unwrap();
|
||||
let is_file = handle.is_file().unwrap();
|
||||
assert!(is_file);
|
||||
|
||||
let is_dir = handle.is_dir().unwrap();
|
||||
assert!(!is_dir);
|
||||
handle.close().unwrap();
|
||||
|
||||
let _ = fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filehandlebox_write_readonly_error() {
|
||||
init_test_provider();
|
||||
|
||||
let path = "/tmp/phase111_readonly_test.txt";
|
||||
let _ = fs::remove_file(path);
|
||||
|
||||
// Create file
|
||||
fs::write(path, "content").unwrap();
|
||||
|
||||
// Open in read mode
|
||||
let mut handle = FileHandleBox::new();
|
||||
handle.open(path, "r").unwrap();
|
||||
|
||||
// Try to write → Error
|
||||
let result = handle.write_all("new");
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("read-only"));
|
||||
|
||||
handle.close().unwrap();
|
||||
let _ = fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore] // This test requires NYASH_RUNTIME_PROFILE=no-fs environment variable
|
||||
fn test_filehandlebox_nofs_profile_error() {
|
||||
// Note: This test should be run with NYASH_RUNTIME_PROFILE=no-fs
|
||||
// For now, we test that open() fails with disabled message when provider is None
|
||||
|
||||
// Cannot directly test NoFs profile in unit tests without setting env var
|
||||
// and reinitializing runtime. This test is marked as ignored and should be
|
||||
// run separately with proper profile configuration.
|
||||
|
||||
// The test would look like:
|
||||
// let mut handle = FileHandleBox::new();
|
||||
// let result = handle.open("/tmp/test.txt", "w");
|
||||
// assert!(result.is_err());
|
||||
// assert!(result.unwrap_err().contains("disabled"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,11 +37,11 @@ impl FileCaps {
|
||||
|
||||
/// Phase 110.5: Capability check helper
|
||||
///
|
||||
/// Validates if the given mode ("r" or "w") is supported by this provider.
|
||||
/// Validates if the given mode ("r", "w", or "a") is supported by this provider.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - mode: "r" (read) or "w" (write)
|
||||
/// - mode: "r" (read), "w" (write), or "a" (append)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
@ -60,7 +60,7 @@ impl FileCaps {
|
||||
return Err("Read not supported by FileBox provider".to_string());
|
||||
}
|
||||
}
|
||||
"w" => {
|
||||
"w" | "a" => { // Phase 111: "a" added
|
||||
if !self.write {
|
||||
return Err("Write not supported by FileBox provider".to_string());
|
||||
}
|
||||
@ -78,8 +78,8 @@ impl FileCaps {
|
||||
pub enum FileError {
|
||||
#[error("io error: {0}")]
|
||||
Io(String),
|
||||
#[error("unsupported operation")]
|
||||
Unsupported,
|
||||
#[error("unsupported operation: {0}")]
|
||||
Unsupported(String),
|
||||
}
|
||||
|
||||
pub type FileResult<T> = Result<T, FileError>;
|
||||
@ -91,6 +91,9 @@ pub trait FileIo: Send + Sync {
|
||||
fn read(&self) -> FileResult<String>;
|
||||
fn write(&self, text: &str) -> FileResult<()>; // Phase 108: write support
|
||||
fn close(&self) -> FileResult<()>;
|
||||
|
||||
/// Phase 111: Downcast support for metadata access
|
||||
fn as_any(&self) -> &dyn std::any::Any;
|
||||
// Future: exists/stat …
|
||||
}
|
||||
|
||||
|
||||
@ -44,13 +44,17 @@ impl FileIo for CoreRoFileIo {
|
||||
|
||||
fn write(&self, _text: &str) -> FileResult<()> {
|
||||
// CoreRoFileIo is read-only, write is not supported
|
||||
Err(FileError::Unsupported)
|
||||
Err(FileError::Unsupported("CoreRoFileIo is read-only".to_string()))
|
||||
}
|
||||
|
||||
fn close(&self) -> FileResult<()> {
|
||||
*self.handle.write().unwrap() = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -65,7 +69,7 @@ mod tests {
|
||||
let result = fileio.write("test");
|
||||
assert!(result.is_err());
|
||||
match result.unwrap_err() {
|
||||
FileError::Unsupported => { /* expected */ }
|
||||
FileError::Unsupported(_) => { /* expected */ }
|
||||
_ => panic!("Expected Unsupported error"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,19 +36,23 @@ impl FileIo for NoFsFileIo {
|
||||
}
|
||||
|
||||
fn open(&self, _path: &str) -> FileResult<()> {
|
||||
Err(FileError::Unsupported)
|
||||
Err(FileError::Unsupported("FileBox disabled in NoFs profile".to_string()))
|
||||
}
|
||||
|
||||
fn read(&self) -> FileResult<String> {
|
||||
Err(FileError::Unsupported)
|
||||
Err(FileError::Unsupported("FileBox disabled in NoFs profile".to_string()))
|
||||
}
|
||||
|
||||
fn write(&self, _text: &str) -> FileResult<()> {
|
||||
Err(FileError::Unsupported)
|
||||
Err(FileError::Unsupported("FileBox disabled in NoFs profile".to_string()))
|
||||
}
|
||||
|
||||
fn close(&self) -> FileResult<()> {
|
||||
Err(FileError::Unsupported)
|
||||
Err(FileError::Unsupported("FileBox disabled in NoFs profile".to_string()))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -20,11 +20,13 @@ use std::sync::{Arc, RwLock};
|
||||
/// **Design decisions**:
|
||||
/// - UTF-8 handling: Uses `read_to_string()` which handles UTF-8 internally
|
||||
/// - One file at a time: Calling open() twice without close() returns Err
|
||||
/// - Read-only: Phase 107 focuses on read operations only
|
||||
/// - Phase 111: Supports "r", "w", "a" modes
|
||||
pub struct Ring0FsFileIo {
|
||||
ring0: Arc<Ring0Context>,
|
||||
/// Current opened file path (None if no file is open)
|
||||
path: RwLock<Option<String>>,
|
||||
/// File mode ("r", "w", "a")
|
||||
mode: RwLock<Option<String>>,
|
||||
}
|
||||
|
||||
impl Ring0FsFileIo {
|
||||
@ -33,6 +35,27 @@ impl Ring0FsFileIo {
|
||||
Self {
|
||||
ring0,
|
||||
path: RwLock::new(None),
|
||||
mode: RwLock::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set file mode (internal helper for FileHandleBox)
|
||||
pub fn set_mode(&self, mode: String) {
|
||||
*self.mode.write().unwrap() = Some(mode);
|
||||
}
|
||||
|
||||
/// Get metadata (internal helper for FileHandleBox)
|
||||
pub fn metadata(&self) -> FileResult<crate::runtime::ring0::FsMetadata> {
|
||||
let current_path = self.path.read().unwrap();
|
||||
match current_path.as_ref() {
|
||||
Some(path) => {
|
||||
let path_obj = Path::new(path);
|
||||
self.ring0.fs.metadata(path_obj)
|
||||
.map_err(|e| FileError::Io(format!("Metadata failed: {}", e)))
|
||||
}
|
||||
None => {
|
||||
Err(FileError::Io("No file path set. Call open() first.".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -82,25 +105,57 @@ impl FileIo for Ring0FsFileIo {
|
||||
|
||||
fn write(&self, text: &str) -> FileResult<()> {
|
||||
let current_path = self.path.read().unwrap();
|
||||
let current_mode = self.mode.read().unwrap();
|
||||
|
||||
match current_path.as_ref() {
|
||||
Some(path) => {
|
||||
// Delegate to Ring0.FsApi (truncate mode: overwrite existing file)
|
||||
match (current_path.as_ref(), current_mode.as_ref()) {
|
||||
(Some(path), Some(mode)) => {
|
||||
let path_obj = Path::new(path);
|
||||
self.ring0.fs.write_all(path_obj, text.as_bytes())
|
||||
.map_err(|e| FileError::Io(format!("Write failed: {}", e)))
|
||||
|
||||
// Mode-based write behavior (Phase 111)
|
||||
match mode.as_str() {
|
||||
"w" => {
|
||||
// Truncate mode: overwrite existing file
|
||||
self.ring0.fs.write_all(path_obj, text.as_bytes())
|
||||
.map_err(|e| FileError::Io(format!("Write failed: {}", e)))
|
||||
}
|
||||
"a" => {
|
||||
// Append mode: append to end of file
|
||||
self.ring0.fs.append_all(path_obj, text.as_bytes())
|
||||
.map_err(|e| FileError::Io(format!("Append failed: {}", e)))
|
||||
}
|
||||
"r" => {
|
||||
// Read-only mode: cannot write
|
||||
Err(FileError::Unsupported(
|
||||
"Cannot write in read-only mode".to_string()
|
||||
))
|
||||
}
|
||||
_ => {
|
||||
Err(FileError::Unsupported(
|
||||
format!("Unsupported mode: {}", mode)
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
(None, _) => {
|
||||
Err(FileError::Io("No file is currently open. Call open() first.".to_string()))
|
||||
}
|
||||
(Some(_), None) => {
|
||||
Err(FileError::Io("File mode not set. Internal error.".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn close(&self) -> FileResult<()> {
|
||||
let mut current_path = self.path.write().unwrap();
|
||||
let mut current_mode = self.mode.write().unwrap();
|
||||
*current_path = None;
|
||||
*current_mode = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@ -182,6 +182,20 @@ impl FsApi for StdFs {
|
||||
.map_err(|e| IoError::WriteFailed(format!("write({}): {}", path.display(), e)))
|
||||
}
|
||||
|
||||
fn append_all(&self, path: &Path, data: &[u8]) -> Result<(), IoError> {
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.create(true) // 存在しなければ作成
|
||||
.append(true) // append モードで開く
|
||||
.open(path)
|
||||
.map_err(|e| IoError::WriteFailed(format!("append_all({}): {}", path.display(), e)))?;
|
||||
|
||||
file.write_all(data)
|
||||
.map_err(|e| IoError::WriteFailed(format!("append write({}): {}", path.display(), e)))
|
||||
}
|
||||
|
||||
fn exists(&self, path: &Path) -> bool {
|
||||
path.exists()
|
||||
}
|
||||
|
||||
@ -105,6 +105,12 @@ pub trait FsApi: Send + Sync {
|
||||
/// ファイルに書き込む
|
||||
fn write_all(&self, path: &Path, data: &[u8]) -> Result<(), IoError>;
|
||||
|
||||
/// ファイルに追記(append)
|
||||
///
|
||||
/// ファイルが存在しない場合は新規作成、存在する場合は末尾に追記。
|
||||
/// Phase 111: write_all と対称的に提供。
|
||||
fn append_all(&self, path: &Path, data: &[u8]) -> Result<(), IoError>;
|
||||
|
||||
/// パスが存在するか確認
|
||||
fn exists(&self, path: &Path) -> bool;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user