use std::path::{Path, PathBuf};
use async_trait::async_trait;
use kaish_types::backend::{
BackendError, BackendResult, MountInfo, PatchOp, ReadRange, ToolInfo, ToolResult, WriteMode,
};
use kaish_types::{DirEntry, PathAccess, ToolArgs};
use crate::ctx::ToolCtx;
#[async_trait]
pub trait KernelBackend: Send + Sync {
async fn read(&self, path: &Path, range: Option<ReadRange>) -> BackendResult<Vec<u8>>;
async fn write(&self, path: &Path, content: &[u8], mode: WriteMode) -> BackendResult<()>;
async fn append(&self, path: &Path, content: &[u8]) -> BackendResult<()>;
async fn patch(&self, path: &Path, ops: &[PatchOp]) -> BackendResult<()>;
async fn list(&self, path: &Path) -> BackendResult<Vec<DirEntry>>;
async fn stat(&self, path: &Path) -> BackendResult<DirEntry>;
async fn mkdir(&self, path: &Path) -> BackendResult<()>;
async fn set_mtime(&self, path: &Path, mtime: std::time::SystemTime) -> BackendResult<()>;
async fn remove(&self, path: &Path, recursive: bool) -> BackendResult<()>;
async fn rename(&self, from: &Path, to: &Path) -> BackendResult<()>;
async fn exists(&self, path: &Path) -> bool;
async fn lstat(&self, path: &Path) -> BackendResult<DirEntry>;
async fn read_link(&self, path: &Path) -> BackendResult<PathBuf>;
async fn symlink(&self, target: &Path, link: &Path) -> BackendResult<()>;
async fn canonicalize(&self, path: &Path, allow_missing_final: bool) -> BackendResult<PathBuf> {
let components: Vec<_> = path.components().collect();
let total = components.len();
let mut current = PathBuf::new();
for (idx, component) in components.iter().enumerate() {
let is_last = idx + 1 == total;
match component {
std::path::Component::RootDir => {}
std::path::Component::CurDir => {}
std::path::Component::ParentDir => {
current.pop();
}
std::path::Component::Normal(_) => {
current.push(component);
current =
resolve_symlink_hop(self, current, is_last && allow_missing_final).await?;
}
std::path::Component::Prefix(_) => {
current.push(component);
}
}
}
Ok(current)
}
async fn call_tool(
&self,
name: &str,
args: ToolArgs,
ctx: &mut dyn ToolCtx,
) -> BackendResult<ToolResult>;
async fn list_tools(&self) -> BackendResult<Vec<ToolInfo>>;
async fn get_tool(&self, name: &str) -> BackendResult<Option<ToolInfo>>;
fn read_only(&self) -> bool;
async fn path_access(&self, path: &Path) -> BackendResult<PathAccess> {
let entry = self.stat(path).await?;
Ok(PathAccess::resolve(entry.permissions, self.read_only()))
}
fn backend_type(&self) -> &str;
fn mounts(&self) -> Vec<MountInfo>;
fn resolve_real_path(&self, path: &Path) -> Option<PathBuf>;
}
const MAX_SYMLINK_HOPS: usize = 40;
async fn resolve_symlink_hop<B: KernelBackend + ?Sized>(
backend: &B,
path: PathBuf,
allow_missing: bool,
) -> BackendResult<PathBuf> {
let mut current = path;
for _ in 0..MAX_SYMLINK_HOPS {
match backend.lstat(¤t).await {
Ok(entry) if entry.is_symlink() => {
let target = backend.read_link(¤t).await?;
current = if target.is_absolute() {
target
} else {
let parent = current.parent().unwrap_or(Path::new(""));
parent.join(target)
};
current = fold_dots(current);
}
Ok(_) => return Ok(current),
Err(BackendError::NotFound(_)) if allow_missing => return Ok(current),
Err(e) => return Err(e),
}
}
Err(BackendError::InvalidOperation(format!(
"too many levels of symbolic links: {}",
current.display()
)))
}
fn fold_dots(path: PathBuf) -> PathBuf {
let mut out = PathBuf::new();
for component in path.components() {
match component {
std::path::Component::ParentDir => {
out.pop();
}
std::path::Component::CurDir => {}
other => out.push(other),
}
}
out
}