Skip to main content
Glama

microsandbox

by microsandbox
path.rs12.4 kB
//! `microsandbox_utils::path` is a module containing path utilities for the microsandbox project. use std::{ path::{Path, PathBuf}, sync::LazyLock, }; use typed_path::{Utf8UnixComponent, Utf8UnixPathBuf}; use crate::{MicrosandboxUtilsError, MicrosandboxUtilsResult}; //-------------------------------------------------------------------------------------------------- // Constants //-------------------------------------------------------------------------------------------------- /// The directory name for microsandbox's project-specific data pub const MICROSANDBOX_ENV_DIR: &str = ".menv"; /// The directory name for microsandbox's global data pub const MICROSANDBOX_HOME_DIR: &str = ".microsandbox"; /// The directory where project read-write layers are stored /// /// Example: <PROJECT_ROOT>/<MICROSANDBOX_ENV_DIR>/<RW_SUBDIR> pub const RW_SUBDIR: &str = "rw"; /// The directory where project patch layers are stored /// /// Example: <PROJECT_ROOT>/<MICROSANDBOX_ENV_DIR>/<PATCH_SUBDIR> pub const PATCH_SUBDIR: &str = "patch"; /// The directory where project logs are stored /// /// Example: <PROJECT_ROOT>/<MICROSANDBOX_ENV_DIR>/<LOG_SUBDIR> pub const LOG_SUBDIR: &str = "log"; /// The directory where global image layers are stored /// /// Example: <MICROSANDBOX_HOME_DIR>/<LAYERS_SUBDIR> pub const LAYERS_SUBDIR: &str = "layers"; /// The directory where installed sandboxes are stored /// /// Example: <MICROSANDBOX_HOME_DIR>/<INSTALLS_SUBDIR> pub const INSTALLS_SUBDIR: &str = "installs"; /// The filename for the project active sandbox database /// /// Example: <PROJECT_ROOT>/<MICROSANDBOX_ENV_DIR>/<SANDBOX_DB_FILENAME> pub const SANDBOX_DB_FILENAME: &str = "sandbox.db"; /// The filename for the global OCI database /// /// Example: <MICROSANDBOX_HOME_DIR>/<OCI_DB_FILENAME> pub const OCI_DB_FILENAME: &str = "oci.db"; /// The directory on the microvm where sandbox scripts are stored pub const SANDBOX_DIR: &str = ".sandbox"; /// The directory on the microvm where sandbox scripts are stored /// /// Example: <SANDBOX_DIR>/<SCRIPTS_DIR> pub const SCRIPTS_DIR: &str = "scripts"; /// The suffix added to extracted layer directories /// /// Example: <MICROSANDBOX_HOME_DIR>/<LAYERS_SUBDIR>/<LAYER_ID>.<EXTRACTED_LAYER_SUFFIX> pub const EXTRACTED_LAYER_SUFFIX: &str = "extracted"; /// The microsandbox config file name. /// /// Example: <PROJECT_ROOT>/<MICROSANDBOX_ENV_DIR>/<SANDBOX_DB_FILENAME> pub const MICROSANDBOX_CONFIG_FILENAME: &str = "Sandboxfile"; /// The shell script name. /// /// Example: <PROJECT_ROOT>/<MICROSANDBOX_ENV_DIR>/<PATCH_SUBDIR>/<CONFIG_NAME>/<SHELL_SCRIPT_NAME> pub const SHELL_SCRIPT_NAME: &str = "shell"; /// The directory where namespaces are stored /// /// Example: <MICROSANDBOX_HOME_DIR>/<NAMESPACES_SUBDIR> pub const NAMESPACES_SUBDIR: &str = "namespaces"; /// The PID file for the server /// /// Example: <MICROSANDBOX_HOME_DIR>/<SERVER_PID_FILE> pub const SERVER_PID_FILE: &str = "server.pid"; /// The server secret key file /// /// Example: <MICROSANDBOX_HOME_DIR>/<SERVER_KEY_FILE> pub const SERVER_KEY_FILE: &str = "server.key"; /// The file where sandbox portal ports are stored /// /// Example: <MICROSANDBOX_HOME_DIR>/<NAMESPACE_SUBDIR>/<PORTAL_PORTS_FILE> pub const PORTAL_PORTS_FILE: &str = "portal.ports"; /// The XDG home directory /// /// Example: <HOME>/.local pub static XDG_HOME_DIR: LazyLock<PathBuf> = LazyLock::new(|| dirs::home_dir().unwrap().join(".local")); /// The bin subdirectory for microsandbox /// /// Example: <XDG_HOME_DIR>/bin pub const XDG_BIN_DIR: &str = "bin"; /// The lib subdirectory for microsandbox /// /// Example: <XDG_HOME_DIR>/lib pub const XDG_LIB_DIR: &str = "lib"; /// The suffix for log files pub const LOG_SUFFIX: &str = "log"; /// The filename for the supervisor's log file pub const SUPERVISOR_LOG_FILENAME: &str = "supervisor.log"; //-------------------------------------------------------------------------------------------------- // Types //-------------------------------------------------------------------------------------------------- /// The type of a supported path. pub enum SupportedPathType { /// Any path type. Any, /// An absolute path. Absolute, /// A relative path. Relative, } //-------------------------------------------------------------------------------------------------- // Functions //-------------------------------------------------------------------------------------------------- /// Normalizes a path string for volume mount comparison. /// /// Rules: /// - Resolves . and .. components where possible /// - Prevents path traversal that would escape the root /// - Removes redundant separators and trailing slashes /// - Case-sensitive comparison (Unix standard) /// - Can enforce path type requirements (absolute, relative, or any) /// /// # Arguments /// * `path` - The path to normalize /// * `path_type` - The required path type (absolute, relative, or any) /// /// # Returns /// An error if the path is invalid, would escape root, or doesn't meet path type requirement pub fn normalize_path(path: &str, path_type: SupportedPathType) -> MicrosandboxUtilsResult<String> { if path.is_empty() { return Err(MicrosandboxUtilsError::PathValidation( "Path cannot be empty".to_string(), )); } let path = Utf8UnixPathBuf::from(path); let mut normalized = Vec::new(); let mut is_absolute = false; let mut depth = 0; for component in path.components() { match component { // Root component must come first if present Utf8UnixComponent::RootDir => { if normalized.is_empty() { is_absolute = true; normalized.push("/".to_string()); } else { return Err(MicrosandboxUtilsError::PathValidation( "Invalid path: root component '/' found in middle of path".to_string(), )); } } // Handle parent directory references Utf8UnixComponent::ParentDir => { if depth > 0 { // Can go up if we have depth normalized.pop(); depth -= 1; } else { // Trying to go above root return Err(MicrosandboxUtilsError::PathValidation( "Invalid path: cannot traverse above root directory".to_string(), )); } } // Skip current dir components Utf8UnixComponent::CurDir => continue, // Normal components are fine Utf8UnixComponent::Normal(c) => { if !c.is_empty() { normalized.push(c.to_string()); depth += 1; } } } } // Check path type requirements match path_type { SupportedPathType::Absolute if !is_absolute => { return Err(MicrosandboxUtilsError::PathValidation( "Path must be absolute (start with '/')".to_string(), )); } SupportedPathType::Relative if is_absolute => { return Err(MicrosandboxUtilsError::PathValidation( "Path must be relative (must not start with '/')".to_string(), )); } _ => {} } if is_absolute { if normalized.len() == 1 { // Just root Ok("/".to_string()) } else { // Join all components with "/" and add root at start Ok(format!("/{}", normalized[1..].join("/"))) } } else { // For relative paths, just join all components Ok(normalized.join("/")) } } /// Resolves the path to a file, checking both environment variable and default locations. /// /// First checks the environment variable specified by `env_var`. /// If that's not set, falls back to `default_path`. /// Returns an error if the file is not found at the resolved location. pub fn resolve_env_path( env_var: &str, default_path: impl AsRef<Path>, ) -> MicrosandboxUtilsResult<PathBuf> { let (path, source) = std::env::var(env_var) .map(|p| (PathBuf::from(p), "environment variable")) .unwrap_or_else(|_| (default_path.as_ref().to_path_buf(), "default path")); if !path.exists() { return Err(MicrosandboxUtilsError::FileNotFound( path.to_string_lossy().to_string(), source.to_string(), )); } Ok(path) } //-------------------------------------------------------------------------------------------------- // Tests //-------------------------------------------------------------------------------------------------- #[cfg(test)] mod tests { use super::*; #[test] fn test_normalize_path() { // Test with SupportedPathType::Absolute assert_eq!( normalize_path("/data/app/", SupportedPathType::Absolute).unwrap(), "/data/app" ); assert_eq!( normalize_path("/data//app", SupportedPathType::Absolute).unwrap(), "/data/app" ); assert_eq!( normalize_path("/data/./app", SupportedPathType::Absolute).unwrap(), "/data/app" ); // Test with SupportedPathType::Relative assert_eq!( normalize_path("data/app/", SupportedPathType::Relative).unwrap(), "data/app" ); assert_eq!( normalize_path("./data/app", SupportedPathType::Relative).unwrap(), "data/app" ); assert_eq!( normalize_path("data//app", SupportedPathType::Relative).unwrap(), "data/app" ); // Test with SupportedPathType::Any assert_eq!( normalize_path("/data/app", SupportedPathType::Any).unwrap(), "/data/app" ); assert_eq!( normalize_path("data/app", SupportedPathType::Any).unwrap(), "data/app" ); // Path traversal within bounds assert_eq!( normalize_path("/data/temp/../app", SupportedPathType::Absolute).unwrap(), "/data/app" ); assert_eq!( normalize_path("data/temp/../app", SupportedPathType::Relative).unwrap(), "data/app" ); // Invalid paths assert!(matches!( normalize_path("data/app", SupportedPathType::Absolute), Err(MicrosandboxUtilsError::PathValidation(e)) if e.contains("must be absolute") )); assert!(matches!( normalize_path("/data/app", SupportedPathType::Relative), Err(MicrosandboxUtilsError::PathValidation(e)) if e.contains("must be relative") )); assert!(matches!( normalize_path("/data/../..", SupportedPathType::Any), Err(MicrosandboxUtilsError::PathValidation(e)) if e.contains("cannot traverse above root") )); } #[test] fn test_normalize_path_complex() { // Complex but valid paths assert_eq!( normalize_path( "/data/./temp/../logs/app/./config/../", SupportedPathType::Absolute ) .unwrap(), "/data/logs/app" ); assert_eq!( normalize_path( "/data///temp/././../app//./test/..", SupportedPathType::Absolute ) .unwrap(), "/data/app" ); // Edge cases assert_eq!( normalize_path("/data/./././.", SupportedPathType::Absolute).unwrap(), "/data" ); assert_eq!( normalize_path("/data/test/../../data/app", SupportedPathType::Absolute).unwrap(), "/data/app" ); // Invalid complex paths assert!(matches!( normalize_path("/data/test/../../../root", SupportedPathType::Any), Err(MicrosandboxUtilsError::PathValidation(e)) if e.contains("cannot traverse above root") )); assert!(matches!( normalize_path("/./data/../..", SupportedPathType::Any), Err(MicrosandboxUtilsError::PathValidation(e)) if e.contains("cannot traverse above root") )); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/microsandbox/microsandbox'

If you have feedback or need assistance with the MCP directory API, please join our Discord server