mod model;
mod repository;
mod sandbox;
use std::fmt;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
pub use model::{
CheckResult, CheckStage, CheckStageResult, Error, Result, RustLibFile, RustLibPath,
};
use repository::{RepositorySnapshot, read_repository, validate_name, write_repository};
static WORK_COUNTER: AtomicU64 = AtomicU64::new(0);
#[derive(Clone)]
pub struct KcodeRustLibs {
rust_libs_root: PathBuf,
shared: Arc<Shared>,
}
impl KcodeRustLibs {
pub fn new(
rust_libs_root: impl AsRef<Path>,
crates_io_registry_token: impl AsRef<str>,
) -> Result<Self> {
let token = RegistryToken::new(crates_io_registry_token.as_ref())?;
let requested_root = absolute_path(rust_libs_root.as_ref())?;
fs::create_dir_all(&requested_root)
.map_err(|source| Error::io("create Rust libraries root", &requested_root, source))?;
let rust_libs_root = fs::canonicalize(&requested_root).map_err(|source| {
Error::io("canonicalize Rust libraries root", &requested_root, source)
})?;
if !fs::metadata(&rust_libs_root)
.map_err(|source| Error::io("inspect Rust libraries root", &rust_libs_root, source))?
.is_dir()
{
return Err(Error::RustLibIsNotDirectory(
rust_libs_root.display().to_string(),
));
}
let work_root = WorkRoot::new()?;
if paths_overlap(&rust_libs_root, work_root.path()) {
return Err(Error::RootsOverlap {
rust_libs_root,
work_root: work_root.path().to_path_buf(),
});
}
Ok(Self {
rust_libs_root,
shared: Arc::new(Shared { token, work_root }),
})
}
pub fn create_rust_lib(&self, name: &str) -> Result<OpenedRustLib> {
validate_name(name)?;
let root = self.rust_libs_root.join(name);
match fs::symlink_metadata(&root) {
Ok(_) => return Err(Error::RustLibAlreadyExists(name.to_owned())),
Err(source) if source.kind() == io::ErrorKind::NotFound => {}
Err(source) => {
return Err(Error::io("inspect Rust library destination", &root, source));
}
}
fs::create_dir(&root).map_err(|source| Error::io("create Rust library", &root, source))?;
let creation = (|| {
let source = root.join("src");
fs::create_dir(&source)
.map_err(|error| Error::io("create source directory", &source, error))?;
let manifest = format!(
"[package]\nname = \"{name}\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\n"
);
fs::write(root.join("Cargo.toml"), manifest).map_err(|error| {
Error::io("write initial manifest", root.join("Cargo.toml"), error)
})?;
fs::write(root.join("Documentation.md"), "").map_err(|error| {
Error::io(
"write initial documentation",
root.join("Documentation.md"),
error,
)
})?;
fs::write(source.join("lib.rs"), "")
.map_err(|error| Error::io("write initial source", source.join("lib.rs"), error))?;
self.open_rust_lib(name)
})();
if creation.is_err() {
let _ = fs::remove_dir_all(&root);
}
creation
}
pub fn open_rust_lib(&self, name: &str) -> Result<OpenedRustLib> {
validate_name(name)?;
let root = self.rust_libs_root.join(name);
let metadata = match fs::symlink_metadata(&root) {
Ok(metadata) => metadata,
Err(source) if source.kind() == io::ErrorKind::NotFound => {
return Err(Error::RustLibNotFound(name.to_owned()));
}
Err(source) => return Err(Error::io("inspect Rust library", &root, source)),
};
if metadata.file_type().is_symlink() {
return Err(Error::SymlinkNotAllowed(root));
}
if !metadata.is_dir() {
return Err(Error::RustLibIsNotDirectory(name.to_owned()));
}
let snapshot = read_repository(&root)?;
Ok(OpenedRustLib {
name: name.to_owned(),
root,
snapshot,
shared: Arc::clone(&self.shared),
})
}
}
impl fmt::Debug for KcodeRustLibs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("KcodeRustLibs")
.field("rust_libs_root", &self.rust_libs_root)
.field("registry_token", &"[REDACTED]")
.finish_non_exhaustive()
}
}
pub struct OpenedRustLib {
name: String,
root: PathBuf,
snapshot: RepositorySnapshot,
shared: Arc<Shared>,
}
impl OpenedRustLib {
pub fn name(&self) -> &str {
&self.name
}
pub fn version(&self) -> &str {
&self.snapshot.version
}
pub fn files(&self) -> &[RustLibFile] {
&self.snapshot.files
}
pub fn write(&mut self, files: &[RustLibFile]) -> Result<()> {
self.snapshot = write_repository(&self.root, &self.snapshot.files, files)?;
Ok(())
}
pub fn check(&self) -> Result<CheckResult> {
sandbox::check_repository(&self.root, self.shared.work_root.path())
}
pub fn publish(&self) -> Result<()> {
let check = self.check()?;
if !check.passed() {
return Err(Error::CheckFailed(check));
}
sandbox::publish_repository(
&self.root,
self.shared.work_root.path(),
self.shared.token.expose(),
)
}
}
struct Shared {
token: RegistryToken,
work_root: WorkRoot,
}
struct RegistryToken(Arc<str>);
impl RegistryToken {
fn new(value: &str) -> Result<Self> {
let value = value.trim();
if value.is_empty() {
return Err(Error::InvalidRegistryToken);
}
Ok(Self(Arc::from(value)))
}
fn expose(&self) -> &str {
&self.0
}
}
impl fmt::Debug for RegistryToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("[REDACTED]")
}
}
struct WorkRoot(PathBuf);
impl WorkRoot {
fn new() -> Result<Self> {
let parent = std::env::temp_dir().join("kcode-rust-libs-v2");
fs::create_dir_all(&parent)
.map_err(|source| Error::io("create temporary work parent", &parent, source))?;
for _ in 0..100 {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
let counter = WORK_COUNTER.fetch_add(1, Ordering::Relaxed);
let path = parent.join(format!(
"process-{}-{timestamp}-{counter}",
std::process::id()
));
match fs::create_dir(&path) {
Ok(()) => {
let canonical = fs::canonicalize(&path).map_err(|source| {
Error::io("canonicalize temporary work root", &path, source)
})?;
return Ok(Self(canonical));
}
Err(source) if source.kind() == io::ErrorKind::AlreadyExists => continue,
Err(source) => {
return Err(Error::io("create temporary work root", path, source));
}
}
}
Err(Error::Sandbox {
stage: "prepare-work-root".to_owned(),
message: "could not allocate a unique process work root".to_owned(),
})
}
fn path(&self) -> &Path {
&self.0
}
}
impl Drop for WorkRoot {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
fn absolute_path(path: &Path) -> Result<PathBuf> {
if path.is_absolute() {
Ok(path.to_path_buf())
} else {
let current = std::env::current_dir()
.map_err(|source| Error::io("read current directory", ".", source))?;
Ok(current.join(path))
}
}
fn paths_overlap(left: &Path, right: &Path) -> bool {
left.starts_with(right) || right.starts_with(left)
}