From ae766b4e9a23a14bb89255c0a87e3cf75dea9a9a Mon Sep 17 00:00:00 2001 From: dr-frmr Date: Mon, 16 Oct 2023 15:56:26 -0400 Subject: [PATCH] remote installs? --- build.rs | 2 +- .../Cargo-component.lock | 0 modules/{app_tracker => app_store}/Cargo.lock | 2 +- modules/{app_tracker => app_store}/Cargo.toml | 2 +- .../pkg/manifest.json | 4 +- modules/app_store/pkg/metadata.json | 5 + .../src/kernel_types.rs | 0 modules/app_store/src/lib.rs | 489 ++++++++++++++++++ .../src/process_lib.rs | 0 .../src/transfer_lib.rs | 0 modules/app_tracker/pkg/metadata.json | 5 - modules/app_tracker/plan.md | 15 - modules/app_tracker/src/lib.rs | 336 ------------ modules/rpc/pkg/manifest.json | 2 +- src/kernel/mod.rs | 4 +- src/process_lib.rs | 65 +++ 16 files changed, 567 insertions(+), 364 deletions(-) rename modules/{app_tracker => app_store}/Cargo-component.lock (100%) rename modules/{app_tracker => app_store}/Cargo.lock (99%) rename modules/{app_tracker => app_store}/Cargo.toml (96%) rename modules/{app_tracker => app_store}/pkg/manifest.json (84%) create mode 100644 modules/app_store/pkg/metadata.json rename modules/{app_tracker => app_store}/src/kernel_types.rs (100%) create mode 100644 modules/app_store/src/lib.rs rename modules/{app_tracker => app_store}/src/process_lib.rs (100%) rename modules/{app_tracker => app_store}/src/transfer_lib.rs (100%) delete mode 100644 modules/app_tracker/pkg/metadata.json delete mode 100644 modules/app_tracker/plan.md delete mode 100644 modules/app_tracker/src/lib.rs diff --git a/build.rs b/build.rs index 5e92be2d..53364bd2 100644 --- a/build.rs +++ b/build.rs @@ -126,7 +126,7 @@ fn main() { } // only execute if one of the modules has source code changes const WASI_APPS: [&str; 9] = [ - "app_tracker", + "app_store", "chess", "homepage", "http_bindings", diff --git a/modules/app_tracker/Cargo-component.lock b/modules/app_store/Cargo-component.lock similarity index 100% rename from modules/app_tracker/Cargo-component.lock rename to modules/app_store/Cargo-component.lock diff --git a/modules/app_tracker/Cargo.lock b/modules/app_store/Cargo.lock similarity index 99% rename from modules/app_tracker/Cargo.lock rename to modules/app_store/Cargo.lock index a736fd13..ac59c0b8 100644 --- a/modules/app_tracker/Cargo.lock +++ b/modules/app_store/Cargo.lock @@ -9,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] -name = "app_tracker" +name = "app_store" version = "0.1.0" dependencies = [ "anyhow", diff --git a/modules/app_tracker/Cargo.toml b/modules/app_store/Cargo.toml similarity index 96% rename from modules/app_tracker/Cargo.toml rename to modules/app_store/Cargo.toml index f1e20708..6c86c41a 100644 --- a/modules/app_tracker/Cargo.toml +++ b/modules/app_store/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "app_tracker" +name = "app_store" version = "0.1.0" edition = "2021" diff --git a/modules/app_tracker/pkg/manifest.json b/modules/app_store/pkg/manifest.json similarity index 84% rename from modules/app_tracker/pkg/manifest.json rename to modules/app_store/pkg/manifest.json index be602d33..1c9ae034 100644 --- a/modules/app_tracker/pkg/manifest.json +++ b/modules/app_store/pkg/manifest.json @@ -1,7 +1,7 @@ [ { - "process_name": "app_tracker", - "process_wasm_path": "/app_tracker.wasm", + "process_name": "main", + "process_wasm_path": "/app_store.wasm", "on_panic": "Restart", "request_networking": true, "request_messaging": [ diff --git a/modules/app_store/pkg/metadata.json b/modules/app_store/pkg/metadata.json new file mode 100644 index 00000000..cbb01c6e --- /dev/null +++ b/modules/app_store/pkg/metadata.json @@ -0,0 +1,5 @@ +{ + "package": "app_store", + "publisher": "uqbar", + "description": "A package manager + app store. This JSON field is optional and you can add whatever you want in addition to this." +} diff --git a/modules/app_tracker/src/kernel_types.rs b/modules/app_store/src/kernel_types.rs similarity index 100% rename from modules/app_tracker/src/kernel_types.rs rename to modules/app_store/src/kernel_types.rs diff --git a/modules/app_store/src/lib.rs b/modules/app_store/src/lib.rs new file mode 100644 index 00000000..f5ba791f --- /dev/null +++ b/modules/app_store/src/lib.rs @@ -0,0 +1,489 @@ +cargo_component_bindings::generate!(); + +use bindings::{ + component::uq_process::types::*, get_capability, get_payload, print_to_terminal, receive, + send_request, send_response, Guest, +}; +use serde::{Deserialize, Serialize}; +use std::collections::{HashMap, HashSet}; + +#[allow(dead_code)] +mod kernel_types; +use kernel_types as kt; + +#[allow(dead_code)] +mod process_lib; +use process_lib::PackageId; + +mod transfer_lib; + +struct Component; + +// #[derive(Serialize, Deserialize)] +// struct AppState { +// // TODO this should mirror onchain listing +// pub name: String, +// pub owner: NodeId, +// pub desc: String, +// pub website: Option, +// pub versions: Vec<(u32, String)>, // TODO +// } + +#[derive(Serialize, Deserialize)] +struct AppTrackerState { + pub mirrored_packages: HashSet, + pub requested_packages: HashMap, // who we're expecting it from +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum AppTrackerRequest { + New { + package: String, + mirror: bool, + }, + NewFromRemote { + package_id: PackageId, + install_from: NodeId, + }, + Install { + package: String, + }, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum AppTrackerResponse { + New { package: String }, + NewFromRemote { package_id: PackageId }, + Install { package: String }, + Error { error: String }, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PackageMetadata { + pub package: String, + pub publisher: String, + pub desc: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PackageManifestEntry { + pub process_name: String, + pub process_wasm_path: String, + pub on_panic: kt::OnPanic, + pub request_networking: bool, + pub request_messaging: Vec, + pub public: bool, +} + +fn parse_command( + our: &Address, + source: &Address, + request_string: String, + state: &mut AppTrackerState, +) -> anyhow::Result> { + match serde_json::from_str(&request_string)? { + // create a new package based on local payload + AppTrackerRequest::New { package, mirror } => { + if our.node != source.node { + return Err(anyhow::anyhow!("new package request from non-local node")); + } + let Some(payload) = get_payload() else { + return Err(anyhow::anyhow!("no payload")); + }; + + let vfs_address = Address { + node: our.node.clone(), + process: ProcessId::from_str("vfs:sys:uqbar")?, + }; + + let _ = process_lib::send_and_await_response( + &vfs_address, + false, + Some(serde_json::to_string(&kt::VfsRequest { + drive: package.clone(), + action: kt::VfsAction::New, + })?), + None, + None, + 5, + )?; + + // add zip bytes + let _ = process_lib::send_and_await_response( + &vfs_address, + true, + Some(serde_json::to_string(&kt::VfsRequest { + drive: package.clone(), + action: kt::VfsAction::Add { + full_path: package.clone().into(), + entry_type: kt::AddEntryType::ZipArchive, + }, + })?), + None, + Some(&payload), + 5, + )?; + + // save the zip file itself in VFS for sharing with other nodes + // call it .zip + let _ = process_lib::send_and_await_response( + &vfs_address, + true, + Some(serde_json::to_string(&kt::VfsRequest { + drive: package.clone(), + action: kt::VfsAction::Add { + full_path: format!("/{}.zip", package), + entry_type: kt::AddEntryType::NewFile, + }, + })?), + None, + Some(&payload), + 5, + )?; + + // if mirror, save in our state + if mirror { + let _ = process_lib::send_and_await_response( + &vfs_address, + false, + Some(serde_json::to_string(&kt::VfsRequest { + drive: package.clone(), + action: kt::VfsAction::GetEntry("/metadata.json".into()), + })?), + None, + None, + 5, + )?; + let Some(payload) = get_payload() else { + return Err(anyhow::anyhow!("no metadata payload")); + }; + let metadata = String::from_utf8(payload.bytes)?; + let metadata = serde_json::from_str::(&metadata)?; + state + .mirrored_packages + .insert(PackageId::new(&metadata.package, &metadata.publisher)); + process_lib::set_state::(&state); + } + + Ok(Some(AppTrackerResponse::New { package })) + } + // if we are the source, forward to install_from target. + // if we install_from, respond with package if we have it + AppTrackerRequest::NewFromRemote { + package_id, + install_from, + } => { + if our.node == source.node { + let _ = send_request( + &Address { + node: install_from.clone(), + process: our.process.clone(), + }, + &Request { + inherit: true, + expects_response: Some(5), // TODO + ipc: Some(serde_json::to_string(&AppTrackerRequest::NewFromRemote { + package_id, + install_from, + })?), + metadata: None, + }, + None, + None, + ); + Ok(None) + } else if our.node == install_from { + let Some(_mirror) = state.mirrored_packages.get(&package_id) else { + return Ok(Some(AppTrackerResponse::Error { error: "package not mirrored here!".into() })) + }; + // get the .zip from VFS and attach as payload to response + let vfs_address = Address { + node: our.node.clone(), + process: ProcessId::from_str("vfs:sys:uqbar")?, + }; + let _ = process_lib::send_and_await_response( + &vfs_address, + false, + Some(serde_json::to_string(&kt::VfsRequest { + drive: package_id.to_string(), + action: kt::VfsAction::GetEntry(format!("/{}.zip", package_id.to_string())), + })?), + None, + None, + 5, + )?; + Ok(Some(AppTrackerResponse::NewFromRemote { package_id })) + } else { + // TODO what to do here? + Ok(None) + } + } + AppTrackerRequest::Install { package } => { + if our.node != source.node { + return Err(anyhow::anyhow!("install request from non-local node")); + } + let vfs_address = Address { + node: our.node.clone(), + process: ProcessId::from_str("vfs:sys:uqbar")?, + }; + + let _ = process_lib::send_and_await_response( + &vfs_address, + false, + Some(serde_json::to_string(&kt::VfsRequest { + drive: package.clone(), + action: kt::VfsAction::GetEntry("/manifest.json".into()), + })?), + None, + None, + 5, + )?; + let Some(payload) = get_payload() else { + return Err(anyhow::anyhow!("no payload")); + }; + let manifest = String::from_utf8(payload.bytes)?; + let manifest = serde_json::from_str::>(&manifest)?; + + for entry in manifest { + let path = if entry.process_wasm_path.starts_with("/") { + entry.process_wasm_path + } else { + format!("/{}", entry.process_wasm_path) + }; + + let (_, hash_response) = process_lib::send_and_await_response( + &vfs_address, + false, + Some(serde_json::to_string(&kt::VfsRequest { + drive: package.clone(), + action: kt::VfsAction::GetHash(path.clone()), + })?), + None, + None, + 5, + )?; + + let Message::Response((Response { ipc: Some(ipc), .. }, _)) = hash_response else { + return Err(anyhow::anyhow!("bad vfs response")); + }; + let kt::VfsResponse::GetHash(Some(hash)) = serde_json::from_str(&ipc)? else { + return Err(anyhow::anyhow!("no hash in vfs")); + }; + + // build initial caps + let mut initial_capabilities: HashSet = HashSet::new(); + if entry.request_networking { + let Some(networking_cap) = get_capability( + &Address { + node: our.node.clone(), + process: ProcessId::from_str("kernel:sys:uqbar")?, + }, + &"\"network\"".to_string(), + ) else { + return Err(anyhow::anyhow!("app-store: no net cap")); + }; + initial_capabilities.insert(kt::de_wit_signed_capability(networking_cap)); + } + let Some(read_cap) = get_capability( + &vfs_address.clone(), + &serde_json::to_string(&serde_json::json!({ + "kind": "read", + "drive": package, + }))?, + ) else { + return Err(anyhow::anyhow!("app-store: no read cap")); + }; + initial_capabilities.insert(kt::de_wit_signed_capability(read_cap)); + let Some(write_cap) = get_capability( + &vfs_address.clone(), + &serde_json::to_string(&serde_json::json!({ + "kind": "write", + "drive": package, + }))?, + ) else { + return Err(anyhow::anyhow!("app-store: no write cap")); + }; + initial_capabilities.insert(kt::de_wit_signed_capability(write_cap)); + + for process_name in &entry.request_messaging { + let Ok(parsed_process_id) = ProcessId::from_str(&process_name) else { + // TODO handle arbitrary caps here + continue; + }; + let Some(messaging_cap) = get_capability( + &Address { + node: our.node.clone(), + process: parsed_process_id.clone(), + }, + &"\"messaging\"".into() + ) else { + print_to_terminal(0, &format!("app-store: no cap for {} to give away!", process_name)); + continue; + }; + initial_capabilities.insert(kt::de_wit_signed_capability(messaging_cap)); + } + + let process_id = format!("{}:{}", entry.process_name, package.clone()); + let Ok(parsed_new_process_id) = ProcessId::from_str(&process_id) else { + return Err(anyhow::anyhow!("app-store: invalid process id!")); + }; + let _ = process_lib::send_request( + &Address { + node: our.node.clone(), + process: ProcessId::from_str("kernel:sys:uqbar")?, + }, + false, + Some(serde_json::to_string(&kt::KernelCommand::KillProcess( + kt::ProcessId::de_wit(parsed_new_process_id.clone()), + ))?), + None, + None, + None, + ); + + // kernel start process takes bytes as payload + wasm_bytes_handle... + // reconsider perhaps + let (_, _bytes_response) = process_lib::send_and_await_response( + &vfs_address, + false, + Some(serde_json::to_string(&kt::VfsRequest { + drive: package.clone(), + action: kt::VfsAction::GetEntry(path), + })?), + None, + None, + 5, + )?; + + let Some(payload) = get_payload() else { + return Err(anyhow::anyhow!("no wasm bytes payload.")); + }; + + let _ = process_lib::send_and_await_response( + &Address { + node: our.node.clone(), + process: ProcessId::from_str("kernel:sys:uqbar")?, + }, + false, + Some(serde_json::to_string(&kt::KernelCommand::StartProcess { + id: kt::ProcessId::de_wit(parsed_new_process_id), + wasm_bytes_handle: hash, + on_panic: entry.on_panic, + initial_capabilities, + public: entry.public, + })?), + None, + Some(&payload), + 5, + )?; + } + Ok(Some(AppTrackerResponse::Install { package })) + } + } +} + +impl Guest for Component { + fn init(our: Address) { + assert_eq!(our.process.to_string(), "main:app_store:uqbar"); + print_to_terminal(0, &format!("app_store main proc: start")); + + let mut state = process_lib::get_state::().unwrap_or(AppTrackerState { + mirrored_packages: HashSet::new(), + requested_packages: HashMap::new(), + }); + + loop { + let (source, message) = match receive() { + Ok((source, message)) => (source, message), + Err((error, _context)) => { + print_to_terminal(0, &format!("net error: {:?}", error.kind)); + continue; + } + }; + match message { + Message::Request(Request { + ipc, + expects_response, + metadata, + .. + }) => { + let Some(command) = ipc else { + continue; + }; + match parse_command(&our, &source, command, &mut state) { + Ok(response) => { + if let Some(_) = expects_response { + let _ = send_response( + &Response { + ipc: Some(serde_json::to_string(&response).unwrap()), + metadata, + }, + None, // payload will be attached here if created in parse_command + ); + }; + } + Err(e) => { + print_to_terminal(0, &format!("app-store: got error {}", e)); + if let Some(_) = expects_response { + let error = AppTrackerResponse::Error { + error: format!("{}", e), + }; + let _ = send_response( + &Response { + ipc: Some(serde_json::to_string(&error).unwrap()), + metadata, + }, + None, + ); + }; + } + } + } + Message::Response((response, _)) => { + // only expecting NewFromRemote for apps we've requested + match serde_json::from_str(&response.ipc.unwrap_or_default()) { + Ok(AppTrackerResponse::NewFromRemote { package_id }) => { + if let Some(install_from) = state.requested_packages.remove(&package_id) + { + if install_from == source.node { + // auto-take zip from payload and request ourself with New + let _ = send_request( + &our, + &Request { + inherit: true, // will inherit payload! + expects_response: None, + ipc: Some( + serde_json::to_string(&AppTrackerRequest::New { + package: package_id.package().into(), + mirror: true, + }) + .unwrap(), + ), + metadata: None, + }, + None, + None, + ); + } else { + print_to_terminal( + 0, + &format!( + "app-store: got install response from bad source: {}", + install_from + ), + ); + } + } + } + err => { + print_to_terminal( + 0, + &format!("app-store: got unexpected response {:?}", err), + ); + } + } + } + } + } + } +} diff --git a/modules/app_tracker/src/process_lib.rs b/modules/app_store/src/process_lib.rs similarity index 100% rename from modules/app_tracker/src/process_lib.rs rename to modules/app_store/src/process_lib.rs diff --git a/modules/app_tracker/src/transfer_lib.rs b/modules/app_store/src/transfer_lib.rs similarity index 100% rename from modules/app_tracker/src/transfer_lib.rs rename to modules/app_store/src/transfer_lib.rs diff --git a/modules/app_tracker/pkg/metadata.json b/modules/app_tracker/pkg/metadata.json deleted file mode 100644 index e080e27f..00000000 --- a/modules/app_tracker/pkg/metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "package": "app_tracker", - "publisher": "uqbar", - "desc": "A package manager + app store. This JSON field is optional and you can add whatever you want in addition to this." -} diff --git a/modules/app_tracker/plan.md b/modules/app_tracker/plan.md deleted file mode 100644 index 2f841c04..00000000 --- a/modules/app_tracker/plan.md +++ /dev/null @@ -1,15 +0,0 @@ -### App Tracker: our built-in package manager that lives in userspace - -*note: 'app' and 'package' will be used interchangeably, but they are the same thing. generally, end users should see 'apps', and developers and the system itself should see 'packages'* - -### Backend - -Tracker requires full read-write to filesystem, along with caps for every other distro app and runtime module. It takes all the caps because it needs the ability to grant them to packages we install! - -In order to load in the currently installed packages, Tracker will access the VFS and read from a hardcoded set of - -### Frontend - -Tracker will present a frontend that shows all the apps you currently have installed. You can see some metadata about them, and uninstall them from this list. - -Tracker will contain a "store" to browse for new apps to install. TODO \ No newline at end of file diff --git a/modules/app_tracker/src/lib.rs b/modules/app_tracker/src/lib.rs deleted file mode 100644 index 034e7956..00000000 --- a/modules/app_tracker/src/lib.rs +++ /dev/null @@ -1,336 +0,0 @@ -cargo_component_bindings::generate!(); - -use bindings::{ - component::uq_process::types::*, get_capability, get_payload, print_to_terminal, receive, - send_request, send_response, Guest, -}; -use serde::{Deserialize, Serialize}; -use std::collections::HashSet; - -#[allow(dead_code)] -mod kernel_types; -use kernel_types as kt; - -#[allow(dead_code)] -mod process_lib; -mod transfer_lib; - -struct Component; - -#[derive(Debug, Serialize, Deserialize)] -pub enum AppTrackerRequest { - New { package: String }, - Install { package: String }, -} - -#[derive(Debug, Serialize, Deserialize)] -pub enum AppTrackerResponse { - New { package: String }, - Install { package: String }, - Error { error: String }, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct PackageManifestEntry { - pub process_name: String, - pub process_wasm_path: String, - pub on_panic: kt::OnPanic, - pub request_networking: bool, - pub request_messaging: Vec, - pub public: bool, -} - -fn parse_command(our: &Address, request_string: String) -> anyhow::Result { - match serde_json::from_str(&request_string)? { - AppTrackerRequest::New { package } => { - let Some(payload) = get_payload() else { - return Err(anyhow::anyhow!("no payload")); - }; - - let vfs_address = Address { - node: our.node.clone(), - process: ProcessId::from_str("vfs:sys:uqbar").unwrap(), - }; - - let _ = process_lib::send_and_await_response( - &vfs_address, - false, - Some( - serde_json::to_string(&kt::VfsRequest { - drive: package.clone(), - action: kt::VfsAction::New, - }) - .unwrap(), - ), - None, - None, - 5, - )?; - - // add zip bytes - let _ = process_lib::send_and_await_response( - &vfs_address, - true, - Some( - serde_json::to_string(&kt::VfsRequest { - drive: package.clone(), - action: kt::VfsAction::Add { - full_path: package.clone().into(), - entry_type: kt::AddEntryType::ZipArchive, - }, - }) - .unwrap(), - ), - None, - Some(&payload), - 5, - )?; - Ok(AppTrackerResponse::New { package }) - } - AppTrackerRequest::Install { package } => { - let vfs_address = Address { - node: our.node.clone(), - process: ProcessId::from_str("vfs:sys:uqbar").unwrap(), - }; - - let _ = process_lib::send_and_await_response( - &vfs_address, - false, - Some( - serde_json::to_string(&kt::VfsRequest { - drive: package.clone(), - action: kt::VfsAction::GetEntry("/manifest.json".into()), - }) - .unwrap(), - ), - None, - None, - 5, - )?; - let Some(payload) = get_payload() else { - return Err(anyhow::anyhow!("no payload")); - }; - let manifest = String::from_utf8(payload.bytes)?; - let manifest = serde_json::from_str::>(&manifest).unwrap(); - - for entry in manifest { - let path = if entry.process_wasm_path.starts_with("/") { - entry.process_wasm_path - } else { - format!("/{}", entry.process_wasm_path) - }; - - let (_, hash_response) = process_lib::send_and_await_response( - &vfs_address, - false, - Some( - serde_json::to_string(&kt::VfsRequest { - drive: package.clone(), - action: kt::VfsAction::GetHash(path.clone()), - }) - .unwrap(), - ), - None, - None, - 5, - )?; - - let Message::Response((Response { ipc: Some(ipc), .. }, _)) = hash_response else { - return Err(anyhow::anyhow!("bad vfs response")); - }; - let kt::VfsResponse::GetHash(Some(hash)) = serde_json::from_str(&ipc).unwrap() else { - return Err(anyhow::anyhow!("no hash in vfs")); - }; - - // build initial caps - let mut initial_capabilities: HashSet = HashSet::new(); - if entry.request_networking { - let Some(networking_cap) = get_capability( - &Address { - node: our.node.clone(), - process: ProcessId::from_str("kernel:sys:uqbar").unwrap(), - }, - &"\"network\"".to_string(), - ) else { - return Err(anyhow::anyhow!("app_tracker: no net cap")); - }; - initial_capabilities.insert(kt::de_wit_signed_capability(networking_cap)); - } - let Some(read_cap) = get_capability( - &vfs_address.clone(), - &serde_json::to_string(&serde_json::json!({ - "kind": "read", - "drive": package, - })).unwrap(), - ) else { - return Err(anyhow::anyhow!("app_tracker: no read cap")); - }; - initial_capabilities.insert(kt::de_wit_signed_capability(read_cap)); - let Some(write_cap) = get_capability( - &vfs_address.clone(), - &serde_json::to_string(&serde_json::json!({ - "kind": "write", - "drive": package, - })).unwrap(), - ) else { - return Err(anyhow::anyhow!("app_tracker: no write cap")); - }; - initial_capabilities.insert(kt::de_wit_signed_capability(write_cap)); - let mut public = false; - - let entry_process_id = match ProcessId::from_str( - &[entry.process_name.clone(), ":".into(), package.clone()].concat(), - ) { - Ok(process_id) => process_id, - Err(_) => { - return Err(anyhow::anyhow!("app_tracker: invalid process id!")); - } - }; - - for process_name in &entry.request_messaging { - let Ok(parsed_process_id) = ProcessId::from_str(&process_name) else { - // TODO handle arbitrary caps here - continue; - }; - let Some(messaging_cap) = get_capability( - &Address { - node: our.node.clone(), - process: parsed_process_id.clone(), - }, - &"\"messaging\"".into() - ) else { - print_to_terminal(0, &format!("app_tracker: no cap for {} to give away!", process_name)); - continue; - }; - initial_capabilities.insert(kt::de_wit_signed_capability(messaging_cap)); - } - - let process_id = format!("{}:{}", entry.process_name, package.clone()); - let Ok(parsed_new_process_id) = ProcessId::from_str(&process_id) else { - return Err(anyhow::anyhow!("app_tracker: invalid process id!")); - }; - let _ = process_lib::send_request( - &Address { - node: our.node.clone(), - process: ProcessId::from_str("kernel:sys:uqbar").unwrap(), - }, - false, - Some( - serde_json::to_string(&kt::KernelCommand::KillProcess( - kt::ProcessId::de_wit(parsed_new_process_id.clone()), - )) - .unwrap(), - ), - None, - None, - None, - ); - - // kernel start process takes bytes as payload + wasm_bytes_handle... - // reconsider perhaps - let (_, _bytes_response) = process_lib::send_and_await_response( - &vfs_address, - false, - Some( - serde_json::to_string(&kt::VfsRequest { - drive: package.clone(), - action: kt::VfsAction::GetEntry(path), - }) - .unwrap(), - ), - None, - None, - 5, - )?; - - let Some(payload) = get_payload() else { - return Err(anyhow::anyhow!("no wasm bytes payload.")); - }; - - let _ = process_lib::send_and_await_response( - &Address { - node: our.node.clone(), - process: ProcessId::from_str("kernel:sys:uqbar").unwrap(), - }, - false, - Some( - serde_json::to_string(&kt::KernelCommand::StartProcess { - id: kt::ProcessId::de_wit(parsed_new_process_id), - wasm_bytes_handle: hash, - on_panic: entry.on_panic, - initial_capabilities, - public, - }) - .unwrap(), - ), - None, - Some(&payload), - 5, - )?; - } - Ok(AppTrackerResponse::Install { package }) - } - } -} - -impl Guest for Component { - fn init(our: Address) { - assert_eq!(our.process.to_string(), "app_tracker:app_tracker:uqbar"); - print_to_terminal(0, &format!("app_tracker: start")); - loop { - let message = match receive() { - Ok((source, message)) => { - if our.node != source.node { - continue; - } - message - } - Err((error, _context)) => { - print_to_terminal(0, &format!("net error: {:?}!", error.kind)); - continue; - } - }; - match message { - Message::Request(Request { - ipc, - expects_response, - metadata, - .. - }) => { - let Some(command) = ipc else { - continue; - }; - match parse_command(&our, command) { - Ok(response) => { - if let Some(_) = expects_response { - let _ = send_response( - &Response { - ipc: Some(serde_json::to_string(&response).unwrap()), - metadata, - }, - None, - ); - }; - } - Err(e) => { - print_to_terminal(0, &format!("app_tracker: got error {}", e)); - if let Some(_) = expects_response { - let error = AppTrackerResponse::Error { - error: format!("{}", e), - }; - let _ = send_response( - &Response { - ipc: Some(serde_json::to_string(&error).unwrap()), - metadata, - }, - None, - ); - }; - } - } - } - _ => continue, - } - } - } -} diff --git a/modules/rpc/pkg/manifest.json b/modules/rpc/pkg/manifest.json index e2518d78..e62fcbe2 100644 --- a/modules/rpc/pkg/manifest.json +++ b/modules/rpc/pkg/manifest.json @@ -6,7 +6,7 @@ "request_networking": false, "request_messaging": [ "http_bindings:http_bindings:uqbar", - "app_tracker:app_tracker:uqbar", + "main:app_store:uqbar", "http_server:sys:uqbar" ], "public": false diff --git a/src/kernel/mod.rs b/src/kernel/mod.rs index 17934f0c..928d3f6d 100644 --- a/src/kernel/mod.rs +++ b/src/kernel/mod.rs @@ -1971,7 +1971,7 @@ async fn make_event_loop( t::Printout { verbosity: 0, content: format!( - "event loop: process {:?} doesn't have capability to send networked messages", + "event loop: process {} doesn't have capability to send networked messages", kernel_message.source.process ) } @@ -2007,7 +2007,7 @@ async fn make_event_loop( t::Printout { verbosity: 0, content: format!( - "event loop: process {:?} doesn't have capability to message process {:?}", + "event loop: process {} doesn't have capability to message process {}", kernel_message.source.process, kernel_message.target.process ) } diff --git a/src/process_lib.rs b/src/process_lib.rs index e68148ab..f16fe34d 100644 --- a/src/process_lib.rs +++ b/src/process_lib.rs @@ -3,6 +3,49 @@ use serde::{Deserialize, Serialize}; use super::bindings::component::uq_process::types::*; use super::bindings::{Address, Payload, ProcessId, SendError}; +#[derive(Hash, Eq, PartialEq, Debug, Clone, Serialize, Deserialize)] +pub struct PackageId { + pub package_name: String, + pub publisher_node: String, +} + +impl PackageId { + pub fn new(package_name: &str, publisher_node: &str) -> Self { + PackageId { + package_name: package_name.into(), + publisher_node: publisher_node.into(), + } + } + pub fn from_str(input: &str) -> Result { + // split string on colons into 2 segments + let mut segments = input.split(':'); + let package_name = segments + .next() + .ok_or(ProcessIdParseError::MissingField)? + .to_string(); + let publisher_node = segments + .next() + .ok_or(ProcessIdParseError::MissingField)? + .to_string(); + if segments.next().is_some() { + return Err(ProcessIdParseError::TooManyColons); + } + Ok(PackageId { + package_name, + publisher_node, + }) + } + pub fn to_string(&self) -> String { + [self.package_name.as_str(), self.publisher_node.as_str()].join(":") + } + pub fn package(&self) -> &str { + &self.package_name + } + pub fn publisher_node(&self) -> &str { + &self.publisher_node + } +} + #[allow(dead_code)] impl ProcessId { /// generates a random u64 number if process_name is not declared @@ -80,6 +123,28 @@ pub enum ProcessIdParseError { MissingField, } +impl std::fmt::Display for ProcessIdParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + ProcessIdParseError::TooManyColons => "Too many colons in ProcessId string", + ProcessIdParseError::MissingField => "Missing field in ProcessId string", + } + ) + } +} + +impl std::error::Error for ProcessIdParseError { + fn description(&self) -> &str { + match self { + ProcessIdParseError::TooManyColons => "Too many colons in ProcessId string", + ProcessIdParseError::MissingField => "Missing field in ProcessId string", + } + } +} + pub fn send_and_await_response( target: &Address, inherit: bool,