VFS API
Useful helper functions can be found in the kinode_process_lib
The VFS API tries to map over the std::fs calls as directly as possible.
Every request takes a path and a corresponding action.
Drives
A drive is a directory within a package's VFS directory, e.g., app_store:sys/pkg/
or your_package:publisher.os/my_drive/
.
Drives are owned by packages.
Packages can share access to drives they own via capabilities.
Each package is spawned with two drives: pkg/
and tmp/
.
All processes in a package have caps to those drives.
Processes can also create additional drives.
pkg/
drive
The pkg/
drive contains metadata about the package that Kinode requires to run that package, .wasm
binaries, and optionally the API of the package and the UI.
When creating packages, the pkg/
drive is populated by kit build
and loaded into the Kinode using kit start-package
.
tmp/
drive
The tmp/
drive can be written to directly by the owning package using standard filesystem functionality (i.e. std::fs
in Rust) via WASI in addition to the Kinode VFS.
Imports
#![allow(unused)] fn main() { use kinode_process_lib::vfs::{ create_drive, open_file, open_dir, create_file, metadata, File, Directory, }; }
Opening/Creating a Drive
#![allow(unused)] fn main() { let drive_path: String = create_drive(our.package_id(), "drive_name")?; // you can now prepend this path to any files/directories you're interacting with let file = open_file(&format!("{}/hello.txt", &drive_path), true); }
Sharing a Drive Capability
#![allow(unused)] fn main() { let vfs_read_cap = serde_json::json!({ "kind": "read", "drive": drive_path, }).to_string(); let vfs_address = Address { node: our.node.clone(), process: ProcessId::from_str("vfs:distro:sys").unwrap(), }; // get this capability from our store let cap = get_capability(&vfs_address, &vfs_read_cap); // now if we have that Capability, we can attach it to a subsequent message. if let Some(cap) = cap { Request::new() .capabilities(vec![cap]) .body(b"hello".to_vec()) .send()?; } }
#![allow(unused)] fn main() { // the receiving process can then save the capability to it's store, and open the drive. save_capabilities(incoming_request.capabilities); let dir = open_dir(&drive_path, false)?; }
Files
Open a File
#![allow(unused)] fn main() { /// Opens a file at path, if no file at path, creates one if boolean create is true. let file_path = format!("{}/hello.txt", &drive_path); let file = open_file(&file_path, true); }
Create a File
#![allow(unused)] fn main() { /// Creates a file at path, if file found at path, truncates it to 0. let file_path = format!("{}/hello.txt", &drive_path); let file = create_file(&file_path); }
Read a File
#![allow(unused)] fn main() { /// Reads the entire file, from start position. /// Returns a vector of bytes. let contents = file.read()?; }
Write a File
#![allow(unused)] fn main() { /// Write entire slice as the new file. /// Truncates anything that existed at path before. let buffer = b"Hello!"; file.write(&buffer)?; }
Write to File
#![allow(unused)] fn main() { /// Write buffer to file at current position, overwriting any existing data. let buffer = b"World!"; file.write_all(&buffer)?; }
Read at position
#![allow(unused)] fn main() { /// Read into buffer from current cursor position /// Returns the amount of bytes read. let mut buffer = vec![0; 5]; file.read_at(&buffer)?; }
Set Length
#![allow(unused)] fn main() { /// Set file length, if given size > underlying file, fills it with 0s. file.set_len(42)?; }
Seek to a position
#![allow(unused)] fn main() { /// Seek file to position. /// Returns the new position. let position = SeekFrom::End(0); file.seek(&position)?; }
Sync
#![allow(unused)] fn main() { /// Syncs path file buffers to disk. file.sync_all()?; }
Metadata
#![allow(unused)] fn main() { /// Metadata of a path, returns file type and length. let metadata = file.metadata()?; }
Directories
Open a Directory
#![allow(unused)] fn main() { /// Opens or creates a directory at path. /// If trying to create an existing file, will just give you the path. let dir_path = format!("{}/my_pics", &drive_path); let dir = open_dir(&dir_path, true); }
Read a Directory
#![allow(unused)] fn main() { /// Iterates through children of directory, returning a vector of DirEntries. /// DirEntries contain the path and file type of each child. let entries = dir.read()?; }
General path Metadata
#![allow(unused)] fn main() { /// Metadata of a path, returns file type and length. let some_path = format!("{}/test", &drive_path); let metadata = metadata(&some_path)?; }
API
#![allow(unused)] fn main() { pub struct VfsRequest { /// path is always prepended by package_id, the capabilities of the topmost directory are checked /// "/your_package:publisher.os/drive_dir/another_dir_or_file" pub path: String, pub action: VfsAction, } pub enum VfsAction { CreateDrive, CreateDir, CreateDirAll, CreateFile, OpenFile { create: bool }, CloseFile, Write, WriteAt, Append, SyncAll, Read, ReadDir, ReadToEnd, ReadExact(u64), ReadToString, Seek { seek_from: SeekFrom }, RemoveFile, RemoveDir, RemoveDirAll, Rename { new_path: String }, Metadata, AddZip, CopyFile { new_path: String }, Len, SetLen(u64), Hash, } pub enum SeekFrom { Start(u64), End(i64), Current(i64), } pub enum FileType { File, Directory, Symlink, Other, } pub struct FileMetadata { pub file_type: FileType, pub len: u64, } pub struct DirEntry { pub path: String, pub file_type: FileType, } pub enum VfsResponse { Ok, Err(VfsError), Read, SeekFrom(u64), ReadDir(Vec<DirEntry>), ReadToString(String), Metadata(FileMetadata), Len(u64), Hash([u8; 32]), } pub enum VfsError { NoCap { action: String, path: String }, BadBytes { action: String, path: String }, BadRequest { error: String }, ParseError { error: String, path: String }, IOError { error: String, path: String }, CapChannelFail { error: String }, BadJson { error: String }, NotFound { path: String }, CreateDirError { path: String, error: String }, } }