diff --git a/nyash.toml b/nyash.toml new file mode 100644 index 00000000..9d5b7e5c --- /dev/null +++ b/nyash.toml @@ -0,0 +1,19 @@ +# Nyash Configuration File +# プラグインによるBox実装の置き換え設定 + +[plugins] +# FileBoxをプラグイン版に置き換え +FileBox = "nyash-filebox-plugin" + +# 他のBoxはビルトインを使用(コメントアウト = ビルトイン) +# StringBox = "my-string-plugin" +# IntegerBox = "my-integer-plugin" + +[plugin_paths] +# プラグインの検索パス(デフォルト) +search_paths = [ + "./plugins/*/target/release", + "./plugins/*/target/debug", + "/usr/local/lib/nyash/plugins", + "~/.nyash/plugins" +] \ No newline at end of file diff --git a/plugins/nyash-filebox-plugin/.gitignore b/plugins/nyash-filebox-plugin/.gitignore new file mode 100644 index 00000000..01833ace --- /dev/null +++ b/plugins/nyash-filebox-plugin/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +*.txt \ No newline at end of file diff --git a/plugins/nyash-filebox-plugin/Cargo.toml b/plugins/nyash-filebox-plugin/Cargo.toml index 1e8561d0..33344023 100644 --- a/plugins/nyash-filebox-plugin/Cargo.toml +++ b/plugins/nyash-filebox-plugin/Cargo.toml @@ -3,35 +3,17 @@ name = "nyash-filebox-plugin" version = "0.1.0" edition = "2021" -# 独立プラグインとして設定 -[workspace] - [lib] -name = "nyash_filebox_plugin" -crate-type = ["cdylib"] # 動的ライブラリ生成 +crate-type = ["cdylib"] # 動的ライブラリとしてビルド [dependencies] -# BID-FFI基盤 -libc = "0.2" -once_cell = "1.19" - -# 簡単なError処理 -thiserror = "1.0" - -# ログ(デバッグ用) -log = { version = "0.4", optional = true } +# 最小限の依存関係のみ [features] default = [] -debug = ["log"] +# プロファイル設定 [profile.release] -# プラグイン最適化 -opt-level = 3 lto = true -codegen-units = 1 -panic = "abort" - -[package.metadata] -description = "Nyash FileBox Plugin - BID-FFI Reference Implementation" -license = "MIT" \ No newline at end of file +strip = true +opt-level = "z" # サイズ最適化 \ No newline at end of file diff --git a/plugins/nyash-filebox-plugin/src/lib.rs b/plugins/nyash-filebox-plugin/src/lib.rs index 5802e0ec..a430b514 100644 --- a/plugins/nyash-filebox-plugin/src/lib.rs +++ b/plugins/nyash-filebox-plugin/src/lib.rs @@ -1,233 +1,174 @@ -//! Nyash FileBox Plugin - BID-FFI Reference Implementation +//! Nyash FileBox Plugin - BID-FFI v1 Implementation //! -//! ファイル操作を提供するプラグインの実装例 -//! Everything is Box哲学に基づく透過的置き換え +//! Provides file I/O operations as a Nyash plugin use std::collections::HashMap; -use std::fs::File; -use std::io::{Read, Write, Seek, SeekFrom}; -use std::sync::{Arc, Mutex}; -use std::ffi::{CStr, CString}; -use std::os::raw::{c_char, c_void}; -use once_cell::sync::Lazy; +use std::os::raw::c_char; +use std::ptr; +use std::sync::Mutex; -mod ffi_types; -use ffi_types::*; +// ============ FFI Types ============ -mod filebox; -use filebox::*; - -/// グローバルファイルハンドルレジストリ -static FILE_REGISTRY: Lazy>> = - Lazy::new(|| Arc::new(Mutex::new(FileBoxRegistry::new()))); - -/// プラグインハンドルカウンター -static mut HANDLE_COUNTER: u32 = 1; - -/// ユニークハンドル生成 -fn next_handle() -> u32 { - unsafe { - let handle = HANDLE_COUNTER; - HANDLE_COUNTER += 1; - handle - } +#[repr(C)] +pub struct NyashHostVtable { + pub alloc: unsafe extern "C" fn(size: usize) -> *mut u8, + pub free: unsafe extern "C" fn(ptr: *mut u8), + pub wake: unsafe extern "C" fn(handle: u64), + pub log: unsafe extern "C" fn(level: i32, msg: *const c_char), } -/// BID-FFIエントリーポイント -/// -/// すべてのプラグイン操作はこの関数を通して実行される +#[repr(C)] +pub struct NyashMethodInfo { + pub method_id: u32, + pub name: *const c_char, + pub signature: u32, +} + +#[repr(C)] +pub struct NyashPluginInfo { + pub type_id: u32, + pub type_name: *const c_char, + pub method_count: usize, + pub methods: *const NyashMethodInfo, +} + +// ============ Error Codes ============ +const NYB_SUCCESS: i32 = 0; +const NYB_E_INVALID_ARGS: i32 = -1; +const NYB_E_INVALID_TYPE: i32 = -2; +const NYB_E_INVALID_METHOD: i32 = -3; +const NYB_E_INVALID_HANDLE: i32 = -4; +const NYB_E_PLUGIN_ERROR: i32 = -5; + +// ============ Method IDs ============ +const METHOD_BIRTH: u32 = 0; // Constructor +const METHOD_OPEN: u32 = 1; +const METHOD_READ: u32 = 2; +const METHOD_WRITE: u32 = 3; +const METHOD_CLOSE: u32 = 4; +const METHOD_FINI: u32 = u32::MAX; // Destructor + +// ============ FileBox Instance ============ +struct FileBoxInstance { + file: Option, + path: String, + buffer: Option>, // プラグインが管理するバッファ +} + +// グローバルインスタンス管理(実際の実装ではより安全な方法を使用) +static mut INSTANCES: Option>> = None; + +// ホスト関数テーブル(初期化時に設定) +static mut HOST_VTABLE: Option<&'static NyashHostVtable> = None; + +// ============ Plugin Entry Points ============ + +/// ABI version +#[no_mangle] +pub extern "C" fn nyash_plugin_abi() -> u32 { + 1 // BID-1 support +} + +/// Plugin initialization +#[no_mangle] +pub extern "C" fn nyash_plugin_init( + host: *const NyashHostVtable, + info: *mut NyashPluginInfo, +) -> i32 { + if host.is_null() || info.is_null() { + return NYB_E_INVALID_ARGS; + } + + unsafe { + HOST_VTABLE = Some(&*host); + + // インスタンス管理初期化 + INSTANCES = Some(Mutex::new(HashMap::new())); + + // Method table + static TYPE_NAME: &[u8] = b"FileBox\0"; + + (*info).type_id = 6; // FileBox type ID + (*info).type_name = TYPE_NAME.as_ptr() as *const c_char; + + // メソッドテーブルは動的に作成(Syncトレイト問題回避) + static METHOD_STORAGE: &'static [[u8; 7]] = &[ + *b"birth\0\0", + *b"open\0\0\0", + *b"read\0\0\0", + *b"write\0\0", + *b"close\0\0", + *b"fini\0\0\0", + ]; + + static mut METHODS: [NyashMethodInfo; 6] = [ + NyashMethodInfo { method_id: 0, name: ptr::null(), signature: 0 }, + NyashMethodInfo { method_id: 0, name: ptr::null(), signature: 0 }, + NyashMethodInfo { method_id: 0, name: ptr::null(), signature: 0 }, + NyashMethodInfo { method_id: 0, name: ptr::null(), signature: 0 }, + NyashMethodInfo { method_id: 0, name: ptr::null(), signature: 0 }, + NyashMethodInfo { method_id: 0, name: ptr::null(), signature: 0 }, + ]; + + // 初回のみメソッドテーブルを初期化 + if METHODS[0].name.is_null() { + METHODS[0] = NyashMethodInfo { + method_id: METHOD_BIRTH, + name: METHOD_STORAGE[0].as_ptr() as *const c_char, + signature: 0xBEEFCAFE, + }; + METHODS[1] = NyashMethodInfo { + method_id: METHOD_OPEN, + name: METHOD_STORAGE[1].as_ptr() as *const c_char, + signature: 0x12345678, + }; + METHODS[2] = NyashMethodInfo { + method_id: METHOD_READ, + name: METHOD_STORAGE[2].as_ptr() as *const c_char, + signature: 0x87654321, + }; + METHODS[3] = NyashMethodInfo { + method_id: METHOD_WRITE, + name: METHOD_STORAGE[3].as_ptr() as *const c_char, + signature: 0x11223344, + }; + METHODS[4] = NyashMethodInfo { + method_id: METHOD_CLOSE, + name: METHOD_STORAGE[4].as_ptr() as *const c_char, + signature: 0xABCDEF00, + }; + METHODS[5] = NyashMethodInfo { + method_id: METHOD_FINI, + name: METHOD_STORAGE[5].as_ptr() as *const c_char, + signature: 0xDEADBEEF, + }; + } + + (*info).method_count = METHODS.len(); + (*info).methods = METHODS.as_ptr(); + } + + NYB_SUCCESS +} + +/// Method invocation - 仮実装 #[no_mangle] pub extern "C" fn nyash_plugin_invoke( - method: *const c_char, - handle: u64, - input_data: *const u8, - input_len: usize, - output_data: *mut *mut u8, - output_len: *mut usize, + _type_id: u32, + _method_id: u32, + _instance_id: u32, + _args: *const u8, + _args_len: usize, + _result: *mut u8, + _result_len: *mut usize, ) -> i32 { - // 基本的な引数チェック - if method.is_null() || output_data.is_null() || output_len.is_null() { - return -1; // INVALID_ARGUMENT - } - - // メソッド名解析 - let method_name = unsafe { - match CStr::from_ptr(method).to_str() { - Ok(s) => s, - Err(_) => return -2, // ENCODING_ERROR - } - }; - - // ハンドル分解 (BID-1仕様) - let type_id = (handle >> 32) as u32; - let instance_id = (handle & 0xFFFFFFFF) as u32; - - // メソッド呼び出し - match invoke_method(method_name, type_id, instance_id, input_data, input_len) { - Ok(result) => { - // 結果をC側に返す - if let Ok(c_result) = CString::new(result) { - let result_bytes = c_result.into_bytes_with_nul(); - let len = result_bytes.len(); - - // メモリ確保(C側でfreeする必要がある) - let ptr = unsafe { libc::malloc(len) as *mut u8 }; - if ptr.is_null() { - return -3; // OUT_OF_MEMORY - } - - unsafe { - std::ptr::copy_nonoverlapping(result_bytes.as_ptr(), ptr, len); - *output_data = ptr; - *output_len = len; - } - 0 // SUCCESS - } else { - -2 // ENCODING_ERROR - } - } - Err(code) => code, - } + NYB_SUCCESS } -/// メソッド呼び出し実装 -fn invoke_method( - method: &str, - type_id: u32, - instance_id: u32, - input_data: *const u8, - input_len: usize, -) -> Result { - match method { - "new" => handle_new(input_data, input_len), - "open" => handle_open(instance_id, input_data, input_len), - "read" => handle_read(instance_id, input_data, input_len), - "write" => handle_write(instance_id, input_data, input_len), - "close" => handle_close(instance_id), - "toString" => handle_to_string(instance_id), - _ => Err(-4), // UNKNOWN_METHOD - } -} - -/// FileBox::new() - 新しいFileBoxインスタンス作成 -fn handle_new(input_data: *const u8, input_len: usize) -> Result { - let handle = next_handle(); - let filebox = FileBoxInstance::new(); - - { - let mut registry = FILE_REGISTRY.lock().unwrap(); - registry.register(handle, filebox); - } - - // BID-1ハンドル返却 (FileBox type_id = 8) - let bid_handle = ((8u64) << 32) | (handle as u64); - Ok(bid_handle.to_string()) -} - -/// FileBox::open(path) - ファイルオープン -fn handle_open(instance_id: u32, input_data: *const u8, input_len: usize) -> Result { - // TLV解析(簡易版) - let path = parse_string_from_tlv(input_data, input_len)?; - - let mut registry = FILE_REGISTRY.lock().unwrap(); - match registry.get_mut(instance_id) { - Some(filebox) => { - match filebox.open(&path) { - Ok(()) => Ok("true".to_string()), - Err(_) => Ok("false".to_string()), - } - } - None => Err(-5), // INVALID_HANDLE - } -} - -/// FileBox::read() - ファイル読み取り -fn handle_read(instance_id: u32, _input_data: *const u8, _input_len: usize) -> Result { - let mut registry = FILE_REGISTRY.lock().unwrap(); - match registry.get_mut(instance_id) { - Some(filebox) => { - match filebox.read() { - Ok(content) => Ok(content), - Err(_) => Ok("".to_string()), - } - } - None => Err(-5), // INVALID_HANDLE - } -} - -/// FileBox::write(data) - ファイル書き込み -fn handle_write(instance_id: u32, input_data: *const u8, input_len: usize) -> Result { - let data = parse_string_from_tlv(input_data, input_len)?; - - let mut registry = FILE_REGISTRY.lock().unwrap(); - match registry.get_mut(instance_id) { - Some(filebox) => { - match filebox.write(&data) { - Ok(()) => Ok("true".to_string()), - Err(_) => Ok("false".to_string()), - } - } - None => Err(-5), // INVALID_HANDLE - } -} - -/// FileBox::close() - ファイルクローズ -fn handle_close(instance_id: u32) -> Result { - let mut registry = FILE_REGISTRY.lock().unwrap(); - match registry.remove(instance_id) { - Some(_) => Ok("true".to_string()), - None => Err(-5), // INVALID_HANDLE - } -} - -/// FileBox::toString() - 文字列表現 -fn handle_to_string(instance_id: u32) -> Result { - let registry = FILE_REGISTRY.lock().unwrap(); - match registry.get(instance_id) { - Some(filebox) => Ok(format!("FileBox({})", filebox.path().unwrap_or("none"))), - None => Err(-5), // INVALID_HANDLE - } -} - -/// 簡易TLV解析(文字列のみ) -fn parse_string_from_tlv(data: *const u8, len: usize) -> Result { - if data.is_null() || len == 0 { - return Ok(String::new()); - } - - // 簡易実装:UTF-8文字列として直接解析 - let slice = unsafe { std::slice::from_raw_parts(data, len) }; - match std::str::from_utf8(slice) { - Ok(s) => Ok(s.to_string()), - Err(_) => Err(-2), // ENCODING_ERROR - } -} - -/// プラグイン情報(メタデータAPI) +/// Plugin shutdown #[no_mangle] -pub extern "C" fn nyash_plugin_info() -> *const c_char { - // プラグイン情報のJSON - let info = r#"{ - "name": "nyash-filebox-plugin", - "version": "0.1.0", - "provides": ["FileBox"], - "abi_version": "bid-1.0" - }"#; - - // リークさせて永続化(プラグインライフタイム中有効) - Box::leak(CString::new(info).unwrap().into_boxed_c_str()).as_ptr() -} - -/// プラグイン初期化 -#[no_mangle] -pub extern "C" fn nyash_plugin_init() -> i32 { - // 初期化処理(必要に応じて) - 0 // SUCCESS -} - -/// プラグイン終了処理 -#[no_mangle] -pub extern "C" fn nyash_plugin_shutdown() -> i32 { - // クリーンアップ処理 - 0 // SUCCESS +pub extern "C" fn nyash_plugin_shutdown() { + unsafe { + INSTANCES = None; + } } \ No newline at end of file diff --git a/tools/plugin-tester/.gitignore b/tools/plugin-tester/.gitignore new file mode 100644 index 00000000..869df07d --- /dev/null +++ b/tools/plugin-tester/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock \ No newline at end of file diff --git a/tools/plugin-tester/Cargo.toml b/tools/plugin-tester/Cargo.toml new file mode 100644 index 00000000..0c476938 --- /dev/null +++ b/tools/plugin-tester/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "plugin-tester" +version = "0.1.0" +edition = "2021" + +[dependencies] +libloading = "0.8" +clap = { version = "4", features = ["derive"] } +colored = "2" \ No newline at end of file diff --git a/tools/plugin-tester/src/main.rs b/tools/plugin-tester/src/main.rs new file mode 100644 index 00000000..4e1305b6 --- /dev/null +++ b/tools/plugin-tester/src/main.rs @@ -0,0 +1,223 @@ +//! Nyash Plugin Tester +//! +//! プラグイン開発者向けの診断ツール +//! Box名を決め打ちせず、プラグインから取得する + +use clap::Parser; +use colored::*; +use libloading::{Library, Symbol}; +use std::ffi::{CStr, CString}; +use std::os::raw::{c_char, c_void}; +use std::path::PathBuf; + +// ============ FFI Types (プラグインと同じ定義) ============ + +#[repr(C)] +pub struct NyashHostVtable { + pub alloc: unsafe extern "C" fn(size: usize) -> *mut u8, + pub free: unsafe extern "C" fn(ptr: *mut u8), + pub wake: unsafe extern "C" fn(handle: u64), + pub log: unsafe extern "C" fn(level: i32, msg: *const c_char), +} + +#[repr(C)] +pub struct NyashMethodInfo { + pub method_id: u32, + pub name: *const c_char, + pub signature: u32, +} + +#[repr(C)] +pub struct NyashPluginInfo { + pub type_id: u32, + pub type_name: *const c_char, + pub method_count: usize, + pub methods: *const NyashMethodInfo, +} + +// ============ CLI Arguments ============ + +#[derive(Parser, Debug)] +#[command(name = "plugin-tester")] +#[command(about = "Nyash plugin diagnostic tool", long_about = None)] +struct Args { + /// Action to perform + #[command(subcommand)] + command: Commands, +} + +#[derive(clap::Subcommand, Debug)] +enum Commands { + /// Check plugin and display information + Check { + /// Path to plugin .so file + plugin: PathBuf, + }, + /// Test plugin lifecycle (birth/fini) + Lifecycle { + /// Path to plugin .so file + plugin: PathBuf, + }, +} + +// ============ Host Functions (テスト用実装) ============ + +unsafe extern "C" fn test_alloc(size: usize) -> *mut u8 { + let layout = std::alloc::Layout::from_size_align(size, 8).unwrap(); + std::alloc::alloc(layout) +} + +unsafe extern "C" fn test_free(ptr: *mut u8) { + if !ptr.is_null() { + // サイズ情報が必要だが、簡易実装のため省略 + } +} + +unsafe extern "C" fn test_wake(_handle: u64) { + // テスト用なので何もしない +} + +unsafe extern "C" fn test_log(level: i32, msg: *const c_char) { + if !msg.is_null() { + let c_str = CStr::from_ptr(msg); + let message = c_str.to_string_lossy(); + + match level { + 0 => println!("{}: {}", "DEBUG".blue(), message), + 1 => println!("{}: {}", "INFO".green(), message), + 2 => println!("{}: {}", "WARN".yellow(), message), + 3 => println!("{}: {}", "ERROR".red(), message), + _ => println!("{}: {}", "UNKNOWN".white(), message), + } + } +} + +static HOST_VTABLE: NyashHostVtable = NyashHostVtable { + alloc: test_alloc, + free: test_free, + wake: test_wake, + log: test_log, +}; + +// ============ Main Functions ============ + +fn main() { + let args = Args::parse(); + + match args.command { + Commands::Check { plugin } => check_plugin(&plugin), + Commands::Lifecycle { plugin } => test_lifecycle(&plugin), + } +} + +fn check_plugin(path: &PathBuf) { + println!("{}", "=== Nyash Plugin Checker ===".bold()); + println!("Plugin: {}", path.display()); + + // プラグインをロード + let library = match unsafe { Library::new(path) } { + Ok(lib) => lib, + Err(e) => { + eprintln!("{}: Failed to load plugin: {}", "ERROR".red(), e); + return; + } + }; + + println!("{}: Plugin loaded successfully", "✓".green()); + + // ABI version確認 + unsafe { + let abi_fn: Symbol u32> = match library.get(b"nyash_plugin_abi") { + Ok(f) => f, + Err(e) => { + eprintln!("{}: nyash_plugin_abi not found: {}", "ERROR".red(), e); + return; + } + }; + + let abi_version = abi_fn(); + println!("{}: ABI version: {}", "✓".green(), abi_version); + + if abi_version != 1 { + eprintln!("{}: Unsupported ABI version (expected 1)", "WARNING".yellow()); + } + } + + // Plugin初期化とBox名取得 + unsafe { + let init_fn: Symbol i32> = + match library.get(b"nyash_plugin_init") { + Ok(f) => f, + Err(e) => { + eprintln!("{}: nyash_plugin_init not found: {}", "ERROR".red(), e); + return; + } + }; + + let mut plugin_info = std::mem::zeroed::(); + let result = init_fn(&HOST_VTABLE, &mut plugin_info); + + if result != 0 { + eprintln!("{}: nyash_plugin_init failed with code {}", "ERROR".red(), result); + return; + } + + println!("{}: Plugin initialized", "✓".green()); + + // 重要:Box名をプラグインから取得(決め打ちしない!) + let box_name = if plugin_info.type_name.is_null() { + "".to_string() + } else { + CStr::from_ptr(plugin_info.type_name).to_string_lossy().to_string() + }; + + println!("\n{}", "Plugin Information:".bold()); + println!(" Box Type: {} (ID: {})", box_name.cyan(), plugin_info.type_id); + println!(" Methods: {}", plugin_info.method_count); + + // メソッド一覧表示 + if plugin_info.method_count > 0 && !plugin_info.methods.is_null() { + println!("\n{}", "Methods:".bold()); + let methods = std::slice::from_raw_parts(plugin_info.methods, plugin_info.method_count); + + for method in methods { + let method_name = if method.name.is_null() { + "".to_string() + } else { + CStr::from_ptr(method.name).to_string_lossy().to_string() + }; + + let method_type = match method.method_id { + 0 => " (constructor)".yellow(), + id if id == u32::MAX => " (destructor)".yellow(), + _ => "".normal(), + }; + + println!(" - {} [ID: {}, Sig: 0x{:08X}]{}", + method_name, + method.method_id, + method.signature, + method_type + ); + } + } + } + + // シャットダウン + unsafe { + if let Ok(shutdown_fn) = library.get::>(b"nyash_plugin_shutdown") { + shutdown_fn(); + println!("\n{}: Plugin shutdown completed", "✓".green()); + } + } + + println!("\n{}", "Check completed!".green().bold()); +} + +fn test_lifecycle(path: &PathBuf) { + println!("{}", "=== Lifecycle Test ===".bold()); + println!("Testing birth/fini for: {}", path.display()); + + // TODO: birth/finiのテスト実装 + println!("{}: Lifecycle test not yet implemented", "TODO".yellow()); +} \ No newline at end of file