From f932f106d33fdbc79dc1ba4ccb15a302c50a0603 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Fri, 19 Apr 2024 17:03:00 +0800 Subject: [PATCH 01/38] feat(judger): :sparkles: impl sandbox::limiter, see rewrite plan for detail --- Cargo.lock | 184 +++++++----- judger/.vscode/settings.json | 8 + judger/Cargo.toml | 7 +- judger/build.rs | 4 - judger/src/error.rs | 5 + judger/src/grpc.rs | 22 -- judger/src/init/cgroup.rs | 31 -- judger/src/init/check.rs | 95 ------- judger/src/init/config.rs | 187 ------------ judger/src/init/logger.rs | 33 --- judger/src/init/mod.rs | 23 -- judger/src/init/volumn.rs | 11 - judger/src/langs/artifact.rs | 363 ------------------------ judger/src/langs/mod.rs | 55 ---- judger/src/langs/spec.rs | 102 ------- judger/src/main.rs | 30 +- judger/src/sandbox/container.rs | 76 ----- judger/src/sandbox/daemon.rs | 54 ---- judger/src/sandbox/limiter/hier.rs | 27 ++ judger/src/sandbox/limiter/mod.rs | 98 +++++++ judger/src/sandbox/limiter/stat.rs | 17 ++ judger/src/sandbox/limiter/wrapper.rs | 66 +++++ judger/src/sandbox/mod.rs | 53 +--- judger/src/sandbox/process.rs | 150 ---------- judger/src/sandbox/resource.rs | 0 judger/src/sandbox/semaphore.rs | 137 +++++++++ judger/src/sandbox/utils/limiter/cpu.rs | 69 ----- judger/src/sandbox/utils/limiter/mem.rs | 25 -- judger/src/sandbox/utils/limiter/mod.rs | 181 ------------ judger/src/sandbox/utils/mod.rs | 3 - judger/src/sandbox/utils/nsjail.rs | 218 -------------- judger/src/sandbox/utils/semaphore.rs | 170 ----------- judger/src/server.rs | 327 --------------------- judger/src/tests/grpc.rs | 73 ----- judger/src/tests/langs.rs | 49 ---- judger/src/tests/mod.rs | 3 - judger/src/tests/sandbox.rs | 205 ------------- 37 files changed, 481 insertions(+), 2680 deletions(-) create mode 100644 judger/.vscode/settings.json delete mode 100644 judger/build.rs create mode 100644 judger/src/error.rs delete mode 100644 judger/src/grpc.rs delete mode 100644 judger/src/init/cgroup.rs delete mode 100644 judger/src/init/check.rs delete mode 100644 judger/src/init/config.rs delete mode 100644 judger/src/init/logger.rs delete mode 100644 judger/src/init/mod.rs delete mode 100644 judger/src/init/volumn.rs delete mode 100644 judger/src/langs/artifact.rs delete mode 100644 judger/src/langs/mod.rs delete mode 100644 judger/src/langs/spec.rs delete mode 100644 judger/src/sandbox/container.rs delete mode 100644 judger/src/sandbox/daemon.rs create mode 100644 judger/src/sandbox/limiter/hier.rs create mode 100644 judger/src/sandbox/limiter/mod.rs create mode 100644 judger/src/sandbox/limiter/stat.rs create mode 100644 judger/src/sandbox/limiter/wrapper.rs delete mode 100644 judger/src/sandbox/process.rs create mode 100644 judger/src/sandbox/resource.rs create mode 100644 judger/src/sandbox/semaphore.rs delete mode 100644 judger/src/sandbox/utils/limiter/cpu.rs delete mode 100644 judger/src/sandbox/utils/limiter/mem.rs delete mode 100644 judger/src/sandbox/utils/limiter/mod.rs delete mode 100644 judger/src/sandbox/utils/mod.rs delete mode 100644 judger/src/sandbox/utils/nsjail.rs delete mode 100644 judger/src/sandbox/utils/semaphore.rs delete mode 100644 judger/src/server.rs delete mode 100644 judger/src/tests/grpc.rs delete mode 100644 judger/src/tests/langs.rs delete mode 100644 judger/src/tests/mod.rs delete mode 100644 judger/src/tests/sandbox.rs diff --git a/Cargo.lock b/Cargo.lock index 4deb4a5f..b8dc3fa1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -685,7 +685,7 @@ dependencies = [ "paste", "postcard", "prost 0.12.3", - "prost-types 0.12.3", + "prost-types", "quick_cache", "rand", "rand_hc", @@ -702,7 +702,7 @@ dependencies = [ "tokio-stream", "toml 0.7.8", "tonic 0.11.0", - "tonic-build 0.11.0", + "tonic-build", "tonic-web", "tower-http", "tracing", @@ -1265,6 +1265,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + [[package]] name = "crossbeam-channel" version = "0.5.12" @@ -1274,6 +1287,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.11" @@ -1738,6 +1770,18 @@ dependencies = [ "subtle", ] +[[package]] +name = "filetime" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.52.0", +] + [[package]] name = "finl_unicode" version = "1.2.0" @@ -1828,7 +1872,7 @@ dependencies = [ "tokio", "toml 0.7.8", "tonic 0.11.0", - "tonic-build 0.11.0", + "tonic-build", "tonic-web-wasm-client", "tracing", "tracing-subscriber", @@ -1844,6 +1888,21 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "fuser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e697f6f62c20b6fad1ba0f84ae909f25971cf16e735273524e3977c94604cf8" +dependencies = [ + "libc", + "log", + "memchr", + "page_size", + "pkg-config", + "smallvec", + "zerocopy", +] + [[package]] name = "futures" version = "0.3.30" @@ -2292,7 +2351,7 @@ dependencies = [ "prost-wkt-types", "serde", "tonic 0.11.0", - "tonic-build 0.11.0", + "tonic-build", ] [[package]] @@ -2813,22 +2872,26 @@ name = "judger" version = "0.1.0" dependencies = [ "cgroups-rs", + "crossbeam", "derive_builder", "env_logger", + "fuser", "futures-core", + "grpc", + "lazy_static", "log", "prost 0.12.3", - "prost-types 0.12.3", + "prost-types", "rustix 0.38.31", "serde", "spin 0.9.8", + "tar", "tempfile", "thiserror", "tokio", "tokio-stream", "toml 0.7.8", "tonic 0.11.0", - "tonic-build 0.9.2", "tower", "tower-layer", "uuid", @@ -3030,7 +3093,7 @@ dependencies = [ "html-escape", "itertools 0.12.1", "leptos_hot_reload", - "prettyplease 0.2.16", + "prettyplease", "proc-macro-error", "proc-macro2", "quote", @@ -3727,6 +3790,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56d80efc4b6721e8be2a10a5df21a30fa0b470f1539e53d8b4e6e75faf938b63" +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "parking" version = "2.2.0" @@ -3957,16 +4030,6 @@ dependencies = [ "log", ] -[[package]] -name = "prettyplease" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" -dependencies = [ - "proc-macro2", - "syn 1.0.109", -] - [[package]] name = "prettyplease" version = "0.2.16" @@ -4073,28 +4136,6 @@ dependencies = [ "prost-derive 0.12.3", ] -[[package]] -name = "prost-build" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" -dependencies = [ - "bytes", - "heck", - "itertools 0.10.5", - "lazy_static", - "log", - "multimap", - "petgraph", - "prettyplease 0.1.25", - "prost 0.11.9", - "prost-types 0.11.9", - "regex", - "syn 1.0.109", - "tempfile", - "which", -] - [[package]] name = "prost-build" version = "0.12.3" @@ -4108,9 +4149,9 @@ dependencies = [ "multimap", "once_cell", "petgraph", - "prettyplease 0.2.16", + "prettyplease", "prost 0.12.3", - "prost-types 0.12.3", + "prost-types", "regex", "syn 2.0.52", "tempfile", @@ -4143,15 +4184,6 @@ dependencies = [ "syn 2.0.52", ] -[[package]] -name = "prost-types" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" -dependencies = [ - "prost 0.11.9", -] - [[package]] name = "prost-types" version = "0.12.3" @@ -4184,8 +4216,8 @@ checksum = "5b31cae9a54ca84fee1504740a82eebf2479532905e106f63ca0c3bc8d780321" dependencies = [ "heck", "prost 0.12.3", - "prost-build 0.12.3", - "prost-types 0.12.3", + "prost-build", + "prost-types", "quote", ] @@ -4197,8 +4229,8 @@ checksum = "435be4a8704091b4c5fb1d79799de7f2dbff53af05edf29385237f8cf7ab37ee" dependencies = [ "chrono", "prost 0.12.3", - "prost-build 0.12.3", - "prost-types 0.12.3", + "prost-build", + "prost-types", "prost-wkt", "prost-wkt-build", "regex", @@ -5654,6 +5686,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tar" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "tempfile" version = "3.10.1" @@ -5692,12 +5735,12 @@ dependencies = [ "log", "pretty_env_logger", "prost 0.12.3", - "prost-types 0.12.3", + "prost-types", "serde", "thiserror", "toml 0.7.8", "tonic 0.11.0", - "tonic-build 0.11.0", + "tonic-build", "tonic-web", "tower", "uuid", @@ -6014,28 +6057,15 @@ dependencies = [ "tracing", ] -[[package]] -name = "tonic-build" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6fdaae4c2c638bb70fe42803a26fbd6fc6ac8c72f5c59f67ecc2a2dcabf4b07" -dependencies = [ - "prettyplease 0.1.25", - "proc-macro2", - "prost-build 0.11.9", - "quote", - "syn 1.0.109", -] - [[package]] name = "tonic-build" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4ef6dd70a610078cb4e338a0f79d06bc759ff1b22d2120c2ff02ae264ba9c2" dependencies = [ - "prettyplease 0.2.16", + "prettyplease", "proc-macro2", - "prost-build 0.12.3", + "prost-build", "quote", "syn 2.0.52", ] @@ -6819,6 +6849,17 @@ dependencies = [ "tap", ] +[[package]] +name = "xattr" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +dependencies = [ + "libc", + "linux-raw-sys 0.4.13", + "rustix 0.38.31", +] + [[package]] name = "xxhash-rust" version = "0.8.10" @@ -6846,6 +6887,7 @@ version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" dependencies = [ + "byteorder", "zerocopy-derive", ] diff --git a/judger/.vscode/settings.json b/judger/.vscode/settings.json new file mode 100644 index 00000000..1e668822 --- /dev/null +++ b/judger/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "files.watcherExclude": { + "plugins/**": true + }, + "rust-analyzer.files.excludeDirs": [ + "plugins","nsjail-docker" + ], +} \ No newline at end of file diff --git a/judger/Cargo.toml b/judger/Cargo.toml index cd43421f..c0c9c46e 100644 --- a/judger/Cargo.toml +++ b/judger/Cargo.toml @@ -15,6 +15,11 @@ prost-types = { workspace = true } thiserror = "1.0.40" toml = { workspace = true } derive_builder = { workspace = true } +fuser = "0.14.0" +tar = "0.4.40" +grpc = { path = "../grpc" } +crossbeam = "0.8.4" +lazy_static = "1.4.0" [dependencies.rustix] version = "0.38.28" @@ -56,5 +61,3 @@ tempfile = "3.8.0" tower = "0.4.13" tower-layer = "0.3.2" -[build-dependencies] -tonic-build = "0.9.2" diff --git a/judger/build.rs b/judger/build.rs deleted file mode 100644 index d073ff69..00000000 --- a/judger/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() -> Result<(), Box> { - tonic_build::compile_protos("../proto/judger.proto")?; - Ok(()) -} diff --git a/judger/src/error.rs b/judger/src/error.rs new file mode 100644 index 00000000..71189f57 --- /dev/null +++ b/judger/src/error.rs @@ -0,0 +1,5 @@ +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("{0}")] + CgroupError(#[from] cgroups_rs::error::Error), +} diff --git a/judger/src/grpc.rs b/judger/src/grpc.rs deleted file mode 100644 index 41343041..00000000 --- a/judger/src/grpc.rs +++ /dev/null @@ -1,22 +0,0 @@ -pub mod prelude { - tonic::include_proto!("oj.judger"); -} - -use std::fmt::Display; - -impl Display for prelude::JudgerCode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let message = match self { - prelude::JudgerCode::Ac => "Accepted", - prelude::JudgerCode::Na => "Unknown", - prelude::JudgerCode::Wa => "Wrong Answer", - prelude::JudgerCode::Ce => "Compile Error", - prelude::JudgerCode::Re => "Runtime Error", - prelude::JudgerCode::Rf => "Restricted Function", - prelude::JudgerCode::Tle => "Time Limit Excess", - prelude::JudgerCode::Mle => "Memory Limit Excess", - prelude::JudgerCode::Ole => "Output Limit Excess", - }; - write!(f, "{}", message) - } -} diff --git a/judger/src/init/cgroup.rs b/judger/src/init/cgroup.rs deleted file mode 100644 index f5710219..00000000 --- a/judger/src/init/cgroup.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::{fs, os::unix::prelude::OsStrExt, path::Path}; - -use super::config::CONFIG; - -// Clean up cgroup -pub fn init() { - let config = CONFIG.get().unwrap(); - let root_cg = Path::new("/sys/fs/cgroup").join(config.runtime.root_cgroup.clone()); - if root_cg.exists() { - for sub_cgroup in root_cg.read_dir().unwrap().flatten() { - if let Ok(meta) = sub_cgroup.metadata() { - if meta.is_dir() { - let mut path = sub_cgroup.path(); - remove_nsjail(&mut path); - fs::remove_dir(path).unwrap(); - } - } - } - } -} - -pub fn remove_nsjail(path: &mut Path) { - log::debug!("Cleaning up cgroup in {}", path.to_string_lossy()); - if let Ok(rd) = path.read_dir() { - for nsjail in rd.flatten() { - if nsjail.file_name().as_bytes().starts_with(b"NSJAIL") { - fs::remove_dir(nsjail.path()).unwrap(); - } - } - } -} diff --git a/judger/src/init/check.rs b/judger/src/init/check.rs deleted file mode 100644 index ab5fe08f..00000000 --- a/judger/src/init/check.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::path::Path; - -use cgroups_rs::{hierarchies, Hierarchy, Subsystem}; - -use super::config::CONFIG; - -// Check if all required systems are met -// abort if necessary -pub fn init() { - let config = CONFIG.get().unwrap(); - - if config.nsjail.is_cgv1() { - let hier = hierarchies::V1::new(); - let subsystems = hier.subsystems(); - if subsystems - .iter() - .any(|sub| matches!(sub, Subsystem::CpuAcct(_))) - { - log::warn!("Subsystem CpuAcct(Cpu Accounting) is unavailable."); - }; - - if subsystems - .iter() - .any(|sub| matches!(sub, Subsystem::CpuSet(_))) - { - log::warn!("Subsystem CpuSet(Cpu Scheduling Per Core) is unavailable."); - }; - - if subsystems - .iter() - .any(|sub| matches!(sub, Subsystem::Cpu(_))) - { - log::warn!("Subsystem Cpu(Cpu Scheduling) is unavailable."); - }; - - if subsystems - .iter() - .any(|sub| matches!(sub, Subsystem::Mem(_))) - { - log::warn!("Subsystem Mem(Memory) is unavailable."); - }; - log::error!("cgroup v1 is not supported, it fail at cpu task"); - std::process::exit(1); - } else { - let hier = hierarchies::V2::new(); - let subsystems = hier.subsystems(); - if subsystems - .iter() - .any(|sub| matches!(sub, Subsystem::CpuSet(_))) - { - log::warn!("Subsystem CpuSet(Cpu Scheduling Per Core) is unavailable."); - }; - - if subsystems - .iter() - .any(|sub| matches!(sub, Subsystem::Cpu(_))) - { - log::warn!("Subsystem Cpu(Cpu Scheduling) is unavailable."); - }; - - if subsystems - .iter() - .any(|sub| matches!(sub, Subsystem::Mem(_))) - { - log::warn!("Subsystem Mem(Memory) is unavailable."); - }; - } - - if !config.nsjail.rootless { - let uid = rustix::process::getuid(); - if !uid.is_root() { - log::warn!("config.rootless is set to false, require root to run"); - } - } else { - let root_cg = Path::new("/sys/fs/cgroup").join(config.runtime.root_cgroup.clone()); - - match root_cg.metadata() { - Ok(meta) => { - if meta.permissions().readonly() { - log::warn!("config.rootless is set to true, but cgroup root is readonly, please either set config.rootless to false or make cgroup root writable"); - } - } - Err(x) => log::error!("Unable to find cgroup root, is it mounted? {}", x), - } - } - - if config.platform.output_limit >= config.platform.available_memory.try_into().unwrap() { - log::error!("config.platform.output_limit is too larget or config.platform.available_memory is too low"); - std::process::exit(1); - } - - if config.platform.output_limit * 8 >= config.platform.available_memory.try_into().unwrap() { - log::warn!("config.platform.output_limit is consider too high"); - } -} diff --git a/judger/src/init/config.rs b/judger/src/init/config.rs deleted file mode 100644 index 3a3ee4fe..00000000 --- a/judger/src/init/config.rs +++ /dev/null @@ -1,187 +0,0 @@ -use std::{path::PathBuf, str::FromStr}; - -use serde::{Deserialize, Serialize}; -use tokio::{fs, io::AsyncReadExt, sync::OnceCell}; - -pub static CONFIG: OnceCell = OnceCell::const_new(); - -static CONFIG_PATH: &str = "config/config.toml"; -static CONFIG_DIR: &str = "config"; - -// config -#[derive(Serialize, Deserialize, Debug)] -#[serde(deny_unknown_fields)] -pub struct GlobalConfig { - #[serde(default)] - pub runtime: Runtime, - #[serde(default)] - pub platform: Platform, - #[serde(default)] - pub nsjail: Nsjail, - #[serde(default)] - pub plugin: Plugin, - #[serde(default)] - pub kernel: Kernel, - #[serde(default)] - pub log_level: usize, - #[serde(default)] - pub secret: Option, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(deny_unknown_fields)] -pub struct Platform { - pub cpu_time_multiplier: f64, - pub available_memory: u64, - pub output_limit: usize, -} - -impl Default for Platform { - fn default() -> Self { - Self { - cpu_time_multiplier: 1.0, - available_memory: 1073741824, - output_limit: 32 * 1024 * 1024, - } - } -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(deny_unknown_fields)] -pub struct Nsjail { - pub runtime: String, - pub rootless: bool, - pub log: String, - pub cgroup_version: CgVersion, -} - -impl Nsjail { - pub fn is_cgv1(&self) -> bool { - self.cgroup_version == CgVersion::V1 - } -} - -impl Default for Nsjail { - fn default() -> Self { - Self { - runtime: "./nsjail-3.1".to_owned(), - rootless: false, - log: "/dev/null".to_owned(), - cgroup_version: CgVersion::V2, - } - } -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub enum CgVersion { - V1, - V2, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(deny_unknown_fields)] -pub struct Plugin { - pub path: String, -} - -impl Default for Plugin { - fn default() -> Self { - Self { - path: "plugins".to_owned(), - } - } -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(deny_unknown_fields)] -pub struct Runtime { - pub temp: PathBuf, - pub bind: String, - pub accuracy: u64, - pub root_cgroup: String, -} - -impl Default for Runtime { - fn default() -> Self { - Self { - temp: PathBuf::from_str(".temp").unwrap(), - bind: "0.0.0.0:8080".to_owned(), - accuracy: 50 * 1000, - root_cgroup: "mdoj/c.".to_owned(), - } - } -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(deny_unknown_fields)] -pub struct Kernel { - #[serde(alias = "USER_HZ")] - pub kernel_hz: i32, - pub tickless: bool, -} - -impl Default for Kernel { - fn default() -> Self { - Self { - kernel_hz: 1000, - tickless: false, - } - } -} - -pub async fn init() { - let mut buf = Vec::new(); - - let config_file = fs::File::open(CONFIG_PATH).await; - - match CONFIG.get() { - Some(_) => { - #[cfg(not(test))] - panic!("config have been set twice, which indicated a bug in the program"); - } - None => { - let config: GlobalConfig = match config_file { - Ok(mut x) => { - if x.metadata().await.unwrap().is_file() { - x.read_to_end(&mut buf).await.unwrap(); - let config = std::str::from_utf8(&buf) - .expect("Unable to parse config, Check config is correct"); - toml::from_str(config).unwrap() - } else { - panic!( - "Unable to open config file, {} should not be symlink or folder", - CONFIG_PATH - ); - } - } - Err(_) => { - println!("Unable to find {}, generating default config", CONFIG_PATH); - - fs::create_dir_all(CONFIG_DIR).await.unwrap(); - - let config: GlobalConfig = toml::from_str("").unwrap(); - - let config_txt = toml::to_string(&config).unwrap(); - fs::write(CONFIG_PATH, config_txt).await.unwrap(); - - println!("Finished, exiting..."); - std::process::exit(0); - } - }; - - CONFIG.set(config).ok(); - } - } -} - -#[cfg(test)] -mod test { - use super::{init, CONFIG}; - - #[tokio::test] - async fn default() { - init().await; - assert!(CONFIG.get().is_some()); - } -} diff --git a/judger/src/init/logger.rs b/judger/src/init/logger.rs deleted file mode 100644 index 43b41825..00000000 --- a/judger/src/init/logger.rs +++ /dev/null @@ -1,33 +0,0 @@ -use super::config::CONFIG; -use log::LevelFilter; - -// setup logger and panic handler -pub fn init() { - let config = CONFIG.get().unwrap(); - - let level = match config.log_level { - #[cfg(debug_assertions)] - 0 => LevelFilter::Trace, - #[cfg(not(debug_assertions))] - 0 => LevelFilter::Debug, - 1 => LevelFilter::Debug, - 2 => LevelFilter::Info, - 3 => LevelFilter::Warn, - 4 => LevelFilter::Error, - _ => LevelFilter::Info, - }; - env_logger::Builder::new() - .filter_module("judger", level) - .try_init() - .ok(); - // make panic propagate across thread to ensure safety - let default_panic = std::panic::take_hook(); - std::panic::set_hook(Box::new(move |info| { - default_panic(info); - log::error!( - "Panic at {}", - info.location().map(|x| x.to_string()).unwrap_or_default() - ); - std::process::exit(1); - })); -} diff --git a/judger/src/init/mod.rs b/judger/src/init/mod.rs deleted file mode 100644 index 176c8c04..00000000 --- a/judger/src/init/mod.rs +++ /dev/null @@ -1,23 +0,0 @@ -use thiserror::Error; - -pub mod cgroup; -pub mod check; -pub mod config; -pub mod logger; -pub mod volumn; - -pub async fn new() { - config::init().await; - logger::init(); - cgroup::init(); - check::init(); - volumn::init().await; -} - -#[derive(Error, Debug)] -pub enum Error { - #[error("unmeet system requirements")] - SystemIncapable, - #[error("Fail to load Langs `{0}`")] - Langs(#[from] crate::langs::InitError), -} diff --git a/judger/src/init/volumn.rs b/judger/src/init/volumn.rs deleted file mode 100644 index d4525bdc..00000000 --- a/judger/src/init/volumn.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::path::Path; - -use tokio::fs; - -use super::config::CONFIG; - -pub async fn init() { - let config = CONFIG.get().unwrap(); - let path: &Path = config.runtime.temp.as_ref(); - fs::create_dir_all(path).await.unwrap(); -} diff --git a/judger/src/langs/artifact.rs b/judger/src/langs/artifact.rs deleted file mode 100644 index 5a79f335..00000000 --- a/judger/src/langs/artifact.rs +++ /dev/null @@ -1,363 +0,0 @@ -use std::{collections::BTreeMap, path::Path}; - -use tokio::fs; -use uuid::Uuid; - -use crate::grpc::prelude::*; -use crate::init::config::CONFIG; -use crate::sandbox::prelude::*; - -use super::InitError; -use super::{spec::LangSpec, Error}; - -// Artifact factory, load module from disk to compile code -// Rely on container daemon to create container -pub struct ArtifactFactory { - runtime: ContainerDaemon, - langs: BTreeMap, -} - -impl ArtifactFactory { - // load all modules from a directory - // - // definition of module: - // 1. a directory with a spec.toml file inside - // 2. resides in `path`(default to "plugins") directory - pub async fn load_dir(&mut self, path: impl AsRef) { - let mut rd_dir = fs::read_dir(path).await.unwrap(); - while let Some(dir) = rd_dir.next_entry().await.unwrap() { - let meta = dir.metadata().await.unwrap(); - if meta.is_dir() { - if let Err(err) = self.load_module(&dir.path()).await { - log::error!( - "Error loading module from {}, {}", - dir.path().to_string_lossy(), - err - ); - }; - } - } - for (uid, module) in self.langs.iter() { - log::info!("Module {} for {}(*.{})", uid, module.name, module.extension); - } - } - // adaptor, load a module from spec.toml - pub async fn load_module(&mut self, spec: impl AsRef) -> Result<(), InitError> { - let spec = LangSpec::from_file(spec).await?; - - assert!(self.langs.insert(spec.uid, spec).is_none()); - - Ok(()) - } - // list all modules - pub fn list_module(&self) -> Vec { - self.langs - .values() - .map(|spec| LangInfo { - lang_uid: spec.uid.clone().to_string(), - lang_name: spec.name.clone(), - info: spec.info.clone(), - lang_ext: spec.extension.clone(), - }) - .collect() - } - // compile code with sepcfication and container deamon - pub async fn compile(&self, uid: &Uuid, code: &[u8]) -> Result { - log::trace!("Compiling program with module {}", uid,); - - let spec = self.langs.get(uid).ok_or(Error::LangNotFound)?; - - let container = self.runtime.create(&spec.path).await.unwrap(); - - let mut process = container - .execute( - &spec - .compile_args - .iter() - .map(|x| x.as_str()) - .collect::>(), - spec.compile_limit.clone().apply_platform(), - ) - .await?; - - process.write_all(code).await?; - - let process = process.wait().await?; - - // if !process.succeed() { - // #[cfg(debug_assertions)] - // log::warn!("{}", process.status); - // return Ok(CompiledArtifact{ - // process, - // spec, - // container:None - // }); - // } - - Ok(CompiledArtifact { - process, - spec, - container, - }) - } -} - -impl Default for ArtifactFactory { - fn default() -> Self { - let config = CONFIG.get().unwrap(); - Self { - runtime: ContainerDaemon::new(config.runtime.temp.clone()), - langs: Default::default(), - } - } -} - -/// Log generate from language plugin -pub struct CompileLog { - pub level: usize, - pub message: String, -} - -impl CompileLog { - /// parse log from raw string, slient error(generate blank message) when malformatted - /// - /// according to plugin specification, log should be in following format - /// - /// ```text - /// 0:trace message - /// 1:debug message - /// 2:info message - /// 3:warn message - /// 4:error message - /// ```` - pub fn from_raw(raw: &[u8]) -> Self { - let raw: Vec<&[u8]> = raw.splitn(2, |x| *x == b':').collect(); - Self { - level: String::from_utf8_lossy(raw[0]).parse().unwrap_or(4), - message: String::from_utf8_lossy(raw[1]).to_string(), - } - } - /// log it to the console - pub fn log(&self) { - match self.level { - 0 => log::trace!("{}", self.message), - 1 => log::debug!("{}", self.message), - 2 => log::info!("{}", self.message), - 3 => log::warn!("{}", self.message), - 4 => log::error!("{}", self.message), - _ => {} - } - } -} - -/// Wrapper for container which contain compiled program in its volume -/// -/// TODO: CompiledInner<'a> was actually derive from ExitProc, consider remove CompiledInner<'a> -/// and replace it with ExitProc -pub struct CompiledArtifact<'a> { - process: ExitProc, - container: Container<'a>, - spec: &'a LangSpec, -} - -impl<'a> CompiledArtifact<'a> { - /// get JudgerCode if the task is surely at state neither AC or WA - pub fn get_expection(&self) -> Option { - if !self.process.succeed() { - Some(JudgerCode::Ce) - } else { - None - } - } - pub fn log(&'a self) -> Box + 'a + Send> { - Box::new( - self.process - .stdout - .split(|x| *x == b'\n') - .filter_map(|x| match x.is_empty() { - true => None, - false => Some(CompileLog::from_raw(x)), - }), - ) - } -} - -impl<'a> CompiledArtifact<'a> { - // run compiled program with input and limit - pub async fn judge( - &mut self, - input: &[u8], - time: u64, - memory: u64, - ) -> Result { - log::debug!("Exit status: {}", self.process.status); - debug_assert!(self.process.succeed()); - let spec = self.spec; - let mut limit = spec.judge_limit.clone().apply_platform(); - - limit.cpu_us *= time; - limit.user_mem *= memory; - - let mut process = self - .container - .execute( - &spec - .judge_args - .iter() - .map(|x| x.as_str()) - .collect::>(), - limit, - ) - .await?; - - process.write_all(input).await.ok(); - - let process = process.wait().await?; - - // TODO: We should handle SysError here - if !process.succeed() { - log::debug!("process status: {:?}", process.status); - return Ok(TaskResult::Fail(JudgerCode::Re)); - } - - Ok(TaskResult::Success(process)) - } - pub async fn exec( - &mut self, - input: &[u8], - time: u64, - memory: u64, - ) -> Result { - log::debug!("Exit status: {}", self.process.status); - debug_assert!(self.process.succeed()); - let spec = self.spec; - let mut limit = spec.judge_limit.clone().apply_platform(); - - limit.cpu_us *= time; - limit.user_mem *= memory; - - let mut process = self - .container - .execute( - &spec - .judge_args - .iter() - .map(|x| x.as_str()) - .collect::>(), - limit, - ) - .await?; - - process.write_all(input).await?; - - let process = process.wait().await?; - - Ok(ExecResult(process)) - } -} - -/// Wrapper for result of process(ended exec process) -/// -/// provide information about process's exitcode, stdout, stderr -pub struct ExecResult(ExitProc); - -impl ExecResult { - pub fn time(&self) -> &CpuStatistics { - &self.0.cpu - } - pub fn mem(&self) -> &MemStatistics { - &self.0.mem - } - pub fn stdout(&self) -> &[u8] { - &self.0.stdout - } -} -/// Wrapper for result of process(ended judge process) -/// -/// provide abliity to report resource usage, exit status, AC or WA -pub enum TaskResult { - Fail(JudgerCode), - Success(ExitProc), -} - -impl TaskResult { - fn process_mut(&mut self) -> Option<&mut ExitProc> { - match self { - TaskResult::Fail(_) => None, - TaskResult::Success(x) => Some(x), - } - } - pub fn process(&self) -> Option<&ExitProc> { - match self { - TaskResult::Fail(_) => None, - TaskResult::Success(x) => Some(x), - } - } - /// get JudgerCode if the task is surely at state neither AC or WA - pub fn get_expection(&mut self) -> Option { - match self { - TaskResult::Fail(x) => Some(*x), - TaskResult::Success(process) => match process.status { - ExitStatus::SigExit(sig) => match sig { - 11 => Some(JudgerCode::Re), - _ => Some(JudgerCode::Rf), - }, - ExitStatus::Code(code) => match code { - 125 => Some(JudgerCode::Mle), - 126 | 127 | 129..=192 => Some(JudgerCode::Rf), - 255 | 0..=124 => None, - _ => Some(JudgerCode::Na), - }, - ExitStatus::MemExhausted => Some(JudgerCode::Mle), - ExitStatus::CpuExhausted => Some(JudgerCode::Tle), - ExitStatus::SysError => Some(JudgerCode::Na), - }, - } - } - // determine whether the output(stdout) match - pub fn assert(&mut self, input: &[u8], mode: JudgeMatchRule) -> bool { - let newline = b'\n'; - let space = b' '; - let stdout = &self.process_mut().unwrap().stdout; - - match mode { - JudgeMatchRule::ExactSame => stdout.iter().zip(input.iter()).all(|(f, s)| f == s), - JudgeMatchRule::IgnoreSnl => { - let stdout_split = stdout.split(|x| *x == newline || *x == space); - let input_split = input.split(|x| *x == newline || *x == space); - for (f, s) in stdout_split.zip(input_split) { - if f.iter().zip(s.iter()).any(|(f, s)| f != s) { - return false; - } - } - true - } - JudgeMatchRule::SkipSnl => { - let stdout_filtered = stdout.iter().filter(|x| **x != newline || **x != space); - let input_filtered = input.iter().filter(|x| **x != newline || **x != space); - - stdout_filtered.zip(input_filtered).all(|(f, s)| f == s) - } - } - } - /// get cpu statistics - pub fn cpu(&self) -> &CpuStatistics { - &self.process().unwrap().cpu - } - /// get memory statistics - pub fn mem(&self) -> &MemStatistics { - &self.process().unwrap().mem - } -} - -impl Limit { - fn apply_platform(mut self) -> Self { - let config = CONFIG.get().unwrap(); - - self.cpu_us = ((self.cpu_us as f64) * config.platform.cpu_time_multiplier) as u64; - self.rt_us = ((self.rt_us as f64) * config.platform.cpu_time_multiplier) as u64; - self.total_us = ((self.total_us as f64) * config.platform.cpu_time_multiplier) as u64; - - self - } -} diff --git a/judger/src/langs/mod.rs b/judger/src/langs/mod.rs deleted file mode 100644 index 4bfaf2cf..00000000 --- a/judger/src/langs/mod.rs +++ /dev/null @@ -1,55 +0,0 @@ -use thiserror::Error; - -use crate::sandbox; - -pub mod artifact; -pub mod spec; - -pub mod prelude { - pub use super::artifact::*; -} - -// Error incur from server setup -#[derive(Error, Debug)] -pub enum InitError { - #[error("`{0}`")] - Serde(#[from] toml::de::Error), - #[error("Language exstension \"spec.toml\" malformated")] - FileMalFormat, - #[error("Language \"spec.toml\" does not exist")] - FileNotExist, - #[error("`{0}`")] - Sandbox(#[from] sandbox::Error), -} - -#[derive(Error, Debug)] -pub enum Error { - #[error("Language not found")] - LangNotFound, - #[error("`{0}`")] - Sandbox(#[from] sandbox::Error), -} - -impl From for tonic::Status { - fn from(value: Error) -> Self { - match value { - Error::LangNotFound => tonic::Status::failed_precondition("lang not found"), - _ => tonic::Status::internal(value.to_string()), - } - } -} - -// impl From for Error { -// fn from(value: sandbox::Error) -> Self { -// match value { -// sandbox::Error::ImpossibleResource -// | sandbox::Error::Stall -// | sandbox::Error::CapturedPipe => Error::Report(JudgerCode::Re), -// sandbox::Error::IO(_) -// | sandbox::Error::ControlGroup(_) -// | sandbox::Error::Libc(_) -// | sandbox::Error::CGroup => Error::Internal(InternalError::JailError(value)), -// sandbox::Error::BufferFull => Error::Report(JudgerCode::Ole), -// } -// } -// } diff --git a/judger/src/langs/spec.rs b/judger/src/langs/spec.rs deleted file mode 100644 index a14b621c..00000000 --- a/judger/src/langs/spec.rs +++ /dev/null @@ -1,102 +0,0 @@ -use std::path::{Path, PathBuf}; - -use serde::{Deserialize, Serialize}; -use tokio::{fs, io::AsyncReadExt}; -use uuid::Uuid; - -use crate::sandbox::Limit; - -use super::InitError; - -/// Language specification -pub struct LangSpec { - pub info: String, - pub extension: String, - pub uid: Uuid, // TODO - pub name: String, - pub compile_args: Vec, - pub compile_limit: Limit, - pub judge_args: Vec, - pub judge_limit: Limit, - pub path: PathBuf, -} - -impl LangSpec { - // load a module from spec.toml - pub async fn from_file(path: impl AsRef) -> Result { - log::trace!("Loading module from {}", path.as_ref().to_string_lossy()); - - let mut buf = Vec::new(); - let mut spec = fs::File::open(path.as_ref().join("spec.toml")) - .await - .map_err(|_| InitError::FileNotExist)?; - spec.read_to_end(&mut buf).await.unwrap(); - - let spec = std::str::from_utf8(&buf).unwrap(); - - let spec: RawLangSpec = toml::from_str(spec).map_err(|_| InitError::FileMalFormat)?; - - let compile_limit = Limit { - lockdown: spec.compile.lockdown, - cpu_us: spec.compile.cpu_time, - rt_us: spec.compile.rt_time, - total_us: spec.compile.total_time, - user_mem: spec.compile.user_mem, - kernel_mem: spec.compile.kernel_mem, - swap_user: 0, - }; - - let judge_limit = Limit { - lockdown: true, - cpu_us: spec.judge.multiplier_cpu, - rt_us: spec.judge.rt_time, - total_us: 3600 * 1000 * 1000 * 1000, - user_mem: spec.judge.multiplier_memory, - kernel_mem: spec.judge.kernel_mem, - swap_user: 0, - }; - - Ok(Self { - path: path.as_ref().join("rootfs").clone(), - info: spec.info, - extension: spec.extension, - uid: spec.uid, - name: spec.name, - compile_args: spec.compile.command, - compile_limit, - judge_args: spec.judge.command, - judge_limit, - }) - } -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(deny_unknown_fields)] -pub struct RawLangSpec { - info: String, - extension: String, - uid: Uuid, - name: String, - compile: Compile, - judge: Judge, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -struct Compile { - lockdown: bool, - pub command: Vec, - pub kernel_mem: u64, - pub user_mem: u64, - pub rt_time: u64, - pub cpu_time: u64, - pub total_time: u64, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Judge { - pub command: Vec, - pub kernel_mem: u64, - pub multiplier_memory: u64, - pub rt_time: u64, - pub multiplier_cpu: u64, -} diff --git a/judger/src/main.rs b/judger/src/main.rs index 17fc3614..2da4966a 100644 --- a/judger/src/main.rs +++ b/judger/src/main.rs @@ -1,30 +1,4 @@ -use std::sync::Arc; - -use grpc::prelude::judger_server::JudgerServer; -use init::config::CONFIG; - -pub mod grpc; -pub mod init; -pub mod langs; +pub mod error; pub mod sandbox; -pub mod server; -#[cfg(test)] -pub mod tests; - -#[tokio::main] -async fn main() { - init::new().await; - - let config = CONFIG.get().unwrap(); - let addr = config.runtime.bind.parse().unwrap(); - - log::info!("Server started"); - - let server = server::Server::new().await; - tonic::transport::Server::builder() - .add_service(JudgerServer::new(Arc::new(server))) - .serve(addr) - .await - .unwrap(); -} +fn main() {} diff --git a/judger/src/sandbox/container.rs b/judger/src/sandbox/container.rs deleted file mode 100644 index 2cf003d6..00000000 --- a/judger/src/sandbox/container.rs +++ /dev/null @@ -1,76 +0,0 @@ -use std::{ - path::PathBuf, - sync::atomic::{AtomicUsize, Ordering}, -}; - -use tokio::fs; - -use crate::{ - init::config::CONFIG, - sandbox::utils::{limiter::Limiter, nsjail::NsJail}, -}; - -use super::{daemon::ContainerDaemon, process::RunningProc, Error, Limit}; - -/// cgroup counter -/// -/// The first container would be mount at /sys/fs/cgroup/mdoj/0 -static CG_COUNTER: AtomicUsize = AtomicUsize::new(0); - -// Container abstraction, call nsjail to execute process, limiter to limit resources -// expect downstream(daemon) setup up and clear tmp files -pub struct Container<'a> { - pub(super) id: String, - pub(super) daemon: &'a ContainerDaemon, - pub(super) root: PathBuf, -} - -impl<'a> Drop for Container<'a> { - fn drop(&mut self) { - let tmp_path = self.daemon.tmp.as_path().join(self.id.clone()); - log::trace!("Cleaning up container with id :{}", self.id); - tokio::spawn(async { fs::remove_dir_all(tmp_path).await }); - } -} - -impl<'a> Container<'a> { - /// execute command inisde container - pub async fn execute(&self, args: &[&str], limit: Limit) -> Result { - let config = CONFIG.get().unwrap(); - - log::trace!("Preparing container with id :{} for new process", self.id); - - let cg_name = format!( - "{}{}", - config.runtime.root_cgroup, - CG_COUNTER.fetch_add(1, Ordering::Acquire) - ); - - let reversed_memory = limit.user_mem + limit.kernel_mem; - let output_limit = config.platform.output_limit as u64; - - let memory_holder = self - .daemon - .memory_counter - .allocate(output_limit + reversed_memory) - .await?; - - let nsjail = NsJail::builder(&self.root) - .cgroup(&cg_name) - .done() - .presist_vol(&self.id) - .mount("src", false) - .done() - .common() - .cmds(args) - .build()?; - - let limiter = Limiter::new(&cg_name, limit)?; - - Ok(RunningProc { - limiter, - nsjail, - _memory_holder: memory_holder, - }) - } -} diff --git a/judger/src/sandbox/daemon.rs b/judger/src/sandbox/daemon.rs deleted file mode 100644 index c53ce2af..00000000 --- a/judger/src/sandbox/daemon.rs +++ /dev/null @@ -1,54 +0,0 @@ -use std::{ - path::{Path, PathBuf}, - sync::atomic::{AtomicU64, Ordering}, -}; - -use tokio::fs; - -use crate::init::config::CONFIG; - -use super::{container::Container, utils::semaphore::MemorySemaphore, Error}; - -// Container daemon, manage container creation and deletion -// setup and clean tmp files, reverse memory through semaphore -pub struct ContainerDaemon { - id_counter: AtomicU64, - pub(super) memory_counter: MemorySemaphore, - pub(super) tmp: PathBuf, -} - -impl ContainerDaemon { - /// create container daemon - pub fn new(tmp: impl AsRef) -> Self { - let config = CONFIG.get().unwrap(); - Self { - id_counter: Default::default(), - memory_counter: MemorySemaphore::new(config.platform.available_memory), - tmp: tmp.as_ref().to_path_buf(), - } - } - /// create container daemon with id, useful in parallel test - pub fn new_with_id(tmp: impl AsRef, id: u64) -> Self { - let mut self_ = Self::new(tmp); - *self_.id_counter.get_mut() = id; - self_ - } - /// create container controlled by daemon - /// - /// delete daemon require all container to end task first - pub async fn create(&self, root: impl AsRef) -> Result, Error> { - let id = self.id_counter.fetch_add(1, Ordering::Acquire).to_string(); - log::trace!("Creating new container: {}", id); - - let container_root = self.tmp.join(id.clone()); - - fs::create_dir(container_root.clone()).await?; - fs::create_dir(container_root.clone().join("src")).await?; - - Ok(Container { - id, - daemon: self, - root: root.as_ref().to_path_buf(), - }) - } -} diff --git a/judger/src/sandbox/limiter/hier.rs b/judger/src/sandbox/limiter/hier.rs new file mode 100644 index 00000000..601ad889 --- /dev/null +++ b/judger/src/sandbox/limiter/hier.rs @@ -0,0 +1,27 @@ +use cgroups_rs::*; +use std::time::Duration; + +pub const MONITOR_ACCURACY: Duration = Duration::from_millis(80); + +pub enum MonitorKind { + Cpu, + CpuAcct, +} + +lazy_static::lazy_static! { + pub static ref MONITER_KIND: MonitorKind = + match hierarchies::auto().v2(){ + true=>MonitorKind::Cpu, + false=>MonitorKind::CpuAcct + } + ; +} + +impl MonitorKind { + pub fn heir(&self) -> Box { + match self { + MonitorKind::Cpu => hierarchies::auto(), + MonitorKind::CpuAcct => Box::new(hierarchies::V1::new()), + } + } +} diff --git a/judger/src/sandbox/limiter/mod.rs b/judger/src/sandbox/limiter/mod.rs new file mode 100644 index 00000000..1d6c1839 --- /dev/null +++ b/judger/src/sandbox/limiter/mod.rs @@ -0,0 +1,98 @@ +pub(self) mod hier; +pub(self) mod stat; +pub(self) mod wrapper; + +pub use stat::*; + +use crate::error::Error; +use cgroups_rs::{cgroup_builder::CgroupBuilder, *}; +use hier::*; +use std::sync::Arc; +use tokio::time::*; + +pub enum ExitReason { + TimeOut, + MemoryOut, +} + +pub async fn monitor(cgroup: Arc, cpu: Cpu) -> ExitReason { + let wrapper = wrapper::CgroupWrapper::new(&cgroup); + + let oom_signal = wrapper.oom(); + + loop { + sleep(MONITOR_ACCURACY/2).await; + + if let Ok(oom_hint) = oom_signal.try_recv() { + log::trace!("oom hint: {}", oom_hint); + return ExitReason::MemoryOut; + } + + if Cpu::out_of_resources(&cpu, wrapper.cpu()) { + return ExitReason::TimeOut; + } + wrapper.cpu(); + } +} + +/// limiter that monitor the resource usage of a cgroup +pub struct Limiter { + cgroup: Arc, + monitor_task: Option>, +} + +impl Drop for Limiter { + fn drop(&mut self) { + if let Some(monitor_task) = &self.monitor_task { + monitor_task.abort(); + } + if MONITER_KIND.heir().v2() { + self.cgroup.kill().expect("cgroup.kill does not exist"); + } else { + // use rustix::process::*; + // pid should not be reused until SIGPIPE send(when Process is Drop) + // therefore, it is safe to try killing the process(only true for nsjail) + + // current implementation of v1 support do nothing, wait for action of cleaning + // up the process on drop + // for pid in self.cgroup.tasks() {} + } + } +} + +impl Limiter { + /// create a new limiter and mount at given path + pub fn new_mount(cg_path: &str, cpu: Cpu, mem: Memory) -> Result { + let cgroup = Arc::new( + CgroupBuilder::new(cg_path) + .memory() + .kernel_memory_limit(mem.kernel as i64) + .memory_hard_limit(mem.user as i64) + .memory_swap_limit(0) + .done() + .cpu() + .period((MONITOR_ACCURACY/2).as_nanos() as u64) + .quota(MONITOR_ACCURACY.as_nanos() as i64) + .realtime_period(MONITOR_ACCURACY.as_nanos() as u64) + .realtime_runtime(MONITOR_ACCURACY.as_nanos() as i64) + .done() + .build(MONITER_KIND.heir())?, + ); + + let monitor_task = Some(tokio::spawn(monitor(cgroup.clone(), cpu))); + + Ok(Self { + cgroup, + monitor_task, + }) + } + /// wait for resource to exhaust + pub async fn wait_exhaust(&mut self) -> ExitReason { + self.monitor_task.take().unwrap().await.unwrap() + } + /// get the current resource usage + pub async fn stat(self)->(Cpu,Memory){ + let wrapper = wrapper::CgroupWrapper::new(&self.cgroup); + (wrapper.cpu(),wrapper.memory()) + } +} diff --git a/judger/src/sandbox/limiter/stat.rs b/judger/src/sandbox/limiter/stat.rs new file mode 100644 index 00000000..bfc8793a --- /dev/null +++ b/judger/src/sandbox/limiter/stat.rs @@ -0,0 +1,17 @@ +pub struct Memory { + pub kernel: u64, + pub user: u64, + pub total: u64, +} + +pub struct Cpu { + pub kernel: u64, + pub user: u64, + pub total: u64, +} + +impl Cpu { + pub(super) fn out_of_resources(resource: &Self, stat: Self) -> bool { + stat.kernel > resource.kernel || stat.user > resource.user || stat.total > resource.total + } +} diff --git a/judger/src/sandbox/limiter/wrapper.rs b/judger/src/sandbox/limiter/wrapper.rs new file mode 100644 index 00000000..f5832cbd --- /dev/null +++ b/judger/src/sandbox/limiter/wrapper.rs @@ -0,0 +1,66 @@ +use cgroups_rs::{cpu::CpuController, cpuacct::CpuAcctController, memory::MemController, Cgroup}; + +use super::stat::*; + +/// newtype wrapper for cgroup form cgroup_rs +pub struct CgroupWrapper<'a> { + cgroup: &'a Cgroup, +} + +impl<'a> CgroupWrapper<'a> { + pub fn new(cgroup: &'a Cgroup) -> Self { + Self { cgroup } + } + pub fn cpu(&self) -> Cpu { + let mut kernel = u64::MAX; + let mut user = u64::MAX; + let mut total = u64::MAX; + + match self.cgroup.controller_of::() { + Some(controller) => { + let usage = controller.cpuacct(); + kernel = usage.usage_sys; + user = usage.usage_user; + total = usage.usage; + } + None => { + let controller: &CpuController = self.cgroup.controller_of().unwrap(); + + let raw: &str = &controller.cpu().stat; + + for (key, value) in raw.split('\n').filter_map(|stmt| stmt.split_once(' ')) { + match key { + "usage_usec" => total = value.parse().unwrap(), + "user_usec" => user = value.parse().unwrap(), + "system_usec" => kernel = value.parse().unwrap(), + _ => {} + }; + } + } + } + + Cpu { + kernel, + user, + total, + } + } + pub fn oom(&self) -> std::sync::mpsc::Receiver { + let controller = self.cgroup.controller_of::().unwrap(); + controller.register_oom_event("mdoj-oom-handler").unwrap() + } + pub fn memory(&self) -> Memory { + let controller = self.cgroup.controller_of::().unwrap(); + let kusage = controller.kmem_stat(); + + let kernel = kusage.max_usage_in_bytes as u64; + let user = controller.memory_stat().max_usage_in_bytes; + let total = kernel + user; + + Memory { + kernel, + user, + total, + } + } +} diff --git a/judger/src/sandbox/mod.rs b/judger/src/sandbox/mod.rs index bac99a13..7873fab4 100644 --- a/judger/src/sandbox/mod.rs +++ b/judger/src/sandbox/mod.rs @@ -1,50 +1,3 @@ -pub(super) mod container; -pub(super) mod daemon; -pub(super) mod process; -pub(super) mod utils; - -use thiserror::Error; - -pub mod prelude { - pub use super::container::Container; - pub use super::daemon::ContainerDaemon; - pub use super::process::{ExitProc, ExitStatus, RunningProc}; - pub use super::utils::limiter::cpu::CpuStatistics; - pub use super::utils::limiter::mem::MemStatistics; - pub use super::utils::semaphore::{MemoryPermit, MemorySemaphore, MemoryStatistic}; - pub use super::Error; - pub use super::Limit; -} - -#[derive(Error, Debug)] -pub enum Error { - #[error("Impossible to run the task given the provided resource preservation policy")] - ImpossibleResource, - #[error("Resource provided, but the process refused to continue")] - Stall, - #[error("The pipe has been capture")] - CapturedPipe, - #[error("IO error: `{0}`")] - IO(#[from] std::io::Error), - #[error("`{0}`")] - ControlGroup(#[from] cgroups_rs::error::Error), - #[error("Error from system call `{0}`")] - Libc(u32), - #[error("Fail calling cgroup, check subsystem and hier support")] - CGroup, - #[error("Read buffer is full before meeting EOF")] - BufferFull, -} - -// const NICE: i32 = 18; - -#[derive(Debug, Clone)] -pub struct Limit { - pub lockdown: bool, - pub cpu_us: u64, - pub rt_us: u64, - pub total_us: u64, - pub user_mem: u64, - pub kernel_mem: u64, - pub swap_user: u64, -} +pub mod limiter; +pub mod semaphore; +pub mod resource; diff --git a/judger/src/sandbox/process.rs b/judger/src/sandbox/process.rs deleted file mode 100644 index 7413132e..00000000 --- a/judger/src/sandbox/process.rs +++ /dev/null @@ -1,150 +0,0 @@ -use std::fmt::Display; - -use tokio::{ - io::{AsyncReadExt, AsyncWriteExt}, - select, time, -}; - -use crate::{init::config::CONFIG, sandbox::utils::limiter::LimitReason}; - -use super::{ - utils::{ - limiter::{cpu::CpuStatistics, mem::MemStatistics, Limiter}, - nsjail::{NsJail, TermStatus}, - semaphore::MemoryPermit, - }, - Error, -}; - -impl From for ExitStatus { - fn from(value: LimitReason) -> Self { - match value { - LimitReason::Cpu => ExitStatus::CpuExhausted, - LimitReason::Mem => ExitStatus::MemExhausted, - LimitReason::SysMem => ExitStatus::SysError, - } - } -} - -impl From for ExitStatus { - fn from(value: TermStatus) -> Self { - match value { - TermStatus::SigExit(x) => ExitStatus::SigExit(x), - TermStatus::Code(x) => ExitStatus::Code(x), - } - } -} - -// an abstraction of running process, no meaningful logic implemented -pub struct RunningProc { - pub(super) limiter: Limiter, - pub(super) nsjail: NsJail, - pub(super) _memory_holder: MemoryPermit, -} - -impl RunningProc { - /// attempt to write entire buffer into process inside container - pub async fn write_all(&mut self, buf: &[u8]) -> Result<(), Error> { - let mut child = self.nsjail.process.as_ref().unwrap().lock().await; - let stdin = child.stdin.as_mut().ok_or(Error::CapturedPipe)?; - - // if the process fclose(stdin), we do slient error - if let Err(err) = stdin.write_all(buf).await { - #[cfg(debug_assertions)] - log::trace!("cannot write process's stdin:{}", err); - } - stdin.shutdown().await.ok(); - - Ok(()) - } - /// wait until the container exit(with any reason) - /// - /// reason of exit: process exit, kill by signal, kill by limiter, process stall - pub async fn wait(mut self) -> Result { - let config = CONFIG.get().unwrap(); - - let mut status: ExitStatus = select! { - reason = self.limiter.wait_exhausted()=>reason.unwrap().into(), - code = self.nsjail.wait()=> code.into(), - _ = time::sleep(time::Duration::from_secs(300))=>{ - // it refuse to continue(keep parking itself, dead ticket lock..ext) - return Err(Error::Stall); - } - }; - // because in the senario of out of memory, process will be either exit with code - // 11(unable to allocate memory) or kill by signal, whichever comes first, - // so we need to check if it is oom - if self.limiter.check_oom() { - status = ExitStatus::MemExhausted; - } - - let mut child = self.nsjail.process.as_ref().unwrap().lock().await; - let mut stdout = child - .stdout - .as_mut() - .ok_or(Error::CapturedPipe)? - .take((config.platform.output_limit) as u64); - - let mut buf = Vec::with_capacity(256); - - stdout.read_to_end(&mut buf).await.unwrap(); - - if stdout.into_inner().read_u8().await.is_ok() { - return Err(Error::BufferFull); - } - - let (cpu, mem) = self.limiter.statistics().await; - let output_limit = config.platform.output_limit as u64; - - let _memory_holder = self._memory_holder.downgrade(output_limit); - Ok(ExitProc { - status, - stdout: buf.to_vec(), - cpu, - mem, - _memory_holder, - }) - } -} - -/// a exited process -pub struct ExitProc { - pub status: ExitStatus, - pub stdout: Vec, - pub cpu: CpuStatistics, - pub mem: MemStatistics, - _memory_holder: MemoryPermit, -} - -impl ExitProc { - /// determine whether a process exit successfully - pub fn succeed(&self) -> bool { - match self.status { - // people tend to forget writing `return 0`, so we treat 255 as vaild - ExitStatus::Code(x) => x == 0 || x == 255, - _ => false, - } - } -} - -/// exit reason -#[derive(Debug, PartialEq, PartialOrd, Ord, Eq)] -pub enum ExitStatus { - SigExit(i32), // RuntimeError - Code(i32), - MemExhausted, - CpuExhausted, - SysError, -} - -impl Display for ExitStatus { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ExitStatus::SigExit(x) => write!(f, "Killed by signal {}", x), - ExitStatus::Code(x) => write!(f, "Exit with code {}", x), - ExitStatus::MemExhausted => write!(f, "Reach memory limit"), - ExitStatus::CpuExhausted => write!(f, "Reach cpu quota"), - ExitStatus::SysError => write!(f, "Unknown system error"), - } - } -} diff --git a/judger/src/sandbox/resource.rs b/judger/src/sandbox/resource.rs new file mode 100644 index 00000000..e69de29b diff --git a/judger/src/sandbox/semaphore.rs b/judger/src/sandbox/semaphore.rs new file mode 100644 index 00000000..854a50a7 --- /dev/null +++ b/judger/src/sandbox/semaphore.rs @@ -0,0 +1,137 @@ +use std::sync::{ + atomic::{self, Ordering}, + Arc, +}; + +use spin::Mutex; +use tokio::sync::oneshot::*; + +struct SemaphoreInner { + permits: atomic::AtomicUsize, + all_permits: usize, + max_wait: usize, + waiters: Mutex>)>>, +} + +#[derive(Clone)] +struct Semaphore(Arc); + +impl Semaphore { + /// Create a new asynchronous semaphore with the given number of permits. + /// + /// asynchronous semaphore is a synchronization primitive that limits the number of concurrent, + /// instead of blocking the thread, yeild to scheduler and wait for the permit. + /// + /// Note that there is no preemption. + pub fn new(all_permits: usize, max_wait: usize) -> Self { + Semaphore(Arc::new(SemaphoreInner { + permits: atomic::AtomicUsize::new(all_permits), + all_permits, + max_wait, + waiters: Mutex::new(Vec::new()), + })) + } + /// get a permit from semaphore + /// + /// It return None if + /// 1. It's impossible to get the permit even no other task is holding the permit + /// 2. The number of waiting task is greater than max_wait + pub async fn get_permit(&self, permit: usize) -> Option { + // FIXME: return Result to differentiate between max_wait_reached and impossible_resource_condition + if permit > self.0.all_permits { + return None; + } + let (tx, rx) = channel::<()>(); + { + let mut waiter = self.0.waiters.lock(); + if waiter.len() >= self.0.max_wait { + return None; + } + waiter.push((permit, Some(tx))); + } + + self.try_wake(); + + rx.await.ok()?; + + Some(Permit { + semaphore: self.clone(), + permit, + }) + } + fn release(&self, permit: usize) { + self.0.permits.fetch_add(permit, Ordering::Relaxed); + self.try_wake(); + } + fn try_wake(&self) { + let mut waiter = self.0.waiters.lock(); + if let Some((permit, ref mut waker)) = waiter.last_mut() { + let mut current = self.0.permits.load(Ordering::Acquire); + loop { + if current < *permit { + return; + } + if let Err(x) = self.0.permits.compare_exchange( + current, + current - *permit, + Ordering::SeqCst, + Ordering::Acquire, + ) { + current = x; + } else { + break; + }; + } + if waker.take().unwrap().send(()).is_err() { + log::warn!("Semaphore waiter disconnected"); + } + waiter.pop(); + } + } +} + +pub struct Permit { + semaphore: Semaphore, + permit: usize, +} + +impl Drop for Permit { + fn drop(&mut self) { + self.semaphore.release(self.permit); + } +} + +#[cfg(test)] +mod test { + use super::Semaphore; + #[tokio::test] + /// test max value of permit + async fn get_permit_max() { + let semaphore = Semaphore::new(1024, 1024); + assert!(semaphore.get_permit(1024).await.is_some()); + assert!(semaphore.get_permit(1025).await.is_none()); + } + #[tokio::test] + // test getting permit with ordering + async fn get_permit_unorder() { + let semaphore = Semaphore::new(1024, 1024); + let permit = semaphore.get_permit(1).await.unwrap(); + let permit1 = tokio::spawn(async move { + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + semaphore.get_permit(1024).await + }); + drop(permit); + assert!(permit1.await.unwrap().is_some()); + } + #[tokio::test] + // test `get_permit` return None when max_wait is reached + async fn get_permit_max_wait() { + let semaphore = Semaphore::new(1024, 1); + let semaphore1 = semaphore.clone(); + let _ = semaphore.get_permit(1).await.unwrap(); + let _ = tokio::spawn(async move { + semaphore.get_permit(1024).await.unwrap(); + }); + dbg!(semaphore1.get_permit(1).await.is_none()); + } +} diff --git a/judger/src/sandbox/utils/limiter/cpu.rs b/judger/src/sandbox/utils/limiter/cpu.rs deleted file mode 100644 index 66eb5e28..00000000 --- a/judger/src/sandbox/utils/limiter/cpu.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::fmt::Display; - -use cgroups_rs::{cpu::CpuController, cpuacct::CpuAcctController, Cgroup}; - -use crate::init::config::CONFIG; - -#[derive(Default, Clone, Debug)] -pub struct CpuStatistics { - pub rt_us: u64, - pub cpu_us: u64, - pub total_us: u64, -} - -impl Display for CpuStatistics { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "realtime:{} ,user: {} , total: {}", - self.rt_us, self.cpu_us, self.total_us - ) - } -} - -impl CpuStatistics { - // generate CPU statistics from cgroup - pub fn from_cgroup(cgroup: &Cgroup) -> Self { - let config = CONFIG.get().unwrap(); - if config.nsjail.is_cgv1() { - let ctrl = cgroup.controller_of().unwrap(); - Self::from_cpuacct_controller(ctrl) - } else { - let ctrl = cgroup.controller_of().unwrap(); - Self::from_cpu_controller(ctrl) - } - } - // generate CPU statistics from cpuacct controller - // which is more accurate, but not supported by cgroup v2 - pub fn from_cpuacct_controller(cpuacct: &CpuAcctController) -> Self { - let acct = cpuacct.cpuacct(); - - Self { - rt_us: acct.usage_sys, - cpu_us: acct.usage_user, - total_us: acct.usage, - } - } - // generate CPU statistics from cpu controller(scheduler) - // which is usually less accurate, but not supported by cgroup v2 - // CFS is recommanded instead of EEVDF - pub fn from_cpu_controller(cpu: &CpuController) -> Self { - let raw: &str = &cpu.cpu().stat; - let mut rt_us = u64::MAX; - let mut cpu_us = u64::MAX; - let mut total_us = u64::MAX; - for (key, value) in raw.split('\n').filter_map(|stmt| stmt.split_once(' ')) { - match key { - "usage_usec" => total_us = value.parse().unwrap(), - "user_usec" => cpu_us = value.parse().unwrap(), - "system_usec" => rt_us = value.parse().unwrap(), - _ => {} - }; - } - Self { - rt_us, - cpu_us, - total_us, - } - } -} diff --git a/judger/src/sandbox/utils/limiter/mem.rs b/judger/src/sandbox/utils/limiter/mem.rs deleted file mode 100644 index ce5bf1a9..00000000 --- a/judger/src/sandbox/utils/limiter/mem.rs +++ /dev/null @@ -1,25 +0,0 @@ -use cgroups_rs::{memory::MemController, Cgroup}; - -#[derive(Default, Clone, Debug)] -pub struct MemStatistics { - pub oom: bool, - pub peak: u64, -} - -impl MemStatistics { - // generate memory statistics from cgroup - pub fn from_cgroup(cgroup: &Cgroup) -> Self { - let ctrl = cgroup.controller_of().unwrap(); - - Self::from_controller(ctrl) - } - // generate memory statistics with memory controller - pub fn from_controller(mem: &MemController) -> Self { - let stat = mem.memory_stat(); - - let oom = stat.oom_control.oom_kill != 0; - let peak = stat.max_usage_in_bytes; - - Self { oom, peak } - } -} diff --git a/judger/src/sandbox/utils/limiter/mod.rs b/judger/src/sandbox/utils/limiter/mod.rs deleted file mode 100644 index 024365e9..00000000 --- a/judger/src/sandbox/utils/limiter/mod.rs +++ /dev/null @@ -1,181 +0,0 @@ -use crate::sandbox::prelude::*; -use std::path::Path; - -use std::sync::Arc; - -use cgroups_rs::Cgroup; -use cgroups_rs::{cgroup_builder::CgroupBuilder, hierarchies}; -use spin::Mutex; -use tokio::fs; -use tokio::sync::oneshot; -use tokio::sync::oneshot::Receiver; -use tokio::task::JoinHandle; -use tokio::time; - -use crate::init::config::CONFIG; - -pub mod cpu; -pub mod mem; - -/// reason for a process terminate by limiter -pub enum LimitReason { - Cpu, - Mem, - SysMem, -} - -/// object for limit resource usage and report it -/// -/// resource monitoring start immediately after the initialized -// be aware that cgroup-rs reset all number of the cgroup to zero, -// so limiter should be initialize after `cgroup_rs::Cgroup` -pub struct Limiter { - task: JoinHandle<()>, - state: Arc>, - limit_oneshot: Option>, - cg_name: String, - cg: Cgroup, -} - -/// state for CpuStatistics, MemStatistics -// why not just make cpu and mem a object and make those own its state? -// because monitoring take time, and we expect cpu and mem not to spawn its own tokio thread -#[derive(Default)] -struct LimiterState { - cpu: CpuStatistics, - mem: MemStatistics, -} - -impl Drop for Limiter { - fn drop(&mut self) { - self.task.abort(); - tokio::spawn(fs::remove_dir( - Path::new("/sys/fs/cgroup/").join(&self.cg_name), - )); - } -} - -async fn monitor( - cg: Cgroup, - state: Arc>, - limit: Limit, - tx: oneshot::Sender, -) { - let config = CONFIG.get().unwrap(); - loop { - time::sleep(time::Duration::from_nanos(config.runtime.accuracy)).await; - - let cpu = CpuStatistics::from_cgroup(&cg); - let mem = MemStatistics::from_cgroup(&cg); - - // let mut resource_status = ResourceStatus::Running; - let mut end = false; - let mut reason = LimitReason::Mem; - - // oom could be incured from invaild configuration - // check other factor to determine whether is a systm failure or MLE - if mem.oom { - log::trace!("Stopping process because it reach its memory limit"); - // even if oom occur, process may still be running(child process killed) - reason = LimitReason::Mem; - end = true; - } else if cpu.rt_us > limit.rt_us - || cpu.cpu_us > limit.cpu_us - || cpu.total_us > limit.total_us - { - log::trace!("Killing process because it reach its cpu quota"); - reason = LimitReason::Cpu; - end = true; - } - - if let Some(mut state) = state.try_lock() { - state.cpu = cpu; - state.mem = mem; - } - // TODO: use unsafe to increase performance(monitoring is a time critical task) - // unsafe { - // let state_ptr = Box::into_raw(Box::new(LimiterState { cpu, mem })); - // drop(Box::from_raw( - // state.swap(state_ptr, Ordering::Relaxed), - // )); - // } - if end { - tx.send(reason).ok(); - cg.kill().unwrap(); - log::trace!("Process was killed"); - break; - } - } -} - -impl Limiter { - /// create limiter with limit - pub fn new(cg_name: &str, limit: Limit) -> Result { - log::trace!("Creating new limiter for {}", cg_name); - let (tx, rx) = oneshot::channel(); - - let state: Arc> = Arc::default(); - - let config = CONFIG.get().unwrap(); - - let cg = CgroupBuilder::new(cg_name) - .memory() - .kernel_memory_limit(limit.kernel_mem as i64) - .memory_hard_limit(limit.user_mem as i64) - .memory_swap_limit(limit.swap_user as i64) - .done() - .cpu() - .period(config.runtime.accuracy) - .quota(config.runtime.accuracy as i64) - .realtime_period(config.runtime.accuracy) - .realtime_runtime(config.runtime.accuracy as i64) - .done(); - - let cg = if config.nsjail.is_cgv1() { - cg.build(Box::new(hierarchies::V1::new())) - } else { - cg.build(Box::new(hierarchies::V2::new())) - }?; - - let cg2 = cg.clone(); - - let task = tokio::spawn(monitor(cg.clone(), state.clone(), limit, tx)); - - Ok(Limiter { - task, - state, - limit_oneshot: Some(rx), - cg_name: cg_name.to_owned(), - cg: cg2, - }) - } - /// check if oom - /// - /// It expose its internal state(use with care), callee should have explaination for usage - pub fn check_oom(&mut self) -> bool { - MemStatistics::from_cgroup(&self.cg).oom - } - /// yield statistics, consume self - pub async fn statistics(self) -> (CpuStatistics, MemStatistics) { - let config = CONFIG.get().unwrap(); - - if !config.kernel.tickless { - time::sleep(time::Duration::from_nanos( - (1000 * 1000 / config.kernel.kernel_hz) as u64, - )) - .await; - } - time::sleep(time::Duration::from_nanos(config.runtime.accuracy)).await; - - let stat = self.state.lock(); - - (stat.cpu.clone(), stat.mem.clone()) - } - /// wait for resouce exhausted - // it's reverse control flow, subject to change - pub fn wait_exhausted(&mut self) -> Receiver { - self.limit_oneshot - .take() - .expect("Limiter cannot be wait twice!") - } -} diff --git a/judger/src/sandbox/utils/mod.rs b/judger/src/sandbox/utils/mod.rs deleted file mode 100644 index ad9e9b6a..00000000 --- a/judger/src/sandbox/utils/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod limiter; -pub mod nsjail; -pub mod semaphore; diff --git a/judger/src/sandbox/utils/nsjail.rs b/judger/src/sandbox/utils/nsjail.rs deleted file mode 100644 index 5c80e131..00000000 --- a/judger/src/sandbox/utils/nsjail.rs +++ /dev/null @@ -1,218 +0,0 @@ -use std::{ - borrow::Cow, - os::unix::process::ExitStatusExt, - path::{Path, PathBuf}, - process::Stdio, -}; - -use tokio::{ - process::{Child, Command}, - sync::Mutex, -}; - -use crate::init::config::CONFIG; - -use super::super::Error; - -/// Nsjail abstraction, don't implement any meaningful logic -/// -/// Just setup args and wrap for Mutex and automatic kill process when drop -pub struct LimitBuilder { - cmds: Vec>, -} - -impl LimitBuilder { - pub fn cgroup(mut self, cgroup_name: &str) -> LimitBuilder { - let config = CONFIG.get().unwrap(); - match config.nsjail.is_cgv1() { - true => { - self.cmds.push(Cow::Borrowed("--cgroup_mem_parent")); - self.cmds.push(Cow::Owned(cgroup_name.to_owned())); - self.cmds.push(Cow::Borrowed("--cgroup_cpu_parent")); - self.cmds.push(Cow::Owned(cgroup_name.to_owned())); - self.cmds.push(Cow::Borrowed("--cgroup_cpu_ms_per_sec")); - self.cmds.push(Cow::Borrowed("1000000000000")); - } - false => { - self.cmds.push(Cow::Borrowed("--use_cgroupv2")); - self.cmds.push(Cow::Borrowed("--cgroup_cpu_parent")); - self.cmds.push(Cow::Owned(cgroup_name.to_owned())); - } - } - // self.cmds.push(Cow::Borrowed("--cgroup_cpu_ms_per_sec")); - // self.cmds.push(Cow::Borrowed("1")); - - self - } - pub fn done(mut self) -> NaJailBuilder { - self.cmds.push(Cow::Borrowed("--disable_clone_newuser")); - self.cmds.push(Cow::Borrowed("--cgroup_mem_swap_max")); - self.cmds.push(Cow::Borrowed("0")); - self.cmds.push(Cow::Borrowed("--disable_clone_newcgroup")); - self.cmds.push(Cow::Borrowed("--env")); - self.cmds.push(Cow::Borrowed( - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - )); - - NaJailBuilder { cmds: self.cmds } - } -} - -pub struct NaJailBuilder { - cmds: Vec>, -} - -impl NaJailBuilder { - pub fn presist_vol(self, id: &str) -> MountBuilder { - let config = CONFIG.get().unwrap(); - let presist_vol = config - .runtime - .temp - .as_path() - .join(id) - .canonicalize() - .unwrap(); - - MountBuilder { - presist_vol, - cmds: self.cmds, - } - } -} - -pub struct MountBuilder { - presist_vol: PathBuf, - cmds: Vec>, -} - -impl MountBuilder { - pub fn mount(mut self, vol: impl AsRef, lockdown: bool) -> MountBuilder { - if lockdown { - self.cmds.push(Cow::Borrowed("--bindmount_ro")); - } else { - self.cmds.push(Cow::Borrowed("--bindmount")); - } - - let source = self.presist_vol.join(vol.as_ref()); - let source = source.to_str().unwrap(); - let dist = vol.as_ref(); - - self.cmds.push(Cow::Owned(format!("{}:/{}", source, dist))); - - self - } - pub fn done(mut self) -> CommonBuilder { - self.cmds.push(Cow::Borrowed("--tmpfsmount")); - self.cmds.push(Cow::Borrowed("/tmp")); - CommonBuilder { cmds: self.cmds } - } -} - -pub struct CommonBuilder { - cmds: Vec>, -} - -impl CommonBuilder { - pub fn common(mut self) -> CmdBuilder { - let config = CONFIG.get().unwrap(); - - self.cmds.push(Cow::Borrowed("-l")); - - self.cmds.push(Cow::Borrowed(&config.nsjail.log)); - - self.cmds.push(Cow::Borrowed("-Me")); - - self.cmds.push(Cow::Borrowed("--")); - - CmdBuilder { cmds: self.cmds } - } -} - -pub struct CmdBuilder { - cmds: Vec>, -} - -impl CmdBuilder { - pub fn cmds(mut self, cmd: &[&str]) -> NsJailBuilder { - for &arg in cmd { - self.cmds.push(Cow::Owned(arg.to_owned())); - } - NsJailBuilder { cmds: self.cmds } - } -} - -pub struct NsJailBuilder { - cmds: Vec>, -} - -impl NsJailBuilder { - pub fn build(self) -> Result { - let config = CONFIG.get().unwrap(); - - log::trace!( - "Running subprocess {} {}", - &config.nsjail.runtime, - self.cmds.join(" ") - ); - - let mut cmd: Command = Command::new(&config.nsjail.runtime); - cmd.args(self.cmds.iter().map(|a| a.as_ref()).collect::>()); - - let process = cmd - .kill_on_drop(true) - .stdout(Stdio::piped()) - .stdin(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - - Ok(NsJail { - process: Some(Mutex::new(process)), - }) - } -} - -pub enum TermStatus { - SigExit(i32), - Code(i32), -} - -pub struct NsJail { - pub process: Option>, -} - -impl Drop for NsJail { - fn drop(&mut self) { - let process = self.process.take().unwrap(); - tokio::spawn(async move { process.lock().await.kill().await.ok() }); - } -} - -impl NsJail { - pub fn builder(root: impl AsRef) -> LimitBuilder { - let root = root.as_ref().canonicalize().unwrap(); - let root = root.to_str().unwrap(); - LimitBuilder { - cmds: vec![ - Cow::Borrowed("--rw"), - Cow::Borrowed("--chroot"), - Cow::Owned(root.to_owned()), - ], - } - } - pub async fn wait(&self) -> TermStatus { - let status = self - .process - .as_ref() - .unwrap() - .lock() - .await - .wait() - .await - .unwrap(); - if let Some(sig) = status.signal() { - TermStatus::SigExit(sig) - } else { - TermStatus::Code(status.code().unwrap()) - } - } -} diff --git a/judger/src/sandbox/utils/semaphore.rs b/judger/src/sandbox/utils/semaphore.rs deleted file mode 100644 index 7aae2074..00000000 --- a/judger/src/sandbox/utils/semaphore.rs +++ /dev/null @@ -1,170 +0,0 @@ -// todo!(): add resource limit - -use std::{ - collections::BTreeSet, - sync::{atomic, Arc}, -}; - -use spin::mutex::Mutex; -use tokio::sync::oneshot; - -use crate::init::config::CONFIG; - -use super::super::Error; - -static MEMID: atomic::AtomicUsize = atomic::AtomicUsize::new(0); - -pub struct MemoryStatistic { - pub available_mem: u64, - pub memory: u64, - pub tasks: u64, -} - -/// A Semaphore for large buffer accounting -/// -/// because tokio::sync::Semaphore default to u32 for inner type -#[derive(Clone)] -pub struct MemorySemaphore(Arc>); - -impl MemorySemaphore { - pub fn new(memory: u64) -> Self { - Self(Arc::new(Mutex::new(MemorySemaphoreInner { - memory, - all_mem: memory, - queue: BTreeSet::new(), - tasks: 0, - }))) - } - pub fn usage(&self) -> MemoryStatistic { - let self_ = self.0.lock(); - MemoryStatistic { - available_mem: self_.memory, - memory: self_.all_mem, - tasks: self_.tasks, - } - } - pub async fn allocate(&self, memory: u64) -> Result { - log::trace!("preserve {}B memory", memory); - let config = CONFIG.get().unwrap(); - - if memory > config.platform.available_memory { - return Err(Error::ImpossibleResource); - } - - let rx: oneshot::Receiver<()> = { - let mut self_lock = self.0.lock(); - - let (tx, rx) = oneshot::channel(); - - self_lock.queue.insert(MemDemand { - memory, - tx, - id: MEMID.fetch_add(1, atomic::Ordering::SeqCst), - }); - drop(self_lock); - - self.deallocate(0); - - rx - }; - - rx.await.unwrap(); - - log::trace!("get {}B memory", memory); - - Ok(MemoryPermit::new(self, memory)) - } - fn deallocate(&self, released_memory: u64) { - let self_ = &mut *self.0.lock(); - - self_.memory += released_memory; - while let Some(demand) = self_.queue.last() { - if demand.memory <= self_.memory { - self_.memory -= demand.memory; - let channel = self_.queue.pop_last().unwrap().tx; - channel.send(()).unwrap(); - } else { - break; - } - } - } -} - -pub struct MemorySemaphoreInner { - memory: u64, - all_mem: u64, - queue: BTreeSet, - tasks: u64, -} - -struct MemDemand { - memory: u64, - tx: oneshot::Sender<()>, - id: usize, -} - -impl Ord for MemDemand { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - (self.memory, &self.id).cmp(&(other.memory, &other.id)) - } -} - -impl PartialOrd for MemDemand { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Eq for MemDemand {} -impl PartialEq for MemDemand { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - } -} - -pub struct MemoryPermit { - memory: u64, - counter: MemorySemaphore, -} - -impl MemoryPermit { - fn new(counter: &MemorySemaphore, memory: u64) -> Self { - counter.0.lock().tasks += 1; - Self { - memory, - counter: counter.clone(), - } - } - pub fn downgrade(mut self, target: u64) -> Self { - self.counter.deallocate(self.memory - target); - self.memory = target; - self - } -} - -impl Drop for MemoryPermit { - fn drop(&mut self) { - { - self.counter.0.lock().tasks -= 1; - } - self.counter.deallocate(self.memory); - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[tokio::test] - async fn test_semaphore() { - crate::init::new().await; - let semaphore = MemorySemaphore::new(100); - let permit = semaphore.allocate(10).await.unwrap(); - assert_eq!(semaphore.usage().available_mem, 90); - drop(permit); - assert_eq!(semaphore.usage().available_mem, 100); - let permit = semaphore.allocate(100).await.unwrap(); - assert_eq!(semaphore.usage().available_mem, 0); - drop(permit); - } -} diff --git a/judger/src/server.rs b/judger/src/server.rs deleted file mode 100644 index 7270162d..00000000 --- a/judger/src/server.rs +++ /dev/null @@ -1,327 +0,0 @@ -use std::{pin::Pin, sync::Arc}; - -use spin::Mutex; -use tokio::sync::mpsc::*; -use tokio_stream::wrappers::ReceiverStream; -use tonic::{Response, Status}; -use uuid::Uuid; - -use crate::{ - init::config::CONFIG, - langs::prelude::{ArtifactFactory, CompileLog}, -}; - -use crate::grpc::prelude::{judger_server::Judger, *}; - -const PENDING_LIMIT: usize = 128; -const STREAM_CHUNK: usize = 1024 * 16; - -pub type UUID = String; - -fn accuracy() -> u64 { - let config = CONFIG.get().unwrap(); - (1000 * 1000 / config.kernel.kernel_hz) as u64 -} - -impl From for ExecResult { - fn from(value: CompileLog) -> Self { - ExecResult { - result: Some(exec_result::Result::Log(Log { - level: value.level as u32, - msg: value.message, - })), - } - } -} - -fn parse_uid(uid: &str) -> Result { - Uuid::parse_str(uid).map_err(|e| { - log::warn!("Invalid uuid: {}", e); - Status::failed_precondition("Invalid uuid") - }) -} - -/// forcely stream message, if client disconnect, there's no need to continue task -async fn force_send(tx: &mut Sender>, item: T) -> Result<(), Status> { - match tx.send(Ok(item)).await { - Ok(_) => Ok(()), - Err(err) => { - log::debug!("client disconnected: {}", err); - Err(Status::cancelled("client disconnect durning operation!")) - } - } -} - -/// check basic auth in request metadata(similar to http header) -fn check_secret(req: tonic::Request) -> Result { - let (meta, _, payload) = req.into_parts(); - let config = CONFIG.get().unwrap(); - if config.secret.is_none() { - return Ok(payload); - } - if let Some(header) = meta.get("Authorization") { - let secret = ["basic ", config.secret.as_ref().unwrap()] - .concat() - .into_bytes(); - let vaild = header - .as_bytes() - .iter() - .zip(secret.iter()) - .map(|(&a, &b)| a == b) - .reduce(|a, b| a && b); - if vaild.unwrap_or(false) { - return Ok(payload); - } - } - Err(Status::permission_denied("Invalid secret")) -} - -/// Server to serve JudgerSet -pub struct Server { - factory: ArtifactFactory, - running: Mutex, -} - -impl Server { - /// init the server with global config(must be set beforehand) - pub async fn new() -> Self { - let config = CONFIG.get().unwrap(); - let mut factory = ArtifactFactory::default(); - - factory.load_dir(config.plugin.path.clone()).await; - - Self { - factory, - running: Mutex::new(PENDING_LIMIT), - } - } - /// check if pending jobs excess PENDING_LIMIT - fn check_pending(self: &Arc) -> Result { - let mut running = self.running.lock(); - if *running > 0 { - *running -= 1; - Ok(PendingGuard(self.clone())) - } else { - log::warn!("Task Waiting queue full!"); - Err(Status::resource_exhausted("")) - } - } -} - -struct PendingGuard(Arc); - -impl Drop for PendingGuard { - fn drop(&mut self) { - *self.0.running.lock() -= 1; - } -} - -/// start each subtasks, it call stream because next subtask is run only if previous AC -async fn judger_stream( - factory: &ArtifactFactory, - payload: JudgeRequest, - tx: &mut Sender>, -) -> Result<(), Status> { - log::debug!("start streaming"); - - let mode: JudgeMatchRule = payload - .rule - .try_into() - .map_err(|_| Status::failed_precondition("Invaild judge matching rule"))?; - let lang = parse_uid(&payload.lang_uid)?; - - let mut compile = factory.compile(&lang, &payload.code).await?; - - compile.log().for_each(|x| x.log()); - - if let Some(code) = compile.get_expection() { - force_send( - tx, - JudgeResponse { - status: code as i32, - ..Default::default() - }, - ) - .await?; - } - - for (running_task, test) in payload.tests.into_iter().enumerate() { - log::trace!("running at {} task", running_task); - - let mut result = compile - .judge(&test.input, payload.time, payload.memory) - .await?; - - if let Some(code) = result.get_expection() { - log::trace!("yield result: {}", code); - force_send( - tx, - JudgeResponse { - status: code.into(), - ..Default::default() - }, - ) - .await?; - break; - } - - let success = result.assert(&test.input, mode); - let code = match success { - true => JudgerCode::Ac, - false => JudgerCode::Wa, - }; - - let time = result.cpu().total_us; - let memory = result.mem().peak; - log::trace!( - "yield result: {}, take memory {}B, total_us: {}ns", - code, - time, - memory - ); - force_send( - tx, - JudgeResponse { - status: code.into(), - time, - memory, - accuracy: accuracy(), - }, - ) - .await?; - if !success { - break; - } - } - Ok(()) -} - -/// start compile and execute the program -/// -/// In future, we should stream the output to eliminate OLE -async fn exec_stream( - factory: &ArtifactFactory, - payload: ExecRequest, - tx: &mut Sender>, -) -> Result<(), Status> { - log::debug!("start streaming"); - - let lang = parse_uid(&payload.lang_uid)?; - - let mut compile = factory.compile(&lang, &payload.code).await?; - - for log in compile.log() { - force_send(tx, log.into()).await?; - } - - if compile.get_expection().is_some() { - force_send( - tx, - CompileLog { - level: 4, - message: "Compile Error, non-zero return code(signal)".to_string(), - } - .into(), - ) - .await?; - return Ok(()); - } - - let mut judge = compile - .judge(&payload.input, payload.time, payload.memory) - .await?; - - if let Some(x) = judge.get_expection() { - force_send( - tx, - CompileLog { - level: 4, - message: format!("Judge Fail with {}", x), - } - .into(), - ) - .await?; - } else { - for chunk in judge.process().unwrap().stdout.chunks(STREAM_CHUNK) { - force_send( - tx, - ExecResult { - result: Some(exec_result::Result::Output(chunk.to_vec())), - }, - ) - .await?; - } - } - - Ok(()) -} - -#[tonic::async_trait] -impl Judger for Arc { - type JudgeStream = - Pin> + Send>>; - - /// see judger.proto - async fn judge( - &self, - req: tonic::Request, - ) -> Result, Status> { - let payload = check_secret(req)?; - let permit = self.check_pending()?; - - log::debug!("start judging"); - - let (mut tx, rx) = channel(8); - - let self_ = self.clone(); - - tokio::spawn(async move { - if let Err(err) = judger_stream(&self_.factory, payload, &mut tx).await { - tx.send(Err(err)).await.ok(); - }; - drop(permit); - }); - - Ok(Response::new(Box::pin(ReceiverStream::new(rx)))) - } - /// see judger.proto - async fn judger_info(&self, req: tonic::Request<()>) -> Result, Status> { - let config = CONFIG.get().unwrap(); - check_secret(req)?; - - let modules = self.factory.list_module(); - - Ok(Response::new(JudgeInfo { - langs: Langs { list: modules }, - memory: config.platform.available_memory, - accuracy: accuracy(), - cpu_factor: config.platform.cpu_time_multiplier as f32, - })) - } - - type ExecStream = Pin> + Send>>; - - /// see judger.proto - async fn exec( - &self, - req: tonic::Request, - ) -> Result, tonic::Status> { - let payload = check_secret(req)?; - let permit = self.check_pending()?; - - log::debug!("start exec"); - - let (mut tx, rx) = channel(8); - - let self_ = self.clone(); - - tokio::spawn(async move { - if let Err(err) = exec_stream(&self_.factory, payload, &mut tx).await { - tx.send(Err(err)).await.ok(); - }; - drop(permit); - }); - - Ok(Response::new(Box::pin(ReceiverStream::new(rx)))) - } -} diff --git a/judger/src/tests/grpc.rs b/judger/src/tests/grpc.rs deleted file mode 100644 index bb8fdf18..00000000 --- a/judger/src/tests/grpc.rs +++ /dev/null @@ -1,73 +0,0 @@ -// use std::sync::Arc; - -// use tempfile::NamedTempFile; -// use tokio::net; -// use tonic::transport::{self, Endpoint, Uri}; -// use tower::service_fn; - -// use crate::grpc::prelude::{ -// judge_response::Task, judger_client::JudgerClient, judger_server::JudgerServer, *, -// }; -// use crate::grpc::server::Server; -// use crate::init; - -// // TODO!: split test -// #[ignore = "it take very long time"] -// #[tokio::test] -// async fn full() { -// init::new().await; - -// // create stub for unix socket -// let server = transport::Server::builder().add_service(JudgerServer::new(Server::new().await)); -// let socket1 = Arc::new(NamedTempFile::new().unwrap().into_temp_path()); -// let socket2 = socket1.clone(); -// let socket3 = socket2.clone(); - -// // server thread(g) -// let server = tokio::spawn(async move { -// let uds = net::UnixListener::bind(&*socket2.clone()).unwrap(); -// server -// .serve_with_incoming(tokio_stream::wrappers::UnixListenerStream::new(uds)) -// .await -// .unwrap(); -// }); - -// let channel = Endpoint::try_from("http://any.url") -// .unwrap() -// .connect_with_connector(service_fn(move |_: Uri| { -// let socket = Arc::clone(&socket1); -// async move { net::UnixStream::connect(&*socket).await } -// })) -// .await -// .unwrap(); - -// let mut client = JudgerClient::new(channel); - -// let request = JudgeRequest { -// lang_uid: "1c41598f-e253-4f81-9ef5-d50bf1e4e74f".to_string(), -// code: b"print(\"basic test\")".to_vec(), -// memory: 1024 * 1024 * 1024, -// time: 1000 * 1000, -// rule: JudgeMatchRule::SkipSnl as i32, -// tests: vec![TestIo { -// input: b"".to_vec(), -// output: b"basic test".to_vec(), -// }], -// }; - -// let (_, mut res, _) = client.judge(request).await.unwrap().into_parts(); - -// // first request indicate test 1 start -// let res1 = res.message().await.unwrap().unwrap().task; -// assert_eq!(res1, Some(Task::Case(1))); - -// let res2 = res.message().await.unwrap().unwrap().task; -// match res2.unwrap() { -// Task::Case(_) => panic!("expect Result"), -// Task::Result(result) => { -// assert_eq!(result.status, JudgerCode::Ac as i32); -// } -// } -// server.abort(); -// std::fs::remove_file(&*socket3).unwrap(); -// } diff --git a/judger/src/tests/langs.rs b/judger/src/tests/langs.rs deleted file mode 100644 index 5a29dca9..00000000 --- a/judger/src/tests/langs.rs +++ /dev/null @@ -1,49 +0,0 @@ -use uuid::Uuid; - -use crate::{grpc::prelude::JudgeMatchRule, init::config::CONFIG, langs::prelude::ArtifactFactory}; - -async fn test_hello_world(factory: &mut ArtifactFactory, uuid: Uuid, code: &[u8]) { - let mut compiled = factory.compile(&uuid, code).await.unwrap(); - - assert!(compiled.get_expection().is_none()); - - let mut result = compiled - .judge(b"", 1000 * 1000, 1024 * 1024 * 128) - .await - .unwrap(); - - assert!(compiled.get_expection().is_none()); - - assert!(result.assert(b"hello world", JudgeMatchRule::SkipSnl)); -} -#[tokio::test] -async fn built_in_plugin() { - crate::init::new().await; - - let config = CONFIG.get().unwrap(); - let mut factory = ArtifactFactory::default(); - - factory.load_dir(config.plugin.path.clone()).await; - - // lua - test_hello_world( - &mut factory, - Uuid::parse_str("1c41598f-e253-4f81-9ef5-d50bf1e4e74f").unwrap(), - b"print(\"hello world\")", - ) - .await; - // cpp - test_hello_world( - &mut factory, - Uuid::parse_str("8a9e1daf-ff89-42c3-b011-bf6fb4bd8b26").unwrap(), - b"#include \nint main(){printf(\"hello world\");return 0;}", - ) - .await; - // c - test_hello_world( - &mut factory, - Uuid::parse_str("7daff707-26b5-4153-90ae-9858b9fd9619").unwrap(), - b"#include \nint main(){printf(\"hello world\");return 0;}", - ) - .await; -} diff --git a/judger/src/tests/mod.rs b/judger/src/tests/mod.rs deleted file mode 100644 index 0e581df6..00000000 --- a/judger/src/tests/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod grpc; -pub mod langs; -pub mod sandbox; diff --git a/judger/src/tests/sandbox.rs b/judger/src/tests/sandbox.rs deleted file mode 100644 index f6675f0f..00000000 --- a/judger/src/tests/sandbox.rs +++ /dev/null @@ -1,205 +0,0 @@ -use crate::sandbox::prelude::*; -use tokio::time; - -#[tokio::test] -async fn exec() { - crate::init::new().await; - - { - let daemon = ContainerDaemon::new_with_id(".temp", 12); - let container = daemon.create("plugins/rlua-54/rootfs").await.unwrap(); - - let process = container - .execute( - &["/rlua-54", "hello"], - Limit { - cpu_us: 1000 * 1000 * 1000, - rt_us: 1000 * 1000 * 1000, - total_us: 20 * 1000, - swap_user: 0, - kernel_mem: 128 * 1024 * 1024, - user_mem: 512 * 1024 * 1024, - lockdown: false, - }, - ) - .await - .unwrap(); - - let process = process.wait().await.unwrap(); - - assert!(process.succeed()); - - let out = process.stdout; - assert_eq!(out, b"hello world\n"); - } - - // unlike async-std, tokio won't wait for all background task to finish before exit - time::sleep(time::Duration::from_millis(12)).await; -} -#[ignore = "does not always pass, need t consider the latency of kernel!"] -#[tokio::test] -async fn cgroup_cpu() { - crate::init::new().await; - - { - let daemon = ContainerDaemon::new_with_id(".temp", 13); - let container = daemon.create("plugins/rlua-54/rootfs").await.unwrap(); - - let process = container - .execute( - &["/rlua-54", "violate", "cpu"], - Limit { - cpu_us: 1000 * 1000 * 1000, - rt_us: 1000 * 1000 * 1000, - total_us: 20 * 1000, - swap_user: 0, - kernel_mem: 128 * 1024 * 1024, - user_mem: 512 * 1024 * 1024, - lockdown: false, - }, - ) - .await - .unwrap(); - - let process = process.wait().await.unwrap(); - - assert_eq!(process.status, ExitStatus::CpuExhausted); - - assert!(!process.succeed()); - } - - // unlike async-std, tokio won't wait for all background task to finish before exit - time::sleep(time::Duration::from_millis(12)).await; -} -#[tokio::test] -async fn network() { - crate::init::new().await; - - { - let daemon = ContainerDaemon::new_with_id(".temp", 14); - let container = daemon.create("plugins/rlua-54/rootfs").await.unwrap(); - - let process = container - .execute( - &["/rlua-54", "violate", "net"], - Limit { - cpu_us: 1000 * 1000 * 1000, - rt_us: 1000 * 1000 * 1000, - total_us: 20 * 1000, - swap_user: 0, - kernel_mem: 128 * 1024 * 1024, - user_mem: 512 * 1024 * 1024, - lockdown: false, - }, - ) - .await - .unwrap(); - - let process = process.wait().await.unwrap(); - - assert!(!process.succeed()); - } - - // unlike async-std, tokio won't wait for all background task to finish before exit - time::sleep(time::Duration::from_millis(12)).await; -} - -#[tokio::test] -async fn memory() { - crate::init::new().await; - - { - let daemon = ContainerDaemon::new_with_id(".temp", 15); - let container = daemon.create("plugins/rlua-54/rootfs").await.unwrap(); - - let process = container - .execute( - &["/rlua-54", "violate", "mem"], - Limit { - cpu_us: 1000 * 1000 * 1000, - rt_us: 1000 * 1000 * 1000, - total_us: 20 * 1000, - swap_user: 0, - kernel_mem: 64 * 1024 * 1024, - user_mem: 64 * 1024 * 1024, - lockdown: false, - }, - ) - .await - .unwrap(); - - let process = process.wait().await.unwrap(); - - assert!(!process.succeed()); - } - - // unlike async-std, tokio won't wait for all background task to finish before exit - time::sleep(time::Duration::from_millis(12)).await; -} - -#[tokio::test] -async fn disk() { - crate::init::new().await; - - { - let daemon = ContainerDaemon::new_with_id(".temp", 16); - let container = daemon.create("plugins/rlua-54/rootfs").await.unwrap(); - - let process = container - .execute( - &["/rlua-54", "violate", "disk"], - Limit { - cpu_us: 1000 * 1000 * 1000, - rt_us: 1000 * 1000 * 1000, - total_us: 20 * 1000, - swap_user: 0, - kernel_mem: 64 * 1024 * 1024, - user_mem: 64 * 1024 * 1024, - lockdown: false, - }, - ) - .await - .unwrap(); - - let process = process.wait().await.unwrap(); - - assert!(!process.succeed()); - } - - // unlike async-std, tokio won't wait for all background task to finish before exit - time::sleep(time::Duration::from_millis(12)).await; -} - -#[tokio::test] -#[ignore = "failing because of the test suite, not the sandbox"] -async fn syscall() { - crate::init::new().await; - - { - let daemon = ContainerDaemon::new_with_id(".temp", 17); - let container = daemon.create("plugins/rlua-54/rootfs").await.unwrap(); - - let process = container - .execute( - &["/rlua-54", "violate", "syscall"], - Limit { - cpu_us: 1000 * 1000 * 1000, - rt_us: 1000 * 1000 * 1000, - total_us: 20 * 1000, - swap_user: 0, - kernel_mem: 64 * 1024 * 1024, - user_mem: 64 * 1024 * 1024, - lockdown: false, - }, - ) - .await - .unwrap(); - - let process = process.wait().await.unwrap(); - - assert!(!process.succeed()); - } - - // unlike async-std, tokio won't wait for all background task to finish before exit - time::sleep(time::Duration::from_millis(12)).await; -} From f1e3c4ed9d35674d61c26233c7f51fccd6b44de8 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Fri, 19 Apr 2024 18:32:55 +0800 Subject: [PATCH 02/38] refactor(Judger): :truck: move `From for Cpu` logic to crate::sandbox::limiter::stat(where Cpu reside) --- judger/src/sandbox/limiter/hier.rs | 3 - judger/src/sandbox/limiter/mod.rs | 53 +++++++--- judger/src/sandbox/limiter/stat.rs | 44 +++++++++ judger/src/sandbox/limiter/wrapper.rs | 38 ++----- judger/src/sandbox/resource.rs | 0 judger/src/sandbox/semaphore.rs | 137 -------------------------- 6 files changed, 90 insertions(+), 185 deletions(-) delete mode 100644 judger/src/sandbox/resource.rs delete mode 100644 judger/src/sandbox/semaphore.rs diff --git a/judger/src/sandbox/limiter/hier.rs b/judger/src/sandbox/limiter/hier.rs index 601ad889..87573b96 100644 --- a/judger/src/sandbox/limiter/hier.rs +++ b/judger/src/sandbox/limiter/hier.rs @@ -1,7 +1,4 @@ use cgroups_rs::*; -use std::time::Duration; - -pub const MONITOR_ACCURACY: Duration = Duration::from_millis(80); pub enum MonitorKind { Cpu, diff --git a/judger/src/sandbox/limiter/mod.rs b/judger/src/sandbox/limiter/mod.rs index 1d6c1839..479fcc79 100644 --- a/judger/src/sandbox/limiter/mod.rs +++ b/judger/src/sandbox/limiter/mod.rs @@ -1,3 +1,8 @@ +//! Provide ability to limit resource and retrieve final cpu and memory usage +//! +//! To use this module, you need to create it (provide resource limitation) and mount it, +//! finally, spawn process(it's user's responsibility to ensure the process +//! is spawned within the cgroup) pub(self) mod hier; pub(self) mod stat; pub(self) mod wrapper; @@ -10,6 +15,9 @@ use hier::*; use std::sync::Arc; use tokio::time::*; +const MONITOR_ACCURACY: Duration = Duration::from_millis(80); + +/// Exit reason of the process pub enum ExitReason { TimeOut, MemoryOut, @@ -21,7 +29,7 @@ pub async fn monitor(cgroup: Arc, cpu: Cpu) -> ExitReason { let oom_signal = wrapper.oom(); loop { - sleep(MONITOR_ACCURACY/2).await; + sleep(MONITOR_ACCURACY / 2).await; if let Ok(oom_hint) = oom_signal.try_recv() { log::trace!("oom hint: {}", oom_hint); @@ -46,16 +54,10 @@ impl Drop for Limiter { if let Some(monitor_task) = &self.monitor_task { monitor_task.abort(); } - if MONITER_KIND.heir().v2() { - self.cgroup.kill().expect("cgroup.kill does not exist"); - } else { - // use rustix::process::*; - // pid should not be reused until SIGPIPE send(when Process is Drop) - // therefore, it is safe to try killing the process(only true for nsjail) - - // current implementation of v1 support do nothing, wait for action of cleaning - // up the process on drop - // for pid in self.cgroup.tasks() {} + debug_assert!(self.cgroup.tasks().is_empty()); + match self.cgroup.tasks().is_empty() { + true => log::warn!("cgroup still have process running"), + false => self.cgroup.delete().expect("cgroup cannot be deleted"), } } } @@ -71,7 +73,7 @@ impl Limiter { .memory_swap_limit(0) .done() .cpu() - .period((MONITOR_ACCURACY/2).as_nanos() as u64) + .period((MONITOR_ACCURACY / 2).as_nanos() as u64) .quota(MONITOR_ACCURACY.as_nanos() as i64) .realtime_period(MONITOR_ACCURACY.as_nanos() as u64) .realtime_runtime(MONITOR_ACCURACY.as_nanos() as i64) @@ -87,12 +89,31 @@ impl Limiter { }) } /// wait for resource to exhaust + /// + /// Please remember that [`Drop::drop`] only optimistic kill(`SIGKILL`) + /// the process inside it, + /// user SHOULD NOT rely on this to kill the process. + /// + /// + /// 2. Actively limit(notify) cpu resource is achieved by polling the cgroup, + /// the delay require special attention, it is only guaranteed + /// to below limitation provided + [`MONITOR_ACCURACY`]. pub async fn wait_exhaust(&mut self) -> ExitReason { - self.monitor_task.take().unwrap().await.unwrap() + let reason = self.monitor_task.take().unwrap().await.unwrap(); + // optimistic kill(`SIGKILL`) the process inside + self.cgroup.kill().expect("cgroup.kill does not exist"); + reason } - /// get the current resource usage - pub async fn stat(self)->(Cpu,Memory){ + /// get the final resource usage + /// + /// Please remember thatActively limit(notify) cpu resource is achieved + /// by polling the cgroup, therefore the delay requirespecial attention, + /// it is only guaranteed to below limitation provided + [`MONITOR_ACCURACY`]. + pub async fn stat(self) -> (Cpu, Memory) { + // there should be no process left + debug_assert!(self.cgroup.tasks().is_empty()); + // poll once more to get final stat let wrapper = wrapper::CgroupWrapper::new(&self.cgroup); - (wrapper.cpu(),wrapper.memory()) + (wrapper.cpu(), wrapper.memory()) } } diff --git a/judger/src/sandbox/limiter/stat.rs b/judger/src/sandbox/limiter/stat.rs index bfc8793a..a719810d 100644 --- a/judger/src/sandbox/limiter/stat.rs +++ b/judger/src/sandbox/limiter/stat.rs @@ -1,3 +1,5 @@ +use cgroups_rs::cpuacct::CpuAcct; + pub struct Memory { pub kernel: u64, pub user: u64, @@ -14,4 +16,46 @@ impl Cpu { pub(super) fn out_of_resources(resource: &Self, stat: Self) -> bool { stat.kernel > resource.kernel || stat.user > resource.user || stat.total > resource.total } + + pub(super) fn from_acct(acct: CpuAcct) -> Self { + Cpu { + kernel: acct.usage_sys, + user: acct.usage_user, + total: acct.usage, + } + } + pub(super) fn from_raw(raw: &str) -> Self { + let mut kernel = u64::MAX; + let mut user = u64::MAX; + let mut total = u64::MAX; + + for (key, value) in raw.split('\n').filter_map(|stmt| stmt.split_once(' ')) { + match key { + "usage_usec" => total = value.parse().unwrap(), + "user_usec" => user = value.parse().unwrap(), + "system_usec" => kernel = value.parse().unwrap(), + _ => {} + }; + } + + Self { + kernel, + user, + total, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + /// Test the [`Cpu::from_raw`] function + fn cpu_from_raw() { + let raw = "usage_usec 158972260000\nuser_usec 115998852000\nsystem_usec 42973408000\ncore_sched.force_idle_usec 0\nnr_periods 0\nnr_throttled 0\nthrottled_usec 0\nnr_bursts 0\nburst_usec 0\n"; + let cpu = Cpu::from_raw(raw); + assert_eq!(cpu.kernel, 158972260000); + assert_eq!(cpu.user, 115998852000); + assert_eq!(cpu.total, 42973408000); + } } diff --git a/judger/src/sandbox/limiter/wrapper.rs b/judger/src/sandbox/limiter/wrapper.rs index f5832cbd..234a5f52 100644 --- a/judger/src/sandbox/limiter/wrapper.rs +++ b/judger/src/sandbox/limiter/wrapper.rs @@ -1,6 +1,7 @@ -use cgroups_rs::{cpu::CpuController, cpuacct::CpuAcctController, memory::MemController, Cgroup}; - +use super::hier::*; use super::stat::*; +use cgroups_rs::{cpu::CpuController, cpuacct::CpuAcctController, memory::MemController, Cgroup}; +use std::ops::Deref; /// newtype wrapper for cgroup form cgroup_rs pub struct CgroupWrapper<'a> { @@ -12,38 +13,17 @@ impl<'a> CgroupWrapper<'a> { Self { cgroup } } pub fn cpu(&self) -> Cpu { - let mut kernel = u64::MAX; - let mut user = u64::MAX; - let mut total = u64::MAX; - - match self.cgroup.controller_of::() { - Some(controller) => { - let usage = controller.cpuacct(); - kernel = usage.usage_sys; - user = usage.usage_user; - total = usage.usage; + match MONITER_KIND.deref() { + MonitorKind::Cpu => { + let controller: &CpuAcctController = self.cgroup.controller_of().unwrap(); + Cpu::from_acct(controller.cpuacct()) } - None => { + MonitorKind::CpuAcct => { let controller: &CpuController = self.cgroup.controller_of().unwrap(); - let raw: &str = &controller.cpu().stat; - - for (key, value) in raw.split('\n').filter_map(|stmt| stmt.split_once(' ')) { - match key { - "usage_usec" => total = value.parse().unwrap(), - "user_usec" => user = value.parse().unwrap(), - "system_usec" => kernel = value.parse().unwrap(), - _ => {} - }; - } + Cpu::from_raw(raw) } } - - Cpu { - kernel, - user, - total, - } } pub fn oom(&self) -> std::sync::mpsc::Receiver { let controller = self.cgroup.controller_of::().unwrap(); diff --git a/judger/src/sandbox/resource.rs b/judger/src/sandbox/resource.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/judger/src/sandbox/semaphore.rs b/judger/src/sandbox/semaphore.rs deleted file mode 100644 index 854a50a7..00000000 --- a/judger/src/sandbox/semaphore.rs +++ /dev/null @@ -1,137 +0,0 @@ -use std::sync::{ - atomic::{self, Ordering}, - Arc, -}; - -use spin::Mutex; -use tokio::sync::oneshot::*; - -struct SemaphoreInner { - permits: atomic::AtomicUsize, - all_permits: usize, - max_wait: usize, - waiters: Mutex>)>>, -} - -#[derive(Clone)] -struct Semaphore(Arc); - -impl Semaphore { - /// Create a new asynchronous semaphore with the given number of permits. - /// - /// asynchronous semaphore is a synchronization primitive that limits the number of concurrent, - /// instead of blocking the thread, yeild to scheduler and wait for the permit. - /// - /// Note that there is no preemption. - pub fn new(all_permits: usize, max_wait: usize) -> Self { - Semaphore(Arc::new(SemaphoreInner { - permits: atomic::AtomicUsize::new(all_permits), - all_permits, - max_wait, - waiters: Mutex::new(Vec::new()), - })) - } - /// get a permit from semaphore - /// - /// It return None if - /// 1. It's impossible to get the permit even no other task is holding the permit - /// 2. The number of waiting task is greater than max_wait - pub async fn get_permit(&self, permit: usize) -> Option { - // FIXME: return Result to differentiate between max_wait_reached and impossible_resource_condition - if permit > self.0.all_permits { - return None; - } - let (tx, rx) = channel::<()>(); - { - let mut waiter = self.0.waiters.lock(); - if waiter.len() >= self.0.max_wait { - return None; - } - waiter.push((permit, Some(tx))); - } - - self.try_wake(); - - rx.await.ok()?; - - Some(Permit { - semaphore: self.clone(), - permit, - }) - } - fn release(&self, permit: usize) { - self.0.permits.fetch_add(permit, Ordering::Relaxed); - self.try_wake(); - } - fn try_wake(&self) { - let mut waiter = self.0.waiters.lock(); - if let Some((permit, ref mut waker)) = waiter.last_mut() { - let mut current = self.0.permits.load(Ordering::Acquire); - loop { - if current < *permit { - return; - } - if let Err(x) = self.0.permits.compare_exchange( - current, - current - *permit, - Ordering::SeqCst, - Ordering::Acquire, - ) { - current = x; - } else { - break; - }; - } - if waker.take().unwrap().send(()).is_err() { - log::warn!("Semaphore waiter disconnected"); - } - waiter.pop(); - } - } -} - -pub struct Permit { - semaphore: Semaphore, - permit: usize, -} - -impl Drop for Permit { - fn drop(&mut self) { - self.semaphore.release(self.permit); - } -} - -#[cfg(test)] -mod test { - use super::Semaphore; - #[tokio::test] - /// test max value of permit - async fn get_permit_max() { - let semaphore = Semaphore::new(1024, 1024); - assert!(semaphore.get_permit(1024).await.is_some()); - assert!(semaphore.get_permit(1025).await.is_none()); - } - #[tokio::test] - // test getting permit with ordering - async fn get_permit_unorder() { - let semaphore = Semaphore::new(1024, 1024); - let permit = semaphore.get_permit(1).await.unwrap(); - let permit1 = tokio::spawn(async move { - tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; - semaphore.get_permit(1024).await - }); - drop(permit); - assert!(permit1.await.unwrap().is_some()); - } - #[tokio::test] - // test `get_permit` return None when max_wait is reached - async fn get_permit_max_wait() { - let semaphore = Semaphore::new(1024, 1); - let semaphore1 = semaphore.clone(); - let _ = semaphore.get_permit(1).await.unwrap(); - let _ = tokio::spawn(async move { - semaphore.get_permit(1024).await.unwrap(); - }); - dbg!(semaphore1.get_permit(1).await.is_none()); - } -} From 971a531e90846539f536d7d17f010644fb7c1123 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Fri, 19 Apr 2024 23:44:31 +0800 Subject: [PATCH 03/38] feat(Judger): :sparkles: parse config module --- judger/src/config.rs | 62 +++++++++ judger/src/error.rs | 4 + judger/src/main.rs | 1 + judger/src/sandbox/daemon/handle.rs | 15 ++ judger/src/sandbox/daemon/mod.rs | 33 +++++ judger/src/sandbox/daemon/semaphore.rs | 184 +++++++++++++++++++++++++ judger/src/sandbox/limiter/hier.rs | 14 +- judger/src/sandbox/limiter/mod.rs | 5 +- judger/src/sandbox/limiter/stat.rs | 6 + judger/src/sandbox/limiter/wrapper.rs | 3 + judger/src/sandbox/mod.rs | 10 +- judger/src/sandbox/process/mod.rs | 69 ++++++++++ judger/src/sandbox/process/nsjail.rs | 60 ++++++++ 13 files changed, 457 insertions(+), 9 deletions(-) create mode 100644 judger/src/config.rs create mode 100644 judger/src/sandbox/daemon/handle.rs create mode 100644 judger/src/sandbox/daemon/mod.rs create mode 100644 judger/src/sandbox/daemon/semaphore.rs create mode 100644 judger/src/sandbox/process/mod.rs create mode 100644 judger/src/sandbox/process/nsjail.rs diff --git a/judger/src/config.rs b/judger/src/config.rs new file mode 100644 index 00000000..ce1acaf9 --- /dev/null +++ b/judger/src/config.rs @@ -0,0 +1,62 @@ +use serde::{Deserialize, Serialize}; + +#[cfg(not(test))] +use std::path::PathBuf; + +#[cfg(not(test))] +fn try_load_config() -> Result> { + use std::ops::Deref; + use std::{fs::File, io::Read}; + + let mut file = File::open("config.toml")?; + let mut buf = String::new(); + file.read_to_string(&mut buf)?; + let config = toml::from_str(buf.as_str())?; + log::info!("load config from {}", CONFIG_PATH.deref().to_string_lossy()); + Ok(config) +} + +#[cfg(not(test))] +lazy_static::lazy_static! { + pub static ref CONFIG_PATH: PathBuf = PathBuf::from("config.toml"); + pub static ref CONFIG: Config=try_load_config().unwrap_or_default(); +} + +#[cfg(test)] +lazy_static::lazy_static! { + pub static ref CONFIG: Config=Config::default(); +} + +#[derive(Serialize, Deserialize, Debug, Default)] +#[serde(rename_all = "camelCase")] +pub enum Accounting { + #[default] + Auto, + CpuAccounting, + Cpu, +} + +#[derive(Serialize, Deserialize, Default)] +pub struct Machine { + cpu: Option, + memory: Option, +} + +fn default_log() -> u8 { + 1 +} + +#[derive(Serialize, Deserialize, Default)] +#[serde(deny_unknown_fields)] +pub struct Config { + #[serde(default)] + pub accounting: Accounting, + #[serde(default)] + pub machine: Machine, + #[serde(default)] + pub rootless: bool, + #[serde(default = "default_log")] + pub log: u8, + #[serde(default)] + pub secret: Option, +} diff --git a/judger/src/error.rs b/judger/src/error.rs index 71189f57..8e3f609f 100644 --- a/judger/src/error.rs +++ b/judger/src/error.rs @@ -2,4 +2,8 @@ pub enum Error { #[error("{0}")] CgroupError(#[from] cgroups_rs::error::Error), + #[error("too few memory")] + LowMemory, + #[error("queue is full")] + QueueFull, } diff --git a/judger/src/main.rs b/judger/src/main.rs index 2da4966a..6b8f896d 100644 --- a/judger/src/main.rs +++ b/judger/src/main.rs @@ -1,3 +1,4 @@ +pub mod config; pub mod error; pub mod sandbox; diff --git a/judger/src/sandbox/daemon/handle.rs b/judger/src/sandbox/daemon/handle.rs new file mode 100644 index 00000000..98e3b23b --- /dev/null +++ b/judger/src/sandbox/daemon/handle.rs @@ -0,0 +1,15 @@ +use super::semaphore::*; + +pub struct Handle { + cg_name: String, + memory: Permit, +} + +impl Handle { + pub(super) fn new(cg_name: String, memory: Permit) -> Self { + Self { cg_name, memory } + } + pub fn get_cg_name(&self) -> &str { + &self.cg_name + } +} diff --git a/judger/src/sandbox/daemon/mod.rs b/judger/src/sandbox/daemon/mod.rs new file mode 100644 index 00000000..13a3ca48 --- /dev/null +++ b/judger/src/sandbox/daemon/mod.rs @@ -0,0 +1,33 @@ +// FIXME: this module is not well designed, it didn't implement meaningful logic(kind like adapter?) +pub mod handle; +pub(self) mod semaphore; +use std::sync::atomic::{AtomicUsize, Ordering}; + +use crate::error::Error; + +const MAX_WAIT: usize = 10; +static CGROUP_PREFIX: &str = "mdoj."; + +pub struct Daemon { + cg_name_counter: AtomicUsize, + memory: semaphore::Semaphore, +} + +impl Daemon { + pub fn new(memory: u64) -> Self { + Self { + cg_name_counter: AtomicUsize::new(0), + memory: semaphore::Semaphore::new(memory, MAX_WAIT), + } + } + // FIXME: daemon should provide direct interface for sandbox, which should rely on process + pub(super) async fn spawn_handle(&self, resource: u64) -> Result { + let cg_name = format!( + "{}{}", + CGROUP_PREFIX, + self.cg_name_counter.fetch_add(1, Ordering::AcqRel) + ); + let memory = self.memory.get_permit(resource).await?; + Ok(handle::Handle::new(cg_name, memory)) + } +} diff --git a/judger/src/sandbox/daemon/semaphore.rs b/judger/src/sandbox/daemon/semaphore.rs new file mode 100644 index 00000000..0e4a6766 --- /dev/null +++ b/judger/src/sandbox/daemon/semaphore.rs @@ -0,0 +1,184 @@ +use std::{ + fmt::Debug, + sync::{ + atomic::{self, Ordering}, + Arc, + }, +}; + +use crate::error::Error as CrateError; +use spin::Mutex; +use tokio::sync::oneshot::*; + +#[derive(Debug, thiserror::Error, PartialEq)] +pub enum Error { + #[error("Max wait reached")] + MaxWaitReached, + #[error("Impossible to get the permit")] + ImpossibleResourceCondition, +} + +impl From for CrateError { + fn from(value: Error) -> CrateError { + match value { + Error::MaxWaitReached => CrateError::QueueFull, + Error::ImpossibleResourceCondition => CrateError::LowMemory, + } + } +} + +struct SemaphoreInner { + permits: atomic::AtomicU64, + all_permits: u64, + max_wait: usize, + waiters: Mutex>)>>, +} + +#[derive(Clone)] +pub struct Semaphore(Arc); + +impl Semaphore { + /// Create a new asynchronous semaphore with the given number of permits. + /// + /// asynchronous semaphore is a synchronization primitive that limits the number of concurrent, + /// instead of blocking the thread, yeild to scheduler and wait for the permit. + /// + /// Note that there is no preemption. + pub fn new(all_permits: u64, max_wait: usize) -> Self { + Semaphore(Arc::new(SemaphoreInner { + permits: atomic::AtomicU64::new(all_permits), + all_permits, + max_wait, + waiters: Mutex::new(Vec::new()), + })) + } + /// get a permit from semaphore + /// + /// It return None if + /// 1. It's impossible to get the permit even no other task is holding the permit + /// 2. The number of waiting task is greater than max_wait + pub async fn get_permit(&self, permit: u64) -> Result { + // FIXME: return Result to differentiate between max_wait_reached and impossible_resource_condition + if permit > self.0.all_permits { + return Err(Error::ImpossibleResourceCondition); + } + let (tx, rx) = channel::<()>(); + { + let mut waiter = self.0.waiters.lock(); + if waiter.len() >= self.0.max_wait { + return Err(Error::MaxWaitReached); + } + waiter.push((permit, Some(tx))); + } + + self.try_wake(); + + rx.await.ok().expect("Channel closed"); + + Ok(Permit { + semaphore: self.clone(), + permit, + }) + } + fn release(&self, permit: u64) { + self.0.permits.fetch_add(permit, Ordering::Relaxed); + self.try_wake(); + } + fn try_wake(&self) { + let mut waiter = self.0.waiters.lock(); + if let Some((permit, ref mut waker)) = waiter.last_mut() { + let mut current = self.0.permits.load(Ordering::Acquire); + loop { + if current < *permit { + return; + } + if let Err(x) = self.0.permits.compare_exchange( + current, + current - *permit, + Ordering::SeqCst, + Ordering::Acquire, + ) { + current = x; + } else { + break; + }; + } + if waker.take().unwrap().send(()).is_err() { + log::warn!("Semaphore waiter disconnected"); + } + waiter.pop(); + } + } +} + +pub(super) struct Permit { + semaphore: Semaphore, + permit: u64, +} + +impl Drop for Permit { + fn drop(&mut self) { + self.semaphore.release(self.permit); + } +} + +impl Debug for Permit { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Permit") + .field("permit", &self.permit) + .finish() + } +} + +impl PartialEq for Permit { + fn eq(&self, other: &Self) -> bool { + self.permit == other.permit + } +} + +#[cfg(test)] +mod test { + use tokio::time; + + use super::*; + #[tokio::test] + /// test [`Semaphore::get_permit`] return [`Err(Error::ImpossibleResourceCondition)`] when max_wait is reached + async fn get_permit_max() { + let semaphore = Semaphore::new(1024, 1024); + assert!(semaphore.get_permit(1024).await.is_ok()); + assert_eq!( + Err(Error::ImpossibleResourceCondition), + semaphore.get_permit(1025).await + ); + } + #[tokio::test] + /// test [`Semaphore::get_permit`] to ensure permit is distributed in order + /// (First come first serve, no matter amount of permit requested) + async fn get_permit_unorder() { + let semaphore = Semaphore::new(1024, 1024); + let permit = semaphore.get_permit(1).await.unwrap(); + let permit1 = tokio::spawn(async move { + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + semaphore.get_permit(1024).await + }); + drop(permit); + assert!(permit1.await.unwrap().is_ok()); + } + #[tokio::test] + /// test [`Semaphore::get_permit`] return [`Err(Error::MaxWaitReached)`] when max_wait is reached + async fn get_permit_max_wait() { + let semaphore = Semaphore::new(1024, 1); + let permit = semaphore.get_permit(1).await.unwrap(); + + let semaphore1 = semaphore.clone(); + + let _ = tokio::spawn(async move { + semaphore.get_permit(1024).await.unwrap(); + }); + + time::sleep(time::Duration::from_millis(4)).await; + assert_eq!(Err(Error::MaxWaitReached), semaphore1.get_permit(1).await); + + drop(permit); + } +} diff --git a/judger/src/sandbox/limiter/hier.rs b/judger/src/sandbox/limiter/hier.rs index 87573b96..7b3ada80 100644 --- a/judger/src/sandbox/limiter/hier.rs +++ b/judger/src/sandbox/limiter/hier.rs @@ -1,3 +1,4 @@ +use crate::config::Accounting; use cgroups_rs::*; pub enum MonitorKind { @@ -7,11 +8,14 @@ pub enum MonitorKind { lazy_static::lazy_static! { pub static ref MONITER_KIND: MonitorKind = - match hierarchies::auto().v2(){ - true=>MonitorKind::Cpu, - false=>MonitorKind::CpuAcct - } - ; + match crate::config::CONFIG.accounting { + Accounting::Auto =>match hierarchies::auto().v2(){ + true=>MonitorKind::Cpu, + false=>MonitorKind::CpuAcct + }, + Accounting::CpuAccounting => MonitorKind::CpuAcct, + Accounting::Cpu => MonitorKind::Cpu, + }; } impl MonitorKind { diff --git a/judger/src/sandbox/limiter/mod.rs b/judger/src/sandbox/limiter/mod.rs index 479fcc79..0f1d179b 100644 --- a/judger/src/sandbox/limiter/mod.rs +++ b/judger/src/sandbox/limiter/mod.rs @@ -17,6 +17,10 @@ use tokio::time::*; const MONITOR_ACCURACY: Duration = Duration::from_millis(80); +lazy_static::lazy_static! { + pub static ref CGROUP_V2:bool=hier::MONITER_KIND.heir().v2(); +} + /// Exit reason of the process pub enum ExitReason { TimeOut, @@ -43,7 +47,6 @@ pub async fn monitor(cgroup: Arc, cpu: Cpu) -> ExitReason { } } -/// limiter that monitor the resource usage of a cgroup pub struct Limiter { cgroup: Arc, monitor_task: Option>, diff --git a/judger/src/sandbox/limiter/stat.rs b/judger/src/sandbox/limiter/stat.rs index a719810d..4d97fd0b 100644 --- a/judger/src/sandbox/limiter/stat.rs +++ b/judger/src/sandbox/limiter/stat.rs @@ -6,6 +6,12 @@ pub struct Memory { pub total: u64, } +impl Memory { + pub fn get_reserved(&self) -> u64 { + self.total.min(self.user + self.kernel) + } +} + pub struct Cpu { pub kernel: u64, pub user: u64, diff --git a/judger/src/sandbox/limiter/wrapper.rs b/judger/src/sandbox/limiter/wrapper.rs index 234a5f52..74bd0c75 100644 --- a/judger/src/sandbox/limiter/wrapper.rs +++ b/judger/src/sandbox/limiter/wrapper.rs @@ -12,6 +12,7 @@ impl<'a> CgroupWrapper<'a> { pub fn new(cgroup: &'a Cgroup) -> Self { Self { cgroup } } + /// get cpu usage(statistics) pub fn cpu(&self) -> Cpu { match MONITER_KIND.deref() { MonitorKind::Cpu => { @@ -25,10 +26,12 @@ impl<'a> CgroupWrapper<'a> { } } } + /// get an receiver(synchronize) for oom event pub fn oom(&self) -> std::sync::mpsc::Receiver { let controller = self.cgroup.controller_of::().unwrap(); controller.register_oom_event("mdoj-oom-handler").unwrap() } + /// get memory usage(statistics) pub fn memory(&self) -> Memory { let controller = self.cgroup.controller_of::().unwrap(); let kusage = controller.kmem_stat(); diff --git a/judger/src/sandbox/mod.rs b/judger/src/sandbox/mod.rs index 7873fab4..665db20d 100644 --- a/judger/src/sandbox/mod.rs +++ b/judger/src/sandbox/mod.rs @@ -1,3 +1,7 @@ -pub mod limiter; -pub mod semaphore; -pub mod resource; +mod daemon; +mod limiter; +mod process; + +pub use daemon::Daemon; + +pub use self::limiter::{Cpu, Memory}; diff --git a/judger/src/sandbox/process/mod.rs b/judger/src/sandbox/process/mod.rs new file mode 100644 index 00000000..3f24a0da --- /dev/null +++ b/judger/src/sandbox/process/mod.rs @@ -0,0 +1,69 @@ +pub(super) mod nsjail; +use std::{ + ffi::{OsStr, OsString}, + path::PathBuf, + str::FromStr, +}; + +use crate::error::Error; +use tokio::process::*; + +use super::{daemon::*, *}; + +lazy_static::lazy_static! { + pub static ref NSJAIL_PATH: PathBuf =PathBuf::from("./nsjail-3.1"); + pub static ref NSJAIL_ARGS: Vec = vec![ + OsString::from("--disable_clone_newuser"), + OsString::from("--disable_clone_newuser"), + OsString::from("--disable_clone_newcgroup"), + OsString::from("--env"), + OsString::from("PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") + ]; + // FIXME: respect config +} + +pub trait Context { + fn get_cpu(&self) -> Cpu; + fn get_memory(&self) -> Memory; + fn create_fs(&self) -> impl AsRef + Send; + fn destroy_fs(&self, fs: impl AsRef + Send); + fn get_args(&self) -> &[OsString]; +} + +impl Daemon { + pub async fn spawn(&self, context: C) -> Result, Error> { + let memory = context.get_memory().get_reserved(); + + let handle = self.spawn_handle(memory).await?; + + Ok(Process { context, handle }) + } +} + +/// A inactive process with resource allocated +pub struct Process { + context: C, + handle: handle::Handle, +} + +impl Process { + pub async fn wait(&mut self) { + let mem = self.context.get_memory(); + let cpu = self.context.get_cpu(); + let rootfs = self.context.create_fs(); + let limiter = limiter::Limiter::new_mount(self.handle.get_cg_name(), cpu, mem).unwrap(); + + let mut args = todo!(); + + // let process=Command::new(NSJAIL_PATH.as_path()) + // .args(&args) + // .spawn() + // .unwrap(); + } +} + +// impl Drop for Process { +// fn drop(&mut self) { +// self.context.destroy_fs(self.rootfs.as_path()); +// } +// } diff --git a/judger/src/sandbox/process/nsjail.rs b/judger/src/sandbox/process/nsjail.rs new file mode 100644 index 00000000..f95b466e --- /dev/null +++ b/judger/src/sandbox/process/nsjail.rs @@ -0,0 +1,60 @@ +use std::{ffi::OsString, ops::Deref}; + +#[derive(Default)] +pub struct ArgFactory { + args: Vec, +} + +impl ArgFactory {} + +trait Argument { + fn get_args(self) -> Vec; +} + +struct BaseArg; + +impl Argument for BaseArg { + fn get_args(self) -> Vec { + vec![ + OsString::from("--disable_clone_newuser"), + OsString::from("--disable_clone_newuser"), + OsString::from("--disable_clone_newcgroup"), + OsString::from("--env"), + OsString::from("PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"), + ] + } +} + +struct CGroupMountArg { + pub cg_path: String, +} + +impl Argument for CGroupMountArg { + fn get_args(self) -> Vec { + match super::limiter::CGROUP_V2.deref() { + true => vec![ + OsString::from("--cgroup_cpu_parent"), + OsString::from(self.cg_path), + ], + false => vec![ + OsString::from("--cgroup_mem_mount"), + format!("/sys/fs/cgroup/memory/{}", self.cg_path.clone()).into(), + OsString::from("--cgroup_cpu_mount"), + format!("/sys/fs/cgroup/cpu/{}", self.cg_path.clone()).into(), + OsString::from("--cgroup_pids_mount"), + format!("/sys/fs/cgroup/pids/{}", self.cg_path).into(), + ] + } + } +} + +struct CGroupVersionArg; + +impl Argument for CGroupVersionArg { + fn get_args(self) -> Vec { + match super::limiter::CGROUP_V2.deref() { + true => vec![OsString::from("--use_cgroupv2")], + false => Vec::new(), + } + } +} From b40558440b2e75968a415239537da818cc0e1e13 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:46:36 +0800 Subject: [PATCH 04/38] feat(Judger): :sparkles: add test and impl AsyncRead, AsyncSeek for TarBlock --- judger/src/filesystem/mod.rs | 1 + judger/src/filesystem/tar/block.rs | 239 +++++++++++++++++++++++++++++ judger/src/filesystem/tar/entry.rs | 0 judger/src/filesystem/tar/mod.rs | 2 + 4 files changed, 242 insertions(+) create mode 100644 judger/src/filesystem/mod.rs create mode 100644 judger/src/filesystem/tar/block.rs create mode 100644 judger/src/filesystem/tar/entry.rs create mode 100644 judger/src/filesystem/tar/mod.rs diff --git a/judger/src/filesystem/mod.rs b/judger/src/filesystem/mod.rs new file mode 100644 index 00000000..c6394bcf --- /dev/null +++ b/judger/src/filesystem/mod.rs @@ -0,0 +1 @@ +mod tar; diff --git a/judger/src/filesystem/tar/block.rs b/judger/src/filesystem/tar/block.rs new file mode 100644 index 00000000..46163258 --- /dev/null +++ b/judger/src/filesystem/tar/block.rs @@ -0,0 +1,239 @@ +use std::{ + future::IntoFuture, + io::SeekFrom, + ops::DerefMut, + pin::{pin, Pin}, + sync::Arc, + task::{Context, Poll}, +}; + +use std::future::Future; +use std::io; +use tokio::{ + io::{AsyncRead, AsyncSeek}, + sync::{Mutex, OwnedMutexGuard}, +}; + +#[derive(Default)] +enum Stage { + Reading(OwnedMutexGuard), + Seeking(OwnedMutexGuard), + #[default] + Done, +} + +impl Stage { + fn take(&mut self) -> Self { + std::mem::take(self) + } +} + +pub struct TarBlock +where + F: AsyncRead + AsyncSeek + Unpin, +{ + file: Arc>, + start: u64, + size: u64, + cursor: u64, + stage: Stage, +} + +impl Clone for TarBlock +where + F: AsyncRead + AsyncSeek + Unpin, +{ + fn clone(&self) -> Self { + Self { + file: self.file.clone(), + start: self.start.clone(), + size: self.size.clone(), + cursor: self.cursor.clone(), + stage: Stage::Done, + } + } +} + +impl TarBlock +where + F: AsyncRead + AsyncSeek + Unpin, +{ + pub fn new(file: Arc>, start: u64, size: u64) -> Self { + Self { + file, + start, + size, + cursor: 0, + stage: Stage::Done, + } + } + #[cfg(test)] + fn from_raw(file: F, start: u64, size: u64) -> Self { + Self { + file: Arc::new(Mutex::new(file)), + start, + size, + cursor: 0, + stage: Stage::Done, + } + } + #[inline] + fn get_seek_from(&self) -> SeekFrom { + SeekFrom::Start(self.start + self.cursor) + } + #[inline] + fn check_bound(&self) -> bool { + self.cursor > self.size + } + #[inline] + fn get_remain(&self) -> u64 { + self.size - self.cursor + } +} + +impl AsyncRead for TarBlock +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> Poll> { + if self.check_bound(){ + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "tar block out of bound", + ))); + } + let original_size = buf.filled().len(); + match self.stage.take() { + Stage::Reading(mut locked) => { + let file = pin!(locked.deref_mut()); + if let Poll::Ready(x) = file.poll_read(cx, buf) { + return match x { + Ok(_) => { + let read_byte = (buf.filled().len() - original_size) as u64; + match read_byte > self.get_remain() { + true => { + buf.set_filled(original_size + self.get_remain() as usize); + self.cursor += self.get_remain(); + } + false => self.cursor += read_byte, + }; + Poll::Ready(Ok(())) + } + Err(err) => Poll::Ready(Err(err)), + }; + } + } + Stage::Seeking(mut locked) => { + let file = pin!(locked.deref_mut()); + if let Poll::Ready(x) = file.poll_complete(cx) { + match x { + Ok(x) => { + self.as_mut().stage = Stage::Reading(locked); + self.as_mut().cursor = x - self.start; + cx.waker().wake_by_ref(); + } + Err(err) => { + return Poll::Ready(Err(err)); + } + } + } + } + Stage::Done => { + if let Poll::Ready(mut locked) = pin!(self.file.clone().lock_owned()).poll(cx) { + let file = pin!(locked.deref_mut()); + if let Err(err) = file.start_seek(self.get_seek_from()) { + return Poll::Ready(Err(err)); + } + self.as_mut().stage = Stage::Seeking(locked); + cx.waker().wake_by_ref(); + } + } + } + Poll::Pending + } +} + +impl AsyncSeek for TarBlock +where + F: AsyncRead + AsyncSeek + Unpin, +{ + fn start_seek(self: Pin<&mut Self>, position: io::SeekFrom) -> io::Result<()> { + let self_ = self.get_mut(); + self_.cursor = match position { + io::SeekFrom::Start(x) => x, + io::SeekFrom::End(x) => (self_.size as i64 + x).try_into().unwrap_or_default(), + io::SeekFrom::Current(x) => (self_.cursor as i64 + x).try_into().unwrap_or_default(), + }; + if self_.check_bound() { + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "tar block out of bound", + )); + } + Ok(()) + } + + fn poll_complete(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(self.cursor)) + } +} + +#[cfg(test)] +mod test { + use std::io::Cursor; + + use tokio::io::{AsyncReadExt, BufReader}; + + use super::*; + #[tokio::test] + async fn normal_read() { + let underlying = BufReader::new(Cursor::new(b"111hello world111")); + let mut block = TarBlock::from_raw(underlying, 3, 11); + + let mut buf = [0_u8; 11]; + block.read_exact(&mut buf).await.unwrap(); + + assert_eq!(buf, *b"hello world"); + } + #[tokio::test] + async fn end_of_file_read() { + let underlying = BufReader::new(Cursor::new(b"111hello world")); + let mut block = TarBlock::from_raw(underlying, 3, 11); + + let mut buf = [0_u8; 11]; + block.read_exact(&mut buf).await.unwrap(); + + assert_eq!( + block.read_u8().await.unwrap_err().kind(), + io::ErrorKind::UnexpectedEof + ); + } + #[tokio::test] + async fn multi_sequential_read() { + let underlying = BufReader::new(Cursor::new(b"111hello world111")); + let mut block = TarBlock::from_raw(underlying, 3, 11); + + for c in b"hello world" { + assert_eq!(block.read_u8().await.unwrap(), *c); + } + } + #[tokio::test] + async fn multi_reader_read() { + let underlying = BufReader::new(Cursor::new(b"111hello world111")); + let underlying = Arc::new(Mutex::new(underlying)); + let block = TarBlock::new(underlying, 3, 11); + + for _ in 0..3000 { + let mut block = block.clone(); + tokio::spawn(async move { + let mut buf = [0_u8; 11]; + block.read_exact(&mut buf).await.unwrap(); + assert_eq!(buf, *b"hello world"); + }); + } + } +} diff --git a/judger/src/filesystem/tar/entry.rs b/judger/src/filesystem/tar/entry.rs new file mode 100644 index 00000000..e69de29b diff --git a/judger/src/filesystem/tar/mod.rs b/judger/src/filesystem/tar/mod.rs new file mode 100644 index 00000000..842a0aca --- /dev/null +++ b/judger/src/filesystem/tar/mod.rs @@ -0,0 +1,2 @@ +mod block; +mod entry; From d2d11aec7a45979e6a038ff4cdfb0b3f5c3a85e8 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:47:39 +0800 Subject: [PATCH 05/38] feat(Judger): :sparkles: rename limiter to monitor, and draft process lifetime --- judger/src/error.rs | 10 +- judger/src/main.rs | 10 +- judger/src/sandbox/daemon/handle.rs | 15 -- judger/src/sandbox/daemon/mod.rs | 33 ---- judger/src/sandbox/mod.rs | 32 +++- .../src/sandbox/{limiter => monitor}/hier.rs | 2 +- .../{limiter/mod.rs => monitor/mem_cpu.rs} | 83 ++++----- judger/src/sandbox/monitor/mod.rs | 158 ++++++++++++++++++ judger/src/sandbox/monitor/output.rs | 91 ++++++++++ .../src/sandbox/{limiter => monitor}/stat.rs | 20 ++- judger/src/sandbox/monitor/walltime.rs | 41 +++++ .../sandbox/{limiter => monitor}/wrapper.rs | 9 +- judger/src/sandbox/process/corpse.rs | 37 ++++ judger/src/sandbox/process/mod.rs | 73 +------- judger/src/sandbox/process/nsjail.rs | 113 +++++++++---- judger/src/sandbox/process/process.rs | 134 +++++++++++++++ judger/src/{sandbox/daemon => }/semaphore.rs | 6 +- 17 files changed, 665 insertions(+), 202 deletions(-) delete mode 100644 judger/src/sandbox/daemon/handle.rs delete mode 100644 judger/src/sandbox/daemon/mod.rs rename judger/src/sandbox/{limiter => monitor}/hier.rs (93%) rename judger/src/sandbox/{limiter/mod.rs => monitor/mem_cpu.rs} (64%) create mode 100644 judger/src/sandbox/monitor/mod.rs create mode 100644 judger/src/sandbox/monitor/output.rs rename judger/src/sandbox/{limiter => monitor}/stat.rs (82%) create mode 100644 judger/src/sandbox/monitor/walltime.rs rename judger/src/sandbox/{limiter => monitor}/wrapper.rs (82%) create mode 100644 judger/src/sandbox/process/corpse.rs create mode 100644 judger/src/sandbox/process/process.rs rename judger/src/{sandbox/daemon => }/semaphore.rs (96%) diff --git a/judger/src/error.rs b/judger/src/error.rs index 8e3f609f..632ebfac 100644 --- a/judger/src/error.rs +++ b/judger/src/error.rs @@ -2,8 +2,10 @@ pub enum Error { #[error("{0}")] CgroupError(#[from] cgroups_rs::error::Error), - #[error("too few memory")] - LowMemory, - #[error("queue is full")] - QueueFull, + #[error("insufficient `{0}`")] + Insufficient(&'static str), + // #[error("Out Of `{0}`")] + // OutOfResource(crate::sandbox::ResourceKind), + #[error("io error")] + IoError(#[from] std::io::Error), } diff --git a/judger/src/main.rs b/judger/src/main.rs index 6b8f896d..76b2efe7 100644 --- a/judger/src/main.rs +++ b/judger/src/main.rs @@ -1,5 +1,9 @@ -pub mod config; -pub mod error; -pub mod sandbox; +mod config; +mod error; +mod filesystem; +mod sandbox; +mod semaphore; +pub use config::CONFIG; +pub use error::Error; fn main() {} diff --git a/judger/src/sandbox/daemon/handle.rs b/judger/src/sandbox/daemon/handle.rs deleted file mode 100644 index 98e3b23b..00000000 --- a/judger/src/sandbox/daemon/handle.rs +++ /dev/null @@ -1,15 +0,0 @@ -use super::semaphore::*; - -pub struct Handle { - cg_name: String, - memory: Permit, -} - -impl Handle { - pub(super) fn new(cg_name: String, memory: Permit) -> Self { - Self { cg_name, memory } - } - pub fn get_cg_name(&self) -> &str { - &self.cg_name - } -} diff --git a/judger/src/sandbox/daemon/mod.rs b/judger/src/sandbox/daemon/mod.rs deleted file mode 100644 index 13a3ca48..00000000 --- a/judger/src/sandbox/daemon/mod.rs +++ /dev/null @@ -1,33 +0,0 @@ -// FIXME: this module is not well designed, it didn't implement meaningful logic(kind like adapter?) -pub mod handle; -pub(self) mod semaphore; -use std::sync::atomic::{AtomicUsize, Ordering}; - -use crate::error::Error; - -const MAX_WAIT: usize = 10; -static CGROUP_PREFIX: &str = "mdoj."; - -pub struct Daemon { - cg_name_counter: AtomicUsize, - memory: semaphore::Semaphore, -} - -impl Daemon { - pub fn new(memory: u64) -> Self { - Self { - cg_name_counter: AtomicUsize::new(0), - memory: semaphore::Semaphore::new(memory, MAX_WAIT), - } - } - // FIXME: daemon should provide direct interface for sandbox, which should rely on process - pub(super) async fn spawn_handle(&self, resource: u64) -> Result { - let cg_name = format!( - "{}{}", - CGROUP_PREFIX, - self.cg_name_counter.fetch_add(1, Ordering::AcqRel) - ); - let memory = self.memory.get_permit(resource).await?; - Ok(handle::Handle::new(cg_name, memory)) - } -} diff --git a/judger/src/sandbox/mod.rs b/judger/src/sandbox/mod.rs index 665db20d..4253adae 100644 --- a/judger/src/sandbox/mod.rs +++ b/judger/src/sandbox/mod.rs @@ -1,7 +1,31 @@ -mod daemon; -mod limiter; +// mod limiter; +mod monitor; mod process; -pub use daemon::Daemon; +use std::{ffi::OsStr, path::Path, time::Duration}; -pub use self::limiter::{Cpu, Memory}; +pub use self::monitor::{Cpu, Memory, Stat}; +pub use process::*; + +pub trait Context: Limit { + type FS: Filesystem; + fn create_fs(&mut self) -> Self::FS; +} + +pub trait Limit { + fn get_cpu(&mut self) -> Cpu; + fn get_memory(&mut self) -> Memory; + fn get_args(&mut self) -> impl Iterator; + fn get_output_limit(&mut self) -> u64; + // fn get_reserved_size(&mut self) -> u64 { + // self.get_memory().get_reserved_size() + self.get_fs_size() + self.get_output_limit() + // } + fn get_walltime(&mut self) -> Duration { + Duration::from_secs(60 * 30) + } +} + +pub trait Filesystem { + fn mount(&mut self) -> impl AsRef + Send; + fn get_size(&mut self) -> u64; +} diff --git a/judger/src/sandbox/limiter/hier.rs b/judger/src/sandbox/monitor/hier.rs similarity index 93% rename from judger/src/sandbox/limiter/hier.rs rename to judger/src/sandbox/monitor/hier.rs index 7b3ada80..e0d95117 100644 --- a/judger/src/sandbox/limiter/hier.rs +++ b/judger/src/sandbox/monitor/hier.rs @@ -8,7 +8,7 @@ pub enum MonitorKind { lazy_static::lazy_static! { pub static ref MONITER_KIND: MonitorKind = - match crate::config::CONFIG.accounting { + match crate::CONFIG.accounting { Accounting::Auto =>match hierarchies::auto().v2(){ true=>MonitorKind::Cpu, false=>MonitorKind::CpuAcct diff --git a/judger/src/sandbox/limiter/mod.rs b/judger/src/sandbox/monitor/mem_cpu.rs similarity index 64% rename from judger/src/sandbox/limiter/mod.rs rename to judger/src/sandbox/monitor/mem_cpu.rs index 0f1d179b..fa0afc20 100644 --- a/judger/src/sandbox/limiter/mod.rs +++ b/judger/src/sandbox/monitor/mem_cpu.rs @@ -1,58 +1,40 @@ -//! Provide ability to limit resource and retrieve final cpu and memory usage -//! -//! To use this module, you need to create it (provide resource limitation) and mount it, -//! finally, spawn process(it's user's responsibility to ensure the process -//! is spawned within the cgroup) -pub(self) mod hier; -pub(self) mod stat; -pub(self) mod wrapper; - -pub use stat::*; - -use crate::error::Error; -use cgroups_rs::{cgroup_builder::CgroupBuilder, *}; -use hier::*; -use std::sync::Arc; +use super::{stat::*, *}; +use cgroups_rs::{cgroup_builder::CgroupBuilder, Cgroup}; +use std::sync::{atomic::Ordering, Arc}; use tokio::time::*; const MONITOR_ACCURACY: Duration = Duration::from_millis(80); -lazy_static::lazy_static! { - pub static ref CGROUP_V2:bool=hier::MONITER_KIND.heir().v2(); -} - -/// Exit reason of the process -pub enum ExitReason { - TimeOut, - MemoryOut, -} +const CG_PATH_COUNTER: AtomicUsize = AtomicUsize::new(0); -pub async fn monitor(cgroup: Arc, cpu: Cpu) -> ExitReason { +async fn monitor(cgroup: Arc, cpu: Cpu) -> MonitorKind { let wrapper = wrapper::CgroupWrapper::new(&cgroup); - let oom_signal = wrapper.oom(); + let oom_signal = wrapper.oom_signal(); loop { sleep(MONITOR_ACCURACY / 2).await; if let Ok(oom_hint) = oom_signal.try_recv() { log::trace!("oom hint: {}", oom_hint); - return ExitReason::MemoryOut; + return MonitorKind::Memory; } if Cpu::out_of_resources(&cpu, wrapper.cpu()) { - return ExitReason::TimeOut; + return MonitorKind::Cpu; } wrapper.cpu(); } } -pub struct Limiter { +/// monitor resource of cpu and memory +pub struct Monitor { cgroup: Arc, - monitor_task: Option>, + cpu: Cpu, + monitor_task: Option>, } -impl Drop for Limiter { +impl Drop for Monitor { fn drop(&mut self) { if let Some(monitor_task) = &self.monitor_task { monitor_task.abort(); @@ -65,11 +47,12 @@ impl Drop for Limiter { } } -impl Limiter { +impl Monitor { /// create a new limiter and mount at given path - pub fn new_mount(cg_path: &str, cpu: Cpu, mem: Memory) -> Result { + pub fn new((mem, cpu): MemAndCpu) -> Result { + let cg_name = format!("mdoj.{}", CG_PATH_COUNTER.fetch_add(1, Ordering::AcqRel)); let cgroup = Arc::new( - CgroupBuilder::new(cg_path) + CgroupBuilder::new(&cg_name) .memory() .kernel_memory_limit(mem.kernel as i64) .memory_hard_limit(mem.user as i64) @@ -84,13 +67,21 @@ impl Limiter { .build(MONITER_KIND.heir())?, ); - let monitor_task = Some(tokio::spawn(monitor(cgroup.clone(), cpu))); + let monitor_task = Some(tokio::spawn(monitor(cgroup.clone(), cpu.clone()))); Ok(Self { cgroup, monitor_task, + cpu, }) } + pub fn get_cg_path(&self) -> &str { + self.cgroup.path() + } +} + +impl super::Monitor for Monitor { + type Resource = MemAndCpu; /// wait for resource to exhaust /// /// Please remember that [`Drop::drop`] only optimistic kill(`SIGKILL`) @@ -101,22 +92,38 @@ impl Limiter { /// 2. Actively limit(notify) cpu resource is achieved by polling the cgroup, /// the delay require special attention, it is only guaranteed /// to below limitation provided + [`MONITOR_ACCURACY`]. - pub async fn wait_exhaust(&mut self) -> ExitReason { + /// + /// This method is cancellation safe + async fn wait_exhaust(&mut self) -> MonitorKind { let reason = self.monitor_task.take().unwrap().await.unwrap(); // optimistic kill(`SIGKILL`) the process inside self.cgroup.kill().expect("cgroup.kill does not exist"); reason } + fn poll_exhaust(&mut self) -> Option { + debug_assert!(self.cgroup.tasks().is_empty()); + + let wrapper = wrapper::CgroupWrapper::new(&self.cgroup); + + if wrapper.oom() { + return Some(MonitorKind::Memory); + } else if Cpu::out_of_resources(&self.cpu, wrapper.cpu()) { + return Some(MonitorKind::Cpu); + } + None + } /// get the final resource usage /// /// Please remember thatActively limit(notify) cpu resource is achieved /// by polling the cgroup, therefore the delay requirespecial attention, /// it is only guaranteed to below limitation provided + [`MONITOR_ACCURACY`]. - pub async fn stat(self) -> (Cpu, Memory) { + async fn stat(self) -> Self::Resource { // there should be no process left debug_assert!(self.cgroup.tasks().is_empty()); // poll once more to get final stat let wrapper = wrapper::CgroupWrapper::new(&self.cgroup); - (wrapper.cpu(), wrapper.memory()) + (wrapper.memory(), wrapper.cpu()) } } + +// FIXME: mock cgroup and test it diff --git a/judger/src/sandbox/monitor/mod.rs b/judger/src/sandbox/monitor/mod.rs new file mode 100644 index 00000000..0b508626 --- /dev/null +++ b/judger/src/sandbox/monitor/mod.rs @@ -0,0 +1,158 @@ +//! Provide ability to limit resource such as memory limit, cpu limit, walltime limit and output limit +pub(self) mod hier; +pub(self) mod mem_cpu; +pub(self) mod output; +pub(self) mod stat; +pub(self) mod walltime; +pub(self) mod wrapper; + +use std::{fmt::Display, sync::atomic::AtomicUsize, time::Duration}; + +pub use stat::*; +use tokio::io::AsyncRead; + +pub(self) use crate::Error; +pub(self) type Result = std::result::Result; +use hier::*; + +use self::output::Output; + +lazy_static::lazy_static! { + pub static ref CGROUP_V2:bool=hier::MONITER_KIND.heir().v2(); +} + +pub trait Monitor { + type Resource; + async fn wait_exhaust(&mut self) -> MonitorKind; + fn poll_exhaust(&mut self) -> Option; + async fn stat(self) -> Self::Resource; +} + +/// Exit reason of the process +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum MonitorKind { + Memory, + Output, + Walltime, + Cpu, +} + +impl Display for MonitorKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Cpu => "cpu time", + Self::Output => "output limit", + Self::Walltime => "wall time", + Self::Memory => "memory", + } + ) + } +} + +// FIXME: composite +/// composite monitor +pub struct StatMonitor { + mem_cpu: mem_cpu::Monitor, + output: output::Monitor

, + walltime: walltime::Monitor, +} + +impl Monitor for StatMonitor

{ + type Resource = Stat; + + async fn wait_exhaust(&mut self) -> MonitorKind { + tokio::select! { + x = self.mem_cpu.wait_exhaust() => x, + x = self.output.wait_exhaust() => x, + x = self.walltime.wait_exhaust() => x, + } + } + + fn poll_exhaust(&mut self) -> Option { + macro_rules! check_exhaust { + ($f:ident) => { + if let Some(reason) = self.$f.poll_exhaust() { + return Some(reason); + } + }; + } + + check_exhaust!(mem_cpu); + check_exhaust!(output); + check_exhaust!(walltime); + + None + } + + async fn stat(self) -> Self::Resource { + let (memory, cpu) = self.mem_cpu.stat().await; + let output = self.output.stat().await; + let walltime = self.walltime.stat().await; + + Stat { + memory, + cpu, + output, + walltime, + } + } +} + +impl StatMonitor

{ + pub fn new() -> StatMonitorBuilder

{ + StatMonitorBuilder::default() + } + pub fn get_cg_path(&self) -> &str { + self.mem_cpu.get_cg_path() + } + pub fn take_buffer(&mut self) -> Vec { + self.output.take_buffer() + } +} + +pub struct StatMonitorBuilder { + mem_cpu: Option, + output: Option>, + walltime: Option, +} + +impl Default for StatMonitorBuilder

{ + fn default() -> Self { + Self { + mem_cpu: Default::default(), + output: Default::default(), + walltime: Default::default(), + } + } +} + +impl StatMonitorBuilder

{ + pub fn mem_cpu(mut self, mem_cpu: MemAndCpu) -> Result { + self.mem_cpu = Some(mem_cpu::Monitor::new(mem_cpu)?); + Ok(self) + } + pub fn output(mut self, output: Output, stdout: P) -> Self { + self.output = Some(output::Monitor::new(output, stdout)); + self + } + pub fn walltime(mut self, walltime: Duration) -> Self { + self.walltime = Some(walltime::Monitor::new(walltime)); + self + } + pub fn build(self) -> Result> { + Ok(StatMonitor { + mem_cpu: self + .mem_cpu + .expect("mem_cpu is required to be set, use mem_cpu method to set it"), + output: self + .output + .expect("output is required to be set, use output method to set it"), + walltime: self + .walltime + .expect("walltime is required to be set, use walltime method to set it"), + }) + } +} diff --git a/judger/src/sandbox/monitor/output.rs b/judger/src/sandbox/monitor/output.rs new file mode 100644 index 00000000..b15c30a6 --- /dev/null +++ b/judger/src/sandbox/monitor/output.rs @@ -0,0 +1,91 @@ +use std::{pin::Pin, task::*}; + +use futures_core::Future; +use tokio::io::*; + +use super::{Result, *}; + +pub type Output = u64; + +/// A [`Future`] that never resolves. +struct Never; +impl Future for Never { + type Output = (); + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + Poll::Pending + } +} + +pub struct Monitor { + buffer: Vec, + reader: Option>>, + ole: bool, +} + +/// Monitor the output of the process +/// +impl Monitor { + fn inner_new(limit: Output, stdin: I) -> Self { + Self { + buffer: Vec::with_capacity(limit as usize / 4), + reader: Some(BufReader::new(stdin.take(limit))), + ole: false, + } + } + async fn inner_wait_exhaust(&mut self) -> Result { + if let Some(mut reader) = self.reader.take() { + reader.read_to_end(&mut self.buffer).await?; + + let mut inner_reader: I = reader.into_inner().into_inner(); + if inner_reader.read_u8().await.is_ok() { + self.ole = true; + return Ok(MonitorKind::Output); + } + } + Never.await; + unreachable!("Never return") + } + pub fn take_buffer(&mut self) -> Vec { + std::mem::take(&mut self.buffer) + } +} + +impl Monitor

{ + pub fn new(limit: Output, stdout: P) -> Self { + Self::inner_new(limit, stdout) + } +} + +impl super::Monitor for Monitor { + type Resource = Output; + + async fn wait_exhaust(&mut self) -> MonitorKind { + self.inner_wait_exhaust().await.unwrap() + } + fn poll_exhaust(&mut self) -> Option { + if !self.ole { + return None; + } + Some(MonitorKind::Output) + } + /// This method may report incorrect value due to tokio's guarantee of cancellation safety + async fn stat(self) -> Self::Resource { + self.buffer.len() as Output + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[tokio::test] + async fn monitor_output_limit() { + let (mut stdin, stdout) = tokio::io::duplex(1024); + let mut monitor = Monitor::inner_new(9, stdout); + stdin.write_all(b"1234567890").await.unwrap(); + assert_eq!( + MonitorKind::Output, + monitor.inner_wait_exhaust().await.unwrap(), + ); + } +} diff --git a/judger/src/sandbox/limiter/stat.rs b/judger/src/sandbox/monitor/stat.rs similarity index 82% rename from judger/src/sandbox/limiter/stat.rs rename to judger/src/sandbox/monitor/stat.rs index 4d97fd0b..1e5647ca 100644 --- a/judger/src/sandbox/limiter/stat.rs +++ b/judger/src/sandbox/monitor/stat.rs @@ -1,5 +1,18 @@ +use std::time::Duration; + use cgroups_rs::cpuacct::CpuAcct; +use super::output::Output; + +pub type MemAndCpu = (Memory, Cpu); + +pub struct Stat { + pub memory: Memory, + pub cpu: Cpu, + pub output: Output, + pub walltime: Duration, +} + pub struct Memory { pub kernel: u64, pub user: u64, @@ -7,11 +20,12 @@ pub struct Memory { } impl Memory { - pub fn get_reserved(&self) -> u64 { + pub fn get_reserved_size(&self) -> u64 { self.total.min(self.user + self.kernel) } } +#[derive(Clone)] pub struct Cpu { pub kernel: u64, pub user: u64, @@ -60,8 +74,8 @@ mod test { fn cpu_from_raw() { let raw = "usage_usec 158972260000\nuser_usec 115998852000\nsystem_usec 42973408000\ncore_sched.force_idle_usec 0\nnr_periods 0\nnr_throttled 0\nthrottled_usec 0\nnr_bursts 0\nburst_usec 0\n"; let cpu = Cpu::from_raw(raw); - assert_eq!(cpu.kernel, 158972260000); + assert_eq!(cpu.kernel, 42973408000); assert_eq!(cpu.user, 115998852000); - assert_eq!(cpu.total, 42973408000); + assert_eq!(cpu.total, 158972260000); } } diff --git a/judger/src/sandbox/monitor/walltime.rs b/judger/src/sandbox/monitor/walltime.rs new file mode 100644 index 00000000..498739f5 --- /dev/null +++ b/judger/src/sandbox/monitor/walltime.rs @@ -0,0 +1,41 @@ +use tokio::time::*; + +use super::*; + +pub type WallTime = Duration; + +pub struct Monitor { + dur: Duration, + start: Option, +} + +impl Monitor { + pub fn new(dur: Duration) -> Self { + Self { dur, start: None } + } +} + +impl super::Monitor for Monitor { + type Resource = WallTime; + + async fn wait_exhaust(&mut self) -> MonitorKind { + self.start = Some(Instant::now()); + sleep(self.dur).await; + MonitorKind::Walltime + } + fn poll_exhaust(&mut self) -> Option { + if let Some(start) = self.start { + if Instant::now() < start + self.dur { + return None; + } + } + Some(MonitorKind::Walltime) + } + + async fn stat(self) -> Self::Resource { + match self.start { + Some(start) => Instant::now().duration_since(start), + None => Duration::ZERO, + } + } +} diff --git a/judger/src/sandbox/limiter/wrapper.rs b/judger/src/sandbox/monitor/wrapper.rs similarity index 82% rename from judger/src/sandbox/limiter/wrapper.rs rename to judger/src/sandbox/monitor/wrapper.rs index 74bd0c75..64689548 100644 --- a/judger/src/sandbox/limiter/wrapper.rs +++ b/judger/src/sandbox/monitor/wrapper.rs @@ -27,7 +27,7 @@ impl<'a> CgroupWrapper<'a> { } } /// get an receiver(synchronize) for oom event - pub fn oom(&self) -> std::sync::mpsc::Receiver { + pub fn oom_signal(&self) -> std::sync::mpsc::Receiver { let controller = self.cgroup.controller_of::().unwrap(); controller.register_oom_event("mdoj-oom-handler").unwrap() } @@ -46,4 +46,11 @@ impl<'a> CgroupWrapper<'a> { total, } } + /// check if oom + /// + /// use [`oom_signal`] if long polling is required + pub fn oom(&self) -> bool { + let controller: &MemController = self.cgroup.controller_of().unwrap(); + controller.memory_stat().oom_control.oom_kill != 0 + } } diff --git a/judger/src/sandbox/process/corpse.rs b/judger/src/sandbox/process/corpse.rs new file mode 100644 index 00000000..87939f8f --- /dev/null +++ b/judger/src/sandbox/process/corpse.rs @@ -0,0 +1,37 @@ +use std::process::ExitStatus; + +use super::monitor::{MonitorKind, Stat}; + +pub struct Corpse { + /// exit code of signal + pub(super) code: Option, + /// exit reason reported by monitor + pub(super) reason: Option, + pub(super) stdout: Vec, + pub(super) stat: Stat, +} + +impl Corpse { + /// get the exit status of the process + /// + /// if the process is killed by resource limit mechanism + /// (monitor dropped), return the reason + pub fn status(&self) -> Result { + if let Some(reason) = self.reason { + Err(reason) + } else { + Ok(self.code.unwrap()) + } + } + /// get the stdout of the process + /// + /// If the process is killed by resource limit mechanism, + /// the stdout may be incomplete(but ordered) + pub fn stdout(&self) -> &[u8] { + &self.stdout + } + /// get the resource usage of the process + pub fn stat(&self) -> &Stat { + &self.stat + } +} diff --git a/judger/src/sandbox/process/mod.rs b/judger/src/sandbox/process/mod.rs index 3f24a0da..bbbd4f1e 100644 --- a/judger/src/sandbox/process/mod.rs +++ b/judger/src/sandbox/process/mod.rs @@ -1,69 +1,8 @@ -pub(super) mod nsjail; -use std::{ - ffi::{OsStr, OsString}, - path::PathBuf, - str::FromStr, -}; +mod corpse; +mod nsjail; +mod process; -use crate::error::Error; -use tokio::process::*; +use super::*; -use super::{daemon::*, *}; - -lazy_static::lazy_static! { - pub static ref NSJAIL_PATH: PathBuf =PathBuf::from("./nsjail-3.1"); - pub static ref NSJAIL_ARGS: Vec = vec![ - OsString::from("--disable_clone_newuser"), - OsString::from("--disable_clone_newuser"), - OsString::from("--disable_clone_newcgroup"), - OsString::from("--env"), - OsString::from("PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") - ]; - // FIXME: respect config -} - -pub trait Context { - fn get_cpu(&self) -> Cpu; - fn get_memory(&self) -> Memory; - fn create_fs(&self) -> impl AsRef + Send; - fn destroy_fs(&self, fs: impl AsRef + Send); - fn get_args(&self) -> &[OsString]; -} - -impl Daemon { - pub async fn spawn(&self, context: C) -> Result, Error> { - let memory = context.get_memory().get_reserved(); - - let handle = self.spawn_handle(memory).await?; - - Ok(Process { context, handle }) - } -} - -/// A inactive process with resource allocated -pub struct Process { - context: C, - handle: handle::Handle, -} - -impl Process { - pub async fn wait(&mut self) { - let mem = self.context.get_memory(); - let cpu = self.context.get_cpu(); - let rootfs = self.context.create_fs(); - let limiter = limiter::Limiter::new_mount(self.handle.get_cg_name(), cpu, mem).unwrap(); - - let mut args = todo!(); - - // let process=Command::new(NSJAIL_PATH.as_path()) - // .args(&args) - // .spawn() - // .unwrap(); - } -} - -// impl Drop for Process { -// fn drop(&mut self) { -// self.context.destroy_fs(self.rootfs.as_path()); -// } -// } +pub use corpse::Corpse; +pub use process::Process; diff --git a/judger/src/sandbox/process/nsjail.rs b/judger/src/sandbox/process/nsjail.rs index f95b466e..8aeb85c1 100644 --- a/judger/src/sandbox/process/nsjail.rs +++ b/judger/src/sandbox/process/nsjail.rs @@ -1,60 +1,113 @@ -use std::{ffi::OsString, ops::Deref}; +use std::{ + borrow::Cow, + ffi::{OsStr, OsString}, + ops::Deref, + os::unix::ffi::OsStrExt, +}; + +pub static NSJAIL_PATH: &str = "./nsjail-3.1"; + +pub trait Argument { + fn get_args(self) -> impl Iterator>; +} #[derive(Default)] pub struct ArgFactory { - args: Vec, + args: Vec>, } -impl ArgFactory {} +impl ArgFactory { + pub fn add(mut self, arg: impl Argument) -> Self { + self.args.extend(arg.get_args()); + self + } -trait Argument { - fn get_args(self) -> Vec; + pub fn build(self) -> Vec { + self.args.into_iter().map(|x| x.into_owned()).collect() + } } -struct BaseArg; +pub struct BaseArg; impl Argument for BaseArg { - fn get_args(self) -> Vec { + fn get_args(self) -> impl Iterator> { vec![ - OsString::from("--disable_clone_newuser"), - OsString::from("--disable_clone_newuser"), - OsString::from("--disable_clone_newcgroup"), - OsString::from("--env"), - OsString::from("PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"), + Cow::Borrowed(OsStr::from_bytes(b"--disable_clone_newuser")), + Cow::Borrowed(OsStr::from_bytes(b"--disable_clone_newcgroup")), + Cow::Borrowed(OsStr::from_bytes(b"--env")), + Cow::Borrowed(OsStr::from_bytes( + b"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + )), ] + .into_iter() } } -struct CGroupMountArg { - pub cg_path: String, +pub struct CGroupMountArg<'a> { + pub cg_name: &'a str, } -impl Argument for CGroupMountArg { - fn get_args(self) -> Vec { - match super::limiter::CGROUP_V2.deref() { +impl<'a> Argument for CGroupMountArg<'a> { + fn get_args(self) -> impl Iterator> { + // note that there is cg_name, cg_path and cg_mount, they are different! + match super::monitor::CGROUP_V2.deref() { true => vec![ - OsString::from("--cgroup_cpu_parent"), - OsString::from(self.cg_path), + Cow::Borrowed(OsStr::from_bytes(b"--cgroup_cpu_parent")), + Cow::Owned(OsString::from(self.cg_name)), ], false => vec![ - OsString::from("--cgroup_mem_mount"), - format!("/sys/fs/cgroup/memory/{}", self.cg_path.clone()).into(), - OsString::from("--cgroup_cpu_mount"), - format!("/sys/fs/cgroup/cpu/{}", self.cg_path.clone()).into(), - OsString::from("--cgroup_pids_mount"), - format!("/sys/fs/cgroup/pids/{}", self.cg_path).into(), - ] + Cow::Borrowed(OsStr::from_bytes(b"--cgroup_mem_mount")), + Cow::Owned(format!("/sys/fs/cgroup/memory/{}", self.cg_name).into()), + Cow::Borrowed(OsStr::from_bytes(b"--cgroup_cpu_mount")), + Cow::Owned(format!("/sys/fs/cgroup/cpu/{}", self.cg_name).into()), + Cow::Borrowed(OsStr::from_bytes(b"--cgroup_pids_mount")), + Cow::Owned(format!("/sys/fs/cgroup/pids/{}", self.cg_name).into()), + ], } + .into_iter() } } -struct CGroupVersionArg; +pub struct CGroupVersionArg; impl Argument for CGroupVersionArg { - fn get_args(self) -> Vec { - match super::limiter::CGROUP_V2.deref() { - true => vec![OsString::from("--use_cgroupv2")], + fn get_args(self) -> impl Iterator> { + match super::monitor::CGROUP_V2.deref() { + true => vec![Cow::Borrowed(OsStr::from_bytes(b"--use_cgroupv2"))], false => Vec::new(), } + .into_iter() + } +} + +pub struct MountArg<'a> { + pub rootfs: &'a OsStr, +} + +impl<'a> Argument for MountArg<'a> { + fn get_args(self) -> impl Iterator> { + vec![ + Cow::Borrowed(OsStr::from_bytes(b"--rw")), + Cow::Owned(OsString::from(self.rootfs)), + ] + .into_iter() + } +} + +pub struct InnerProcessArg<'a, I> +where + I: Iterator, +{ + pub inner_args: I, +} + +impl<'a, I> Argument for InnerProcessArg<'a, I> +where + I: Iterator, +{ + fn get_args(self) -> impl Iterator> { + vec![Cow::Borrowed(OsStr::from_bytes(b"--"))] + .into_iter() + .chain(self.inner_args.map(|x| Cow::Owned(x.to_owned()))) } } diff --git a/judger/src/sandbox/process/process.rs b/judger/src/sandbox/process/process.rs new file mode 100644 index 00000000..2459b0ed --- /dev/null +++ b/judger/src/sandbox/process/process.rs @@ -0,0 +1,134 @@ +use super::{monitor::*, Context}; +use crate::sandbox::Filesystem; +use crate::Error; +use std::process::Stdio; +use tokio::{ + io::{self, AsyncWriteExt, DuplexStream}, + process::*, + time, +}; + +use super::{corpse::Corpse, nsjail::*}; + +struct MountedProcess { + context: C, + fs: C::FS, +} + +impl MountedProcess { + fn new(mut context: C) -> Self { + Self { + fs: context.create_fs(), + context, + } + } +} + +struct MonitoredProcess { + fs: C::FS, + context: C, + monitor: StatMonitor, + stdout: DuplexStream, +} + +impl MonitoredProcess { + fn new(context: C) -> Result { + let process = MountedProcess::new(context); + let mut context = process.context; + + let mem = context.get_memory(); + let cpu = context.get_cpu(); + let walltime = context.get_walltime(); + let output_limit = context.get_output_limit(); + let (fake_stdout, stdout) = io::duplex(1024); + + Ok(Self { + monitor: StatMonitorBuilder::default() + .mem_cpu((mem, cpu))? + .walltime(walltime) + .output(output_limit, fake_stdout) + .build() + .unwrap(), + stdout, + context, + fs: process.fs, + }) + } +} + +impl From> for Process { + fn from(value: MonitoredProcess) -> Self { + Process { + fs: value.fs, + context: value.context, + monitor: value.monitor, + stdout: value.stdout, + } + } +} + +pub struct Process { + fs: C::FS, + context: C, + monitor: StatMonitor, + stdout: DuplexStream, +} + +impl Process { + pub fn new(context: C) -> Result { + MonitoredProcess::new(context).map(Into::into) + } + fn spawn_raw_process(&mut self) -> Result { + let mut cmd = Command::new(NSJAIL_PATH); + cmd.kill_on_drop(true); + cmd.stdin(Stdio::piped()); + cmd.stdout(Stdio::piped()); + cmd.stderr(Stdio::null()); + + let arg_factory = ArgFactory::default() + .add(BaseArg) + .add(CGroupVersionArg) + .add(CGroupMountArg { + cg_name: self.monitor.get_cg_path(), + }) + .add(MountArg { + rootfs: self.fs.mount().as_ref().as_os_str(), + }) + .add(InnerProcessArg { + inner_args: self.context.get_args(), + }); + + cmd.args(arg_factory.build()); + + Ok(cmd.spawn()?) + } + pub async fn wait(mut self, input: Vec) -> Result { + let mut process = self.spawn_raw_process()?; + + let mut stdin = process.stdin.take().unwrap(); + tokio::spawn(async move { stdin.write_all(&input).await }); + + let stdout = process.stdout.take().unwrap(); + tokio::spawn(async move { + let mut stdout = stdout; + if let Err(err) = io::copy(&mut stdout, &mut self.stdout).await { + log::debug!("Fail forwarding buffer: {}", err); + } + }); + + let mut monitor = self.monitor; + let code = tokio::select! { + _=monitor.wait_exhaust()=>{None}, + x=process.wait()=>{ + time::sleep(time::Duration::from_millis(100)).await; + Some(x?)} + }; + + Ok(Corpse { + code, + reason: monitor.poll_exhaust(), + stdout: monitor.take_buffer(), + stat: monitor.stat().await, + }) + } +} diff --git a/judger/src/sandbox/daemon/semaphore.rs b/judger/src/semaphore.rs similarity index 96% rename from judger/src/sandbox/daemon/semaphore.rs rename to judger/src/semaphore.rs index 0e4a6766..d4725b4e 100644 --- a/judger/src/sandbox/daemon/semaphore.rs +++ b/judger/src/semaphore.rs @@ -6,7 +6,7 @@ use std::{ }, }; -use crate::error::Error as CrateError; +use crate::Error as CrateError; use spin::Mutex; use tokio::sync::oneshot::*; @@ -21,8 +21,8 @@ pub enum Error { impl From for CrateError { fn from(value: Error) -> CrateError { match value { - Error::MaxWaitReached => CrateError::QueueFull, - Error::ImpossibleResourceCondition => CrateError::LowMemory, + Error::MaxWaitReached => CrateError::Insufficient("queuing quota"), + Error::ImpossibleResourceCondition => CrateError::Insufficient("memory"), } } } From fdd0897ed43b812beaa8d0302bed884ec40364dc Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Mon, 29 Apr 2024 22:07:07 +0800 Subject: [PATCH 06/38] feat(Judger): :sparkles: finish test and tarball internal map --- judger/src/filesystem/tar/block.rs | 17 +++- judger/src/filesystem/tar/entry.rs | 54 +++++++++++ judger/src/filesystem/tar/map.rs | 146 +++++++++++++++++++++++++++++ judger/src/filesystem/tar/mod.rs | 1 + judger/src/semaphore.rs | 18 +++- judger/test/nested.tar | Bin 0 -> 4608 bytes judger/test/single_file.tar | Bin 0 -> 2048 bytes 7 files changed, 233 insertions(+), 3 deletions(-) create mode 100644 judger/src/filesystem/tar/map.rs create mode 100644 judger/test/nested.tar create mode 100644 judger/test/single_file.tar diff --git a/judger/src/filesystem/tar/block.rs b/judger/src/filesystem/tar/block.rs index 46163258..1062e86a 100644 --- a/judger/src/filesystem/tar/block.rs +++ b/judger/src/filesystem/tar/block.rs @@ -14,7 +14,7 @@ use tokio::{ sync::{Mutex, OwnedMutexGuard}, }; -#[derive(Default)] +#[derive(Default,Debug)] enum Stage { Reading(OwnedMutexGuard), Seeking(OwnedMutexGuard), @@ -28,6 +28,7 @@ impl Stage { } } +#[derive(Debug)] pub struct TarBlock where F: AsyncRead + AsyncSeek + Unpin, @@ -39,6 +40,18 @@ where stage: Stage, } +impl PartialEq for TarBlock +where + F: AsyncRead + AsyncSeek + Unpin, +{ + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.file, &other.file) + && self.start == other.start + && self.size == other.size + && self.cursor == other.cursor + } +} + impl Clone for TarBlock where F: AsyncRead + AsyncSeek + Unpin, @@ -100,7 +113,7 @@ where cx: &mut Context<'_>, buf: &mut tokio::io::ReadBuf<'_>, ) -> Poll> { - if self.check_bound(){ + if self.check_bound() { return Poll::Ready(Err(io::Error::new( io::ErrorKind::UnexpectedEof, "tar block out of bound", diff --git a/judger/src/filesystem/tar/entry.rs b/judger/src/filesystem/tar/entry.rs index e69de29b..1c13a14a 100644 --- a/judger/src/filesystem/tar/entry.rs +++ b/judger/src/filesystem/tar/entry.rs @@ -0,0 +1,54 @@ +//! collection of entry +//! +//! In tar file, structure is like this: +//! | type | content | ... +//! +//! And we map each type of content to BTreeMap + +use crate::semaphore::*; +use std::{ffi::OsString, sync::Arc}; + +use tokio::{ + fs::File, + io::{AsyncRead, AsyncSeek}, +}; + +use super::block::TarBlock; + +/// Entry from tar file, should be readonly +#[derive(Debug)] +pub enum Entry +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + Link(Arc), + Directory, + File(TarBlock), +} + +impl PartialEq for Entry +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Link(l0), Self::Link(r0)) => l0 == r0, + (Self::File(l0), Self::File(r0)) => l0 == r0, + _ => core::mem::discriminant(self) == core::mem::discriminant(other), + } + } +} + +/// Entry from tar file, it's a replacement of Entry +pub enum MutEntry { + Link(OsString), + Directory, + File(Vec), + Removed, +} + +/// A workaround to not use dynamic dispatch and compact the size of Entry +pub enum MixedEntry { + Mut(Permit, Arc), + Immut(Entry), +} diff --git a/judger/src/filesystem/tar/map.rs b/judger/src/filesystem/tar/map.rs new file mode 100644 index 00000000..26d19050 --- /dev/null +++ b/judger/src/filesystem/tar/map.rs @@ -0,0 +1,146 @@ +use std::{ + collections::BTreeMap, + ffi::OsString, + io::{Error, Read}, + path::Path, + sync::Arc, +}; + +use tar::*; +use tokio::{ + fs::File, + io::{AsyncRead, AsyncSeek}, + sync::Mutex, + task::spawn_blocking, +}; +#[cfg(test)] +use std::io::Cursor; +#[cfg(test)] +use tokio::io::BufReader; + +use super::{block::TarBlock, entry::Entry}; + +pub struct TarMap(BTreeMap>) +where + F: AsyncRead + AsyncSeek + Unpin + 'static; + +impl TarMap where F: AsyncRead + AsyncSeek + Unpin + 'static {} + +fn parse_entry( + map: &mut BTreeMap>, + entry: tar::Entry<'_, R>, + file: &Arc>, +) -> Result<(), Error> +where + F: AsyncRead + AsyncSeek + Unpin, + R: Read, +{ + let path = entry.path()?; + if let Some(link_path) = entry.link_name()? { + map.insert( + path.as_os_str().to_owned(), + Entry::Link(Arc::new(link_path.as_os_str().to_owned())), + ); + } else { + let start = entry.raw_file_position(); + let size = entry.size(); + map.insert( + path.as_os_str().to_owned(), + Entry::File(TarBlock::new(file.clone(), start, size)), + ); + } + let mut ancestors = path.ancestors(); + ancestors.next(); + for ancestor in ancestors { + if !map.contains_key(ancestor.as_os_str()) { + map.insert(ancestor.as_os_str().to_owned(), Entry::Directory); + } + } + + Ok(()) +} + +impl TarMap +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ + pub async fn inner_new(file: F, std_file: impl Read + Send + 'static) -> Result { + let file = Arc::new(Mutex::new(file)); + + let map = spawn_blocking(move || -> Result<_, Error> { + let mut map = BTreeMap::new(); + + let mut archive = Archive::new(std_file); + let entries = archive.entries()?; + for entry in entries { + parse_entry(&mut map, entry?, &file)?; + } + + Ok(map) + }) + .await??; + + Ok(Self(map)) + } +} + +impl TarMap { + pub async fn new(path: impl AsRef + Clone) -> Result { + let file = File::open(path.clone()).await?; + let std_file = File::open(path).await?.into_std().await; + Self::inner_new(file, std_file).await + } +} + +#[cfg(test)] +impl TarMap>> +where + T: AsRef<[u8]> + Send + Unpin + Clone + 'static, +{ + pub async fn test_new(content: T) -> Result { + let file = BufReader::new(Cursor::new(content.clone())); + let std_file = BufReader::new(Cursor::new(content)).into_inner(); + Self::inner_new(file, std_file).await + } +} + +#[cfg(test)] +mod test { + use std::ffi::OsStr; + use tokio::io::AsyncReadExt; + use super::*; + + macro_rules! assert_content { + ($entry:expr, $content:expr) => { + if let Entry::File(ref mut file) = $entry { + let mut buf = vec![0_u8; $content.len()]; + file.read_exact(&mut buf).await.unwrap(); + assert_eq!(&buf, $content); + } else { + panic!("entry is not a file") + } + }; + } + + #[tokio::test] + async fn single_file_map() { + let content = include_bytes!("../../../test/single_file.tar"); + + let mut map = TarMap::test_new(content).await.unwrap(); + + let single_file = map.0.get_mut(OsStr::new("single_file.txt")).unwrap(); + + assert_content!(single_file, b"hello world"); + } + #[tokio::test] + async fn nested_map() { + let content = include_bytes!("../../../test/nested.tar"); + + let mut map = TarMap::test_new(content).await.unwrap(); + + assert_eq!(*map.0.get(OsStr::new("nest")).unwrap(), Entry::Directory); + assert_content!(map.0.get_mut(OsStr::new("nest/a.txt")).unwrap(), b"a"); + assert_content!(map.0.get_mut(OsStr::new("nest/b.txt")).unwrap(), b"b"); + assert_content!(map.0.get_mut(OsStr::new("o.txt")).unwrap(), b"o"); + } +} diff --git a/judger/src/filesystem/tar/mod.rs b/judger/src/filesystem/tar/mod.rs index 842a0aca..7608b5ae 100644 --- a/judger/src/filesystem/tar/mod.rs +++ b/judger/src/filesystem/tar/mod.rs @@ -1,2 +1,3 @@ mod block; mod entry; +mod map; diff --git a/judger/src/semaphore.rs b/judger/src/semaphore.rs index d4725b4e..0f727b71 100644 --- a/judger/src/semaphore.rs +++ b/judger/src/semaphore.rs @@ -111,11 +111,27 @@ impl Semaphore { } } -pub(super) struct Permit { +pub struct Permit { semaphore: Semaphore, permit: u64, } +impl Permit { + #[inline] + pub fn merge(&mut self, mut other: Permit) { + self.permit += other.permit; + other.permit = 0; + } + pub async fn add(&mut self, permit: u64) -> Result<(), Error> { + let other = self.semaphore.get_permit(permit).await?; + self.merge(other); + Ok(()) + } + pub fn count(&self) -> u64 { + self.permit + } +} + impl Drop for Permit { fn drop(&mut self) { self.semaphore.release(self.permit); diff --git a/judger/test/nested.tar b/judger/test/nested.tar new file mode 100644 index 0000000000000000000000000000000000000000..71b83b18de841530198a9460da6f9b3ccf876e75 GIT binary patch literal 4608 zcmeH}!3x7542FFapI}S0#^m;7zxlJZ@(4FK@o zX$64ZL4D`U9vUY?>y34uG@!A@D(%{~wO4V>_#edo tJ=SsVAtD;X5?I?>znuG@??0h0|9^H8|Mys$wTFmk2mv7=1cbnF1m3y_S$+Tj literal 0 HcmV?d00001 diff --git a/judger/test/single_file.tar b/judger/test/single_file.tar new file mode 100644 index 0000000000000000000000000000000000000000..0d29253d62bac564a4e74066df5cd2af230cdcda GIT binary patch literal 2048 zcmXTU%uCNnjZe$WN!2T_A3&)_FSz26@SOj!hYGQGI9=aqFpO8GnCm_#(fC7@vLCwra&B@7E cD9 Date: Mon, 29 Apr 2024 22:27:14 +0800 Subject: [PATCH 07/38] cargo clippy --- judger/src/filesystem/tar/block.rs | 9 ++++----- judger/src/filesystem/tar/map.rs | 10 +++++----- judger/src/sandbox/mod.rs | 3 +-- judger/src/sandbox/monitor/mod.rs | 16 ++++++++-------- judger/src/sandbox/monitor/wrapper.rs | 2 +- judger/src/sandbox/process/mod.rs | 2 -- 6 files changed, 19 insertions(+), 23 deletions(-) diff --git a/judger/src/filesystem/tar/block.rs b/judger/src/filesystem/tar/block.rs index 1062e86a..700009f9 100644 --- a/judger/src/filesystem/tar/block.rs +++ b/judger/src/filesystem/tar/block.rs @@ -1,5 +1,4 @@ use std::{ - future::IntoFuture, io::SeekFrom, ops::DerefMut, pin::{pin, Pin}, @@ -14,7 +13,7 @@ use tokio::{ sync::{Mutex, OwnedMutexGuard}, }; -#[derive(Default,Debug)] +#[derive(Default, Debug)] enum Stage { Reading(OwnedMutexGuard), Seeking(OwnedMutexGuard), @@ -59,9 +58,9 @@ where fn clone(&self) -> Self { Self { file: self.file.clone(), - start: self.start.clone(), - size: self.size.clone(), - cursor: self.cursor.clone(), + start: self.start, + size: self.size, + cursor: self.cursor, stage: Stage::Done, } } diff --git a/judger/src/filesystem/tar/map.rs b/judger/src/filesystem/tar/map.rs index 26d19050..300f671d 100644 --- a/judger/src/filesystem/tar/map.rs +++ b/judger/src/filesystem/tar/map.rs @@ -6,17 +6,17 @@ use std::{ sync::Arc, }; +#[cfg(test)] +use std::io::Cursor; use tar::*; +#[cfg(test)] +use tokio::io::BufReader; use tokio::{ fs::File, io::{AsyncRead, AsyncSeek}, sync::Mutex, task::spawn_blocking, }; -#[cfg(test)] -use std::io::Cursor; -#[cfg(test)] -use tokio::io::BufReader; use super::{block::TarBlock, entry::Entry}; @@ -106,9 +106,9 @@ where #[cfg(test)] mod test { + use super::*; use std::ffi::OsStr; use tokio::io::AsyncReadExt; - use super::*; macro_rules! assert_content { ($entry:expr, $content:expr) => { diff --git a/judger/src/sandbox/mod.rs b/judger/src/sandbox/mod.rs index 4253adae..1f3a463a 100644 --- a/judger/src/sandbox/mod.rs +++ b/judger/src/sandbox/mod.rs @@ -4,8 +4,7 @@ mod process; use std::{ffi::OsStr, path::Path, time::Duration}; -pub use self::monitor::{Cpu, Memory, Stat}; -pub use process::*; +pub use self::monitor::{Cpu, Memory}; pub trait Context: Limit { type FS: Filesystem; diff --git a/judger/src/sandbox/monitor/mod.rs b/judger/src/sandbox/monitor/mod.rs index 0b508626..4f29b806 100644 --- a/judger/src/sandbox/monitor/mod.rs +++ b/judger/src/sandbox/monitor/mod.rs @@ -1,18 +1,18 @@ //! Provide ability to limit resource such as memory limit, cpu limit, walltime limit and output limit -pub(self) mod hier; -pub(self) mod mem_cpu; -pub(self) mod output; -pub(self) mod stat; -pub(self) mod walltime; -pub(self) mod wrapper; + mod hier; + mod mem_cpu; + mod output; + mod stat; + mod walltime; + mod wrapper; use std::{fmt::Display, sync::atomic::AtomicUsize, time::Duration}; pub use stat::*; use tokio::io::AsyncRead; -pub(self) use crate::Error; -pub(self) type Result = std::result::Result; + use crate::Error; + type Result = std::result::Result; use hier::*; use self::output::Output; diff --git a/judger/src/sandbox/monitor/wrapper.rs b/judger/src/sandbox/monitor/wrapper.rs index 64689548..b182aaf2 100644 --- a/judger/src/sandbox/monitor/wrapper.rs +++ b/judger/src/sandbox/monitor/wrapper.rs @@ -36,7 +36,7 @@ impl<'a> CgroupWrapper<'a> { let controller = self.cgroup.controller_of::().unwrap(); let kusage = controller.kmem_stat(); - let kernel = kusage.max_usage_in_bytes as u64; + let kernel = kusage.max_usage_in_bytes; let user = controller.memory_stat().max_usage_in_bytes; let total = kernel + user; diff --git a/judger/src/sandbox/process/mod.rs b/judger/src/sandbox/process/mod.rs index bbbd4f1e..47c81526 100644 --- a/judger/src/sandbox/process/mod.rs +++ b/judger/src/sandbox/process/mod.rs @@ -4,5 +4,3 @@ mod process; use super::*; -pub use corpse::Corpse; -pub use process::Process; From e3793d92b0be42b6f48bdc97149f6693a8065394 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Mon, 29 Apr 2024 22:29:44 +0800 Subject: [PATCH 08/38] cargo clippy --- backend/src/init/logger.rs | 2 +- backend/src/util/error.rs | 2 +- frontend/src/components/select.rs | 1 - frontend/src/config.rs | 1 - frontend/src/grpc.rs | 1 - frontend/src/main.rs | 1 - frontend/src/pages/login.rs | 3 +-- frontend/src/pages/problems.rs | 5 ++--- 8 files changed, 5 insertions(+), 11 deletions(-) diff --git a/backend/src/init/logger.rs b/backend/src/init/logger.rs index cbd448ef..31b69471 100644 --- a/backend/src/init/logger.rs +++ b/backend/src/init/logger.rs @@ -152,5 +152,5 @@ pub fn init(config: &GlobalConfig) -> super::Result { _ => Level::INFO, }; - init_tracing_subscriber(level, config.opentelemetry.as_ref().map(|x| x.as_str())) + init_tracing_subscriber(level, config.opentelemetry.as_deref()) } diff --git a/backend/src/util/error.rs b/backend/src/util/error.rs index 7cddb8c1..5c410fb0 100644 --- a/backend/src/util/error.rs +++ b/backend/src/util/error.rs @@ -143,7 +143,7 @@ impl ToString for Tracing { "trace_id: {}, span_id: {}, log_id: {}", self.trace_id, self.span_id, - self.log_id.to_string() + self.log_id ) } } diff --git a/frontend/src/components/select.rs b/frontend/src/components/select.rs index bb12262d..ca8db7d5 100644 --- a/frontend/src/components/select.rs +++ b/frontend/src/components/select.rs @@ -1,6 +1,5 @@ use leptos::*; -use super::Merge; #[derive(Debug, Clone, PartialEq, Eq)] struct SelectedValue(ReadSignal); diff --git a/frontend/src/config.rs b/frontend/src/config.rs index d4f2d42f..310d28f2 100644 --- a/frontend/src/config.rs +++ b/frontend/src/config.rs @@ -1,4 +1,3 @@ -use std::sync::OnceLock; use cfg_if::cfg_if; use leptos::*; diff --git a/frontend/src/grpc.rs b/frontend/src/grpc.rs index 4fbf28bd..abd141d8 100644 --- a/frontend/src/grpc.rs +++ b/frontend/src/grpc.rs @@ -1,5 +1,4 @@ pub use grpc::backend::*; -use tonic::Code; use crate::{config::server_config, error::*}; diff --git a/frontend/src/main.rs b/frontend/src/main.rs index 11ac35cf..04f4f305 100644 --- a/frontend/src/main.rs +++ b/frontend/src/main.rs @@ -1,4 +1,3 @@ -use anyhow::Result; #[cfg(feature = "ssr")] #[actix_web::main] diff --git a/frontend/src/pages/login.rs b/frontend/src/pages/login.rs index d0c8d502..2d00b28d 100644 --- a/frontend/src/pages/login.rs +++ b/frontend/src/pages/login.rs @@ -50,8 +50,7 @@ pub fn Login() -> impl IntoView { let error_msg = move || { submit.value()() - .map(|r| r.err()) - .flatten() + .and_then(|r| r.err()) .map(|e| match e { ErrorKind::NotFound => { "Username or password is incorrect".to_owned() diff --git a/frontend/src/pages/problems.rs b/frontend/src/pages/problems.rs index a1d71601..b3abf59a 100644 --- a/frontend/src/pages/problems.rs +++ b/frontend/src/pages/problems.rs @@ -1,12 +1,11 @@ -use std::{borrow::BorrowMut, default, ops::DerefMut, rc::Rc}; -use leptos::{html::s, *}; +use leptos::{*}; use leptos_router::*; use serde::{Deserialize, Serialize}; use crate::{ components::*, - config::{self, use_token, WithToken}, + config::{use_token, WithToken}, error::*, grpc::{problem_set_client::*, *}, pages::{error_fallback, problems::toggle::Toggle}, From eea0b797e70811079dde04937e39cf2a05d84f3b Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Wed, 1 May 2024 01:30:07 +0800 Subject: [PATCH 09/38] feat(Judger): :construction: draft filesystem and add PathTree(both locked and without lock) --- Cargo.lock | 100 +++++++--- backend/src/util/error.rs | 4 +- frontend/src/components/select.rs | 1 - frontend/src/config.rs | 1 - frontend/src/main.rs | 1 - frontend/src/pages/login.rs | 14 +- frontend/src/pages/problems.rs | 3 +- judger/Cargo.toml | 6 +- judger/src/filesystem/fuse.rs | 30 +++ judger/src/filesystem/macro_.rs | 25 +++ judger/src/filesystem/mod.rs | 10 + judger/src/filesystem/overlay/block.rs | 248 +++++++++++++++++++++++++ judger/src/filesystem/overlay/entry.rs | 38 ++++ judger/src/filesystem/overlay/mod.rs | 4 + judger/src/filesystem/path_tree.rs | 216 +++++++++++++++++++++ judger/src/filesystem/table/handle.rs | 39 ++++ judger/src/filesystem/table/inode.rs | 36 ++++ judger/src/filesystem/table/mod.rs | 5 + judger/src/filesystem/tar/block.rs | 97 ++++------ judger/src/filesystem/tar/entry.rs | 34 ++-- judger/src/filesystem/tar/map.rs | 2 +- judger/src/filesystem/tar/mod.rs | 4 + judger/src/sandbox/monitor/mod.rs | 16 +- judger/src/sandbox/process/mod.rs | 1 - 24 files changed, 809 insertions(+), 126 deletions(-) create mode 100644 judger/src/filesystem/fuse.rs create mode 100644 judger/src/filesystem/macro_.rs create mode 100644 judger/src/filesystem/overlay/block.rs create mode 100644 judger/src/filesystem/overlay/entry.rs create mode 100644 judger/src/filesystem/overlay/mod.rs create mode 100644 judger/src/filesystem/path_tree.rs create mode 100644 judger/src/filesystem/table/handle.rs create mode 100644 judger/src/filesystem/table/inode.rs create mode 100644 judger/src/filesystem/table/mod.rs diff --git a/Cargo.lock b/Cargo.lock index b8dc3fa1..220dbda5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -477,6 +477,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-notify" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db96c36382f56ea91e5d012c4c7170046c7731449daa46655cbcabc594c8bee6" +dependencies = [ + "event-listener 4.0.3", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-recursion" version = "1.0.5" @@ -1349,6 +1360,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "cstr" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68523903c8ae5aacfa32a0d9ae60cadeb764e1da14ee0d26b1f3089f13a54636" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "darling" version = "0.14.4" @@ -1889,18 +1910,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] -name = "fuser" -version = "0.14.0" +name = "fuse3" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e697f6f62c20b6fad1ba0f84ae909f25971cf16e735273524e3977c94604cf8" +checksum = "2a1df9aa50861c978c49522864d986093bb773558d431ea54c9e258ad3d6ce28" dependencies = [ + "async-notify", + "bincode", + "bytes", + "cstr", + "futures-channel", + "futures-util", "libc", - "log", - "memchr", - "page_size", - "pkg-config", - "smallvec", - "zerocopy", + "nix 0.28.0", + "serde", + "slab", + "tokio", + "tracing", + "trait-make", + "which 6.0.1", ] [[package]] @@ -2875,7 +2903,7 @@ dependencies = [ "crossbeam", "derive_builder", "env_logger", - "fuser", + "fuse3", "futures-core", "grpc", "lazy_static", @@ -3368,6 +3396,15 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "migration" version = "0.1.0" @@ -3470,6 +3507,7 @@ dependencies = [ "cfg-if", "cfg_aliases", "libc", + "memoffset", ] [[package]] @@ -3790,16 +3828,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56d80efc4b6721e8be2a10a5df21a30fa0b470f1539e53d8b4e6e75faf938b63" -[[package]] -name = "page_size" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "parking" version = "2.2.0" @@ -4155,7 +4183,7 @@ dependencies = [ "regex", "syn 2.0.52", "tempfile", - "which", + "which 4.4.2", ] [[package]] @@ -6271,6 +6299,17 @@ dependencies = [ "web-sys", ] +[[package]] +name = "trait-make" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96cbd06a7b648f1603e60d75d9ed295d096b340d30e9f9324f4b512b5d40cd92" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -6624,6 +6663,18 @@ dependencies = [ "rustix 0.38.31", ] +[[package]] +name = "which" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7" +dependencies = [ + "either", + "home", + "rustix 0.38.31", + "winsafe", +] + [[package]] name = "whoami" version = "1.5.0" @@ -6840,6 +6891,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "wyz" version = "0.5.1" @@ -6887,7 +6944,6 @@ version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" dependencies = [ - "byteorder", "zerocopy-derive", ] diff --git a/backend/src/util/error.rs b/backend/src/util/error.rs index 5c410fb0..46b24d9c 100644 --- a/backend/src/util/error.rs +++ b/backend/src/util/error.rs @@ -141,9 +141,7 @@ impl ToString for Tracing { fn to_string(&self) -> String { format!( "trace_id: {}, span_id: {}, log_id: {}", - self.trace_id, - self.span_id, - self.log_id + self.trace_id, self.span_id, self.log_id ) } } diff --git a/frontend/src/components/select.rs b/frontend/src/components/select.rs index ca8db7d5..416ca614 100644 --- a/frontend/src/components/select.rs +++ b/frontend/src/components/select.rs @@ -1,6 +1,5 @@ use leptos::*; - #[derive(Debug, Clone, PartialEq, Eq)] struct SelectedValue(ReadSignal); diff --git a/frontend/src/config.rs b/frontend/src/config.rs index 310d28f2..71b3dc26 100644 --- a/frontend/src/config.rs +++ b/frontend/src/config.rs @@ -1,4 +1,3 @@ - use cfg_if::cfg_if; use leptos::*; use leptos_use::{utils::JsonCodec, *}; diff --git a/frontend/src/main.rs b/frontend/src/main.rs index 04f4f305..1e1594b7 100644 --- a/frontend/src/main.rs +++ b/frontend/src/main.rs @@ -1,4 +1,3 @@ - #[cfg(feature = "ssr")] #[actix_web::main] async fn main() -> Result<()> { diff --git a/frontend/src/pages/login.rs b/frontend/src/pages/login.rs index 2d00b28d..1e0cb2b6 100644 --- a/frontend/src/pages/login.rs +++ b/frontend/src/pages/login.rs @@ -49,14 +49,12 @@ pub fn Login() -> impl IntoView { }); let error_msg = move || { - submit.value()() - .and_then(|r| r.err()) - .map(|e| match e { - ErrorKind::NotFound => { - "Username or password is incorrect".to_owned() - } - e => e.to_string(), - }) + submit.value()().and_then(|r| r.err()).map(|e| match e { + ErrorKind::NotFound => { + "Username or password is incorrect".to_owned() + } + e => e.to_string(), + }) }; view! { diff --git a/frontend/src/pages/problems.rs b/frontend/src/pages/problems.rs index b3abf59a..4f317f71 100644 --- a/frontend/src/pages/problems.rs +++ b/frontend/src/pages/problems.rs @@ -1,5 +1,4 @@ - -use leptos::{*}; +use leptos::*; use leptos_router::*; use serde::{Deserialize, Serialize}; diff --git a/judger/Cargo.toml b/judger/Cargo.toml index c0c9c46e..06cc1624 100644 --- a/judger/Cargo.toml +++ b/judger/Cargo.toml @@ -15,15 +15,19 @@ prost-types = { workspace = true } thiserror = "1.0.40" toml = { workspace = true } derive_builder = { workspace = true } -fuser = "0.14.0" tar = "0.4.40" grpc = { path = "../grpc" } crossbeam = "0.8.4" lazy_static = "1.4.0" +[dependencies.fuse3] +version = "0.7.1" +features = ["tokio-runtime", "unprivileged"] + [dependencies.rustix] version = "0.38.28" features = ["process", "thread"] + [dependencies.uuid] version = "1.6.1" features = ["serde"] diff --git a/judger/src/filesystem/fuse.rs b/judger/src/filesystem/fuse.rs new file mode 100644 index 00000000..4f5ba13a --- /dev/null +++ b/judger/src/filesystem/fuse.rs @@ -0,0 +1,30 @@ +// use fuse3::{raw::{reply::*, Request}, Errno}; +// use futures_core::Future; +// use tokio::fs::File; + +// use super::table::{HandleTable, INodeTable}; + +// pub struct FileSystem { +// inode_table: INodeTable, +// handle_table: HandleTable, +// } + +// impl fuse3::raw::Filesystem for FileSystem { +// fn init(&self, req: Request) -> impl Future> + Send { +// todo!() +// } + +// fn destroy(&self, req: Request) -> impl Future + Send { +// todo!() +// } + +// #[doc = r" dir entry stream given by [`readdir`][Filesystem::readdir]."] +// type DirEntryStream<'a> +// where +// Self: 'a; + +// #[doc = r" dir entry plus stream given by [`readdirplus`][Filesystem::readdirplus]."] +// type DirEntryPlusStream<'a> +// where +// Self: 'a; +// } diff --git a/judger/src/filesystem/macro_.rs b/judger/src/filesystem/macro_.rs new file mode 100644 index 00000000..953391a9 --- /dev/null +++ b/judger/src/filesystem/macro_.rs @@ -0,0 +1,25 @@ +macro_rules! chain_poll { + ($poll:expr) => {{ + let poll_ = $poll; + if poll_.is_pending() { + return Poll::Pending; + } + match poll_ { + Poll::Ready(x) => x, + Poll::Pending => unreachable!(), + } + }}; +} +macro_rules! report_poll { + ($ans:expr) => {{ + if let Err(err) = $ans { + return Poll::Ready(Err(err)); + } + match $ans { + Ok(x) => x, + Err(_) => unreachable!(), + } + }}; +} + +pub(crate) use {chain_poll, report_poll}; diff --git a/judger/src/filesystem/mod.rs b/judger/src/filesystem/mod.rs index c6394bcf..e1b3a3de 100644 --- a/judger/src/filesystem/mod.rs +++ b/judger/src/filesystem/mod.rs @@ -1 +1,11 @@ +mod fuse; +mod macro_; +mod overlay; +mod path_tree; +mod table; mod tar; + +type INODE = u64; +type HANDLE = u64; + +use path_tree::*; diff --git a/judger/src/filesystem/overlay/block.rs b/judger/src/filesystem/overlay/block.rs new file mode 100644 index 00000000..05f95df6 --- /dev/null +++ b/judger/src/filesystem/overlay/block.rs @@ -0,0 +1,248 @@ +use crate::filesystem::macro_::{chain_poll, report_poll}; +use std::future::Future; +use std::io; +use std::{ + io::SeekFrom, + ops::{Deref, DerefMut}, + pin::{pin, Pin}, + sync::Arc, + task::{Context, Poll}, +}; +use tokio::{ + io::{AsyncRead, AsyncSeek, AsyncWrite}, + sync::{Mutex, OwnedMutexGuard}, +}; + +const MEMBLOCK_BLOCKSIZE: usize = 4096; + +#[derive(Debug, Default)] +enum MemStage { + Seeking(OwnedMutexGuard>, SeekFrom), + SeekStart(SeekFrom), + Reading(OwnedMutexGuard>), + // Writing(OwnedMutexGuard>), + #[default] + Done, +} + +impl MemStage { + fn take(&mut self) -> Self { + std::mem::take(self) + } +} + +#[derive(Default)] +pub struct MemBlock { + data: Arc>>, + cursor: usize, + stage: MemStage, + write_buffer: Vec, +} + +impl Clone for MemBlock { + fn clone(&self) -> Self { + Self { + data: self.data.clone(), + cursor: self.cursor.clone(), + stage: MemStage::default(), + write_buffer: self.write_buffer.clone(), + } + } +} + +impl MemBlock { + pub fn new(data: Vec) -> Self { + Self { + data: Arc::new(Mutex::new(data)), + cursor: 0, + stage: MemStage::Done, + write_buffer: Vec::new(), + } + } +} + +impl AsyncRead for MemBlock { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> Poll> { + match self.stage.take() { + MemStage::Reading(locked) => { + if locked.len() < self.cursor { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "mem block out of bound", + ))); + } + let slice = &locked.deref()[self.cursor + ..(self.cursor + MEMBLOCK_BLOCKSIZE.min(buf.remaining())).min(locked.len())]; + self.cursor += slice.len(); + buf.put_slice(slice); + return Poll::Ready(Ok(())); + } + _ => { + let locked = chain_poll!(pin!(self.data.clone().lock_owned()).poll(cx)); + self.as_mut().stage = MemStage::Reading(locked); + cx.waker().wake_by_ref(); + } + } + Poll::Pending + } +} + +impl AsyncSeek for MemBlock { + fn start_seek(mut self: Pin<&mut Self>, position: SeekFrom) -> io::Result<()> { + self.stage = MemStage::SeekStart(position); + Ok(()) + } + + fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.stage.take() { + MemStage::SeekStart(position) => { + let locked = chain_poll!(pin!(self.data.clone().lock_owned()).poll(cx)); + self.stage = MemStage::Seeking(locked, position); + cx.waker().wake_by_ref(); + } + MemStage::Seeking(locked, position) => { + let size = locked.len() as i64; + let new_position = match position { + SeekFrom::Start(x) => x.try_into().unwrap_or_default(), + SeekFrom::End(x) => size.saturating_sub(x), + SeekFrom::Current(x) => (self.cursor as i64).saturating_add(x), + }; + if new_position < 0 { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidInput, + "invalid seek position", + ))); + } + if new_position > size { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "mem block out of bound", + ))); + } + self.cursor = new_position as usize; + return Poll::Ready(Ok(self.cursor as u64)); + } + _ => { + return Poll::Ready(Ok(self.cursor as u64)); + } + } + Poll::Pending + } +} + +impl AsyncWrite for MemBlock { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + self.write_buffer.extend_from_slice(&buf); + if self.write_buffer.len() >= MEMBLOCK_BLOCKSIZE { + report_poll!(chain_poll!(self.as_mut().poll_flush(cx))); + } + Poll::Ready(Ok(buf.len())) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut locked = chain_poll!(pin!(self.data.clone().lock_owned()).poll(cx)); + locked.extend_from_slice(&self.write_buffer); + self.write_buffer.clear(); + Poll::Ready(Ok(())) + } + + fn poll_shutdown( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + self.as_mut().poll_flush(cx) + } +} + +#[cfg(test)] +mod test { + use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; + + use super::*; + #[tokio::test] + async fn mem_normal_read() { + let data = b"hello world".to_vec(); + let mut block = MemBlock::new(data); + let mut buf = [0_u8; 11]; + block.read_exact(&mut buf).await.unwrap(); + + assert_eq!(buf, *b"hello world"); + } + #[tokio::test] + async fn mem_end_of_file_read() { + let mut block = MemBlock::new(b"1234".to_vec()); + let mut buf = Vec::new(); + block.read_to_end(&mut buf).await.unwrap(); + + assert_eq!(&*buf, b"1234"); + } + #[tokio::test] + async fn mem_start_seek() { + let mut block = MemBlock::new(b"111hello world1111".to_vec()); + block.seek(SeekFrom::Start(3)).await.unwrap(); + + let mut buf = [0_u8; 11]; + block.read_exact(&mut buf).await.unwrap(); + + assert_eq!(buf, *b"hello world"); + } + #[tokio::test] + async fn mem_end_seek() { + let mut block = MemBlock::new(b"111hello world1111".to_vec()); + block.seek(SeekFrom::End(15)).await.unwrap(); + + let mut buf = [0_u8; 11]; + block.read_exact(&mut buf).await.unwrap(); + + assert_eq!(buf, *b"hello world"); + } + #[tokio::test] + async fn mem_rel_seek() { + let mut block = MemBlock::new(b"111hello world1111".to_vec()); + for _ in 0..3 { + block.seek(SeekFrom::Current(1)).await.unwrap(); + } + + let mut buf = [0_u8; 11]; + block.read_exact(&mut buf).await.unwrap(); + + assert_eq!(buf, *b"hello world"); + } + #[tokio::test] + async fn mem_normal_write() { + let mut block = MemBlock::default(); + block.write_all(b"hello").await.unwrap(); + block.write_all(b" ").await.unwrap(); + block.write_all(b"world").await.unwrap(); + + assert!(block.read_u8().await.is_err()); + + block.flush().await.unwrap(); + + let mut buf = [0_u8; 11]; + block.read_exact(&mut buf).await.unwrap(); + + assert_eq!(buf, *b"hello world"); + } + #[tokio::test] + async fn mem_multi_read() { + let block = MemBlock::new(b"hello world".to_vec()); + + for _ in 0..3000 { + let mut block = block.clone(); + tokio::spawn(async move { + let mut buf = [0_u8; 11]; + block.read_exact(&mut buf).await.unwrap(); + assert_eq!(buf, *b"hello world"); + }); + } + } +} diff --git a/judger/src/filesystem/overlay/entry.rs b/judger/src/filesystem/overlay/entry.rs new file mode 100644 index 00000000..39989a36 --- /dev/null +++ b/judger/src/filesystem/overlay/entry.rs @@ -0,0 +1,38 @@ +use std::{ffi::OsString, sync::Arc}; + +use tokio::io::{AsyncRead, AsyncSeek}; + +use crate::{ + filesystem::{tar::Entry, INODE}, + semaphore::*, +}; + +use super::block::MemBlock; + +pub type ArcEntry = Arc>>; + +/// Entry from tar file, it's a replacement of Entry +pub enum MutEntry { + SymLink(OsString), + HardLink(INODE), + Directory, + File(MemBlock), + Removed, +} + +/// A workaround to not use dynamic dispatch and compact the size of Entry +pub enum MixedEntry +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + Mut(Permit, Arc), + Immut(Entry), +} + +// operation that's per file +// impl MixedEntry { +// pub async fn read(){} +// pub async fn write(){} +// pub async fn flush(){} +// pub async fn close(){} +// } diff --git a/judger/src/filesystem/overlay/mod.rs b/judger/src/filesystem/overlay/mod.rs new file mode 100644 index 00000000..faf25867 --- /dev/null +++ b/judger/src/filesystem/overlay/mod.rs @@ -0,0 +1,4 @@ +mod block; +mod entry; + +pub use entry::*; diff --git a/judger/src/filesystem/path_tree.rs b/judger/src/filesystem/path_tree.rs new file mode 100644 index 00000000..63097620 --- /dev/null +++ b/judger/src/filesystem/path_tree.rs @@ -0,0 +1,216 @@ +use std::{ + collections::HashMap, + ffi::OsString, + ops::{Deref, DerefMut}, + path::{Component, Path}, + sync::Arc, +}; + +use tokio::sync::{OwnedRwLockReadGuard, OwnedRwLockWriteGuard, RwLock}; + +type ArcRwLock = Arc>; + +struct LockNode { + children: HashMap>>, + value: Option, +} + +impl Default for LockNode { + fn default() -> Self { + Self { + children: Default::default(), + value: Default::default(), + } + } +} + +struct WriteNodeGuard(OwnedRwLockWriteGuard>); + +impl Deref for WriteNodeGuard { + type Target = V; + + fn deref(&self) -> &Self::Target { + self.0.value.as_ref().unwrap() + } +} + +impl DerefMut for WriteNodeGuard { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.value.as_mut().unwrap() + } +} + +struct ReadNodeGuard(OwnedRwLockReadGuard>); + +impl Deref for ReadNodeGuard { + type Target = V; + + fn deref(&self) -> &Self::Target { + self.0.value.as_ref().unwrap() + } +} + +/// Path tree with partial locking +pub struct LockPathTree(ArcRwLock>); + +impl LockPathTree { + pub fn new() -> Self { + LockPathTree(Arc::new(RwLock::new(LockNode::default()))) + } + + #[inline] + async fn get_child(&self, path: impl AsRef) -> Option>> { + let mut root = self.0.clone(); + for component in path.as_ref().components() { + match component { + Component::Prefix(x) => unreachable!("Windows only: {:?}", x), + Component::RootDir | Component::CurDir | Component::ParentDir => { + log::trace!("RootDir | CurDir | ParentDir"); + } + Component::Normal(x) => { + let child = root.read().await.children.get(x)?.clone(); + root = child; + } + } + } + Some(root) + } + pub async fn insert_path(&self, path: impl AsRef, value: V) -> Option { + let mut root = self.0.clone(); + for component in path.as_ref().components() { + match component { + Component::Prefix(x) => unreachable!("Windows only: {:?}", x), + Component::RootDir | Component::CurDir | Component::ParentDir => { + log::trace!("RootDir | CurDir | ParentDir"); + } + Component::Normal(x) => { + let child = if let Some(child) = root.read().await.children.get(x) { + child.clone() + } else { + let node = Arc::new(RwLock::new(LockNode::default())); + root.write() + .await + .children + .insert(x.to_os_string(), node.clone()); + node + }; + root = child; + } + } + } + let mut root = root.write().await; + root.value.replace(value) + } + pub async fn get_mut( + &self, + path: impl AsRef, + ) -> Option> { + match self.get_child(path).await { + Some(root) => Some(WriteNodeGuard(root.write_owned().await)), + None => None, + } + } + pub async fn get(&self, path: impl AsRef) -> Option + Deref> { + match self.get_child(path).await { + Some(root) => Some(ReadNodeGuard(root.read_owned().await)), + None => None, + } + } +} + +struct Node { + children: HashMap>, + value: Option, +} + +impl Default for Node { + fn default() -> Self { + Self { + children: Default::default(), + value: Default::default(), + } + } +} + +pub struct PathTree(Node); + +impl PathTree { + pub fn new() -> Self { + PathTree(Node { + children: HashMap::new(), + value: None, + }) + } + #[inline] + fn get_mut_child<'a>(&'a mut self, path: impl AsRef) -> Option<&'a mut Node> { + let mut root = &mut self.0; + for component in path.as_ref().components() { + match component { + Component::Prefix(x) => unreachable!("Windows only: {:?}", x), + Component::RootDir | Component::CurDir | Component::ParentDir => { + log::trace!("RootDir | CurDir | ParentDir"); + } + Component::Normal(x) => { + root = root.children.get_mut(x)?; + } + } + } + Some(root) + } + pub fn insert_path(&mut self, path: impl AsRef, value: V) -> Option { + let mut root = &mut self.0; + for component in path.as_ref().components() { + match component { + Component::Prefix(x) => unreachable!("Windows only: {:?}", x), + Component::RootDir | Component::CurDir | Component::ParentDir => { + log::trace!("RootDir | CurDir | ParentDir"); + } + Component::Normal(x) => { + // bypass borrow checker + match root.children.contains_key(x) { + true => { + root = root.children.get_mut(x).unwrap(); + } + false => { + let node = Node { + children: HashMap::new(), + value: None, + }; + root.children.insert(x.to_os_string(), node); + root = root.children.get_mut(x).unwrap(); + } + }; + } + } + } + root.value.replace(value) + } + #[inline] + fn get_child(&self, path: impl AsRef) -> Option<&Node> { + let mut root = &self.0; + for component in path.as_ref().components() { + match component { + Component::Prefix(x) => unreachable!("Windows only: {:?}", x), + Component::RootDir | Component::CurDir | Component::ParentDir => { + log::trace!("RootDir | CurDir | ParentDir"); + } + Component::Normal(x) => { + root = root.children.get(x)?; + } + } + } + Some(root) + } + fn get_mut(&mut self, path: impl AsRef) -> Option<&mut V> { + self.get_mut_child(path) + .and_then(|node| node.value.as_mut()) + } + fn get(&self, path: impl AsRef) -> Option<&V> { + self.get_child(path).and_then(|node| node.value.as_ref()) + } +} + +#[cfg(test)] +mod test { + use super::*; +} diff --git a/judger/src/filesystem/table/handle.rs b/judger/src/filesystem/table/handle.rs new file mode 100644 index 00000000..e5819fc3 --- /dev/null +++ b/judger/src/filesystem/table/handle.rs @@ -0,0 +1,39 @@ +use std::{ + collections::BTreeMap, + sync::{atomic::AtomicU64, Arc}, +}; + +use spin::RwLock; +use tokio::{ + io::{AsyncRead, AsyncSeek}, + sync::Mutex, +}; + +use crate::filesystem::overlay::*; + +pub struct HandleTable +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + handle_generator: AtomicU64, + table: RwLock>>>>, +} + +impl HandleTable +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + pub fn new_handle(&self, entry: ArcEntry) -> u64 { + let handle = self + .handle_generator + .fetch_add(1, std::sync::atomic::Ordering::AcqRel); + self.table.write().insert(handle, entry); + handle + } + pub fn get_entry(&self, handle: u64) -> Option> { + self.table.read().get(&handle).cloned() + } + pub fn remove_entry(&self, handle: u64) -> Option> { + self.table.write().remove(&handle) + } +} diff --git a/judger/src/filesystem/table/inode.rs b/judger/src/filesystem/table/inode.rs new file mode 100644 index 00000000..ddf82725 --- /dev/null +++ b/judger/src/filesystem/table/inode.rs @@ -0,0 +1,36 @@ +use std::{ + collections::BTreeMap, + sync::{atomic::AtomicU64, Arc}, +}; + +use spin::RwLock; +use tokio::{ + io::{AsyncRead, AsyncSeek}, + sync::Mutex, +}; + +use crate::{ + filesystem::overlay::{ArcEntry, MixedEntry}, + semaphore::Semaphore, +}; + +pub struct INodeTable +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + semaphore: Arc, + table: RwLock>>, + handle_generator: AtomicU64, +} + +impl INodeTable +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + pub async fn new_inode(&self, entry: ArcEntry) { + let handle = self + .handle_generator + .fetch_add(1, std::sync::atomic::Ordering::AcqRel); + self.table.write().insert(handle, entry); + } +} diff --git a/judger/src/filesystem/table/mod.rs b/judger/src/filesystem/table/mod.rs new file mode 100644 index 00000000..4a8cd648 --- /dev/null +++ b/judger/src/filesystem/table/mod.rs @@ -0,0 +1,5 @@ +mod handle; +mod inode; + +pub use handle::HandleTable; +pub use inode::INodeTable; diff --git a/judger/src/filesystem/tar/block.rs b/judger/src/filesystem/tar/block.rs index 700009f9..df907430 100644 --- a/judger/src/filesystem/tar/block.rs +++ b/judger/src/filesystem/tar/block.rs @@ -1,27 +1,29 @@ +use crate::filesystem::macro_::{chain_poll, report_poll}; +use std::future::Future; +use std::io; use std::{ io::SeekFrom, - ops::DerefMut, + ops::{Deref, DerefMut}, pin::{pin, Pin}, sync::Arc, task::{Context, Poll}, }; - -use std::future::Future; -use std::io; use tokio::{ - io::{AsyncRead, AsyncSeek}, + io::{AsyncRead, AsyncSeek, AsyncWrite}, sync::{Mutex, OwnedMutexGuard}, }; +const MEMBLOCK_BLOCKSIZE: usize = 4096; + #[derive(Default, Debug)] -enum Stage { +enum TarStage { Reading(OwnedMutexGuard), Seeking(OwnedMutexGuard), #[default] Done, } -impl Stage { +impl TarStage { fn take(&mut self) -> Self { std::mem::take(self) } @@ -36,7 +38,7 @@ where start: u64, size: u64, cursor: u64, - stage: Stage, + stage: TarStage, } impl PartialEq for TarBlock @@ -61,7 +63,7 @@ where start: self.start, size: self.size, cursor: self.cursor, - stage: Stage::Done, + stage: TarStage::Done, } } } @@ -76,7 +78,7 @@ where start, size, cursor: 0, - stage: Stage::Done, + stage: TarStage::Done, } } #[cfg(test)] @@ -86,7 +88,7 @@ where start, size, cursor: 0, - stage: Stage::Done, + stage: TarStage::Done, } } #[inline] @@ -120,49 +122,32 @@ where } let original_size = buf.filled().len(); match self.stage.take() { - Stage::Reading(mut locked) => { - let file = pin!(locked.deref_mut()); - if let Poll::Ready(x) = file.poll_read(cx, buf) { - return match x { - Ok(_) => { - let read_byte = (buf.filled().len() - original_size) as u64; - match read_byte > self.get_remain() { - true => { - buf.set_filled(original_size + self.get_remain() as usize); - self.cursor += self.get_remain(); - } - false => self.cursor += read_byte, - }; - Poll::Ready(Ok(())) - } - Err(err) => Poll::Ready(Err(err)), - }; - } - } - Stage::Seeking(mut locked) => { - let file = pin!(locked.deref_mut()); - if let Poll::Ready(x) = file.poll_complete(cx) { - match x { - Ok(x) => { - self.as_mut().stage = Stage::Reading(locked); - self.as_mut().cursor = x - self.start; - cx.waker().wake_by_ref(); - } - Err(err) => { - return Poll::Ready(Err(err)); - } + TarStage::Reading(mut locked) => { + report_poll!(chain_poll!(pin!(locked.deref_mut()).poll_read(cx, buf))); + let read_byte = (buf.filled().len() - original_size) as u64; + match read_byte > self.get_remain() { + true => { + buf.set_filled(original_size + self.get_remain() as usize); + self.cursor += self.get_remain(); } - } + false => self.cursor += read_byte, + }; + return Poll::Ready(Ok(())); } - Stage::Done => { - if let Poll::Ready(mut locked) = pin!(self.file.clone().lock_owned()).poll(cx) { - let file = pin!(locked.deref_mut()); - if let Err(err) = file.start_seek(self.get_seek_from()) { - return Poll::Ready(Err(err)); - } - self.as_mut().stage = Stage::Seeking(locked); - cx.waker().wake_by_ref(); + TarStage::Seeking(mut locked) => { + let result = chain_poll!(pin!(locked.deref_mut()).poll_complete(cx)); + let read_byte = report_poll!(result); + self.as_mut().stage = TarStage::Reading(locked); + self.as_mut().cursor = read_byte - self.start; + cx.waker().wake_by_ref(); + } + TarStage::Done => { + let mut locked = chain_poll!(pin!(self.file.clone().lock_owned()).poll(cx)); + if let Err(err) = pin!(locked.deref_mut()).start_seek(self.get_seek_from()) { + return Poll::Ready(Err(err)); } + self.as_mut().stage = TarStage::Seeking(locked); + cx.waker().wake_by_ref(); } } Poll::Pending @@ -198,11 +183,11 @@ where mod test { use std::io::Cursor; - use tokio::io::{AsyncReadExt, BufReader}; + use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt, BufReader}; use super::*; #[tokio::test] - async fn normal_read() { + async fn tar_normal_read() { let underlying = BufReader::new(Cursor::new(b"111hello world111")); let mut block = TarBlock::from_raw(underlying, 3, 11); @@ -212,7 +197,7 @@ mod test { assert_eq!(buf, *b"hello world"); } #[tokio::test] - async fn end_of_file_read() { + async fn tar_end_of_file_read() { let underlying = BufReader::new(Cursor::new(b"111hello world")); let mut block = TarBlock::from_raw(underlying, 3, 11); @@ -225,7 +210,7 @@ mod test { ); } #[tokio::test] - async fn multi_sequential_read() { + async fn tar_multi_sequential_read() { let underlying = BufReader::new(Cursor::new(b"111hello world111")); let mut block = TarBlock::from_raw(underlying, 3, 11); @@ -234,7 +219,7 @@ mod test { } } #[tokio::test] - async fn multi_reader_read() { + async fn tar_multi_reader_read() { let underlying = BufReader::new(Cursor::new(b"111hello world111")); let underlying = Arc::new(Mutex::new(underlying)); let block = TarBlock::new(underlying, 3, 11); diff --git a/judger/src/filesystem/tar/entry.rs b/judger/src/filesystem/tar/entry.rs index 1c13a14a..0b116b51 100644 --- a/judger/src/filesystem/tar/entry.rs +++ b/judger/src/filesystem/tar/entry.rs @@ -5,13 +5,11 @@ //! //! And we map each type of content to BTreeMap -use crate::semaphore::*; use std::{ffi::OsString, sync::Arc}; -use tokio::{ - fs::File, - io::{AsyncRead, AsyncSeek}, -}; +use tokio::io::{AsyncRead, AsyncSeek}; + +use crate::filesystem::INODE; use super::block::TarBlock; @@ -21,34 +19,28 @@ pub enum Entry where F: AsyncRead + AsyncSeek + Unpin + 'static, { - Link(Arc), + SymLink(Arc), + HardLink(INODE), Directory, File(TarBlock), } +// impl Entry +// where +// F: AsyncRead + AsyncSeek + Unpin + 'static, +// { +// pub fn read(&mut self) +// } + impl PartialEq for Entry where F: AsyncRead + AsyncSeek + Unpin + 'static, { fn eq(&self, other: &Self) -> bool { match (self, other) { - (Self::Link(l0), Self::Link(r0)) => l0 == r0, + (Self::SymLink(l0), Self::SymLink(r0)) => l0 == r0, (Self::File(l0), Self::File(r0)) => l0 == r0, _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } - -/// Entry from tar file, it's a replacement of Entry -pub enum MutEntry { - Link(OsString), - Directory, - File(Vec), - Removed, -} - -/// A workaround to not use dynamic dispatch and compact the size of Entry -pub enum MixedEntry { - Mut(Permit, Arc), - Immut(Entry), -} diff --git a/judger/src/filesystem/tar/map.rs b/judger/src/filesystem/tar/map.rs index 300f671d..0340185d 100644 --- a/judger/src/filesystem/tar/map.rs +++ b/judger/src/filesystem/tar/map.rs @@ -39,7 +39,7 @@ where if let Some(link_path) = entry.link_name()? { map.insert( path.as_os_str().to_owned(), - Entry::Link(Arc::new(link_path.as_os_str().to_owned())), + Entry::SymLink(Arc::new(link_path.as_os_str().to_owned())), ); } else { let start = entry.raw_file_position(); diff --git a/judger/src/filesystem/tar/mod.rs b/judger/src/filesystem/tar/mod.rs index 7608b5ae..a8af6116 100644 --- a/judger/src/filesystem/tar/mod.rs +++ b/judger/src/filesystem/tar/mod.rs @@ -1,3 +1,7 @@ mod block; mod entry; mod map; + +pub use block::TarBlock; +pub use entry::Entry; +pub use map::TarMap; diff --git a/judger/src/sandbox/monitor/mod.rs b/judger/src/sandbox/monitor/mod.rs index 4f29b806..b33a9d2a 100644 --- a/judger/src/sandbox/monitor/mod.rs +++ b/judger/src/sandbox/monitor/mod.rs @@ -1,18 +1,18 @@ //! Provide ability to limit resource such as memory limit, cpu limit, walltime limit and output limit - mod hier; - mod mem_cpu; - mod output; - mod stat; - mod walltime; - mod wrapper; +mod hier; +mod mem_cpu; +mod output; +mod stat; +mod walltime; +mod wrapper; use std::{fmt::Display, sync::atomic::AtomicUsize, time::Duration}; pub use stat::*; use tokio::io::AsyncRead; - use crate::Error; - type Result = std::result::Result; +use crate::Error; +type Result = std::result::Result; use hier::*; use self::output::Output; diff --git a/judger/src/sandbox/process/mod.rs b/judger/src/sandbox/process/mod.rs index 47c81526..0d3c9d2f 100644 --- a/judger/src/sandbox/process/mod.rs +++ b/judger/src/sandbox/process/mod.rs @@ -3,4 +3,3 @@ mod nsjail; mod process; use super::*; - From 63ce5d9dc38cd5ffa47a63606e0bd9815fa9ab00 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Wed, 1 May 2024 14:51:24 +0800 Subject: [PATCH 10/38] feat(Judger): :sparkles: introduce concept of workspace to LockPathTree --- judger/src/filesystem/overlay/block.rs | 14 +- judger/src/filesystem/path_tree.rs | 201 +++++++++++++++---------- judger/src/filesystem/tar/block.rs | 8 +- 3 files changed, 130 insertions(+), 93 deletions(-) diff --git a/judger/src/filesystem/overlay/block.rs b/judger/src/filesystem/overlay/block.rs index 05f95df6..834fbf7a 100644 --- a/judger/src/filesystem/overlay/block.rs +++ b/judger/src/filesystem/overlay/block.rs @@ -168,7 +168,7 @@ mod test { use super::*; #[tokio::test] - async fn mem_normal_read() { + async fn normal_read() { let data = b"hello world".to_vec(); let mut block = MemBlock::new(data); let mut buf = [0_u8; 11]; @@ -177,7 +177,7 @@ mod test { assert_eq!(buf, *b"hello world"); } #[tokio::test] - async fn mem_end_of_file_read() { + async fn end_of_file_read() { let mut block = MemBlock::new(b"1234".to_vec()); let mut buf = Vec::new(); block.read_to_end(&mut buf).await.unwrap(); @@ -185,7 +185,7 @@ mod test { assert_eq!(&*buf, b"1234"); } #[tokio::test] - async fn mem_start_seek() { + async fn start_seek() { let mut block = MemBlock::new(b"111hello world1111".to_vec()); block.seek(SeekFrom::Start(3)).await.unwrap(); @@ -195,7 +195,7 @@ mod test { assert_eq!(buf, *b"hello world"); } #[tokio::test] - async fn mem_end_seek() { + async fn end_seek() { let mut block = MemBlock::new(b"111hello world1111".to_vec()); block.seek(SeekFrom::End(15)).await.unwrap(); @@ -205,7 +205,7 @@ mod test { assert_eq!(buf, *b"hello world"); } #[tokio::test] - async fn mem_rel_seek() { + async fn rel_seek() { let mut block = MemBlock::new(b"111hello world1111".to_vec()); for _ in 0..3 { block.seek(SeekFrom::Current(1)).await.unwrap(); @@ -217,7 +217,7 @@ mod test { assert_eq!(buf, *b"hello world"); } #[tokio::test] - async fn mem_normal_write() { + async fn normal_write() { let mut block = MemBlock::default(); block.write_all(b"hello").await.unwrap(); block.write_all(b" ").await.unwrap(); @@ -233,7 +233,7 @@ mod test { assert_eq!(buf, *b"hello world"); } #[tokio::test] - async fn mem_multi_read() { + async fn multi_read() { let block = MemBlock::new(b"hello world".to_vec()); for _ in 0..3000 { diff --git a/judger/src/filesystem/path_tree.rs b/judger/src/filesystem/path_tree.rs index 63097620..90b3fe4f 100644 --- a/judger/src/filesystem/path_tree.rs +++ b/judger/src/filesystem/path_tree.rs @@ -1,120 +1,142 @@ use std::{ collections::HashMap, - ffi::OsString, + ffi::{OsStr, OsString}, + mem::MaybeUninit, ops::{Deref, DerefMut}, path::{Component, Path}, sync::Arc, }; -use tokio::sync::{OwnedRwLockReadGuard, OwnedRwLockWriteGuard, RwLock}; +use tokio::sync::RwLock; -type ArcRwLock = Arc>; - -struct LockNode { - children: HashMap>>, - value: Option, +fn to_internal_path<'a>(path: &'a impl AsRef) -> impl Iterator { + path.as_ref() + .components() + .filter_map(|component| match component { + Component::Prefix(x) => unreachable!("Windows only: {:?}", x), + Component::RootDir | Component::CurDir | Component::ParentDir => None, + Component::Normal(x) => Some(x), + }) } -impl Default for LockNode { - fn default() -> Self { - Self { - children: Default::default(), - value: Default::default(), - } - } +pub enum InsertResult { + AlreadyExists(N), + Inserted(Option), + ParentNotFound, + IsRoot, } -struct WriteNodeGuard(OwnedRwLockWriteGuard>); +pub struct LockNode { + children: HashMap>, + value: MaybeUninit, +} -impl Deref for WriteNodeGuard { +impl Deref for LockNode { type Target = V; - fn deref(&self) -> &Self::Target { - self.0.value.as_ref().unwrap() + unsafe { self.value.assume_init_ref() } } } -impl DerefMut for WriteNodeGuard { +impl DerefMut for LockNode { fn deref_mut(&mut self) -> &mut Self::Target { - self.0.value.as_mut().unwrap() + unsafe { self.value.assume_init_mut() } } } -struct ReadNodeGuard(OwnedRwLockReadGuard>); +type ArcRwNode = Arc>>; -impl Deref for ReadNodeGuard { - type Target = V; +impl LockNode { + /// get child node by component + #[inline] + pub async fn get_by_component(&mut self, component: &OsStr) -> Option> { + self.children.get(component).cloned() + } + /// insert child node by component + /// + /// return the old child node if it exists + pub async fn insert_component( + &mut self, + component: OsString, + value: V, + ) -> Option> { + self.children.insert( + component, + Arc::new(RwLock::new(LockNode { + children: Default::default(), + value: MaybeUninit::new(value), + })), + ) + } + pub async fn remove_component(&mut self, component: &OsStr) -> Option> { + self.children.remove(component) + } + /// get child node by path + pub async fn get_by_path( + self_: ArcRwNode, + path: impl Iterator, + ) -> Option> { + let mut root = self_; + for component in path { + let child = root.read().await.children.get(component)?.clone(); + root = child; + } + Some(root) + } + /// insert child node by path + pub async fn insert_path( + self_: ArcRwNode, + path: impl Iterator, + value: V, + ) -> InsertResult> { + let path = path.collect::>(); + let mut root = self_; + if path.is_empty() { + return InsertResult::IsRoot; + } + let (last, path) = path.split_last().unwrap(); + for component in path { + let child = match root.read().await.children.get(*component) { + Some(child) => child.clone(), + None => return InsertResult::ParentNotFound, + }; + root = child; + } + let mut root = root.write().await; + InsertResult::Inserted(root.insert_component(last.to_os_string(), value).await) + } +} - fn deref(&self) -> &Self::Target { - self.0.value.as_ref().unwrap() +impl LockNode { + fn new(value: V) -> Self { + Self { + children: Default::default(), + value: MaybeUninit::new(value), + } + } + fn new_uninit() -> Self { + Self { + children: Default::default(), + value: MaybeUninit::uninit(), + } } } /// Path tree with partial locking -pub struct LockPathTree(ArcRwLock>); +pub struct LockPathTree(ArcRwNode); impl LockPathTree { pub fn new() -> Self { - LockPathTree(Arc::new(RwLock::new(LockNode::default()))) + LockPathTree(Arc::new(RwLock::new(LockNode::new_uninit()))) } - + /// insert path recursively #[inline] - async fn get_child(&self, path: impl AsRef) -> Option>> { - let mut root = self.0.clone(); - for component in path.as_ref().components() { - match component { - Component::Prefix(x) => unreachable!("Windows only: {:?}", x), - Component::RootDir | Component::CurDir | Component::ParentDir => { - log::trace!("RootDir | CurDir | ParentDir"); - } - Component::Normal(x) => { - let child = root.read().await.children.get(x)?.clone(); - root = child; - } - } - } - Some(root) - } - pub async fn insert_path(&self, path: impl AsRef, value: V) -> Option { - let mut root = self.0.clone(); - for component in path.as_ref().components() { - match component { - Component::Prefix(x) => unreachable!("Windows only: {:?}", x), - Component::RootDir | Component::CurDir | Component::ParentDir => { - log::trace!("RootDir | CurDir | ParentDir"); - } - Component::Normal(x) => { - let child = if let Some(child) = root.read().await.children.get(x) { - child.clone() - } else { - let node = Arc::new(RwLock::new(LockNode::default())); - root.write() - .await - .children - .insert(x.to_os_string(), node.clone()); - node - }; - root = child; - } - } - } - let mut root = root.write().await; - root.value.replace(value) - } - pub async fn get_mut( + pub async fn insert_path( &self, path: impl AsRef, - ) -> Option> { - match self.get_child(path).await { - Some(root) => Some(WriteNodeGuard(root.write_owned().await)), - None => None, - } - } - pub async fn get(&self, path: impl AsRef) -> Option + Deref> { - match self.get_child(path).await { - Some(root) => Some(ReadNodeGuard(root.read_owned().await)), - None => None, - } + value: V, + ) -> InsertResult> { + LockNode::insert_path(self.0.clone(), to_internal_path(&path), value).await } } @@ -213,4 +235,19 @@ impl PathTree { #[cfg(test)] mod test { use super::*; + mod lock { + use super::*; + + #[tokio::test] + async fn insert_lookup() { + // tree.insert_path(Path::new("/abc/efg"), value) + } + } + + mod mutable { + use super::*; + } + + // #[tokio::test] + // async fn lock } diff --git a/judger/src/filesystem/tar/block.rs b/judger/src/filesystem/tar/block.rs index df907430..04be9929 100644 --- a/judger/src/filesystem/tar/block.rs +++ b/judger/src/filesystem/tar/block.rs @@ -187,7 +187,7 @@ mod test { use super::*; #[tokio::test] - async fn tar_normal_read() { + async fn normal_read() { let underlying = BufReader::new(Cursor::new(b"111hello world111")); let mut block = TarBlock::from_raw(underlying, 3, 11); @@ -197,7 +197,7 @@ mod test { assert_eq!(buf, *b"hello world"); } #[tokio::test] - async fn tar_end_of_file_read() { + async fn end_of_file_read() { let underlying = BufReader::new(Cursor::new(b"111hello world")); let mut block = TarBlock::from_raw(underlying, 3, 11); @@ -210,7 +210,7 @@ mod test { ); } #[tokio::test] - async fn tar_multi_sequential_read() { + async fn multi_sequential_read() { let underlying = BufReader::new(Cursor::new(b"111hello world111")); let mut block = TarBlock::from_raw(underlying, 3, 11); @@ -219,7 +219,7 @@ mod test { } } #[tokio::test] - async fn tar_multi_reader_read() { + async fn multi_reader_read() { let underlying = BufReader::new(Cursor::new(b"111hello world111")); let underlying = Arc::new(Mutex::new(underlying)); let block = TarBlock::new(underlying, 3, 11); From a2d8904a0be3c0aaf90a6fa9f15940b300e76762 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Wed, 1 May 2024 18:48:35 +0800 Subject: [PATCH 11/38] test(Judger): :white_check_mark: add test for path tree \ --- judger/src/filesystem/path_tree.rs | 261 +++++++++++++++----------- judger/src/filesystem/table/handle.rs | 12 +- judger/src/filesystem/table/inode.rs | 10 +- judger/src/filesystem/tar/block.rs | 14 +- 4 files changed, 166 insertions(+), 131 deletions(-) diff --git a/judger/src/filesystem/path_tree.rs b/judger/src/filesystem/path_tree.rs index 90b3fe4f..e10aa1dc 100644 --- a/judger/src/filesystem/path_tree.rs +++ b/judger/src/filesystem/path_tree.rs @@ -19,6 +19,7 @@ fn to_internal_path<'a>(path: &'a impl AsRef) -> impl Iterator { AlreadyExists(N), Inserted(Option), @@ -26,27 +27,48 @@ pub enum InsertResult { IsRoot, } +impl PartialEq for InsertResult> { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::AlreadyExists(l0), Self::AlreadyExists(r0)) => Arc::ptr_eq(l0, r0), + (Self::Inserted(l0), Self::Inserted(r0)) => match (l0, r0) { + (Some(l0), Some(r0)) => Arc::ptr_eq(l0, r0), + (None, None) => true, + _ => false, + }, + _ => core::mem::discriminant(self) == core::mem::discriminant(other), + } + } +} + +#[derive(Debug)] pub struct LockNode { children: HashMap>, - value: MaybeUninit, + value: V, } impl Deref for LockNode { type Target = V; fn deref(&self) -> &Self::Target { - unsafe { self.value.assume_init_ref() } + &self.value } } impl DerefMut for LockNode { fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { self.value.assume_init_mut() } + &mut self.value } } type ArcRwNode = Arc>>; impl LockNode { + fn new(value: V) -> Self { + Self { + children: Default::default(), + value, + } + } /// get child node by component #[inline] pub async fn get_by_component(&mut self, component: &OsStr) -> Option> { @@ -60,13 +82,8 @@ impl LockNode { component: OsString, value: V, ) -> Option> { - self.children.insert( - component, - Arc::new(RwLock::new(LockNode { - children: Default::default(), - value: MaybeUninit::new(value), - })), - ) + self.children + .insert(component, Arc::new(RwLock::new(LockNode::new(value)))) } pub async fn remove_component(&mut self, component: &OsStr) -> Option> { self.children.remove(component) @@ -77,6 +94,7 @@ impl LockNode { path: impl Iterator, ) -> Option> { let mut root = self_; + let path = path.peekable(); for component in path { let child = root.read().await.children.get(component)?.clone(); root = child; @@ -107,29 +125,18 @@ impl LockNode { } } -impl LockNode { - fn new(value: V) -> Self { - Self { - children: Default::default(), - value: MaybeUninit::new(value), - } - } - fn new_uninit() -> Self { - Self { - children: Default::default(), - value: MaybeUninit::uninit(), - } - } -} - /// Path tree with partial locking +#[derive(Clone)] pub struct LockPathTree(ArcRwNode); impl LockPathTree { - pub fn new() -> Self { - LockPathTree(Arc::new(RwLock::new(LockNode::new_uninit()))) + pub fn new(root: V) -> Self { + LockPathTree(Arc::new(RwLock::new(LockNode::new(root)))) + } + pub async fn get_by_path(&self, path: impl AsRef) -> Option> { + LockNode::get_by_path(self.0.clone(), to_internal_path(&path)).await } - /// insert path recursively + /// insert path #[inline] pub async fn insert_path( &self, @@ -142,112 +149,152 @@ impl LockPathTree { struct Node { children: HashMap>, - value: Option, + value: V, } -impl Default for Node { - fn default() -> Self { +impl Node { + fn new(value: V) -> Self { Self { children: Default::default(), - value: Default::default(), + value, } } } pub struct PathTree(Node); -impl PathTree { +impl PathTree +where + V: Default, +{ pub fn new() -> Self { - PathTree(Node { - children: HashMap::new(), - value: None, - }) + PathTree(Node::new(Default::default())) } - #[inline] - fn get_mut_child<'a>(&'a mut self, path: impl AsRef) -> Option<&'a mut Node> { + pub fn insert_path(&mut self, path: impl AsRef, mut value: V) -> Option + where + V: Default, + { let mut root = &mut self.0; - for component in path.as_ref().components() { - match component { - Component::Prefix(x) => unreachable!("Windows only: {:?}", x), - Component::RootDir | Component::CurDir | Component::ParentDir => { - log::trace!("RootDir | CurDir | ParentDir"); - } - Component::Normal(x) => { - root = root.children.get_mut(x)?; - } - } - } - Some(root) - } - pub fn insert_path(&mut self, path: impl AsRef, value: V) -> Option { - let mut root = &mut self.0; - for component in path.as_ref().components() { - match component { - Component::Prefix(x) => unreachable!("Windows only: {:?}", x), - Component::RootDir | Component::CurDir | Component::ParentDir => { - log::trace!("RootDir | CurDir | ParentDir"); - } - Component::Normal(x) => { - // bypass borrow checker - match root.children.contains_key(x) { - true => { - root = root.children.get_mut(x).unwrap(); - } - false => { - let node = Node { - children: HashMap::new(), - value: None, - }; - root.children.insert(x.to_os_string(), node); - root = root.children.get_mut(x).unwrap(); - } - }; - } - } + let path = to_internal_path(&path).collect::>(); + let (last, path) = path.split_last().unwrap(); + for component in path { + root = root + .children + .entry(component.to_os_string()) + .or_insert_with(|| Node::new(Default::default())); } - root.value.replace(value) - } - #[inline] - fn get_child(&self, path: impl AsRef) -> Option<&Node> { - let mut root = &self.0; - for component in path.as_ref().components() { - match component { - Component::Prefix(x) => unreachable!("Windows only: {:?}", x), - Component::RootDir | Component::CurDir | Component::ParentDir => { - log::trace!("RootDir | CurDir | ParentDir"); - } - Component::Normal(x) => { - root = root.children.get(x)?; - } + match root.children.get_mut(*last) { + Some(x) => { + std::mem::swap(&mut x.value, &mut value); + Some(value) } + None => None, } - Some(root) } fn get_mut(&mut self, path: impl AsRef) -> Option<&mut V> { - self.get_mut_child(path) - .and_then(|node| node.value.as_mut()) + let this = &mut *self; + let mut root = &mut this.0; + for component in to_internal_path(&path) { + root = root.children.get_mut(component)?; + } + Some(&mut root.value) } fn get(&self, path: impl AsRef) -> Option<&V> { - self.get_child(path).and_then(|node| node.value.as_ref()) + let this = &self; + let mut root = &this.0; + for component in to_internal_path(&path) { + root = root.children.get(component)?; + } + Some(&root.value) } } #[cfg(test)] -mod test { +mod lock_test { use super::*; - mod lock { - use super::*; - #[tokio::test] - async fn insert_lookup() { - // tree.insert_path(Path::new("/abc/efg"), value) - } + #[tokio::test] + async fn insert_parent_not_found() { + let tree = LockPathTree::new(0); + assert_eq!( + tree.insert_path("a/b/c", 1).await, + InsertResult::ParentNotFound + ); } - - mod mutable { - use super::*; + #[tokio::test] + async fn insert_is_root() { + let tree = LockPathTree::new(0); + assert_eq!(tree.insert_path("", 1).await, InsertResult::IsRoot); } + #[tokio::test] + async fn insert() { + let tree = LockPathTree::new(0); + macro_rules! insert { + ($path:expr, $val:expr) => { + assert_eq!( + tree.insert_path($path, $val).await, + InsertResult::Inserted(None) + ); + }; + } + macro_rules! lookup { + ($path:expr,$val:expr) => { + assert_eq!( + tree.get_by_path($path).await.unwrap().read().await.value, + $val + ); + }; + } - // #[tokio::test] - // async fn lock + insert!("a", 1); + insert!("a/u", 2); + insert!("a/h", 3); + insert!("a/h/f", 4); + lookup!("/", 0); + lookup!("a", 1); + lookup!("a/u", 2); + lookup!("a/h", 3); + lookup!("a/h/f", 4); + } + #[tokio::test(flavor = "multi_thread", worker_threads = 8)] + async fn multi_lookup() { + let tree = LockPathTree::new(0); + tree.insert_path("a", 1).await; + tree.insert_path("a/u", 2).await; + tree.insert_path("a/h", 3).await; + tree.insert_path("a/h/f", 4).await; + async fn lookup(tree: &LockPathTree, path: &str, val: i32) { + for _ in 0..30 { + let tree = tree.clone(); + let path = path.to_string(); + tokio::spawn(async move { + for _ in 0..300 { + assert_eq!( + tree.get_by_path(&path).await.unwrap().read().await.value, + val + ); + } + }) + .await + .unwrap(); + } + } + tokio::join!( + lookup(&tree, "a", 1), + lookup(&tree, "a/u", 2), + lookup(&tree, "a/h", 3), + lookup(&tree, "a/h/f", 4) + ); + } + #[cfg(taregt_os = "windows")] + #[tokio::test] + #[should_panic] + async fn windows() { + let tree = LockPathTree::new(0); + tree.insert_path("C:\\a", 1).await; + } +} +#[cfg(test)] +mod test { + use super::*; } diff --git a/judger/src/filesystem/table/handle.rs b/judger/src/filesystem/table/handle.rs index e5819fc3..b0cc3daa 100644 --- a/judger/src/filesystem/table/handle.rs +++ b/judger/src/filesystem/table/handle.rs @@ -1,13 +1,7 @@ -use std::{ - collections::BTreeMap, - sync::{atomic::AtomicU64, Arc}, -}; +use std::{collections::BTreeMap, sync::atomic::AtomicU64}; use spin::RwLock; -use tokio::{ - io::{AsyncRead, AsyncSeek}, - sync::Mutex, -}; +use tokio::io::{AsyncRead, AsyncSeek}; use crate::filesystem::overlay::*; @@ -16,7 +10,7 @@ where F: AsyncRead + AsyncSeek + Unpin + 'static, { handle_generator: AtomicU64, - table: RwLock>>>>, + table: RwLock>>, } impl HandleTable diff --git a/judger/src/filesystem/table/inode.rs b/judger/src/filesystem/table/inode.rs index ddf82725..1ae9e5e1 100644 --- a/judger/src/filesystem/table/inode.rs +++ b/judger/src/filesystem/table/inode.rs @@ -4,15 +4,9 @@ use std::{ }; use spin::RwLock; -use tokio::{ - io::{AsyncRead, AsyncSeek}, - sync::Mutex, -}; +use tokio::io::{AsyncRead, AsyncSeek}; -use crate::{ - filesystem::overlay::{ArcEntry, MixedEntry}, - semaphore::Semaphore, -}; +use crate::{filesystem::overlay::ArcEntry, semaphore::Semaphore}; pub struct INodeTable where diff --git a/judger/src/filesystem/tar/block.rs b/judger/src/filesystem/tar/block.rs index 04be9929..8bc221bd 100644 --- a/judger/src/filesystem/tar/block.rs +++ b/judger/src/filesystem/tar/block.rs @@ -3,18 +3,16 @@ use std::future::Future; use std::io; use std::{ io::SeekFrom, - ops::{Deref, DerefMut}, + ops::DerefMut, pin::{pin, Pin}, sync::Arc, task::{Context, Poll}, }; use tokio::{ - io::{AsyncRead, AsyncSeek, AsyncWrite}, + io::{AsyncRead, AsyncSeek}, sync::{Mutex, OwnedMutexGuard}, }; -const MEMBLOCK_BLOCKSIZE: usize = 4096; - #[derive(Default, Debug)] enum TarStage { Reading(OwnedMutexGuard), @@ -183,7 +181,7 @@ where mod test { use std::io::Cursor; - use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt, BufReader}; + use tokio::io::{AsyncReadExt, BufReader}; use super::*; #[tokio::test] @@ -218,18 +216,20 @@ mod test { assert_eq!(block.read_u8().await.unwrap(), *c); } } - #[tokio::test] + #[tokio::test(flavor = "multi_thread", worker_threads = 8)] async fn multi_reader_read() { let underlying = BufReader::new(Cursor::new(b"111hello world111")); let underlying = Arc::new(Mutex::new(underlying)); let block = TarBlock::new(underlying, 3, 11); - for _ in 0..3000 { + for _ in 0..30 { let mut block = block.clone(); tokio::spawn(async move { + for _ in 0..400 { let mut buf = [0_u8; 11]; block.read_exact(&mut buf).await.unwrap(); assert_eq!(buf, *b"hello world"); + } }); } } From 6abdd309cd3f030101977e97f4a05d7c6768a93c Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Thu, 2 May 2024 18:21:23 +0800 Subject: [PATCH 12/38] feat(Judger): :sparkles: finish inode and file handle table --- judger/src/filesystem/table/handle.rs | 27 +++++------ judger/src/filesystem/table/inode.rs | 68 ++++++++++++++++++++------- judger/src/filesystem/table/mod.rs | 2 +- 3 files changed, 63 insertions(+), 34 deletions(-) diff --git a/judger/src/filesystem/table/handle.rs b/judger/src/filesystem/table/handle.rs index b0cc3daa..ac75b8d8 100644 --- a/judger/src/filesystem/table/handle.rs +++ b/judger/src/filesystem/table/handle.rs @@ -1,33 +1,30 @@ use std::{collections::BTreeMap, sync::atomic::AtomicU64}; use spin::RwLock; -use tokio::io::{AsyncRead, AsyncSeek}; -use crate::filesystem::overlay::*; - -pub struct HandleTable -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ +pub struct HandleTable { handle_generator: AtomicU64, - table: RwLock>>, + table: RwLock>, } -impl HandleTable -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - pub fn new_handle(&self, entry: ArcEntry) -> u64 { +impl HandleTable { + pub fn new() -> Self { + Self { + handle_generator: AtomicU64::new(0), + table: RwLock::new(BTreeMap::new()), + } + } + pub fn add(&self, entry: E) -> u64 { let handle = self .handle_generator .fetch_add(1, std::sync::atomic::Ordering::AcqRel); self.table.write().insert(handle, entry); handle } - pub fn get_entry(&self, handle: u64) -> Option> { + pub fn get(&self, handle: u64) -> Option { self.table.read().get(&handle).cloned() } - pub fn remove_entry(&self, handle: u64) -> Option> { + pub fn remove(&self, handle: u64) -> Option { self.table.write().remove(&handle) } } diff --git a/judger/src/filesystem/table/inode.rs b/judger/src/filesystem/table/inode.rs index 1ae9e5e1..846298c6 100644 --- a/judger/src/filesystem/table/inode.rs +++ b/judger/src/filesystem/table/inode.rs @@ -1,30 +1,62 @@ use std::{ collections::BTreeMap, - sync::{atomic::AtomicU64, Arc}, + sync::atomic::{AtomicU64, Ordering}, }; use spin::RwLock; -use tokio::io::{AsyncRead, AsyncSeek}; -use crate::{filesystem::overlay::ArcEntry, semaphore::Semaphore}; +const MAX_INODE: u64 = 1 << 63; -pub struct INodeTable -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - semaphore: Arc, - table: RwLock>>, - handle_generator: AtomicU64, +pub trait Identified { + fn get_id(&self) -> usize; } -impl INodeTable -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - pub async fn new_inode(&self, entry: ArcEntry) { - let handle = self - .handle_generator +pub struct INodeTable { + inode: RwLock>, + id: RwLock>, + inode_generator: AtomicU64, +} + +impl INodeTable { + pub fn new() -> Self { + Self { + inode: RwLock::new(BTreeMap::new()), + id: RwLock::new(BTreeMap::new()), + inode_generator: AtomicU64::new(1), + } + } + pub fn allocate(&self, mut f: F) -> E + where + F: FnMut(u64) -> E, + { + let inode = self + .inode_generator .fetch_add(1, std::sync::atomic::Ordering::AcqRel); - self.table.write().insert(handle, entry); + let entry = f(inode); + match { self.id.read().get(&entry.get_id()) } { + Some(&x) => f(x), + None => { + self.inode.write().insert(inode, entry.clone()); + entry + } + } + } + /// get entry by inode + pub fn get(&self, inode: u64) -> Option { + self.inode.read().get(&inode).cloned() + } + /// deallocate inode + pub fn remove(&self, inode: u64) { + // FIXME: inode should be reused + if let Some(e) = { self.inode.write().remove(&inode) } { + self.id.write().remove(&e.get_id()); + } + } + pub fn get_free_inode(&self) -> u64 { + MAX_INODE - self.get_used_inode() + } + #[inline] + pub fn get_used_inode(&self) -> u64 { + self.inode_generator.load(Ordering::Acquire) } } diff --git a/judger/src/filesystem/table/mod.rs b/judger/src/filesystem/table/mod.rs index 4a8cd648..397faa97 100644 --- a/judger/src/filesystem/table/mod.rs +++ b/judger/src/filesystem/table/mod.rs @@ -2,4 +2,4 @@ mod handle; mod inode; pub use handle::HandleTable; -pub use inode::INodeTable; +pub use inode::{INodeTable, Identified}; From 11d96595d72ce3e3e0e3c1096ddaa7d7d5dcb387 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Thu, 2 May 2024 18:22:37 +0800 Subject: [PATCH 13/38] fix(Judger): :zap: fix locking scheme --- judger/src/filesystem/fuse.rs | 30 --- judger/src/filesystem/overlay/block.rs | 4 +- judger/src/filesystem/overlay/entry.rs | 130 +++++++++-- judger/src/filesystem/overlay/mod.rs | 39 ++++ judger/src/filesystem/overlay/reply.rs | 40 ++++ judger/src/filesystem/path_tree.rs | 300 ------------------------- judger/src/filesystem/tar/block.rs | 6 +- judger/src/filesystem/tar/entry.rs | 30 ++- judger/src/filesystem/tar/map.rs | 2 +- judger/src/filesystem/tar/mod.rs | 21 +- judger/src/filesystem/tar/tree.rs | 82 +++++++ 11 files changed, 317 insertions(+), 367 deletions(-) delete mode 100644 judger/src/filesystem/fuse.rs create mode 100644 judger/src/filesystem/overlay/reply.rs delete mode 100644 judger/src/filesystem/path_tree.rs create mode 100644 judger/src/filesystem/tar/tree.rs diff --git a/judger/src/filesystem/fuse.rs b/judger/src/filesystem/fuse.rs deleted file mode 100644 index 4f5ba13a..00000000 --- a/judger/src/filesystem/fuse.rs +++ /dev/null @@ -1,30 +0,0 @@ -// use fuse3::{raw::{reply::*, Request}, Errno}; -// use futures_core::Future; -// use tokio::fs::File; - -// use super::table::{HandleTable, INodeTable}; - -// pub struct FileSystem { -// inode_table: INodeTable, -// handle_table: HandleTable, -// } - -// impl fuse3::raw::Filesystem for FileSystem { -// fn init(&self, req: Request) -> impl Future> + Send { -// todo!() -// } - -// fn destroy(&self, req: Request) -> impl Future + Send { -// todo!() -// } - -// #[doc = r" dir entry stream given by [`readdir`][Filesystem::readdir]."] -// type DirEntryStream<'a> -// where -// Self: 'a; - -// #[doc = r" dir entry plus stream given by [`readdirplus`][Filesystem::readdirplus]."] -// type DirEntryPlusStream<'a> -// where -// Self: 'a; -// } diff --git a/judger/src/filesystem/overlay/block.rs b/judger/src/filesystem/overlay/block.rs index 834fbf7a..3d33f636 100644 --- a/judger/src/filesystem/overlay/block.rs +++ b/judger/src/filesystem/overlay/block.rs @@ -3,7 +3,7 @@ use std::future::Future; use std::io; use std::{ io::SeekFrom, - ops::{Deref, DerefMut}, + ops::Deref, pin::{pin, Pin}, sync::Arc, task::{Context, Poll}, @@ -13,7 +13,7 @@ use tokio::{ sync::{Mutex, OwnedMutexGuard}, }; -const MEMBLOCK_BLOCKSIZE: usize = 4096; +pub const MEMBLOCK_BLOCKSIZE: usize = 4096; #[derive(Debug, Default)] enum MemStage { diff --git a/judger/src/filesystem/overlay/entry.rs b/judger/src/filesystem/overlay/entry.rs index 39989a36..0681931e 100644 --- a/judger/src/filesystem/overlay/entry.rs +++ b/judger/src/filesystem/overlay/entry.rs @@ -1,38 +1,134 @@ -use std::{ffi::OsString, sync::Arc}; +use std::{ + ffi::{OsStr, OsString}, + sync::Arc, +}; use tokio::io::{AsyncRead, AsyncSeek}; -use crate::{ - filesystem::{tar::Entry, INODE}, - semaphore::*, +use crate::filesystem::{ + table::{INodeTable, Identified}, + tar::Entry as RoEntry, + tree::ArcNode, }; use super::block::MemBlock; -pub type ArcEntry = Arc>>; +type ArcEntry = ArcNode>; /// Entry from tar file, it's a replacement of Entry -pub enum MutEntry { +#[derive(Default)] +pub enum RwEntry { SymLink(OsString), - HardLink(INODE), + HardLink(u64), + #[default] Directory, File(MemBlock), Removed, } /// A workaround to not use dynamic dispatch and compact the size of Entry -pub enum MixedEntry +pub enum EntryKind +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + Rw(ArcNode), + Ro(ArcNode>), +} + +impl Clone for EntryKind +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + fn clone(&self) -> Self { + match self { + Self::Rw(arg0) => Self::Rw(arg0.clone()), + Self::Ro(arg0) => Self::Ro(arg0.clone()), + } + } +} + +impl EntryKind where F: AsyncRead + AsyncSeek + Unpin + 'static, { - Mut(Permit, Arc), - Immut(Entry), + pub async fn get_child_by_componment(&self, component: &OsStr) -> Option { + match self { + Self::Rw(node) => (node.read().await.get_by_component(component)).map(Self::Rw), + Self::Ro(node) => todo!(), + } + } } -// operation that's per file -// impl MixedEntry { -// pub async fn read(){} -// pub async fn write(){} -// pub async fn flush(){} -// pub async fn close(){} -// } +pub struct Entry +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + pub kind: EntryKind, + inode: u64, +} + +impl Identified for Entry +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + fn get_id(&self) -> usize { + match &self.kind { + EntryKind::Rw(x) => Arc::as_ptr(x) as usize, + EntryKind::Ro(x) => Arc::as_ptr(x) as usize, + } + } +} + +impl Clone for Entry +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + fn clone(&self) -> Self { + Self { + kind: self.kind.clone(), + inode: self.inode.clone(), + } + } +} + +impl Entry +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + #[inline] + pub fn from_kind(kind: EntryKind, inode: u64) -> Self { + Self { kind, inode } + } + #[inline] + pub fn get_inode(&self) -> u64 { + self.inode + } +} + +impl INodeTable> +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + pub fn add_entry_rw(&self, entry: ArcNode) -> Entry { + self.allocate(|x| Entry::from_kind(EntryKind::Rw(entry.clone()), x)) + } + pub fn add_entry_ro(&self, entry: ArcNode>) -> Entry { + self.allocate(|x| Entry::from_kind(EntryKind::Ro(entry.clone()), x)) + } + pub fn add_entry_kind(&self, kind: EntryKind) -> Entry { + self.allocate(|x| Entry::from_kind(kind.clone(), x)) + } + pub fn lookup(&self, inode: u64) -> Option> { + self.get(inode) + } + pub async fn get_child_by_componment(&self, inode: u64, component: &OsStr) -> Option> { + if let Some(entry) = self.get(inode) { + return entry + .kind + .get_child_by_componment(component) + .await + .map(|kind| self.add_entry_kind(kind)); + } + None + } +} diff --git a/judger/src/filesystem/overlay/mod.rs b/judger/src/filesystem/overlay/mod.rs index faf25867..f7009102 100644 --- a/judger/src/filesystem/overlay/mod.rs +++ b/judger/src/filesystem/overlay/mod.rs @@ -1,4 +1,43 @@ mod block; mod entry; +mod reply; +pub use block::MEMBLOCK_BLOCKSIZE as BLOCKSIZE; pub use entry::*; +pub use reply::Parsable; +use tokio::io::{AsyncRead, AsyncSeek}; + +use super::{ + table::{HandleTable, INodeTable}, + tar::TarLayer, +}; + +pub struct Overlay +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + tar: TarLayer, + pub inode: INodeTable>, + pub handle: HandleTable>, +} + +impl Overlay +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + pub async fn new(tar: TarLayer) -> Self { + let inode = INodeTable::new(); + inode.add_entry_ro(tar.get_root().await); + Self { + tar, + inode, + handle: HandleTable::new(), + } + } + pub fn get_root(&self) -> Entry { + self.inode.get(1).unwrap() + } + pub fn lookup(&self, inode: u64) -> Option> { + self.inode.get(inode) + } +} diff --git a/judger/src/filesystem/overlay/reply.rs b/judger/src/filesystem/overlay/reply.rs new file mode 100644 index 00000000..6c9b6575 --- /dev/null +++ b/judger/src/filesystem/overlay/reply.rs @@ -0,0 +1,40 @@ +use std::time::Duration; + +use fuse3::{raw::{reply::*, Request}, Timestamp}; +use tokio::io::{AsyncRead, AsyncSeek}; + +use super::Entry; + +pub trait Parsable +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + fn parse(request: Request, entry: Entry) -> Self; +} + +impl Parsable for ReplyEntry +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + fn parse(request: Request, entry: Entry) -> Self { + Self { + ttl: Duration::from_secs(30), + attr: FileAttr { + ino: entry.get_inode(), + size: 0, + blocks: 0, + atime: Timestamp::new(0, 0), + mtime: Timestamp::new(0, 0), + ctime: Timestamp::new(0, 0), + kind: todo!(), + perm: todo!(), + nlink: todo!(), + uid: todo!(), + gid: todo!(), + rdev: todo!(), + blksize: todo!(), + }, + generation: 1, + } + } +} diff --git a/judger/src/filesystem/path_tree.rs b/judger/src/filesystem/path_tree.rs deleted file mode 100644 index e10aa1dc..00000000 --- a/judger/src/filesystem/path_tree.rs +++ /dev/null @@ -1,300 +0,0 @@ -use std::{ - collections::HashMap, - ffi::{OsStr, OsString}, - mem::MaybeUninit, - ops::{Deref, DerefMut}, - path::{Component, Path}, - sync::Arc, -}; - -use tokio::sync::RwLock; - -fn to_internal_path<'a>(path: &'a impl AsRef) -> impl Iterator { - path.as_ref() - .components() - .filter_map(|component| match component { - Component::Prefix(x) => unreachable!("Windows only: {:?}", x), - Component::RootDir | Component::CurDir | Component::ParentDir => None, - Component::Normal(x) => Some(x), - }) -} - -#[derive(Debug)] -pub enum InsertResult { - AlreadyExists(N), - Inserted(Option), - ParentNotFound, - IsRoot, -} - -impl PartialEq for InsertResult> { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::AlreadyExists(l0), Self::AlreadyExists(r0)) => Arc::ptr_eq(l0, r0), - (Self::Inserted(l0), Self::Inserted(r0)) => match (l0, r0) { - (Some(l0), Some(r0)) => Arc::ptr_eq(l0, r0), - (None, None) => true, - _ => false, - }, - _ => core::mem::discriminant(self) == core::mem::discriminant(other), - } - } -} - -#[derive(Debug)] -pub struct LockNode { - children: HashMap>, - value: V, -} - -impl Deref for LockNode { - type Target = V; - fn deref(&self) -> &Self::Target { - &self.value - } -} - -impl DerefMut for LockNode { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.value - } -} - -type ArcRwNode = Arc>>; - -impl LockNode { - fn new(value: V) -> Self { - Self { - children: Default::default(), - value, - } - } - /// get child node by component - #[inline] - pub async fn get_by_component(&mut self, component: &OsStr) -> Option> { - self.children.get(component).cloned() - } - /// insert child node by component - /// - /// return the old child node if it exists - pub async fn insert_component( - &mut self, - component: OsString, - value: V, - ) -> Option> { - self.children - .insert(component, Arc::new(RwLock::new(LockNode::new(value)))) - } - pub async fn remove_component(&mut self, component: &OsStr) -> Option> { - self.children.remove(component) - } - /// get child node by path - pub async fn get_by_path( - self_: ArcRwNode, - path: impl Iterator, - ) -> Option> { - let mut root = self_; - let path = path.peekable(); - for component in path { - let child = root.read().await.children.get(component)?.clone(); - root = child; - } - Some(root) - } - /// insert child node by path - pub async fn insert_path( - self_: ArcRwNode, - path: impl Iterator, - value: V, - ) -> InsertResult> { - let path = path.collect::>(); - let mut root = self_; - if path.is_empty() { - return InsertResult::IsRoot; - } - let (last, path) = path.split_last().unwrap(); - for component in path { - let child = match root.read().await.children.get(*component) { - Some(child) => child.clone(), - None => return InsertResult::ParentNotFound, - }; - root = child; - } - let mut root = root.write().await; - InsertResult::Inserted(root.insert_component(last.to_os_string(), value).await) - } -} - -/// Path tree with partial locking -#[derive(Clone)] -pub struct LockPathTree(ArcRwNode); - -impl LockPathTree { - pub fn new(root: V) -> Self { - LockPathTree(Arc::new(RwLock::new(LockNode::new(root)))) - } - pub async fn get_by_path(&self, path: impl AsRef) -> Option> { - LockNode::get_by_path(self.0.clone(), to_internal_path(&path)).await - } - /// insert path - #[inline] - pub async fn insert_path( - &self, - path: impl AsRef, - value: V, - ) -> InsertResult> { - LockNode::insert_path(self.0.clone(), to_internal_path(&path), value).await - } -} - -struct Node { - children: HashMap>, - value: V, -} - -impl Node { - fn new(value: V) -> Self { - Self { - children: Default::default(), - value, - } - } -} - -pub struct PathTree(Node); - -impl PathTree -where - V: Default, -{ - pub fn new() -> Self { - PathTree(Node::new(Default::default())) - } - pub fn insert_path(&mut self, path: impl AsRef, mut value: V) -> Option - where - V: Default, - { - let mut root = &mut self.0; - let path = to_internal_path(&path).collect::>(); - let (last, path) = path.split_last().unwrap(); - for component in path { - root = root - .children - .entry(component.to_os_string()) - .or_insert_with(|| Node::new(Default::default())); - } - match root.children.get_mut(*last) { - Some(x) => { - std::mem::swap(&mut x.value, &mut value); - Some(value) - } - None => None, - } - } - fn get_mut(&mut self, path: impl AsRef) -> Option<&mut V> { - let this = &mut *self; - let mut root = &mut this.0; - for component in to_internal_path(&path) { - root = root.children.get_mut(component)?; - } - Some(&mut root.value) - } - fn get(&self, path: impl AsRef) -> Option<&V> { - let this = &self; - let mut root = &this.0; - for component in to_internal_path(&path) { - root = root.children.get(component)?; - } - Some(&root.value) - } -} - -#[cfg(test)] -mod lock_test { - use super::*; - - #[tokio::test] - async fn insert_parent_not_found() { - let tree = LockPathTree::new(0); - assert_eq!( - tree.insert_path("a/b/c", 1).await, - InsertResult::ParentNotFound - ); - } - #[tokio::test] - async fn insert_is_root() { - let tree = LockPathTree::new(0); - assert_eq!(tree.insert_path("", 1).await, InsertResult::IsRoot); - } - #[tokio::test] - async fn insert() { - let tree = LockPathTree::new(0); - macro_rules! insert { - ($path:expr, $val:expr) => { - assert_eq!( - tree.insert_path($path, $val).await, - InsertResult::Inserted(None) - ); - }; - } - macro_rules! lookup { - ($path:expr,$val:expr) => { - assert_eq!( - tree.get_by_path($path).await.unwrap().read().await.value, - $val - ); - }; - } - - insert!("a", 1); - insert!("a/u", 2); - insert!("a/h", 3); - insert!("a/h/f", 4); - lookup!("/", 0); - lookup!("a", 1); - lookup!("a/u", 2); - lookup!("a/h", 3); - lookup!("a/h/f", 4); - } - #[tokio::test(flavor = "multi_thread", worker_threads = 8)] - async fn multi_lookup() { - let tree = LockPathTree::new(0); - tree.insert_path("a", 1).await; - tree.insert_path("a/u", 2).await; - tree.insert_path("a/h", 3).await; - tree.insert_path("a/h/f", 4).await; - async fn lookup(tree: &LockPathTree, path: &str, val: i32) { - for _ in 0..30 { - let tree = tree.clone(); - let path = path.to_string(); - tokio::spawn(async move { - for _ in 0..300 { - assert_eq!( - tree.get_by_path(&path).await.unwrap().read().await.value, - val - ); - } - }) - .await - .unwrap(); - } - } - tokio::join!( - lookup(&tree, "a", 1), - lookup(&tree, "a/u", 2), - lookup(&tree, "a/h", 3), - lookup(&tree, "a/h/f", 4) - ); - } - #[cfg(taregt_os = "windows")] - #[tokio::test] - #[should_panic] - async fn windows() { - let tree = LockPathTree::new(0); - tree.insert_path("C:\\a", 1).await; - } -} -#[cfg(test)] -mod test { - use super::*; -} diff --git a/judger/src/filesystem/tar/block.rs b/judger/src/filesystem/tar/block.rs index 8bc221bd..c645d21c 100644 --- a/judger/src/filesystem/tar/block.rs +++ b/judger/src/filesystem/tar/block.rs @@ -226,9 +226,9 @@ mod test { let mut block = block.clone(); tokio::spawn(async move { for _ in 0..400 { - let mut buf = [0_u8; 11]; - block.read_exact(&mut buf).await.unwrap(); - assert_eq!(buf, *b"hello world"); + let mut buf = [0_u8; 11]; + block.read_exact(&mut buf).await.unwrap(); + assert_eq!(buf, *b"hello world"); } }); } diff --git a/judger/src/filesystem/tar/entry.rs b/judger/src/filesystem/tar/entry.rs index 0b116b51..7854fb57 100644 --- a/judger/src/filesystem/tar/entry.rs +++ b/judger/src/filesystem/tar/entry.rs @@ -5,32 +5,38 @@ //! //! And we map each type of content to BTreeMap -use std::{ffi::OsString, sync::Arc}; +use std::ffi::OsString; use tokio::io::{AsyncRead, AsyncSeek}; -use crate::filesystem::INODE; - use super::block::TarBlock; /// Entry from tar file, should be readonly -#[derive(Debug)] +#[derive(Debug, Default)] pub enum Entry where F: AsyncRead + AsyncSeek + Unpin + 'static, { - SymLink(Arc), - HardLink(INODE), + SymLink(OsString), + HardLink(u64), + #[default] Directory, File(TarBlock), } -// impl Entry -// where -// F: AsyncRead + AsyncSeek + Unpin + 'static, -// { -// pub fn read(&mut self) -// } +impl Clone for Entry +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + fn clone(&self) -> Self { + match self { + Self::SymLink(arg0) => Self::SymLink(arg0.clone()), + Self::HardLink(arg0) => Self::HardLink(arg0.clone()), + Self::Directory => Self::Directory, + Self::File(arg0) => Self::File(arg0.clone()), + } + } +} impl PartialEq for Entry where diff --git a/judger/src/filesystem/tar/map.rs b/judger/src/filesystem/tar/map.rs index 0340185d..49c6985d 100644 --- a/judger/src/filesystem/tar/map.rs +++ b/judger/src/filesystem/tar/map.rs @@ -39,7 +39,7 @@ where if let Some(link_path) = entry.link_name()? { map.insert( path.as_os_str().to_owned(), - Entry::SymLink(Arc::new(link_path.as_os_str().to_owned())), + Entry::SymLink(link_path.as_os_str().to_owned()), ); } else { let start = entry.raw_file_position(); diff --git a/judger/src/filesystem/tar/mod.rs b/judger/src/filesystem/tar/mod.rs index a8af6116..733db675 100644 --- a/judger/src/filesystem/tar/mod.rs +++ b/judger/src/filesystem/tar/mod.rs @@ -2,6 +2,23 @@ mod block; mod entry; mod map; -pub use block::TarBlock; pub use entry::Entry; -pub use map::TarMap; +use tokio::io::{AsyncRead, AsyncSeek}; + +use super::tree::{ArcNode, Tree}; + +pub struct TarLayer +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + map: Tree>, +} + +impl TarLayer +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + pub async fn get_root(&self) -> ArcNode> { + self.map.get_by_path("/").await.unwrap() + } +} diff --git a/judger/src/filesystem/tar/tree.rs b/judger/src/filesystem/tar/tree.rs new file mode 100644 index 00000000..2d6cecb4 --- /dev/null +++ b/judger/src/filesystem/tar/tree.rs @@ -0,0 +1,82 @@ +use std::{ + collections::HashMap, + ffi::{OsStr, OsString}, + path::{Component, Path}, +}; + +fn to_internal_path<'a>(path: &'a impl AsRef) -> impl Iterator { + path.as_ref() + .components() + .filter_map(|component| match component { + Component::Prefix(x) => unreachable!("Windows only: {:?}", x), + Component::RootDir | Component::CurDir | Component::ParentDir => None, + Component::Normal(x) => Some(x), + }) +} + +struct Node { + children: HashMap>, + value: V, +} + +impl Node { + fn new(value: V) -> Self { + Self { + children: Default::default(), + value, + } + } +} + +pub struct Tree(Node); + +impl Tree +where + V: Default, +{ + pub fn new() -> Self { + Tree(Node::new(Default::default())) + } + pub fn insert_path(&mut self, path: impl AsRef, mut value: V) -> Option + where + V: Default, + { + let mut root = &mut self.0; + let path = to_internal_path(&path).collect::>(); + let (last, path) = path.split_last().unwrap(); + for component in path { + root = root + .children + .entry(component.to_os_string()) + .or_insert_with(|| Node::new(Default::default())); + } + match root.children.get_mut(*last) { + Some(x) => { + std::mem::swap(&mut x.value, &mut value); + Some(value) + } + None => None, + } + } + pub fn get_mut(&mut self, path: impl AsRef) -> Option<&mut V> { + let this = &mut *self; + let mut root = &mut this.0; + for component in to_internal_path(&path) { + root = root.children.get_mut(component)?; + } + Some(&mut root.value) + } + pub fn get(&self, path: impl AsRef) -> Option<&V> { + let this = &self; + let mut root = &this.0; + for component in to_internal_path(&path) { + root = root.children.get(component)?; + } + Some(&root.value) + } +} + +#[cfg(test)] +mod test { + use super::*; +} From f46a80a9d1bebb84362f844ccf1c471742654d2c Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Thu, 2 May 2024 18:23:07 +0800 Subject: [PATCH 14/38] feat(Judger): :construction: draft fuse3 adapter --- judger/src/filesystem/adapter.rs | 113 +++++++++++++ judger/src/filesystem/mod.rs | 13 +- judger/src/filesystem/overlay/reply.rs | 5 +- judger/src/filesystem/tree.rs | 225 +++++++++++++++++++++++++ 4 files changed, 349 insertions(+), 7 deletions(-) create mode 100644 judger/src/filesystem/adapter.rs create mode 100644 judger/src/filesystem/tree.rs diff --git a/judger/src/filesystem/adapter.rs b/judger/src/filesystem/adapter.rs new file mode 100644 index 00000000..c5dee60e --- /dev/null +++ b/judger/src/filesystem/adapter.rs @@ -0,0 +1,113 @@ +use std::num::NonZeroU32; + +use crate::filesystem::overlay::BLOCKSIZE; +use crate::semaphore::Semaphore; +use fuse3::{ + raw::{reply::*, Request}, + Errno, Result as FuseResult, +}; +use std::future::{ready as future_ready, Future}; +use tokio::io::{AsyncRead, AsyncSeek}; + +use super::{ + overlay::{Entry, Overlay}, + table::HandleTable, + tree::ArcNode, +}; + +type VecStream = tokio_stream::Iter>>; + +type INODE = u64; +type HANDLE = u64; + +// fn to_attr(inode: u64) -> FileAttr { +// FileAttr { +// ino: inode, +// size: todo!(), +// blocks: todo!(), +// atime: todo!(), +// mtime: todo!(), +// ctime: todo!(), +// kind: todo!(), +// perm: todo!(), +// nlink: todo!(), +// uid: todo!(), +// gid: todo!(), +// rdev: todo!(), +// blksize: todo!(), +// } +// } + +pub struct Filesystem +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + handle_table: HandleTable>>, + overlay: Overlay, + semaphore: Semaphore, +} + +impl fuse3::raw::Filesystem for Filesystem +where + F: AsyncRead + AsyncSeek + Unpin + Send + Sync + 'static, +{ + type DirEntryStream<'a>=VecStream> where Self: 'a; + type DirEntryPlusStream<'a>=VecStream> where Self: 'a; + + fn init(&self, _: Request) -> impl Future> + Send { + future_ready(Ok(ReplyInit { + max_write: NonZeroU32::new(BLOCKSIZE as u32).unwrap(), + })) + } + + fn destroy(&self, _: Request) -> impl Future + Send { + future_ready(()) + } + + fn lookup( + &self, + req: Request, + parent: u64, + name: &std::ffi::OsStr, + ) -> impl Future> + Send { + async move { + match self + .overlay + .inode + .get_child_by_componment(parent, name) + .await + { + Some(x) => todo!(), + None => Err(Errno::new_not_exist()), + } + } + } + fn forget( + &self, + _: Request, + inode: u64, + _: u64, + ) -> impl core::future::Future + Send { + self.overlay.inode.remove(inode); + future_ready(()) + } + fn statfs( + &self, + _: Request, + inode: u64, + ) -> impl Future> + Send { + // FIXME: report files in directory + async { + Ok(ReplyStatFs { + blocks: 0, + bfree: 4096 * 4096, + bavail: 4096 * 2048, + files: 0, + ffree: self.overlay.inode.get_free_inode(), + bsize: BLOCKSIZE as u32, + namelen: 256, + frsize: BLOCKSIZE as u32, + }) + } + } +} diff --git a/judger/src/filesystem/mod.rs b/judger/src/filesystem/mod.rs index e1b3a3de..7b7a3739 100644 --- a/judger/src/filesystem/mod.rs +++ b/judger/src/filesystem/mod.rs @@ -1,11 +1,12 @@ -mod fuse; +use fuse3::FileType; + +mod adapter; mod macro_; mod overlay; -mod path_tree; mod table; mod tar; +mod tree; -type INODE = u64; -type HANDLE = u64; - -use path_tree::*; +trait EntryTrait { + fn kind(&self) -> FileType; +} diff --git a/judger/src/filesystem/overlay/reply.rs b/judger/src/filesystem/overlay/reply.rs index 6c9b6575..39efc272 100644 --- a/judger/src/filesystem/overlay/reply.rs +++ b/judger/src/filesystem/overlay/reply.rs @@ -1,6 +1,9 @@ use std::time::Duration; -use fuse3::{raw::{reply::*, Request}, Timestamp}; +use fuse3::{ + raw::{reply::*, Request}, + Timestamp, +}; use tokio::io::{AsyncRead, AsyncSeek}; use super::Entry; diff --git a/judger/src/filesystem/tree.rs b/judger/src/filesystem/tree.rs new file mode 100644 index 00000000..c881bdb7 --- /dev/null +++ b/judger/src/filesystem/tree.rs @@ -0,0 +1,225 @@ +use std::{ + collections::HashMap, + ffi::{OsStr, OsString}, + ops::{Deref, DerefMut}, + path::{Component, Path}, + sync::Arc, +}; + +use tokio::sync::RwLock; + +fn to_internal_path<'a>(path: &'a impl AsRef) -> impl Iterator { + path.as_ref() + .components() + .filter_map(|component| match component { + Component::Prefix(x) => unreachable!("Windows only: {:?}", x), + Component::RootDir | Component::CurDir | Component::ParentDir => None, + Component::Normal(x) => Some(x), + }) +} + +#[derive(Debug)] +pub enum InsertResult { + AlreadyExists(N), + Inserted(Option), + ParentNotFound, + IsRoot, +} + +impl PartialEq for InsertResult> { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::AlreadyExists(l0), Self::AlreadyExists(r0)) => Arc::ptr_eq(l0, r0), + (Self::Inserted(l0), Self::Inserted(r0)) => match (l0, r0) { + (Some(l0), Some(r0)) => Arc::ptr_eq(l0, r0), + (None, None) => true, + _ => false, + }, + _ => core::mem::discriminant(self) == core::mem::discriminant(other), + } + } +} + +#[derive(Debug)] +pub struct Node { + children: HashMap>, + value: V, +} + +impl Deref for Node { + type Target = V; + fn deref(&self) -> &Self::Target { + &self.value + } +} + +impl DerefMut for Node { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.value + } +} + +pub type ArcNode = Arc>>; + +impl Node { + fn new(value: V) -> Self { + Self { + children: Default::default(), + value, + } + } + /// get child node by component + #[inline] + pub fn get_by_component(&self, component: &OsStr) -> Option> { + self.children.get(component).cloned() + } + /// insert child node by component + /// + /// return the old child node if it exists + pub fn insert_component(&mut self, component: OsString, value: V) -> Option> { + self.children + .insert(component, Arc::new(RwLock::new(Node::new(value)))) + } + pub fn remove_component(&mut self, component: &OsStr) -> Option> { + self.children.remove(component) + } + /// get child node by path + pub async fn get_by_path( + self_: ArcNode, + path: impl Iterator, + ) -> Option> { + let mut root = self_; + let path = path.peekable(); + for component in path { + let child = root.read().await.children.get(component)?.clone(); + root = child; + } + Some(root) + } + /// insert child node by path + pub async fn insert_path( + self_: ArcNode, + path: impl Iterator, + value: V, + ) -> InsertResult> { + let path = path.collect::>(); + let mut root = self_; + if path.is_empty() { + return InsertResult::IsRoot; + } + let (last, path) = path.split_last().unwrap(); + for component in path { + let child = match root.read().await.children.get(*component) { + Some(child) => child.clone(), + None => return InsertResult::ParentNotFound, + }; + root = child; + } + let mut root = root.write().await; + InsertResult::Inserted(root.insert_component(last.to_os_string(), value)) + } +} + +/// Path tree with partial locking +#[derive(Clone)] +pub struct Tree(ArcNode); + +impl Tree { + pub fn new(root: V) -> Self { + Tree(Arc::new(RwLock::new(Node::new(root)))) + } + pub async fn get_by_path(&self, path: impl AsRef) -> Option> { + Node::get_by_path(self.0.clone(), to_internal_path(&path)).await + } + /// insert path + #[inline] + pub async fn insert_path(&self, path: impl AsRef, value: V) -> InsertResult> { + Node::insert_path(self.0.clone(), to_internal_path(&path), value).await + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[tokio::test] + async fn insert_parent_not_found() { + let tree = Tree::new(0); + assert_eq!( + tree.insert_path("a/b/c", 1).await, + InsertResult::ParentNotFound + ); + } + #[tokio::test] + async fn insert_is_root() { + let tree = Tree::new(0); + assert_eq!(tree.insert_path("", 1).await, InsertResult::IsRoot); + } + #[tokio::test] + async fn insert() { + let tree = Tree::new(0); + macro_rules! insert { + ($path:expr, $val:expr) => { + assert_eq!( + tree.insert_path($path, $val).await, + InsertResult::Inserted(None) + ); + }; + } + macro_rules! lookup { + ($path:expr,$val:expr) => { + assert_eq!( + tree.get_by_path($path).await.unwrap().read().await.value, + $val + ); + }; + } + + insert!("a", 1); + insert!("a/u", 2); + insert!("a/h", 3); + insert!("a/h/f", 4); + lookup!("/", 0); + lookup!("a", 1); + lookup!("a/u", 2); + lookup!("a/h", 3); + lookup!("a/h/f", 4); + } + #[tokio::test(flavor = "multi_thread", worker_threads = 8)] + async fn multi_lookup() { + let tree = Tree::new(0); + tree.insert_path("a", 1).await; + tree.insert_path("a/u", 2).await; + tree.insert_path("a/h", 3).await; + tree.insert_path("a/h/f", 4).await; + async fn lookup(tree: &Tree, path: &str, val: i32) { + for _ in 0..30 { + let tree = tree.clone(); + let path = path.to_string(); + tokio::spawn(async move { + for _ in 0..300 { + assert_eq!( + tree.get_by_path(&path).await.unwrap().read().await.value, + val + ); + } + }) + .await + .unwrap(); + } + } + tokio::join!( + lookup(&tree, "a", 1), + lookup(&tree, "a/u", 2), + lookup(&tree, "a/h", 3), + lookup(&tree, "a/h/f", 4) + ); + } + #[cfg(taregt_os = "windows")] + #[tokio::test] + #[should_panic] + async fn windows() { + let tree = Tree::new(0); + tree.insert_path("C:\\a", 1).await; + } +} From ee98bb4e3f48cc33f97470d8a6c6ef9e9245fa39 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Sat, 4 May 2024 16:45:42 +0800 Subject: [PATCH 15/38] refactor(Judger): :recycle: move Entry and Block type and fix state sharing on tree by locking --- judger/src/error.rs | 4 + judger/src/filesystem/adapter.rs | 3 +- judger/src/filesystem/entry/mod.rs | 71 ++++++ .../filesystem/{tar/block.rs => entry/ro.rs} | 77 +++++++ .../{overlay/block.rs => entry/rw.rs} | 44 +++- judger/src/filesystem/entry/template.rs | 204 ++++++++++++++++++ judger/src/filesystem/mod.rs | 16 +- judger/src/filesystem/overlay/entry.rs | 134 ------------ judger/src/filesystem/overlay/mod.rs | 2 - judger/src/filesystem/table/inode.rs | 67 +++--- judger/src/filesystem/table/mod.rs | 2 +- judger/src/filesystem/tar/entry.rs | 52 ----- judger/src/filesystem/tar/map.rs | 146 ------------- judger/src/filesystem/tar/mod.rs | 24 --- judger/src/filesystem/tar/tree.rs | 82 ------- judger/src/filesystem/tree.rs | 161 +++++++++++--- 16 files changed, 578 insertions(+), 511 deletions(-) create mode 100644 judger/src/filesystem/entry/mod.rs rename judger/src/filesystem/{tar/block.rs => entry/ro.rs} (78%) rename judger/src/filesystem/{overlay/block.rs => entry/rw.rs} (88%) create mode 100644 judger/src/filesystem/entry/template.rs delete mode 100644 judger/src/filesystem/overlay/entry.rs delete mode 100644 judger/src/filesystem/tar/entry.rs delete mode 100644 judger/src/filesystem/tar/map.rs delete mode 100644 judger/src/filesystem/tar/mod.rs delete mode 100644 judger/src/filesystem/tar/tree.rs diff --git a/judger/src/error.rs b/judger/src/error.rs index 632ebfac..f23298cf 100644 --- a/judger/src/error.rs +++ b/judger/src/error.rs @@ -1,3 +1,5 @@ +use tokio::sync::broadcast::error; + #[derive(Debug, thiserror::Error)] pub enum Error { #[error("{0}")] @@ -8,4 +10,6 @@ pub enum Error { // OutOfResource(crate::sandbox::ResourceKind), #[error("io error")] IoError(#[from] std::io::Error), + #[error("invaild tarball: `{0}`")] + InvalidTarball(&'static str), } diff --git a/judger/src/filesystem/adapter.rs b/judger/src/filesystem/adapter.rs index c5dee60e..3e6a31f4 100644 --- a/judger/src/filesystem/adapter.rs +++ b/judger/src/filesystem/adapter.rs @@ -1,6 +1,5 @@ use std::num::NonZeroU32; -use crate::filesystem::overlay::BLOCKSIZE; use crate::semaphore::Semaphore; use fuse3::{ raw::{reply::*, Request}, @@ -10,7 +9,7 @@ use std::future::{ready as future_ready, Future}; use tokio::io::{AsyncRead, AsyncSeek}; use super::{ - overlay::{Entry, Overlay}, + overlay::{Overlay}, table::HandleTable, tree::ArcNode, }; diff --git a/judger/src/filesystem/entry/mod.rs b/judger/src/filesystem/entry/mod.rs new file mode 100644 index 00000000..ee550c14 --- /dev/null +++ b/judger/src/filesystem/entry/mod.rs @@ -0,0 +1,71 @@ +use fuse3::FileType; +use tokio::{ + io::{AsyncRead, AsyncSeek}, + sync::RwLock, +}; + +use self::prelude::*; + +mod ro; +mod rw; +mod template; + +pub const MEMBLOCK_BLOCKSIZE: usize = 4096; + +mod prelude { + pub use super::ro::Entry as ReadEntry; + pub use super::rw::Entry as WriteEntry; + pub use super::MEMBLOCK_BLOCKSIZE; + pub use super::{Entry, InoEntry}; +} + +#[derive(Debug)] +pub enum Entry +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ + Read(ReadEntry), + Write(WriteEntry), +} + +impl Clone for Entry +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ + fn clone(&self) -> Self { + match self { + Self::Read(arg0) => Self::Read(arg0.clone()), + Self::Write(arg0) => Self::Write(arg0.clone()), + } + } +} + +impl Default for Entry +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ + fn default() -> Self { + Self::Write(WriteEntry::Directory) + } +} + +#[derive(Debug)] +pub struct InoEntry +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ + entry: Entry, + pub inode: u64, +} + +impl InoEntry +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ + pub async fn kind(&self) -> FileType { + match &self.entry { + Entry::Read(read_entry) => read_entry.kind(), + Entry::Write(write_entry) => write_entry.kind(), + } + } +} diff --git a/judger/src/filesystem/tar/block.rs b/judger/src/filesystem/entry/ro.rs similarity index 78% rename from judger/src/filesystem/tar/block.rs rename to judger/src/filesystem/entry/ro.rs index c645d21c..801657fc 100644 --- a/judger/src/filesystem/tar/block.rs +++ b/judger/src/filesystem/entry/ro.rs @@ -1,4 +1,14 @@ +//! collection of entry +//! +//! In tar file, structure is like this: +//! | type | content | ... +//! +//! And we map each type of content to BTreeMap + +use std::ffi::OsString; + use crate::filesystem::macro_::{chain_poll, report_poll}; +use fuse3::FileType; use std::future::Future; use std::io; use std::{ @@ -13,6 +23,73 @@ use tokio::{ sync::{Mutex, OwnedMutexGuard}, }; +/// Entry from tar file, should be readonly +#[derive(Debug, Default)] +pub enum Entry +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + SymLink(OsString), + HardLink(u64), + #[default] + Directory, + File(TarBlock), +} + +impl Entry +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + #[inline] + pub fn new_dir() -> Self { + Self::default() + } + #[inline] + pub fn new_file(block: TarBlock) -> Self { + Self::File(block) + } + #[inline] + pub fn new_symlink(target: OsString) -> Self { + Self::SymLink(target) + } + #[inline] + pub fn kind(&self) -> FileType { + match self { + Self::SymLink(_) => FileType::Symlink, + Self::HardLink(_) => FileType::RegularFile, + Self::Directory => FileType::Directory, + Self::File(_) => FileType::RegularFile, + } + } +} + +impl Clone for Entry +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + fn clone(&self) -> Self { + match self { + Self::SymLink(arg0) => Self::SymLink(arg0.clone()), + Self::HardLink(arg0) => Self::HardLink(arg0.clone()), + Self::Directory => Self::Directory, + Self::File(arg0) => Self::File(arg0.clone()), + } + } +} + +impl PartialEq for Entry +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::SymLink(l0), Self::SymLink(r0)) => l0 == r0, + (Self::File(l0), Self::File(r0)) => l0 == r0, + _ => core::mem::discriminant(self) == core::mem::discriminant(other), + } + } +} + #[derive(Default, Debug)] enum TarStage { Reading(OwnedMutexGuard), diff --git a/judger/src/filesystem/overlay/block.rs b/judger/src/filesystem/entry/rw.rs similarity index 88% rename from judger/src/filesystem/overlay/block.rs rename to judger/src/filesystem/entry/rw.rs index 3d33f636..741f0a39 100644 --- a/judger/src/filesystem/overlay/block.rs +++ b/judger/src/filesystem/entry/rw.rs @@ -1,4 +1,6 @@ use crate::filesystem::macro_::{chain_poll, report_poll}; +use fuse3::FileType; +use std::ffi::OsString; use std::future::Future; use std::io; use std::{ @@ -13,7 +15,41 @@ use tokio::{ sync::{Mutex, OwnedMutexGuard}, }; -pub const MEMBLOCK_BLOCKSIZE: usize = 4096; +use super::MEMBLOCK_BLOCKSIZE; + +#[derive(Default, Clone, Debug)] +pub enum Entry { + SymLink(OsString), + HardLink(u64), + #[default] + Directory, + File(MemBlock), + Removed, +} + +impl Entry { + pub fn new_dir() -> Self { + Self::default() + } + pub fn new_file() -> Self { + Self::File(MemBlock::new(Vec::new())) + } + pub fn new_symlink(target: OsString) -> Self { + Self::SymLink(target) + } + pub fn new_hardlink(inode: u64) -> Self { + Self::HardLink(inode) + } + pub fn kind(&self) -> FileType { + match self { + Self::SymLink(_) => FileType::Symlink, + Self::HardLink(_) => FileType::RegularFile, + Self::Directory => FileType::Directory, + Self::File(_) => FileType::RegularFile, + Self::Removed => FileType::RegularFile, + } + } +} #[derive(Debug, Default)] enum MemStage { @@ -31,8 +67,8 @@ impl MemStage { } } -#[derive(Default)] -pub struct MemBlock { +#[derive(Default, Debug)] +struct MemBlock { data: Arc>>, cursor: usize, stage: MemStage, @@ -51,7 +87,7 @@ impl Clone for MemBlock { } impl MemBlock { - pub fn new(data: Vec) -> Self { + fn new(data: Vec) -> Self { Self { data: Arc::new(Mutex::new(data)), cursor: 0, diff --git a/judger/src/filesystem/entry/template.rs b/judger/src/filesystem/entry/template.rs new file mode 100644 index 00000000..0c009a4d --- /dev/null +++ b/judger/src/filesystem/entry/template.rs @@ -0,0 +1,204 @@ +use super::prelude::ReadEntry; +use std::{ + collections::BTreeMap, + ffi::OsString, + io::Read, + os::unix::ffi::OsStringExt, + path::Path, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, + }, +}; + +use spin::rwlock::RwLock; +#[cfg(test)] +use std::io::Cursor; +use tar::{Archive, EntryType}; +// use tar::*; +#[cfg(test)] +use tokio::io::BufReader; +use tokio::{ + fs::File, + io::{AsyncRead, AsyncSeek}, + sync::Mutex, + task::spawn_blocking, +}; + +use crate::{ + filesystem::{ + table::{InodeHandle, InodeTable}, + tree::{arc_lock, ArcNode, InsertResult, Node, Tree}, + }, + Error, +}; + +use super::{ro::TarBlock, Entry, InoEntry}; + +impl InodeTable>> +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ + fn new_read_entry(&self, entry: ReadEntry) -> ArcNode> { + let handle = self.allocate(); + let node = Node::new(InoEntry { + entry: Entry::Read(entry), + inode: handle.get_inode(), + }); + handle.consume(node.clone()); + node + } +} + +pub struct TarTree +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ + pub tree: Tree>, + pub inode: InodeTable>>, +} + +impl Default for TarTree +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ + fn default() -> Self { + let inode = InodeTable::default(); + let handle = inode.allocate_root(); + let root = Node::new(InoEntry { + entry: Entry::Read(ReadEntry::new_dir()), + inode: handle.get_inode(), + }); + let tree = Tree::new(root.clone()); + handle.consume(root); + Self { tree, inode } + } +} + +impl TarTree +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ + async fn parse_entry( + &mut self, + entry: tar::Entry<'_, R>, + file: &Arc>, + ) -> Result<(), Error> { + let path = entry.path()?; + let read_entry = match entry.header().entry_type() { + EntryType::Regular | EntryType::Continuous => { + let start = entry.raw_file_position(); + let size = entry.size(); + ReadEntry::new_file(TarBlock::new(file.clone(), start, size)) + } + EntryType::Symlink => ReadEntry::new_symlink(OsString::from_vec( + entry.link_name_bytes().unwrap().into_owned(), + )), + EntryType::Directory => ReadEntry::new_dir(), + _ => { + log::warn!("unsupported entry type: {:?}", entry.header().entry_type()); + return Ok(()); + } + }; + + let node = self.inode.new_read_entry(read_entry); + match self + .tree + .insert_path_recursive(path, node, || { + self.inode.new_read_entry(ReadEntry::new_dir()) + }) + .await + { + InsertResult::AlreadyExists(_) => Err(Error::InvalidTarball("duplicated entry")), + InsertResult::ParentNotFound => unreachable!(), + _ => Ok(()), + } + } + // FIXME: this block + pub async fn inner_new(file: F, std_file: impl Read + Send + 'static) -> Result { + let mut archive = Archive::new(std_file); + let file = Arc::new(Mutex::new(file)); + + let mut self_ = Self::default(); + let entries = archive.entries()?; + for entry in entries { + self_.parse_entry(entry?, &file).await?; + } + Ok(self_) + } +} + +impl TarTree { + pub async fn new(path: impl AsRef + Clone) -> Result { + let file = File::open(path.clone()).await?; + let std_file = File::open(path).await?.into_std().await; + Self::inner_new(file, std_file).await + } +} + +#[cfg(test)] +impl TarTree>> +where + T: AsRef<[u8]> + Send + Unpin + Clone + 'static, +{ + pub async fn test_new(content: T) -> Result { + let file = BufReader::new(Cursor::new(content.clone())); + let std_file = BufReader::new(Cursor::new(content)).into_inner(); + Self::inner_new(file, std_file).await + } +} + +#[cfg(test)] +mod test { + use super::*; + use fuse3::FileType; + use std::{ffi::OsStr, ops::Deref}; + use tokio::io::AsyncReadExt; + + macro_rules! assert_content { + ($entry:expr, $content:expr) => { + if let Entry::File(ref mut file) = $entry { + let mut buf = vec![0_u8; $content.len()]; + file.read_exact(&mut buf).await.unwrap(); + assert_eq!(&buf, $content); + } else { + panic!("entry is not a file") + } + }; + } + macro_rules! assert_kind { + ($tree:expr,$path:expr, $kind:ident) => {{ + let node = $tree.tree.get_by_path($path).await.unwrap(); + let locked = node.read().await; + let entry = locked.deref().deref(); + assert_eq!(entry.kind().await, FileType::$kind, "entry: {:?}", entry); + }}; + } + + // #[tokio::test] + // async fn single_file_map() { + // let content = include_bytes!("../../../test/single_file.tar"); + + // let tree = TarTree::test_new(content).await.unwrap(); + + // let file=tree.tree.get_root().read().await.get_by_component(OsStr::new("single_file.txt")).unwrap(); + + // let single_file = map.0.get_mut(OsStr::new("single_file.txt")).unwrap(); + + // assert_content!(single_file, b"hello world"); + // } + #[tokio::test] + async fn nested_map() { + let content = include_bytes!("../../../test/nested.tar"); + + let tree = TarTree::test_new(content).await.unwrap(); + + assert_kind!(tree, "nest", Directory); + assert_kind!(tree, "nest/a.txt", RegularFile); + assert_kind!(tree, "nest/b.txt", RegularFile); + assert_kind!(tree, "o.txt", RegularFile); + // assert_content!(map.tree.get_mut(OsStr::new("nest/a.txt")).unwrap(), b"a"); + // assert_content!(map.tree.get_mut(OsStr::new("nest/b.txt")).unwrap(), b"b"); + // assert_content!(map.tree.get_mut(OsStr::new("o.txt")).unwrap(), b"o"); + } +} diff --git a/judger/src/filesystem/mod.rs b/judger/src/filesystem/mod.rs index 7b7a3739..afae31de 100644 --- a/judger/src/filesystem/mod.rs +++ b/judger/src/filesystem/mod.rs @@ -1,12 +1,14 @@ -use fuse3::FileType; - -mod adapter; +// mod adapter; +mod entry; mod macro_; -mod overlay; +// mod overlay; mod table; -mod tar; mod tree; -trait EntryTrait { - fn kind(&self) -> FileType; +#[derive(thiserror::Error, Debug)] +pub enum FuseError { + #[error("io error")] + IoError(#[from] std::io::Error), + #[error("not a readable file")] + NotReadable, } diff --git a/judger/src/filesystem/overlay/entry.rs b/judger/src/filesystem/overlay/entry.rs deleted file mode 100644 index 0681931e..00000000 --- a/judger/src/filesystem/overlay/entry.rs +++ /dev/null @@ -1,134 +0,0 @@ -use std::{ - ffi::{OsStr, OsString}, - sync::Arc, -}; - -use tokio::io::{AsyncRead, AsyncSeek}; - -use crate::filesystem::{ - table::{INodeTable, Identified}, - tar::Entry as RoEntry, - tree::ArcNode, -}; - -use super::block::MemBlock; - -type ArcEntry = ArcNode>; - -/// Entry from tar file, it's a replacement of Entry -#[derive(Default)] -pub enum RwEntry { - SymLink(OsString), - HardLink(u64), - #[default] - Directory, - File(MemBlock), - Removed, -} - -/// A workaround to not use dynamic dispatch and compact the size of Entry -pub enum EntryKind -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - Rw(ArcNode), - Ro(ArcNode>), -} - -impl Clone for EntryKind -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - fn clone(&self) -> Self { - match self { - Self::Rw(arg0) => Self::Rw(arg0.clone()), - Self::Ro(arg0) => Self::Ro(arg0.clone()), - } - } -} - -impl EntryKind -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - pub async fn get_child_by_componment(&self, component: &OsStr) -> Option { - match self { - Self::Rw(node) => (node.read().await.get_by_component(component)).map(Self::Rw), - Self::Ro(node) => todo!(), - } - } -} - -pub struct Entry -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - pub kind: EntryKind, - inode: u64, -} - -impl Identified for Entry -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - fn get_id(&self) -> usize { - match &self.kind { - EntryKind::Rw(x) => Arc::as_ptr(x) as usize, - EntryKind::Ro(x) => Arc::as_ptr(x) as usize, - } - } -} - -impl Clone for Entry -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - fn clone(&self) -> Self { - Self { - kind: self.kind.clone(), - inode: self.inode.clone(), - } - } -} - -impl Entry -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - #[inline] - pub fn from_kind(kind: EntryKind, inode: u64) -> Self { - Self { kind, inode } - } - #[inline] - pub fn get_inode(&self) -> u64 { - self.inode - } -} - -impl INodeTable> -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - pub fn add_entry_rw(&self, entry: ArcNode) -> Entry { - self.allocate(|x| Entry::from_kind(EntryKind::Rw(entry.clone()), x)) - } - pub fn add_entry_ro(&self, entry: ArcNode>) -> Entry { - self.allocate(|x| Entry::from_kind(EntryKind::Ro(entry.clone()), x)) - } - pub fn add_entry_kind(&self, kind: EntryKind) -> Entry { - self.allocate(|x| Entry::from_kind(kind.clone(), x)) - } - pub fn lookup(&self, inode: u64) -> Option> { - self.get(inode) - } - pub async fn get_child_by_componment(&self, inode: u64, component: &OsStr) -> Option> { - if let Some(entry) = self.get(inode) { - return entry - .kind - .get_child_by_componment(component) - .await - .map(|kind| self.add_entry_kind(kind)); - } - None - } -} diff --git a/judger/src/filesystem/overlay/mod.rs b/judger/src/filesystem/overlay/mod.rs index f7009102..a57022f0 100644 --- a/judger/src/filesystem/overlay/mod.rs +++ b/judger/src/filesystem/overlay/mod.rs @@ -1,5 +1,3 @@ -mod block; -mod entry; mod reply; pub use block::MEMBLOCK_BLOCKSIZE as BLOCKSIZE; diff --git a/judger/src/filesystem/table/inode.rs b/judger/src/filesystem/table/inode.rs index 846298c6..250131b1 100644 --- a/judger/src/filesystem/table/inode.rs +++ b/judger/src/filesystem/table/inode.rs @@ -1,45 +1,67 @@ use std::{ collections::BTreeMap, + mem::ManuallyDrop, sync::atomic::{AtomicU64, Ordering}, }; +use futures_core::{future::BoxFuture, Future}; use spin::RwLock; const MAX_INODE: u64 = 1 << 63; -pub trait Identified { - fn get_id(&self) -> usize; +pub struct InodeHandle<'a, E: Clone> { + inode: u64, + table: &'a InodeTable, } -pub struct INodeTable { +impl<'a, E: Clone> Drop for InodeHandle<'a, E> { + fn drop(&mut self) { + panic!("InodeHandle should be consumed by allocate") + } +} + +impl<'a, E: Clone> InodeHandle<'a, E> { + pub fn get_inode(&self) -> u64 { + self.inode + } + pub fn consume(self, value: E) { + assert!(self.table.inode.write().insert(self.inode, value).is_none()); + ManuallyDrop::new(self); + } +} + +pub struct InodeTable { inode: RwLock>, - id: RwLock>, inode_generator: AtomicU64, } -impl INodeTable { - pub fn new() -> Self { +impl Default for InodeTable { + fn default() -> Self { Self { inode: RwLock::new(BTreeMap::new()), - id: RwLock::new(BTreeMap::new()), - inode_generator: AtomicU64::new(1), + inode_generator: AtomicU64::new(2), + } + } +} + +impl InodeTable { + pub fn cloned(&self) -> Self { + Self { + inode: RwLock::new(self.inode.read().clone()), + inode_generator: AtomicU64::new(self.inode_generator.load(Ordering::SeqCst)), } } - pub fn allocate(&self, mut f: F) -> E - where - F: FnMut(u64) -> E, - { + pub fn allocate_root(&self) -> InodeHandle { + InodeHandle { + inode: 1, + table: self, + } + } + pub fn allocate(&self) -> InodeHandle { let inode = self .inode_generator .fetch_add(1, std::sync::atomic::Ordering::AcqRel); - let entry = f(inode); - match { self.id.read().get(&entry.get_id()) } { - Some(&x) => f(x), - None => { - self.inode.write().insert(inode, entry.clone()); - entry - } - } + InodeHandle { inode, table: self } } /// get entry by inode pub fn get(&self, inode: u64) -> Option { @@ -47,10 +69,7 @@ impl INodeTable { } /// deallocate inode pub fn remove(&self, inode: u64) { - // FIXME: inode should be reused - if let Some(e) = { self.inode.write().remove(&inode) } { - self.id.write().remove(&e.get_id()); - } + // FIXME: inode should be reused, currently it's a non-op } pub fn get_free_inode(&self) -> u64 { MAX_INODE - self.get_used_inode() diff --git a/judger/src/filesystem/table/mod.rs b/judger/src/filesystem/table/mod.rs index 397faa97..6fa25ac4 100644 --- a/judger/src/filesystem/table/mod.rs +++ b/judger/src/filesystem/table/mod.rs @@ -2,4 +2,4 @@ mod handle; mod inode; pub use handle::HandleTable; -pub use inode::{INodeTable, Identified}; +pub use inode::{InodeHandle, InodeTable}; diff --git a/judger/src/filesystem/tar/entry.rs b/judger/src/filesystem/tar/entry.rs deleted file mode 100644 index 7854fb57..00000000 --- a/judger/src/filesystem/tar/entry.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! collection of entry -//! -//! In tar file, structure is like this: -//! | type | content | ... -//! -//! And we map each type of content to BTreeMap - -use std::ffi::OsString; - -use tokio::io::{AsyncRead, AsyncSeek}; - -use super::block::TarBlock; - -/// Entry from tar file, should be readonly -#[derive(Debug, Default)] -pub enum Entry -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - SymLink(OsString), - HardLink(u64), - #[default] - Directory, - File(TarBlock), -} - -impl Clone for Entry -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - fn clone(&self) -> Self { - match self { - Self::SymLink(arg0) => Self::SymLink(arg0.clone()), - Self::HardLink(arg0) => Self::HardLink(arg0.clone()), - Self::Directory => Self::Directory, - Self::File(arg0) => Self::File(arg0.clone()), - } - } -} - -impl PartialEq for Entry -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::SymLink(l0), Self::SymLink(r0)) => l0 == r0, - (Self::File(l0), Self::File(r0)) => l0 == r0, - _ => core::mem::discriminant(self) == core::mem::discriminant(other), - } - } -} diff --git a/judger/src/filesystem/tar/map.rs b/judger/src/filesystem/tar/map.rs deleted file mode 100644 index 49c6985d..00000000 --- a/judger/src/filesystem/tar/map.rs +++ /dev/null @@ -1,146 +0,0 @@ -use std::{ - collections::BTreeMap, - ffi::OsString, - io::{Error, Read}, - path::Path, - sync::Arc, -}; - -#[cfg(test)] -use std::io::Cursor; -use tar::*; -#[cfg(test)] -use tokio::io::BufReader; -use tokio::{ - fs::File, - io::{AsyncRead, AsyncSeek}, - sync::Mutex, - task::spawn_blocking, -}; - -use super::{block::TarBlock, entry::Entry}; - -pub struct TarMap(BTreeMap>) -where - F: AsyncRead + AsyncSeek + Unpin + 'static; - -impl TarMap where F: AsyncRead + AsyncSeek + Unpin + 'static {} - -fn parse_entry( - map: &mut BTreeMap>, - entry: tar::Entry<'_, R>, - file: &Arc>, -) -> Result<(), Error> -where - F: AsyncRead + AsyncSeek + Unpin, - R: Read, -{ - let path = entry.path()?; - if let Some(link_path) = entry.link_name()? { - map.insert( - path.as_os_str().to_owned(), - Entry::SymLink(link_path.as_os_str().to_owned()), - ); - } else { - let start = entry.raw_file_position(); - let size = entry.size(); - map.insert( - path.as_os_str().to_owned(), - Entry::File(TarBlock::new(file.clone(), start, size)), - ); - } - let mut ancestors = path.ancestors(); - ancestors.next(); - for ancestor in ancestors { - if !map.contains_key(ancestor.as_os_str()) { - map.insert(ancestor.as_os_str().to_owned(), Entry::Directory); - } - } - - Ok(()) -} - -impl TarMap -where - F: AsyncRead + AsyncSeek + Unpin + Send + 'static, -{ - pub async fn inner_new(file: F, std_file: impl Read + Send + 'static) -> Result { - let file = Arc::new(Mutex::new(file)); - - let map = spawn_blocking(move || -> Result<_, Error> { - let mut map = BTreeMap::new(); - - let mut archive = Archive::new(std_file); - let entries = archive.entries()?; - for entry in entries { - parse_entry(&mut map, entry?, &file)?; - } - - Ok(map) - }) - .await??; - - Ok(Self(map)) - } -} - -impl TarMap { - pub async fn new(path: impl AsRef + Clone) -> Result { - let file = File::open(path.clone()).await?; - let std_file = File::open(path).await?.into_std().await; - Self::inner_new(file, std_file).await - } -} - -#[cfg(test)] -impl TarMap>> -where - T: AsRef<[u8]> + Send + Unpin + Clone + 'static, -{ - pub async fn test_new(content: T) -> Result { - let file = BufReader::new(Cursor::new(content.clone())); - let std_file = BufReader::new(Cursor::new(content)).into_inner(); - Self::inner_new(file, std_file).await - } -} - -#[cfg(test)] -mod test { - use super::*; - use std::ffi::OsStr; - use tokio::io::AsyncReadExt; - - macro_rules! assert_content { - ($entry:expr, $content:expr) => { - if let Entry::File(ref mut file) = $entry { - let mut buf = vec![0_u8; $content.len()]; - file.read_exact(&mut buf).await.unwrap(); - assert_eq!(&buf, $content); - } else { - panic!("entry is not a file") - } - }; - } - - #[tokio::test] - async fn single_file_map() { - let content = include_bytes!("../../../test/single_file.tar"); - - let mut map = TarMap::test_new(content).await.unwrap(); - - let single_file = map.0.get_mut(OsStr::new("single_file.txt")).unwrap(); - - assert_content!(single_file, b"hello world"); - } - #[tokio::test] - async fn nested_map() { - let content = include_bytes!("../../../test/nested.tar"); - - let mut map = TarMap::test_new(content).await.unwrap(); - - assert_eq!(*map.0.get(OsStr::new("nest")).unwrap(), Entry::Directory); - assert_content!(map.0.get_mut(OsStr::new("nest/a.txt")).unwrap(), b"a"); - assert_content!(map.0.get_mut(OsStr::new("nest/b.txt")).unwrap(), b"b"); - assert_content!(map.0.get_mut(OsStr::new("o.txt")).unwrap(), b"o"); - } -} diff --git a/judger/src/filesystem/tar/mod.rs b/judger/src/filesystem/tar/mod.rs deleted file mode 100644 index 733db675..00000000 --- a/judger/src/filesystem/tar/mod.rs +++ /dev/null @@ -1,24 +0,0 @@ -mod block; -mod entry; -mod map; - -pub use entry::Entry; -use tokio::io::{AsyncRead, AsyncSeek}; - -use super::tree::{ArcNode, Tree}; - -pub struct TarLayer -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - map: Tree>, -} - -impl TarLayer -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - pub async fn get_root(&self) -> ArcNode> { - self.map.get_by_path("/").await.unwrap() - } -} diff --git a/judger/src/filesystem/tar/tree.rs b/judger/src/filesystem/tar/tree.rs deleted file mode 100644 index 2d6cecb4..00000000 --- a/judger/src/filesystem/tar/tree.rs +++ /dev/null @@ -1,82 +0,0 @@ -use std::{ - collections::HashMap, - ffi::{OsStr, OsString}, - path::{Component, Path}, -}; - -fn to_internal_path<'a>(path: &'a impl AsRef) -> impl Iterator { - path.as_ref() - .components() - .filter_map(|component| match component { - Component::Prefix(x) => unreachable!("Windows only: {:?}", x), - Component::RootDir | Component::CurDir | Component::ParentDir => None, - Component::Normal(x) => Some(x), - }) -} - -struct Node { - children: HashMap>, - value: V, -} - -impl Node { - fn new(value: V) -> Self { - Self { - children: Default::default(), - value, - } - } -} - -pub struct Tree(Node); - -impl Tree -where - V: Default, -{ - pub fn new() -> Self { - Tree(Node::new(Default::default())) - } - pub fn insert_path(&mut self, path: impl AsRef, mut value: V) -> Option - where - V: Default, - { - let mut root = &mut self.0; - let path = to_internal_path(&path).collect::>(); - let (last, path) = path.split_last().unwrap(); - for component in path { - root = root - .children - .entry(component.to_os_string()) - .or_insert_with(|| Node::new(Default::default())); - } - match root.children.get_mut(*last) { - Some(x) => { - std::mem::swap(&mut x.value, &mut value); - Some(value) - } - None => None, - } - } - pub fn get_mut(&mut self, path: impl AsRef) -> Option<&mut V> { - let this = &mut *self; - let mut root = &mut this.0; - for component in to_internal_path(&path) { - root = root.children.get_mut(component)?; - } - Some(&mut root.value) - } - pub fn get(&self, path: impl AsRef) -> Option<&V> { - let this = &self; - let mut root = &this.0; - for component in to_internal_path(&path) { - root = root.children.get(component)?; - } - Some(&root.value) - } -} - -#[cfg(test)] -mod test { - use super::*; -} diff --git a/judger/src/filesystem/tree.rs b/judger/src/filesystem/tree.rs index c881bdb7..482275f8 100644 --- a/judger/src/filesystem/tree.rs +++ b/judger/src/filesystem/tree.rs @@ -6,8 +6,13 @@ use std::{ sync::Arc, }; +use futures_core::{future::BoxFuture, Future}; use tokio::sync::RwLock; +pub fn arc_lock(x: T) -> Arc> { + Arc::new(RwLock::new(x)) +} + fn to_internal_path<'a>(path: &'a impl AsRef) -> impl Iterator { path.as_ref() .components() @@ -62,11 +67,28 @@ impl DerefMut for Node { pub type ArcNode = Arc>>; impl Node { - fn new(value: V) -> Self { - Self { + pub fn new(value: V) -> ArcNode { + Arc::new(RwLock::new(Self { children: Default::default(), value, - } + })) + } + fn arc_clone<'a>(x: &'a mut ArcNode) -> BoxFuture<'a, ()> + where + V: Clone + Send + Sync + 'static, + { + Box::pin(async move { + let mut node = { + let lock = x.deref().read().await; + let value = lock.deref().value.clone(); + let children = lock.deref().children.clone(); + Node { value, children } + }; + for (_, mut v) in node.children.iter_mut() { + Self::arc_clone(&mut v).await; + } + *x = arc_lock(node); + }) } /// get child node by component #[inline] @@ -76,9 +98,12 @@ impl Node { /// insert child node by component /// /// return the old child node if it exists - pub fn insert_component(&mut self, component: OsString, value: V) -> Option> { - self.children - .insert(component, Arc::new(RwLock::new(Node::new(value)))) + pub fn insert_component( + &mut self, + component: OsString, + value: ArcNode, + ) -> Option> { + self.children.insert(component, value) } pub fn remove_component(&mut self, component: &OsStr) -> Option> { self.children.remove(component) @@ -96,12 +121,15 @@ impl Node { } Some(root) } - /// insert child node by path - pub async fn insert_path( + async fn insert_closure( self_: ArcNode, path: impl Iterator, - value: V, - ) -> InsertResult> { + value: ArcNode, + mut f: F, + ) -> InsertResult> + where + F: FnMut(&OsStr, &mut Node) -> Result, InsertResult>>, + { let path = path.collect::>(); let mut root = self_; if path.is_empty() { @@ -109,9 +137,11 @@ impl Node { } let (last, path) = path.split_last().unwrap(); for component in path { - let child = match root.read().await.children.get(*component) { - Some(child) => child.clone(), - None => return InsertResult::ParentNotFound, + let child = match f(component, root.write().await.deref_mut()) { + Ok(x) => x, + Err(x) => { + return x; + } }; root = child; } @@ -121,20 +151,70 @@ impl Node { } /// Path tree with partial locking -#[derive(Clone)] pub struct Tree(ArcNode); +impl Default for Tree +where + V: Default, +{ + fn default() -> Self { + Self(Node::new(Default::default())) + } +} + impl Tree { - pub fn new(root: V) -> Self { - Tree(Arc::new(RwLock::new(Node::new(root)))) + pub fn new(root: ArcNode) -> Self { + Tree(root) + } + pub fn cloned(&self) -> Self { + Self(self.0.clone()) + } + pub async fn clone(&self) -> Self + where + V: Clone + Send + Sync + 'static, + { + let mut new_node = self.0.clone(); + Node::arc_clone(&mut new_node).await; + Self(new_node) } pub async fn get_by_path(&self, path: impl AsRef) -> Option> { Node::get_by_path(self.0.clone(), to_internal_path(&path)).await } - /// insert path - #[inline] - pub async fn insert_path(&self, path: impl AsRef, value: V) -> InsertResult> { - Node::insert_path(self.0.clone(), to_internal_path(&path), value).await + pub async fn insert_path( + &self, + path: impl AsRef, + value: ArcNode, + ) -> InsertResult> { + let path = to_internal_path(&path); + Node::insert_closure(self.0.clone(), path, value, |component, root| { + match root.children.get(component) { + Some(child) => Ok(child.clone()), + None => return Err(InsertResult::ParentNotFound), + } + }) + .await + } + pub fn get_root(&self) -> ArcNode { + self.0.clone() + } + pub async fn insert_path_recursive( + &self, + path: impl AsRef, + value: ArcNode, + mut default: F, + ) -> InsertResult> + where + F: FnMut() -> ArcNode, + { + let path = to_internal_path(&path); + Node::insert_closure(self.0.clone(), path, value, |component, root| { + Ok(root + .children + .entry(component.to_os_string()) + .or_insert_with(|| default()) + .clone()) + }) + .await } } @@ -144,24 +224,27 @@ mod test { #[tokio::test] async fn insert_parent_not_found() { - let tree = Tree::new(0); + let tree = Tree::new(Node::new(0)); assert_eq!( - tree.insert_path("a/b/c", 1).await, + tree.insert_path("a/b/c", Node::new(1)).await, InsertResult::ParentNotFound ); } #[tokio::test] async fn insert_is_root() { - let tree = Tree::new(0); - assert_eq!(tree.insert_path("", 1).await, InsertResult::IsRoot); + let tree = Tree::new(Node::new(0)); + assert_eq!( + tree.insert_path("", Node::new(1)).await, + InsertResult::IsRoot + ); } #[tokio::test] async fn insert() { - let tree = Tree::new(0); + let tree = Tree::new(Node::new(0)); macro_rules! insert { ($path:expr, $val:expr) => { assert_eq!( - tree.insert_path($path, $val).await, + tree.insert_path($path, Node::new($val)).await, InsertResult::Inserted(None) ); }; @@ -187,14 +270,14 @@ mod test { } #[tokio::test(flavor = "multi_thread", worker_threads = 8)] async fn multi_lookup() { - let tree = Tree::new(0); - tree.insert_path("a", 1).await; - tree.insert_path("a/u", 2).await; - tree.insert_path("a/h", 3).await; - tree.insert_path("a/h/f", 4).await; + let tree = Tree::new(Node::new(0)); + tree.insert_path("a", Node::new(1)).await; + tree.insert_path("a/u", Node::new(2)).await; + tree.insert_path("a/h", Node::new(3)).await; + tree.insert_path("a/h/f", Node::new(4)).await; async fn lookup(tree: &Tree, path: &str, val: i32) { for _ in 0..30 { - let tree = tree.clone(); + let tree = tree.cloned(); let path = path.to_string(); tokio::spawn(async move { for _ in 0..300 { @@ -215,11 +298,23 @@ mod test { lookup(&tree, "a/h/f", 4) ); } + #[tokio::test] + async fn deep_clone() { + let tree_a = Tree::new(Node::new(0)); + let tree_b = tree_a.clone().await; + tree_a.insert_path("a", Node::new(1)).await; + tree_a.insert_path("a/u", Node::new(2)).await; + tree_a.insert_path("a/h", Node::new(3)).await; + tree_a.insert_path("a/h/f", Node::new(4)).await; + assert!(tree_a.get_by_path("a/h/f").await.is_some()); + assert!(tree_b.get_by_path("a/h/f").await.is_none()); + assert!(tree_b.get_by_path("a").await.is_none()); + } #[cfg(taregt_os = "windows")] #[tokio::test] #[should_panic] async fn windows() { - let tree = Tree::new(0); + let tree = Tree::new(Node::new(0)); tree.insert_path("C:\\a", 1).await; } } From 23477f8ad682bf35979520ec8d89bd587d922d80 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Mon, 6 May 2024 15:34:46 +0800 Subject: [PATCH 16/38] refactor(Judger): :recycle: refactor Entry and (Read/Write)Entry to improve proformance(less locking) and use remove duplicated code by NewType pattern --- Cargo.lock | 10 +- judger/Cargo.toml | 2 + judger/src/filesystem/adapter.rs | 168 ++++++++++++++++++------ judger/src/filesystem/entry/mod.rs | 41 +++++- judger/src/filesystem/entry/ro.rs | 33 ++++- judger/src/filesystem/entry/rw.rs | 55 +++++++- judger/src/filesystem/entry/template.rs | 26 +--- judger/src/filesystem/entry/wrapper.rs | 57 ++++++++ judger/src/filesystem/mod.rs | 40 +++++- judger/src/filesystem/overlay/mod.rs | 41 ------ judger/src/filesystem/overlay/reply.rs | 43 ------ judger/src/filesystem/reply.rs | 54 ++++++++ judger/src/filesystem/tree.rs | 5 + judger/src/sandbox/mod.rs | 3 - 14 files changed, 402 insertions(+), 176 deletions(-) create mode 100644 judger/src/filesystem/entry/wrapper.rs delete mode 100644 judger/src/filesystem/overlay/mod.rs delete mode 100644 judger/src/filesystem/overlay/reply.rs create mode 100644 judger/src/filesystem/reply.rs diff --git a/Cargo.lock b/Cargo.lock index 220dbda5..958784dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -933,9 +933,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "bytestring" @@ -2899,6 +2899,7 @@ dependencies = [ name = "judger" version = "0.1.0" dependencies = [ + "bytes", "cgroups-rs", "crossbeam", "derive_builder", @@ -2907,6 +2908,7 @@ dependencies = [ "futures-core", "grpc", "lazy_static", + "libc", "log", "prost 0.12.3", "prost-types", @@ -3224,9 +3226,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libm" diff --git a/judger/Cargo.toml b/judger/Cargo.toml index 06cc1624..9b186850 100644 --- a/judger/Cargo.toml +++ b/judger/Cargo.toml @@ -19,6 +19,8 @@ tar = "0.4.40" grpc = { path = "../grpc" } crossbeam = "0.8.4" lazy_static = "1.4.0" +libc = "0.2.154" +bytes = "1.6.0" [dependencies.fuse3] version = "0.7.1" diff --git a/judger/src/filesystem/adapter.rs b/judger/src/filesystem/adapter.rs index 3e6a31f4..6d6f8060 100644 --- a/judger/src/filesystem/adapter.rs +++ b/judger/src/filesystem/adapter.rs @@ -1,48 +1,36 @@ -use std::num::NonZeroU32; +use std::{ffi::OsStr, num::NonZeroU32}; -use crate::semaphore::Semaphore; +use crate::{ + filesystem::{reply::ImmutParsable, FuseError}, + semaphore::Semaphore, +}; use fuse3::{ raw::{reply::*, Request}, - Errno, Result as FuseResult, + FileType, Result as FuseResult, }; use std::future::{ready as future_ready, Future}; use tokio::io::{AsyncRead, AsyncSeek}; use super::{ - overlay::{Overlay}, - table::HandleTable, + entry::prelude::*, + table::{self, HandleTable}, tree::ArcNode, }; -type VecStream = tokio_stream::Iter>>; - -type INODE = u64; -type HANDLE = u64; +type VecStream = tokio_stream::Iter>; -// fn to_attr(inode: u64) -> FileAttr { -// FileAttr { -// ino: inode, -// size: todo!(), -// blocks: todo!(), -// atime: todo!(), -// mtime: todo!(), -// ctime: todo!(), -// kind: todo!(), -// perm: todo!(), -// nlink: todo!(), -// uid: todo!(), -// gid: todo!(), -// rdev: todo!(), -// blksize: todo!(), -// } +// macro_rules! cerr { +// ($e:ident) => { +// Errno::from(libc::$e) +// }; // } pub struct Filesystem where - F: AsyncRead + AsyncSeek + Unpin + 'static, + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { - handle_table: HandleTable>>, - overlay: Overlay, + handle_table: HandleTable>>, + tree: TarTree, semaphore: Semaphore, } @@ -67,18 +55,16 @@ where &self, req: Request, parent: u64, - name: &std::ffi::OsStr, + name: &OsStr, ) -> impl Future> + Send { async move { - match self - .overlay - .inode - .get_child_by_componment(parent, name) - .await - { - Some(x) => todo!(), - None => Err(Errno::new_not_exist()), + if let Some(entry) = self.tree.inode.get(parent) { + let entry = entry.read().await; + if let Some(entry) = entry.get_by_component(name) { + return Ok(ReplyEntry::parse(req, entry).await); + } } + Err(FuseError::InodeNotFound.into()) } } fn forget( @@ -87,7 +73,7 @@ where inode: u64, _: u64, ) -> impl core::future::Future + Send { - self.overlay.inode.remove(inode); + self.tree.inode.remove(inode); future_ready(()) } fn statfs( @@ -95,18 +81,120 @@ where _: Request, inode: u64, ) -> impl Future> + Send { - // FIXME: report files in directory async { Ok(ReplyStatFs { blocks: 0, bfree: 4096 * 4096, bavail: 4096 * 2048, files: 0, - ffree: self.overlay.inode.get_free_inode(), + ffree: self.tree.inode.get_free_inode(), bsize: BLOCKSIZE as u32, namelen: 256, frsize: BLOCKSIZE as u32, }) } } + fn opendir( + &self, + req: Request, + inode: u64, + flags: u32, + ) -> impl Future> + Send { + async move { + let entry = self.tree.inode.get(inode).ok_or(FuseError::InodeNotFound)?; + if entry.read().await.kind().await != FileType::Directory { + return Err(FuseError::NotDir.into()); + } + let handle = self.handle_table.add(entry); + Ok(ReplyOpen { + fh: handle, + flags: 0, + }) + } + } + fn read( + &self, + req: Request, + inode: u64, + fh: u64, + offset: u64, + size: u32, + ) -> impl Future> + Send { + async move { + let entry = self.handle_table.get(fh).ok_or(FuseError::HandleNotFound)?; + let mut entry = entry.write().await; + entry + .read(offset, size) + .await + .map(|data| ReplyData { data }) + .map_err(Into::into) + } + } + fn readdir<'a>( + &'a self, + req: Request, + parent: u64, + fh: u64, + offset: i64, + ) -> impl Future>>> + Send { + async move { + let entry = self.handle_table.get(fh).ok_or(FuseError::NotDir)?; + let entry = entry.read().await; + // FIXME: use stream rather than vec iterator + let mut result: Vec> = Vec::new(); + for (name, child) in entry.list_child() { + let child = child.read().await; + result.push(Ok(child.dir_entry(name.to_os_string()).await)); + } + Ok(ReplyDirectory { + entries: tokio_stream::iter(result.into_iter()), + }) + } + } + fn access( + &self, + req: Request, + inode: u64, + mask: u32, + ) -> impl Future> + Send { + future_ready(Ok(())) + } + fn fsync( + &self, + req: Request, + inode: u64, + fh: u64, + datasync: bool, + ) -> impl Future> + Send { + future_ready(Ok(())) + } + fn fsyncdir( + &self, + req: Request, + inode: u64, + fh: u64, + datasync: bool, + ) -> impl Future> + Send { + future_ready(Ok(())) + } + fn write( + &self, + req: Request, + inode: u64, + fh: u64, + offset: u64, + data: &[u8], + write_flags: u32, + flags: u32, + ) -> impl Future> + Send { + async move { + let entry = self.handle_table.get(fh).ok_or(FuseError::HandleNotFound)?; + let mut entry = entry.write().await; + entry + .write(offset, data) + .await + .map(|written| ReplyWrite { written }) + .map_err(Into::into) + } + } } diff --git a/judger/src/filesystem/entry/mod.rs b/judger/src/filesystem/entry/mod.rs index ee550c14..6ed04616 100644 --- a/judger/src/filesystem/entry/mod.rs +++ b/judger/src/filesystem/entry/mod.rs @@ -1,21 +1,25 @@ -use fuse3::FileType; -use tokio::{ - io::{AsyncRead, AsyncSeek}, - sync::RwLock, -}; +use std::ffi::OsString; + +use bytes::Bytes; +use fuse3::{raw::reply::DirectoryEntry, FileType}; +use tokio::io::{AsyncRead, AsyncSeek}; use self::prelude::*; +use super::FuseError; + mod ro; mod rw; mod template; +mod wrapper; pub const MEMBLOCK_BLOCKSIZE: usize = 4096; -mod prelude { +pub mod prelude { pub use super::ro::Entry as ReadEntry; pub use super::rw::Entry as WriteEntry; - pub use super::MEMBLOCK_BLOCKSIZE; + pub use super::template::TarTree; + pub use super::MEMBLOCK_BLOCKSIZE as BLOCKSIZE; pub use super::{Entry, InoEntry}; } @@ -68,4 +72,27 @@ where Entry::Write(write_entry) => write_entry.kind(), } } + pub async fn read(&mut self, offset: u64, size: u32) -> Result { + match &mut self.entry { + Entry::Read(entry) => entry.read(offset, size).await, + Entry::Write(entry) => entry.read(offset, size).await, + } + } + pub async fn write(&mut self, offset: u64, data: &[u8]) -> Result { + match &mut self.entry { + Entry::Read(entry) => entry.write(offset, data).await, + Entry::Write(entry) => entry.write(offset, data).await, + } + } + // pub async fn + pub async fn dir_entry(&self, name: OsString) -> DirectoryEntry { + // But for libfuse, an offset of zero means that offsets are + // not supported, so we shift everything by one. + DirectoryEntry { + inode: self.inode, + kind: self.kind().await, + name, + offset: 0, + } + } } diff --git a/judger/src/filesystem/entry/ro.rs b/judger/src/filesystem/entry/ro.rs index 801657fc..fca87bef 100644 --- a/judger/src/filesystem/entry/ro.rs +++ b/judger/src/filesystem/entry/ro.rs @@ -7,12 +7,15 @@ use std::ffi::OsString; -use crate::filesystem::macro_::{chain_poll, report_poll}; +use crate::filesystem::{ + macro_::{chain_poll, report_poll}, + FuseError, +}; +use bytes::Bytes; use fuse3::FileType; -use std::future::Future; -use std::io; use std::{ - io::SeekFrom, + future::Future, + io::{self, SeekFrom}, ops::DerefMut, pin::{pin, Pin}, sync::Arc, @@ -23,6 +26,8 @@ use tokio::{ sync::{Mutex, OwnedMutexGuard}, }; +use super::wrapper::FuseRead; + /// Entry from tar file, should be readonly #[derive(Debug, Default)] pub enum Entry @@ -61,6 +66,16 @@ where Self::File(_) => FileType::RegularFile, } } + pub async fn read(&mut self, offset: u64, size: u32) -> Result { + // FIXME: follow symlink + if let Self::File(block) = self { + return FuseRead(block).read(offset, size).await; + } + Err(FuseError::IsDir) + } + pub async fn write(&mut self, offset: u64, data: &[u8]) -> Result { + Err(FuseError::Unimplemented) + } } impl Clone for Entry @@ -104,6 +119,12 @@ impl TarStage { } } +/// A block in tar file, should be readonly +/// +/// Note that [`TarBlock`] behavior like [`tokio::fs::File`], +/// except that it dones't shares the same underlying file session +/// by cloning(Reads, writes, and seeks would **not** affect both +/// [`TarBlock`] instances simultaneously.) #[derive(Debug)] pub struct TarBlock where @@ -145,7 +166,7 @@ where impl TarBlock where - F: AsyncRead + AsyncSeek + Unpin, + F: AsyncRead + AsyncSeek + Unpin + 'static, { pub fn new(file: Arc>, start: u64, size: u64) -> Self { Self { @@ -231,7 +252,7 @@ where impl AsyncSeek for TarBlock where - F: AsyncRead + AsyncSeek + Unpin, + F: AsyncRead + AsyncSeek + Unpin + 'static, { fn start_seek(self: Pin<&mut Self>, position: io::SeekFrom) -> io::Result<()> { let self_ = self.get_mut(); diff --git a/judger/src/filesystem/entry/rw.rs b/judger/src/filesystem/entry/rw.rs index 741f0a39..0dfe7cb7 100644 --- a/judger/src/filesystem/entry/rw.rs +++ b/judger/src/filesystem/entry/rw.rs @@ -1,10 +1,13 @@ -use crate::filesystem::macro_::{chain_poll, report_poll}; +use crate::filesystem::{ + macro_::{chain_poll, report_poll}, + FuseError, +}; +use bytes::Bytes; use fuse3::FileType; -use std::ffi::OsString; -use std::future::Future; -use std::io; use std::{ - io::SeekFrom, + ffi::OsString, + future::Future, + io::{self, SeekFrom}, ops::Deref, pin::{pin, Pin}, sync::Arc, @@ -15,6 +18,7 @@ use tokio::{ sync::{Mutex, OwnedMutexGuard}, }; +use super::wrapper::{FuseRead, FuseWrite}; use super::MEMBLOCK_BLOCKSIZE; #[derive(Default, Clone, Debug)] @@ -49,6 +53,19 @@ impl Entry { Self::Removed => FileType::RegularFile, } } + pub async fn read(&mut self, offset: u64, size: u32) -> Result { + // FIXME: follow symlink + if let Self::File(block) = self { + return FuseRead(block).read(offset, size).await; + } + Err(FuseError::IsDir) + } + pub async fn write(&mut self, offset: u64, data: &[u8]) -> Result { + if let Self::File(block) = self { + return FuseWrite(block).write(offset, data).await; + } + Err(FuseError::IsDir) + } } #[derive(Debug, Default)] @@ -68,7 +85,7 @@ impl MemStage { } #[derive(Default, Debug)] -struct MemBlock { +pub struct MemBlock { data: Arc>>, cursor: usize, stage: MemStage, @@ -281,4 +298,30 @@ mod test { }); } } + #[tokio::test] + #[should_panic] + async fn test_take_read() { + let block = MemBlock::new(b"hello world".to_vec()); + let mut buffer = [0; 5]; + + // read at most five bytes + let mut handle = block.take(5); + handle.read_exact(&mut buffer).await.unwrap(); + assert_eq!(buffer, *b"hello"); + + // read the rest + let mut buffer = [0; 6]; + handle.read_exact(&mut buffer).await.unwrap(); + assert_eq!(buffer, *b" world"); + } + #[tokio::test] + async fn test_take_short_read() { + let block = MemBlock::new(b"hello ".to_vec()); + let mut buffer = Vec::new(); + + // read at most five bytes + let mut handle = block.take(100); + handle.read_to_end(&mut buffer).await.unwrap(); + assert_eq!(buffer, b"hello "); + } } diff --git a/judger/src/filesystem/entry/template.rs b/judger/src/filesystem/entry/template.rs index 0c009a4d..7f91564a 100644 --- a/judger/src/filesystem/entry/template.rs +++ b/judger/src/filesystem/entry/template.rs @@ -1,41 +1,25 @@ use super::prelude::ReadEntry; -use std::{ - collections::BTreeMap, - ffi::OsString, - io::Read, - os::unix::ffi::OsStringExt, - path::Path, - sync::{ - atomic::{AtomicU64, Ordering}, - Arc, - }, -}; +use std::{ffi::OsString, io::Read, os::unix::ffi::OsStringExt, path::Path, sync::Arc}; -use spin::rwlock::RwLock; #[cfg(test)] use std::io::Cursor; use tar::{Archive, EntryType}; -// use tar::*; #[cfg(test)] use tokio::io::BufReader; use tokio::{ fs::File, io::{AsyncRead, AsyncSeek}, sync::Mutex, - task::spawn_blocking, }; use crate::{ - filesystem::{ - table::{InodeHandle, InodeTable}, - tree::{arc_lock, ArcNode, InsertResult, Node, Tree}, - }, + filesystem::{table, tree::*}, Error, }; use super::{ro::TarBlock, Entry, InoEntry}; -impl InodeTable>> +impl table::InodeTable>> where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { @@ -55,7 +39,7 @@ where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { pub tree: Tree>, - pub inode: InodeTable>>, + pub inode: table::InodeTable>>, } impl Default for TarTree @@ -63,7 +47,7 @@ where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { fn default() -> Self { - let inode = InodeTable::default(); + let inode = table::InodeTable::default(); let handle = inode.allocate_root(); let root = Node::new(InoEntry { entry: Entry::Read(ReadEntry::new_dir()), diff --git a/judger/src/filesystem/entry/wrapper.rs b/judger/src/filesystem/entry/wrapper.rs new file mode 100644 index 00000000..90aa0871 --- /dev/null +++ b/judger/src/filesystem/entry/wrapper.rs @@ -0,0 +1,57 @@ +use std::io::SeekFrom; + +use bytes::Bytes; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, AsyncWrite, AsyncWriteExt}; + +use crate::filesystem::FuseError; + +pub struct FuseRead<'a, W>(pub &'a mut W) +where + W: AsyncRead + AsyncSeek + Clone + Unpin; + +impl<'a, W> FuseRead<'a, W> +where + W: AsyncRead + AsyncSeek + Clone + Unpin, +{ + pub async fn read(&mut self, offset: u64, size: u32) -> Result { + let mut buf = Vec::with_capacity(size as usize); + self.0 + .seek(SeekFrom::Start(offset)) + .await + .map_err(|_| FuseError::Eof)?; + + self.0 + .clone() + .take(size as u64) + .read_to_end(&mut buf) + .await + .map_err(|_| FuseError::Eof)?; + + self.0 + .seek(SeekFrom::Current(buf.len() as i64)) + .await + .unwrap(); + Ok(buf.try_into().unwrap()) + } +} + +pub struct FuseWrite<'a, W>(pub &'a mut W) +where + W: AsyncWrite + AsyncSeek + Clone + Unpin; + +impl<'a, W> FuseWrite<'a, W> +where + W: AsyncWrite + AsyncSeek + Clone + Unpin, +{ + pub async fn write(&mut self, offset: u64, data: &[u8]) -> Result { + if data.len() >= u32::MAX as usize { + return Err(FuseError::OutOfRange); + } + self.0 + .seek(SeekFrom::Start(offset)) + .await + .map_err(|_| FuseError::Eof)?; + self.0.write_all(data).await.unwrap(); + Ok(data.len() as u32) + } +} diff --git a/judger/src/filesystem/mod.rs b/judger/src/filesystem/mod.rs index afae31de..6fe5af1e 100644 --- a/judger/src/filesystem/mod.rs +++ b/judger/src/filesystem/mod.rs @@ -1,14 +1,44 @@ -// mod adapter; +mod adapter; mod entry; mod macro_; -// mod overlay; +mod reply; mod table; mod tree; +pub use entry::prelude::*; + #[derive(thiserror::Error, Debug)] pub enum FuseError { - #[error("io error")] - IoError(#[from] std::io::Error), #[error("not a readable file")] - NotReadable, + IsDir, + #[error("end of file")] + Eof, + #[error("not a dir")] + NotDir, + #[error("out of resource")] + OutOfPermit, + #[error("number too large")] + OutOfRange, + #[error("unimplemented")] + Unimplemented, + #[error("missed inode")] + InodeNotFound, + #[error("missed handle")] + HandleNotFound, +} + +impl From for fuse3::Errno { + fn from(value: FuseError) -> Self { + match value { + FuseError::IsDir => libc::EISDIR, + FuseError::NotDir => libc::ENOTDIR, + FuseError::Eof => libc::EOF, + FuseError::OutOfPermit => libc::ENOMEM, + _ => { + log::warn!("FUSE driver broken: {}", value); + libc::ENOMEM + } + } + .into() + } } diff --git a/judger/src/filesystem/overlay/mod.rs b/judger/src/filesystem/overlay/mod.rs deleted file mode 100644 index a57022f0..00000000 --- a/judger/src/filesystem/overlay/mod.rs +++ /dev/null @@ -1,41 +0,0 @@ -mod reply; - -pub use block::MEMBLOCK_BLOCKSIZE as BLOCKSIZE; -pub use entry::*; -pub use reply::Parsable; -use tokio::io::{AsyncRead, AsyncSeek}; - -use super::{ - table::{HandleTable, INodeTable}, - tar::TarLayer, -}; - -pub struct Overlay -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - tar: TarLayer, - pub inode: INodeTable>, - pub handle: HandleTable>, -} - -impl Overlay -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - pub async fn new(tar: TarLayer) -> Self { - let inode = INodeTable::new(); - inode.add_entry_ro(tar.get_root().await); - Self { - tar, - inode, - handle: HandleTable::new(), - } - } - pub fn get_root(&self) -> Entry { - self.inode.get(1).unwrap() - } - pub fn lookup(&self, inode: u64) -> Option> { - self.inode.get(inode) - } -} diff --git a/judger/src/filesystem/overlay/reply.rs b/judger/src/filesystem/overlay/reply.rs deleted file mode 100644 index 39efc272..00000000 --- a/judger/src/filesystem/overlay/reply.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::time::Duration; - -use fuse3::{ - raw::{reply::*, Request}, - Timestamp, -}; -use tokio::io::{AsyncRead, AsyncSeek}; - -use super::Entry; - -pub trait Parsable -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - fn parse(request: Request, entry: Entry) -> Self; -} - -impl Parsable for ReplyEntry -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - fn parse(request: Request, entry: Entry) -> Self { - Self { - ttl: Duration::from_secs(30), - attr: FileAttr { - ino: entry.get_inode(), - size: 0, - blocks: 0, - atime: Timestamp::new(0, 0), - mtime: Timestamp::new(0, 0), - ctime: Timestamp::new(0, 0), - kind: todo!(), - perm: todo!(), - nlink: todo!(), - uid: todo!(), - gid: todo!(), - rdev: todo!(), - blksize: todo!(), - }, - generation: 1, - } - } -} diff --git a/judger/src/filesystem/reply.rs b/judger/src/filesystem/reply.rs new file mode 100644 index 00000000..7ca2c32e --- /dev/null +++ b/judger/src/filesystem/reply.rs @@ -0,0 +1,54 @@ +use std::{sync::Arc, time::Duration}; + +use fuse3::{ + raw::{reply::*, Request}, + Timestamp, +}; +use tokio::io::{AsyncRead, AsyncSeek}; + +use super::{ + entry::{prelude::BLOCKSIZE, InoEntry}, + tree::ArcNode, +}; + +pub trait ImmutParsable +where + F: AsyncRead + AsyncSeek + Send + Unpin + 'static, +{ + async fn parse(request: Request, entry: ArcNode>) -> Self; +} + +impl ImmutParsable for ReplyEntry +where + F: AsyncRead + AsyncSeek + Send + Unpin + 'static, +{ + #[inline] + async fn parse(request: Request, entry: ArcNode>) -> Self { + let nlink = Arc::strong_count(&entry) - 1; + let entry = entry.read().await; + Self { + ttl: Duration::from_secs(30), + attr: FileAttr { + ino: entry.inode, + size: 0, + blocks: 0, + atime: Timestamp::new(0, 0), + mtime: Timestamp::new(0, 0), + ctime: Timestamp::new(0, 0), + kind: entry.kind().await, + perm: (libc::S_IREAD + | libc::S_IWRITE + | libc::S_IEXEC + | libc::S_IRWXU + | libc::S_IRWXO + | libc::S_ISVTX) as u16, + nlink: nlink as u32, + uid: request.gid, + gid: request.uid, + rdev: 179 << 16 + 02, + blksize: BLOCKSIZE as u32, + }, + generation: 1, + } + } +} diff --git a/judger/src/filesystem/tree.rs b/judger/src/filesystem/tree.rs index 482275f8..496d782c 100644 --- a/judger/src/filesystem/tree.rs +++ b/judger/src/filesystem/tree.rs @@ -90,6 +90,11 @@ impl Node { *x = arc_lock(node); }) } + pub fn list_child(&self) -> impl Iterator)> { + self.children + .iter() + .map(|(a, b)| (a.as_os_str(), b.clone())) + } /// get child node by component #[inline] pub fn get_by_component(&self, component: &OsStr) -> Option> { diff --git a/judger/src/sandbox/mod.rs b/judger/src/sandbox/mod.rs index 1f3a463a..1a4e3ac2 100644 --- a/judger/src/sandbox/mod.rs +++ b/judger/src/sandbox/mod.rs @@ -16,9 +16,6 @@ pub trait Limit { fn get_memory(&mut self) -> Memory; fn get_args(&mut self) -> impl Iterator; fn get_output_limit(&mut self) -> u64; - // fn get_reserved_size(&mut self) -> u64 { - // self.get_memory().get_reserved_size() + self.get_fs_size() + self.get_output_limit() - // } fn get_walltime(&mut self) -> Duration { Duration::from_secs(60 * 30) } From 11827b75d4e75bd12b4f4d0c0d1daf39a51b5906 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Tue, 7 May 2024 10:28:49 +0800 Subject: [PATCH 17/38] feat(Judger): :sparkles: add template --- judger/src/filesystem/adapter.rs | 59 ++++++++++++++----- judger/src/filesystem/entry/mod.rs | 4 +- .../filesystem/entry/{template.rs => tar.rs} | 14 ++++- judger/src/filesystem/mod.rs | 5 ++ judger/src/filesystem/table/inode.rs | 9 +++ judger/src/filesystem/tree.rs | 43 ++++++++++++++ judger/src/main.rs | 1 + 7 files changed, 118 insertions(+), 17 deletions(-) rename judger/src/filesystem/entry/{template.rs => tar.rs} (92%) diff --git a/judger/src/filesystem/adapter.rs b/judger/src/filesystem/adapter.rs index 6d6f8060..fae9c584 100644 --- a/judger/src/filesystem/adapter.rs +++ b/judger/src/filesystem/adapter.rs @@ -1,29 +1,23 @@ -use std::{ffi::OsStr, num::NonZeroU32}; +use std::{ffi::OsStr, num::NonZeroU32, path::Path}; use crate::{ filesystem::{reply::ImmutParsable, FuseError}, - semaphore::Semaphore, + semaphore::{Permit, Semaphore}, + Error, }; use fuse3::{ raw::{reply::*, Request}, FileType, Result as FuseResult, }; use std::future::{ready as future_ready, Future}; -use tokio::io::{AsyncRead, AsyncSeek}; - -use super::{ - entry::prelude::*, - table::{self, HandleTable}, - tree::ArcNode, +use tokio::{ + fs::File, + io::{AsyncRead, AsyncSeek}, }; -type VecStream = tokio_stream::Iter>; +use super::{entry::prelude::*, table::HandleTable, tree::ArcNode}; -// macro_rules! cerr { -// ($e:ident) => { -// Errno::from(libc::$e) -// }; -// } +type VecStream = tokio_stream::Iter>; pub struct Filesystem where @@ -32,6 +26,7 @@ where handle_table: HandleTable>>, tree: TarTree, semaphore: Semaphore, + _permit: Permit, } impl fuse3::raw::Filesystem for Filesystem @@ -187,6 +182,7 @@ where write_flags: u32, flags: u32, ) -> impl Future> + Send { + /// FIXME: use semaphore to limit the write async move { let entry = self.handle_table.get(fh).ok_or(FuseError::HandleNotFound)?; let mut entry = entry.write().await; @@ -198,3 +194,38 @@ where } } } + +pub struct Template +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ + tree: TarTree, + semaphore: Semaphore, +} + +impl Template +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ + fn new_inner(tree: TarTree, memory: u64) -> Self { + Self { + tree, + semaphore: Semaphore::new(memory, 10), + } + } + pub async fn as_template(&self, size: u64) -> Filesystem { + Filesystem { + handle_table: HandleTable::new(), + tree: self.tree.cloned().await, + semaphore: self.semaphore.clone(), + _permit: self.semaphore.get_permit(size).await.unwrap(), + } + } +} + +impl Template { + pub async fn new(path: impl AsRef + Clone, size: u64) -> Result { + let tree = TarTree::new(path).await?; + Ok(Self::new_inner(tree, size)) + } +} diff --git a/judger/src/filesystem/entry/mod.rs b/judger/src/filesystem/entry/mod.rs index 6ed04616..41749ee0 100644 --- a/judger/src/filesystem/entry/mod.rs +++ b/judger/src/filesystem/entry/mod.rs @@ -10,7 +10,7 @@ use super::FuseError; mod ro; mod rw; -mod template; +mod tar; mod wrapper; pub const MEMBLOCK_BLOCKSIZE: usize = 4096; @@ -18,7 +18,7 @@ pub const MEMBLOCK_BLOCKSIZE: usize = 4096; pub mod prelude { pub use super::ro::Entry as ReadEntry; pub use super::rw::Entry as WriteEntry; - pub use super::template::TarTree; + pub use super::tar::TarTree; pub use super::MEMBLOCK_BLOCKSIZE as BLOCKSIZE; pub use super::{Entry, InoEntry}; } diff --git a/judger/src/filesystem/entry/template.rs b/judger/src/filesystem/entry/tar.rs similarity index 92% rename from judger/src/filesystem/entry/template.rs rename to judger/src/filesystem/entry/tar.rs index 7f91564a..0e8e6572 100644 --- a/judger/src/filesystem/entry/template.rs +++ b/judger/src/filesystem/entry/tar.rs @@ -1,5 +1,5 @@ use super::prelude::ReadEntry; -use std::{ffi::OsString, io::Read, os::unix::ffi::OsStringExt, path::Path, sync::Arc}; +use std::{ffi::OsString, io::Read, ops::Deref, os::unix::ffi::OsStringExt, path::Path, sync::Arc}; #[cfg(test)] use std::io::Cursor; @@ -63,6 +63,18 @@ impl TarTree where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { + pub async fn cloned(&self) -> Self { + let tree = self.tree.cloned(); + let inode = table::InodeTable::default(); + let mut iter = tree.iter(); + + while let Some(entry) = iter.next().await { + let entry_inode = entry.read().await.inode; + inode.clone_update_entry(entry_inode, entry).await; + } + + Self { tree, inode } + } async fn parse_entry( &mut self, entry: tar::Entry<'_, R>, diff --git a/judger/src/filesystem/mod.rs b/judger/src/filesystem/mod.rs index 6fe5af1e..af448160 100644 --- a/judger/src/filesystem/mod.rs +++ b/judger/src/filesystem/mod.rs @@ -1,3 +1,8 @@ +//! Filesystem module that is mountable(actuall mount and +//! is accessible for user in this operation system) +//! +//! + mod adapter; mod entry; mod macro_; diff --git a/judger/src/filesystem/table/inode.rs b/judger/src/filesystem/table/inode.rs index 250131b1..016548e2 100644 --- a/judger/src/filesystem/table/inode.rs +++ b/judger/src/filesystem/table/inode.rs @@ -45,24 +45,33 @@ impl Default for InodeTable { } impl InodeTable { + /// clone the inode table + /// + /// Note: it only deep clone geberator and inode, not the entry pub fn cloned(&self) -> Self { Self { inode: RwLock::new(self.inode.read().clone()), inode_generator: AtomicU64::new(self.inode_generator.load(Ordering::SeqCst)), } } + /// allocate root inode with handle pub fn allocate_root(&self) -> InodeHandle { InodeHandle { inode: 1, table: self, } } + /// allocate inode with handle pub fn allocate(&self) -> InodeHandle { let inode = self .inode_generator .fetch_add(1, std::sync::atomic::Ordering::AcqRel); InodeHandle { inode, table: self } } + /// update(clone) entry by providing new entry with inode + pub async fn clone_update_entry(&self, inode: u64, entry: E) { + self.inode.write().insert(inode, entry); + } /// get entry by inode pub fn get(&self, inode: u64) -> Option { self.inode.read().get(&inode).cloned() diff --git a/judger/src/filesystem/tree.rs b/judger/src/filesystem/tree.rs index 496d782c..4732a21e 100644 --- a/judger/src/filesystem/tree.rs +++ b/judger/src/filesystem/tree.rs @@ -221,6 +221,29 @@ impl Tree { }) .await } + pub fn iter(&self) -> TreeIter { + TreeIter::new(self.0.clone()) + } +} + +pub struct TreeIter { + stack: Vec>, +} + +impl TreeIter { + fn new(root: ArcNode) -> Self { + Self { stack: vec![root] } + } + pub async fn next(&mut self) -> Option> { + let node = self.stack.pop()?; + { + let lock = node.read().await; + for (_, child) in lock.children.iter() { + self.stack.push(child.clone()); + } + } + Some(node) + } } #[cfg(test)] @@ -322,4 +345,24 @@ mod test { let tree = Tree::new(Node::new(0)); tree.insert_path("C:\\a", 1).await; } + #[tokio::test] + async fn iter() { + let tree = Tree::new(Node::new(0)); + tree.insert_path("a", Node::new(1)).await; + tree.insert_path("a/u", Node::new(2)).await; + tree.insert_path("a/u/f", Node::new(3)).await; + tree.insert_path("a/u/f/h", Node::new(4)).await; + let mut iter = tree.iter(); + macro_rules! check_next { + ($e:expr) => { + assert_eq!(iter.next().await.unwrap().read().await.value, $e); + }; + } + check_next!(0); + check_next!(1); + check_next!(2); + check_next!(3); + check_next!(4); + assert!(iter.next().await.is_none()); + } } diff --git a/judger/src/main.rs b/judger/src/main.rs index 76b2efe7..e07477d9 100644 --- a/judger/src/main.rs +++ b/judger/src/main.rs @@ -1,6 +1,7 @@ mod config; mod error; mod filesystem; +mod language; mod sandbox; mod semaphore; From 0a2c15bd114940ac9baf2a62a102a2f815174607 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Tue, 7 May 2024 10:29:31 +0800 Subject: [PATCH 18/38] docs(Judger): :memo: update document for `crate::sandbox` --- .github/workflows/master.yml | 2 + judger/plugins/rlua-54/spec.toml | 5 +- judger/src/language/config.rs | 154 +++++++++++++++++++++++++ judger/src/language/mod.rs | 1 + judger/src/sandbox/mod.rs | 23 +++- judger/src/sandbox/monitor/hier.rs | 4 + judger/src/sandbox/monitor/mem_cpu.rs | 1 + judger/src/sandbox/monitor/mod.rs | 20 +++- judger/src/sandbox/monitor/stat.rs | 4 + judger/src/sandbox/monitor/walltime.rs | 1 - judger/src/sandbox/process/corpse.rs | 1 + judger/src/sandbox/process/mod.rs | 15 +++ judger/src/sandbox/process/nsjail.rs | 7 ++ judger/src/sandbox/process/process.rs | 6 +- 14 files changed, 235 insertions(+), 9 deletions(-) create mode 100644 judger/src/language/config.rs create mode 100644 judger/src/language/mod.rs diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index c25aa6dc..1dd6452b 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -45,6 +45,8 @@ jobs: run: mkdir -p backend/config && cargo test -p backend - name: Run Frontend Unit Test run: cargo test -p frontend + - name: Run Judger Unit Test + run: cargo test -p judger - name: Lint run: | cargo fmt --all -- --check diff --git a/judger/plugins/rlua-54/spec.toml b/judger/plugins/rlua-54/spec.toml index 72e99341..2d8bd3db 100644 --- a/judger/plugins/rlua-54/spec.toml +++ b/judger/plugins/rlua-54/spec.toml @@ -5,7 +5,6 @@ name = "rlua-54" # must be same as dictionary name uid = "1c41598f-e253-4f81-9ef5-d50bf1e4e74f" # be sure it's unique [compile] -lockdown = true command = ["/rlua-54","compile"] kernel_mem = 67108864 user_mem = 268435456 @@ -16,6 +15,6 @@ total_time = 10000000 [judge] command = ["/rlua-54","execute"] kernel_mem = 67108864 -multiplier_memory = 6 # user_mem +memory_multiplier = 6 # user_mem rt_time = 1000000 -multiplier_cpu = 3 # cpu_time +cpu_multiplier = 3 # cpu_time diff --git a/judger/src/language/config.rs b/judger/src/language/config.rs new file mode 100644 index 00000000..f2352c6c --- /dev/null +++ b/judger/src/language/config.rs @@ -0,0 +1,154 @@ +use std::{ffi::OsString, time::Duration}; + +use serde::Deserialize; +use tokio::io::{AsyncRead, AsyncReadExt}; +use uuid::Uuid; + +use crate::sandbox::{Cpu, Memory}; + +pub struct Config { + pub compile_limit: (Cpu, Memory, u64, Duration), + pub judge_limit: (Cpu, Memory, u64, Duration), + pub compile_command: Vec, + pub judge_command: Vec, +} + +impl Config { + async fn from_reader(mut reader: impl AsyncRead + Unpin) -> Self { + let mut buf = String::new(); + reader.read_to_string(&mut buf).await.unwrap(); + let mut raw: Raw = toml::from_str(&buf).unwrap(); + raw.compile.fill(); + raw.judge.fill(); + + Self { + compile_limit: ( + Cpu { + kernel: todo!(), + user: todo!(), + total: todo!(), + }, + Memory { + kernel: todo!(), + user: todo!(), + total: todo!(), + }, + raw.compile.output_limit.unwrap(), + Duration::from_millis(raw.compile.walltime.unwrap()), + ), + judge_limit: todo!(), + compile_command: todo!(), + judge_command: todo!(), + } + } +} + +#[derive(Deserialize)] +struct Raw { + info: String, + extension: String, + name: String, + id: Uuid, + compile: RawCompile, + judge: RawJudge, +} + +#[derive(Deserialize)] +struct RawCompile { + command: Vec, + kernel_mem: Option, + user_mem: Option, + rt_time: Option, + cpu_time: Option, + total_time: Option, + output_limit: Option, + walltime: Option, +} + +impl RawCompile { + fn fill(&mut self) { + let template = Self::default(); + macro_rules! try_fill { + ($f:ident) => { + if self.$f.is_none(){ + self.$f=template.$f; + } + }; + ($f:ident,$($e:ident),+) => { + try_fill!($f); + try_fill!($($e),+); + } + } + try_fill!( + kernel_mem, + user_mem, + rt_time, + cpu_time, + total_time, + output_limit, + walltime + ); + } +} + +impl Default for RawCompile { + fn default() -> Self { + Self { + command: Vec::new(), + kernel_mem: Some(67108864), + user_mem: Some(268435456), + rt_time: Some(1000000), + cpu_time: Some(1000000), + total_time: Some(10000000), + output_limit: Some(4096), + walltime: Some(360000000), + } + } +} + +#[derive(Deserialize)] +struct RawJudge { + command: Vec, + kernel_mem: Option, + rt_time: Option, + memory_multiplier: Option, + cpu_multiplier: Option, + walltime: Option, +} + +impl RawJudge { + fn fill(&mut self) { + let template = Self::default(); + macro_rules! try_fill { + ($f:ident) => { + if self.$f.is_none(){ + self.$f=template.$f; + } + }; + ($f:ident,$($e:ident),+) => { + try_fill!($f); + try_fill!($($e),+); + } + } + try_fill!( + kernel_mem, + rt_time, + memory_multiplier, + cpu_multiplier, + walltime + ); + } +} + +impl Default for RawJudge { + fn default() -> Self { + Self { + command: Vec::new(), + kernel_mem: Some(67108864), + rt_time: Some(1000000), + memory_multiplier: Some(1.0), + cpu_multiplier: Some(1.0), + walltime: Some(360000000), + } + } +} diff --git a/judger/src/language/mod.rs b/judger/src/language/mod.rs new file mode 100644 index 00000000..1bf79dfa --- /dev/null +++ b/judger/src/language/mod.rs @@ -0,0 +1 @@ +mod config; diff --git a/judger/src/sandbox/mod.rs b/judger/src/sandbox/mod.rs index 1a4e3ac2..20d09953 100644 --- a/judger/src/sandbox/mod.rs +++ b/judger/src/sandbox/mod.rs @@ -1,4 +1,3 @@ -// mod limiter; mod monitor; mod process; @@ -6,16 +5,19 @@ use std::{ffi::OsStr, path::Path, time::Duration}; pub use self::monitor::{Cpu, Memory}; +/// Context of the sandbox +/// +/// define resource limit and filesystem is out of the scope of `filesystem` pub trait Context: Limit { type FS: Filesystem; fn create_fs(&mut self) -> Self::FS; + fn get_args(&mut self) -> impl Iterator; } pub trait Limit { fn get_cpu(&mut self) -> Cpu; fn get_memory(&mut self) -> Memory; - fn get_args(&mut self) -> impl Iterator; - fn get_output_limit(&mut self) -> u64; + fn get_output(&mut self) -> u64; fn get_walltime(&mut self) -> Duration { Duration::from_secs(60 * 30) } @@ -25,3 +27,18 @@ pub trait Filesystem { fn mount(&mut self) -> impl AsRef + Send; fn get_size(&mut self) -> u64; } + +impl Limit for (Cpu, Memory, u64, Duration) { + fn get_cpu(&mut self) -> Cpu { + self.0.clone() + } + fn get_memory(&mut self) -> Memory { + self.1.clone() + } + fn get_output(&mut self) -> u64 { + self.2 + } + fn get_walltime(&mut self) -> Duration { + self.3 + } +} diff --git a/judger/src/sandbox/monitor/hier.rs b/judger/src/sandbox/monitor/hier.rs index e0d95117..73d75be7 100644 --- a/judger/src/sandbox/monitor/hier.rs +++ b/judger/src/sandbox/monitor/hier.rs @@ -1,8 +1,11 @@ use crate::config::Accounting; use cgroups_rs::*; +/// type of monitor for cpu pub enum MonitorKind { + /// use `cpu.stat` from cpu subsystem Cpu, + /// use cpu accounting subsystem CpuAcct, } @@ -19,6 +22,7 @@ lazy_static::lazy_static! { } impl MonitorKind { + /// get the hierarchy(cgroup v1/v2) of monitor pub fn heir(&self) -> Box { match self { MonitorKind::Cpu => hierarchies::auto(), diff --git a/judger/src/sandbox/monitor/mem_cpu.rs b/judger/src/sandbox/monitor/mem_cpu.rs index fa0afc20..61bbd399 100644 --- a/judger/src/sandbox/monitor/mem_cpu.rs +++ b/judger/src/sandbox/monitor/mem_cpu.rs @@ -3,6 +3,7 @@ use cgroups_rs::{cgroup_builder::CgroupBuilder, Cgroup}; use std::sync::{atomic::Ordering, Arc}; use tokio::time::*; +/// maximum allow time deviation for cpu monitor const MONITOR_ACCURACY: Duration = Duration::from_millis(80); const CG_PATH_COUNTER: AtomicUsize = AtomicUsize::new(0); diff --git a/judger/src/sandbox/monitor/mod.rs b/judger/src/sandbox/monitor/mod.rs index b33a9d2a..ac60fd53 100644 --- a/judger/src/sandbox/monitor/mod.rs +++ b/judger/src/sandbox/monitor/mod.rs @@ -23,8 +23,26 @@ lazy_static::lazy_static! { pub trait Monitor { type Resource; - async fn wait_exhaust(&mut self) -> MonitorKind; + /// wait for exhuast of resource + /// + /// This function is cancel safe. + async fn wait_exhaust(&mut self) -> MonitorKind { + log::warn!("unimplemented wait_exhaust!"); + loop { + if let Some(reason) = self.poll_exhaust() { + return reason; + } + tokio::time::sleep(Duration::from_millis(12)).await; + } + } + /// poll for exhuast of resource + /// + /// Implementor should do bith [`wait_exhaust`] and [`poll_exhaust`] + /// for better performance. fn poll_exhaust(&mut self) -> Option; + /// get the resource usage + /// + /// Please note that [`poll_exhaust`] might not be called before this function async fn stat(self) -> Self::Resource; } diff --git a/judger/src/sandbox/monitor/stat.rs b/judger/src/sandbox/monitor/stat.rs index 1e5647ca..76aed626 100644 --- a/judger/src/sandbox/monitor/stat.rs +++ b/judger/src/sandbox/monitor/stat.rs @@ -6,6 +6,7 @@ use super::output::Output; pub type MemAndCpu = (Memory, Cpu); +/// statistics of resource usage pub struct Stat { pub memory: Memory, pub cpu: Cpu, @@ -13,6 +14,8 @@ pub struct Stat { pub walltime: Duration, } +/// memory usage(in bytes) +#[derive(Clone)] pub struct Memory { pub kernel: u64, pub user: u64, @@ -25,6 +28,7 @@ impl Memory { } } +/// cpu usage(in nanoseconds) #[derive(Clone)] pub struct Cpu { pub kernel: u64, diff --git a/judger/src/sandbox/monitor/walltime.rs b/judger/src/sandbox/monitor/walltime.rs index 498739f5..cad038d2 100644 --- a/judger/src/sandbox/monitor/walltime.rs +++ b/judger/src/sandbox/monitor/walltime.rs @@ -31,7 +31,6 @@ impl super::Monitor for Monitor { } Some(MonitorKind::Walltime) } - async fn stat(self) -> Self::Resource { match self.start { Some(start) => Instant::now().duration_since(start), diff --git a/judger/src/sandbox/process/corpse.rs b/judger/src/sandbox/process/corpse.rs index 87939f8f..f96d36d2 100644 --- a/judger/src/sandbox/process/corpse.rs +++ b/judger/src/sandbox/process/corpse.rs @@ -2,6 +2,7 @@ use std::process::ExitStatus; use super::monitor::{MonitorKind, Stat}; +/// A corpse of a process pub struct Corpse { /// exit code of signal pub(super) code: Option, diff --git a/judger/src/sandbox/process/mod.rs b/judger/src/sandbox/process/mod.rs index 0d3c9d2f..b67be480 100644 --- a/judger/src/sandbox/process/mod.rs +++ b/judger/src/sandbox/process/mod.rs @@ -1,3 +1,18 @@ +//! A module that provides a way to setup environment for a process and run. +//! +//! Using this module should be SAFE(can't launching a process without +//! explicit resource limitation) +//! +//! ```norun +//! use process::*; +//! +//! // implement process context yourself +//! let ctx=Context::new(); +//! +//! let process=Process::new(ctx).unwrap(); +//! let corpse=process.wait(b"data for stdin").await.unwrap(); +//! ``` + mod corpse; mod nsjail; mod process; diff --git a/judger/src/sandbox/process/nsjail.rs b/judger/src/sandbox/process/nsjail.rs index 8aeb85c1..e7207777 100644 --- a/judger/src/sandbox/process/nsjail.rs +++ b/judger/src/sandbox/process/nsjail.rs @@ -11,6 +11,7 @@ pub trait Argument { fn get_args(self) -> impl Iterator>; } +/// factory pattern for conbinating arguments #[derive(Default)] pub struct ArgFactory { args: Vec>, @@ -27,6 +28,7 @@ impl ArgFactory { } } +/// base auguments for nsjail pub struct BaseArg; impl Argument for BaseArg { @@ -43,6 +45,7 @@ impl Argument for BaseArg { } } +/// arguments for setting cgroup pub struct CGroupMountArg<'a> { pub cg_name: &'a str, } @@ -51,6 +54,7 @@ impl<'a> Argument for CGroupMountArg<'a> { fn get_args(self) -> impl Iterator> { // note that there is cg_name, cg_path and cg_mount, they are different! match super::monitor::CGROUP_V2.deref() { + // this is a patch(not default behavior of nsjail) true => vec![ Cow::Borrowed(OsStr::from_bytes(b"--cgroup_cpu_parent")), Cow::Owned(OsString::from(self.cg_name)), @@ -68,6 +72,7 @@ impl<'a> Argument for CGroupMountArg<'a> { } } +/// arguments for setting cgroup version pub struct CGroupVersionArg; impl Argument for CGroupVersionArg { @@ -80,6 +85,7 @@ impl Argument for CGroupVersionArg { } } +/// arguments for rootfs mount pub struct MountArg<'a> { pub rootfs: &'a OsStr, } @@ -94,6 +100,7 @@ impl<'a> Argument for MountArg<'a> { } } +/// arguments for launching inner process pub struct InnerProcessArg<'a, I> where I: Iterator, diff --git a/judger/src/sandbox/process/process.rs b/judger/src/sandbox/process/process.rs index 2459b0ed..f3bf63d8 100644 --- a/judger/src/sandbox/process/process.rs +++ b/judger/src/sandbox/process/process.rs @@ -10,6 +10,7 @@ use tokio::{ use super::{corpse::Corpse, nsjail::*}; +/// A unlaunched process that is mounted with a filesystem struct MountedProcess { context: C, fs: C::FS, @@ -24,6 +25,7 @@ impl MountedProcess { } } +/// a monitored process struct MonitoredProcess { fs: C::FS, context: C, @@ -39,7 +41,7 @@ impl MonitoredProcess { let mem = context.get_memory(); let cpu = context.get_cpu(); let walltime = context.get_walltime(); - let output_limit = context.get_output_limit(); + let output_limit = context.get_output(); let (fake_stdout, stdout) = io::duplex(1024); Ok(Self { @@ -67,6 +69,7 @@ impl From> for Process { } } +/// A running process pub struct Process { fs: C::FS, context: C, @@ -102,6 +105,7 @@ impl Process { Ok(cmd.spawn()?) } + /// spawn a process and wait for it to finish pub async fn wait(mut self, input: Vec) -> Result { let mut process = self.spawn_raw_process()?; From eb959c6192b74f67caa71ad8e9d157cb2f444841 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Wed, 8 May 2024 23:03:10 +0800 Subject: [PATCH 19/38] fix(Judger): :sparkles: add trigger for filesystem mount --- Cargo.lock | 48 ++++- judger/Cargo.toml | 1 + judger/src/config.rs | 32 ++- judger/src/filesystem/adapter.rs | 273 ++++++++++++++++++++------ judger/src/filesystem/entry/mod.rs | 84 ++++++-- judger/src/filesystem/entry/ro.rs | 4 + judger/src/filesystem/entry/rw.rs | 3 + judger/src/filesystem/mod.rs | 4 + judger/src/filesystem/reply.rs | 96 ++++++--- judger/src/filesystem/table/handle.rs | 5 +- judger/src/filesystem/table/inode.rs | 1 + judger/src/language/daemon.rs | 29 +++ judger/src/language/mod.rs | 1 + 13 files changed, 466 insertions(+), 115 deletions(-) create mode 100644 judger/src/language/daemon.rs diff --git a/Cargo.lock b/Cargo.lock index 958784dd..f3a906f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1659,6 +1659,15 @@ dependencies = [ "syn 2.0.52", ] +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", +] + [[package]] name = "env_logger" version = "0.10.2" @@ -1672,6 +1681,18 @@ dependencies = [ "termcolor", ] +[[package]] +name = "env_logger" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -2903,7 +2924,7 @@ dependencies = [ "cgroups-rs", "crossbeam", "derive_builder", - "env_logger", + "env_logger 0.10.2", "fuse3", "futures-core", "grpc", @@ -2917,6 +2938,7 @@ dependencies = [ "spin 0.9.8", "tar", "tempfile", + "test-log", "thiserror", "tokio", "tokio-stream", @@ -4056,7 +4078,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" dependencies = [ - "env_logger", + "env_logger 0.10.2", "log", ] @@ -5748,6 +5770,28 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test-log" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dffced63c2b5c7be278154d76b479f9f9920ed34e7574201407f0b14e2bbb93" +dependencies = [ + "env_logger 0.11.3", + "test-log-macros", + "tracing-subscriber", +] + +[[package]] +name = "test-log-macros" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + [[package]] name = "testsuit" version = "0.1.0" diff --git a/judger/Cargo.toml b/judger/Cargo.toml index 9b186850..860208e5 100644 --- a/judger/Cargo.toml +++ b/judger/Cargo.toml @@ -21,6 +21,7 @@ crossbeam = "0.8.4" lazy_static = "1.4.0" libc = "0.2.154" bytes = "1.6.0" +test-log = "0.2.16" [dependencies.fuse3] version = "0.7.1" diff --git a/judger/src/config.rs b/judger/src/config.rs index ce1acaf9..ca411e07 100644 --- a/judger/src/config.rs +++ b/judger/src/config.rs @@ -36,27 +36,49 @@ pub enum Accounting { Cpu, } +fn default_ratio_cpu() -> f32 { + 1.0 +} +fn default_ratio_memory() -> f32 { + 1.0 +} + #[derive(Serialize, Deserialize, Default)] -pub struct Machine { - cpu: Option, - memory: Option, +pub struct Ratio { + #[serde(default = "default_ratio_cpu")] + pub cpu: f32, + #[serde(default = "default_ratio_memory")] + pub memory: f32, } fn default_log() -> u8 { 1 } +fn default_ratio() -> Ratio { + Ratio { + cpu: 1.0, + memory: 1024.0, + } +} + +fn default_memory() -> u64 { + 1024 * 1024 * 1024 +} + #[derive(Serialize, Deserialize, Default)] #[serde(deny_unknown_fields)] pub struct Config { #[serde(default)] pub accounting: Accounting, - #[serde(default)] - pub machine: Machine, + #[serde(default = "default_ratio")] + pub ratio: Ratio, #[serde(default)] pub rootless: bool, #[serde(default = "default_log")] pub log: u8, #[serde(default)] pub secret: Option, + #[serde(default = "default_memory")] + pub memory: u64, } diff --git a/judger/src/filesystem/adapter.rs b/judger/src/filesystem/adapter.rs index fae9c584..c76fadda 100644 --- a/judger/src/filesystem/adapter.rs +++ b/judger/src/filesystem/adapter.rs @@ -1,13 +1,10 @@ -use std::{ffi::OsStr, num::NonZeroU32, path::Path}; +use std::{ffi::OsStr, num::NonZeroU32, path::Path, sync::atomic::AtomicI64, time::Duration}; -use crate::{ - filesystem::{reply::ImmutParsable, FuseError}, - semaphore::{Permit, Semaphore}, - Error, -}; +use crate::{filesystem::FuseError, semaphore::Permit, Error}; +use bytes::Bytes; use fuse3::{ - raw::{reply::*, Request}, - FileType, Result as FuseResult, + raw::{reply::*, MountHandle, Request, Session}, + FileType, MountOptions, Result as FuseResult, }; use std::future::{ready as future_ready, Future}; use tokio::{ @@ -15,20 +12,49 @@ use tokio::{ io::{AsyncRead, AsyncSeek}, }; -use super::{entry::prelude::*, table::HandleTable, tree::ArcNode}; +use super::{entry::prelude::*, reply::*, table::HandleTable, tree::ArcNode}; type VecStream = tokio_stream::Iter>; +pub struct FilesystemHandle(Option); + +impl Drop for FilesystemHandle { + fn drop(&mut self) { + tokio::spawn(self.0.take().unwrap().unmount()); + } +} + pub struct Filesystem where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { handle_table: HandleTable>>, tree: TarTree, - semaphore: Semaphore, + resource: AtomicI64, _permit: Permit, } +impl Filesystem +where + F: AsyncRead + AsyncSeek + Unpin + Send + Sync + 'static, +{ + pub async fn mount(self, path: impl AsRef + Clone) -> FilesystemHandle { + let uid = unsafe { libc::getuid() }; + let gid = unsafe { libc::getgid() }; + + let mut mount_options = MountOptions::default(); + + mount_options.uid(uid).gid(gid).force_readdir_plus(true); + + let handle = Session::new(mount_options) + .mount_with_unprivileged(self, path.as_ref()) + .await + .unwrap(); + + FilesystemHandle(Some(handle)) + } +} + impl fuse3::raw::Filesystem for Filesystem where F: AsyncRead + AsyncSeek + Unpin + Send + Sync + 'static, @@ -56,7 +82,7 @@ where if let Some(entry) = self.tree.inode.get(parent) { let entry = entry.read().await; if let Some(entry) = entry.get_by_component(name) { - return Ok(ReplyEntry::parse(req, entry).await); + return Ok(reply_entry(req, entry).await); } } Err(FuseError::InodeNotFound.into()) @@ -100,11 +126,8 @@ where if entry.read().await.kind().await != FileType::Directory { return Err(FuseError::NotDir.into()); } - let handle = self.handle_table.add(entry); - Ok(ReplyOpen { - fh: handle, - flags: 0, - }) + let fh = self.handle_table.add(entry); + Ok(ReplyOpen { fh, flags: 0 }) } } fn read( @@ -119,33 +142,32 @@ where let entry = self.handle_table.get(fh).ok_or(FuseError::HandleNotFound)?; let mut entry = entry.write().await; entry - .read(offset, size) + .read(offset, size, &self.resource) .await .map(|data| ReplyData { data }) .map_err(Into::into) } } - fn readdir<'a>( - &'a self, - req: Request, - parent: u64, - fh: u64, - offset: i64, - ) -> impl Future>>> + Send { - async move { - let entry = self.handle_table.get(fh).ok_or(FuseError::NotDir)?; - let entry = entry.read().await; - // FIXME: use stream rather than vec iterator - let mut result: Vec> = Vec::new(); - for (name, child) in entry.list_child() { - let child = child.read().await; - result.push(Ok(child.dir_entry(name.to_os_string()).await)); - } - Ok(ReplyDirectory { - entries: tokio_stream::iter(result.into_iter()), - }) - } - } + // fn readdir<'a>( + // &'a self, + // req: Request, + // parent: u64, + // fh: u64, + // offset: i64, + // ) -> impl Future>>> + Send { + // async move { + // let entry = self.tree.inode.get(parent).ok_or(FuseError::NotDir)?; + // let entry = entry.read().await; + // // FIXME: use stream rather than vec iterator + // let mut result: Vec> = Vec::new(); + // for (name, child) in entry.list_child() { + // result.push(Ok(dir_entry(name.to_os_string(), child).await)); + // } + // Ok(ReplyDirectory { + // entries: tokio_stream::iter(result.into_iter()), + // }) + // } + // } fn access( &self, req: Request, @@ -182,50 +204,187 @@ where write_flags: u32, flags: u32, ) -> impl Future> + Send { - /// FIXME: use semaphore to limit the write + // FIXME: use semaphore to limit the write async move { let entry = self.handle_table.get(fh).ok_or(FuseError::HandleNotFound)?; let mut entry = entry.write().await; entry - .write(offset, data) + .write(offset, data, &self.resource) .await .map(|written| ReplyWrite { written }) .map_err(Into::into) } } + fn open( + &self, + req: Request, + inode: u64, + flags: u32, + ) -> impl Future> + Send { + async move { + let entry = self.tree.inode.get(inode).ok_or(FuseError::InodeNotFound)?; + let fh = self.handle_table.add(entry); + Ok(ReplyOpen { fh, flags: 0 }) + } + } + fn readdirplus<'a>( + &'a self, + req: Request, + parent: u64, + fh: u64, + offset: u64, + lock_owner: u64, + ) -> impl Future>>> + Send + { + async move { + let entry = self.tree.inode.get(parent).ok_or(FuseError::NotDir)?; + let entry = entry.read().await; + if entry.kind().await != FileType::Directory { + return Err(FuseError::NotDir.into()); + } + // FIXME: use stream rather than vec iterator + let parent_attr = file_attr(&entry).await; + let mut result: Vec> = Vec::new(); + log::info!("parent inode: {}", parent); + for (name, child) in entry.list_child().skip(offset as usize) { + let a = dir_entry_plus(parent_attr, name.to_os_string(), child).await; + log::info!("child inode: {}", a.inode); + result.push(Ok(a)); + } + + Ok(ReplyDirectoryPlus { + entries: tokio_stream::iter(result.into_iter()), + }) + } + } + fn fallocate( + &self, + req: Request, + inode: u64, + fh: u64, + offset: u64, + length: u64, + mode: u32, + ) -> impl Future> + Send { + async move { + if let Some(entry) = self.tree.inode.get(inode) { + match entry.read().await.kind().await { + FileType::Directory | FileType::NamedPipe | FileType::CharDevice => {} + _ => return Ok(()), + } + } + Err(FuseError::IsDir.into()) + } + } + + fn interrupt(&self, req: Request, unique: u64) -> impl Future> + Send { + future_ready(Ok(())) + } + fn getattr( + &self, + req: Request, + inode: u64, + fh: Option, + flags: u32, + ) -> impl Future> + Send { + async move { + let root = self.tree.tree.get_root(); + let entry = root.read().await; + Ok(ReplyAttr { + ttl: Duration::from_secs(30), + attr: file_attr(&entry).await, + }) + } + } + // fn create( + // &self, + // req: Request, + // parent: u64, + // name: &OsStr, + // mode: u32, + // flags: u32, + // ) -> impl core::future::Future> + Send { + // async move { + // let parent = self + // .tree + // .inode + // .get(parent) + // .ok_or(FuseError::InodeNotFound)?; + // let mut parent = parent.write().await; + // let child = parent.create(name, mode).await?; + // let fh = self.handle_table.add(child); + // Ok(ReplyCreated { + // entry: child.dir_entry(name.to_os_string()).await, + // ttl: 0, + // flags: 0, + // fh, + // }) + // } + // } } -pub struct Template +pub struct Template(TarTree) where - F: AsyncRead + AsyncSeek + Unpin + Send + 'static, -{ - tree: TarTree, - semaphore: Semaphore, -} + F: AsyncRead + AsyncSeek + Unpin + Send + 'static; impl Template where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { - fn new_inner(tree: TarTree, memory: u64) -> Self { - Self { - tree, - semaphore: Semaphore::new(memory, 10), - } + fn new_inner(tree: TarTree) -> Self { + Self(tree) } - pub async fn as_template(&self, size: u64) -> Filesystem { + pub async fn as_filesystem(&self, permit: Permit) -> Filesystem { Filesystem { handle_table: HandleTable::new(), - tree: self.tree.cloned().await, - semaphore: self.semaphore.clone(), - _permit: self.semaphore.get_permit(size).await.unwrap(), + tree: self.0.cloned().await, + resource: AtomicI64::new( + permit + .count() + .try_into() + .expect(&format!("filesystem max size: {}", i64::MAX - 1)), + ), + _permit: permit, } } } impl Template { - pub async fn new(path: impl AsRef + Clone, size: u64) -> Result { + pub async fn new(path: impl AsRef + Clone) -> Result { let tree = TarTree::new(path).await?; - Ok(Self::new_inner(tree, size)) + Ok(Self::new_inner(tree)) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::semaphore::Semaphore; + use env_logger::*; + use tokio::time; + + #[tokio::test] + #[ignore = "not meant to be tested"] + async fn real_run() { + Builder::from_default_env() + .filter_level(log::LevelFilter::Debug) + .try_init() + .ok(); + + log::info!("start"); + let global_resource = Semaphore::new(4096 * 1024 * 1024, 1); + let template = Template::new("test/nested.tar").await.unwrap(); + let filesystem = template + .as_filesystem( + global_resource + .get_permit(1024 * 1024 * 1024) + .await + .unwrap(), + ) + .await; + let handle = filesystem.mount("./.temp/21").await; + + time::sleep(time::Duration::from_secs(300)).await; + drop(handle); } } diff --git a/judger/src/filesystem/entry/mod.rs b/judger/src/filesystem/entry/mod.rs index 41749ee0..89b9bf29 100644 --- a/judger/src/filesystem/entry/mod.rs +++ b/judger/src/filesystem/entry/mod.rs @@ -1,8 +1,14 @@ -use std::ffi::OsString; +use std::{ + ffi::OsString, + io::SeekFrom, + sync::atomic::{AtomicI64, Ordering}, +}; use bytes::Bytes; use fuse3::{raw::reply::DirectoryEntry, FileType}; -use tokio::io::{AsyncRead, AsyncSeek}; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt}; + +use crate::semaphore::Semaphore; use self::prelude::*; @@ -23,6 +29,41 @@ pub mod prelude { pub use super::{Entry, InoEntry}; } +impl ReadEntry +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ + async fn into_write( + value: ReadEntry, + resource: &AtomicI64, + ) -> Result { + if let ReadEntry::File(block) = &value { + let required_space = block.get_size() as i64; + if resource.fetch_sub(required_space, Ordering::AcqRel) < required_space { + return Err(FuseError::OutOfPermit); + } + } + let value = match value { + ReadEntry::SymLink(target) => WriteEntry::SymLink(target), + ReadEntry::HardLink(inode) => WriteEntry::HardLink(inode), + ReadEntry::Directory => WriteEntry::Directory, + ReadEntry::File(mut block) => { + block + .seek(SeekFrom::Start(0)) + .await + .map_err(|_| FuseError::Underlaying)?; + let mut data = Vec::new(); + block + .read_to_end(&mut data) + .await + .map_err(|_| FuseError::Underlaying)?; + WriteEntry::new_data(data) + } + }; + Ok(value) + } +} + #[derive(Debug)] pub enum Entry where @@ -72,27 +113,34 @@ where Entry::Write(write_entry) => write_entry.kind(), } } - pub async fn read(&mut self, offset: u64, size: u32) -> Result { - match &mut self.entry { - Entry::Read(entry) => entry.read(offset, size).await, - Entry::Write(entry) => entry.read(offset, size).await, + pub async fn read( + &mut self, + offset: u64, + size: u32, + resource: &AtomicI64, + ) -> Result { + if let Entry::Read(entry) = &mut self.entry { + self.entry = Entry::Write(ReadEntry::into_write(entry.clone(), &resource).await?); + } + if let Entry::Write(entry) = &mut self.entry { + entry.read(offset, size).await + } else { + unreachable!() } } - pub async fn write(&mut self, offset: u64, data: &[u8]) -> Result { + pub async fn write( + &mut self, + offset: u64, + data: &[u8], + resource: &AtomicI64, + ) -> Result { + let required_space = data.len() as i64; + if resource.fetch_sub(required_space, Ordering::AcqRel) < required_space { + return Err(FuseError::OutOfPermit); + } match &mut self.entry { Entry::Read(entry) => entry.write(offset, data).await, Entry::Write(entry) => entry.write(offset, data).await, } } - // pub async fn - pub async fn dir_entry(&self, name: OsString) -> DirectoryEntry { - // But for libfuse, an offset of zero means that offsets are - // not supported, so we shift everything by one. - DirectoryEntry { - inode: self.inode, - kind: self.kind().await, - name, - offset: 0, - } - } } diff --git a/judger/src/filesystem/entry/ro.rs b/judger/src/filesystem/entry/ro.rs index fca87bef..4b0de9ab 100644 --- a/judger/src/filesystem/entry/ro.rs +++ b/judger/src/filesystem/entry/ro.rs @@ -177,6 +177,10 @@ where stage: TarStage::Done, } } + #[inline] + pub fn get_size(&self) -> u64 { + self.size + } #[cfg(test)] fn from_raw(file: F, start: u64, size: u64) -> Self { Self { diff --git a/judger/src/filesystem/entry/rw.rs b/judger/src/filesystem/entry/rw.rs index 0dfe7cb7..b34df6d3 100644 --- a/judger/src/filesystem/entry/rw.rs +++ b/judger/src/filesystem/entry/rw.rs @@ -38,6 +38,9 @@ impl Entry { pub fn new_file() -> Self { Self::File(MemBlock::new(Vec::new())) } + pub fn new_data(data: Vec) -> Self { + Self::File(MemBlock::new(data)) + } pub fn new_symlink(target: OsString) -> Self { Self::SymLink(target) } diff --git a/judger/src/filesystem/mod.rs b/judger/src/filesystem/mod.rs index af448160..3a8bef0b 100644 --- a/judger/src/filesystem/mod.rs +++ b/judger/src/filesystem/mod.rs @@ -11,6 +11,7 @@ mod table; mod tree; pub use entry::prelude::*; +use tokio::sync::broadcast::error; #[derive(thiserror::Error, Debug)] pub enum FuseError { @@ -30,10 +31,13 @@ pub enum FuseError { InodeNotFound, #[error("missed handle")] HandleNotFound, + #[error("underlaying file error")] + Underlaying, } impl From for fuse3::Errno { fn from(value: FuseError) -> Self { + log::warn!("FUSE driver broken: {}", value); match value { FuseError::IsDir => libc::EISDIR, FuseError::NotDir => libc::ENOTDIR, diff --git a/judger/src/filesystem/reply.rs b/judger/src/filesystem/reply.rs index 7ca2c32e..11d19e9e 100644 --- a/judger/src/filesystem/reply.rs +++ b/judger/src/filesystem/reply.rs @@ -1,4 +1,4 @@ -use std::{sync::Arc, time::Duration}; +use std::{ffi::OsString, sync::Arc, time::Duration}; use fuse3::{ raw::{reply::*, Request}, @@ -11,44 +11,76 @@ use super::{ tree::ArcNode, }; -pub trait ImmutParsable +pub async fn dir_entry_plus( + parent_attr: FileAttr, + name: OsString, + entry: ArcNode>, +) -> DirectoryEntryPlus where F: AsyncRead + AsyncSeek + Send + Unpin + 'static, { - async fn parse(request: Request, entry: ArcNode>) -> Self; + let entry = entry.read().await; + DirectoryEntryPlus { + inode: entry.inode, + generation: 1, + kind: entry.kind().await, + name, + offset: 1, + attr: parent_attr, + entry_ttl: Duration::from_secs(30), + attr_ttl: Duration::from_secs(30), + } +} + +pub async fn dir_entry(name: OsString, entry: ArcNode>) -> DirectoryEntry +where + F: AsyncRead + AsyncSeek + Send + Unpin + 'static, +{ + let entry = entry.read().await; + DirectoryEntry { + inode: entry.inode, + kind: entry.kind().await, + name, + offset: 1, + } +} + +#[inline] +pub async fn reply_entry(request: Request, entry: ArcNode>) -> ReplyEntry +where + F: AsyncRead + AsyncSeek + Send + Unpin + 'static, +{ + let nlink = Arc::strong_count(&entry) - 1; + let entry = entry.read().await; + ReplyEntry { + ttl: Duration::from_secs(30), + attr: file_attr(&entry).await, + generation: 1, + } } -impl ImmutParsable for ReplyEntry +pub async fn file_attr(entry: &InoEntry) -> FileAttr where F: AsyncRead + AsyncSeek + Send + Unpin + 'static, { - #[inline] - async fn parse(request: Request, entry: ArcNode>) -> Self { - let nlink = Arc::strong_count(&entry) - 1; - let entry = entry.read().await; - Self { - ttl: Duration::from_secs(30), - attr: FileAttr { - ino: entry.inode, - size: 0, - blocks: 0, - atime: Timestamp::new(0, 0), - mtime: Timestamp::new(0, 0), - ctime: Timestamp::new(0, 0), - kind: entry.kind().await, - perm: (libc::S_IREAD - | libc::S_IWRITE - | libc::S_IEXEC - | libc::S_IRWXU - | libc::S_IRWXO - | libc::S_ISVTX) as u16, - nlink: nlink as u32, - uid: request.gid, - gid: request.uid, - rdev: 179 << 16 + 02, - blksize: BLOCKSIZE as u32, - }, - generation: 1, - } + FileAttr { + ino: entry.inode, + size: 0, + blocks: 0, + atime: Timestamp::new(0, 0), + mtime: Timestamp::new(0, 0), + ctime: Timestamp::new(0, 0), + kind: entry.kind().await, + perm: (libc::S_IREAD + | libc::S_IWRITE + | libc::S_IEXEC + | libc::S_IRWXU + | libc::S_IRWXO + | libc::S_ISVTX) as u16, + nlink: 1, + uid: 0, + gid: 0, + rdev: 179 << 16 + 02, + blksize: BLOCKSIZE as u32, } } diff --git a/judger/src/filesystem/table/handle.rs b/judger/src/filesystem/table/handle.rs index ac75b8d8..2bfe5712 100644 --- a/judger/src/filesystem/table/handle.rs +++ b/judger/src/filesystem/table/handle.rs @@ -10,7 +10,7 @@ pub struct HandleTable { impl HandleTable { pub fn new() -> Self { Self { - handle_generator: AtomicU64::new(0), + handle_generator: AtomicU64::new(1), table: RwLock::new(BTreeMap::new()), } } @@ -18,13 +18,16 @@ impl HandleTable { let handle = self .handle_generator .fetch_add(1, std::sync::atomic::Ordering::AcqRel); + log::trace!("allocate handle: {}", handle); self.table.write().insert(handle, entry); handle } pub fn get(&self, handle: u64) -> Option { + log::debug!("get handle: {}", handle); self.table.read().get(&handle).cloned() } pub fn remove(&self, handle: u64) -> Option { + log::trace!("deallocate handle: {}", handle); self.table.write().remove(&handle) } } diff --git a/judger/src/filesystem/table/inode.rs b/judger/src/filesystem/table/inode.rs index 016548e2..abe51279 100644 --- a/judger/src/filesystem/table/inode.rs +++ b/judger/src/filesystem/table/inode.rs @@ -66,6 +66,7 @@ impl InodeTable { let inode = self .inode_generator .fetch_add(1, std::sync::atomic::Ordering::AcqRel); + log::trace!("allocate inode: {}", inode); InodeHandle { inode, table: self } } /// update(clone) entry by providing new entry with inode diff --git a/judger/src/language/daemon.rs b/judger/src/language/daemon.rs new file mode 100644 index 00000000..931fdd84 --- /dev/null +++ b/judger/src/language/daemon.rs @@ -0,0 +1,29 @@ +use std::collections::BTreeMap; + +use uuid::Uuid; + +use super::config::*; +use crate::semaphore::Semaphore; +use crate::CONFIG; + +static PLUGIN_PATH: &str = "./plugins"; +/// max queue judging task +const MAX_QUEUE: usize = 10; + +pub struct Daemon { + semaphore: Semaphore, + templates: BTreeMap, +} + +impl Daemon { + pub fn new() -> Self { + let semaphore = Semaphore::new(CONFIG.memory, MAX_QUEUE); + let mut templates = BTreeMap::new(); + todo!("Load plugins"); + // design a loader struct + Daemon { + semaphore, + templates, + } + } +} diff --git a/judger/src/language/mod.rs b/judger/src/language/mod.rs index 1bf79dfa..863bfb9c 100644 --- a/judger/src/language/mod.rs +++ b/judger/src/language/mod.rs @@ -1 +1,2 @@ mod config; +mod daemon; From e3085fe12970e8c8e3977dc332146af9ed7a7c7a Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Thu, 9 May 2024 00:05:52 +0800 Subject: [PATCH 20/38] feat(Judger): :construction: add adj table --- Cargo.lock | 7 + judger/Cargo.toml | 1 + judger/src/filesystem/adj.rs | 197 +++++++++++++++++++++++++++++ judger/src/filesystem/entry/mod.rs | 4 +- judger/src/filesystem/mod.rs | 1 + 5 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 judger/src/filesystem/adj.rs diff --git a/Cargo.lock b/Cargo.lock index f3a906f7..21c6b5c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2931,6 +2931,7 @@ dependencies = [ "lazy_static", "libc", "log", + "peekmore", "prost 0.12.3", "prost-types", "rustix 0.38.31", @@ -3893,6 +3894,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +[[package]] +name = "peekmore" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9163e1259760e83d528d1b3171e5100c1767f10c52e1c4d6afad26e63d47d758" + [[package]] name = "pem-rfc7468" version = "0.7.0" diff --git a/judger/Cargo.toml b/judger/Cargo.toml index 860208e5..293a18fc 100644 --- a/judger/Cargo.toml +++ b/judger/Cargo.toml @@ -22,6 +22,7 @@ lazy_static = "1.4.0" libc = "0.2.154" bytes = "1.6.0" test-log = "0.2.16" +peekmore = "1.3.0" [dependencies.fuse3] version = "0.7.1" diff --git a/judger/src/filesystem/adj.rs b/judger/src/filesystem/adj.rs new file mode 100644 index 00000000..8af3643b --- /dev/null +++ b/judger/src/filesystem/adj.rs @@ -0,0 +1,197 @@ +use std::{ + collections::HashMap, + ffi::{OsStr, OsString}, +}; + +const ID_MIN: usize = 1; + +struct Node { + parent_idx: usize, + value: V, + children: HashMap, +} + +pub struct AdjTable { + by_id: Vec>, +} + +impl AdjTable { + pub fn new() -> Self { + Self { by_id: vec![] } + } + pub fn insert_root(&mut self, value: V) -> NodeWrapper { + let idx = self.by_id.len(); + self.by_id.push(Node { + parent_idx: 0, + value, + children: HashMap::new(), + }); + NodeWrapper { table: self, idx } + } + pub fn get_root(&mut self) -> NodeWrapper { + NodeWrapper { + table: self, + idx: 0, + } + } + pub fn get_by_id(&mut self, id: usize) -> Option> { + if id < ID_MIN || id >= self.by_id.len() + ID_MIN { + return None; + } + Some(NodeWrapper { + table: self, + idx: id - ID_MIN, + }) + } + pub fn get_by_path<'a>( + &mut self, + mut path: impl Iterator, + ) -> Option> { + let mut idx = self.get_root().idx; + while let Some(name) = path.next() { + if self.by_id[idx].children.contains_key(name) { + idx = self.by_id[idx].children[name]; + } else { + return None; + } + } + Some(NodeWrapper { table: self, idx }) + } + pub fn get_by_path_or_insert( + &mut self, + path: impl Iterator, + mut default_value: F, + ) -> NodeWrapper + where + F: FnMut() -> V, + { + let mut idx = self.get_root().idx; + for name in path { + if self.by_id[idx].children.contains_key(&name) { + idx = self.by_id[idx].children[&name]; + } else { + let new_idx = self.by_id.len(); + self.by_id.push(Node { + parent_idx: idx, + value: default_value(), + children: HashMap::new(), + }); + self.by_id[idx].children.insert(name, new_idx); + } + } + NodeWrapper { table: self, idx } + } + pub fn insert_by_path( + &mut self, + path: impl Iterator, + mut default_value: F, + value: V, + ) -> NodeWrapper + where + F: FnMut() -> V, + { + let mut idx = self.get_root().idx; + let mut path = path.peekable(); + debug_assert!(path.peek().is_some()); + let mut seg; + while path.peek().is_some() { + seg = path.next().unwrap(); + if self.by_id[idx].children.contains_key(&seg) { + idx = self.by_id[idx].children[&seg]; + } else { + let new_idx = self.by_id.len(); + self.by_id.push(Node { + parent_idx: idx, + value: default_value(), + children: HashMap::new(), + }); + self.by_id[idx].children.insert(seg, new_idx); + idx = new_idx; + } + } + self.by_id[idx].value = value; + NodeWrapper { table: self, idx } + } +} + +pub struct NodeWrapper<'a, V> { + table: &'a mut AdjTable, + idx: usize, +} + +impl<'a, V> NodeWrapper<'a, V> { + pub fn get_id(&self) -> usize { + self.idx + ID_MIN + } + pub fn is_root(&self) -> bool { + self.idx == 0 + } + pub fn insert(&mut self, name: OsString, value: V) -> NodeWrapper { + let idx = self.table.by_id.len(); + self.table.by_id.push(Node { + parent_idx: self.idx, + value, + children: HashMap::new(), + }); + self.table.by_id[self.idx].children.insert(name, idx); + NodeWrapper { + table: self.table, + idx, + } + } + pub fn parent(self) -> Option> { + if self.idx == 0 { + return None; + } + let parent_idx = self.table.by_id[self.idx].parent_idx; + Some(NodeWrapper { + table: self.table, + idx: parent_idx, + }) + } + fn children(self) -> impl Iterator + 'a { + self.table.by_id[self.idx] + .children + .iter() + .map(|(_, &idx)| idx + ID_MIN) + } + fn get_value(&self) -> &V { + &self.table.by_id[self.idx].value + } +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_adj_table() { + let mut table = super::AdjTable::new(); + let mut root = table.insert_root(0); + root.insert(OsStr::new("a").into(), 1); + let mut b = root.insert(OsStr::new("b").into(), 2); + + let c = b.insert(OsStr::new("c").into(), 3); + + assert_eq!(c.get_id(), 4); + assert_eq!(b.children().collect::>(), vec![4]); + } + #[test] + fn get_or_insert() { + let mut table = super::AdjTable::new(); + table.insert_root(0); + table.insert_by_path( + vec!["abc", "efg", "123", "456"] + .into_iter() + .map(|x| OsStr::new(x).into()), + || 4, + 10, + ); + let root = table.get_root(); + let l1 = root.children().next().unwrap(); + let l2 = table.get_by_id(l1).unwrap().children().next().unwrap(); + let l3 = table.get_by_id(l2).unwrap().children().next().unwrap(); + let l4 = table.get_by_id(l3).unwrap().children().next().unwrap(); + assert_eq!(l4, 5); + assert_eq!(table.get_by_id(l4).unwrap().get_value(), &10); + } +} diff --git a/judger/src/filesystem/entry/mod.rs b/judger/src/filesystem/entry/mod.rs index 89b9bf29..9ab4107c 100644 --- a/judger/src/filesystem/entry/mod.rs +++ b/judger/src/filesystem/entry/mod.rs @@ -5,11 +5,9 @@ use std::{ }; use bytes::Bytes; -use fuse3::{raw::reply::DirectoryEntry, FileType}; +use fuse3::FileType; use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt}; -use crate::semaphore::Semaphore; - use self::prelude::*; use super::FuseError; diff --git a/judger/src/filesystem/mod.rs b/judger/src/filesystem/mod.rs index 3a8bef0b..77d8f731 100644 --- a/judger/src/filesystem/mod.rs +++ b/judger/src/filesystem/mod.rs @@ -4,6 +4,7 @@ //! mod adapter; +mod adj; mod entry; mod macro_; mod reply; From ec73c1a020be1c9bb63d495a9c5cbad54225440f Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Thu, 9 May 2024 19:18:36 +0800 Subject: [PATCH 21/38] refactor(Judger): :fire: remove tree, use mininal practicial design, add error handling --- Cargo.lock | 55 +-- backend/src/controller/judger/mod.rs | 1 - judger/Cargo.toml | 3 +- judger/src/error.rs | 15 - judger/src/filesystem/adapter.rs | 390 ------------------ judger/src/filesystem/adapter/error.rs | 43 ++ judger/src/filesystem/adapter/fuse.rs | 367 ++++++++++++++++ .../filesystem/{table => adapter}/handle.rs | 0 judger/src/filesystem/adapter/mod.rs | 45 ++ judger/src/filesystem/{ => adapter}/reply.rs | 59 +-- judger/src/filesystem/adapter/template.rs | 39 ++ judger/src/filesystem/adj.rs | 175 ++++++-- judger/src/filesystem/entry/mod.rs | 201 +++++---- judger/src/filesystem/entry/ro.rs | 114 +---- judger/src/filesystem/entry/rw.rs | 61 +-- judger/src/filesystem/entry/tar.rs | 151 +++---- judger/src/filesystem/entry/wrapper.rs | 32 +- judger/src/filesystem/error.rs | 1 + judger/src/filesystem/mod.rs | 48 +-- judger/src/filesystem/resource.rs | 17 + judger/src/filesystem/table/inode.rs | 91 ---- judger/src/filesystem/table/mod.rs | 5 - judger/src/filesystem/tree.rs | 368 ----------------- judger/src/main.rs | 5 +- judger/src/sandbox/error.rs | 8 + judger/src/sandbox/mod.rs | 3 +- judger/src/sandbox/monitor/mem_cpu.rs | 2 +- judger/src/sandbox/monitor/mod.rs | 9 +- judger/src/sandbox/monitor/output.rs | 2 +- judger/src/sandbox/monitor/wrapper.rs | 3 +- judger/src/sandbox/process/process.rs | 8 +- judger/src/semaphore.rs | 17 +- 32 files changed, 922 insertions(+), 1416 deletions(-) delete mode 100644 judger/src/filesystem/adapter.rs create mode 100644 judger/src/filesystem/adapter/error.rs create mode 100644 judger/src/filesystem/adapter/fuse.rs rename judger/src/filesystem/{table => adapter}/handle.rs (100%) create mode 100644 judger/src/filesystem/adapter/mod.rs rename judger/src/filesystem/{ => adapter}/reply.rs (53%) create mode 100644 judger/src/filesystem/adapter/template.rs create mode 100644 judger/src/filesystem/error.rs create mode 100644 judger/src/filesystem/resource.rs delete mode 100644 judger/src/filesystem/table/inode.rs delete mode 100644 judger/src/filesystem/table/mod.rs delete mode 100644 judger/src/filesystem/tree.rs create mode 100644 judger/src/sandbox/error.rs diff --git a/Cargo.lock b/Cargo.lock index 21c6b5c7..958784dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1659,15 +1659,6 @@ dependencies = [ "syn 2.0.52", ] -[[package]] -name = "env_filter" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" -dependencies = [ - "log", -] - [[package]] name = "env_logger" version = "0.10.2" @@ -1681,18 +1672,6 @@ dependencies = [ "termcolor", ] -[[package]] -name = "env_logger" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "log", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -2924,14 +2903,13 @@ dependencies = [ "cgroups-rs", "crossbeam", "derive_builder", - "env_logger 0.10.2", + "env_logger", "fuse3", "futures-core", "grpc", "lazy_static", "libc", "log", - "peekmore", "prost 0.12.3", "prost-types", "rustix 0.38.31", @@ -2939,7 +2917,6 @@ dependencies = [ "spin 0.9.8", "tar", "tempfile", - "test-log", "thiserror", "tokio", "tokio-stream", @@ -3894,12 +3871,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" -[[package]] -name = "peekmore" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9163e1259760e83d528d1b3171e5100c1767f10c52e1c4d6afad26e63d47d758" - [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -4085,7 +4056,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" dependencies = [ - "env_logger 0.10.2", + "env_logger", "log", ] @@ -5777,28 +5748,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "test-log" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dffced63c2b5c7be278154d76b479f9f9920ed34e7574201407f0b14e2bbb93" -dependencies = [ - "env_logger 0.11.3", - "test-log-macros", - "tracing-subscriber", -] - -[[package]] -name = "test-log-macros" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.52", -] - [[package]] name = "testsuit" version = "0.1.0" diff --git a/backend/src/controller/judger/mod.rs b/backend/src/controller/judger/mod.rs index af86abaa..a1f74373 100644 --- a/backend/src/controller/judger/mod.rs +++ b/backend/src/controller/judger/mod.rs @@ -1,4 +1,3 @@ -// FIXME: we don't need Meter mod pubsub; mod route; mod score; diff --git a/judger/Cargo.toml b/judger/Cargo.toml index 293a18fc..0146a039 100644 --- a/judger/Cargo.toml +++ b/judger/Cargo.toml @@ -21,8 +21,6 @@ crossbeam = "0.8.4" lazy_static = "1.4.0" libc = "0.2.154" bytes = "1.6.0" -test-log = "0.2.16" -peekmore = "1.3.0" [dependencies.fuse3] version = "0.7.1" @@ -45,6 +43,7 @@ features = [ "fs", "io-util", "parking_lot", + "signal" ] # TODO migrate to 10 diff --git a/judger/src/error.rs b/judger/src/error.rs index f23298cf..e69de29b 100644 --- a/judger/src/error.rs +++ b/judger/src/error.rs @@ -1,15 +0,0 @@ -use tokio::sync::broadcast::error; - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("{0}")] - CgroupError(#[from] cgroups_rs::error::Error), - #[error("insufficient `{0}`")] - Insufficient(&'static str), - // #[error("Out Of `{0}`")] - // OutOfResource(crate::sandbox::ResourceKind), - #[error("io error")] - IoError(#[from] std::io::Error), - #[error("invaild tarball: `{0}`")] - InvalidTarball(&'static str), -} diff --git a/judger/src/filesystem/adapter.rs b/judger/src/filesystem/adapter.rs deleted file mode 100644 index c76fadda..00000000 --- a/judger/src/filesystem/adapter.rs +++ /dev/null @@ -1,390 +0,0 @@ -use std::{ffi::OsStr, num::NonZeroU32, path::Path, sync::atomic::AtomicI64, time::Duration}; - -use crate::{filesystem::FuseError, semaphore::Permit, Error}; -use bytes::Bytes; -use fuse3::{ - raw::{reply::*, MountHandle, Request, Session}, - FileType, MountOptions, Result as FuseResult, -}; -use std::future::{ready as future_ready, Future}; -use tokio::{ - fs::File, - io::{AsyncRead, AsyncSeek}, -}; - -use super::{entry::prelude::*, reply::*, table::HandleTable, tree::ArcNode}; - -type VecStream = tokio_stream::Iter>; - -pub struct FilesystemHandle(Option); - -impl Drop for FilesystemHandle { - fn drop(&mut self) { - tokio::spawn(self.0.take().unwrap().unmount()); - } -} - -pub struct Filesystem -where - F: AsyncRead + AsyncSeek + Unpin + Send + 'static, -{ - handle_table: HandleTable>>, - tree: TarTree, - resource: AtomicI64, - _permit: Permit, -} - -impl Filesystem -where - F: AsyncRead + AsyncSeek + Unpin + Send + Sync + 'static, -{ - pub async fn mount(self, path: impl AsRef + Clone) -> FilesystemHandle { - let uid = unsafe { libc::getuid() }; - let gid = unsafe { libc::getgid() }; - - let mut mount_options = MountOptions::default(); - - mount_options.uid(uid).gid(gid).force_readdir_plus(true); - - let handle = Session::new(mount_options) - .mount_with_unprivileged(self, path.as_ref()) - .await - .unwrap(); - - FilesystemHandle(Some(handle)) - } -} - -impl fuse3::raw::Filesystem for Filesystem -where - F: AsyncRead + AsyncSeek + Unpin + Send + Sync + 'static, -{ - type DirEntryStream<'a>=VecStream> where Self: 'a; - type DirEntryPlusStream<'a>=VecStream> where Self: 'a; - - fn init(&self, _: Request) -> impl Future> + Send { - future_ready(Ok(ReplyInit { - max_write: NonZeroU32::new(BLOCKSIZE as u32).unwrap(), - })) - } - - fn destroy(&self, _: Request) -> impl Future + Send { - future_ready(()) - } - - fn lookup( - &self, - req: Request, - parent: u64, - name: &OsStr, - ) -> impl Future> + Send { - async move { - if let Some(entry) = self.tree.inode.get(parent) { - let entry = entry.read().await; - if let Some(entry) = entry.get_by_component(name) { - return Ok(reply_entry(req, entry).await); - } - } - Err(FuseError::InodeNotFound.into()) - } - } - fn forget( - &self, - _: Request, - inode: u64, - _: u64, - ) -> impl core::future::Future + Send { - self.tree.inode.remove(inode); - future_ready(()) - } - fn statfs( - &self, - _: Request, - inode: u64, - ) -> impl Future> + Send { - async { - Ok(ReplyStatFs { - blocks: 0, - bfree: 4096 * 4096, - bavail: 4096 * 2048, - files: 0, - ffree: self.tree.inode.get_free_inode(), - bsize: BLOCKSIZE as u32, - namelen: 256, - frsize: BLOCKSIZE as u32, - }) - } - } - fn opendir( - &self, - req: Request, - inode: u64, - flags: u32, - ) -> impl Future> + Send { - async move { - let entry = self.tree.inode.get(inode).ok_or(FuseError::InodeNotFound)?; - if entry.read().await.kind().await != FileType::Directory { - return Err(FuseError::NotDir.into()); - } - let fh = self.handle_table.add(entry); - Ok(ReplyOpen { fh, flags: 0 }) - } - } - fn read( - &self, - req: Request, - inode: u64, - fh: u64, - offset: u64, - size: u32, - ) -> impl Future> + Send { - async move { - let entry = self.handle_table.get(fh).ok_or(FuseError::HandleNotFound)?; - let mut entry = entry.write().await; - entry - .read(offset, size, &self.resource) - .await - .map(|data| ReplyData { data }) - .map_err(Into::into) - } - } - // fn readdir<'a>( - // &'a self, - // req: Request, - // parent: u64, - // fh: u64, - // offset: i64, - // ) -> impl Future>>> + Send { - // async move { - // let entry = self.tree.inode.get(parent).ok_or(FuseError::NotDir)?; - // let entry = entry.read().await; - // // FIXME: use stream rather than vec iterator - // let mut result: Vec> = Vec::new(); - // for (name, child) in entry.list_child() { - // result.push(Ok(dir_entry(name.to_os_string(), child).await)); - // } - // Ok(ReplyDirectory { - // entries: tokio_stream::iter(result.into_iter()), - // }) - // } - // } - fn access( - &self, - req: Request, - inode: u64, - mask: u32, - ) -> impl Future> + Send { - future_ready(Ok(())) - } - fn fsync( - &self, - req: Request, - inode: u64, - fh: u64, - datasync: bool, - ) -> impl Future> + Send { - future_ready(Ok(())) - } - fn fsyncdir( - &self, - req: Request, - inode: u64, - fh: u64, - datasync: bool, - ) -> impl Future> + Send { - future_ready(Ok(())) - } - fn write( - &self, - req: Request, - inode: u64, - fh: u64, - offset: u64, - data: &[u8], - write_flags: u32, - flags: u32, - ) -> impl Future> + Send { - // FIXME: use semaphore to limit the write - async move { - let entry = self.handle_table.get(fh).ok_or(FuseError::HandleNotFound)?; - let mut entry = entry.write().await; - entry - .write(offset, data, &self.resource) - .await - .map(|written| ReplyWrite { written }) - .map_err(Into::into) - } - } - fn open( - &self, - req: Request, - inode: u64, - flags: u32, - ) -> impl Future> + Send { - async move { - let entry = self.tree.inode.get(inode).ok_or(FuseError::InodeNotFound)?; - let fh = self.handle_table.add(entry); - Ok(ReplyOpen { fh, flags: 0 }) - } - } - fn readdirplus<'a>( - &'a self, - req: Request, - parent: u64, - fh: u64, - offset: u64, - lock_owner: u64, - ) -> impl Future>>> + Send - { - async move { - let entry = self.tree.inode.get(parent).ok_or(FuseError::NotDir)?; - let entry = entry.read().await; - if entry.kind().await != FileType::Directory { - return Err(FuseError::NotDir.into()); - } - // FIXME: use stream rather than vec iterator - let parent_attr = file_attr(&entry).await; - let mut result: Vec> = Vec::new(); - log::info!("parent inode: {}", parent); - for (name, child) in entry.list_child().skip(offset as usize) { - let a = dir_entry_plus(parent_attr, name.to_os_string(), child).await; - log::info!("child inode: {}", a.inode); - result.push(Ok(a)); - } - - Ok(ReplyDirectoryPlus { - entries: tokio_stream::iter(result.into_iter()), - }) - } - } - fn fallocate( - &self, - req: Request, - inode: u64, - fh: u64, - offset: u64, - length: u64, - mode: u32, - ) -> impl Future> + Send { - async move { - if let Some(entry) = self.tree.inode.get(inode) { - match entry.read().await.kind().await { - FileType::Directory | FileType::NamedPipe | FileType::CharDevice => {} - _ => return Ok(()), - } - } - Err(FuseError::IsDir.into()) - } - } - - fn interrupt(&self, req: Request, unique: u64) -> impl Future> + Send { - future_ready(Ok(())) - } - fn getattr( - &self, - req: Request, - inode: u64, - fh: Option, - flags: u32, - ) -> impl Future> + Send { - async move { - let root = self.tree.tree.get_root(); - let entry = root.read().await; - Ok(ReplyAttr { - ttl: Duration::from_secs(30), - attr: file_attr(&entry).await, - }) - } - } - // fn create( - // &self, - // req: Request, - // parent: u64, - // name: &OsStr, - // mode: u32, - // flags: u32, - // ) -> impl core::future::Future> + Send { - // async move { - // let parent = self - // .tree - // .inode - // .get(parent) - // .ok_or(FuseError::InodeNotFound)?; - // let mut parent = parent.write().await; - // let child = parent.create(name, mode).await?; - // let fh = self.handle_table.add(child); - // Ok(ReplyCreated { - // entry: child.dir_entry(name.to_os_string()).await, - // ttl: 0, - // flags: 0, - // fh, - // }) - // } - // } -} - -pub struct Template(TarTree) -where - F: AsyncRead + AsyncSeek + Unpin + Send + 'static; - -impl Template -where - F: AsyncRead + AsyncSeek + Unpin + Send + 'static, -{ - fn new_inner(tree: TarTree) -> Self { - Self(tree) - } - pub async fn as_filesystem(&self, permit: Permit) -> Filesystem { - Filesystem { - handle_table: HandleTable::new(), - tree: self.0.cloned().await, - resource: AtomicI64::new( - permit - .count() - .try_into() - .expect(&format!("filesystem max size: {}", i64::MAX - 1)), - ), - _permit: permit, - } - } -} - -impl Template { - pub async fn new(path: impl AsRef + Clone) -> Result { - let tree = TarTree::new(path).await?; - Ok(Self::new_inner(tree)) - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::semaphore::Semaphore; - use env_logger::*; - use tokio::time; - - #[tokio::test] - #[ignore = "not meant to be tested"] - async fn real_run() { - Builder::from_default_env() - .filter_level(log::LevelFilter::Debug) - .try_init() - .ok(); - - log::info!("start"); - let global_resource = Semaphore::new(4096 * 1024 * 1024, 1); - let template = Template::new("test/nested.tar").await.unwrap(); - let filesystem = template - .as_filesystem( - global_resource - .get_permit(1024 * 1024 * 1024) - .await - .unwrap(), - ) - .await; - let handle = filesystem.mount("./.temp/21").await; - - time::sleep(time::Duration::from_secs(300)).await; - drop(handle); - } -} diff --git a/judger/src/filesystem/adapter/error.rs b/judger/src/filesystem/adapter/error.rs new file mode 100644 index 00000000..239e6ac9 --- /dev/null +++ b/judger/src/filesystem/adapter/error.rs @@ -0,0 +1,43 @@ +#[derive(thiserror::Error, Debug)] +pub enum FuseError { + #[error("not a readable file")] + IsDir, + #[error("end of file")] + Eof, + #[error("not a dir")] + NotDir, + #[error("out of resource")] + OutOfPermit, + #[error("number too large")] + OutOfRange, + #[error("unimplemented")] + Unimplemented, + #[error("missed inode")] + InvaildIno, + #[error("missed handle")] + HandleNotFound, + #[error("underlaying file error")] + Underlaying, + #[error("invalid path")] + InvalidPath, +} + +impl From for fuse3::Errno { + fn from(value: FuseError) -> Self { + match value { + FuseError::IsDir => libc::EISDIR, + FuseError::NotDir => libc::ENOTDIR, + FuseError::Eof => libc::EOF, + FuseError::OutOfPermit => { + log::info!("out of resource"); + libc::ENOMEM + } + FuseError::InvalidPath | FuseError::InvaildIno => libc::ENOENT, + _ => { + log::warn!("FUSE driver broken: {}", value); + libc::ENOMEM + } + } + .into() + } +} diff --git a/judger/src/filesystem/adapter/fuse.rs b/judger/src/filesystem/adapter/fuse.rs new file mode 100644 index 00000000..9bb7ca13 --- /dev/null +++ b/judger/src/filesystem/adapter/fuse.rs @@ -0,0 +1,367 @@ +use std::{ffi::OsStr, num::NonZeroU32, path::Path, sync::Arc}; + +use futures_core::Future; +use spin::Mutex; +use tokio::io::{AsyncRead, AsyncSeek}; + +use crate::{ + filesystem::{resource::Resource, TarTree, BLOCKSIZE}, + semaphore::Permit, +}; + +use super::{error::FuseError, handle::HandleTable, reply::*}; +use fuse3::{ + raw::{reply::*, *}, + Result as FuseResult, *, +}; + +type VecStream = tokio_stream::Iter>; +pub struct Filesystem +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ + handle_table: HandleTable, + tree: Mutex>, + resource: Arc, + _permit: Permit, +} + +impl Filesystem +where + F: AsyncRead + AsyncSeek + Unpin + Send + Sync + 'static, +{ + pub(super) fn new(tree: TarTree, permit: Permit) -> Self { + Self { + handle_table: HandleTable::new(), + tree: Mutex::new(tree), + resource: Arc::new(Resource::new(permit.count())), + _permit: permit, + } + } + pub async fn mount(self, path: impl AsRef + Clone) -> std::io::Result { + let uid = unsafe { libc::getuid() }; + let gid = unsafe { libc::getgid() }; + + let mut mount_options = MountOptions::default(); + + mount_options.uid(uid).gid(gid); + + Session::new(mount_options) + .mount_with_unprivileged(self, path.as_ref()) + .await + } +} + +impl fuse3::raw::Filesystem for Filesystem +where + F: AsyncRead + AsyncSeek + Unpin + Send + Sync + 'static, +{ + type DirEntryStream<'a>=VecStream> where Self: 'a; + type DirEntryPlusStream<'a>=VecStream> where Self: 'a; + + fn init(&self, _: Request) -> impl Future> + Send { + async { + Ok(ReplyInit { + max_write: NonZeroU32::new(BLOCKSIZE as u32).unwrap(), + }) + } + } + + fn destroy(&self, _: Request) -> impl Future + Send { + async {} + } + + fn lookup( + &self, + req: Request, + parent: u64, + name: &OsStr, + ) -> impl Future> + Send { + async move { + let tree = self.tree.lock(); + let node = tree.get(parent as usize).ok_or(FuseError::InvaildIno)?; + log::info!( + "parent name: {}", + String::from_utf8_lossy(node.get_name().as_encoded_bytes()) + ); + log::info!( + "lookup name: {}", + String::from_utf8_lossy(name.as_encoded_bytes()) + ); + let entry = node.get_by_component(name).ok_or(FuseError::InvalidPath)?; + // FIXME: unsure about the inode + Ok(reply_entry(req, entry.get_value(), parent)) + } + } + fn forget(&self, _: Request, inode: u64, _: u64) -> impl Future + Send { + async {} + } + fn statfs( + &self, + _: Request, + inode: u64, + ) -> impl Future> + Send { + async { + let tree = self.tree.lock(); + Ok(ReplyStatFs { + blocks: 0, + bfree: 4096 * 4096, + bavail: 4096 * 2048, + files: 0, + ffree: tree.get_remain_capacity() as u64, + bsize: BLOCKSIZE as u32, + namelen: 256, + frsize: BLOCKSIZE as u32, + }) + } + } + fn opendir( + &self, + req: Request, + inode: u64, + flags: u32, + ) -> impl Future> + Send { + async move { + let tree = self.tree.lock(); + let node = tree.get(inode as usize).ok_or(FuseError::InvaildIno)?; + if node.get_value().kind() != FileType::Directory { + return Err(FuseError::NotDir.into()); + } + let fh = self.handle_table.add(node.get_id()); + Ok(ReplyOpen { fh, flags: 0 }) + } + } + fn open( + &self, + req: Request, + inode: u64, + flags: u32, + ) -> impl Future> + Send { + async move { + let tree = self.tree.lock(); + let entry = tree.get(inode as usize).ok_or(FuseError::InvaildIno)?; + let fh = self.handle_table.add(entry.get_id()); + Ok(ReplyOpen { fh, flags: 0 }) + } + } + fn readdir<'a>( + &'a self, + req: Request, + parent: u64, + fh: u64, + offset: i64, + ) -> impl Future>>> + Send { + async move { + let tree = self.tree.lock(); + let node = tree.get(parent as usize).ok_or(FuseError::InvaildIno)?; + + if node.get_value().kind() != FileType::Directory { + return Err(FuseError::NotDir.into()); + } + + let parent_node = node.parent().unwrap_or_else(|| tree.get_root()); + + let entries = vec![ + Ok(dir_entry( + OsStr::new(".").to_os_string(), + node.get_value(), + node.get_id() as u64, + )), + Ok(dir_entry( + OsStr::new("..").to_os_string(), + parent_node.get_value(), + parent_node.get_id() as u64, + )), + ] + .into_iter() + .chain( + node.children() + .map(|inode| { + let node = tree.get(inode).unwrap(); + dir_entry( + node.get_name().to_os_string(), + node.get_value(), + inode as u64, + ) + }) + .map(Ok), + ) + .skip(offset as usize) + .collect::>(); + + Ok(ReplyDirectory { + entries: tokio_stream::iter(entries), + }) + } + } + fn readdirplus<'a>( + &'a self, + req: Request, + parent: u64, + fh: u64, + offset: u64, + lock_owner: u64, + ) -> impl Future>>> + Send + { + async move { + let tree = self.tree.lock(); + let node = tree.get(parent as usize).ok_or(FuseError::InvaildIno)?; + + if node.get_value().kind() != FileType::Directory { + return Err(FuseError::NotDir.into()); + } + + let parent_node = node.parent().unwrap_or_else(|| tree.get_root()); + + let entries = vec![ + Ok(dir_entry_plus( + OsStr::new(".").to_os_string(), + node.get_value(), + node.get_id() as u64, + 1, + )), + Ok(dir_entry_plus( + OsStr::new("..").to_os_string(), + parent_node.get_value(), + parent_node.get_id() as u64, + 2, + )), + ] + .into_iter() + .chain( + node.children() + .enumerate() + .map(|(offset, inode)| { + let node = tree.get(inode).unwrap(); + dir_entry_plus( + node.get_name().to_os_string(), + node.get_value(), + inode as u64, + (offset + 3) as i64, + ) + }) + .map(Ok), + ) + .skip(offset as usize) + .collect::>(); + + Ok(ReplyDirectoryPlus { + entries: tokio_stream::iter(entries), + }) + } + } + fn read( + &self, + req: Request, + inode: u64, + fh: u64, + offset: u64, + size: u32, + ) -> impl Future> + Send { + async move { + let handle = { + let tree = self.tree.lock(); + let entry = self.handle_table.get(fh).ok_or(FuseError::HandleNotFound)?; + let node = tree.get(entry).ok_or(FuseError::InvaildIno)?; + let entry = node.get_value(); + entry.get_read_handle() + } + .ok_or(FuseError::IsDir)?; + + handle + .read(offset, size) + .await + .map(|data| ReplyData { data }) + .map_err(Into::into) + } + } + fn write( + &self, + req: Request, + inode: u64, + fh: u64, + offset: u64, + data: &[u8], + write_flags: u32, + flags: u32, + ) -> impl Future> + Send { + async move { + let handle = { + let mut tree = self.tree.lock(); + let entry = self.handle_table.get(fh).ok_or(FuseError::HandleNotFound)?; + let mut node = tree.get_mut(entry).ok_or(FuseError::InvaildIno)?; + let entry = node.get_value(); + entry.get_write_handle() + } + .ok_or(FuseError::IsDir)?; + let resource = self.resource.clone(); + let written = handle.write(offset, data, &resource).await?; + Ok(ReplyWrite { written }) + } + } + fn access( + &self, + req: Request, + inode: u64, + mask: u32, + ) -> impl Future> + Send { + async { Ok(()) } + } + fn fsync( + &self, + req: Request, + inode: u64, + fh: u64, + datasync: bool, + ) -> impl Future> + Send { + async { Ok(()) } + } + fn fsyncdir( + &self, + req: Request, + inode: u64, + fh: u64, + datasync: bool, + ) -> impl Future> + Send { + async { Ok(()) } + } + fn fallocate( + &self, + req: Request, + inode: u64, + fh: u64, + offset: u64, + length: u64, + mode: u32, + ) -> impl Future> + Send { + async move { + let tree = self.tree.lock(); + let node = tree.get(inode as usize).ok_or(FuseError::InvaildIno)?; + + match node.get_value().kind() { + FileType::Directory | FileType::NamedPipe | FileType::CharDevice => { + Err(FuseError::IsDir.into()) + } + _ => Ok(()), + } + } + } + + fn interrupt(&self, req: Request, unique: u64) -> impl Future> + Send { + async { Ok(()) } + } + fn getattr( + &self, + req: Request, + inode: u64, + fh: Option, + flags: u32, + ) -> impl Future> + Send { + async move { + let tree = self.tree.lock(); + let entry = tree.get(inode as usize).ok_or(FuseError::InvaildIno)?; + // FIXME: unsure about the inode + Ok(reply_attr(entry.get_value(), inode)) + } + } +} diff --git a/judger/src/filesystem/table/handle.rs b/judger/src/filesystem/adapter/handle.rs similarity index 100% rename from judger/src/filesystem/table/handle.rs rename to judger/src/filesystem/adapter/handle.rs diff --git a/judger/src/filesystem/adapter/mod.rs b/judger/src/filesystem/adapter/mod.rs new file mode 100644 index 00000000..07c9a920 --- /dev/null +++ b/judger/src/filesystem/adapter/mod.rs @@ -0,0 +1,45 @@ +mod error; +mod fuse; +mod handle; +mod reply; +mod template; + +pub use fuse::Filesystem; +pub use template::Template; + +#[cfg(test)] +mod test { + use super::*; + use crate::semaphore::Semaphore; + use env_logger::*; + + #[tokio::test] + #[ignore = "not meant to be tested"] + async fn test_mount() { + Builder::from_default_env() + .filter_level(log::LevelFilter::Trace) + .try_init() + .ok(); + + log::info!("mounting test tarball in .temp/1 ..."); + let global_resource = Semaphore::new(4096 * 1024 * 1024, 1); + let template = Template::new("test/nested.tar").await.unwrap(); + let filesystem = template + .as_filesystem( + global_resource + .get_permit(1024 * 1024 * 1024) + .await + .unwrap(), + ) + .await; + let mut mount_handle = filesystem.mount("./.temp/1").await.unwrap(); + let handle = &mut mount_handle; + + tokio::select! { + res = handle => res.unwrap(), + _ = tokio::signal::ctrl_c() => { + mount_handle.unmount().await.unwrap() + } + } + } +} diff --git a/judger/src/filesystem/reply.rs b/judger/src/filesystem/adapter/reply.rs similarity index 53% rename from judger/src/filesystem/reply.rs rename to judger/src/filesystem/adapter/reply.rs index 11d19e9e..4f935983 100644 --- a/judger/src/filesystem/reply.rs +++ b/judger/src/filesystem/adapter/reply.rs @@ -1,4 +1,4 @@ -use std::{ffi::OsString, sync::Arc, time::Duration}; +use std::{ffi::OsString, time::Duration}; use fuse3::{ raw::{reply::*, Request}, @@ -6,71 +6,74 @@ use fuse3::{ }; use tokio::io::{AsyncRead, AsyncSeek}; -use super::{ - entry::{prelude::BLOCKSIZE, InoEntry}, - tree::ArcNode, -}; +use crate::filesystem::{Entry, BLOCKSIZE}; -pub async fn dir_entry_plus( - parent_attr: FileAttr, +pub fn dir_entry_plus( name: OsString, - entry: ArcNode>, + entry: &Entry, + inode: u64, + offset: i64, ) -> DirectoryEntryPlus where F: AsyncRead + AsyncSeek + Send + Unpin + 'static, { - let entry = entry.read().await; DirectoryEntryPlus { - inode: entry.inode, - generation: 1, - kind: entry.kind().await, + inode, + generation: 0, + kind: entry.kind(), name, - offset: 1, - attr: parent_attr, + offset, + attr: file_attr(entry, inode), entry_ttl: Duration::from_secs(30), attr_ttl: Duration::from_secs(30), } } -pub async fn dir_entry(name: OsString, entry: ArcNode>) -> DirectoryEntry +pub fn dir_entry(name: OsString, entry: &Entry, inode: u64) -> DirectoryEntry where F: AsyncRead + AsyncSeek + Send + Unpin + 'static, { - let entry = entry.read().await; DirectoryEntry { - inode: entry.inode, - kind: entry.kind().await, + inode, + kind: entry.kind(), name, offset: 1, } } -#[inline] -pub async fn reply_entry(request: Request, entry: ArcNode>) -> ReplyEntry +pub fn reply_attr(entry: &Entry, inode: u64) -> ReplyAttr +where + F: AsyncRead + AsyncSeek + Send + Unpin + 'static, +{ + ReplyAttr { + ttl: Duration::from_secs(30), + attr: file_attr(&entry, inode), + } +} + +pub fn reply_entry(request: Request, entry: &Entry, inode: u64) -> ReplyEntry where F: AsyncRead + AsyncSeek + Send + Unpin + 'static, { - let nlink = Arc::strong_count(&entry) - 1; - let entry = entry.read().await; ReplyEntry { ttl: Duration::from_secs(30), - attr: file_attr(&entry).await, - generation: 1, + attr: file_attr(&entry, inode), + generation: 0, } } -pub async fn file_attr(entry: &InoEntry) -> FileAttr +pub fn file_attr(entry: &Entry, inode: u64) -> FileAttr where F: AsyncRead + AsyncSeek + Send + Unpin + 'static, { FileAttr { - ino: entry.inode, - size: 0, + ino: inode, + size: entry.get_size(), blocks: 0, atime: Timestamp::new(0, 0), mtime: Timestamp::new(0, 0), ctime: Timestamp::new(0, 0), - kind: entry.kind().await, + kind: entry.kind(), perm: (libc::S_IREAD | libc::S_IWRITE | libc::S_IEXEC diff --git a/judger/src/filesystem/adapter/template.rs b/judger/src/filesystem/adapter/template.rs new file mode 100644 index 00000000..991edb29 --- /dev/null +++ b/judger/src/filesystem/adapter/template.rs @@ -0,0 +1,39 @@ +use std::path::Path; + +use tokio::{ + fs::File, + io::{AsyncRead, AsyncSeek}, +}; + +use crate::{ + filesystem::{adj::DeepClone, TarTree}, + semaphore::Permit, +}; + +use super::fuse::Filesystem; + +pub struct Template +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ + tree: TarTree, +} + +impl Template +where + F: AsyncRead + AsyncSeek + Unpin + Send + Sync + 'static, +{ + pub fn new_inner(tree: TarTree) -> Self { + Self { tree } + } + pub async fn as_filesystem(&self, permit: Permit) -> Filesystem { + Filesystem::new(self.tree.deep_clone().await, permit) + } +} + +impl Template { + pub async fn new(path: impl AsRef + Clone) -> std::io::Result { + let tree = TarTree::new(path).await?; + Ok(Self::new_inner(tree)) + } +} diff --git a/judger/src/filesystem/adj.rs b/judger/src/filesystem/adj.rs index 8af3643b..d2c39571 100644 --- a/judger/src/filesystem/adj.rs +++ b/judger/src/filesystem/adj.rs @@ -1,9 +1,24 @@ use std::{ collections::HashMap, ffi::{OsStr, OsString}, + path::{Component, Path}, }; const ID_MIN: usize = 1; +const REMAIN_CAPACITY: u32 = 1 << 31; + +pub fn to_internal_path<'a>(path: &'a Path) -> impl Iterator + 'a { + path.components().filter_map(|component| match component { + Component::Prefix(x) => unreachable!("Windows only: {:?}", x), + Component::RootDir | Component::CurDir | Component::ParentDir => None, + Component::Normal(x) => Some(x), + }) + // .collect::>() +} + +pub trait DeepClone { + async fn deep_clone(&self) -> Self; +} struct Node { parent_idx: usize, @@ -11,30 +26,53 @@ struct Node { children: HashMap, } +impl DeepClone for Node { + async fn deep_clone(&self) -> Self { + Self { + parent_idx: self.parent_idx, + value: self.value.deep_clone().await, + children: self.children.iter().map(|(k, v)| (k.clone(), *v)).collect(), + } + } +} + pub struct AdjTable { by_id: Vec>, } +impl DeepClone for AdjTable { + async fn deep_clone(&self) -> Self { + let mut by_id = Vec::with_capacity(self.by_id.len()); + for node in &self.by_id { + by_id.push(node.deep_clone().await); + } + Self { by_id } + } +} + impl AdjTable { pub fn new() -> Self { Self { by_id: vec![] } } - pub fn insert_root(&mut self, value: V) -> NodeWrapper { + pub fn insert_root(&mut self, value: V) -> NodeWrapperMut { let idx = self.by_id.len(); self.by_id.push(Node { parent_idx: 0, value, children: HashMap::new(), }); - NodeWrapper { table: self, idx } + NodeWrapperMut { table: self, idx } } - pub fn get_root(&mut self) -> NodeWrapper { + pub fn get_root(&self) -> NodeWrapper { NodeWrapper { table: self, idx: 0, } } - pub fn get_by_id(&mut self, id: usize) -> Option> { + pub fn get_remain_capacity(&self) -> u32 { + REMAIN_CAPACITY - self.by_id.len() as u32 + } + pub fn get(&self, id: usize) -> Option> { if id < ID_MIN || id >= self.by_id.len() + ID_MIN { return None; } @@ -43,8 +81,17 @@ impl AdjTable { idx: id - ID_MIN, }) } + pub fn get_mut(&mut self, id: usize) -> Option> { + if id < ID_MIN || id >= self.by_id.len() + ID_MIN { + return None; + } + Some(NodeWrapperMut { + table: self, + idx: id - ID_MIN, + }) + } pub fn get_by_path<'a>( - &mut self, + &self, mut path: impl Iterator, ) -> Option> { let mut idx = self.get_root().idx; @@ -61,7 +108,7 @@ impl AdjTable { &mut self, path: impl Iterator, mut default_value: F, - ) -> NodeWrapper + ) -> NodeWrapperMut where F: FnMut() -> V, { @@ -79,14 +126,14 @@ impl AdjTable { self.by_id[idx].children.insert(name, new_idx); } } - NodeWrapper { table: self, idx } + NodeWrapperMut { table: self, idx } } - pub fn insert_by_path( + pub fn insert_by_path<'a, F>( &mut self, - path: impl Iterator, + path: impl Iterator, mut default_value: F, value: V, - ) -> NodeWrapper + ) -> NodeWrapperMut where F: FnMut() -> V, { @@ -96,8 +143,8 @@ impl AdjTable { let mut seg; while path.peek().is_some() { seg = path.next().unwrap(); - if self.by_id[idx].children.contains_key(&seg) { - idx = self.by_id[idx].children[&seg]; + if self.by_id[idx].children.contains_key(seg) { + idx = self.by_id[idx].children[seg]; } else { let new_idx = self.by_id.len(); self.by_id.push(Node { @@ -105,17 +152,17 @@ impl AdjTable { value: default_value(), children: HashMap::new(), }); - self.by_id[idx].children.insert(seg, new_idx); + self.by_id[idx].children.insert(seg.to_os_string(), new_idx); idx = new_idx; } } self.by_id[idx].value = value; - NodeWrapper { table: self, idx } + NodeWrapperMut { table: self, idx } } } pub struct NodeWrapper<'a, V> { - table: &'a mut AdjTable, + table: &'a AdjTable, idx: usize, } @@ -126,7 +173,60 @@ impl<'a, V> NodeWrapper<'a, V> { pub fn is_root(&self) -> bool { self.idx == 0 } - pub fn insert(&mut self, name: OsString, value: V) -> NodeWrapper { + pub fn parent(&self) -> Option> { + if self.is_root() { + return None; + } + let parent_idx = self.table.by_id[self.idx].parent_idx; + Some(NodeWrapper { + table: self.table, + idx: parent_idx, + }) + } + pub fn children(self) -> impl Iterator + 'a { + log::info!( + "children length: {}", + self.table.by_id[self.idx].children.len() + ); + self.table.by_id[self.idx] + .children + .iter() + .map(|(_, &idx)| idx + ID_MIN) + } + pub fn get_value(&self) -> &V { + &self.table.by_id[self.idx].value + } + pub fn get_name(&self) -> &OsStr { + if self.is_root() { + OsStr::new("/") + } else { + self.table.by_id[self.table.by_id[self.idx].parent_idx] + .children + .iter() + .find(|(_, &idx)| idx == self.idx) + .unwrap() + .0 + } + } + pub fn get_by_component(&self, component: &OsStr) -> Option> { + if let Some(&idx) = self.table.by_id[self.idx].children.get(component) { + Some(NodeWrapper { + table: self.table, + idx, + }) + } else { + None + } + } +} + +pub struct NodeWrapperMut<'a, V> { + table: &'a mut AdjTable, + idx: usize, +} + +impl<'a, V> NodeWrapperMut<'a, V> { + pub fn insert(&mut self, name: OsString, value: V) -> NodeWrapperMut { let idx = self.table.by_id.len(); self.table.by_id.push(Node { parent_idx: self.idx, @@ -134,29 +234,36 @@ impl<'a, V> NodeWrapper<'a, V> { children: HashMap::new(), }); self.table.by_id[self.idx].children.insert(name, idx); - NodeWrapper { + NodeWrapperMut { table: self.table, idx, } } - pub fn parent(self) -> Option> { - if self.idx == 0 { - return None; - } - let parent_idx = self.table.by_id[self.idx].parent_idx; - Some(NodeWrapper { + pub fn get_id(&self) -> usize { + NodeWrapper { table: self.table, - idx: parent_idx, - }) + idx: self.idx, + } + .get_id() + } + pub fn get_value(&mut self) -> &mut V { + &mut self.table.by_id[self.idx].value + } + pub fn get_by_component(&mut self, component: &OsStr) -> Option> { + if let Some(&idx) = self.table.by_id[self.idx].children.get(component) { + Some(NodeWrapperMut { + table: self.table, + idx, + }) + } else { + None + } } - fn children(self) -> impl Iterator + 'a { + pub fn children(&mut self) -> impl Iterator + '_ { self.table.by_id[self.idx] .children .iter() - .map(|(_, &idx)| idx + ID_MIN) - } - fn get_value(&self) -> &V { - &self.table.by_id[self.idx].value + .map(|(_, &idx)| idx) } } @@ -188,10 +295,10 @@ mod test { ); let root = table.get_root(); let l1 = root.children().next().unwrap(); - let l2 = table.get_by_id(l1).unwrap().children().next().unwrap(); - let l3 = table.get_by_id(l2).unwrap().children().next().unwrap(); - let l4 = table.get_by_id(l3).unwrap().children().next().unwrap(); + let l2 = table.get(l1).unwrap().children().next().unwrap(); + let l3 = table.get(l2).unwrap().children().next().unwrap(); + let l4 = table.get(l3).unwrap().children().next().unwrap(); assert_eq!(l4, 5); - assert_eq!(table.get_by_id(l4).unwrap().get_value(), &10); + assert_eq!(table.get(l4).unwrap().get_value(), &10); } } diff --git a/judger/src/filesystem/entry/mod.rs b/judger/src/filesystem/entry/mod.rs index 9ab4107c..310d15ed 100644 --- a/judger/src/filesystem/entry/mod.rs +++ b/judger/src/filesystem/entry/mod.rs @@ -1,16 +1,19 @@ -use std::{ - ffi::OsString, - io::SeekFrom, - sync::atomic::{AtomicI64, Ordering}, -}; +use std::{ffi::OsString, ops::Deref, sync::Arc}; use bytes::Bytes; use fuse3::FileType; -use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt}; +use tokio::{ + io::{AsyncRead, AsyncSeek}, + sync::{Mutex, OwnedMutexGuard}, +}; -use self::prelude::*; +use self::{ + ro::TarBlock, + rw::MemBlock, + wrapper::{FuseRead, FuseWrite}, +}; -use super::FuseError; +use super::{adj::DeepClone, resource::Resource}; mod ro; mod rw; @@ -20,125 +23,147 @@ mod wrapper; pub const MEMBLOCK_BLOCKSIZE: usize = 4096; pub mod prelude { - pub use super::ro::Entry as ReadEntry; - pub use super::rw::Entry as WriteEntry; pub use super::tar::TarTree; + pub use super::Entry; pub use super::MEMBLOCK_BLOCKSIZE as BLOCKSIZE; - pub use super::{Entry, InoEntry}; } -impl ReadEntry -where - F: AsyncRead + AsyncSeek + Unpin + Send + 'static, -{ - async fn into_write( - value: ReadEntry, - resource: &AtomicI64, - ) -> Result { - if let ReadEntry::File(block) = &value { - let required_space = block.get_size() as i64; - if resource.fetch_sub(required_space, Ordering::AcqRel) < required_space { - return Err(FuseError::OutOfPermit); - } - } - let value = match value { - ReadEntry::SymLink(target) => WriteEntry::SymLink(target), - ReadEntry::HardLink(inode) => WriteEntry::HardLink(inode), - ReadEntry::Directory => WriteEntry::Directory, - ReadEntry::File(mut block) => { - block - .seek(SeekFrom::Start(0)) - .await - .map_err(|_| FuseError::Underlaying)?; - let mut data = Vec::new(); - block - .read_to_end(&mut data) - .await - .map_err(|_| FuseError::Underlaying)?; - WriteEntry::new_data(data) - } - }; - Ok(value) - } +async fn clone_arc(arc: &Arc>) -> Arc> { + let inner = arc.deref(); + let lock = inner.lock().await; + Arc::new(Mutex::new(lock.deref().clone())) } -#[derive(Debug)] +#[derive(Debug, Default)] pub enum Entry where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { - Read(ReadEntry), - Write(WriteEntry), + SymLink(OsString), + HardLink(u64), + #[default] + Directory, + TarFile(Arc>>), + MemFile(Arc>), } -impl Clone for Entry +impl DeepClone for Entry where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { - fn clone(&self) -> Self { + async fn deep_clone(&self) -> Self { match self { - Self::Read(arg0) => Self::Read(arg0.clone()), - Self::Write(arg0) => Self::Write(arg0.clone()), + Self::SymLink(x) => Self::SymLink(x.clone()), + Self::HardLink(x) => Self::HardLink(*x), + Self::Directory => Self::Directory, + Self::TarFile(block) => Self::TarFile(clone_arc(block).await), + Self::MemFile(block) => Self::MemFile(clone_arc(block).await), } } } -impl Default for Entry +impl Entry where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { - fn default() -> Self { - Self::Write(WriteEntry::Directory) + pub fn kind(&self) -> FileType { + match self { + Self::SymLink(_) => FileType::Symlink, + Self::HardLink(_) => FileType::RegularFile, + Self::Directory => FileType::Directory, + Self::TarFile(_) => FileType::RegularFile, + Self::MemFile(_) => FileType::RegularFile, + } + } + pub fn get_size(&self) -> u64 { + match self { + Self::SymLink(x) => x.len() as u64, + Self::HardLink(_) => 0, + Self::Directory => 0, + Self::TarFile(_) | Self::MemFile(_) => 1, + } + } + pub fn get_read_handle(&self) -> Option> { + match self { + Self::TarFile(block) => Some(ReadHandle::TarFile(block.clone())), + Self::MemFile(block) => Some(ReadHandle::MemFile(block.clone())), + _ => None, + } + } + pub fn get_write_handle(&mut self) -> Option> { + match self { + Self::TarFile(block) => { + let tar_block = block.clone(); + let mem_block = Arc::new(Mutex::new(MemBlock::new(Vec::new()))); + *self = Self::MemFile(mem_block.clone()); + Some(WriteHandle::TarFile( + tar_block, + mem_block.try_lock_owned().unwrap(), + )) + } + Self::MemFile(block) => Some(WriteHandle::MemFile(block.clone())), + _ => None, + } } } -#[derive(Debug)] -pub struct InoEntry +pub enum ReadHandle where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { - entry: Entry, - pub inode: u64, + TarFile(Arc>>), + MemFile(Arc>), } -impl InoEntry +impl ReadHandle where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { - pub async fn kind(&self) -> FileType { - match &self.entry { - Entry::Read(read_entry) => read_entry.kind(), - Entry::Write(write_entry) => write_entry.kind(), - } - } - pub async fn read( - &mut self, - offset: u64, - size: u32, - resource: &AtomicI64, - ) -> Result { - if let Entry::Read(entry) = &mut self.entry { - self.entry = Entry::Write(ReadEntry::into_write(entry.clone(), &resource).await?); - } - if let Entry::Write(entry) = &mut self.entry { - entry.read(offset, size).await - } else { - unreachable!() + pub async fn read(&self, offset: u64, size: u32) -> std::io::Result { + match self { + Self::TarFile(block) => { + let mut lock = block.lock().await; + FuseRead(&mut *lock).read(offset, size).await + } + Self::MemFile(block) => { + let mut lock = block.lock().await; + FuseRead(&mut *lock).read(offset, size).await + } } } +} + +pub enum WriteHandle +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ + TarFile(Arc>>, OwnedMutexGuard), + MemFile(Arc>), +} + +impl WriteHandle +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ pub async fn write( - &mut self, + &self, offset: u64, data: &[u8], - resource: &AtomicI64, - ) -> Result { - let required_space = data.len() as i64; - if resource.fetch_sub(required_space, Ordering::AcqRel) < required_space { - return Err(FuseError::OutOfPermit); - } - match &mut self.entry { - Entry::Read(entry) => entry.write(offset, data).await, - Entry::Write(entry) => entry.write(offset, data).await, + resource: &Resource, + ) -> std::io::Result { + match self { + Self::TarFile(tar_block, mem_block) => { + // let mut lock = tar_block.lock().await; + // let mem_block=MemBlock::new(lock.read_all().await.unwrap()); + todo!() + } + Self::MemFile(block) => { + resource + .comsume(data.len() as u32) + .ok_or(std::io::Error::from(std::io::ErrorKind::Other))?; + let mut lock = block.lock().await; + FuseWrite(&mut *lock).write(offset, data).await + } } } } diff --git a/judger/src/filesystem/entry/ro.rs b/judger/src/filesystem/entry/ro.rs index 4b0de9ab..310beba5 100644 --- a/judger/src/filesystem/entry/ro.rs +++ b/judger/src/filesystem/entry/ro.rs @@ -5,14 +5,7 @@ //! //! And we map each type of content to BTreeMap -use std::ffi::OsString; - -use crate::filesystem::{ - macro_::{chain_poll, report_poll}, - FuseError, -}; -use bytes::Bytes; -use fuse3::FileType; +use crate::filesystem::macro_::{chain_poll, report_poll}; use std::{ future::Future, io::{self, SeekFrom}, @@ -22,89 +15,10 @@ use std::{ task::{Context, Poll}, }; use tokio::{ - io::{AsyncRead, AsyncSeek}, + io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt}, sync::{Mutex, OwnedMutexGuard}, }; -use super::wrapper::FuseRead; - -/// Entry from tar file, should be readonly -#[derive(Debug, Default)] -pub enum Entry -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - SymLink(OsString), - HardLink(u64), - #[default] - Directory, - File(TarBlock), -} - -impl Entry -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - #[inline] - pub fn new_dir() -> Self { - Self::default() - } - #[inline] - pub fn new_file(block: TarBlock) -> Self { - Self::File(block) - } - #[inline] - pub fn new_symlink(target: OsString) -> Self { - Self::SymLink(target) - } - #[inline] - pub fn kind(&self) -> FileType { - match self { - Self::SymLink(_) => FileType::Symlink, - Self::HardLink(_) => FileType::RegularFile, - Self::Directory => FileType::Directory, - Self::File(_) => FileType::RegularFile, - } - } - pub async fn read(&mut self, offset: u64, size: u32) -> Result { - // FIXME: follow symlink - if let Self::File(block) = self { - return FuseRead(block).read(offset, size).await; - } - Err(FuseError::IsDir) - } - pub async fn write(&mut self, offset: u64, data: &[u8]) -> Result { - Err(FuseError::Unimplemented) - } -} - -impl Clone for Entry -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - fn clone(&self) -> Self { - match self { - Self::SymLink(arg0) => Self::SymLink(arg0.clone()), - Self::HardLink(arg0) => Self::HardLink(arg0.clone()), - Self::Directory => Self::Directory, - Self::File(arg0) => Self::File(arg0.clone()), - } - } -} - -impl PartialEq for Entry -where - F: AsyncRead + AsyncSeek + Unpin + 'static, -{ - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::SymLink(l0), Self::SymLink(r0)) => l0 == r0, - (Self::File(l0), Self::File(r0)) => l0 == r0, - _ => core::mem::discriminant(self) == core::mem::discriminant(other), - } - } -} - #[derive(Default, Debug)] enum TarStage { Reading(OwnedMutexGuard), @@ -117,6 +31,12 @@ impl TarStage { fn take(&mut self) -> Self { std::mem::take(self) } + fn take_seeking(&mut self) -> OwnedMutexGuard { + if let Self::Seeking(locked) = self.take() { + return locked; + } + unreachable!("") + } } /// A block in tar file, should be readonly @@ -181,6 +101,13 @@ where pub fn get_size(&self) -> u64 { self.size } + pub async fn read_all(&self) -> std::io::Result> { + let mut buf = Vec::with_capacity(self.size as usize); + let mut block = self.clone(); + block.seek(SeekFrom::Start(0)).await?; + block.read_to_end(&mut buf).await?; + Ok(buf) + } #[cfg(test)] fn from_raw(file: F, start: u64, size: u64) -> Self { Self { @@ -221,8 +148,8 @@ where ))); } let original_size = buf.filled().len(); - match self.stage.take() { - TarStage::Reading(mut locked) => { + match &mut self.stage { + TarStage::Reading(ref mut locked) => { report_poll!(chain_poll!(pin!(locked.deref_mut()).poll_read(cx, buf))); let read_byte = (buf.filled().len() - original_size) as u64; match read_byte > self.get_remain() { @@ -232,12 +159,13 @@ where } false => self.cursor += read_byte, }; + self.stage.take(); return Poll::Ready(Ok(())); } - TarStage::Seeking(mut locked) => { + TarStage::Seeking(ref mut locked) => { let result = chain_poll!(pin!(locked.deref_mut()).poll_complete(cx)); let read_byte = report_poll!(result); - self.as_mut().stage = TarStage::Reading(locked); + self.as_mut().stage = TarStage::Reading(self.stage.take_seeking()); self.as_mut().cursor = read_byte - self.start; cx.waker().wake_by_ref(); } @@ -283,7 +211,7 @@ where mod test { use std::io::Cursor; - use tokio::io::{AsyncReadExt, BufReader}; + use tokio::io::BufReader; use super::*; #[tokio::test] diff --git a/judger/src/filesystem/entry/rw.rs b/judger/src/filesystem/entry/rw.rs index b34df6d3..33198096 100644 --- a/judger/src/filesystem/entry/rw.rs +++ b/judger/src/filesystem/entry/rw.rs @@ -1,11 +1,5 @@ -use crate::filesystem::{ - macro_::{chain_poll, report_poll}, - FuseError, -}; -use bytes::Bytes; -use fuse3::FileType; +use crate::filesystem::macro_::{chain_poll, report_poll}; use std::{ - ffi::OsString, future::Future, io::{self, SeekFrom}, ops::Deref, @@ -18,59 +12,8 @@ use tokio::{ sync::{Mutex, OwnedMutexGuard}, }; -use super::wrapper::{FuseRead, FuseWrite}; use super::MEMBLOCK_BLOCKSIZE; -#[derive(Default, Clone, Debug)] -pub enum Entry { - SymLink(OsString), - HardLink(u64), - #[default] - Directory, - File(MemBlock), - Removed, -} - -impl Entry { - pub fn new_dir() -> Self { - Self::default() - } - pub fn new_file() -> Self { - Self::File(MemBlock::new(Vec::new())) - } - pub fn new_data(data: Vec) -> Self { - Self::File(MemBlock::new(data)) - } - pub fn new_symlink(target: OsString) -> Self { - Self::SymLink(target) - } - pub fn new_hardlink(inode: u64) -> Self { - Self::HardLink(inode) - } - pub fn kind(&self) -> FileType { - match self { - Self::SymLink(_) => FileType::Symlink, - Self::HardLink(_) => FileType::RegularFile, - Self::Directory => FileType::Directory, - Self::File(_) => FileType::RegularFile, - Self::Removed => FileType::RegularFile, - } - } - pub async fn read(&mut self, offset: u64, size: u32) -> Result { - // FIXME: follow symlink - if let Self::File(block) = self { - return FuseRead(block).read(offset, size).await; - } - Err(FuseError::IsDir) - } - pub async fn write(&mut self, offset: u64, data: &[u8]) -> Result { - if let Self::File(block) = self { - return FuseWrite(block).write(offset, data).await; - } - Err(FuseError::IsDir) - } -} - #[derive(Debug, Default)] enum MemStage { Seeking(OwnedMutexGuard>, SeekFrom), @@ -107,7 +50,7 @@ impl Clone for MemBlock { } impl MemBlock { - fn new(data: Vec) -> Self { + pub fn new(data: Vec) -> Self { Self { data: Arc::new(Mutex::new(data)), cursor: 0, diff --git a/judger/src/filesystem/entry/tar.rs b/judger/src/filesystem/entry/tar.rs index 0e8e6572..14250ec1 100644 --- a/judger/src/filesystem/entry/tar.rs +++ b/judger/src/filesystem/entry/tar.rs @@ -1,5 +1,11 @@ -use super::prelude::ReadEntry; -use std::{ffi::OsString, io::Read, ops::Deref, os::unix::ffi::OsStringExt, path::Path, sync::Arc}; +use std::{ + ffi::OsString, + io::Read, + ops::{Deref, DerefMut}, + os::unix::ffi::OsStringExt, + path::Path, + sync::Arc, +}; #[cfg(test)] use std::io::Cursor; @@ -8,38 +14,45 @@ use tar::{Archive, EntryType}; use tokio::io::BufReader; use tokio::{ fs::File, - io::{AsyncRead, AsyncSeek}, + io::{AsyncRead, AsyncSeek, Result}, sync::Mutex, }; -use crate::{ - filesystem::{table, tree::*}, - Error, -}; +use crate::filesystem::adj::{to_internal_path, AdjTable, DeepClone}; + +use super::{ro::TarBlock, Entry}; -use super::{ro::TarBlock, Entry, InoEntry}; +pub struct TarTree(AdjTable>) +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static; + +impl DeepClone for TarTree +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ + async fn deep_clone(&self) -> Self { + Self(self.0.deep_clone().await) + } +} -impl table::InodeTable>> +impl DerefMut for TarTree where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { - fn new_read_entry(&self, entry: ReadEntry) -> ArcNode> { - let handle = self.allocate(); - let node = Node::new(InoEntry { - entry: Entry::Read(entry), - inode: handle.get_inode(), - }); - handle.consume(node.clone()); - node + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 } } -pub struct TarTree +impl Deref for TarTree where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { - pub tree: Tree>, - pub inode: table::InodeTable>>, + type Target = AdjTable>; + + fn deref(&self) -> &Self::Target { + &self.0 + } } impl Default for TarTree @@ -47,15 +60,9 @@ where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { fn default() -> Self { - let inode = table::InodeTable::default(); - let handle = inode.allocate_root(); - let root = Node::new(InoEntry { - entry: Entry::Read(ReadEntry::new_dir()), - inode: handle.get_inode(), - }); - let tree = Tree::new(root.clone()); - handle.consume(root); - Self { tree, inode } + let mut tree = AdjTable::new(); + tree.insert_root(Entry::Directory); + Self(tree) } } @@ -63,55 +70,38 @@ impl TarTree where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { - pub async fn cloned(&self) -> Self { - let tree = self.tree.cloned(); - let inode = table::InodeTable::default(); - let mut iter = tree.iter(); - - while let Some(entry) = iter.next().await { - let entry_inode = entry.read().await.inode; - inode.clone_update_entry(entry_inode, entry).await; - } - - Self { tree, inode } - } async fn parse_entry( &mut self, entry: tar::Entry<'_, R>, file: &Arc>, - ) -> Result<(), Error> { + ) -> Result<()> { let path = entry.path()?; - let read_entry = match entry.header().entry_type() { + let entry = match entry.header().entry_type() { EntryType::Regular | EntryType::Continuous => { let start = entry.raw_file_position(); let size = entry.size(); - ReadEntry::new_file(TarBlock::new(file.clone(), start, size)) + Entry::TarFile(Arc::new(Mutex::new(TarBlock::new( + file.clone(), + start, + size, + )))) } - EntryType::Symlink => ReadEntry::new_symlink(OsString::from_vec( + EntryType::Symlink => Entry::SymLink(OsString::from_vec( entry.link_name_bytes().unwrap().into_owned(), )), - EntryType::Directory => ReadEntry::new_dir(), + EntryType::Directory => Entry::Directory, _ => { log::warn!("unsupported entry type: {:?}", entry.header().entry_type()); return Ok(()); } }; - let node = self.inode.new_read_entry(read_entry); - match self - .tree - .insert_path_recursive(path, node, || { - self.inode.new_read_entry(ReadEntry::new_dir()) - }) - .await - { - InsertResult::AlreadyExists(_) => Err(Error::InvalidTarball("duplicated entry")), - InsertResult::ParentNotFound => unreachable!(), - _ => Ok(()), - } + self.0 + .insert_by_path(to_internal_path(&path), || Entry::Directory, entry); + Ok(()) } // FIXME: this block - pub async fn inner_new(file: F, std_file: impl Read + Send + 'static) -> Result { + pub async fn inner_new(file: F, std_file: impl Read + Send + 'static) -> Result { let mut archive = Archive::new(std_file); let file = Arc::new(Mutex::new(file)); @@ -125,7 +115,7 @@ where } impl TarTree { - pub async fn new(path: impl AsRef + Clone) -> Result { + pub async fn new(path: impl AsRef + Clone) -> Result { let file = File::open(path.clone()).await?; let std_file = File::open(path).await?.into_std().await; Self::inner_new(file, std_file).await @@ -137,7 +127,7 @@ impl TarTree>> where T: AsRef<[u8]> + Send + Unpin + Clone + 'static, { - pub async fn test_new(content: T) -> Result { + pub async fn test_new(content: T) -> Result { let file = BufReader::new(Cursor::new(content.clone())); let std_file = BufReader::new(Cursor::new(content)).into_inner(); Self::inner_new(file, std_file).await @@ -148,41 +138,17 @@ where mod test { use super::*; use fuse3::FileType; - use std::{ffi::OsStr, ops::Deref}; - use tokio::io::AsyncReadExt; - - macro_rules! assert_content { - ($entry:expr, $content:expr) => { - if let Entry::File(ref mut file) = $entry { - let mut buf = vec![0_u8; $content.len()]; - file.read_exact(&mut buf).await.unwrap(); - assert_eq!(&buf, $content); - } else { - panic!("entry is not a file") - } - }; - } + macro_rules! assert_kind { ($tree:expr,$path:expr, $kind:ident) => {{ - let node = $tree.tree.get_by_path($path).await.unwrap(); - let locked = node.read().await; - let entry = locked.deref().deref(); - assert_eq!(entry.kind().await, FileType::$kind, "entry: {:?}", entry); + let node = $tree + .get_by_path(to_internal_path(Path::new($path))) + .unwrap(); + let entry = node; + assert_eq!(entry.get_value().kind(), FileType::$kind); }}; } - // #[tokio::test] - // async fn single_file_map() { - // let content = include_bytes!("../../../test/single_file.tar"); - - // let tree = TarTree::test_new(content).await.unwrap(); - - // let file=tree.tree.get_root().read().await.get_by_component(OsStr::new("single_file.txt")).unwrap(); - - // let single_file = map.0.get_mut(OsStr::new("single_file.txt")).unwrap(); - - // assert_content!(single_file, b"hello world"); - // } #[tokio::test] async fn nested_map() { let content = include_bytes!("../../../test/nested.tar"); @@ -193,8 +159,5 @@ mod test { assert_kind!(tree, "nest/a.txt", RegularFile); assert_kind!(tree, "nest/b.txt", RegularFile); assert_kind!(tree, "o.txt", RegularFile); - // assert_content!(map.tree.get_mut(OsStr::new("nest/a.txt")).unwrap(), b"a"); - // assert_content!(map.tree.get_mut(OsStr::new("nest/b.txt")).unwrap(), b"b"); - // assert_content!(map.tree.get_mut(OsStr::new("o.txt")).unwrap(), b"o"); } } diff --git a/judger/src/filesystem/entry/wrapper.rs b/judger/src/filesystem/entry/wrapper.rs index 90aa0871..7aa590bf 100644 --- a/judger/src/filesystem/entry/wrapper.rs +++ b/judger/src/filesystem/entry/wrapper.rs @@ -3,8 +3,6 @@ use std::io::SeekFrom; use bytes::Bytes; use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, AsyncWrite, AsyncWriteExt}; -use crate::filesystem::FuseError; - pub struct FuseRead<'a, W>(pub &'a mut W) where W: AsyncRead + AsyncSeek + Clone + Unpin; @@ -13,24 +11,18 @@ impl<'a, W> FuseRead<'a, W> where W: AsyncRead + AsyncSeek + Clone + Unpin, { - pub async fn read(&mut self, offset: u64, size: u32) -> Result { + pub async fn read(&mut self, offset: u64, size: u32) -> std::io::Result { let mut buf = Vec::with_capacity(size as usize); - self.0 - .seek(SeekFrom::Start(offset)) - .await - .map_err(|_| FuseError::Eof)?; + self.0.seek(SeekFrom::Start(offset)).await?; self.0 .clone() .take(size as u64) .read_to_end(&mut buf) - .await - .map_err(|_| FuseError::Eof)?; + .await?; + + self.0.seek(SeekFrom::Current(buf.len() as i64)).await?; - self.0 - .seek(SeekFrom::Current(buf.len() as i64)) - .await - .unwrap(); Ok(buf.try_into().unwrap()) } } @@ -43,15 +35,11 @@ impl<'a, W> FuseWrite<'a, W> where W: AsyncWrite + AsyncSeek + Clone + Unpin, { - pub async fn write(&mut self, offset: u64, data: &[u8]) -> Result { - if data.len() >= u32::MAX as usize { - return Err(FuseError::OutOfRange); - } - self.0 - .seek(SeekFrom::Start(offset)) - .await - .map_err(|_| FuseError::Eof)?; - self.0.write_all(data).await.unwrap(); + pub async fn write(&mut self, offset: u64, data: &[u8]) -> std::io::Result { + assert!(data.len() < (u32::MAX - 1) as usize); + self.0.seek(SeekFrom::Start(offset)).await?; + + self.0.write_all(data).await?; Ok(data.len() as u32) } } diff --git a/judger/src/filesystem/error.rs b/judger/src/filesystem/error.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/judger/src/filesystem/error.rs @@ -0,0 +1 @@ + diff --git a/judger/src/filesystem/mod.rs b/judger/src/filesystem/mod.rs index 77d8f731..bf329472 100644 --- a/judger/src/filesystem/mod.rs +++ b/judger/src/filesystem/mod.rs @@ -1,54 +1,10 @@ //! Filesystem module that is mountable(actuall mount and //! is accessible for user in this operation system) -//! -//! - mod adapter; mod adj; mod entry; +mod error; mod macro_; -mod reply; -mod table; -mod tree; +mod resource; pub use entry::prelude::*; -use tokio::sync::broadcast::error; - -#[derive(thiserror::Error, Debug)] -pub enum FuseError { - #[error("not a readable file")] - IsDir, - #[error("end of file")] - Eof, - #[error("not a dir")] - NotDir, - #[error("out of resource")] - OutOfPermit, - #[error("number too large")] - OutOfRange, - #[error("unimplemented")] - Unimplemented, - #[error("missed inode")] - InodeNotFound, - #[error("missed handle")] - HandleNotFound, - #[error("underlaying file error")] - Underlaying, -} - -impl From for fuse3::Errno { - fn from(value: FuseError) -> Self { - log::warn!("FUSE driver broken: {}", value); - match value { - FuseError::IsDir => libc::EISDIR, - FuseError::NotDir => libc::ENOTDIR, - FuseError::Eof => libc::EOF, - FuseError::OutOfPermit => libc::ENOMEM, - _ => { - log::warn!("FUSE driver broken: {}", value); - libc::ENOMEM - } - } - .into() - } -} diff --git a/judger/src/filesystem/resource.rs b/judger/src/filesystem/resource.rs new file mode 100644 index 00000000..d19b88eb --- /dev/null +++ b/judger/src/filesystem/resource.rs @@ -0,0 +1,17 @@ +use std::sync::atomic::{AtomicU64, Ordering}; + +pub struct Resource(AtomicU64); + +impl Resource { + pub fn new(cap: u64) -> Self { + Self(AtomicU64::new(cap)) + } + pub fn comsume(&self, size: u32) -> Option<()> { + let a = self.0.fetch_sub(size as u64, Ordering::AcqRel); + if (a | (1 << 63)) != 0 { + None + } else { + Some(()) + } + } +} diff --git a/judger/src/filesystem/table/inode.rs b/judger/src/filesystem/table/inode.rs deleted file mode 100644 index abe51279..00000000 --- a/judger/src/filesystem/table/inode.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::{ - collections::BTreeMap, - mem::ManuallyDrop, - sync::atomic::{AtomicU64, Ordering}, -}; - -use futures_core::{future::BoxFuture, Future}; -use spin::RwLock; - -const MAX_INODE: u64 = 1 << 63; - -pub struct InodeHandle<'a, E: Clone> { - inode: u64, - table: &'a InodeTable, -} - -impl<'a, E: Clone> Drop for InodeHandle<'a, E> { - fn drop(&mut self) { - panic!("InodeHandle should be consumed by allocate") - } -} - -impl<'a, E: Clone> InodeHandle<'a, E> { - pub fn get_inode(&self) -> u64 { - self.inode - } - pub fn consume(self, value: E) { - assert!(self.table.inode.write().insert(self.inode, value).is_none()); - ManuallyDrop::new(self); - } -} - -pub struct InodeTable { - inode: RwLock>, - inode_generator: AtomicU64, -} - -impl Default for InodeTable { - fn default() -> Self { - Self { - inode: RwLock::new(BTreeMap::new()), - inode_generator: AtomicU64::new(2), - } - } -} - -impl InodeTable { - /// clone the inode table - /// - /// Note: it only deep clone geberator and inode, not the entry - pub fn cloned(&self) -> Self { - Self { - inode: RwLock::new(self.inode.read().clone()), - inode_generator: AtomicU64::new(self.inode_generator.load(Ordering::SeqCst)), - } - } - /// allocate root inode with handle - pub fn allocate_root(&self) -> InodeHandle { - InodeHandle { - inode: 1, - table: self, - } - } - /// allocate inode with handle - pub fn allocate(&self) -> InodeHandle { - let inode = self - .inode_generator - .fetch_add(1, std::sync::atomic::Ordering::AcqRel); - log::trace!("allocate inode: {}", inode); - InodeHandle { inode, table: self } - } - /// update(clone) entry by providing new entry with inode - pub async fn clone_update_entry(&self, inode: u64, entry: E) { - self.inode.write().insert(inode, entry); - } - /// get entry by inode - pub fn get(&self, inode: u64) -> Option { - self.inode.read().get(&inode).cloned() - } - /// deallocate inode - pub fn remove(&self, inode: u64) { - // FIXME: inode should be reused, currently it's a non-op - } - pub fn get_free_inode(&self) -> u64 { - MAX_INODE - self.get_used_inode() - } - #[inline] - pub fn get_used_inode(&self) -> u64 { - self.inode_generator.load(Ordering::Acquire) - } -} diff --git a/judger/src/filesystem/table/mod.rs b/judger/src/filesystem/table/mod.rs deleted file mode 100644 index 6fa25ac4..00000000 --- a/judger/src/filesystem/table/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod handle; -mod inode; - -pub use handle::HandleTable; -pub use inode::{InodeHandle, InodeTable}; diff --git a/judger/src/filesystem/tree.rs b/judger/src/filesystem/tree.rs deleted file mode 100644 index 4732a21e..00000000 --- a/judger/src/filesystem/tree.rs +++ /dev/null @@ -1,368 +0,0 @@ -use std::{ - collections::HashMap, - ffi::{OsStr, OsString}, - ops::{Deref, DerefMut}, - path::{Component, Path}, - sync::Arc, -}; - -use futures_core::{future::BoxFuture, Future}; -use tokio::sync::RwLock; - -pub fn arc_lock(x: T) -> Arc> { - Arc::new(RwLock::new(x)) -} - -fn to_internal_path<'a>(path: &'a impl AsRef) -> impl Iterator { - path.as_ref() - .components() - .filter_map(|component| match component { - Component::Prefix(x) => unreachable!("Windows only: {:?}", x), - Component::RootDir | Component::CurDir | Component::ParentDir => None, - Component::Normal(x) => Some(x), - }) -} - -#[derive(Debug)] -pub enum InsertResult { - AlreadyExists(N), - Inserted(Option), - ParentNotFound, - IsRoot, -} - -impl PartialEq for InsertResult> { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::AlreadyExists(l0), Self::AlreadyExists(r0)) => Arc::ptr_eq(l0, r0), - (Self::Inserted(l0), Self::Inserted(r0)) => match (l0, r0) { - (Some(l0), Some(r0)) => Arc::ptr_eq(l0, r0), - (None, None) => true, - _ => false, - }, - _ => core::mem::discriminant(self) == core::mem::discriminant(other), - } - } -} - -#[derive(Debug)] -pub struct Node { - children: HashMap>, - value: V, -} - -impl Deref for Node { - type Target = V; - fn deref(&self) -> &Self::Target { - &self.value - } -} - -impl DerefMut for Node { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.value - } -} - -pub type ArcNode = Arc>>; - -impl Node { - pub fn new(value: V) -> ArcNode { - Arc::new(RwLock::new(Self { - children: Default::default(), - value, - })) - } - fn arc_clone<'a>(x: &'a mut ArcNode) -> BoxFuture<'a, ()> - where - V: Clone + Send + Sync + 'static, - { - Box::pin(async move { - let mut node = { - let lock = x.deref().read().await; - let value = lock.deref().value.clone(); - let children = lock.deref().children.clone(); - Node { value, children } - }; - for (_, mut v) in node.children.iter_mut() { - Self::arc_clone(&mut v).await; - } - *x = arc_lock(node); - }) - } - pub fn list_child(&self) -> impl Iterator)> { - self.children - .iter() - .map(|(a, b)| (a.as_os_str(), b.clone())) - } - /// get child node by component - #[inline] - pub fn get_by_component(&self, component: &OsStr) -> Option> { - self.children.get(component).cloned() - } - /// insert child node by component - /// - /// return the old child node if it exists - pub fn insert_component( - &mut self, - component: OsString, - value: ArcNode, - ) -> Option> { - self.children.insert(component, value) - } - pub fn remove_component(&mut self, component: &OsStr) -> Option> { - self.children.remove(component) - } - /// get child node by path - pub async fn get_by_path( - self_: ArcNode, - path: impl Iterator, - ) -> Option> { - let mut root = self_; - let path = path.peekable(); - for component in path { - let child = root.read().await.children.get(component)?.clone(); - root = child; - } - Some(root) - } - async fn insert_closure( - self_: ArcNode, - path: impl Iterator, - value: ArcNode, - mut f: F, - ) -> InsertResult> - where - F: FnMut(&OsStr, &mut Node) -> Result, InsertResult>>, - { - let path = path.collect::>(); - let mut root = self_; - if path.is_empty() { - return InsertResult::IsRoot; - } - let (last, path) = path.split_last().unwrap(); - for component in path { - let child = match f(component, root.write().await.deref_mut()) { - Ok(x) => x, - Err(x) => { - return x; - } - }; - root = child; - } - let mut root = root.write().await; - InsertResult::Inserted(root.insert_component(last.to_os_string(), value)) - } -} - -/// Path tree with partial locking -pub struct Tree(ArcNode); - -impl Default for Tree -where - V: Default, -{ - fn default() -> Self { - Self(Node::new(Default::default())) - } -} - -impl Tree { - pub fn new(root: ArcNode) -> Self { - Tree(root) - } - pub fn cloned(&self) -> Self { - Self(self.0.clone()) - } - pub async fn clone(&self) -> Self - where - V: Clone + Send + Sync + 'static, - { - let mut new_node = self.0.clone(); - Node::arc_clone(&mut new_node).await; - Self(new_node) - } - pub async fn get_by_path(&self, path: impl AsRef) -> Option> { - Node::get_by_path(self.0.clone(), to_internal_path(&path)).await - } - pub async fn insert_path( - &self, - path: impl AsRef, - value: ArcNode, - ) -> InsertResult> { - let path = to_internal_path(&path); - Node::insert_closure(self.0.clone(), path, value, |component, root| { - match root.children.get(component) { - Some(child) => Ok(child.clone()), - None => return Err(InsertResult::ParentNotFound), - } - }) - .await - } - pub fn get_root(&self) -> ArcNode { - self.0.clone() - } - pub async fn insert_path_recursive( - &self, - path: impl AsRef, - value: ArcNode, - mut default: F, - ) -> InsertResult> - where - F: FnMut() -> ArcNode, - { - let path = to_internal_path(&path); - Node::insert_closure(self.0.clone(), path, value, |component, root| { - Ok(root - .children - .entry(component.to_os_string()) - .or_insert_with(|| default()) - .clone()) - }) - .await - } - pub fn iter(&self) -> TreeIter { - TreeIter::new(self.0.clone()) - } -} - -pub struct TreeIter { - stack: Vec>, -} - -impl TreeIter { - fn new(root: ArcNode) -> Self { - Self { stack: vec![root] } - } - pub async fn next(&mut self) -> Option> { - let node = self.stack.pop()?; - { - let lock = node.read().await; - for (_, child) in lock.children.iter() { - self.stack.push(child.clone()); - } - } - Some(node) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[tokio::test] - async fn insert_parent_not_found() { - let tree = Tree::new(Node::new(0)); - assert_eq!( - tree.insert_path("a/b/c", Node::new(1)).await, - InsertResult::ParentNotFound - ); - } - #[tokio::test] - async fn insert_is_root() { - let tree = Tree::new(Node::new(0)); - assert_eq!( - tree.insert_path("", Node::new(1)).await, - InsertResult::IsRoot - ); - } - #[tokio::test] - async fn insert() { - let tree = Tree::new(Node::new(0)); - macro_rules! insert { - ($path:expr, $val:expr) => { - assert_eq!( - tree.insert_path($path, Node::new($val)).await, - InsertResult::Inserted(None) - ); - }; - } - macro_rules! lookup { - ($path:expr,$val:expr) => { - assert_eq!( - tree.get_by_path($path).await.unwrap().read().await.value, - $val - ); - }; - } - - insert!("a", 1); - insert!("a/u", 2); - insert!("a/h", 3); - insert!("a/h/f", 4); - lookup!("/", 0); - lookup!("a", 1); - lookup!("a/u", 2); - lookup!("a/h", 3); - lookup!("a/h/f", 4); - } - #[tokio::test(flavor = "multi_thread", worker_threads = 8)] - async fn multi_lookup() { - let tree = Tree::new(Node::new(0)); - tree.insert_path("a", Node::new(1)).await; - tree.insert_path("a/u", Node::new(2)).await; - tree.insert_path("a/h", Node::new(3)).await; - tree.insert_path("a/h/f", Node::new(4)).await; - async fn lookup(tree: &Tree, path: &str, val: i32) { - for _ in 0..30 { - let tree = tree.cloned(); - let path = path.to_string(); - tokio::spawn(async move { - for _ in 0..300 { - assert_eq!( - tree.get_by_path(&path).await.unwrap().read().await.value, - val - ); - } - }) - .await - .unwrap(); - } - } - tokio::join!( - lookup(&tree, "a", 1), - lookup(&tree, "a/u", 2), - lookup(&tree, "a/h", 3), - lookup(&tree, "a/h/f", 4) - ); - } - #[tokio::test] - async fn deep_clone() { - let tree_a = Tree::new(Node::new(0)); - let tree_b = tree_a.clone().await; - tree_a.insert_path("a", Node::new(1)).await; - tree_a.insert_path("a/u", Node::new(2)).await; - tree_a.insert_path("a/h", Node::new(3)).await; - tree_a.insert_path("a/h/f", Node::new(4)).await; - assert!(tree_a.get_by_path("a/h/f").await.is_some()); - assert!(tree_b.get_by_path("a/h/f").await.is_none()); - assert!(tree_b.get_by_path("a").await.is_none()); - } - #[cfg(taregt_os = "windows")] - #[tokio::test] - #[should_panic] - async fn windows() { - let tree = Tree::new(Node::new(0)); - tree.insert_path("C:\\a", 1).await; - } - #[tokio::test] - async fn iter() { - let tree = Tree::new(Node::new(0)); - tree.insert_path("a", Node::new(1)).await; - tree.insert_path("a/u", Node::new(2)).await; - tree.insert_path("a/u/f", Node::new(3)).await; - tree.insert_path("a/u/f/h", Node::new(4)).await; - let mut iter = tree.iter(); - macro_rules! check_next { - ($e:expr) => { - assert_eq!(iter.next().await.unwrap().read().await.value, $e); - }; - } - check_next!(0); - check_next!(1); - check_next!(2); - check_next!(3); - check_next!(4); - assert!(iter.next().await.is_none()); - } -} diff --git a/judger/src/main.rs b/judger/src/main.rs index e07477d9..53cfe9f6 100644 --- a/judger/src/main.rs +++ b/judger/src/main.rs @@ -1,10 +1,11 @@ mod config; -mod error; mod filesystem; mod language; mod sandbox; mod semaphore; +use std::sync::Arc; + pub use config::CONFIG; -pub use error::Error; + fn main() {} diff --git a/judger/src/sandbox/error.rs b/judger/src/sandbox/error.rs new file mode 100644 index 00000000..420e4734 --- /dev/null +++ b/judger/src/sandbox/error.rs @@ -0,0 +1,8 @@ +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("{0}")] + CgroupError(#[from] cgroups_rs::error::Error), + #[error("io error")] + IoError(#[from] std::io::Error), +} +pub type Result = std::result::Result; diff --git a/judger/src/sandbox/mod.rs b/judger/src/sandbox/mod.rs index 20d09953..572c6883 100644 --- a/judger/src/sandbox/mod.rs +++ b/judger/src/sandbox/mod.rs @@ -1,10 +1,11 @@ +mod error; mod monitor; mod process; use std::{ffi::OsStr, path::Path, time::Duration}; pub use self::monitor::{Cpu, Memory}; - +pub use error::Error; /// Context of the sandbox /// /// define resource limit and filesystem is out of the scope of `filesystem` diff --git a/judger/src/sandbox/monitor/mem_cpu.rs b/judger/src/sandbox/monitor/mem_cpu.rs index 61bbd399..6f11882d 100644 --- a/judger/src/sandbox/monitor/mem_cpu.rs +++ b/judger/src/sandbox/monitor/mem_cpu.rs @@ -50,7 +50,7 @@ impl Drop for Monitor { impl Monitor { /// create a new limiter and mount at given path - pub fn new((mem, cpu): MemAndCpu) -> Result { + pub fn new((mem, cpu): MemAndCpu) -> Result { let cg_name = format!("mdoj.{}", CG_PATH_COUNTER.fetch_add(1, Ordering::AcqRel)); let cgroup = Arc::new( CgroupBuilder::new(&cg_name) diff --git a/judger/src/sandbox/monitor/mod.rs b/judger/src/sandbox/monitor/mod.rs index ac60fd53..80a8966e 100644 --- a/judger/src/sandbox/monitor/mod.rs +++ b/judger/src/sandbox/monitor/mod.rs @@ -11,12 +11,12 @@ use std::{fmt::Display, sync::atomic::AtomicUsize, time::Duration}; pub use stat::*; use tokio::io::AsyncRead; -use crate::Error; -type Result = std::result::Result; use hier::*; use self::output::Output; +use super::Error; + lazy_static::lazy_static! { pub static ref CGROUP_V2:bool=hier::MONITER_KIND.heir().v2(); } @@ -70,7 +70,6 @@ impl Display for MonitorKind { } } -// FIXME: composite /// composite monitor pub struct StatMonitor { mem_cpu: mem_cpu::Monitor, @@ -148,7 +147,7 @@ impl Default for StatMonitorBuilder

{ } impl StatMonitorBuilder

{ - pub fn mem_cpu(mut self, mem_cpu: MemAndCpu) -> Result { + pub fn mem_cpu(mut self, mem_cpu: MemAndCpu) -> Result { self.mem_cpu = Some(mem_cpu::Monitor::new(mem_cpu)?); Ok(self) } @@ -160,7 +159,7 @@ impl StatMonitorBuilder

{ self.walltime = Some(walltime::Monitor::new(walltime)); self } - pub fn build(self) -> Result> { + pub fn build(self) -> Result, Error> { Ok(StatMonitor { mem_cpu: self .mem_cpu diff --git a/judger/src/sandbox/monitor/output.rs b/judger/src/sandbox/monitor/output.rs index b15c30a6..81a563a4 100644 --- a/judger/src/sandbox/monitor/output.rs +++ b/judger/src/sandbox/monitor/output.rs @@ -3,7 +3,7 @@ use std::{pin::Pin, task::*}; use futures_core::Future; use tokio::io::*; -use super::{Result, *}; +use crate::sandbox::monitor::MonitorKind; pub type Output = u64; diff --git a/judger/src/sandbox/monitor/wrapper.rs b/judger/src/sandbox/monitor/wrapper.rs index b182aaf2..6643835d 100644 --- a/judger/src/sandbox/monitor/wrapper.rs +++ b/judger/src/sandbox/monitor/wrapper.rs @@ -1,5 +1,4 @@ -use super::hier::*; -use super::stat::*; +use super::{hier::*, stat::*}; use cgroups_rs::{cpu::CpuController, cpuacct::CpuAcctController, memory::MemController, Cgroup}; use std::ops::Deref; diff --git a/judger/src/sandbox/process/process.rs b/judger/src/sandbox/process/process.rs index f3bf63d8..888f77cf 100644 --- a/judger/src/sandbox/process/process.rs +++ b/judger/src/sandbox/process/process.rs @@ -1,15 +1,10 @@ -use super::{monitor::*, Context}; -use crate::sandbox::Filesystem; -use crate::Error; +use super::{corpse::Corpse, error::Error, monitor::*, nsjail::*, Context, Filesystem}; use std::process::Stdio; use tokio::{ io::{self, AsyncWriteExt, DuplexStream}, process::*, time, }; - -use super::{corpse::Corpse, nsjail::*}; - /// A unlaunched process that is mounted with a filesystem struct MountedProcess { context: C, @@ -81,6 +76,7 @@ impl Process { pub fn new(context: C) -> Result { MonitoredProcess::new(context).map(Into::into) } + /// spawn a raw process fn spawn_raw_process(&mut self) -> Result { let mut cmd = Command::new(NSJAIL_PATH); cmd.kill_on_drop(true); diff --git a/judger/src/semaphore.rs b/judger/src/semaphore.rs index 0f727b71..cf79f867 100644 --- a/judger/src/semaphore.rs +++ b/judger/src/semaphore.rs @@ -6,7 +6,6 @@ use std::{ }, }; -use crate::Error as CrateError; use spin::Mutex; use tokio::sync::oneshot::*; @@ -18,14 +17,14 @@ pub enum Error { ImpossibleResourceCondition, } -impl From for CrateError { - fn from(value: Error) -> CrateError { - match value { - Error::MaxWaitReached => CrateError::Insufficient("queuing quota"), - Error::ImpossibleResourceCondition => CrateError::Insufficient("memory"), - } - } -} +// impl From for CrateError { +// fn from(value: Error) -> CrateError { +// match value { +// Error::MaxWaitReached => CrateError::Insufficient("queuing quota"), +// Error::ImpossibleResourceCondition => CrateError::Insufficient("memory"), +// } +// } +// } struct SemaphoreInner { permits: atomic::AtomicU64, From a84ba278864bb0a7b23e3b23882c3c2ea11e4cda Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Fri, 10 May 2024 11:39:10 +0800 Subject: [PATCH 22/38] refactor(Judger): :fire: use direct sync function to produce future --- judger/src/filesystem/adapter/error.rs | 2 + judger/src/filesystem/adapter/fuse.rs | 123 ++++-- judger/src/filesystem/adapter/handle.rs | 25 +- judger/src/filesystem/adapter/mod.rs | 2 +- judger/src/filesystem/adapter/reply.rs | 40 +- judger/src/filesystem/adapter/template.rs | 4 +- judger/src/filesystem/entry/mod.rs | 143 +++---- judger/src/filesystem/entry/ro.rs | 321 ++++++-------- judger/src/filesystem/entry/rw.rs | 459 +++++++++++---------- judger/src/filesystem/entry/tar.rs | 14 +- judger/src/filesystem/entry/wrapper.rs | 45 -- judger/src/filesystem/macro_.rs | 25 -- judger/src/filesystem/mod.rs | 3 +- judger/src/filesystem/resource.rs | 2 +- judger/src/filesystem/{adj.rs => table.rs} | 30 +- judger/test/helloworld.txt | 1 + 16 files changed, 559 insertions(+), 680 deletions(-) delete mode 100644 judger/src/filesystem/entry/wrapper.rs delete mode 100644 judger/src/filesystem/macro_.rs rename judger/src/filesystem/{adj.rs => table.rs} (92%) create mode 100644 judger/test/helloworld.txt diff --git a/judger/src/filesystem/adapter/error.rs b/judger/src/filesystem/adapter/error.rs index 239e6ac9..2df5faa5 100644 --- a/judger/src/filesystem/adapter/error.rs +++ b/judger/src/filesystem/adapter/error.rs @@ -24,6 +24,8 @@ pub enum FuseError { impl From for fuse3::Errno { fn from(value: FuseError) -> Self { + #[cfg(test)] + log::warn!("FUSE driver return result: {}", value); match value { FuseError::IsDir => libc::EISDIR, FuseError::NotDir => libc::ENOTDIR, diff --git a/judger/src/filesystem/adapter/fuse.rs b/judger/src/filesystem/adapter/fuse.rs index 9bb7ca13..fdfaef21 100644 --- a/judger/src/filesystem/adapter/fuse.rs +++ b/judger/src/filesystem/adapter/fuse.rs @@ -3,9 +3,10 @@ use std::{ffi::OsStr, num::NonZeroU32, path::Path, sync::Arc}; use futures_core::Future; use spin::Mutex; use tokio::io::{AsyncRead, AsyncSeek}; +use tokio::sync::Mutex as AsyncMutex; use crate::{ - filesystem::{resource::Resource, TarTree, BLOCKSIZE}, + filesystem::{resource::Resource, Entry, TarTree, BLOCKSIZE}, semaphore::Permit, }; @@ -20,7 +21,7 @@ pub struct Filesystem where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { - handle_table: HandleTable, + handle_table: HandleTable>>, tree: Mutex>, resource: Arc, _permit: Permit, @@ -80,17 +81,9 @@ where async move { let tree = self.tree.lock(); let node = tree.get(parent as usize).ok_or(FuseError::InvaildIno)?; - log::info!( - "parent name: {}", - String::from_utf8_lossy(node.get_name().as_encoded_bytes()) - ); - log::info!( - "lookup name: {}", - String::from_utf8_lossy(name.as_encoded_bytes()) - ); let entry = node.get_by_component(name).ok_or(FuseError::InvalidPath)?; // FIXME: unsure about the inode - Ok(reply_entry(req, entry.get_value(), parent)) + Ok(reply_entry(&req, entry.get_value(), parent)) } } fn forget(&self, _: Request, inode: u64, _: u64) -> impl Future + Send { @@ -127,7 +120,9 @@ where if node.get_value().kind() != FileType::Directory { return Err(FuseError::NotDir.into()); } - let fh = self.handle_table.add(node.get_id()); + let fh = self + .handle_table + .add(AsyncMutex::new(node.get_value().clone())); Ok(ReplyOpen { fh, flags: 0 }) } } @@ -140,7 +135,12 @@ where async move { let tree = self.tree.lock(); let entry = tree.get(inode as usize).ok_or(FuseError::InvaildIno)?; - let fh = self.handle_table.add(entry.get_id()); + if entry.get_value().kind() == FileType::Directory { + return Err(FuseError::IsDir.into()); + } + let fh = self + .handle_table + .add(AsyncMutex::new(entry.get_value().clone())); Ok(ReplyOpen { fh, flags: 0 }) } } @@ -215,12 +215,14 @@ where let entries = vec![ Ok(dir_entry_plus( + &req, OsStr::new(".").to_os_string(), node.get_value(), node.get_id() as u64, 1, )), Ok(dir_entry_plus( + &req, OsStr::new("..").to_os_string(), parent_node.get_value(), parent_node.get_id() as u64, @@ -234,6 +236,7 @@ where .map(|(offset, inode)| { let node = tree.get(inode).unwrap(); dir_entry_plus( + &req, node.get_name().to_os_string(), node.get_value(), inode as u64, @@ -259,20 +262,13 @@ where size: u32, ) -> impl Future> + Send { async move { - let handle = { - let tree = self.tree.lock(); - let entry = self.handle_table.get(fh).ok_or(FuseError::HandleNotFound)?; - let node = tree.get(entry).ok_or(FuseError::InvaildIno)?; - let entry = node.get_value(); - entry.get_read_handle() - } - .ok_or(FuseError::IsDir)?; - - handle + let entry = self.handle_table.get(fh).ok_or(FuseError::HandleNotFound)?; + let mut lock = entry.lock().await; + Ok(lock .read(offset, size) .await - .map(|data| ReplyData { data }) - .map_err(Into::into) + .ok_or(Into::::into(FuseError::IsDir))? + .map(|data| ReplyData { data })?) } } fn write( @@ -286,17 +282,16 @@ where flags: u32, ) -> impl Future> + Send { async move { - let handle = { - let mut tree = self.tree.lock(); - let entry = self.handle_table.get(fh).ok_or(FuseError::HandleNotFound)?; - let mut node = tree.get_mut(entry).ok_or(FuseError::InvaildIno)?; - let entry = node.get_value(); - entry.get_write_handle() - } - .ok_or(FuseError::IsDir)?; - let resource = self.resource.clone(); - let written = handle.write(offset, data, &resource).await?; - Ok(ReplyWrite { written }) + let entry = self + .handle_table + .get(fh) + .ok_or(FuseError::HandleNotFound) + .unwrap(); + + Ok(Entry::write(entry, offset, data, &self.resource) + .await + .ok_or_else(|| Into::::into(FuseError::IsDir))? + .map(|written| ReplyWrite { written })?) } } fn access( @@ -305,6 +300,7 @@ where inode: u64, mask: u32, ) -> impl Future> + Send { + // FIXME: only allow current user to access async { Ok(()) } } fn fsync( @@ -346,7 +342,6 @@ where } } } - fn interrupt(&self, req: Request, unique: u64) -> impl Future> + Send { async { Ok(()) } } @@ -361,7 +356,59 @@ where let tree = self.tree.lock(); let entry = tree.get(inode as usize).ok_or(FuseError::InvaildIno)?; // FIXME: unsure about the inode - Ok(reply_attr(entry.get_value(), inode)) + Ok(reply_attr(&req, entry.get_value(), inode)) + } + } + fn setattr( + &self, + req: Request, + inode: Inode, + fh: Option, + set_attr: SetAttr, + ) -> impl Future> + Send { + async move { + let tree = self.tree.lock(); + let node = tree.get(inode as usize).ok_or(FuseError::InvaildIno)?; + Ok(reply_attr(&req, node.get_value(), inode)) + } + } + fn create( + &self, + req: Request, + parent: u64, + name: &OsStr, + mode: u32, + flags: u32, + ) -> impl Future> + Send { + async move { + let mut tree = self.tree.lock(); + let mut parent_node = tree.get_mut(parent as usize).ok_or(FuseError::InvaildIno)?; + if parent_node.get_value().kind() != FileType::Directory { + return Err(FuseError::NotDir.into()); + } + let mut node = parent_node.insert(name.to_os_string(), Entry::new_file()); + + // FIXME: append mode + Ok(reply_created(&req, node.get_value())) + } + } + fn mkdir( + &self, + req: Request, + parent: u64, + name: &OsStr, + mode: u32, + umask: u32, + ) -> impl Future> + Send { + async move { + let mut tree = self.tree.lock(); + let mut parent_node = tree.get_mut(parent as usize).ok_or(FuseError::InvaildIno)?; + if parent_node.get_value().kind() != FileType::Directory { + return Err(FuseError::NotDir.into()); + } + let mut node = parent_node.insert(name.to_os_string(), Entry::Directory); + let ino = node.get_id() as u64; + Ok(reply_entry(&req, node.get_value(), ino)) } } } diff --git a/judger/src/filesystem/adapter/handle.rs b/judger/src/filesystem/adapter/handle.rs index 2bfe5712..ba27b3e6 100644 --- a/judger/src/filesystem/adapter/handle.rs +++ b/judger/src/filesystem/adapter/handle.rs @@ -1,17 +1,20 @@ -use std::{collections::BTreeMap, sync::atomic::AtomicU64}; +use std::{ + collections::BTreeMap, + sync::{atomic::AtomicU64, Arc}, +}; -use spin::RwLock; +use spin::Mutex; -pub struct HandleTable { +pub struct HandleTable { handle_generator: AtomicU64, - table: RwLock>, + table: Mutex>>, } -impl HandleTable { +impl HandleTable { pub fn new() -> Self { Self { handle_generator: AtomicU64::new(1), - table: RwLock::new(BTreeMap::new()), + table: Mutex::new(BTreeMap::new()), } } pub fn add(&self, entry: E) -> u64 { @@ -19,15 +22,15 @@ impl HandleTable { .handle_generator .fetch_add(1, std::sync::atomic::Ordering::AcqRel); log::trace!("allocate handle: {}", handle); - self.table.write().insert(handle, entry); + self.table.lock().insert(handle, Arc::new(entry)); handle } - pub fn get(&self, handle: u64) -> Option { + pub fn get(&self, handle: u64) -> Option> { log::debug!("get handle: {}", handle); - self.table.read().get(&handle).cloned() + self.table.lock().get(&handle).cloned() } - pub fn remove(&self, handle: u64) -> Option { + pub fn remove(&self, handle: u64) -> Option> { log::trace!("deallocate handle: {}", handle); - self.table.write().remove(&handle) + self.table.lock().remove(&handle) } } diff --git a/judger/src/filesystem/adapter/mod.rs b/judger/src/filesystem/adapter/mod.rs index 07c9a920..34d43505 100644 --- a/judger/src/filesystem/adapter/mod.rs +++ b/judger/src/filesystem/adapter/mod.rs @@ -32,7 +32,7 @@ mod test { .unwrap(), ) .await; - let mut mount_handle = filesystem.mount("./.temp/1").await.unwrap(); + let mut mount_handle = filesystem.mount("./.temp/11").await.unwrap(); let handle = &mut mount_handle; tokio::select! { diff --git a/judger/src/filesystem/adapter/reply.rs b/judger/src/filesystem/adapter/reply.rs index 4f935983..957472d3 100644 --- a/judger/src/filesystem/adapter/reply.rs +++ b/judger/src/filesystem/adapter/reply.rs @@ -8,7 +8,10 @@ use tokio::io::{AsyncRead, AsyncSeek}; use crate::filesystem::{Entry, BLOCKSIZE}; +const TTL: Duration = Duration::from_secs(1); + pub fn dir_entry_plus( + req: &Request, name: OsString, entry: &Entry, inode: u64, @@ -23,9 +26,9 @@ where kind: entry.kind(), name, offset, - attr: file_attr(entry, inode), - entry_ttl: Duration::from_secs(30), - attr_ttl: Duration::from_secs(30), + attr: file_attr(req, entry, inode), + entry_ttl: TTL, + attr_ttl: TTL, } } @@ -41,28 +44,28 @@ where } } -pub fn reply_attr(entry: &Entry, inode: u64) -> ReplyAttr +pub fn reply_attr(req: &Request, entry: &Entry, inode: u64) -> ReplyAttr where F: AsyncRead + AsyncSeek + Send + Unpin + 'static, { ReplyAttr { - ttl: Duration::from_secs(30), - attr: file_attr(&entry, inode), + ttl: TTL, + attr: file_attr(req, &entry, inode), } } -pub fn reply_entry(request: Request, entry: &Entry, inode: u64) -> ReplyEntry +pub fn reply_entry(req: &Request, entry: &Entry, inode: u64) -> ReplyEntry where F: AsyncRead + AsyncSeek + Send + Unpin + 'static, { ReplyEntry { - ttl: Duration::from_secs(30), - attr: file_attr(&entry, inode), + ttl: TTL, + attr: file_attr(req, &entry, inode), generation: 0, } } -pub fn file_attr(entry: &Entry, inode: u64) -> FileAttr +pub fn file_attr(req: &Request, entry: &Entry, inode: u64) -> FileAttr where F: AsyncRead + AsyncSeek + Send + Unpin + 'static, { @@ -81,9 +84,22 @@ where | libc::S_IRWXO | libc::S_ISVTX) as u16, nlink: 1, - uid: 0, - gid: 0, + uid: req.uid, + gid: req.gid, rdev: 179 << 16 + 02, blksize: BLOCKSIZE as u32, } } + +pub fn reply_created(req: &Request, entry: &Entry) -> ReplyCreated +where + F: AsyncRead + AsyncSeek + Send + Unpin + 'static, +{ + ReplyCreated { + ttl: TTL, + attr: file_attr(req, entry, 0), + generation: 0, + fh: 0, + flags: 0, + } +} diff --git a/judger/src/filesystem/adapter/template.rs b/judger/src/filesystem/adapter/template.rs index 991edb29..b4d17f0f 100644 --- a/judger/src/filesystem/adapter/template.rs +++ b/judger/src/filesystem/adapter/template.rs @@ -6,7 +6,7 @@ use tokio::{ }; use crate::{ - filesystem::{adj::DeepClone, TarTree}, + filesystem::{table::DeepClone, TarTree}, semaphore::Permit, }; @@ -27,7 +27,7 @@ where Self { tree } } pub async fn as_filesystem(&self, permit: Permit) -> Filesystem { - Filesystem::new(self.tree.deep_clone().await, permit) + Filesystem::new(self.tree.clone(), permit) } } diff --git a/judger/src/filesystem/entry/mod.rs b/judger/src/filesystem/entry/mod.rs index 310d15ed..5c24daec 100644 --- a/judger/src/filesystem/entry/mod.rs +++ b/judger/src/filesystem/entry/mod.rs @@ -1,31 +1,31 @@ -use std::{ffi::OsString, ops::Deref, sync::Arc}; +mod ro; +mod rw; +mod tar; +pub mod prelude { + pub use super::tar::TarTree; + pub use super::Entry; + pub use super::MEMBLOCK_BLOCKSIZE as BLOCKSIZE; +} +use self::{ro::TarBlock, rw::MemBlock}; use bytes::Bytes; use fuse3::FileType; +use std::{ffi::OsString, ops::Deref, sync::Arc}; use tokio::{ io::{AsyncRead, AsyncSeek}, sync::{Mutex, OwnedMutexGuard}, }; -use self::{ - ro::TarBlock, - rw::MemBlock, - wrapper::{FuseRead, FuseWrite}, -}; - -use super::{adj::DeepClone, resource::Resource}; - -mod ro; -mod rw; -mod tar; -mod wrapper; +use super::{table::DeepClone, resource::Resource}; pub const MEMBLOCK_BLOCKSIZE: usize = 4096; -pub mod prelude { - pub use super::tar::TarTree; - pub use super::Entry; - pub use super::MEMBLOCK_BLOCKSIZE as BLOCKSIZE; +pub trait FuseReadTrait { + async fn read(&mut self, offset: u64, size: u32) -> std::io::Result; +} + +pub trait FuseWriteTrait { + async fn write(&mut self, offset: u64, data: &[u8]) -> std::io::Result; } async fn clone_arc(arc: &Arc>) -> Arc> { @@ -34,6 +34,9 @@ async fn clone_arc(arc: &Arc>) -> Arc> { Arc::new(Mutex::new(lock.deref().clone())) } +/// Entry in the filesystem +/// +/// cloning the entry would clone file state #[derive(Debug, Default)] pub enum Entry where @@ -43,21 +46,21 @@ where HardLink(u64), #[default] Directory, - TarFile(Arc>>), - MemFile(Arc>), + TarFile(TarBlock), + MemFile(MemBlock), } -impl DeepClone for Entry +impl Clone for Entry where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { - async fn deep_clone(&self) -> Self { + fn clone(&self) -> Self { match self { - Self::SymLink(x) => Self::SymLink(x.clone()), - Self::HardLink(x) => Self::HardLink(*x), + Self::SymLink(arg0) => Self::SymLink(arg0.clone()), + Self::HardLink(arg0) => Self::HardLink(arg0.clone()), Self::Directory => Self::Directory, - Self::TarFile(block) => Self::TarFile(clone_arc(block).await), - Self::MemFile(block) => Self::MemFile(clone_arc(block).await), + Self::TarFile(arg0) => Self::TarFile(arg0.clone()), + Self::MemFile(arg0) => Self::MemFile(arg0.clone()), } } } @@ -66,6 +69,9 @@ impl Entry where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { + pub fn new_file() -> Self { + Self::MemFile(MemBlock::default()) + } pub fn kind(&self) -> FileType { match self { Self::SymLink(_) => FileType::Symlink, @@ -80,90 +86,33 @@ where Self::SymLink(x) => x.len() as u64, Self::HardLink(_) => 0, Self::Directory => 0, - Self::TarFile(_) | Self::MemFile(_) => 1, + Self::TarFile(x) => x.get_size() as u64, + Self::MemFile(x) => x.get_size(), } } - pub fn get_read_handle(&self) -> Option> { + pub async fn read(&mut self, offset: u64, size: u32) -> Option> { match self { - Self::TarFile(block) => Some(ReadHandle::TarFile(block.clone())), - Self::MemFile(block) => Some(ReadHandle::MemFile(block.clone())), + Self::TarFile(block) => Some(Ok(block.read(offset, size).await.unwrap())), + Self::MemFile(block) => Some(block.read(offset, size).await), _ => None, } } - pub fn get_write_handle(&mut self) -> Option> { - match self { - Self::TarFile(block) => { - let tar_block = block.clone(); - let mem_block = Arc::new(Mutex::new(MemBlock::new(Vec::new()))); - *self = Self::MemFile(mem_block.clone()); - Some(WriteHandle::TarFile( - tar_block, - mem_block.try_lock_owned().unwrap(), - )) - } - Self::MemFile(block) => Some(WriteHandle::MemFile(block.clone())), - _ => None, - } - } -} - -pub enum ReadHandle -where - F: AsyncRead + AsyncSeek + Unpin + Send + 'static, -{ - TarFile(Arc>>), - MemFile(Arc>), -} - -impl ReadHandle -where - F: AsyncRead + AsyncSeek + Unpin + Send + 'static, -{ - pub async fn read(&self, offset: u64, size: u32) -> std::io::Result { - match self { - Self::TarFile(block) => { - let mut lock = block.lock().await; - FuseRead(&mut *lock).read(offset, size).await - } - Self::MemFile(block) => { - let mut lock = block.lock().await; - FuseRead(&mut *lock).read(offset, size).await - } - } - } -} - -pub enum WriteHandle -where - F: AsyncRead + AsyncSeek + Unpin + Send + 'static, -{ - TarFile(Arc>>, OwnedMutexGuard), - MemFile(Arc>), -} - -impl WriteHandle -where - F: AsyncRead + AsyncSeek + Unpin + Send + 'static, -{ pub async fn write( - &self, + self_: Arc>, offset: u64, data: &[u8], resource: &Resource, - ) -> std::io::Result { - match self { - Self::TarFile(tar_block, mem_block) => { - // let mut lock = tar_block.lock().await; - // let mem_block=MemBlock::new(lock.read_all().await.unwrap()); + ) -> Option> { + let mut lock = self_.lock().await; + if resource.comsume(data.len() as u32).is_none() { + return Some(Err(std::io::Error::from(std::io::ErrorKind::Other))); + } + match &mut *lock { + Self::MemFile(block) => Some(block.write(offset, data).await), + Self::TarFile(block) => { todo!() } - Self::MemFile(block) => { - resource - .comsume(data.len() as u32) - .ok_or(std::io::Error::from(std::io::ErrorKind::Other))?; - let mut lock = block.lock().await; - FuseWrite(&mut *lock).write(offset, data).await - } + _ => None, } } } diff --git a/judger/src/filesystem/entry/ro.rs b/judger/src/filesystem/entry/ro.rs index 310beba5..b6c71e6c 100644 --- a/judger/src/filesystem/entry/ro.rs +++ b/judger/src/filesystem/entry/ro.rs @@ -5,39 +5,16 @@ //! //! And we map each type of content to BTreeMap -use crate::filesystem::macro_::{chain_poll, report_poll}; use std::{ - future::Future, io::{self, SeekFrom}, - ops::DerefMut, - pin::{pin, Pin}, sync::Arc, - task::{Context, Poll}, }; use tokio::{ io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt}, - sync::{Mutex, OwnedMutexGuard}, + sync::Mutex, }; -#[derive(Default, Debug)] -enum TarStage { - Reading(OwnedMutexGuard), - Seeking(OwnedMutexGuard), - #[default] - Done, -} - -impl TarStage { - fn take(&mut self) -> Self { - std::mem::take(self) - } - fn take_seeking(&mut self) -> OwnedMutexGuard { - if let Self::Seeking(locked) = self.take() { - return locked; - } - unreachable!("") - } -} +use super::FuseReadTrait; /// A block in tar file, should be readonly /// @@ -52,215 +29,177 @@ where { file: Arc>, start: u64, - size: u64, - cursor: u64, - stage: TarStage, -} - -impl PartialEq for TarBlock -where - F: AsyncRead + AsyncSeek + Unpin, -{ - fn eq(&self, other: &Self) -> bool { - Arc::ptr_eq(&self.file, &other.file) - && self.start == other.start - && self.size == other.size - && self.cursor == other.cursor - } -} - -impl Clone for TarBlock -where - F: AsyncRead + AsyncSeek + Unpin, -{ - fn clone(&self) -> Self { - Self { - file: self.file.clone(), - start: self.start, - size: self.size, - cursor: self.cursor, - stage: TarStage::Done, - } - } + size: u32, + cursor: u32, } impl TarBlock where F: AsyncRead + AsyncSeek + Unpin + 'static, { - pub fn new(file: Arc>, start: u64, size: u64) -> Self { + pub fn new(file: Arc>, start: u64, size: u32) -> Self { + log::info!("new block: start={}, size={}", start, size); Self { file, start, size, cursor: 0, - stage: TarStage::Done, } } #[inline] - pub fn get_size(&self) -> u64 { + pub fn get_size(&self) -> u32 { self.size } - pub async fn read_all(&self) -> std::io::Result> { - let mut buf = Vec::with_capacity(self.size as usize); - let mut block = self.clone(); - block.seek(SeekFrom::Start(0)).await?; - block.read_to_end(&mut buf).await?; - Ok(buf) - } + // pub async fn read_all(&self) -> std::io::Result> { + // // let mut buf = Vec::with_capacity(self.size as usize); + // // let mut block = self.clone(); + // // block.seek(SeekFrom::Start(0)).await?; + // // block.read_to_end(&mut buf).await?; + // // Ok(buf) + // todo!() + // } #[cfg(test)] - fn from_raw(file: F, start: u64, size: u64) -> Self { + fn from_raw(file: F, start: u64, size: u32) -> Self { Self { file: Arc::new(Mutex::new(file)), start, size, cursor: 0, - stage: TarStage::Done, } } #[inline] - fn get_seek_from(&self) -> SeekFrom { - SeekFrom::Start(self.start + self.cursor) - } - #[inline] - fn check_bound(&self) -> bool { - self.cursor > self.size + fn get_seek_from(&self, offset: u64) -> Option { + if self.cursor > self.size { + None + } else { + Some(SeekFrom::Start(self.start + offset + (self.cursor) as u64)) + } } #[inline] - fn get_remain(&self) -> u64 { + fn get_remain(&self) -> u32 { self.size - self.cursor } } -impl AsyncRead for TarBlock +impl FuseReadTrait for TarBlock where F: AsyncRead + AsyncSeek + Unpin + 'static, { - fn poll_read( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut tokio::io::ReadBuf<'_>, - ) -> Poll> { - if self.check_bound() { - return Poll::Ready(Err(io::Error::new( - io::ErrorKind::UnexpectedEof, - "tar block out of bound", - ))); - } - let original_size = buf.filled().len(); - match &mut self.stage { - TarStage::Reading(ref mut locked) => { - report_poll!(chain_poll!(pin!(locked.deref_mut()).poll_read(cx, buf))); - let read_byte = (buf.filled().len() - original_size) as u64; - match read_byte > self.get_remain() { - true => { - buf.set_filled(original_size + self.get_remain() as usize); - self.cursor += self.get_remain(); - } - false => self.cursor += read_byte, - }; - self.stage.take(); - return Poll::Ready(Ok(())); - } - TarStage::Seeking(ref mut locked) => { - let result = chain_poll!(pin!(locked.deref_mut()).poll_complete(cx)); - let read_byte = report_poll!(result); - self.as_mut().stage = TarStage::Reading(self.stage.take_seeking()); - self.as_mut().cursor = read_byte - self.start; - cx.waker().wake_by_ref(); - } - TarStage::Done => { - let mut locked = chain_poll!(pin!(self.file.clone().lock_owned()).poll(cx)); - if let Err(err) = pin!(locked.deref_mut()).start_seek(self.get_seek_from()) { - return Poll::Ready(Err(err)); - } - self.as_mut().stage = TarStage::Seeking(locked); - cx.waker().wake_by_ref(); - } + async fn read(&mut self, offset: u64, size: u32) -> std::io::Result { + let size = size as usize; + let mut lock = self.file.lock().await; + let seek_from = self.get_seek_from(offset).ok_or(io::Error::new( + io::ErrorKind::UnexpectedEof, + "tar block out of bound", + ))?; + lock.seek(seek_from).await?; + + let mut buf = vec![0_u8; size]; + + let mut readed_byte = 0; + while readed_byte < size { + match lock.read(&mut buf).await { + Err(err) if readed_byte == 0 => return Err(err), + Ok(0) | Err(_) => break, + Ok(x) => readed_byte += x, + }; } - Poll::Pending + readed_byte = readed_byte.min(size); + self.cursor += readed_byte as u32; + + buf.resize(readed_byte, 0_u8); + Ok(bytes::Bytes::from(buf)) } } -impl AsyncSeek for TarBlock +impl PartialEq for TarBlock where - F: AsyncRead + AsyncSeek + Unpin + 'static, + F: AsyncRead + AsyncSeek + Unpin, { - fn start_seek(self: Pin<&mut Self>, position: io::SeekFrom) -> io::Result<()> { - let self_ = self.get_mut(); - self_.cursor = match position { - io::SeekFrom::Start(x) => x, - io::SeekFrom::End(x) => (self_.size as i64 + x).try_into().unwrap_or_default(), - io::SeekFrom::Current(x) => (self_.cursor as i64 + x).try_into().unwrap_or_default(), - }; - if self_.check_bound() { - return Err(io::Error::new( - io::ErrorKind::UnexpectedEof, - "tar block out of bound", - )); - } - Ok(()) - } - - fn poll_complete(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(self.cursor)) + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.file, &other.file) + && self.start == other.start + && self.size == other.size + && self.cursor == other.cursor } } -#[cfg(test)] -mod test { - use std::io::Cursor; - - use tokio::io::BufReader; - - use super::*; - #[tokio::test] - async fn normal_read() { - let underlying = BufReader::new(Cursor::new(b"111hello world111")); - let mut block = TarBlock::from_raw(underlying, 3, 11); - - let mut buf = [0_u8; 11]; - block.read_exact(&mut buf).await.unwrap(); - - assert_eq!(buf, *b"hello world"); - } - #[tokio::test] - async fn end_of_file_read() { - let underlying = BufReader::new(Cursor::new(b"111hello world")); - let mut block = TarBlock::from_raw(underlying, 3, 11); - - let mut buf = [0_u8; 11]; - block.read_exact(&mut buf).await.unwrap(); - - assert_eq!( - block.read_u8().await.unwrap_err().kind(), - io::ErrorKind::UnexpectedEof - ); - } - #[tokio::test] - async fn multi_sequential_read() { - let underlying = BufReader::new(Cursor::new(b"111hello world111")); - let mut block = TarBlock::from_raw(underlying, 3, 11); - - for c in b"hello world" { - assert_eq!(block.read_u8().await.unwrap(), *c); - } - } - #[tokio::test(flavor = "multi_thread", worker_threads = 8)] - async fn multi_reader_read() { - let underlying = BufReader::new(Cursor::new(b"111hello world111")); - let underlying = Arc::new(Mutex::new(underlying)); - let block = TarBlock::new(underlying, 3, 11); - - for _ in 0..30 { - let mut block = block.clone(); - tokio::spawn(async move { - for _ in 0..400 { - let mut buf = [0_u8; 11]; - block.read_exact(&mut buf).await.unwrap(); - assert_eq!(buf, *b"hello world"); - } - }); +impl Clone for TarBlock +where + F: AsyncRead + AsyncSeek + Unpin, +{ + fn clone(&self) -> Self { + Self { + file: self.file.clone(), + start: self.start, + size: self.size, + cursor: 0, } } } + +// #[cfg(test)] +// mod test { +// use std::io::Cursor; + +// use tokio::{fs::File, io::BufReader}; + +// use super::*; +// #[tokio::test] +// async fn file_io() { +// let file = File::open("test/single_file.tar").await.unwrap(); +// let mut block = TarBlock::new(Arc::new(Mutex::new(file)), 512, 11); +// let mut buf = [0_u8; 11]; +// block.read_exact(&mut buf).await.unwrap(); +// assert_eq!(buf, *b"hello world"); +// } +// #[tokio::test] +// async fn normal_read() { +// let underlying = BufReader::new(Cursor::new(b"111hello world111")); +// let mut block = TarBlock::from_raw(underlying, 3, 11); + +// let mut buf = [0_u8; 11]; +// block.read_exact(&mut buf).await.unwrap(); + +// assert_eq!(buf, *b"hello world"); +// } +// #[tokio::test] +// async fn end_of_file_read() { +// let underlying = BufReader::new(Cursor::new(b"111hello world")); +// let mut block = TarBlock::from_raw(underlying, 3, 11); + +// let mut buf = [0_u8; 11]; +// block.read_exact(&mut buf).await.unwrap(); + +// assert_eq!( +// block.read_u8().await.unwrap_err().kind(), +// io::ErrorKind::UnexpectedEof +// ); +// } +// #[tokio::test] +// async fn multi_sequential_read() { +// let underlying = BufReader::new(Cursor::new(b"111hello world111")); +// let mut block = TarBlock::from_raw(underlying, 3, 11); + +// for c in b"hello world" { +// assert_eq!(block.read_u8().await.unwrap(), *c); +// } +// } +// #[tokio::test(flavor = "multi_thread", worker_threads = 8)] +// async fn multi_reader_read() { +// let underlying = BufReader::new(Cursor::new(b"111hello world111")); +// let underlying = Arc::new(Mutex::new(underlying)); +// let block = TarBlock::new(underlying, 3, 11); + +// for _ in 0..30 { +// let mut block = block.clone(); +// tokio::spawn(async move { +// for _ in 0..400 { +// let mut buf = [0_u8; 11]; +// block.read_exact(&mut buf).await.unwrap(); +// assert_eq!(buf, *b"hello world"); +// } +// }); +// } +// } +// } diff --git a/judger/src/filesystem/entry/rw.rs b/judger/src/filesystem/entry/rw.rs index 33198096..de13703b 100644 --- a/judger/src/filesystem/entry/rw.rs +++ b/judger/src/filesystem/entry/rw.rs @@ -1,273 +1,282 @@ -use crate::filesystem::macro_::{chain_poll, report_poll}; -use std::{ - future::Future, - io::{self, SeekFrom}, - ops::Deref, - pin::{pin, Pin}, - sync::Arc, - task::{Context, Poll}, -}; -use tokio::{ - io::{AsyncRead, AsyncSeek, AsyncWrite}, - sync::{Mutex, OwnedMutexGuard}, -}; +use std::{io, ops::Deref, sync::Arc}; +use tokio::sync::Mutex; -use super::MEMBLOCK_BLOCKSIZE; - -#[derive(Debug, Default)] -enum MemStage { - Seeking(OwnedMutexGuard>, SeekFrom), - SeekStart(SeekFrom), - Reading(OwnedMutexGuard>), - // Writing(OwnedMutexGuard>), - #[default] - Done, -} - -impl MemStage { - fn take(&mut self) -> Self { - std::mem::take(self) - } -} +use super::{FuseReadTrait, FuseWriteTrait, MEMBLOCK_BLOCKSIZE}; +/// A block in memory +/// +/// Note that [`MemBlock`] behavior like [`tokio::fs::File`], +/// except that it dones't shares the same underlying file session +/// by cloning(Reads, writes, and seeks would **not** affect both +/// [`MemBlock`] instances simultaneously.) #[derive(Default, Debug)] pub struct MemBlock { data: Arc>>, cursor: usize, - stage: MemStage, write_buffer: Vec, } -impl Clone for MemBlock { - fn clone(&self) -> Self { - Self { - data: self.data.clone(), - cursor: self.cursor.clone(), - stage: MemStage::default(), - write_buffer: self.write_buffer.clone(), - } - } -} - impl MemBlock { pub fn new(data: Vec) -> Self { Self { data: Arc::new(Mutex::new(data)), cursor: 0, - stage: MemStage::Done, write_buffer: Vec::new(), } } + pub fn get_size(&self) -> u64 { + self.data.try_lock().map(|x| x.len()).unwrap_or_default() as u64 + } } -impl AsyncRead for MemBlock { - fn poll_read( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut tokio::io::ReadBuf<'_>, - ) -> Poll> { - match self.stage.take() { - MemStage::Reading(locked) => { - if locked.len() < self.cursor { - return Poll::Ready(Err(io::Error::new( - io::ErrorKind::UnexpectedEof, - "mem block out of bound", - ))); - } - let slice = &locked.deref()[self.cursor - ..(self.cursor + MEMBLOCK_BLOCKSIZE.min(buf.remaining())).min(locked.len())]; - self.cursor += slice.len(); - buf.put_slice(slice); - return Poll::Ready(Ok(())); - } - _ => { - let locked = chain_poll!(pin!(self.data.clone().lock_owned()).poll(cx)); - self.as_mut().stage = MemStage::Reading(locked); - cx.waker().wake_by_ref(); - } +impl FuseReadTrait for MemBlock { + async fn read(&mut self, offset: u64, size: u32) -> std::io::Result { + let locked = self.data.lock().await; + if locked.len() < offset as usize { + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "mem block out of bound", + )); } - Poll::Pending + let offset = offset as usize; + let slice = &locked.deref()[offset..(offset + size as usize).min(locked.len())]; + Ok(bytes::Bytes::copy_from_slice(slice)) } } - -impl AsyncSeek for MemBlock { - fn start_seek(mut self: Pin<&mut Self>, position: SeekFrom) -> io::Result<()> { - self.stage = MemStage::SeekStart(position); - Ok(()) - } - - fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.stage.take() { - MemStage::SeekStart(position) => { - let locked = chain_poll!(pin!(self.data.clone().lock_owned()).poll(cx)); - self.stage = MemStage::Seeking(locked, position); - cx.waker().wake_by_ref(); - } - MemStage::Seeking(locked, position) => { - let size = locked.len() as i64; - let new_position = match position { - SeekFrom::Start(x) => x.try_into().unwrap_or_default(), - SeekFrom::End(x) => size.saturating_sub(x), - SeekFrom::Current(x) => (self.cursor as i64).saturating_add(x), - }; - if new_position < 0 { - return Poll::Ready(Err(io::Error::new( - io::ErrorKind::InvalidInput, - "invalid seek position", - ))); - } - if new_position > size { - return Poll::Ready(Err(io::Error::new( - io::ErrorKind::UnexpectedEof, - "mem block out of bound", - ))); - } - self.cursor = new_position as usize; - return Poll::Ready(Ok(self.cursor as u64)); - } - _ => { - return Poll::Ready(Ok(self.cursor as u64)); - } +impl FuseWriteTrait for MemBlock { + async fn write(&mut self, offset: u64, data: &[u8]) -> std::io::Result { + let mut locked = self.data.lock().await; + if locked.len() < offset as usize { + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "mem block out of bound", + )); } - Poll::Pending + locked.resize(offset as usize, 0); + locked.extend_from_slice(data); + Ok(data.len() as u32) } } -impl AsyncWrite for MemBlock { - fn poll_write( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - self.write_buffer.extend_from_slice(&buf); - if self.write_buffer.len() >= MEMBLOCK_BLOCKSIZE { - report_poll!(chain_poll!(self.as_mut().poll_flush(cx))); +impl Clone for MemBlock { + fn clone(&self) -> Self { + Self { + data: self.data.clone(), + cursor: self.cursor.clone(), + write_buffer: self.write_buffer.clone(), } - Poll::Ready(Ok(buf.len())) } +} - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let mut locked = chain_poll!(pin!(self.data.clone().lock_owned()).poll(cx)); - locked.extend_from_slice(&self.write_buffer); - self.write_buffer.clear(); - Poll::Ready(Ok(())) - } +// impl AsyncRead for MemBlock { +// fn poll_read( +// mut self: Pin<&mut Self>, +// cx: &mut Context<'_>, +// buf: &mut tokio::io::ReadBuf<'_>, +// ) -> Poll> { +// let cursor = self.cursor; +// match &mut self.stage { +// MemStage::Reading(ref mut locked) => { +// if locked.len() < cursor { +// return Poll::Ready(Err(io::Error::new( +// io::ErrorKind::UnexpectedEof, +// "mem block out of bound", +// ))); +// } +// let slice = &locked.deref() +// [cursor..(cursor + MEMBLOCK_BLOCKSIZE.min(buf.remaining())).min(locked.len())]; +// buf.put_slice(slice); +// self.cursor += slice.len(); +// return Poll::Ready(Ok(())); +// } +// _ => { +// let locked = chain_poll!(pin!(self.data.clone().lock_owned()).poll(cx)); +// self.as_mut().stage = MemStage::Reading(locked); +// cx.waker().wake_by_ref(); +// } +// } +// Poll::Pending +// } +// } - fn poll_shutdown( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - self.as_mut().poll_flush(cx) - } -} +// impl AsyncSeek for MemBlock { +// fn start_seek(mut self: Pin<&mut Self>, position: SeekFrom) -> io::Result<()> { +// self.stage = MemStage::SeekStart(position); +// Ok(()) +// } -#[cfg(test)] -mod test { - use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; +// fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { +// match &self.stage { +// MemStage::SeekStart(_) => { +// let locked = chain_poll!(pin!(self.data.clone().lock_owned()).poll(cx)); +// self.stage = MemStage::Seeking(locked, self.stage.take_seek_start()); +// cx.waker().wake_by_ref(); +// } +// MemStage::Seeking(ref locked, ref position) => { +// let size = locked.len() as i64; +// let new_position = match position { +// SeekFrom::Start(x) => (*x).try_into().unwrap_or_default(), +// SeekFrom::End(x) => size.saturating_sub(*x), +// SeekFrom::Current(x) => (self.cursor as i64).saturating_add(*x), +// }; +// if new_position < 0 { +// return Poll::Ready(Err(io::Error::new( +// io::ErrorKind::InvalidInput, +// "invalid seek position", +// ))); +// } +// if new_position > size { +// return Poll::Ready(Err(io::Error::new( +// io::ErrorKind::UnexpectedEof, +// "mem block out of bound", +// ))); +// } +// self.cursor = new_position as usize; +// return Poll::Ready(Ok(self.cursor as u64)); +// } +// _ => { +// return Poll::Ready(Ok(self.cursor as u64)); +// } +// } +// Poll::Pending +// } +// } - use super::*; - #[tokio::test] - async fn normal_read() { - let data = b"hello world".to_vec(); - let mut block = MemBlock::new(data); - let mut buf = [0_u8; 11]; - block.read_exact(&mut buf).await.unwrap(); +// impl AsyncWrite for MemBlock { +// fn poll_write( +// mut self: Pin<&mut Self>, +// cx: &mut Context<'_>, +// buf: &[u8], +// ) -> Poll> { +// self.write_buffer.extend_from_slice(&buf); +// if self.write_buffer.len() >= MEMBLOCK_BLOCKSIZE { +// report_poll!(chain_poll!(self.as_mut().poll_flush(cx)), self.stage); +// } +// Poll::Ready(Ok(buf.len())) +// } - assert_eq!(buf, *b"hello world"); - } - #[tokio::test] - async fn end_of_file_read() { - let mut block = MemBlock::new(b"1234".to_vec()); - let mut buf = Vec::new(); - block.read_to_end(&mut buf).await.unwrap(); +// fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { +// let mut locked = chain_poll!(pin!(self.data.clone().lock_owned()).poll(cx)); +// locked.extend_from_slice(&self.write_buffer); +// self.write_buffer.clear(); +// Poll::Ready(Ok(())) +// } - assert_eq!(&*buf, b"1234"); - } - #[tokio::test] - async fn start_seek() { - let mut block = MemBlock::new(b"111hello world1111".to_vec()); - block.seek(SeekFrom::Start(3)).await.unwrap(); +// fn poll_shutdown( +// mut self: Pin<&mut Self>, +// cx: &mut Context<'_>, +// ) -> Poll> { +// self.as_mut().poll_flush(cx) +// } +// } - let mut buf = [0_u8; 11]; - block.read_exact(&mut buf).await.unwrap(); +// #[cfg(test)] +// mod test { +// use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; - assert_eq!(buf, *b"hello world"); - } - #[tokio::test] - async fn end_seek() { - let mut block = MemBlock::new(b"111hello world1111".to_vec()); - block.seek(SeekFrom::End(15)).await.unwrap(); +// use super::*; +// #[tokio::test] +// async fn normal_read() { +// let data = b"hello world".to_vec(); +// let mut block = MemBlock::new(data); +// let mut buf = [0_u8; 11]; +// block.read_exact(&mut buf).await.unwrap(); - let mut buf = [0_u8; 11]; - block.read_exact(&mut buf).await.unwrap(); +// assert_eq!(buf, *b"hello world"); +// } +// #[tokio::test] +// async fn end_of_file_read() { +// let mut block = MemBlock::new(b"1234".to_vec()); +// let mut buf = Vec::new(); +// block.read_to_end(&mut buf).await.unwrap(); - assert_eq!(buf, *b"hello world"); - } - #[tokio::test] - async fn rel_seek() { - let mut block = MemBlock::new(b"111hello world1111".to_vec()); - for _ in 0..3 { - block.seek(SeekFrom::Current(1)).await.unwrap(); - } +// assert_eq!(&*buf, b"1234"); +// } +// #[tokio::test] +// async fn start_seek() { +// let mut block = MemBlock::new(b"111hello world1111".to_vec()); +// block.seek(SeekFrom::Start(3)).await.unwrap(); - let mut buf = [0_u8; 11]; - block.read_exact(&mut buf).await.unwrap(); +// let mut buf = [0_u8; 11]; +// block.read_exact(&mut buf).await.unwrap(); - assert_eq!(buf, *b"hello world"); - } - #[tokio::test] - async fn normal_write() { - let mut block = MemBlock::default(); - block.write_all(b"hello").await.unwrap(); - block.write_all(b" ").await.unwrap(); - block.write_all(b"world").await.unwrap(); +// assert_eq!(buf, *b"hello world"); +// } +// #[tokio::test] +// async fn end_seek() { +// let mut block = MemBlock::new(b"111hello world1111".to_vec()); +// block.seek(SeekFrom::End(15)).await.unwrap(); - assert!(block.read_u8().await.is_err()); +// let mut buf = [0_u8; 11]; +// block.read_exact(&mut buf).await.unwrap(); - block.flush().await.unwrap(); +// assert_eq!(buf, *b"hello world"); +// } +// #[tokio::test] +// async fn rel_seek() { +// let mut block = MemBlock::new(b"111hello world1111".to_vec()); +// for _ in 0..3 { +// block.seek(SeekFrom::Current(1)).await.unwrap(); +// } - let mut buf = [0_u8; 11]; - block.read_exact(&mut buf).await.unwrap(); +// let mut buf = [0_u8; 11]; +// block.read_exact(&mut buf).await.unwrap(); - assert_eq!(buf, *b"hello world"); - } - #[tokio::test] - async fn multi_read() { - let block = MemBlock::new(b"hello world".to_vec()); +// assert_eq!(buf, *b"hello world"); +// } +// #[tokio::test] +// async fn normal_write() { +// let mut block = MemBlock::default(); +// block.write_all(b"hello").await.unwrap(); +// block.write_all(b" ").await.unwrap(); +// block.write_all(b"world").await.unwrap(); - for _ in 0..3000 { - let mut block = block.clone(); - tokio::spawn(async move { - let mut buf = [0_u8; 11]; - block.read_exact(&mut buf).await.unwrap(); - assert_eq!(buf, *b"hello world"); - }); - } - } - #[tokio::test] - #[should_panic] - async fn test_take_read() { - let block = MemBlock::new(b"hello world".to_vec()); - let mut buffer = [0; 5]; +// assert!(block.read_u8().await.is_err()); - // read at most five bytes - let mut handle = block.take(5); - handle.read_exact(&mut buffer).await.unwrap(); - assert_eq!(buffer, *b"hello"); +// block.flush().await.unwrap(); - // read the rest - let mut buffer = [0; 6]; - handle.read_exact(&mut buffer).await.unwrap(); - assert_eq!(buffer, *b" world"); - } - #[tokio::test] - async fn test_take_short_read() { - let block = MemBlock::new(b"hello ".to_vec()); - let mut buffer = Vec::new(); +// let mut buf = [0_u8; 11]; +// block.read_exact(&mut buf).await.unwrap(); - // read at most five bytes - let mut handle = block.take(100); - handle.read_to_end(&mut buffer).await.unwrap(); - assert_eq!(buffer, b"hello "); - } -} +// assert_eq!(buf, *b"hello world"); +// } +// #[tokio::test] +// async fn multi_read() { +// let block = MemBlock::new(b"hello world".to_vec()); + +// for _ in 0..3000 { +// let mut block = block.clone(); +// tokio::spawn(async move { +// let mut buf = [0_u8; 11]; +// block.read_exact(&mut buf).await.unwrap(); +// assert_eq!(buf, *b"hello world"); +// }); +// } +// } +// #[tokio::test] +// #[should_panic] +// async fn test_take_read() { +// let block = MemBlock::new(b"hello world".to_vec()); +// let mut buffer = [0; 5]; + +// // read at most five bytes +// let mut handle = block.take(5); +// handle.read_exact(&mut buffer).await.unwrap(); +// assert_eq!(buffer, *b"hello"); + +// // read the rest +// let mut buffer = [0; 6]; +// handle.read_exact(&mut buffer).await.unwrap(); +// assert_eq!(buffer, *b" world"); +// } +// #[tokio::test] +// async fn test_take_short_read() { +// let block = MemBlock::new(b"hello ".to_vec()); +// let mut buffer = Vec::new(); + +// // read at most five bytes +// let mut handle = block.take(100); +// handle.read_to_end(&mut buffer).await.unwrap(); +// assert_eq!(buffer, b"hello "); +// } +// } diff --git a/judger/src/filesystem/entry/tar.rs b/judger/src/filesystem/entry/tar.rs index 14250ec1..0be0b099 100644 --- a/judger/src/filesystem/entry/tar.rs +++ b/judger/src/filesystem/entry/tar.rs @@ -18,7 +18,7 @@ use tokio::{ sync::Mutex, }; -use crate::filesystem::adj::{to_internal_path, AdjTable, DeepClone}; +use crate::filesystem::table::{to_internal_path, AdjTable}; use super::{ro::TarBlock, Entry}; @@ -26,12 +26,12 @@ pub struct TarTree(AdjTable>) where F: AsyncRead + AsyncSeek + Unpin + Send + 'static; -impl DeepClone for TarTree +impl Clone for TarTree where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { - async fn deep_clone(&self) -> Self { - Self(self.0.deep_clone().await) + fn clone(&self) -> Self { + Self(self.0.clone()) } } @@ -80,11 +80,7 @@ where EntryType::Regular | EntryType::Continuous => { let start = entry.raw_file_position(); let size = entry.size(); - Entry::TarFile(Arc::new(Mutex::new(TarBlock::new( - file.clone(), - start, - size, - )))) + Entry::TarFile(TarBlock::new(file.clone(), start, size as u32)) } EntryType::Symlink => Entry::SymLink(OsString::from_vec( entry.link_name_bytes().unwrap().into_owned(), diff --git a/judger/src/filesystem/entry/wrapper.rs b/judger/src/filesystem/entry/wrapper.rs deleted file mode 100644 index 7aa590bf..00000000 --- a/judger/src/filesystem/entry/wrapper.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::io::SeekFrom; - -use bytes::Bytes; -use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, AsyncWrite, AsyncWriteExt}; - -pub struct FuseRead<'a, W>(pub &'a mut W) -where - W: AsyncRead + AsyncSeek + Clone + Unpin; - -impl<'a, W> FuseRead<'a, W> -where - W: AsyncRead + AsyncSeek + Clone + Unpin, -{ - pub async fn read(&mut self, offset: u64, size: u32) -> std::io::Result { - let mut buf = Vec::with_capacity(size as usize); - self.0.seek(SeekFrom::Start(offset)).await?; - - self.0 - .clone() - .take(size as u64) - .read_to_end(&mut buf) - .await?; - - self.0.seek(SeekFrom::Current(buf.len() as i64)).await?; - - Ok(buf.try_into().unwrap()) - } -} - -pub struct FuseWrite<'a, W>(pub &'a mut W) -where - W: AsyncWrite + AsyncSeek + Clone + Unpin; - -impl<'a, W> FuseWrite<'a, W> -where - W: AsyncWrite + AsyncSeek + Clone + Unpin, -{ - pub async fn write(&mut self, offset: u64, data: &[u8]) -> std::io::Result { - assert!(data.len() < (u32::MAX - 1) as usize); - self.0.seek(SeekFrom::Start(offset)).await?; - - self.0.write_all(data).await?; - Ok(data.len() as u32) - } -} diff --git a/judger/src/filesystem/macro_.rs b/judger/src/filesystem/macro_.rs deleted file mode 100644 index 953391a9..00000000 --- a/judger/src/filesystem/macro_.rs +++ /dev/null @@ -1,25 +0,0 @@ -macro_rules! chain_poll { - ($poll:expr) => {{ - let poll_ = $poll; - if poll_.is_pending() { - return Poll::Pending; - } - match poll_ { - Poll::Ready(x) => x, - Poll::Pending => unreachable!(), - } - }}; -} -macro_rules! report_poll { - ($ans:expr) => {{ - if let Err(err) = $ans { - return Poll::Ready(Err(err)); - } - match $ans { - Ok(x) => x, - Err(_) => unreachable!(), - } - }}; -} - -pub(crate) use {chain_poll, report_poll}; diff --git a/judger/src/filesystem/mod.rs b/judger/src/filesystem/mod.rs index bf329472..a2dcc3b1 100644 --- a/judger/src/filesystem/mod.rs +++ b/judger/src/filesystem/mod.rs @@ -1,10 +1,9 @@ //! Filesystem module that is mountable(actuall mount and //! is accessible for user in this operation system) mod adapter; -mod adj; +mod table; mod entry; mod error; -mod macro_; mod resource; pub use entry::prelude::*; diff --git a/judger/src/filesystem/resource.rs b/judger/src/filesystem/resource.rs index d19b88eb..ff943397 100644 --- a/judger/src/filesystem/resource.rs +++ b/judger/src/filesystem/resource.rs @@ -8,7 +8,7 @@ impl Resource { } pub fn comsume(&self, size: u32) -> Option<()> { let a = self.0.fetch_sub(size as u64, Ordering::AcqRel); - if (a | (1 << 63)) != 0 { + if (a & (1 << 63)) != 0 { None } else { Some(()) diff --git a/judger/src/filesystem/adj.rs b/judger/src/filesystem/table.rs similarity index 92% rename from judger/src/filesystem/adj.rs rename to judger/src/filesystem/table.rs index d2c39571..8d4700d2 100644 --- a/judger/src/filesystem/adj.rs +++ b/judger/src/filesystem/table.rs @@ -1,5 +1,5 @@ use std::{ - collections::HashMap, + collections::{BTreeMap}, ffi::{OsStr, OsString}, path::{Component, Path}, }; @@ -20,10 +20,11 @@ pub trait DeepClone { async fn deep_clone(&self) -> Self; } +#[derive(Clone)] struct Node { parent_idx: usize, value: V, - children: HashMap, + children: BTreeMap, // FIXME: use BtreeMap } impl DeepClone for Node { @@ -36,20 +37,11 @@ impl DeepClone for Node { } } +#[derive(Clone)] pub struct AdjTable { by_id: Vec>, } -impl DeepClone for AdjTable { - async fn deep_clone(&self) -> Self { - let mut by_id = Vec::with_capacity(self.by_id.len()); - for node in &self.by_id { - by_id.push(node.deep_clone().await); - } - Self { by_id } - } -} - impl AdjTable { pub fn new() -> Self { Self { by_id: vec![] } @@ -59,7 +51,7 @@ impl AdjTable { self.by_id.push(Node { parent_idx: 0, value, - children: HashMap::new(), + children: BTreeMap::new(), }); NodeWrapperMut { table: self, idx } } @@ -121,7 +113,7 @@ impl AdjTable { self.by_id.push(Node { parent_idx: idx, value: default_value(), - children: HashMap::new(), + children: BTreeMap::new(), }); self.by_id[idx].children.insert(name, new_idx); } @@ -150,7 +142,7 @@ impl AdjTable { self.by_id.push(Node { parent_idx: idx, value: default_value(), - children: HashMap::new(), + children: BTreeMap::new(), }); self.by_id[idx].children.insert(seg.to_os_string(), new_idx); idx = new_idx; @@ -184,10 +176,6 @@ impl<'a, V> NodeWrapper<'a, V> { }) } pub fn children(self) -> impl Iterator + 'a { - log::info!( - "children length: {}", - self.table.by_id[self.idx].children.len() - ); self.table.by_id[self.idx] .children .iter() @@ -231,7 +219,7 @@ impl<'a, V> NodeWrapperMut<'a, V> { self.table.by_id.push(Node { parent_idx: self.idx, value, - children: HashMap::new(), + children: BTreeMap::new(), }); self.table.by_id[self.idx].children.insert(name, idx); NodeWrapperMut { @@ -263,7 +251,7 @@ impl<'a, V> NodeWrapperMut<'a, V> { self.table.by_id[self.idx] .children .iter() - .map(|(_, &idx)| idx) + .map(|(_, &idx)| idx + ID_MIN) } } diff --git a/judger/test/helloworld.txt b/judger/test/helloworld.txt new file mode 100644 index 00000000..71d7efc6 --- /dev/null +++ b/judger/test/helloworld.txt @@ -0,0 +1 @@ +111hello world111 \ No newline at end of file From 6340782a89110d7b449d078cbf0447ee455a0c07 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Fri, 10 May 2024 12:08:55 +0800 Subject: [PATCH 23/38] fix(Judger): :bug: Fix lookup --- judger/src/filesystem/adapter/fuse.rs | 24 ++++++++++++------------ judger/src/filesystem/adapter/mod.rs | 4 ++-- judger/src/filesystem/entry/mod.rs | 6 ++---- judger/src/filesystem/mod.rs | 2 +- judger/src/filesystem/table.rs | 2 +- 5 files changed, 18 insertions(+), 20 deletions(-) diff --git a/judger/src/filesystem/adapter/fuse.rs b/judger/src/filesystem/adapter/fuse.rs index fdfaef21..a39eecc9 100644 --- a/judger/src/filesystem/adapter/fuse.rs +++ b/judger/src/filesystem/adapter/fuse.rs @@ -80,10 +80,10 @@ where ) -> impl Future> + Send { async move { let tree = self.tree.lock(); - let node = tree.get(parent as usize).ok_or(FuseError::InvaildIno)?; - let entry = node.get_by_component(name).ok_or(FuseError::InvalidPath)?; + let parent_node = tree.get(parent as usize).ok_or(FuseError::InvaildIno)?; + let node = parent_node.get_by_component(name).ok_or(FuseError::InvalidPath)?; // FIXME: unsure about the inode - Ok(reply_entry(&req, entry.get_value(), parent)) + Ok(reply_entry(&req, node.get_value(), node.get_id() as u64)) } } fn forget(&self, _: Request, inode: u64, _: u64) -> impl Future + Send { @@ -134,13 +134,13 @@ where ) -> impl Future> + Send { async move { let tree = self.tree.lock(); - let entry = tree.get(inode as usize).ok_or(FuseError::InvaildIno)?; - if entry.get_value().kind() == FileType::Directory { + let node = tree.get(inode as usize).ok_or(FuseError::InvaildIno)?; + if node.get_value().kind() == FileType::Directory { return Err(FuseError::IsDir.into()); } let fh = self .handle_table - .add(AsyncMutex::new(entry.get_value().clone())); + .add(AsyncMutex::new(node.get_value().clone())); Ok(ReplyOpen { fh, flags: 0 }) } } @@ -262,8 +262,8 @@ where size: u32, ) -> impl Future> + Send { async move { - let entry = self.handle_table.get(fh).ok_or(FuseError::HandleNotFound)?; - let mut lock = entry.lock().await; + let session = self.handle_table.get(fh).ok_or(FuseError::HandleNotFound)?; + let mut lock = session.lock().await; Ok(lock .read(offset, size) .await @@ -282,13 +282,13 @@ where flags: u32, ) -> impl Future> + Send { async move { - let entry = self + let session = self .handle_table .get(fh) .ok_or(FuseError::HandleNotFound) .unwrap(); - Ok(Entry::write(entry, offset, data, &self.resource) + Ok(Entry::write(session, offset, data, &self.resource) .await .ok_or_else(|| Into::::into(FuseError::IsDir))? .map(|written| ReplyWrite { written })?) @@ -354,9 +354,9 @@ where ) -> impl Future> + Send { async move { let tree = self.tree.lock(); - let entry = tree.get(inode as usize).ok_or(FuseError::InvaildIno)?; + let node = tree.get(inode as usize).ok_or(FuseError::InvaildIno)?; // FIXME: unsure about the inode - Ok(reply_attr(&req, entry.get_value(), inode)) + Ok(reply_attr(&req, node.get_value(), inode)) } } fn setattr( diff --git a/judger/src/filesystem/adapter/mod.rs b/judger/src/filesystem/adapter/mod.rs index 34d43505..49356437 100644 --- a/judger/src/filesystem/adapter/mod.rs +++ b/judger/src/filesystem/adapter/mod.rs @@ -21,7 +21,7 @@ mod test { .try_init() .ok(); - log::info!("mounting test tarball in .temp/1 ..."); + log::info!("mounting test tarball in .temp ..."); let global_resource = Semaphore::new(4096 * 1024 * 1024, 1); let template = Template::new("test/nested.tar").await.unwrap(); let filesystem = template @@ -32,7 +32,7 @@ mod test { .unwrap(), ) .await; - let mut mount_handle = filesystem.mount("./.temp/11").await.unwrap(); + let mut mount_handle = filesystem.mount("./.temp/18").await.unwrap(); let handle = &mut mount_handle; tokio::select! { diff --git a/judger/src/filesystem/entry/mod.rs b/judger/src/filesystem/entry/mod.rs index 5c24daec..0568b275 100644 --- a/judger/src/filesystem/entry/mod.rs +++ b/judger/src/filesystem/entry/mod.rs @@ -16,7 +16,7 @@ use tokio::{ sync::{Mutex, OwnedMutexGuard}, }; -use super::{table::DeepClone, resource::Resource}; +use super::{resource::Resource, table::DeepClone}; pub const MEMBLOCK_BLOCKSIZE: usize = 4096; @@ -109,9 +109,7 @@ where } match &mut *lock { Self::MemFile(block) => Some(block.write(offset, data).await), - Self::TarFile(block) => { - todo!() - } + Self::TarFile(block) => Some(Err(std::io::Error::from(std::io::ErrorKind::Other))), _ => None, } } diff --git a/judger/src/filesystem/mod.rs b/judger/src/filesystem/mod.rs index a2dcc3b1..90c8e28e 100644 --- a/judger/src/filesystem/mod.rs +++ b/judger/src/filesystem/mod.rs @@ -1,9 +1,9 @@ //! Filesystem module that is mountable(actuall mount and //! is accessible for user in this operation system) mod adapter; -mod table; mod entry; mod error; mod resource; +mod table; pub use entry::prelude::*; diff --git a/judger/src/filesystem/table.rs b/judger/src/filesystem/table.rs index 8d4700d2..fdcdd1e3 100644 --- a/judger/src/filesystem/table.rs +++ b/judger/src/filesystem/table.rs @@ -1,5 +1,5 @@ use std::{ - collections::{BTreeMap}, + collections::BTreeMap, ffi::{OsStr, OsString}, path::{Component, Path}, }; From d0bf6561d1657aa922f27685a24eee53b962fbd2 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Fri, 10 May 2024 17:04:03 +0800 Subject: [PATCH 24/38] feat(Judger): :construction: draft crate::sandbox::Context implementer in language model --- judger/src/filesystem/adapter/fuse.rs | 106 ++++++++++-- judger/src/filesystem/adapter/mod.rs | 14 +- judger/src/filesystem/adapter/reply.rs | 2 +- judger/src/filesystem/adapter/template.rs | 10 +- judger/src/filesystem/entry/mod.rs | 17 +- judger/src/filesystem/entry/ro.rs | 15 +- judger/src/filesystem/entry/rw.rs | 2 +- judger/src/filesystem/entry/tar.rs | 4 + judger/src/filesystem/mkdtemp.rs | 33 ++++ judger/src/filesystem/mod.rs | 4 +- judger/src/filesystem/table.rs | 14 -- judger/src/language/config.rs | 21 ++- judger/src/language/daemon.rs | 13 +- judger/src/language/mod.rs | 1 + judger/src/language/plugin.rs | 96 +++++++++++ judger/src/main.rs | 3 - judger/src/sandbox/mod.rs | 13 +- judger/src/semaphore.rs | 199 ---------------------- 18 files changed, 293 insertions(+), 274 deletions(-) create mode 100644 judger/src/filesystem/mkdtemp.rs create mode 100644 judger/src/language/plugin.rs delete mode 100644 judger/src/semaphore.rs diff --git a/judger/src/filesystem/adapter/fuse.rs b/judger/src/filesystem/adapter/fuse.rs index a39eecc9..ee2ed6bd 100644 --- a/judger/src/filesystem/adapter/fuse.rs +++ b/judger/src/filesystem/adapter/fuse.rs @@ -3,12 +3,10 @@ use std::{ffi::OsStr, num::NonZeroU32, path::Path, sync::Arc}; use futures_core::Future; use spin::Mutex; use tokio::io::{AsyncRead, AsyncSeek}; -use tokio::sync::Mutex as AsyncMutex; +use tokio::sync::{Mutex as AsyncMutex, OwnedSemaphorePermit}; -use crate::{ - filesystem::{resource::Resource, Entry, TarTree, BLOCKSIZE}, - semaphore::Permit, -}; +use crate::filesystem::entry::{Entry, TarTree, BLOCKSIZE}; +use crate::filesystem::resource::Resource; use super::{error::FuseError, handle::HandleTable, reply::*}; use fuse3::{ @@ -24,19 +22,17 @@ where handle_table: HandleTable>>, tree: Mutex>, resource: Arc, - _permit: Permit, } impl Filesystem where F: AsyncRead + AsyncSeek + Unpin + Send + Sync + 'static, { - pub(super) fn new(tree: TarTree, permit: Permit) -> Self { + pub(super) fn new(tree: TarTree, permit: u64) -> Self { Self { handle_table: HandleTable::new(), tree: Mutex::new(tree), - resource: Arc::new(Resource::new(permit.count())), - _permit: permit, + resource: Arc::new(Resource::new(permit)), } } pub async fn mount(self, path: impl AsRef + Clone) -> std::io::Result { @@ -81,7 +77,9 @@ where async move { let tree = self.tree.lock(); let parent_node = tree.get(parent as usize).ok_or(FuseError::InvaildIno)?; - let node = parent_node.get_by_component(name).ok_or(FuseError::InvalidPath)?; + let node = parent_node + .get_by_component(name) + .ok_or(FuseError::InvalidPath)?; // FIXME: unsure about the inode Ok(reply_entry(&req, node.get_value(), node.get_id() as u64)) } @@ -89,6 +87,20 @@ where fn forget(&self, _: Request, inode: u64, _: u64) -> impl Future + Send { async {} } + fn release( + &self, + req: Request, + inode: u64, + fh: u64, + flags: u32, + lock_owner: u64, + flush: bool, + ) -> impl Future> + Send { + async move { + self.handle_table.remove(fh); + Ok(()) + } + } fn statfs( &self, _: Request, @@ -412,3 +424,77 @@ where } } } + +#[cfg(test)] +mod test { + use std::{ + ffi::OsStr, + sync::atomic::{AtomicU64, Ordering}, + }; + + use fuse3::{ + raw::{Filesystem as _, Request}, + Errno, + }; + use tokio::fs::File; + + use crate::filesystem::adapter::Template; + + use super::Filesystem; + + const UNIQUE_COUNTER: AtomicU64 = AtomicU64::new(0); + + async fn nested_tar() -> Filesystem { + let template = Template::new("test/nested.tar").await.unwrap(); + template.as_filesystem(1024 * 1024) + } + fn spawn_request() -> Request { + Request { + unique: UNIQUE_COUNTER.fetch_add(1, Ordering::AcqRel), + uid: 1000, + gid: 1000, + pid: 2, + } + } + + #[tokio::test] + async fn lookup() { + let fs = nested_tar().await; + assert_eq!( + fs.lookup(spawn_request(), 1, OsStr::new("nest")) + .await + .unwrap() + .attr + .ino, + 2 + ); + assert_eq!( + fs.lookup(spawn_request(), 1, OsStr::new("o.txt")) + .await + .unwrap() + .attr + .ino, + 5 + ); + assert_eq!( + fs.lookup(spawn_request(), 2, OsStr::new("a.txt")) + .await + .unwrap() + .attr + .ino, + 3 + ); + assert_eq!( + fs.lookup(spawn_request(), 2, OsStr::new("o.txt")) + .await + .unwrap_err(), + Errno::new_not_exist() + ); + assert_eq!( + fs.lookup(spawn_request(), 100, OsStr::new("o.txt")) + .await + .unwrap_err(), + libc::ENOENT.into() + ) + } +} diff --git a/judger/src/filesystem/adapter/mod.rs b/judger/src/filesystem/adapter/mod.rs index 49356437..f7e8e20f 100644 --- a/judger/src/filesystem/adapter/mod.rs +++ b/judger/src/filesystem/adapter/mod.rs @@ -10,7 +10,7 @@ pub use template::Template; #[cfg(test)] mod test { use super::*; - use crate::semaphore::Semaphore; + // use crate::semaphore::Semaphore; use env_logger::*; #[tokio::test] @@ -22,17 +22,9 @@ mod test { .ok(); log::info!("mounting test tarball in .temp ..."); - let global_resource = Semaphore::new(4096 * 1024 * 1024, 1); let template = Template::new("test/nested.tar").await.unwrap(); - let filesystem = template - .as_filesystem( - global_resource - .get_permit(1024 * 1024 * 1024) - .await - .unwrap(), - ) - .await; - let mut mount_handle = filesystem.mount("./.temp/18").await.unwrap(); + let filesystem = template.as_filesystem(1024 * 1024 * 1024); + let mut mount_handle = filesystem.mount("./.temp/1").await.unwrap(); let handle = &mut mount_handle; tokio::select! { diff --git a/judger/src/filesystem/adapter/reply.rs b/judger/src/filesystem/adapter/reply.rs index 957472d3..b5c8cc7e 100644 --- a/judger/src/filesystem/adapter/reply.rs +++ b/judger/src/filesystem/adapter/reply.rs @@ -6,7 +6,7 @@ use fuse3::{ }; use tokio::io::{AsyncRead, AsyncSeek}; -use crate::filesystem::{Entry, BLOCKSIZE}; +use crate::filesystem::{entry::Entry, entry::BLOCKSIZE}; const TTL: Duration = Duration::from_secs(1); diff --git a/judger/src/filesystem/adapter/template.rs b/judger/src/filesystem/adapter/template.rs index b4d17f0f..ecd752bb 100644 --- a/judger/src/filesystem/adapter/template.rs +++ b/judger/src/filesystem/adapter/template.rs @@ -5,10 +5,7 @@ use tokio::{ io::{AsyncRead, AsyncSeek}, }; -use crate::{ - filesystem::{table::DeepClone, TarTree}, - semaphore::Permit, -}; +use crate::filesystem::entry::TarTree; use super::fuse::Filesystem; @@ -26,9 +23,12 @@ where pub fn new_inner(tree: TarTree) -> Self { Self { tree } } - pub async fn as_filesystem(&self, permit: Permit) -> Filesystem { + pub fn as_filesystem(&self, permit: u64) -> Filesystem { Filesystem::new(self.tree.clone(), permit) } + pub async fn read_by_path(&self, path: impl AsRef) -> Option> { + self.tree.read_by_path(path).await + } } impl Template { diff --git a/judger/src/filesystem/entry/mod.rs b/judger/src/filesystem/entry/mod.rs index 0568b275..53104ef2 100644 --- a/judger/src/filesystem/entry/mod.rs +++ b/judger/src/filesystem/entry/mod.rs @@ -1,11 +1,6 @@ mod ro; mod rw; mod tar; -pub mod prelude { - pub use super::tar::TarTree; - pub use super::Entry; - pub use super::MEMBLOCK_BLOCKSIZE as BLOCKSIZE; -} use self::{ro::TarBlock, rw::MemBlock}; use bytes::Bytes; @@ -16,9 +11,10 @@ use tokio::{ sync::{Mutex, OwnedMutexGuard}, }; -use super::{resource::Resource, table::DeepClone}; +use super::resource::Resource; -pub const MEMBLOCK_BLOCKSIZE: usize = 4096; +pub use tar::TarTree; +pub const BLOCKSIZE: usize = 4096; pub trait FuseReadTrait { async fn read(&mut self, offset: u64, size: u32) -> std::io::Result; @@ -97,6 +93,13 @@ where _ => None, } } + pub async fn read_all(&self) -> Option> { + match self { + Self::TarFile(block) => Some(block.read_all().await.expect("tar ball corrupted")), + Self::MemFile(block) => None, + _ => None, + } + } pub async fn write( self_: Arc>, offset: u64, diff --git a/judger/src/filesystem/entry/ro.rs b/judger/src/filesystem/entry/ro.rs index b6c71e6c..d4aebc3d 100644 --- a/judger/src/filesystem/entry/ro.rs +++ b/judger/src/filesystem/entry/ro.rs @@ -50,14 +50,13 @@ where pub fn get_size(&self) -> u32 { self.size } - // pub async fn read_all(&self) -> std::io::Result> { - // // let mut buf = Vec::with_capacity(self.size as usize); - // // let mut block = self.clone(); - // // block.seek(SeekFrom::Start(0)).await?; - // // block.read_to_end(&mut buf).await?; - // // Ok(buf) - // todo!() - // } + pub async fn read_all(&self) -> std::io::Result> { + let mut lock = self.file.lock().await; + lock.seek(SeekFrom::Start(self.start)).await?; + let mut buf = vec![0_u8; self.size as usize]; + lock.read_exact(&mut buf).await?; + Ok(buf) + } #[cfg(test)] fn from_raw(file: F, start: u64, size: u32) -> Self { Self { diff --git a/judger/src/filesystem/entry/rw.rs b/judger/src/filesystem/entry/rw.rs index de13703b..1b3c32df 100644 --- a/judger/src/filesystem/entry/rw.rs +++ b/judger/src/filesystem/entry/rw.rs @@ -1,7 +1,7 @@ use std::{io, ops::Deref, sync::Arc}; use tokio::sync::Mutex; -use super::{FuseReadTrait, FuseWriteTrait, MEMBLOCK_BLOCKSIZE}; +use super::{FuseReadTrait, FuseWriteTrait, BLOCKSIZE}; /// A block in memory /// diff --git a/judger/src/filesystem/entry/tar.rs b/judger/src/filesystem/entry/tar.rs index 0be0b099..a8abcab3 100644 --- a/judger/src/filesystem/entry/tar.rs +++ b/judger/src/filesystem/entry/tar.rs @@ -70,6 +70,10 @@ impl TarTree where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { + pub async fn read_by_path(&self, path: impl AsRef) -> Option> { + let node = self.0.get_by_path(to_internal_path(path.as_ref()))?; + Some(node.get_value().read_all().await.unwrap()) + } async fn parse_entry( &mut self, entry: tar::Entry<'_, R>, diff --git a/judger/src/filesystem/mkdtemp.rs b/judger/src/filesystem/mkdtemp.rs new file mode 100644 index 00000000..f353a7cd --- /dev/null +++ b/judger/src/filesystem/mkdtemp.rs @@ -0,0 +1,33 @@ +use std::{ + ffi::{CStr, CString, OsStr}, + os::unix::ffi::OsStrExt, + path::{Path, PathBuf}, +}; + +use tokio::fs::remove_dir; + +pub struct MkdTemp(PathBuf); + +impl Drop for MkdTemp { + fn drop(&mut self) { + tokio::spawn(remove_dir(self.0.clone())); + } +} + +impl MkdTemp { + pub fn new() -> Self { + Self(unsafe { Self::new_inner("mdoj-XXXXXX") }) + } + pub unsafe fn new_inner(template: &str) -> PathBuf { + let template = CString::new(template).unwrap(); + let tmp_ptr = libc::mkdtemp(template.as_ptr() as *mut _); + let tmp_path = CStr::from_ptr(tmp_ptr); + let str_path = OsStr::from_bytes(tmp_path.to_bytes()); + drop(template); + // libc::free(tmp_ptr as *mut _); + PathBuf::from(str_path) + } + pub fn get_path(&self) -> &Path { + self.0.as_path() + } +} diff --git a/judger/src/filesystem/mod.rs b/judger/src/filesystem/mod.rs index 90c8e28e..4dcfc335 100644 --- a/judger/src/filesystem/mod.rs +++ b/judger/src/filesystem/mod.rs @@ -3,7 +3,9 @@ mod adapter; mod entry; mod error; +mod mkdtemp; mod resource; mod table; -pub use entry::prelude::*; +pub use adapter::{Filesystem, Template}; +pub use fuse3::raw::MountHandle; diff --git a/judger/src/filesystem/table.rs b/judger/src/filesystem/table.rs index fdcdd1e3..2550ebf7 100644 --- a/judger/src/filesystem/table.rs +++ b/judger/src/filesystem/table.rs @@ -16,10 +16,6 @@ pub fn to_internal_path<'a>(path: &'a Path) -> impl Iterator + 'a // .collect::>() } -pub trait DeepClone { - async fn deep_clone(&self) -> Self; -} - #[derive(Clone)] struct Node { parent_idx: usize, @@ -27,16 +23,6 @@ struct Node { children: BTreeMap, // FIXME: use BtreeMap } -impl DeepClone for Node { - async fn deep_clone(&self) -> Self { - Self { - parent_idx: self.parent_idx, - value: self.value.deep_clone().await, - children: self.children.iter().map(|(k, v)| (k.clone(), *v)).collect(), - } - } -} - #[derive(Clone)] pub struct AdjTable { by_id: Vec>, diff --git a/judger/src/language/config.rs b/judger/src/language/config.rs index f2352c6c..08cbf78d 100644 --- a/judger/src/language/config.rs +++ b/judger/src/language/config.rs @@ -1,23 +1,28 @@ -use std::{ffi::OsString, time::Duration}; +use std::{ffi::OsString, path::Path, time::Duration}; use serde::Deserialize; -use tokio::io::{AsyncRead, AsyncReadExt}; +use tokio::{ + fs::read_dir, + io::{AsyncRead, AsyncReadExt}, +}; use uuid::Uuid; use crate::sandbox::{Cpu, Memory}; -pub struct Config { +async fn load_plugin(path: impl AsRef) { + let dir_list = read_dir(path).await; +} + +pub struct Spec { pub compile_limit: (Cpu, Memory, u64, Duration), pub judge_limit: (Cpu, Memory, u64, Duration), pub compile_command: Vec, pub judge_command: Vec, } -impl Config { - async fn from_reader(mut reader: impl AsyncRead + Unpin) -> Self { - let mut buf = String::new(); - reader.read_to_string(&mut buf).await.unwrap(); - let mut raw: Raw = toml::from_str(&buf).unwrap(); +impl Spec { + pub fn from_str(content: &str) -> Self { + let mut raw: Raw = toml::from_str(content).unwrap(); raw.compile.fill(); raw.judge.fill(); diff --git a/judger/src/language/daemon.rs b/judger/src/language/daemon.rs index 931fdd84..67432f10 100644 --- a/judger/src/language/daemon.rs +++ b/judger/src/language/daemon.rs @@ -1,10 +1,10 @@ use std::collections::BTreeMap; +use tokio::{fs::File, sync::Semaphore}; use uuid::Uuid; use super::config::*; -use crate::semaphore::Semaphore; -use crate::CONFIG; +use crate::{filesystem::Template, CONFIG}; static PLUGIN_PATH: &str = "./plugins"; /// max queue judging task @@ -12,12 +12,17 @@ const MAX_QUEUE: usize = 10; pub struct Daemon { semaphore: Semaphore, - templates: BTreeMap, + templates: BTreeMap, +} + +struct Plugin { + config: Spec, + template: Template, } impl Daemon { pub fn new() -> Self { - let semaphore = Semaphore::new(CONFIG.memory, MAX_QUEUE); + let semaphore = Semaphore::new(todo!()); let mut templates = BTreeMap::new(); todo!("Load plugins"); // design a loader struct diff --git a/judger/src/language/mod.rs b/judger/src/language/mod.rs index 863bfb9c..589f46cc 100644 --- a/judger/src/language/mod.rs +++ b/judger/src/language/mod.rs @@ -1,2 +1,3 @@ mod config; mod daemon; +mod plugin; diff --git a/judger/src/language/plugin.rs b/judger/src/language/plugin.rs new file mode 100644 index 00000000..0764025f --- /dev/null +++ b/judger/src/language/plugin.rs @@ -0,0 +1,96 @@ +use std::{ + ffi::OsStr, + marker::PhantomData, + path::{Path, PathBuf}, + sync::Arc, +}; + +use rustix::path::Arg; +use tokio::fs::{read_dir, File}; + +use crate::{ + filesystem::{Filesystem, Template}, + sandbox::{Context as SandboxCtx, Filesystem as SandboxFS, Limit}, +}; + +use super::config::Spec; + +static EXTENSION: &str = "lang"; + +pub async fn load_plugins(path: impl AsRef) -> std::io::Result> { + let mut plugins = Vec::new(); + let mut dir_list = read_dir(path).await?; + while let Some(entry) = dir_list.next_entry().await? { + let path = entry.path(); + let ext = path.extension(); + if path.is_file() && ext.is_some() && ext.unwrap() == EXTENSION { + let plugin = Plugin::new(path).await?; + plugins.push(plugin); + } + } + Ok(plugins) +} + +pub struct Plugin { + spec: Spec, + template: Template, +} + +impl Plugin { + pub async fn new(path: impl AsRef + Clone) -> std::io::Result { + let template = Template::new(path.clone()).await?; + let spec_source = template.read_by_path("spec.toml").await.expect(&format!( + "sepc.toml not found in plugin {}", + path.as_ref().display() + )); + let spec = Spec::from_str(&spec_source.to_string_lossy()); + + Ok(Self { spec, template }) + } + pub async fn as_runner(self: Arc) -> PluginRunner { + PluginRunner { + source: self.clone(), + filesystem: self.template.as_filesystem(0), + _stage: PhantomData, + } + } +} + +pub struct Compile; +pub struct Execute; + +pub struct PluginRunner { + source: Arc, + filesystem: Filesystem, + _stage: PhantomData, +} + +impl SandboxCtx for PluginRunner { + type FS = PathBuf; + + fn create_fs(&mut self) -> Self::FS { + todo!() + } + + fn get_args(&mut self) -> impl Iterator { + self.source + .spec + .compile_command + .iter() + .map(|arg| arg.as_ref()) + } +} + +impl Limit for PluginRunner { + fn get_cpu(&mut self) -> crate::sandbox::Cpu { + todo!() + } + + fn get_memory(&mut self) -> crate::sandbox::Memory { + todo!() + } + + fn get_output(&mut self) -> u64 { + todo!() + } +} diff --git a/judger/src/main.rs b/judger/src/main.rs index 53cfe9f6..d00a56dc 100644 --- a/judger/src/main.rs +++ b/judger/src/main.rs @@ -2,9 +2,6 @@ mod config; mod filesystem; mod language; mod sandbox; -mod semaphore; - -use std::sync::Arc; pub use config::CONFIG; diff --git a/judger/src/sandbox/mod.rs b/judger/src/sandbox/mod.rs index 572c6883..27fe20c7 100644 --- a/judger/src/sandbox/mod.rs +++ b/judger/src/sandbox/mod.rs @@ -2,7 +2,11 @@ mod error; mod monitor; mod process; -use std::{ffi::OsStr, path::Path, time::Duration}; +use std::{ + ffi::OsStr, + path::{Path, PathBuf}, + time::Duration, +}; pub use self::monitor::{Cpu, Memory}; pub use error::Error; @@ -26,7 +30,12 @@ pub trait Limit { pub trait Filesystem { fn mount(&mut self) -> impl AsRef + Send; - fn get_size(&mut self) -> u64; +} + +impl Filesystem for PathBuf { + fn mount(&mut self) -> impl AsRef + Send { + self.as_path().iter() + } } impl Limit for (Cpu, Memory, u64, Duration) { diff --git a/judger/src/semaphore.rs b/judger/src/semaphore.rs deleted file mode 100644 index cf79f867..00000000 --- a/judger/src/semaphore.rs +++ /dev/null @@ -1,199 +0,0 @@ -use std::{ - fmt::Debug, - sync::{ - atomic::{self, Ordering}, - Arc, - }, -}; - -use spin::Mutex; -use tokio::sync::oneshot::*; - -#[derive(Debug, thiserror::Error, PartialEq)] -pub enum Error { - #[error("Max wait reached")] - MaxWaitReached, - #[error("Impossible to get the permit")] - ImpossibleResourceCondition, -} - -// impl From for CrateError { -// fn from(value: Error) -> CrateError { -// match value { -// Error::MaxWaitReached => CrateError::Insufficient("queuing quota"), -// Error::ImpossibleResourceCondition => CrateError::Insufficient("memory"), -// } -// } -// } - -struct SemaphoreInner { - permits: atomic::AtomicU64, - all_permits: u64, - max_wait: usize, - waiters: Mutex>)>>, -} - -#[derive(Clone)] -pub struct Semaphore(Arc); - -impl Semaphore { - /// Create a new asynchronous semaphore with the given number of permits. - /// - /// asynchronous semaphore is a synchronization primitive that limits the number of concurrent, - /// instead of blocking the thread, yeild to scheduler and wait for the permit. - /// - /// Note that there is no preemption. - pub fn new(all_permits: u64, max_wait: usize) -> Self { - Semaphore(Arc::new(SemaphoreInner { - permits: atomic::AtomicU64::new(all_permits), - all_permits, - max_wait, - waiters: Mutex::new(Vec::new()), - })) - } - /// get a permit from semaphore - /// - /// It return None if - /// 1. It's impossible to get the permit even no other task is holding the permit - /// 2. The number of waiting task is greater than max_wait - pub async fn get_permit(&self, permit: u64) -> Result { - // FIXME: return Result to differentiate between max_wait_reached and impossible_resource_condition - if permit > self.0.all_permits { - return Err(Error::ImpossibleResourceCondition); - } - let (tx, rx) = channel::<()>(); - { - let mut waiter = self.0.waiters.lock(); - if waiter.len() >= self.0.max_wait { - return Err(Error::MaxWaitReached); - } - waiter.push((permit, Some(tx))); - } - - self.try_wake(); - - rx.await.ok().expect("Channel closed"); - - Ok(Permit { - semaphore: self.clone(), - permit, - }) - } - fn release(&self, permit: u64) { - self.0.permits.fetch_add(permit, Ordering::Relaxed); - self.try_wake(); - } - fn try_wake(&self) { - let mut waiter = self.0.waiters.lock(); - if let Some((permit, ref mut waker)) = waiter.last_mut() { - let mut current = self.0.permits.load(Ordering::Acquire); - loop { - if current < *permit { - return; - } - if let Err(x) = self.0.permits.compare_exchange( - current, - current - *permit, - Ordering::SeqCst, - Ordering::Acquire, - ) { - current = x; - } else { - break; - }; - } - if waker.take().unwrap().send(()).is_err() { - log::warn!("Semaphore waiter disconnected"); - } - waiter.pop(); - } - } -} - -pub struct Permit { - semaphore: Semaphore, - permit: u64, -} - -impl Permit { - #[inline] - pub fn merge(&mut self, mut other: Permit) { - self.permit += other.permit; - other.permit = 0; - } - pub async fn add(&mut self, permit: u64) -> Result<(), Error> { - let other = self.semaphore.get_permit(permit).await?; - self.merge(other); - Ok(()) - } - pub fn count(&self) -> u64 { - self.permit - } -} - -impl Drop for Permit { - fn drop(&mut self) { - self.semaphore.release(self.permit); - } -} - -impl Debug for Permit { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Permit") - .field("permit", &self.permit) - .finish() - } -} - -impl PartialEq for Permit { - fn eq(&self, other: &Self) -> bool { - self.permit == other.permit - } -} - -#[cfg(test)] -mod test { - use tokio::time; - - use super::*; - #[tokio::test] - /// test [`Semaphore::get_permit`] return [`Err(Error::ImpossibleResourceCondition)`] when max_wait is reached - async fn get_permit_max() { - let semaphore = Semaphore::new(1024, 1024); - assert!(semaphore.get_permit(1024).await.is_ok()); - assert_eq!( - Err(Error::ImpossibleResourceCondition), - semaphore.get_permit(1025).await - ); - } - #[tokio::test] - /// test [`Semaphore::get_permit`] to ensure permit is distributed in order - /// (First come first serve, no matter amount of permit requested) - async fn get_permit_unorder() { - let semaphore = Semaphore::new(1024, 1024); - let permit = semaphore.get_permit(1).await.unwrap(); - let permit1 = tokio::spawn(async move { - tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; - semaphore.get_permit(1024).await - }); - drop(permit); - assert!(permit1.await.unwrap().is_ok()); - } - #[tokio::test] - /// test [`Semaphore::get_permit`] return [`Err(Error::MaxWaitReached)`] when max_wait is reached - async fn get_permit_max_wait() { - let semaphore = Semaphore::new(1024, 1); - let permit = semaphore.get_permit(1).await.unwrap(); - - let semaphore1 = semaphore.clone(); - - let _ = tokio::spawn(async move { - semaphore.get_permit(1024).await.unwrap(); - }); - - time::sleep(time::Duration::from_millis(4)).await; - assert_eq!(Err(Error::MaxWaitReached), semaphore1.get_permit(1).await); - - drop(permit); - } -} From db5faa9049bffec99f399da771a067c212b7ddfe Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Sun, 12 May 2024 23:30:22 +0800 Subject: [PATCH 25/38] refactor(Judger): :memo: small change to interface expose by filesystem and sandbox for less boilerplate of consumer --- judger/src/error.rs | 9 +++++ judger/src/filesystem/adapter/fuse.rs | 5 ++- judger/src/filesystem/adapter/mod.rs | 2 +- judger/src/filesystem/error.rs | 1 - judger/src/filesystem/handle.rs | 35 ++++++++++++++++++ judger/src/filesystem/mkdtemp.rs | 30 +++++++++++---- judger/src/filesystem/mod.rs | 6 +-- judger/src/main.rs | 2 + judger/src/sandbox/mod.rs | 9 +++-- judger/src/sandbox/monitor/stat.rs | 53 ++++++++++++++++++++++++++- judger/src/sandbox/process/corpse.rs | 3 ++ judger/src/sandbox/process/mod.rs | 3 ++ judger/src/sandbox/process/process.rs | 9 +++-- 13 files changed, 147 insertions(+), 20 deletions(-) delete mode 100644 judger/src/filesystem/error.rs create mode 100644 judger/src/filesystem/handle.rs diff --git a/judger/src/error.rs b/judger/src/error.rs index e69de29b..0f1c82c2 100644 --- a/judger/src/error.rs +++ b/judger/src/error.rs @@ -0,0 +1,9 @@ +use super::sandbox::Error as SandboxError; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("io error: {0}")] + Io(#[from] std::io::Error), + #[error("sandbox error: {0}")] + Sandbox(#[from] SandboxError), +} diff --git a/judger/src/filesystem/adapter/fuse.rs b/judger/src/filesystem/adapter/fuse.rs index ee2ed6bd..f4f8f344 100644 --- a/judger/src/filesystem/adapter/fuse.rs +++ b/judger/src/filesystem/adapter/fuse.rs @@ -35,7 +35,10 @@ where resource: Arc::new(Resource::new(permit)), } } - pub async fn mount(self, path: impl AsRef + Clone) -> std::io::Result { + pub async fn mount_with_path( + self, + path: impl AsRef + Clone, + ) -> std::io::Result { let uid = unsafe { libc::getuid() }; let gid = unsafe { libc::getgid() }; diff --git a/judger/src/filesystem/adapter/mod.rs b/judger/src/filesystem/adapter/mod.rs index f7e8e20f..200cd929 100644 --- a/judger/src/filesystem/adapter/mod.rs +++ b/judger/src/filesystem/adapter/mod.rs @@ -24,7 +24,7 @@ mod test { log::info!("mounting test tarball in .temp ..."); let template = Template::new("test/nested.tar").await.unwrap(); let filesystem = template.as_filesystem(1024 * 1024 * 1024); - let mut mount_handle = filesystem.mount("./.temp/1").await.unwrap(); + let mut mount_handle = filesystem.mount_with_path("./.temp/1").await.unwrap(); let handle = &mut mount_handle; tokio::select! { diff --git a/judger/src/filesystem/error.rs b/judger/src/filesystem/error.rs deleted file mode 100644 index 8b137891..00000000 --- a/judger/src/filesystem/error.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/judger/src/filesystem/handle.rs b/judger/src/filesystem/handle.rs new file mode 100644 index 00000000..227a6385 --- /dev/null +++ b/judger/src/filesystem/handle.rs @@ -0,0 +1,35 @@ +use super::adapter::Filesystem; + +use tokio::io::{AsyncRead, AsyncSeek}; + +use super::mkdtemp::MkdTemp; + +pub struct MountHandle(Option, Option); + +impl MountHandle { + pub fn get_path(&self) -> &std::path::Path { + self.1.as_ref().unwrap().get_path() + } +} + +impl Drop for MountHandle { + fn drop(&mut self) { + let handle = self.0.take().unwrap(); + let mountpoint = self.1.take().unwrap(); + tokio::spawn(async move { + handle.unmount().await.unwrap(); + drop(mountpoint); + }); + } +} + +impl Filesystem +where + F: AsyncRead + AsyncSeek + Unpin + Send + Sync + 'static, +{ + pub async fn mount(self) -> std::io::Result { + let mountpoint = MkdTemp::new(); + let handle = self.mount_with_path(mountpoint.get_path()).await?; + Ok(MountHandle(Some(handle), Some(mountpoint))) + } +} diff --git a/judger/src/filesystem/mkdtemp.rs b/judger/src/filesystem/mkdtemp.rs index f353a7cd..9fc2d615 100644 --- a/judger/src/filesystem/mkdtemp.rs +++ b/judger/src/filesystem/mkdtemp.rs @@ -1,5 +1,5 @@ use std::{ - ffi::{CStr, CString, OsStr}, + ffi::{CString, OsStr}, os::unix::ffi::OsStrExt, path::{Path, PathBuf}, }; @@ -16,18 +16,34 @@ impl Drop for MkdTemp { impl MkdTemp { pub fn new() -> Self { - Self(unsafe { Self::new_inner("mdoj-XXXXXX") }) + Self(unsafe { Self::new_inner("/tmp/mdoj-fs-runtime-XXXXXX") }) } pub unsafe fn new_inner(template: &str) -> PathBuf { let template = CString::new(template).unwrap(); - let tmp_ptr = libc::mkdtemp(template.as_ptr() as *mut _); - let tmp_path = CStr::from_ptr(tmp_ptr); - let str_path = OsStr::from_bytes(tmp_path.to_bytes()); - drop(template); - // libc::free(tmp_ptr as *mut _); + libc::mkdtemp(template.as_ptr() as *mut _); + let str_path = OsStr::from_bytes(template.to_bytes()); PathBuf::from(str_path) } pub fn get_path(&self) -> &Path { self.0.as_path() } } + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use tokio::time::sleep; + + use super::*; + + #[tokio::test] + async fn test_mkdtemp() { + let tmp = MkdTemp::new(); + let path = tmp.get_path().to_path_buf(); + println!("{:?}", path); + drop(tmp); + sleep(Duration::from_millis(20)).await; + assert!(!path.exists()); + } +} diff --git a/judger/src/filesystem/mod.rs b/judger/src/filesystem/mod.rs index 4dcfc335..3393101d 100644 --- a/judger/src/filesystem/mod.rs +++ b/judger/src/filesystem/mod.rs @@ -1,11 +1,11 @@ -//! Filesystem module that is mountable(actuall mount and +//! Filesystem module that is mountable(actuallly mount and //! is accessible for user in this operation system) mod adapter; mod entry; -mod error; +mod handle; mod mkdtemp; mod resource; mod table; pub use adapter::{Filesystem, Template}; -pub use fuse3::raw::MountHandle; +pub use handle::MountHandle; diff --git a/judger/src/main.rs b/judger/src/main.rs index d00a56dc..f0549033 100644 --- a/judger/src/main.rs +++ b/judger/src/main.rs @@ -1,8 +1,10 @@ mod config; +mod error; mod filesystem; mod language; mod sandbox; pub use config::CONFIG; +type Result = std::result::Result; fn main() {} diff --git a/judger/src/sandbox/mod.rs b/judger/src/sandbox/mod.rs index 27fe20c7..8c84955b 100644 --- a/judger/src/sandbox/mod.rs +++ b/judger/src/sandbox/mod.rs @@ -10,12 +10,15 @@ use std::{ pub use self::monitor::{Cpu, Memory}; pub use error::Error; +pub use monitor::MonitorKind; +pub use process::{Corpse, Process}; + /// Context of the sandbox /// /// define resource limit and filesystem is out of the scope of `filesystem` pub trait Context: Limit { type FS: Filesystem; - fn create_fs(&mut self) -> Self::FS; + fn get_fs(&mut self) -> Self::FS; fn get_args(&mut self) -> impl Iterator; } @@ -29,11 +32,11 @@ pub trait Limit { } pub trait Filesystem { - fn mount(&mut self) -> impl AsRef + Send; + fn get_path(&mut self) -> impl AsRef + Send; } impl Filesystem for PathBuf { - fn mount(&mut self) -> impl AsRef + Send { + fn get_path(&mut self) -> impl AsRef + Send { self.as_path().iter() } } diff --git a/judger/src/sandbox/monitor/stat.rs b/judger/src/sandbox/monitor/stat.rs index 76aed626..150f0dea 100644 --- a/judger/src/sandbox/monitor/stat.rs +++ b/judger/src/sandbox/monitor/stat.rs @@ -1,4 +1,7 @@ -use std::time::Duration; +use std::{ + ops::{Div, Mul}, + time::Duration, +}; use cgroups_rs::cpuacct::CpuAcct; @@ -22,6 +25,30 @@ pub struct Memory { pub total: u64, } +impl Mul for Memory { + type Output = Memory; + + fn mul(self, rhs: f64) -> Self::Output { + Memory { + kernel: (self.kernel as f64 * rhs) as u64, + user: (self.user as f64 * rhs) as u64, + total: (self.total as f64 * rhs) as u64, + } + } +} + +impl Div for Memory { + type Output = Memory; + + fn div(self, rhs: f64) -> Self::Output { + Memory { + kernel: (self.kernel as f64 / rhs) as u64, + user: (self.user as f64 / rhs) as u64, + total: (self.total as f64 / rhs) as u64, + } + } +} + impl Memory { pub fn get_reserved_size(&self) -> u64 { self.total.min(self.user + self.kernel) @@ -36,6 +63,30 @@ pub struct Cpu { pub total: u64, } +impl Mul for Cpu { + type Output = Cpu; + + fn mul(self, rhs: f64) -> Self::Output { + Cpu { + kernel: (self.kernel as f64 * rhs) as u64, + user: (self.user as f64 * rhs) as u64, + total: (self.total as f64 * rhs) as u64, + } + } +} + +impl Div for Cpu { + type Output = Cpu; + + fn div(self, rhs: f64) -> Self::Output { + Cpu { + kernel: (self.kernel as f64 / rhs) as u64, + user: (self.user as f64 / rhs) as u64, + total: (self.total as f64 / rhs) as u64, + } + } +} + impl Cpu { pub(super) fn out_of_resources(resource: &Self, stat: Self) -> bool { stat.kernel > resource.kernel || stat.user > resource.user || stat.total > resource.total diff --git a/judger/src/sandbox/process/corpse.rs b/judger/src/sandbox/process/corpse.rs index f96d36d2..961004e6 100644 --- a/judger/src/sandbox/process/corpse.rs +++ b/judger/src/sandbox/process/corpse.rs @@ -35,4 +35,7 @@ impl Corpse { pub fn stat(&self) -> &Stat { &self.stat } + pub fn success(&self) -> bool { + self.reason.is_none() && self.code.is_some() && self.code.unwrap().success() + } } diff --git a/judger/src/sandbox/process/mod.rs b/judger/src/sandbox/process/mod.rs index b67be480..8dcf3834 100644 --- a/judger/src/sandbox/process/mod.rs +++ b/judger/src/sandbox/process/mod.rs @@ -18,3 +18,6 @@ mod nsjail; mod process; use super::*; + +pub use corpse::*; +pub use process::*; diff --git a/judger/src/sandbox/process/process.rs b/judger/src/sandbox/process/process.rs index 888f77cf..0e49d3db 100644 --- a/judger/src/sandbox/process/process.rs +++ b/judger/src/sandbox/process/process.rs @@ -14,7 +14,7 @@ struct MountedProcess { impl MountedProcess { fn new(mut context: C) -> Self { Self { - fs: context.create_fs(), + fs: context.get_fs(), context, } } @@ -91,7 +91,7 @@ impl Process { cg_name: self.monitor.get_cg_path(), }) .add(MountArg { - rootfs: self.fs.mount().as_ref().as_os_str(), + rootfs: self.fs.get_path().as_ref().as_os_str(), }) .add(InnerProcessArg { inner_args: self.context.get_args(), @@ -109,7 +109,7 @@ impl Process { tokio::spawn(async move { stdin.write_all(&input).await }); let stdout = process.stdout.take().unwrap(); - tokio::spawn(async move { + let io_proxy=tokio::spawn(async move { let mut stdout = stdout; if let Err(err) = io::copy(&mut stdout, &mut self.stdout).await { log::debug!("Fail forwarding buffer: {}", err); @@ -123,6 +123,9 @@ impl Process { time::sleep(time::Duration::from_millis(100)).await; Some(x?)} }; + // wait for the proxy to finish for full output + // in case of OLE, the monitor will drop and the proxy will be cancelled(yield) + io_proxy.await.unwrap(); Ok(Corpse { code, From fc7cf4ce136966da30c45f362faf63657725ac56 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Sun, 12 May 2024 23:31:00 +0800 Subject: [PATCH 26/38] feat(Judger): :construction: draft timelife of a runner --- judger/src/language/daemon.rs | 2 +- judger/src/language/mod.rs | 3 +- judger/src/language/plugin.rs | 75 +++++----------------- judger/src/language/{config.rs => spec.rs} | 7 +- judger/src/language/stage/assert.rs | 51 +++++++++++++++ judger/src/language/stage/compile.rs | 66 +++++++++++++++++++ judger/src/language/stage/judge.rs | 63 ++++++++++++++++++ judger/src/language/stage/mod.rs | 6 ++ judger/src/sandbox/process/process.rs | 2 +- 9 files changed, 211 insertions(+), 64 deletions(-) rename judger/src/language/{config.rs => spec.rs} (94%) create mode 100644 judger/src/language/stage/assert.rs create mode 100644 judger/src/language/stage/compile.rs create mode 100644 judger/src/language/stage/judge.rs create mode 100644 judger/src/language/stage/mod.rs diff --git a/judger/src/language/daemon.rs b/judger/src/language/daemon.rs index 67432f10..7e06efd1 100644 --- a/judger/src/language/daemon.rs +++ b/judger/src/language/daemon.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; use tokio::{fs::File, sync::Semaphore}; use uuid::Uuid; -use super::config::*; +use super::spec::*; use crate::{filesystem::Template, CONFIG}; static PLUGIN_PATH: &str = "./plugins"; diff --git a/judger/src/language/mod.rs b/judger/src/language/mod.rs index 589f46cc..04d47985 100644 --- a/judger/src/language/mod.rs +++ b/judger/src/language/mod.rs @@ -1,3 +1,4 @@ -mod config; mod daemon; mod plugin; +mod spec; +mod stage; diff --git a/judger/src/language/plugin.rs b/judger/src/language/plugin.rs index 0764025f..95d375ef 100644 --- a/judger/src/language/plugin.rs +++ b/judger/src/language/plugin.rs @@ -1,23 +1,16 @@ -use std::{ - ffi::OsStr, - marker::PhantomData, - path::{Path, PathBuf}, - sync::Arc, -}; +use std::{path::Path, sync::Arc}; use rustix::path::Arg; use tokio::fs::{read_dir, File}; -use crate::{ - filesystem::{Filesystem, Template}, - sandbox::{Context as SandboxCtx, Filesystem as SandboxFS, Limit}, -}; +use crate::filesystem::*; -use super::config::Spec; +use super::{spec::Spec, stage::CompileRunner}; +use crate::Result; static EXTENSION: &str = "lang"; -pub async fn load_plugins(path: impl AsRef) -> std::io::Result> { +pub async fn load_plugins(path: impl AsRef) -> Result> { let mut plugins = Vec::new(); let mut dir_list = read_dir(path).await?; while let Some(entry) = dir_list.next_entry().await? { @@ -32,65 +25,27 @@ pub async fn load_plugins(path: impl AsRef) -> std::io::Result } pub struct Plugin { - spec: Spec, + spec: Arc, template: Template, } impl Plugin { - pub async fn new(path: impl AsRef + Clone) -> std::io::Result { + pub async fn new(path: impl AsRef + Clone) -> Result { let template = Template::new(path.clone()).await?; let spec_source = template.read_by_path("spec.toml").await.expect(&format!( "sepc.toml not found in plugin {}", path.as_ref().display() )); - let spec = Spec::from_str(&spec_source.to_string_lossy()); + let spec = Arc::new(Spec::from_str(&spec_source.to_string_lossy())); Ok(Self { spec, template }) } - pub async fn as_runner(self: Arc) -> PluginRunner { - PluginRunner { - source: self.clone(), - filesystem: self.template.as_filesystem(0), - _stage: PhantomData, - } - } -} - -pub struct Compile; -pub struct Execute; - -pub struct PluginRunner { - source: Arc, - filesystem: Filesystem, - _stage: PhantomData, -} - -impl SandboxCtx for PluginRunner { - type FS = PathBuf; - - fn create_fs(&mut self) -> Self::FS { - todo!() - } - - fn get_args(&mut self) -> impl Iterator { - self.source - .spec - .compile_command - .iter() - .map(|arg| arg.as_ref()) - } -} - -impl Limit for PluginRunner { - fn get_cpu(&mut self) -> crate::sandbox::Cpu { - todo!() - } - - fn get_memory(&mut self) -> crate::sandbox::Memory { - todo!() - } - - fn get_output(&mut self) -> u64 { - todo!() + pub async fn as_runner(self: Arc) -> Result { + let filesystem = self + .template + .as_filesystem(self.spec.fs_limit) + .mount() + .await?; + Ok(CompileRunner::new(self.spec.clone(), filesystem)) } } diff --git a/judger/src/language/config.rs b/judger/src/language/spec.rs similarity index 94% rename from judger/src/language/config.rs rename to judger/src/language/spec.rs index 08cbf78d..6a634ef4 100644 --- a/judger/src/language/config.rs +++ b/judger/src/language/spec.rs @@ -14,19 +14,24 @@ async fn load_plugin(path: impl AsRef) { } pub struct Spec { + pub fs_limit: u64, pub compile_limit: (Cpu, Memory, u64, Duration), - pub judge_limit: (Cpu, Memory, u64, Duration), + judge_limit: (Cpu, Memory, u64, Duration), pub compile_command: Vec, pub judge_command: Vec, } impl Spec { + pub fn get_judge_limit(&self, cpu: Cpu, mem: Memory) -> (Cpu, Memory, u64, Duration) { + todo!() + } pub fn from_str(content: &str) -> Self { let mut raw: Raw = toml::from_str(content).unwrap(); raw.compile.fill(); raw.judge.fill(); Self { + fs_limit: todo!(), compile_limit: ( Cpu { kernel: todo!(), diff --git a/judger/src/language/stage/assert.rs b/judger/src/language/stage/assert.rs new file mode 100644 index 00000000..96ef7991 --- /dev/null +++ b/judger/src/language/stage/assert.rs @@ -0,0 +1,51 @@ +use std::sync::Arc; + +use crate::{ + language::spec::Spec, + sandbox::{Corpse, MonitorKind}, +}; + +pub enum AssertionMode { + SkipSpace, + SkipContinousSpace, + Exact, +} + +pub enum AssertResult { + Accept, + WrongAnswer, + RuntimeError, + TimeLimitExceeded, + MemoryLimitExceeded, + OutputLimitExceeded, + RealTimeLimitExceeded, + CompileError, + SystemError, +} +pub struct AssertRunner { + pub spec: Arc, + pub corpse: Corpse, +} + +impl AssertRunner { + pub fn new(spec: Arc, corpse: Corpse) -> Self { + Self { spec, corpse } + } + fn assert_output(&self, output: &[u8], mode: AssertionMode) -> AssertResult { + todo!() + } + pub fn get_result(&self, output: &[u8], mode: AssertionMode) -> AssertResult { + match self.corpse.status() { + Ok(status) => match status.success() { + true => self.assert_output(output, mode), + false => AssertResult::WrongAnswer, + }, + Err(reason) => match reason { + MonitorKind::Cpu => AssertResult::TimeLimitExceeded, + MonitorKind::Memory => AssertResult::MemoryLimitExceeded, + MonitorKind::Output => AssertResult::OutputLimitExceeded, + MonitorKind::Walltime => AssertResult::RealTimeLimitExceeded, + }, + } + } +} diff --git a/judger/src/language/stage/compile.rs b/judger/src/language/stage/compile.rs new file mode 100644 index 00000000..d63d421a --- /dev/null +++ b/judger/src/language/stage/compile.rs @@ -0,0 +1,66 @@ +use std::{path::PathBuf, sync::Arc, time::Duration}; + +use crate::{ + filesystem::MountHandle, + language::spec::Spec, + sandbox::{Context, Limit, Process}, + Result, +}; + +use super::JudgeRunner; + +pub struct CompileRunner { + spec: Arc, + handle: MountHandle, +} + +impl CompileRunner { + pub fn new(spec: Arc, handle: MountHandle) -> Self { + Self { spec, handle } + } + pub async fn run(self) -> Result> { + let ctx = CompileCtx { + spec: self.spec.clone(), + path: self.handle.get_path().to_path_buf(), + }; + let process = Process::new(ctx)?; + let corpse = process.wait(Vec::new()).await?; + if !corpse.success() { + log::debug!("compile failed {:?}", corpse.status()); + return Ok(None); + } + + let runner = JudgeRunner::new(self.handle, self.spec); + Ok(Some(runner)) + } +} + +struct CompileCtx { + spec: Arc, + path: PathBuf, +} + +impl Limit for CompileCtx { + fn get_cpu(&mut self) -> crate::sandbox::Cpu { + todo!() + } + fn get_memory(&mut self) -> crate::sandbox::Memory { + todo!() + } + fn get_output(&mut self) -> u64 { + todo!() + } + fn get_walltime(&mut self) -> Duration { + todo!() + } +} + +impl Context for CompileCtx { + type FS = PathBuf; + fn get_fs(&mut self) -> Self::FS { + self.path.clone() + } + fn get_args(&mut self) -> impl Iterator { + self.spec.compile_command.iter().map(|arg| arg.as_os_str()) + } +} diff --git a/judger/src/language/stage/judge.rs b/judger/src/language/stage/judge.rs new file mode 100644 index 00000000..1ad41fc8 --- /dev/null +++ b/judger/src/language/stage/judge.rs @@ -0,0 +1,63 @@ +use std::{path::PathBuf, sync::Arc, time::Duration}; + +use crate::{ + filesystem::MountHandle, + language::spec::Spec, + sandbox::{Context, Cpu, Limit, Memory, Process}, + Result, +}; + +use super::assert::AssertRunner; + +pub struct JudgeRunner { + filesystem: MountHandle, + spec: Arc, +} + +impl JudgeRunner { + pub fn new(filesystem: MountHandle, spec: Arc) -> Self { + Self { filesystem, spec } + } + pub async fn run(self, (mem, cpu): (Memory, Cpu), input: Vec) -> Result { + let ctx = JudgeCtx { + spec: self.spec.clone(), + path: self.filesystem.get_path().to_path_buf(), + limit: self.spec.get_judge_limit(cpu, mem), + }; + let process = Process::new(ctx)?; + let corpse = process.wait(input).await?; + drop(self.filesystem); + Ok(AssertRunner::new(self.spec, corpse)) + } +} + +struct JudgeCtx { + spec: Arc, + path: std::path::PathBuf, + limit: (Cpu, Memory, u64, Duration), +} + +impl Limit for JudgeCtx { + fn get_cpu(&mut self) -> Cpu { + self.limit.0.clone() + } + fn get_memory(&mut self) -> Memory { + self.limit.1.clone() + } + fn get_output(&mut self) -> u64 { + self.limit.2 + } + fn get_walltime(&mut self) -> Duration { + self.limit.3 + } +} + +impl Context for JudgeCtx { + type FS = PathBuf; + fn get_fs(&mut self) -> Self::FS { + self.path.clone() + } + fn get_args(&mut self) -> impl Iterator { + self.spec.judge_command.iter().map(|s| s.as_ref()) + } +} diff --git a/judger/src/language/stage/mod.rs b/judger/src/language/stage/mod.rs new file mode 100644 index 00000000..2d12e1b2 --- /dev/null +++ b/judger/src/language/stage/mod.rs @@ -0,0 +1,6 @@ +mod assert; +mod compile; +mod judge; + +pub use compile::CompileRunner; +pub use judge::JudgeRunner; diff --git a/judger/src/sandbox/process/process.rs b/judger/src/sandbox/process/process.rs index 0e49d3db..099c95b4 100644 --- a/judger/src/sandbox/process/process.rs +++ b/judger/src/sandbox/process/process.rs @@ -109,7 +109,7 @@ impl Process { tokio::spawn(async move { stdin.write_all(&input).await }); let stdout = process.stdout.take().unwrap(); - let io_proxy=tokio::spawn(async move { + let io_proxy = tokio::spawn(async move { let mut stdout = stdout; if let Err(err) = io::copy(&mut stdout, &mut self.stdout).await { log::debug!("Fail forwarding buffer: {}", err); From 74daeda427cf4bcdc6d62e55a54a0490659e547d Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Mon, 13 May 2024 10:05:13 +0800 Subject: [PATCH 27/38] feat(Judger): :sparkles: add compile and assert logic for language --- judger/src/filesystem/adapter/fuse.rs | 11 +++- judger/src/filesystem/entry/mod.rs | 3 + judger/src/language/builder.rs | 30 ++++++++++ judger/src/language/mod.rs | 1 + judger/src/language/plugin.rs | 70 ++++++++++++++++++---- judger/src/language/spec.rs | 65 +++++++++++++-------- judger/src/language/stage/assert.rs | 51 ---------------- judger/src/language/stage/compile.rs | 10 ++-- judger/src/language/stage/judge.rs | 84 ++++++++++----------------- judger/src/language/stage/mod.rs | 25 +++++++- judger/src/language/stage/run.rs | 63 ++++++++++++++++++++ judger/src/sandbox/mod.rs | 2 +- judger/src/sandbox/monitor/stat.rs | 1 + 13 files changed, 269 insertions(+), 147 deletions(-) create mode 100644 judger/src/language/builder.rs delete mode 100644 judger/src/language/stage/assert.rs create mode 100644 judger/src/language/stage/run.rs diff --git a/judger/src/filesystem/adapter/fuse.rs b/judger/src/filesystem/adapter/fuse.rs index f4f8f344..4f694eb1 100644 --- a/judger/src/filesystem/adapter/fuse.rs +++ b/judger/src/filesystem/adapter/fuse.rs @@ -3,10 +3,11 @@ use std::{ffi::OsStr, num::NonZeroU32, path::Path, sync::Arc}; use futures_core::Future; use spin::Mutex; use tokio::io::{AsyncRead, AsyncSeek}; -use tokio::sync::{Mutex as AsyncMutex, OwnedSemaphorePermit}; +use tokio::sync::Mutex as AsyncMutex; use crate::filesystem::entry::{Entry, TarTree, BLOCKSIZE}; use crate::filesystem::resource::Resource; +use crate::filesystem::table::to_internal_path; use super::{error::FuseError, handle::HandleTable, reply::*}; use fuse3::{ @@ -50,6 +51,14 @@ where .mount_with_unprivileged(self, path.as_ref()) .await } + pub fn insert_by_path(&self, path: impl AsRef, content: Vec) { + let mut tree = self.tree.lock(); + tree.insert_by_path( + to_internal_path(path.as_ref()), + || Entry::Directory, + Entry::new_file_with_content(content), + ); + } } impl fuse3::raw::Filesystem for Filesystem diff --git a/judger/src/filesystem/entry/mod.rs b/judger/src/filesystem/entry/mod.rs index 53104ef2..425bd77a 100644 --- a/judger/src/filesystem/entry/mod.rs +++ b/judger/src/filesystem/entry/mod.rs @@ -68,6 +68,9 @@ where pub fn new_file() -> Self { Self::MemFile(MemBlock::default()) } + pub fn new_file_with_content(content: Vec) -> Self { + Self::MemFile(MemBlock::new(content)) + } pub fn kind(&self) -> FileType { match self { Self::SymLink(_) => FileType::Symlink, diff --git a/judger/src/language/builder.rs b/judger/src/language/builder.rs new file mode 100644 index 00000000..d6e36d3e --- /dev/null +++ b/judger/src/language/builder.rs @@ -0,0 +1,30 @@ +use super::stage::{AssertionMode, StatusCode}; + +pub struct JudgeArgs { + pub(super) mem: u64, + pub(super) cpu: u64, + pub(super) input: Vec, + pub(super) output: Vec, + pub(super) mode: AssertionMode, + pub(super) source: Vec, +} + +pub struct ExecuteArgs { + pub(super) mem: u64, + pub(super) cpu: u64, + pub(super) input: Vec, + pub(super) source: Vec, +} + +pub struct JudgeResult { + pub status: StatusCode, + pub time: u64, + pub memory: u64, +} + +pub struct ExecuteResult { + pub time: u64, + pub memory: u64, + pub output: Vec, + pub code: i32, +} diff --git a/judger/src/language/mod.rs b/judger/src/language/mod.rs index 04d47985..d607b035 100644 --- a/judger/src/language/mod.rs +++ b/judger/src/language/mod.rs @@ -1,3 +1,4 @@ +mod builder; mod daemon; mod plugin; mod spec; diff --git a/judger/src/language/plugin.rs b/judger/src/language/plugin.rs index 95d375ef..7387b70a 100644 --- a/judger/src/language/plugin.rs +++ b/judger/src/language/plugin.rs @@ -3,9 +3,16 @@ use std::{path::Path, sync::Arc}; use rustix::path::Arg; use tokio::fs::{read_dir, File}; -use crate::filesystem::*; +use crate::{ + filesystem::*, + sandbox::{Cpu, Memory}, +}; -use super::{spec::Spec, stage::CompileRunner}; +use super::{ + builder::*, + spec::Spec, + stage::{AssertionMode, Compiler, StatusCode}, +}; use crate::Result; static EXTENSION: &str = "lang"; @@ -24,9 +31,19 @@ pub async fn load_plugins(path: impl AsRef) -> Result> { Ok(plugins) } +impl JudgeResult { + fn new(status: StatusCode) -> Self { + Self { + status, + time: 0, + memory: 0, + } + } +} + pub struct Plugin { - spec: Arc, - template: Template, + pub(super) spec: Arc, + pub(super) template: Template, } impl Plugin { @@ -40,12 +57,43 @@ impl Plugin { Ok(Self { spec, template }) } - pub async fn as_runner(self: Arc) -> Result { - let filesystem = self - .template - .as_filesystem(self.spec.fs_limit) - .mount() - .await?; - Ok(CompileRunner::new(self.spec.clone(), filesystem)) + pub async fn as_compiler(self: Arc, source: Vec) -> Result { + let filesystem = self.template.as_filesystem(self.spec.fs_limit); + filesystem.insert_by_path(self.spec.file.as_os_str(), source); + Ok(Compiler::new(self.spec.clone(), filesystem.mount().await?)) + } + pub async fn judge(self: Arc, args: JudgeArgs) -> Result { + // for judge: it has three stages: compile, run, judge + let compiler = self.as_compiler(args.source).await?; + Ok(match compiler.compile().await? { + Some(runner) => { + let judger = runner.run((args.mem, args.cpu), args.input).await?; + let status = judger.get_result(&args.output, args.mode); + + let stat = judger.stat(); + + JudgeResult { + status, + time: stat.cpu.total, + memory: stat.memory.total, + } + } + None => JudgeResult::new(StatusCode::CompileError), + }) + } + pub async fn execute(self: Arc, args: ExecuteArgs) -> Result> { + let compiler = self.as_compiler(args.source).await?; + Ok(match compiler.compile().await? { + Some(runner) => { + let judger = runner.run((args.mem, args.cpu), args.input).await?; + + todo!("stream output"); + + let stat = judger.stat(); + + Some(todo!()) + } + None => None, + }) } } diff --git a/judger/src/language/spec.rs b/judger/src/language/spec.rs index 6a634ef4..ee46e472 100644 --- a/judger/src/language/spec.rs +++ b/judger/src/language/spec.rs @@ -7,7 +7,7 @@ use tokio::{ }; use uuid::Uuid; -use crate::sandbox::{Cpu, Memory}; +use crate::sandbox::{Cpu, Memory, Stat}; async fn load_plugin(path: impl AsRef) { let dir_list = read_dir(path).await; @@ -19,42 +19,48 @@ pub struct Spec { judge_limit: (Cpu, Memory, u64, Duration), pub compile_command: Vec, pub judge_command: Vec, + pub file: OsString, } impl Spec { - pub fn get_judge_limit(&self, cpu: Cpu, mem: Memory) -> (Cpu, Memory, u64, Duration) { + pub fn get_judge_limit(&self, cpu: u64, mem: u64) -> Stat { + todo!() + } + pub fn get_raw_stat(&self, stat: &Stat) -> Stat { todo!() } pub fn from_str(content: &str) -> Self { let mut raw: Raw = toml::from_str(content).unwrap(); - raw.compile.fill(); - raw.judge.fill(); + raw.fill(); Self { - fs_limit: todo!(), + fs_limit: raw.fs_limit.unwrap(), compile_limit: ( Cpu { - kernel: todo!(), - user: todo!(), - total: todo!(), + kernel: raw.compile.rt_time.unwrap(), + user: raw.compile.cpu_time.unwrap(), + total: raw.compile.time.unwrap(), }, Memory { - kernel: todo!(), - user: todo!(), - total: todo!(), + kernel: raw.compile.kernel_mem.unwrap(), + user: raw.compile.user_mem.unwrap(), + total: raw.compile.memory.unwrap(), }, raw.compile.output_limit.unwrap(), Duration::from_millis(raw.compile.walltime.unwrap()), ), judge_limit: todo!(), - compile_command: todo!(), - judge_command: todo!(), + compile_command: raw.compile.command, + judge_command: raw.judge.command, + file: raw.file, } } } #[derive(Deserialize)] struct Raw { + fs_limit: Option, + file: OsString, info: String, extension: String, name: String, @@ -63,14 +69,25 @@ struct Raw { judge: RawJudge, } +impl Raw { + pub fn fill(&mut self) { + if self.fs_limit.is_none() { + self.fs_limit = Some(67108864); + } + self.compile.fill(); + self.judge.fill(); + } +} + #[derive(Deserialize)] struct RawCompile { command: Vec, kernel_mem: Option, + memory: Option, user_mem: Option, rt_time: Option, cpu_time: Option, - total_time: Option, + time: Option, output_limit: Option, walltime: Option, } @@ -94,9 +111,10 @@ impl RawCompile { user_mem, rt_time, cpu_time, - total_time, + time, output_limit, - walltime + walltime, + memory ); } } @@ -105,12 +123,13 @@ impl Default for RawCompile { fn default() -> Self { Self { command: Vec::new(), - kernel_mem: Some(67108864), - user_mem: Some(268435456), + kernel_mem: Some(268435456), + memory: Some(268435456), + user_mem: Some(8589934592), rt_time: Some(1000000), - cpu_time: Some(1000000), - total_time: Some(10000000), - output_limit: Some(4096), + cpu_time: Some(10000000000), + time: Some(10000000), + output_limit: Some(33554432), walltime: Some(360000000), } } @@ -154,8 +173,8 @@ impl Default for RawJudge { fn default() -> Self { Self { command: Vec::new(), - kernel_mem: Some(67108864), - rt_time: Some(1000000), + kernel_mem: Some(268435456), + rt_time: Some(10000000), memory_multiplier: Some(1.0), cpu_multiplier: Some(1.0), walltime: Some(360000000), diff --git a/judger/src/language/stage/assert.rs b/judger/src/language/stage/assert.rs deleted file mode 100644 index 96ef7991..00000000 --- a/judger/src/language/stage/assert.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::sync::Arc; - -use crate::{ - language::spec::Spec, - sandbox::{Corpse, MonitorKind}, -}; - -pub enum AssertionMode { - SkipSpace, - SkipContinousSpace, - Exact, -} - -pub enum AssertResult { - Accept, - WrongAnswer, - RuntimeError, - TimeLimitExceeded, - MemoryLimitExceeded, - OutputLimitExceeded, - RealTimeLimitExceeded, - CompileError, - SystemError, -} -pub struct AssertRunner { - pub spec: Arc, - pub corpse: Corpse, -} - -impl AssertRunner { - pub fn new(spec: Arc, corpse: Corpse) -> Self { - Self { spec, corpse } - } - fn assert_output(&self, output: &[u8], mode: AssertionMode) -> AssertResult { - todo!() - } - pub fn get_result(&self, output: &[u8], mode: AssertionMode) -> AssertResult { - match self.corpse.status() { - Ok(status) => match status.success() { - true => self.assert_output(output, mode), - false => AssertResult::WrongAnswer, - }, - Err(reason) => match reason { - MonitorKind::Cpu => AssertResult::TimeLimitExceeded, - MonitorKind::Memory => AssertResult::MemoryLimitExceeded, - MonitorKind::Output => AssertResult::OutputLimitExceeded, - MonitorKind::Walltime => AssertResult::RealTimeLimitExceeded, - }, - } - } -} diff --git a/judger/src/language/stage/compile.rs b/judger/src/language/stage/compile.rs index d63d421a..f4167a64 100644 --- a/judger/src/language/stage/compile.rs +++ b/judger/src/language/stage/compile.rs @@ -7,18 +7,18 @@ use crate::{ Result, }; -use super::JudgeRunner; +use super::Runner; -pub struct CompileRunner { +pub struct Compiler { spec: Arc, handle: MountHandle, } -impl CompileRunner { +impl Compiler { pub fn new(spec: Arc, handle: MountHandle) -> Self { Self { spec, handle } } - pub async fn run(self) -> Result> { + pub async fn compile(self) -> Result> { let ctx = CompileCtx { spec: self.spec.clone(), path: self.handle.get_path().to_path_buf(), @@ -30,7 +30,7 @@ impl CompileRunner { return Ok(None); } - let runner = JudgeRunner::new(self.handle, self.spec); + let runner = Runner::new(self.handle, self.spec); Ok(Some(runner)) } } diff --git a/judger/src/language/stage/judge.rs b/judger/src/language/stage/judge.rs index 1ad41fc8..ff08861f 100644 --- a/judger/src/language/stage/judge.rs +++ b/judger/src/language/stage/judge.rs @@ -1,63 +1,43 @@ -use std::{path::PathBuf, sync::Arc, time::Duration}; +use std::sync::Arc; use crate::{ - filesystem::MountHandle, language::spec::Spec, - sandbox::{Context, Cpu, Limit, Memory, Process}, - Result, + sandbox::{Corpse, MonitorKind, Stat}, }; -use super::assert::AssertRunner; +use super::{AssertionMode, StatusCode}; -pub struct JudgeRunner { - filesystem: MountHandle, +pub struct Judger { spec: Arc, + corpse: Corpse, } -impl JudgeRunner { - pub fn new(filesystem: MountHandle, spec: Arc) -> Self { - Self { filesystem, spec } - } - pub async fn run(self, (mem, cpu): (Memory, Cpu), input: Vec) -> Result { - let ctx = JudgeCtx { - spec: self.spec.clone(), - path: self.filesystem.get_path().to_path_buf(), - limit: self.spec.get_judge_limit(cpu, mem), - }; - let process = Process::new(ctx)?; - let corpse = process.wait(input).await?; - drop(self.filesystem); - Ok(AssertRunner::new(self.spec, corpse)) - } -} - -struct JudgeCtx { - spec: Arc, - path: std::path::PathBuf, - limit: (Cpu, Memory, u64, Duration), -} - -impl Limit for JudgeCtx { - fn get_cpu(&mut self) -> Cpu { - self.limit.0.clone() - } - fn get_memory(&mut self) -> Memory { - self.limit.1.clone() - } - fn get_output(&mut self) -> u64 { - self.limit.2 - } - fn get_walltime(&mut self) -> Duration { - self.limit.3 - } -} - -impl Context for JudgeCtx { - type FS = PathBuf; - fn get_fs(&mut self) -> Self::FS { - self.path.clone() - } - fn get_args(&mut self) -> impl Iterator { - self.spec.judge_command.iter().map(|s| s.as_ref()) +impl Judger { + pub fn new(spec: Arc, corpse: Corpse) -> Self { + Self { spec, corpse } + } + pub fn stat(&self) -> Stat { + let stat = self.corpse.stat(); + self.spec.get_raw_stat(stat) + } + // pub fn stream_output(&self) -> Vec { + // self.corpse.stream_stdout() + // } + fn assert_output(&self, output: &[u8], mode: AssertionMode) -> StatusCode { + todo!() + } + pub fn get_result(&self, output: &[u8], mode: AssertionMode) -> StatusCode { + match self.corpse.status() { + Ok(status) => match status.success() { + true => self.assert_output(output, mode), + false => StatusCode::WrongAnswer, + }, + Err(reason) => match reason { + MonitorKind::Cpu => StatusCode::TimeLimitExceeded, + MonitorKind::Memory => StatusCode::MemoryLimitExceeded, + MonitorKind::Output => StatusCode::OutputLimitExceeded, + MonitorKind::Walltime => StatusCode::RealTimeLimitExceeded, + }, + } } } diff --git a/judger/src/language/stage/mod.rs b/judger/src/language/stage/mod.rs index 2d12e1b2..38df4fa5 100644 --- a/judger/src/language/stage/mod.rs +++ b/judger/src/language/stage/mod.rs @@ -1,6 +1,25 @@ -mod assert; mod compile; mod judge; +mod run; -pub use compile::CompileRunner; -pub use judge::JudgeRunner; +pub use compile::Compiler; +pub use judge::Judger; +pub use run::Runner; + +pub enum StatusCode { + Accept, + WrongAnswer, + RuntimeError, + TimeLimitExceeded, + MemoryLimitExceeded, + OutputLimitExceeded, + RealTimeLimitExceeded, + CompileError, + SystemError, +} + +pub enum AssertionMode { + SkipSpace, + SkipContinousSpace, + Exact, +} diff --git a/judger/src/language/stage/run.rs b/judger/src/language/stage/run.rs new file mode 100644 index 00000000..ee0c74ef --- /dev/null +++ b/judger/src/language/stage/run.rs @@ -0,0 +1,63 @@ +use std::{path::PathBuf, sync::Arc, time::Duration}; + +use crate::{ + filesystem::MountHandle, + language::spec::Spec, + sandbox::{Context, Cpu, Limit, Memory, Process, Stat}, + Result, +}; + +use super::judge::Judger; + +pub struct Runner { + filesystem: MountHandle, + spec: Arc, +} + +impl Runner { + pub fn new(filesystem: MountHandle, spec: Arc) -> Self { + Self { filesystem, spec } + } + pub async fn run(self, (mem, cpu): (u64, u64), input: Vec) -> Result { + let ctx = RunCtx { + spec: self.spec.clone(), + path: self.filesystem.get_path().to_path_buf(), + limit: self.spec.get_judge_limit(cpu, mem), + }; + let process = Process::new(ctx)?; + let corpse = process.wait(input).await?; + drop(self.filesystem); + Ok(Judger::new(self.spec, corpse)) + } +} + +struct RunCtx { + spec: Arc, + path: std::path::PathBuf, + limit: Stat, +} + +impl Limit for RunCtx { + fn get_cpu(&mut self) -> Cpu { + self.limit.cpu.clone() + } + fn get_memory(&mut self) -> Memory { + self.limit.memory.clone() + } + fn get_output(&mut self) -> u64 { + self.limit.output + } + fn get_walltime(&mut self) -> Duration { + self.limit.walltime + } +} + +impl Context for RunCtx { + type FS = PathBuf; + fn get_fs(&mut self) -> Self::FS { + self.path.clone() + } + fn get_args(&mut self) -> impl Iterator { + self.spec.judge_command.iter().map(|s| s.as_ref()) + } +} diff --git a/judger/src/sandbox/mod.rs b/judger/src/sandbox/mod.rs index 8c84955b..2752b248 100644 --- a/judger/src/sandbox/mod.rs +++ b/judger/src/sandbox/mod.rs @@ -8,7 +8,7 @@ use std::{ time::Duration, }; -pub use self::monitor::{Cpu, Memory}; +pub use self::monitor::{Cpu, Memory, Stat}; pub use error::Error; pub use monitor::MonitorKind; pub use process::{Corpse, Process}; diff --git a/judger/src/sandbox/monitor/stat.rs b/judger/src/sandbox/monitor/stat.rs index 150f0dea..29a3648a 100644 --- a/judger/src/sandbox/monitor/stat.rs +++ b/judger/src/sandbox/monitor/stat.rs @@ -10,6 +10,7 @@ use super::output::Output; pub type MemAndCpu = (Memory, Cpu); /// statistics of resource usage +#[derive(Clone)] pub struct Stat { pub memory: Memory, pub cpu: Cpu, From 25cd75a9a2ab87152b3250a81b597f5766824e8f Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Mon, 13 May 2024 11:41:26 +0800 Subject: [PATCH 28/38] docs(Judger): :memo: add docs --- judger/src/filesystem/adapter/fuse.rs | 11 ++- judger/src/filesystem/adapter/handle.rs | 4 + judger/src/filesystem/adapter/template.rs | 19 ++--- judger/src/filesystem/mkdtemp.rs | 3 + judger/src/filesystem/resource.rs | 5 ++ judger/src/filesystem/table.rs | 54 ++++++++++--- judger/src/language/builder.rs | 98 ++++++++++++++++++++++ judger/src/language/daemon.rs | 34 -------- judger/src/language/mod.rs | 4 +- judger/src/language/plugin.rs | 28 +++++-- judger/src/language/spec.rs | 99 ++++++++++++++++++++--- judger/src/language/stage/compile.rs | 8 +- judger/src/sandbox/monitor/mod.rs | 5 +- 13 files changed, 291 insertions(+), 81 deletions(-) delete mode 100644 judger/src/language/daemon.rs diff --git a/judger/src/filesystem/adapter/fuse.rs b/judger/src/filesystem/adapter/fuse.rs index 4f694eb1..0dea7782 100644 --- a/judger/src/filesystem/adapter/fuse.rs +++ b/judger/src/filesystem/adapter/fuse.rs @@ -29,13 +29,15 @@ impl Filesystem where F: AsyncRead + AsyncSeek + Unpin + Send + Sync + 'static, { - pub(super) fn new(tree: TarTree, permit: u64) -> Self { + /// Create a new filesystem + pub(super) fn new(tree: TarTree, fs_size: u64) -> Self { Self { handle_table: HandleTable::new(), tree: Mutex::new(tree), - resource: Arc::new(Resource::new(permit)), + resource: Arc::new(Resource::new(fs_size)), } } + /// Mount the filesystem to a path pub async fn mount_with_path( self, path: impl AsRef + Clone, @@ -51,6 +53,7 @@ where .mount_with_unprivileged(self, path.as_ref()) .await } + /// Insert a file by path pub fn insert_by_path(&self, path: impl AsRef, content: Vec) { let mut tree = self.tree.lock(); tree.insert_by_path( @@ -183,7 +186,7 @@ where return Err(FuseError::NotDir.into()); } - let parent_node = node.parent().unwrap_or_else(|| tree.get_root()); + let parent_node = node.parent().unwrap_or_else(|| tree.get_first()); let entries = vec![ Ok(dir_entry( @@ -235,7 +238,7 @@ where return Err(FuseError::NotDir.into()); } - let parent_node = node.parent().unwrap_or_else(|| tree.get_root()); + let parent_node = node.parent().unwrap_or_else(|| tree.get_first()); let entries = vec![ Ok(dir_entry_plus( diff --git a/judger/src/filesystem/adapter/handle.rs b/judger/src/filesystem/adapter/handle.rs index ba27b3e6..845cb3ff 100644 --- a/judger/src/filesystem/adapter/handle.rs +++ b/judger/src/filesystem/adapter/handle.rs @@ -11,12 +11,14 @@ pub struct HandleTable { } impl HandleTable { + /// Create a new handle table pub fn new() -> Self { Self { handle_generator: AtomicU64::new(1), table: Mutex::new(BTreeMap::new()), } } + /// Add an entry to the table pub fn add(&self, entry: E) -> u64 { let handle = self .handle_generator @@ -25,10 +27,12 @@ impl HandleTable { self.table.lock().insert(handle, Arc::new(entry)); handle } + /// Get an entry from the table pub fn get(&self, handle: u64) -> Option> { log::debug!("get handle: {}", handle); self.table.lock().get(&handle).cloned() } + /// Remove an entry from the table pub fn remove(&self, handle: u64) -> Option> { log::trace!("deallocate handle: {}", handle); self.table.lock().remove(&handle) diff --git a/judger/src/filesystem/adapter/template.rs b/judger/src/filesystem/adapter/template.rs index ecd752bb..28e7c54e 100644 --- a/judger/src/filesystem/adapter/template.rs +++ b/judger/src/filesystem/adapter/template.rs @@ -9,31 +9,28 @@ use crate::filesystem::entry::TarTree; use super::fuse::Filesystem; -pub struct Template +pub struct Template(TarTree) where - F: AsyncRead + AsyncSeek + Unpin + Send + 'static, -{ - tree: TarTree, -} + F: AsyncRead + AsyncSeek + Unpin + Send + 'static; impl Template where F: AsyncRead + AsyncSeek + Unpin + Send + Sync + 'static, { - pub fn new_inner(tree: TarTree) -> Self { - Self { tree } - } + /// use template to create a filesystem pub fn as_filesystem(&self, permit: u64) -> Filesystem { - Filesystem::new(self.tree.clone(), permit) + Filesystem::new(self.0.clone(), permit) } + /// read a file by path pub async fn read_by_path(&self, path: impl AsRef) -> Option> { - self.tree.read_by_path(path).await + self.0.read_by_path(path).await } } impl Template { + /// Create a new template from a tar file pub async fn new(path: impl AsRef + Clone) -> std::io::Result { let tree = TarTree::new(path).await?; - Ok(Self::new_inner(tree)) + Ok(Self(tree)) } } diff --git a/judger/src/filesystem/mkdtemp.rs b/judger/src/filesystem/mkdtemp.rs index 9fc2d615..84793854 100644 --- a/judger/src/filesystem/mkdtemp.rs +++ b/judger/src/filesystem/mkdtemp.rs @@ -6,6 +6,7 @@ use std::{ use tokio::fs::remove_dir; +/// A safe wrapper around [`libc::mkdtemp`] pub struct MkdTemp(PathBuf); impl Drop for MkdTemp { @@ -15,6 +16,7 @@ impl Drop for MkdTemp { } impl MkdTemp { + /// Create a new MkdTemp pub fn new() -> Self { Self(unsafe { Self::new_inner("/tmp/mdoj-fs-runtime-XXXXXX") }) } @@ -24,6 +26,7 @@ impl MkdTemp { let str_path = OsStr::from_bytes(template.to_bytes()); PathBuf::from(str_path) } + /// get_path acquired by the MkdTemp pub fn get_path(&self) -> &Path { self.0.as_path() } diff --git a/judger/src/filesystem/resource.rs b/judger/src/filesystem/resource.rs index ff943397..e86f618e 100644 --- a/judger/src/filesystem/resource.rs +++ b/judger/src/filesystem/resource.rs @@ -1,11 +1,16 @@ use std::sync::atomic::{AtomicU64, Ordering}; +/// A resource counter +/// +/// unlike `Semaphore`, the resource is not reusable pub struct Resource(AtomicU64); impl Resource { + /// Create a new resource counter pub fn new(cap: u64) -> Self { Self(AtomicU64::new(cap)) } + /// consume some amount of resource pub fn comsume(&self, size: u32) -> Option<()> { let a = self.0.fetch_sub(size as u64, Ordering::AcqRel); if (a & (1 << 63)) != 0 { diff --git a/judger/src/filesystem/table.rs b/judger/src/filesystem/table.rs index 2550ebf7..5c43f3ca 100644 --- a/judger/src/filesystem/table.rs +++ b/judger/src/filesystem/table.rs @@ -5,8 +5,9 @@ use std::{ }; const ID_MIN: usize = 1; -const REMAIN_CAPACITY: u32 = 1 << 31; +const MAX_ID_CAPACITY: u32 = 1 << 31; +/// convert a path to internal path(prefixes on the tree) pub fn to_internal_path<'a>(path: &'a Path) -> impl Iterator + 'a { path.components().filter_map(|component| match component { Component::Prefix(x) => unreachable!("Windows only: {:?}", x), @@ -16,6 +17,7 @@ pub fn to_internal_path<'a>(path: &'a Path) -> impl Iterator + 'a // .collect::>() } +/// A node on adjacency table #[derive(Clone)] struct Node { parent_idx: usize, @@ -23,6 +25,10 @@ struct Node { children: BTreeMap, // FIXME: use BtreeMap } +/// A table to store the tree structure with +/// the ability to allocate id up to [`MAX_ID_CAPACITY`] +/// +/// The table has ability to store a multiple disconnected tree #[derive(Clone)] pub struct AdjTable { by_id: Vec>, @@ -32,6 +38,7 @@ impl AdjTable { pub fn new() -> Self { Self { by_id: vec![] } } + /// Insert a root node pub fn insert_root(&mut self, value: V) -> NodeWrapperMut { let idx = self.by_id.len(); self.by_id.push(Node { @@ -41,15 +48,23 @@ impl AdjTable { }); NodeWrapperMut { table: self, idx } } - pub fn get_root(&self) -> NodeWrapper { + /// get first inserted node(one of the root node) + /// + /// # Panics + /// It panic if the table is empty(there is no root node) + pub fn get_first(&self) -> NodeWrapper { NodeWrapper { table: self, idx: 0, } } + /// get remain capacity of the table + /// + /// The capacity is the maximum number of ino that can be allocated pub fn get_remain_capacity(&self) -> u32 { - REMAIN_CAPACITY - self.by_id.len() as u32 + MAX_ID_CAPACITY - self.by_id.len() as u32 } + /// get a node by id pub fn get(&self, id: usize) -> Option> { if id < ID_MIN || id >= self.by_id.len() + ID_MIN { return None; @@ -59,6 +74,7 @@ impl AdjTable { idx: id - ID_MIN, }) } + /// get a mutable node by id pub fn get_mut(&mut self, id: usize) -> Option> { if id < ID_MIN || id >= self.by_id.len() + ID_MIN { return None; @@ -68,11 +84,12 @@ impl AdjTable { idx: id - ID_MIN, }) } + /// get a node by path pub fn get_by_path<'a>( &self, mut path: impl Iterator, ) -> Option> { - let mut idx = self.get_root().idx; + let mut idx = self.get_first().idx; while let Some(name) = path.next() { if self.by_id[idx].children.contains_key(name) { idx = self.by_id[idx].children[name]; @@ -82,6 +99,9 @@ impl AdjTable { } Some(NodeWrapper { table: self, idx }) } + /// get a mutable node by path or inserted(if not exists) + /// + /// Note that it could create multiple nodes along the search pub fn get_by_path_or_insert( &mut self, path: impl Iterator, @@ -90,7 +110,7 @@ impl AdjTable { where F: FnMut() -> V, { - let mut idx = self.get_root().idx; + let mut idx = self.get_first().idx; for name in path { if self.by_id[idx].children.contains_key(&name) { idx = self.by_id[idx].children[&name]; @@ -106,6 +126,10 @@ impl AdjTable { } NodeWrapperMut { table: self, idx } } + /// insert a node by path + /// + /// if the path is not exists, it will create the path + /// (edge is filled with [`default_value()`]) pub fn insert_by_path<'a, F>( &mut self, path: impl Iterator, @@ -115,7 +139,7 @@ impl AdjTable { where F: FnMut() -> V, { - let mut idx = self.get_root().idx; + let mut idx = self.get_first().idx; let mut path = path.peekable(); debug_assert!(path.peek().is_some()); let mut seg; @@ -145,12 +169,15 @@ pub struct NodeWrapper<'a, V> { } impl<'a, V> NodeWrapper<'a, V> { + /// get id of the node pub fn get_id(&self) -> usize { self.idx + ID_MIN } + /// check if the node is root pub fn is_root(&self) -> bool { self.idx == 0 } + /// get parent node pub fn parent(&self) -> Option> { if self.is_root() { return None; @@ -161,15 +188,18 @@ impl<'a, V> NodeWrapper<'a, V> { idx: parent_idx, }) } + /// get children nodes' id pub fn children(self) -> impl Iterator + 'a { self.table.by_id[self.idx] .children .iter() .map(|(_, &idx)| idx + ID_MIN) } + /// get value of the node pub fn get_value(&self) -> &V { &self.table.by_id[self.idx].value } + /// get name of the node pub fn get_name(&self) -> &OsStr { if self.is_root() { OsStr::new("/") @@ -182,6 +212,7 @@ impl<'a, V> NodeWrapper<'a, V> { .0 } } + /// get node by component pub fn get_by_component(&self, component: &OsStr) -> Option> { if let Some(&idx) = self.table.by_id[self.idx].children.get(component) { Some(NodeWrapper { @@ -200,19 +231,21 @@ pub struct NodeWrapperMut<'a, V> { } impl<'a, V> NodeWrapperMut<'a, V> { - pub fn insert(&mut self, name: OsString, value: V) -> NodeWrapperMut { + /// insert a node by component + pub fn insert(&mut self, component: OsString, value: V) -> NodeWrapperMut { let idx = self.table.by_id.len(); self.table.by_id.push(Node { parent_idx: self.idx, value, children: BTreeMap::new(), }); - self.table.by_id[self.idx].children.insert(name, idx); + self.table.by_id[self.idx].children.insert(component, idx); NodeWrapperMut { table: self.table, idx, } } + /// get id of the node pub fn get_id(&self) -> usize { NodeWrapper { table: self.table, @@ -220,9 +253,11 @@ impl<'a, V> NodeWrapperMut<'a, V> { } .get_id() } + /// get value of the node pub fn get_value(&mut self) -> &mut V { &mut self.table.by_id[self.idx].value } + /// get children node by component pub fn get_by_component(&mut self, component: &OsStr) -> Option> { if let Some(&idx) = self.table.by_id[self.idx].children.get(component) { Some(NodeWrapperMut { @@ -233,6 +268,7 @@ impl<'a, V> NodeWrapperMut<'a, V> { None } } + /// get children nodes' id pub fn children(&mut self) -> impl Iterator + '_ { self.table.by_id[self.idx] .children @@ -267,7 +303,7 @@ mod test { || 4, 10, ); - let root = table.get_root(); + let root = table.get_first(); let l1 = root.children().next().unwrap(); let l2 = table.get(l1).unwrap().children().next().unwrap(); let l3 = table.get(l2).unwrap().children().next().unwrap(); diff --git a/judger/src/language/builder.rs b/judger/src/language/builder.rs index d6e36d3e..8a3d4db8 100644 --- a/judger/src/language/builder.rs +++ b/judger/src/language/builder.rs @@ -28,3 +28,101 @@ pub struct ExecuteResult { pub output: Vec, pub code: i32, } + +pub struct JudgeArgBuilder { + mem: Option, + cpu: Option, + input: Option>, + output: Option>, + mode: Option, + source: Option>, +} + +impl JudgeArgBuilder { + pub fn new() -> Self { + Self { + mem: None, + cpu: None, + input: None, + output: None, + mode: None, + source: None, + } + } + pub fn mem(mut self, mem: u64) -> Self { + self.mem = Some(mem); + self + } + pub fn cpu(mut self, cpu: u64) -> Self { + self.cpu = Some(cpu); + self + } + pub fn input(mut self, input: Vec) -> Self { + self.input = Some(input); + self + } + pub fn output(mut self, output: Vec) -> Self { + self.output = Some(output); + self + } + pub fn mode(mut self, mode: AssertionMode) -> Self { + self.mode = Some(mode); + self + } + pub fn source(mut self, source: Vec) -> Self { + self.source = Some(source); + self + } + pub fn build(self) -> JudgeArgs { + JudgeArgs { + mem: self.mem.expect("mem is not set"), + cpu: self.cpu.expect("cpu is not set"), + input: self.input.expect("input is not set"), + output: self.output.expect("output is not set"), + mode: self.mode.expect("mode is not set"), + source: self.source.expect("source is not set"), + } + } +} + +pub struct ExecuteArgBuilder { + mem: Option, + cpu: Option, + input: Option>, + source: Option>, +} + +impl ExecuteArgBuilder { + pub fn new() -> Self { + Self { + mem: None, + cpu: None, + input: None, + source: None, + } + } + pub fn mem(mut self, mem: u64) -> Self { + self.mem = Some(mem); + self + } + pub fn cpu(mut self, cpu: u64) -> Self { + self.cpu = Some(cpu); + self + } + pub fn input(mut self, input: Vec) -> Self { + self.input = Some(input); + self + } + pub fn source(mut self, source: Vec) -> Self { + self.source = Some(source); + self + } + pub fn build(self) -> ExecuteArgs { + ExecuteArgs { + mem: self.mem.expect("mem is not set"), + cpu: self.cpu.expect("cpu is not set"), + input: self.input.expect("input is not set"), + source: self.source.expect("source is not set"), + } + } +} diff --git a/judger/src/language/daemon.rs b/judger/src/language/daemon.rs deleted file mode 100644 index 7e06efd1..00000000 --- a/judger/src/language/daemon.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::collections::BTreeMap; - -use tokio::{fs::File, sync::Semaphore}; -use uuid::Uuid; - -use super::spec::*; -use crate::{filesystem::Template, CONFIG}; - -static PLUGIN_PATH: &str = "./plugins"; -/// max queue judging task -const MAX_QUEUE: usize = 10; - -pub struct Daemon { - semaphore: Semaphore, - templates: BTreeMap, -} - -struct Plugin { - config: Spec, - template: Template, -} - -impl Daemon { - pub fn new() -> Self { - let semaphore = Semaphore::new(todo!()); - let mut templates = BTreeMap::new(); - todo!("Load plugins"); - // design a loader struct - Daemon { - semaphore, - templates, - } - } -} diff --git a/judger/src/language/mod.rs b/judger/src/language/mod.rs index d607b035..dabc8509 100644 --- a/judger/src/language/mod.rs +++ b/judger/src/language/mod.rs @@ -1,5 +1,7 @@ mod builder; -mod daemon; mod plugin; mod spec; mod stage; + +pub use builder::*; +pub use plugin::Plugin; diff --git a/judger/src/language/plugin.rs b/judger/src/language/plugin.rs index 7387b70a..dab0a287 100644 --- a/judger/src/language/plugin.rs +++ b/judger/src/language/plugin.rs @@ -1,7 +1,8 @@ -use std::{path::Path, sync::Arc}; +use std::{collections::BTreeMap, path::Path, sync::Arc}; use rustix::path::Arg; use tokio::fs::{read_dir, File}; +use uuid::Uuid; use crate::{ filesystem::*, @@ -31,6 +32,20 @@ pub async fn load_plugins(path: impl AsRef) -> Result> { Ok(plugins) } +pub struct PluginMap(BTreeMap); + +impl PluginMap { + pub async fn new(path: impl AsRef) -> Result { + let plugins = load_plugins(path).await?; + let mut map = BTreeMap::new(); + + for plugin in plugins { + map.insert(plugin.spec.id, plugin); + } + Ok(Self(map)) + } +} + impl JudgeResult { fn new(status: StatusCode) -> Self { Self { @@ -43,12 +58,12 @@ impl JudgeResult { pub struct Plugin { pub(super) spec: Arc, - pub(super) template: Template, + pub(super) template: Arc>, } impl Plugin { pub async fn new(path: impl AsRef + Clone) -> Result { - let template = Template::new(path.clone()).await?; + let template = Arc::new(Template::new(path.clone()).await?); let spec_source = template.read_by_path("spec.toml").await.expect(&format!( "sepc.toml not found in plugin {}", path.as_ref().display() @@ -62,7 +77,7 @@ impl Plugin { filesystem.insert_by_path(self.spec.file.as_os_str(), source); Ok(Compiler::new(self.spec.clone(), filesystem.mount().await?)) } - pub async fn judge(self: Arc, args: JudgeArgs) -> Result { + async fn judge(self: Arc, args: JudgeArgs) -> Result { // for judge: it has three stages: compile, run, judge let compiler = self.as_compiler(args.source).await?; Ok(match compiler.compile().await? { @@ -81,7 +96,7 @@ impl Plugin { None => JudgeResult::new(StatusCode::CompileError), }) } - pub async fn execute(self: Arc, args: ExecuteArgs) -> Result> { + async fn execute(self: Arc, args: ExecuteArgs) -> Result> { let compiler = self.as_compiler(args.source).await?; Ok(match compiler.compile().await? { Some(runner) => { @@ -96,4 +111,7 @@ impl Plugin { None => None, }) } + pub fn get_memory_reserved(&self, mem: u64) -> u64 { + self.spec.get_memory_reserved_size(mem) + } } diff --git a/judger/src/language/spec.rs b/judger/src/language/spec.rs index ee46e472..5ba81344 100644 --- a/judger/src/language/spec.rs +++ b/judger/src/language/spec.rs @@ -13,10 +13,59 @@ async fn load_plugin(path: impl AsRef) { let dir_list = read_dir(path).await; } +pub struct CpuFactor { + kernel: u64, + user: u64, + total: f64, +} + +impl CpuFactor { + pub fn get(&self, cpu: u64) -> Cpu { + Cpu { + kernel: self.kernel, + user: self.user, + total: (cpu as f64 * self.total) as u64, + } + } + pub fn get_raw(&self, cpu: Cpu) -> Cpu { + Cpu { + kernel: (cpu.kernel as f64 / self.total) as u64, + user: (cpu.user as f64 / self.total) as u64, + total: (cpu.total as f64 / self.total) as u64, + } + } +} + +pub struct MemFactor { + kernel: u64, + user: u64, + total: f64, +} + +impl MemFactor { + pub fn get(&self, mem: u64) -> Memory { + Memory { + kernel: self.kernel, + user: self.user, + total: (mem as f64 * self.total) as u64, + } + } + pub fn get_raw(&self, mem: Memory) -> Memory { + Memory { + kernel: ((mem.kernel as f64) / self.total) as u64, + user: ((mem.user as f64) / self.total) as u64, + total: ((mem.total as f64) / self.total) as u64, + } + } +} + pub struct Spec { + pub id: Uuid, pub fs_limit: u64, - pub compile_limit: (Cpu, Memory, u64, Duration), - judge_limit: (Cpu, Memory, u64, Duration), + pub compile_limit: Stat, + judge_cpu_factor: CpuFactor, + judge_mem_factor: MemFactor, + judge_limit: (u64, Duration), pub compile_command: Vec, pub judge_command: Vec, pub file: OsString, @@ -24,35 +73,63 @@ pub struct Spec { impl Spec { pub fn get_judge_limit(&self, cpu: u64, mem: u64) -> Stat { - todo!() + let cpu = self.judge_cpu_factor.get(cpu); + let mem = self.judge_mem_factor.get(mem); + Stat { + cpu, + memory: mem, + output: self.judge_limit.0, + walltime: self.judge_limit.1, + } } pub fn get_raw_stat(&self, stat: &Stat) -> Stat { - todo!() + let mut stat = stat.clone(); + stat.cpu = self.judge_cpu_factor.get_raw(stat.cpu); + stat.memory = self.judge_mem_factor.get_raw(stat.memory); + stat + } + pub fn get_memory_reserved_size(&self, mem: u64) -> u64 { + self.judge_mem_factor.get(mem).get_reserved_size() + self.fs_limit } pub fn from_str(content: &str) -> Self { let mut raw: Raw = toml::from_str(content).unwrap(); raw.fill(); + // FIXME: use compsition instead Self { + id: raw.id, fs_limit: raw.fs_limit.unwrap(), - compile_limit: ( - Cpu { + compile_limit: Stat { + cpu: Cpu { kernel: raw.compile.rt_time.unwrap(), user: raw.compile.cpu_time.unwrap(), total: raw.compile.time.unwrap(), }, - Memory { + memory: Memory { kernel: raw.compile.kernel_mem.unwrap(), user: raw.compile.user_mem.unwrap(), total: raw.compile.memory.unwrap(), }, - raw.compile.output_limit.unwrap(), - Duration::from_millis(raw.compile.walltime.unwrap()), - ), - judge_limit: todo!(), + output: raw.compile.output_limit.unwrap(), + walltime: Duration::from_nanos(raw.compile.walltime.unwrap()), + }, compile_command: raw.compile.command, judge_command: raw.judge.command, file: raw.file, + judge_cpu_factor: CpuFactor { + kernel: raw.judge.kernel_mem.unwrap(), + user: raw.judge.rt_time.unwrap(), + total: raw.judge.cpu_multiplier.unwrap(), + }, + judge_mem_factor: MemFactor { + kernel: raw.judge.kernel_mem.unwrap(), + user: raw.judge.rt_time.unwrap(), + total: raw.judge.memory_multiplier.unwrap(), + }, + judge_limit: ( + raw.judge.walltime.unwrap(), + Duration::from_nanos(raw.judge.rt_time.unwrap()), + ), } } } diff --git a/judger/src/language/stage/compile.rs b/judger/src/language/stage/compile.rs index f4167a64..abefe824 100644 --- a/judger/src/language/stage/compile.rs +++ b/judger/src/language/stage/compile.rs @@ -42,16 +42,16 @@ struct CompileCtx { impl Limit for CompileCtx { fn get_cpu(&mut self) -> crate::sandbox::Cpu { - todo!() + self.spec.compile_limit.cpu.clone() } fn get_memory(&mut self) -> crate::sandbox::Memory { - todo!() + self.spec.compile_limit.memory.clone() } fn get_output(&mut self) -> u64 { - todo!() + self.spec.compile_limit.output } fn get_walltime(&mut self) -> Duration { - todo!() + self.spec.compile_limit.walltime } } diff --git a/judger/src/sandbox/monitor/mod.rs b/judger/src/sandbox/monitor/mod.rs index 80a8966e..667be203 100644 --- a/judger/src/sandbox/monitor/mod.rs +++ b/judger/src/sandbox/monitor/mod.rs @@ -27,7 +27,9 @@ pub trait Monitor { /// /// This function is cancel safe. async fn wait_exhaust(&mut self) -> MonitorKind { - log::warn!("unimplemented wait_exhaust!"); + // those low level call is likely have event listener(like epoll) + // monitor should use those listener to implement this function + log::warn!("unimplemented wait_exhaust, use poll_exhaust instead!"); loop { if let Some(reason) = self.poll_exhaust() { return reason; @@ -87,7 +89,6 @@ impl Monitor for StatMonitor

{ x = self.walltime.wait_exhaust() => x, } } - fn poll_exhaust(&mut self) -> Option { macro_rules! check_exhaust { ($f:ident) => { From 96ef9b5599fa50494127d76660f2ee713f2d5e6c Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Mon, 13 May 2024 20:39:55 +0800 Subject: [PATCH 29/38] feat(Judger): :sparkles: stream judge request --- Cargo.lock | 34 +-------- judger/Cargo.toml | 3 +- judger/src/error.rs | 35 +++++++++ judger/src/language/builder.rs | 24 ++++-- judger/src/language/mod.rs | 2 +- judger/src/language/plugin.rs | 114 ++++++++++++++++++++++------- judger/src/language/spec.rs | 10 +-- judger/src/language/stage/mod.rs | 22 +++++- judger/src/language/stage/run.rs | 5 +- judger/src/main.rs | 1 + judger/src/sandbox/monitor/stat.rs | 33 ++++++++- judger/src/server.rs | 111 ++++++++++++++++++++++++++++ 12 files changed, 305 insertions(+), 89 deletions(-) create mode 100644 judger/src/server.rs diff --git a/Cargo.lock b/Cargo.lock index 958784dd..14e08ba3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1276,19 +1276,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", -] - [[package]] name = "crossbeam-channel" version = "0.5.12" @@ -1298,25 +1285,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "crossbeam-deque" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-queue" version = "0.3.11" @@ -2899,9 +2867,9 @@ dependencies = [ name = "judger" version = "0.1.0" dependencies = [ + "async-stream", "bytes", "cgroups-rs", - "crossbeam", "derive_builder", "env_logger", "fuse3", diff --git a/judger/Cargo.toml b/judger/Cargo.toml index 0146a039..466a0d3a 100644 --- a/judger/Cargo.toml +++ b/judger/Cargo.toml @@ -17,10 +17,10 @@ toml = { workspace = true } derive_builder = { workspace = true } tar = "0.4.40" grpc = { path = "../grpc" } -crossbeam = "0.8.4" lazy_static = "1.4.0" libc = "0.2.154" bytes = "1.6.0" +async-stream = "0.3.5" [dependencies.fuse3] version = "0.7.1" @@ -67,4 +67,3 @@ features = ["mutex", "spin_mutex"] tempfile = "3.8.0" tower = "0.4.13" tower-layer = "0.3.2" - diff --git a/judger/src/error.rs b/judger/src/error.rs index 0f1c82c2..808ab4ad 100644 --- a/judger/src/error.rs +++ b/judger/src/error.rs @@ -1,3 +1,5 @@ +use tonic::Status; + use super::sandbox::Error as SandboxError; #[derive(thiserror::Error, Debug)] @@ -6,4 +8,37 @@ pub enum Error { Io(#[from] std::io::Error), #[error("sandbox error: {0}")] Sandbox(#[from] SandboxError), + #[error("32 bit problem")] + Platform, +} + +impl From for Status { + fn from(value: Error) -> Self { + log::error!("{:?}", value); + Status::internal("internal error: unknown") + } +} + +#[derive(thiserror::Error, Debug)] +pub enum ClientError { + #[error("invaild secret")] + InvaildSecret, + #[error("invaild language uuid")] + InvaildLanguageUuid, + #[error("impossible memory requirement")] + ImpossibleMemoryRequirement, +} + +impl From for Status { + fn from(value: ClientError) -> Self { + match value { + ClientError::InvaildSecret => Status::permission_denied("Invaild secret"), + ClientError::InvaildLanguageUuid => { + Status::failed_precondition("Invaild language uuid") + } + ClientError::ImpossibleMemoryRequirement => { + Status::failed_precondition("Impossible memory requirement") + } + } + } } diff --git a/judger/src/language/builder.rs b/judger/src/language/builder.rs index 8a3d4db8..181ab059 100644 --- a/judger/src/language/builder.rs +++ b/judger/src/language/builder.rs @@ -1,10 +1,12 @@ +use grpc::judger::JudgeResponse; + use super::stage::{AssertionMode, StatusCode}; pub struct JudgeArgs { pub(super) mem: u64, pub(super) cpu: u64, - pub(super) input: Vec, - pub(super) output: Vec, + pub(super) input: Vec>, + pub(super) output: Vec>, pub(super) mode: AssertionMode, pub(super) source: Vec, } @@ -22,6 +24,12 @@ pub struct JudgeResult { pub memory: u64, } +impl From for JudgeResponse { + fn from(value: JudgeResult) -> Self { + todo!() + } +} + pub struct ExecuteResult { pub time: u64, pub memory: u64, @@ -32,8 +40,8 @@ pub struct ExecuteResult { pub struct JudgeArgBuilder { mem: Option, cpu: Option, - input: Option>, - output: Option>, + input: Option>>, + output: Option>>, mode: Option, source: Option>, } @@ -57,12 +65,12 @@ impl JudgeArgBuilder { self.cpu = Some(cpu); self } - pub fn input(mut self, input: Vec) -> Self { - self.input = Some(input); + pub fn input(mut self, input: impl Iterator>) -> Self { + self.input = Some(input.collect()); self } - pub fn output(mut self, output: Vec) -> Self { - self.output = Some(output); + pub fn output(mut self, output: impl Iterator>) -> Self { + self.output = Some(output.collect()); self } pub fn mode(mut self, mode: AssertionMode) -> Self { diff --git a/judger/src/language/mod.rs b/judger/src/language/mod.rs index dabc8509..f0488862 100644 --- a/judger/src/language/mod.rs +++ b/judger/src/language/mod.rs @@ -4,4 +4,4 @@ mod spec; mod stage; pub use builder::*; -pub use plugin::Plugin; +pub use plugin::{Map, Plugin}; diff --git a/judger/src/language/plugin.rs b/judger/src/language/plugin.rs index dab0a287..d41f3452 100644 --- a/judger/src/language/plugin.rs +++ b/judger/src/language/plugin.rs @@ -1,24 +1,47 @@ -use std::{collections::BTreeMap, path::Path, sync::Arc}; +use std::{collections::BTreeMap, path::Path, pin::Pin, sync::Arc}; +use async_stream::{stream, try_stream}; +use futures_core::Stream; use rustix::path::Arg; -use tokio::fs::{read_dir, File}; +use tokio::{ + fs::{read_dir, File}, + io::{AsyncRead, AsyncSeek}, +}; use uuid::Uuid; -use crate::{ - filesystem::*, - sandbox::{Cpu, Memory}, -}; +use crate::{filesystem::*, sandbox::Stat}; use super::{ builder::*, spec::Spec, - stage::{AssertionMode, Compiler, StatusCode}, + stage::{Compiler, StatusCode}, }; use crate::Result; +macro_rules! trys { + ($ele:expr) => { + match $ele { + Ok(x) => x, + Err(err) => { + return Box::pin(stream! { + yield Err(err); + }); + } + } + }; + ($ele:expr,$ret:expr) => { + match $ele { + Some(x) => x, + None => { + return Box::pin(stream! {yield $ret;}); + } + } + }; +} + static EXTENSION: &str = "lang"; -pub async fn load_plugins(path: impl AsRef) -> Result> { +pub async fn load_plugins(path: impl AsRef) -> Result>> { let mut plugins = Vec::new(); let mut dir_list = read_dir(path).await?; while let Some(entry) = dir_list.next_entry().await? { @@ -32,9 +55,11 @@ pub async fn load_plugins(path: impl AsRef) -> Result> { Ok(plugins) } -pub struct PluginMap(BTreeMap); +pub struct Map(BTreeMap>) +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static; -impl PluginMap { +impl Map { pub async fn new(path: impl AsRef) -> Result { let plugins = load_plugins(path).await?; let mut map = BTreeMap::new(); @@ -44,24 +69,42 @@ impl PluginMap { } Ok(Self(map)) } + pub fn get(&self, id: &Uuid) -> Option> { + self.0.get(id).cloned() + } } impl JudgeResult { - fn new(status: StatusCode) -> Self { + fn compile_error() -> Self { Self { - status, + status: StatusCode::CompileError, time: 0, memory: 0, } } } -pub struct Plugin { +pub struct Plugin +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ pub(super) spec: Arc, - pub(super) template: Arc>, + pub(super) template: Arc>, +} + +impl Clone for Plugin +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ + fn clone(&self) -> Self { + Self { + spec: self.spec.clone(), + template: self.template.clone(), + } + } } -impl Plugin { +impl Plugin { pub async fn new(path: impl AsRef + Clone) -> Result { let template = Arc::new(Template::new(path.clone()).await?); let spec_source = template.read_by_path("spec.toml").await.expect(&format!( @@ -72,34 +115,49 @@ impl Plugin { Ok(Self { spec, template }) } - pub async fn as_compiler(self: Arc, source: Vec) -> Result { +} + +impl Plugin +where + F: AsyncRead + AsyncSeek + Unpin + Send + Sync + 'static, +{ + pub async fn as_compiler(&self, source: Vec) -> Result { let filesystem = self.template.as_filesystem(self.spec.fs_limit); filesystem.insert_by_path(self.spec.file.as_os_str(), source); Ok(Compiler::new(self.spec.clone(), filesystem.mount().await?)) } - async fn judge(self: Arc, args: JudgeArgs) -> Result { - // for judge: it has three stages: compile, run, judge - let compiler = self.as_compiler(args.source).await?; - Ok(match compiler.compile().await? { - Some(runner) => { - let judger = runner.run((args.mem, args.cpu), args.input).await?; - let status = judger.get_result(&args.output, args.mode); + pub async fn judge( + &self, + args: JudgeArgs, + ) -> Pin> + Send>> { + let compiler = trys!(self.as_compiler(args.source).await); + let maybe_runner = trys!(compiler.compile().await); + let mut runner = trys!(maybe_runner, Ok(JudgeResult::compile_error())); + + let mem_cpu = (args.mem, args.cpu); + let mode = args.mode; + let mut io = args.input.into_iter().zip(args.output.into_iter()); + Box::pin(try_stream! { + while let Some((input,output))=io.next(){ + let judger = runner.run(mem_cpu.clone(), input).await?; + let status = judger.get_result(&output, mode); let stat = judger.stat(); - - JudgeResult { + yield JudgeResult { status, time: stat.cpu.total, memory: stat.memory.total, + }; + if status!=StatusCode::Accepted{ + break; } } - None => JudgeResult::new(StatusCode::CompileError), }) } - async fn execute(self: Arc, args: ExecuteArgs) -> Result> { + pub async fn execute(&self, args: ExecuteArgs) -> Result> { let compiler = self.as_compiler(args.source).await?; Ok(match compiler.compile().await? { - Some(runner) => { + Some(mut runner) => { let judger = runner.run((args.mem, args.cpu), args.input).await?; todo!("stream output"); diff --git a/judger/src/language/spec.rs b/judger/src/language/spec.rs index 5ba81344..7fc60d31 100644 --- a/judger/src/language/spec.rs +++ b/judger/src/language/spec.rs @@ -1,18 +1,10 @@ -use std::{ffi::OsString, path::Path, time::Duration}; +use std::{ffi::OsString, time::Duration}; use serde::Deserialize; -use tokio::{ - fs::read_dir, - io::{AsyncRead, AsyncReadExt}, -}; use uuid::Uuid; use crate::sandbox::{Cpu, Memory, Stat}; -async fn load_plugin(path: impl AsRef) { - let dir_list = read_dir(path).await; -} - pub struct CpuFactor { kernel: u64, user: u64, diff --git a/judger/src/language/stage/mod.rs b/judger/src/language/stage/mod.rs index 38df4fa5..cb273beb 100644 --- a/judger/src/language/stage/mod.rs +++ b/judger/src/language/stage/mod.rs @@ -3,11 +3,13 @@ mod judge; mod run; pub use compile::Compiler; +use grpc::judger::JudgeMatchRule; pub use judge::Judger; pub use run::Runner; +#[derive(Clone, Copy, PartialEq, Eq)] pub enum StatusCode { - Accept, + Accepted, WrongAnswer, RuntimeError, TimeLimitExceeded, @@ -18,8 +20,26 @@ pub enum StatusCode { SystemError, } +#[derive(Clone, Copy)] pub enum AssertionMode { SkipSpace, SkipContinousSpace, Exact, } + +impl From for AssertionMode { + fn from(value: i32) -> Self { + let mode: JudgeMatchRule = value.try_into().unwrap_or_default(); + mode.into() + } +} + +impl From for AssertionMode { + fn from(rule: JudgeMatchRule) -> Self { + match rule { + JudgeMatchRule::ExactSame => AssertionMode::Exact, + JudgeMatchRule::IgnoreSnl => AssertionMode::SkipSpace, + JudgeMatchRule::SkipSnl => AssertionMode::SkipContinousSpace, + } + } +} diff --git a/judger/src/language/stage/run.rs b/judger/src/language/stage/run.rs index ee0c74ef..46421f46 100644 --- a/judger/src/language/stage/run.rs +++ b/judger/src/language/stage/run.rs @@ -18,7 +18,7 @@ impl Runner { pub fn new(filesystem: MountHandle, spec: Arc) -> Self { Self { filesystem, spec } } - pub async fn run(self, (mem, cpu): (u64, u64), input: Vec) -> Result { + pub async fn run(&mut self, (mem, cpu): (u64, u64), input: Vec) -> Result { let ctx = RunCtx { spec: self.spec.clone(), path: self.filesystem.get_path().to_path_buf(), @@ -26,8 +26,7 @@ impl Runner { }; let process = Process::new(ctx)?; let corpse = process.wait(input).await?; - drop(self.filesystem); - Ok(Judger::new(self.spec, corpse)) + Ok(Judger::new(self.spec.clone(), corpse)) } } diff --git a/judger/src/main.rs b/judger/src/main.rs index f0549033..94d76156 100644 --- a/judger/src/main.rs +++ b/judger/src/main.rs @@ -3,6 +3,7 @@ mod error; mod filesystem; mod language; mod sandbox; +mod server; pub use config::CONFIG; type Result = std::result::Result; diff --git a/judger/src/sandbox/monitor/stat.rs b/judger/src/sandbox/monitor/stat.rs index 29a3648a..d40b2cc7 100644 --- a/judger/src/sandbox/monitor/stat.rs +++ b/judger/src/sandbox/monitor/stat.rs @@ -1,5 +1,5 @@ use std::{ - ops::{Div, Mul}, + ops::{Add, AddAssign, Div, Mul}, time::Duration, }; @@ -10,7 +10,7 @@ use super::output::Output; pub type MemAndCpu = (Memory, Cpu); /// statistics of resource usage -#[derive(Clone)] +#[derive(Clone, Default)] pub struct Stat { pub memory: Memory, pub cpu: Cpu, @@ -18,14 +18,31 @@ pub struct Stat { pub walltime: Duration, } +impl AddAssign for Stat { + fn add_assign(&mut self, rhs: Stat) { + self.memory += rhs.memory; + self.cpu += rhs.cpu; + self.output += rhs.output; + self.walltime += rhs.walltime; + } +} + /// memory usage(in bytes) -#[derive(Clone)] +#[derive(Clone, Default)] pub struct Memory { pub kernel: u64, pub user: u64, pub total: u64, } +impl AddAssign for Memory { + fn add_assign(&mut self, rhs: Memory) { + self.kernel += rhs.kernel; + self.user += rhs.user; + self.total += rhs.total; + } +} + impl Mul for Memory { type Output = Memory; @@ -57,13 +74,21 @@ impl Memory { } /// cpu usage(in nanoseconds) -#[derive(Clone)] +#[derive(Clone, Default)] pub struct Cpu { pub kernel: u64, pub user: u64, pub total: u64, } +impl AddAssign for Cpu { + fn add_assign(&mut self, rhs: Cpu) { + self.kernel += rhs.kernel; + self.user += rhs.user; + self.total += rhs.total; + } +} + impl Mul for Cpu { type Output = Cpu; diff --git a/judger/src/server.rs b/judger/src/server.rs new file mode 100644 index 00000000..44c5aab5 --- /dev/null +++ b/judger/src/server.rs @@ -0,0 +1,111 @@ +use std::{pin::Pin, str::FromStr, sync::Arc}; + +use async_stream::try_stream; +use futures_core::Stream; +use grpc::judger::{judger_server::*, *}; +use tokio::{fs::File, sync::Semaphore}; +use tokio_stream::StreamExt; +use tonic::{Request, Response, Status}; +use uuid::Uuid; + +use crate::{ + error::{ClientError, Error}, + language::{JudgeArgBuilder, Map}, + CONFIG, +}; + +fn check_secret(req: tonic::Request) -> Result { + let (meta, _, payload) = req.into_parts(); + if CONFIG.secret.is_none() { + return Ok(payload); + } + let secret = CONFIG.secret.as_ref().unwrap(); + if let Some(header) = meta.get("Authorization") { + let secret = ["basic ", secret].concat().into_bytes(); + let vaild = header + .as_bytes() + .iter() + .zip(secret.iter()) + .map(|(&a, &b)| a == b) + .reduce(|a, b| a && b); + if vaild.unwrap_or(false) { + return Ok(payload); + } + } + Err(Status::permission_denied("Invalid secret")) +} + +pub struct Server { + semaphore: Arc, + plugins: Map, +} + +#[tonic::async_trait] +impl Judger for Server { + type JudgeStream = Pin> + Send>>; + + async fn judge( + &self, + req: Request, + ) -> Result, Status> { + let payload = check_secret(req)?; + + let memory = payload.memory; + let cpu = payload.time; + let source = payload.code; + let uuid = + Uuid::from_str(&payload.lang_uid).map_err(|_| ClientError::InvaildLanguageUuid)?; + + let plugin = self + .plugins + .get(&uuid) + .ok_or(ClientError::InvaildLanguageUuid)?; + let resource: u32 = plugin + .get_memory_reserved(payload.memory) + .try_into() + .map_err(|_| Error::Platform)?; + let permit = self + .semaphore + .clone() + .acquire_many_owned(resource) + .await + .map_err(|_| ClientError::ImpossibleMemoryRequirement)?; + + let (input, output): (Vec>, Vec>) = payload + .tests + .into_iter() + .map(|x| (x.input, x.output)) + .unzip(); + + let args = JudgeArgBuilder::new() + .cpu(cpu) + .mem(memory) + .input(input.into_iter()) + .output(output.into_iter()) + .mode(payload.rule.into()) + .source(source) + .build(); + + let mut result = plugin.judge(args).await; + + Ok(Response::new(Box::pin(try_stream! { + while let Some(r) = result.next().await { + yield JudgeResponse::from(r?); + } + drop(permit); + }))) + } + + async fn judger_info(&self, req: tonic::Request<()>) -> Result, Status> { + todo!() + } + + type ExecStream = tokio_stream::Iter>>; + + async fn exec( + &self, + req: Request, + ) -> Result, tonic::Status> { + todo!() + } +} From 2811cd33cd2f652c1ce9cb9015947cd9bdb4bf06 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Sat, 18 May 2024 02:13:53 +0800 Subject: [PATCH 30/38] feat: :sparkles: rebuild rlua-54 plugin --- Cargo.toml | 1 + judger/Cargo.toml | 2 +- judger/plugins/.dockerignore | 2 - judger/plugins/.gitignore | 1 + judger/plugins/rlua-54/spec.toml | 16 ++----- judger/plugins/rlua-54/src/compile.rs | 3 -- judger/src/config.rs | 34 ++++++++++++-- judger/src/filesystem/adapter/mod.rs | 4 +- judger/src/filesystem/entry/ro.rs | 17 ++----- judger/src/language/builder.rs | 9 +++- judger/src/language/plugin.rs | 14 ++++++ judger/src/language/spec.rs | 40 ++++++++++++---- judger/src/language/stage/compile.rs | 4 +- judger/src/language/stage/judge.rs | 66 ++++++++++++++++++++++++++- judger/src/language/stage/mod.rs | 18 +++++++- judger/src/main.rs | 21 ++++++++- judger/src/sandbox/macro_.rs | 11 +++++ judger/src/sandbox/mod.rs | 1 + judger/src/sandbox/monitor/hier.rs | 10 +++- judger/src/sandbox/monitor/mem_cpu.rs | 33 ++++++++------ judger/src/sandbox/monitor/stat.rs | 6 +-- judger/src/sandbox/monitor/wrapper.rs | 45 ++++++++++++++++-- judger/src/sandbox/process/corpse.rs | 1 + judger/src/sandbox/process/nsjail.rs | 3 ++ judger/src/sandbox/process/process.rs | 34 ++++++++++++-- judger/src/server.rs | 27 ++++++++++- 26 files changed, 342 insertions(+), 81 deletions(-) delete mode 100644 judger/plugins/.dockerignore create mode 100644 judger/plugins/.gitignore create mode 100644 judger/src/sandbox/macro_.rs diff --git a/Cargo.toml b/Cargo.toml index 8550e674..9edd09ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "backend/migration", "grpc", ] +exclude = ["judger/plugins/rlua-54"] [workspace.dependencies] prost = "0.12.3" diff --git a/judger/Cargo.toml b/judger/Cargo.toml index 466a0d3a..2f0bbfd2 100644 --- a/judger/Cargo.toml +++ b/judger/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cgroups-rs = "0.3.2" +cgroups-rs = "0.3.4" env_logger = "0.10.1" futures-core = "0.3.30" log = "0.4.17" diff --git a/judger/plugins/.dockerignore b/judger/plugins/.dockerignore deleted file mode 100644 index 3b4efca1..00000000 --- a/judger/plugins/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -/c-11 -/cpp-11 \ No newline at end of file diff --git a/judger/plugins/.gitignore b/judger/plugins/.gitignore new file mode 100644 index 00000000..709a832d --- /dev/null +++ b/judger/plugins/.gitignore @@ -0,0 +1 @@ +/*.lang \ No newline at end of file diff --git a/judger/plugins/rlua-54/spec.toml b/judger/plugins/rlua-54/spec.toml index 2d8bd3db..fe5a2223 100644 --- a/judger/plugins/rlua-54/spec.toml +++ b/judger/plugins/rlua-54/spec.toml @@ -1,20 +1,12 @@ -# memory in byte, time in microsecond +fs_limit = 3145728 +file = "/src/code.txt" info = "A lightweight Lua 5.4 runtime build for both secure sandboxing and modj-sandbox test" extension = "lua" # same extension means same language -name = "rlua-54" # must be same as dictionary name -uid = "1c41598f-e253-4f81-9ef5-d50bf1e4e74f" # be sure it's unique +name = "rlua-54" +id = "1c41598f-e253-4f81-9ef5-d50bf1e4e74f" # be sure it's unique [compile] command = ["/rlua-54","compile"] -kernel_mem = 67108864 -user_mem = 268435456 -rt_time = 1000000 -cpu_time = 10000000 -total_time = 10000000 [judge] command = ["/rlua-54","execute"] -kernel_mem = 67108864 -memory_multiplier = 6 # user_mem -rt_time = 1000000 -cpu_multiplier = 3 # cpu_time diff --git a/judger/plugins/rlua-54/src/compile.rs b/judger/plugins/rlua-54/src/compile.rs index 1f24c94f..b2e44ad1 100644 --- a/judger/plugins/rlua-54/src/compile.rs +++ b/judger/plugins/rlua-54/src/compile.rs @@ -4,8 +4,5 @@ use std::{ }; pub fn compile() { - let mut buf = Vec::new(); - stdin().read_to_end(&mut buf).unwrap(); - fs::write(crate::LUA_SRC, buf).unwrap(); } diff --git a/judger/src/config.rs b/judger/src/config.rs index ca411e07..3db8ba58 100644 --- a/judger/src/config.rs +++ b/judger/src/config.rs @@ -2,6 +2,10 @@ use serde::{Deserialize, Serialize}; #[cfg(not(test))] use std::path::PathBuf; +use std::{ + net::{SocketAddr, SocketAddrV4}, + str::FromStr, +}; #[cfg(not(test))] fn try_load_config() -> Result> { @@ -36,19 +40,19 @@ pub enum Accounting { Cpu, } -fn default_ratio_cpu() -> f32 { +fn default_ratio_cpu() -> f64 { 1.0 } -fn default_ratio_memory() -> f32 { +fn default_ratio_memory() -> f64 { 1.0 } #[derive(Serialize, Deserialize, Default)] pub struct Ratio { #[serde(default = "default_ratio_cpu")] - pub cpu: f32, + pub cpu: f64, #[serde(default = "default_ratio_memory")] - pub memory: f32, + pub memory: f64, } fn default_log() -> u8 { @@ -66,7 +70,11 @@ fn default_memory() -> u64 { 1024 * 1024 * 1024 } -#[derive(Serialize, Deserialize, Default)] +fn default_addr() -> SocketAddr { + SocketAddr::from_str("0.0.0.0:8081").unwrap() +} + +#[derive(Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Config { #[serde(default)] @@ -81,4 +89,20 @@ pub struct Config { pub secret: Option, #[serde(default = "default_memory")] pub memory: u64, + #[serde(default = "default_addr")] + pub address: SocketAddr, +} + +impl Default for Config { + fn default() -> Self { + Self { + accounting: Default::default(), + ratio: default_ratio(), + rootless: false, + log: default_log(), + secret: None, + memory: default_memory(), + address: default_addr(), + } + } } diff --git a/judger/src/filesystem/adapter/mod.rs b/judger/src/filesystem/adapter/mod.rs index 200cd929..c2d2e379 100644 --- a/judger/src/filesystem/adapter/mod.rs +++ b/judger/src/filesystem/adapter/mod.rs @@ -22,9 +22,9 @@ mod test { .ok(); log::info!("mounting test tarball in .temp ..."); - let template = Template::new("test/nested.tar").await.unwrap(); + let template = Template::new("plugins/rlua-54.lang").await.unwrap(); let filesystem = template.as_filesystem(1024 * 1024 * 1024); - let mut mount_handle = filesystem.mount_with_path("./.temp/1").await.unwrap(); + let mut mount_handle = filesystem.mount_with_path("./.temp/5").await.unwrap(); let handle = &mut mount_handle; tokio::select! { diff --git a/judger/src/filesystem/entry/ro.rs b/judger/src/filesystem/entry/ro.rs index d4aebc3d..3c6799c3 100644 --- a/judger/src/filesystem/entry/ro.rs +++ b/judger/src/filesystem/entry/ro.rs @@ -38,7 +38,7 @@ where F: AsyncRead + AsyncSeek + Unpin + 'static, { pub fn new(file: Arc>, start: u64, size: u32) -> Self { - log::info!("new block: start={}, size={}", start, size); + log::trace!("new block: start={}, size={}", start, size); Self { file, start, @@ -85,7 +85,8 @@ where F: AsyncRead + AsyncSeek + Unpin + 'static, { async fn read(&mut self, offset: u64, size: u32) -> std::io::Result { - let size = size as usize; + let size = size.min(self.size - self.cursor) as usize; + let mut lock = self.file.lock().await; let seek_from = self.get_seek_from(offset).ok_or(io::Error::new( io::ErrorKind::UnexpectedEof, @@ -95,18 +96,10 @@ where let mut buf = vec![0_u8; size]; - let mut readed_byte = 0; - while readed_byte < size { - match lock.read(&mut buf).await { - Err(err) if readed_byte == 0 => return Err(err), - Ok(0) | Err(_) => break, - Ok(x) => readed_byte += x, - }; + if let Err(err) = lock.read_exact(&mut buf).await { + log::warn!("tarball change at runtime, result in error: {}", err); } - readed_byte = readed_byte.min(size); - self.cursor += readed_byte as u32; - buf.resize(readed_byte, 0_u8); Ok(bytes::Bytes::from(buf)) } } diff --git a/judger/src/language/builder.rs b/judger/src/language/builder.rs index 181ab059..e727a1dc 100644 --- a/judger/src/language/builder.rs +++ b/judger/src/language/builder.rs @@ -1,4 +1,4 @@ -use grpc::judger::JudgeResponse; +use grpc::judger::{JudgeResponse, JudgerCode}; use super::stage::{AssertionMode, StatusCode}; @@ -26,7 +26,12 @@ pub struct JudgeResult { impl From for JudgeResponse { fn from(value: JudgeResult) -> Self { - todo!() + JudgeResponse { + status: Into::::into(value.status) as i32, + time: value.time, + memory: value.memory, + accuracy: 0, // FIXME: accuracy + } } } diff --git a/judger/src/language/plugin.rs b/judger/src/language/plugin.rs index d41f3452..9548394e 100644 --- a/judger/src/language/plugin.rs +++ b/judger/src/language/plugin.rs @@ -2,6 +2,7 @@ use std::{collections::BTreeMap, path::Path, pin::Pin, sync::Arc}; use async_stream::{stream, try_stream}; use futures_core::Stream; +use grpc::judger::LangInfo; use rustix::path::Arg; use tokio::{ fs::{read_dir, File}, @@ -46,8 +47,10 @@ pub async fn load_plugins(path: impl AsRef) -> Result>> { let mut dir_list = read_dir(path).await?; while let Some(entry) = dir_list.next_entry().await? { let path = entry.path(); + log::trace!("find potential plugin from {}", path.display()); let ext = path.extension(); if path.is_file() && ext.is_some() && ext.unwrap() == EXTENSION { + log::info!("load plugin from {}", path.display()); let plugin = Plugin::new(path).await?; plugins.push(plugin); } @@ -72,6 +75,9 @@ impl Map { pub fn get(&self, id: &Uuid) -> Option> { self.0.get(id).cloned() } + pub fn iter(&self) -> impl Iterator> { + self.0.values() + } } impl JudgeResult { @@ -121,7 +127,14 @@ impl Plugin where F: AsyncRead + AsyncSeek + Unpin + Send + Sync + 'static, { + pub fn get_info(&self) -> &LangInfo { + &self.spec.info + } pub async fn as_compiler(&self, source: Vec) -> Result { + log::trace!( + "create compiler from plugin {}", + self.spec.info.lang_name.as_str() + ); let filesystem = self.template.as_filesystem(self.spec.fs_limit); filesystem.insert_by_path(self.spec.file.as_os_str(), source); Ok(Compiler::new(self.spec.clone(), filesystem.mount().await?)) @@ -132,6 +145,7 @@ where ) -> Pin> + Send>> { let compiler = trys!(self.as_compiler(args.source).await); let maybe_runner = trys!(compiler.compile().await); + log::debug!("runner created"); let mut runner = trys!(maybe_runner, Ok(JudgeResult::compile_error())); let mem_cpu = (args.mem, args.cpu); diff --git a/judger/src/language/spec.rs b/judger/src/language/spec.rs index 7fc60d31..b75318bd 100644 --- a/judger/src/language/spec.rs +++ b/judger/src/language/spec.rs @@ -1,5 +1,6 @@ -use std::{ffi::OsString, time::Duration}; +use std::{ffi::OsString, str::FromStr, time::Duration}; +use grpc::judger::LangInfo; use serde::Deserialize; use uuid::Uuid; @@ -61,6 +62,7 @@ pub struct Spec { pub compile_command: Vec, pub judge_command: Vec, pub file: OsString, + pub info: LangInfo, } impl Spec { @@ -89,6 +91,7 @@ impl Spec { // FIXME: use compsition instead Self { + info: LangInfo::from(&raw), id: raw.id, fs_limit: raw.fs_limit.unwrap(), compile_limit: Stat { @@ -105,9 +108,19 @@ impl Spec { output: raw.compile.output_limit.unwrap(), walltime: Duration::from_nanos(raw.compile.walltime.unwrap()), }, - compile_command: raw.compile.command, - judge_command: raw.judge.command, - file: raw.file, + compile_command: raw + .compile + .command + .iter() + .map(|x| OsString::from(x)) + .collect(), + judge_command: raw + .judge + .command + .iter() + .map(|x| OsString::from(x)) + .collect(), + file: OsString::from(raw.file), judge_cpu_factor: CpuFactor { kernel: raw.judge.kernel_mem.unwrap(), user: raw.judge.rt_time.unwrap(), @@ -127,9 +140,9 @@ impl Spec { } #[derive(Deserialize)] -struct Raw { +pub struct Raw { fs_limit: Option, - file: OsString, + file: String, info: String, extension: String, name: String, @@ -138,6 +151,17 @@ struct Raw { judge: RawJudge, } +impl<'a> From<&'a Raw> for LangInfo { + fn from(value: &'a Raw) -> Self { + LangInfo { + lang_uid: value.id.to_string(), + lang_name: value.name.clone(), + info: value.info.clone(), + lang_ext: value.extension.clone(), + } + } +} + impl Raw { pub fn fill(&mut self) { if self.fs_limit.is_none() { @@ -150,7 +174,7 @@ impl Raw { #[derive(Deserialize)] struct RawCompile { - command: Vec, + command: Vec, kernel_mem: Option, memory: Option, user_mem: Option, @@ -206,7 +230,7 @@ impl Default for RawCompile { #[derive(Deserialize)] struct RawJudge { - command: Vec, + command: Vec, kernel_mem: Option, rt_time: Option, memory_multiplier: Option, diff --git a/judger/src/language/stage/compile.rs b/judger/src/language/stage/compile.rs index abefe824..bdef2ab9 100644 --- a/judger/src/language/stage/compile.rs +++ b/judger/src/language/stage/compile.rs @@ -1,3 +1,4 @@ +use core::time; use std::{path::PathBuf, sync::Arc, time::Duration}; use crate::{ @@ -26,7 +27,8 @@ impl Compiler { let process = Process::new(ctx)?; let corpse = process.wait(Vec::new()).await?; if !corpse.success() { - log::debug!("compile failed {:?}", corpse.status()); + log::trace!("compile failed, corpse: {:?}", corpse); + tokio::time::sleep(Duration::from_secs(3600)).await; return Ok(None); } diff --git a/judger/src/language/stage/judge.rs b/judger/src/language/stage/judge.rs index ff08861f..07205faf 100644 --- a/judger/src/language/stage/judge.rs +++ b/judger/src/language/stage/judge.rs @@ -24,7 +24,71 @@ impl Judger { // self.corpse.stream_stdout() // } fn assert_output(&self, output: &[u8], mode: AssertionMode) -> StatusCode { - todo!() + let input = self.corpse.stdout(); + match mode { + AssertionMode::SkipSpace => { + // skip space and newline, continous space and single space is consider different + let output = output.iter().map(|x| match x { + b'\n' | b' ' => b' ', + x => *x, + }); + let input = input.iter().map(|x| match x { + b'\n' | b' ' => b' ', + x => *x, + }); + for (i, o) in input.zip(output) { + if i != o { + return StatusCode::WrongAnswer; + } + } + } + AssertionMode::SkipContinousSpace => { + // skip space and newline, continous space is consider same + let output = output.iter().map(|x| match x { + b'\n' | b' ' => b' ', + x => *x, + }); + let input = input.iter().map(|x| match x { + b'\n' | b' ' => b' ', + x => *x, + }); + let mut output = output.peekable(); + let mut input = input.peekable(); + while let (Some(&i), Some(&o)) = (input.peek(), output.peek()) { + if i == b' ' { + while let Some(&x) = input.peek() { + if x != b' ' { + break; + } + input.next(); + } + while let Some(&x) = output.peek() { + if x != b' ' { + break; + } + output.next(); + } + } else if i != o { + return StatusCode::WrongAnswer; + } else { + input.next(); + output.next(); + } + } + if input.peek().is_some() || output.peek().is_some() { + return StatusCode::WrongAnswer; + } + } + AssertionMode::Exact => { + for (i, o) in input.iter().zip(output.iter()) { + if i != o { + return StatusCode::WrongAnswer; + } + } + } + } + + StatusCode::Accepted } pub fn get_result(&self, output: &[u8], mode: AssertionMode) -> StatusCode { match self.corpse.status() { diff --git a/judger/src/language/stage/mod.rs b/judger/src/language/stage/mod.rs index cb273beb..af3cceec 100644 --- a/judger/src/language/stage/mod.rs +++ b/judger/src/language/stage/mod.rs @@ -3,7 +3,7 @@ mod judge; mod run; pub use compile::Compiler; -use grpc::judger::JudgeMatchRule; +use grpc::{judger::JudgeMatchRule, judger::JudgerCode}; pub use judge::Judger; pub use run::Runner; @@ -43,3 +43,19 @@ impl From for AssertionMode { } } } + +impl From for JudgerCode { + fn from(value: StatusCode) -> Self { + match value { + StatusCode::Accepted => Self::Ac, + StatusCode::WrongAnswer => Self::Wa, + StatusCode::RuntimeError => Self::Re, + StatusCode::TimeLimitExceeded => Self::Tle, + StatusCode::MemoryLimitExceeded => Self::Mle, + StatusCode::OutputLimitExceeded => Self::Ole, + StatusCode::RealTimeLimitExceeded => Self::Na, + StatusCode::CompileError => Self::Ce, + StatusCode::SystemError => Self::Na, + } + } +} diff --git a/judger/src/main.rs b/judger/src/main.rs index 94d76156..2e8f1d0a 100644 --- a/judger/src/main.rs +++ b/judger/src/main.rs @@ -6,6 +6,25 @@ mod sandbox; mod server; pub use config::CONFIG; + +use grpc::judger::judger_server::JudgerServer; +use server::Server; + type Result = std::result::Result; -fn main() {} +#[tokio::main] +async fn main() { + // FIXME: use CONFIG for logging + env_logger::Builder::from_default_env() + .filter_level(log::LevelFilter::Trace) + .try_init() + .ok(); + + let server = Server::new().await.unwrap(); + + tonic::transport::Server::builder() + .add_service(JudgerServer::new(server)) + .serve(CONFIG.address) + .await + .unwrap(); +} diff --git a/judger/src/sandbox/macro_.rs b/judger/src/sandbox/macro_.rs new file mode 100644 index 00000000..6053f331 --- /dev/null +++ b/judger/src/sandbox/macro_.rs @@ -0,0 +1,11 @@ +#[macro_export] +macro_rules! async_loop { + ($e:expr) => { + async move { + loop { + $e + tokio::time::sleep(crate::sandbox::monitor::mem_cpu::MONITOR_ACCURACY).await; + } + } + }; +} diff --git a/judger/src/sandbox/mod.rs b/judger/src/sandbox/mod.rs index 2752b248..5e529bd8 100644 --- a/judger/src/sandbox/mod.rs +++ b/judger/src/sandbox/mod.rs @@ -1,4 +1,5 @@ mod error; +mod macro_; mod monitor; mod process; diff --git a/judger/src/sandbox/monitor/hier.rs b/judger/src/sandbox/monitor/hier.rs index 73d75be7..7201084b 100644 --- a/judger/src/sandbox/monitor/hier.rs +++ b/judger/src/sandbox/monitor/hier.rs @@ -10,8 +10,8 @@ pub enum MonitorKind { } lazy_static::lazy_static! { - pub static ref MONITER_KIND: MonitorKind = - match crate::CONFIG.accounting { + pub static ref MONITER_KIND: MonitorKind = { + let kind=match crate::CONFIG.accounting { Accounting::Auto =>match hierarchies::auto().v2(){ true=>MonitorKind::Cpu, false=>MonitorKind::CpuAcct @@ -19,6 +19,12 @@ lazy_static::lazy_static! { Accounting::CpuAccounting => MonitorKind::CpuAcct, Accounting::Cpu => MonitorKind::Cpu, }; + match kind.heir().v2(){ + true=>log::info!("using cgroup v2"), + false=>log::info!("using cgroup v1") + } + kind + }; } impl MonitorKind { diff --git a/judger/src/sandbox/monitor/mem_cpu.rs b/judger/src/sandbox/monitor/mem_cpu.rs index 6f11882d..321bc864 100644 --- a/judger/src/sandbox/monitor/mem_cpu.rs +++ b/judger/src/sandbox/monitor/mem_cpu.rs @@ -1,10 +1,12 @@ +use crate::async_loop; + use super::{stat::*, *}; use cgroups_rs::{cgroup_builder::CgroupBuilder, Cgroup}; use std::sync::{atomic::Ordering, Arc}; -use tokio::time::*; +use tokio::{select, time::*}; /// maximum allow time deviation for cpu monitor -const MONITOR_ACCURACY: Duration = Duration::from_millis(80); +pub const MONITOR_ACCURACY: Duration = Duration::from_millis(80); const CG_PATH_COUNTER: AtomicUsize = AtomicUsize::new(0); @@ -13,18 +15,20 @@ async fn monitor(cgroup: Arc, cpu: Cpu) -> MonitorKind { let oom_signal = wrapper.oom_signal(); - loop { - sleep(MONITOR_ACCURACY / 2).await; - - if let Ok(oom_hint) = oom_signal.try_recv() { - log::trace!("oom hint: {}", oom_hint); - return MonitorKind::Memory; + let cpu_future = async_loop!({ + if Cpu::out_of_resources(&cpu, wrapper.cpu()) { + break; } + wrapper.cpu(); + }); - if Cpu::out_of_resources(&cpu, wrapper.cpu()) { + select! { + _ = cpu_future=>{ return MonitorKind::Cpu; + }, + _ = oom_signal.wait()=>{ + return MonitorKind::Memory; } - wrapper.cpu(); } } @@ -52,6 +56,7 @@ impl Monitor { /// create a new limiter and mount at given path pub fn new((mem, cpu): MemAndCpu) -> Result { let cg_name = format!("mdoj.{}", CG_PATH_COUNTER.fetch_add(1, Ordering::AcqRel)); + log::trace!("create cgroup, name: {}", cg_name); let cgroup = Arc::new( CgroupBuilder::new(&cg_name) .memory() @@ -60,16 +65,18 @@ impl Monitor { .memory_swap_limit(0) .done() .cpu() - .period((MONITOR_ACCURACY / 2).as_nanos() as u64) + // .period((MONITOR_ACCURACY / 2).as_nanos() as u64) .quota(MONITOR_ACCURACY.as_nanos() as i64) .realtime_period(MONITOR_ACCURACY.as_nanos() as u64) - .realtime_runtime(MONITOR_ACCURACY.as_nanos() as i64) + // .realtime_runtime(MONITOR_ACCURACY.as_nanos() as i64) .done() .build(MONITER_KIND.heir())?, ); + // FIXME: set oom control let monitor_task = Some(tokio::spawn(monitor(cgroup.clone(), cpu.clone()))); + log::debug!("cgroup created: {}", cgroup.path()); Ok(Self { cgroup, monitor_task, @@ -96,7 +103,7 @@ impl super::Monitor for Monitor { /// /// This method is cancellation safe async fn wait_exhaust(&mut self) -> MonitorKind { - let reason = self.monitor_task.take().unwrap().await.unwrap(); + let reason = self.monitor_task.as_mut().unwrap().await.unwrap(); // optimistic kill(`SIGKILL`) the process inside self.cgroup.kill().expect("cgroup.kill does not exist"); reason diff --git a/judger/src/sandbox/monitor/stat.rs b/judger/src/sandbox/monitor/stat.rs index d40b2cc7..1de40700 100644 --- a/judger/src/sandbox/monitor/stat.rs +++ b/judger/src/sandbox/monitor/stat.rs @@ -10,7 +10,7 @@ use super::output::Output; pub type MemAndCpu = (Memory, Cpu); /// statistics of resource usage -#[derive(Clone, Default)] +#[derive(Clone, Default, Debug)] pub struct Stat { pub memory: Memory, pub cpu: Cpu, @@ -28,7 +28,7 @@ impl AddAssign for Stat { } /// memory usage(in bytes) -#[derive(Clone, Default)] +#[derive(Clone, Default, Debug)] pub struct Memory { pub kernel: u64, pub user: u64, @@ -74,7 +74,7 @@ impl Memory { } /// cpu usage(in nanoseconds) -#[derive(Clone, Default)] +#[derive(Clone, Default, Debug)] pub struct Cpu { pub kernel: u64, pub user: u64, diff --git a/judger/src/sandbox/monitor/wrapper.rs b/judger/src/sandbox/monitor/wrapper.rs index 6643835d..fbf396b7 100644 --- a/judger/src/sandbox/monitor/wrapper.rs +++ b/judger/src/sandbox/monitor/wrapper.rs @@ -1,6 +1,29 @@ +use crate::async_loop; + use super::{hier::*, stat::*}; use cgroups_rs::{cpu::CpuController, cpuacct::CpuAcctController, memory::MemController, Cgroup}; -use std::ops::Deref; +use std::{ops::Deref, pin::pin}; +use tokio::{sync::oneshot, task::JoinHandle, time}; + +pub struct OOMSignal { + rx: Option>, +} + +impl Drop for OOMSignal { + fn drop(&mut self) { + self.rx.take().unwrap().abort(); + } +} + +impl OOMSignal { + fn new(rx: JoinHandle<()>) -> Self { + Self { rx: Some(rx) } + } + pub async fn wait(mut self) { + let rx = pin!(self.rx.as_mut().unwrap()); + rx.await.ok(); + } +} /// newtype wrapper for cgroup form cgroup_rs pub struct CgroupWrapper<'a> { @@ -14,11 +37,11 @@ impl<'a> CgroupWrapper<'a> { /// get cpu usage(statistics) pub fn cpu(&self) -> Cpu { match MONITER_KIND.deref() { - MonitorKind::Cpu => { + MonitorKind::CpuAcct => { let controller: &CpuAcctController = self.cgroup.controller_of().unwrap(); Cpu::from_acct(controller.cpuacct()) } - MonitorKind::CpuAcct => { + MonitorKind::Cpu => { let controller: &CpuController = self.cgroup.controller_of().unwrap(); let raw: &str = &controller.cpu().stat; Cpu::from_raw(raw) @@ -26,9 +49,21 @@ impl<'a> CgroupWrapper<'a> { } } /// get an receiver(synchronize) for oom event - pub fn oom_signal(&self) -> std::sync::mpsc::Receiver { + pub fn oom_signal(&self) -> OOMSignal { let controller = self.cgroup.controller_of::().unwrap(); - controller.register_oom_event("mdoj-oom-handler").unwrap() + if self.cgroup.v2() { + let controller = controller.to_owned(); + OOMSignal::new(tokio::spawn(async_loop!({ + if controller.memory_stat().oom_control.oom_kill != 0 { + break; + } + }))) + } else { + let oom_signal = controller.register_oom_event("mdoj_oom").unwrap(); + OOMSignal::new(tokio::task::spawn_blocking(move || { + oom_signal.recv().ok(); + })) + } } /// get memory usage(statistics) pub fn memory(&self) -> Memory { diff --git a/judger/src/sandbox/process/corpse.rs b/judger/src/sandbox/process/corpse.rs index 961004e6..c71e6cc2 100644 --- a/judger/src/sandbox/process/corpse.rs +++ b/judger/src/sandbox/process/corpse.rs @@ -3,6 +3,7 @@ use std::process::ExitStatus; use super::monitor::{MonitorKind, Stat}; /// A corpse of a process +#[derive(Debug)] pub struct Corpse { /// exit code of signal pub(super) code: Option, diff --git a/judger/src/sandbox/process/nsjail.rs b/judger/src/sandbox/process/nsjail.rs index e7207777..d279972f 100644 --- a/judger/src/sandbox/process/nsjail.rs +++ b/judger/src/sandbox/process/nsjail.rs @@ -34,6 +34,8 @@ pub struct BaseArg; impl Argument for BaseArg { fn get_args(self) -> impl Iterator> { vec![ + Cow::Borrowed(OsStr::from_bytes(b"-l")), + Cow::Borrowed(OsStr::from_bytes(b"/dev/null")), Cow::Borrowed(OsStr::from_bytes(b"--disable_clone_newuser")), Cow::Borrowed(OsStr::from_bytes(b"--disable_clone_newcgroup")), Cow::Borrowed(OsStr::from_bytes(b"--env")), @@ -94,6 +96,7 @@ impl<'a> Argument for MountArg<'a> { fn get_args(self) -> impl Iterator> { vec![ Cow::Borrowed(OsStr::from_bytes(b"--rw")), + Cow::Borrowed(OsStr::from_bytes(b"--chroot")), Cow::Owned(OsString::from(self.rootfs)), ] .into_iter() diff --git a/judger/src/sandbox/process/process.rs b/judger/src/sandbox/process/process.rs index 099c95b4..5fadac1d 100644 --- a/judger/src/sandbox/process/process.rs +++ b/judger/src/sandbox/process/process.rs @@ -1,5 +1,9 @@ use super::{corpse::Corpse, error::Error, monitor::*, nsjail::*, Context, Filesystem}; -use std::process::Stdio; +use std::{ + ffi::{OsStr, OsString}, + os::unix::ffi::OsStrExt, + process::Stdio, +}; use tokio::{ io::{self, AsyncWriteExt, DuplexStream}, process::*, @@ -72,6 +76,17 @@ pub struct Process { stdout: DuplexStream, } +fn get_inner_args<'a>( + mut args: impl Iterator, + mut root: OsString, +) -> Vec { + // check spec before unwrap + root.push(args.next().unwrap()); + let mut r = vec![root]; + r.extend(args.map(|x| x.to_os_string())); + r +} + impl Process { pub fn new(context: C) -> Result { MonitoredProcess::new(context).map(Into::into) @@ -84,6 +99,11 @@ impl Process { cmd.stdout(Stdio::piped()); cmd.stderr(Stdio::null()); + let inner_args = get_inner_args( + self.context.get_args(), + self.fs.get_path().as_ref().as_os_str().to_os_string(), + ); + let arg_factory = ArgFactory::default() .add(BaseArg) .add(CGroupVersionArg) @@ -94,10 +114,13 @@ impl Process { rootfs: self.fs.get_path().as_ref().as_os_str(), }) .add(InnerProcessArg { - inner_args: self.context.get_args(), + inner_args: inner_args.iter().map(|x| x.as_os_str()), }); - cmd.args(arg_factory.build()); + let args = arg_factory.build(); + + log::trace!("spawn process with args: {:?}", args); + cmd.args(args); Ok(cmd.spawn()?) } @@ -118,10 +141,11 @@ impl Process { let mut monitor = self.monitor; let code = tokio::select! { - _=monitor.wait_exhaust()=>{None}, + _=monitor.wait_exhaust()=>None, x=process.wait()=>{ time::sleep(time::Duration::from_millis(100)).await; - Some(x?)} + Some(x?) + } }; // wait for the proxy to finish for full output // in case of OLE, the monitor will drop and the proxy will be cancelled(yield) diff --git a/judger/src/server.rs b/judger/src/server.rs index 44c5aab5..9e03f63a 100644 --- a/judger/src/server.rs +++ b/judger/src/server.rs @@ -14,6 +14,8 @@ use crate::{ CONFIG, }; +const PLUGIN_PATH: &str = "plugins"; + fn check_secret(req: tonic::Request) -> Result { let (meta, _, payload) = req.into_parts(); if CONFIG.secret.is_none() { @@ -40,6 +42,14 @@ pub struct Server { plugins: Map, } +impl Server { + pub async fn new() -> crate::Result { + let semaphore = Arc::new(Semaphore::new(CONFIG.memory.try_into().unwrap())); + let plugins = Map::new(PLUGIN_PATH).await?; + Ok(Server { semaphore, plugins }) + } +} + #[tonic::async_trait] impl Judger for Server { type JudgeStream = Pin> + Send>>; @@ -97,15 +107,28 @@ impl Judger for Server { } async fn judger_info(&self, req: tonic::Request<()>) -> Result, Status> { - todo!() + check_secret(req)?; + let list = self + .plugins + .iter() + .map(|v| v.get_info().clone()) + .collect::>(); + Ok(Response::new(JudgeInfo { + memory: CONFIG.memory, + accuracy: 0, // FIXME: accuracy + langs: Langs { list }, + cpu_factor: CONFIG.ratio.cpu as f32, + })) } - type ExecStream = tokio_stream::Iter>>; + type ExecStream = Pin> + Send>>; async fn exec( &self, req: Request, ) -> Result, tonic::Status> { + let payload = check_secret(req)?; + todo!() } } From 7f37c974a0035cb876065991d28952d9e17456a3 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Sat, 18 May 2024 15:33:11 +0800 Subject: [PATCH 31/38] test(Judger): :white_check_mark: verify TLE, WA, AC of rlua-54 --- judger/Cargo.toml | 5 ++++- judger/src/filesystem/adapter/handle.rs | 2 +- judger/src/filesystem/adapter/reply.rs | 7 +------ judger/src/filesystem/entry/mod.rs | 1 + judger/src/filesystem/entry/ro.rs | 3 ++- judger/src/language/plugin.rs | 1 + judger/src/language/spec.rs | 21 ++++++++++++--------- judger/src/language/stage/compile.rs | 2 +- judger/src/language/stage/mod.rs | 4 ++-- judger/src/main.rs | 11 +++++++++++ judger/src/sandbox/monitor/mem_cpu.rs | 16 +++++++++++----- judger/src/sandbox/monitor/output.rs | 1 + judger/src/sandbox/process/nsjail.rs | 16 ++++++++++++++-- judger/src/sandbox/process/process.rs | 22 +++++++++++++++------- 14 files changed, 77 insertions(+), 35 deletions(-) diff --git a/judger/Cargo.toml b/judger/Cargo.toml index 2f0bbfd2..81047fae 100644 --- a/judger/Cargo.toml +++ b/judger/Cargo.toml @@ -9,7 +9,6 @@ edition = "2021" cgroups-rs = "0.3.4" env_logger = "0.10.1" futures-core = "0.3.30" -log = "0.4.17" prost = { workspace = true } prost-types = { workspace = true } thiserror = "1.0.40" @@ -22,6 +21,10 @@ libc = "0.2.154" bytes = "1.6.0" async-stream = "0.3.5" +[dependencies.log] +version = "0.4.17" +features = ["release_max_level_debug"] + [dependencies.fuse3] version = "0.7.1" features = ["tokio-runtime", "unprivileged"] diff --git a/judger/src/filesystem/adapter/handle.rs b/judger/src/filesystem/adapter/handle.rs index 845cb3ff..2a0e8d65 100644 --- a/judger/src/filesystem/adapter/handle.rs +++ b/judger/src/filesystem/adapter/handle.rs @@ -29,7 +29,7 @@ impl HandleTable { } /// Get an entry from the table pub fn get(&self, handle: u64) -> Option> { - log::debug!("get handle: {}", handle); + log::trace!("get handle: {}", handle); self.table.lock().get(&handle).cloned() } /// Remove an entry from the table diff --git a/judger/src/filesystem/adapter/reply.rs b/judger/src/filesystem/adapter/reply.rs index b5c8cc7e..5592a96f 100644 --- a/judger/src/filesystem/adapter/reply.rs +++ b/judger/src/filesystem/adapter/reply.rs @@ -77,12 +77,7 @@ where mtime: Timestamp::new(0, 0), ctime: Timestamp::new(0, 0), kind: entry.kind(), - perm: (libc::S_IREAD - | libc::S_IWRITE - | libc::S_IEXEC - | libc::S_IRWXU - | libc::S_IRWXO - | libc::S_ISVTX) as u16, + perm: (libc::S_IRWXO | libc::S_IRWXG | libc::S_IRWXU) as u16, nlink: 1, uid: req.uid, gid: req.gid, diff --git a/judger/src/filesystem/entry/mod.rs b/judger/src/filesystem/entry/mod.rs index 425bd77a..055f979e 100644 --- a/judger/src/filesystem/entry/mod.rs +++ b/judger/src/filesystem/entry/mod.rs @@ -15,6 +15,7 @@ use super::resource::Resource; pub use tar::TarTree; pub const BLOCKSIZE: usize = 4096; +const MAX_READ_BLK: usize = 1024; pub trait FuseReadTrait { async fn read(&mut self, offset: u64, size: u32) -> std::io::Result; diff --git a/judger/src/filesystem/entry/ro.rs b/judger/src/filesystem/entry/ro.rs index 3c6799c3..7ad570af 100644 --- a/judger/src/filesystem/entry/ro.rs +++ b/judger/src/filesystem/entry/ro.rs @@ -14,7 +14,7 @@ use tokio::{ sync::Mutex, }; -use super::FuseReadTrait; +use super::{FuseReadTrait, BLOCKSIZE, MAX_READ_BLK}; /// A block in tar file, should be readonly /// @@ -86,6 +86,7 @@ where { async fn read(&mut self, offset: u64, size: u32) -> std::io::Result { let size = size.min(self.size - self.cursor) as usize; + let size=size.min(BLOCKSIZE*MAX_READ_BLK); let mut lock = self.file.lock().await; let seek_from = self.get_seek_from(offset).ok_or(io::Error::new( diff --git a/judger/src/language/plugin.rs b/judger/src/language/plugin.rs index 9548394e..5bd91d77 100644 --- a/judger/src/language/plugin.rs +++ b/judger/src/language/plugin.rs @@ -155,6 +155,7 @@ where while let Some((input,output))=io.next(){ let judger = runner.run(mem_cpu.clone(), input).await?; let status = judger.get_result(&output, mode); + log::trace!("status: {:?}", status); let stat = judger.stat(); yield JudgeResult { diff --git a/judger/src/language/spec.rs b/judger/src/language/spec.rs index b75318bd..e8ee10a9 100644 --- a/judger/src/language/spec.rs +++ b/judger/src/language/spec.rs @@ -132,8 +132,8 @@ impl Spec { total: raw.judge.memory_multiplier.unwrap(), }, judge_limit: ( - raw.judge.walltime.unwrap(), - Duration::from_nanos(raw.judge.rt_time.unwrap()), + raw.judge.output.unwrap(), + Duration::from_nanos(raw.judge.walltime.unwrap()), ), } } @@ -219,11 +219,11 @@ impl Default for RawCompile { kernel_mem: Some(268435456), memory: Some(268435456), user_mem: Some(8589934592), - rt_time: Some(1000000), - cpu_time: Some(10000000000), - time: Some(10000000), + rt_time: Some(7e8 as u64), + cpu_time: Some(10e9 as u64), + time: Some(10e9 as u64), output_limit: Some(33554432), - walltime: Some(360000000), + walltime: Some(260e9 as u64), } } } @@ -236,6 +236,7 @@ struct RawJudge { memory_multiplier: Option, cpu_multiplier: Option, walltime: Option, + output: Option, } impl RawJudge { @@ -257,7 +258,8 @@ impl RawJudge { rt_time, memory_multiplier, cpu_multiplier, - walltime + walltime, + output ); } } @@ -267,10 +269,11 @@ impl Default for RawJudge { Self { command: Vec::new(), kernel_mem: Some(268435456), - rt_time: Some(10000000), + rt_time: Some(7e8 as u64), memory_multiplier: Some(1.0), cpu_multiplier: Some(1.0), - walltime: Some(360000000), + walltime: Some(360e9 as u64), + output: Some(1024*1024*16), } } } diff --git a/judger/src/language/stage/compile.rs b/judger/src/language/stage/compile.rs index bdef2ab9..d192fc07 100644 --- a/judger/src/language/stage/compile.rs +++ b/judger/src/language/stage/compile.rs @@ -28,7 +28,7 @@ impl Compiler { let corpse = process.wait(Vec::new()).await?; if !corpse.success() { log::trace!("compile failed, corpse: {:?}", corpse); - tokio::time::sleep(Duration::from_secs(3600)).await; + // tokio::time::sleep(Duration::from_secs(600)).await; return Ok(None); } diff --git a/judger/src/language/stage/mod.rs b/judger/src/language/stage/mod.rs index af3cceec..52987dd9 100644 --- a/judger/src/language/stage/mod.rs +++ b/judger/src/language/stage/mod.rs @@ -4,10 +4,9 @@ mod run; pub use compile::Compiler; use grpc::{judger::JudgeMatchRule, judger::JudgerCode}; -pub use judge::Judger; pub use run::Runner; -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum StatusCode { Accepted, WrongAnswer, @@ -46,6 +45,7 @@ impl From for AssertionMode { impl From for JudgerCode { fn from(value: StatusCode) -> Self { + match value { StatusCode::Accepted => Self::Ac, StatusCode::WrongAnswer => Self::Wa, diff --git a/judger/src/main.rs b/judger/src/main.rs index 2e8f1d0a..a12896f7 100644 --- a/judger/src/main.rs +++ b/judger/src/main.rs @@ -20,6 +20,17 @@ async fn main() { .try_init() .ok(); + let default_panic = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |info| { + log::error!("something panic, exiting..."); + default_panic(info); + std::process::exit(1); + })); + + + #[cfg(debug_assertions)] + log::warn!("running debug build"); + let server = Server::new().await.unwrap(); tonic::transport::Server::builder() diff --git a/judger/src/sandbox/monitor/mem_cpu.rs b/judger/src/sandbox/monitor/mem_cpu.rs index 321bc864..2bce248e 100644 --- a/judger/src/sandbox/monitor/mem_cpu.rs +++ b/judger/src/sandbox/monitor/mem_cpu.rs @@ -19,7 +19,6 @@ async fn monitor(cgroup: Arc, cpu: Cpu) -> MonitorKind { if Cpu::out_of_resources(&cpu, wrapper.cpu()) { break; } - wrapper.cpu(); }); select! { @@ -44,10 +43,17 @@ impl Drop for Monitor { if let Some(monitor_task) = &self.monitor_task { monitor_task.abort(); } - debug_assert!(self.cgroup.tasks().is_empty()); - match self.cgroup.tasks().is_empty() { - true => log::warn!("cgroup still have process running"), - false => self.cgroup.delete().expect("cgroup cannot be deleted"), + // FIXME: use explicit control flow + // currently is controlled by dropping order, and it can be broken + // if one of the thread panics + match self.cgroup.v2(){ + true => { + self.cgroup.kill().expect("cgroup.kill does not exist"); + self.cgroup.delete().unwrap(); + }, + false => { + self.cgroup.set_release_agent("").unwrap(); + }, } } } diff --git a/judger/src/sandbox/monitor/output.rs b/judger/src/sandbox/monitor/output.rs index 81a563a4..5cac3c7c 100644 --- a/judger/src/sandbox/monitor/output.rs +++ b/judger/src/sandbox/monitor/output.rs @@ -26,6 +26,7 @@ pub struct Monitor { /// impl Monitor { fn inner_new(limit: Output, stdin: I) -> Self { + log::info!("Output limit: {}", limit); Self { buffer: Vec::with_capacity(limit as usize / 4), reader: Some(BufReader::new(stdin.take(limit))), diff --git a/judger/src/sandbox/process/nsjail.rs b/judger/src/sandbox/process/nsjail.rs index d279972f..1b1c98c6 100644 --- a/judger/src/sandbox/process/nsjail.rs +++ b/judger/src/sandbox/process/nsjail.rs @@ -3,6 +3,7 @@ use std::{ ffi::{OsStr, OsString}, ops::Deref, os::unix::ffi::OsStrExt, + path::Path, }; pub static NSJAIL_PATH: &str = "./nsjail-3.1"; @@ -34,10 +35,13 @@ pub struct BaseArg; impl Argument for BaseArg { fn get_args(self) -> impl Iterator> { vec![ + Cow::Borrowed(OsStr::from_bytes(b"-Me")), Cow::Borrowed(OsStr::from_bytes(b"-l")), + #[cfg(not(debug_assertions))] Cow::Borrowed(OsStr::from_bytes(b"/dev/null")), + #[cfg(debug_assertions)] + Cow::Borrowed(OsStr::from_bytes(b"nsjail.log")), Cow::Borrowed(OsStr::from_bytes(b"--disable_clone_newuser")), - Cow::Borrowed(OsStr::from_bytes(b"--disable_clone_newcgroup")), Cow::Borrowed(OsStr::from_bytes(b"--env")), Cow::Borrowed(OsStr::from_bytes( b"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", @@ -58,10 +62,16 @@ impl<'a> Argument for CGroupMountArg<'a> { match super::monitor::CGROUP_V2.deref() { // this is a patch(not default behavior of nsjail) true => vec![ + Cow::Borrowed(OsStr::from_bytes(b"--disable_clone_newcgroup")), + Cow::Borrowed(OsStr::from_bytes(b"--cgroup_mem_swap_max")), + Cow::Borrowed(OsStr::from_bytes(b"0")), Cow::Borrowed(OsStr::from_bytes(b"--cgroup_cpu_parent")), Cow::Owned(OsString::from(self.cg_name)), ], false => vec![ + Cow::Borrowed(OsStr::from_bytes(b"--disable_clone_newcgroup")), + Cow::Borrowed(OsStr::from_bytes(b"--cgroup_mem_swap_max")), + Cow::Borrowed(OsStr::from_bytes(b"0")), Cow::Borrowed(OsStr::from_bytes(b"--cgroup_mem_mount")), Cow::Owned(format!("/sys/fs/cgroup/memory/{}", self.cg_name).into()), Cow::Borrowed(OsStr::from_bytes(b"--cgroup_cpu_mount")), @@ -89,12 +99,14 @@ impl Argument for CGroupVersionArg { /// arguments for rootfs mount pub struct MountArg<'a> { - pub rootfs: &'a OsStr, + pub rootfs: &'a Path, } impl<'a> Argument for MountArg<'a> { fn get_args(self) -> impl Iterator> { vec![ + Cow::Borrowed(OsStr::from_bytes(b"--tmpfsmount")), + Cow::Borrowed(OsStr::from_bytes(b"/tmp")), Cow::Borrowed(OsStr::from_bytes(b"--rw")), Cow::Borrowed(OsStr::from_bytes(b"--chroot")), Cow::Owned(OsString::from(self.rootfs)), diff --git a/judger/src/sandbox/process/process.rs b/judger/src/sandbox/process/process.rs index 5fadac1d..5be31f92 100644 --- a/judger/src/sandbox/process/process.rs +++ b/judger/src/sandbox/process/process.rs @@ -2,6 +2,7 @@ use super::{corpse::Corpse, error::Error, monitor::*, nsjail::*, Context, Filesy use std::{ ffi::{OsStr, OsString}, os::unix::ffi::OsStrExt, + path::{Path, PathBuf}, process::Stdio, }; use tokio::{ @@ -91,6 +92,17 @@ impl Process { pub fn new(context: C) -> Result { MonitoredProcess::new(context).map(Into::into) } + fn get_env(&mut self) -> OsString { + let root = self.fs.get_path(); + // FIXME: check spec before unwrap + let jail = self.context.get_args().next().unwrap(); + let unjailed = [jail, root.as_ref().as_os_str()].join(OsStr::new("")); + let unjailed = PathBuf::from(unjailed); + + let mut ancestors = unjailed.ancestors(); + ancestors.next().unwrap(); + ancestors.next().unwrap().as_os_str().to_os_string() + } /// spawn a raw process fn spawn_raw_process(&mut self) -> Result { let mut cmd = Command::new(NSJAIL_PATH); @@ -98,11 +110,7 @@ impl Process { cmd.stdin(Stdio::piped()); cmd.stdout(Stdio::piped()); cmd.stderr(Stdio::null()); - - let inner_args = get_inner_args( - self.context.get_args(), - self.fs.get_path().as_ref().as_os_str().to_os_string(), - ); + cmd.env("PATH", self.get_env()); let arg_factory = ArgFactory::default() .add(BaseArg) @@ -111,10 +119,10 @@ impl Process { cg_name: self.monitor.get_cg_path(), }) .add(MountArg { - rootfs: self.fs.get_path().as_ref().as_os_str(), + rootfs: self.fs.get_path().as_ref(), }) .add(InnerProcessArg { - inner_args: inner_args.iter().map(|x| x.as_os_str()), + inner_args: self.context.get_args(), }); let args = arg_factory.build(); From 66c812cd6a5e294bef12d8d830135194aeeb2261 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Mon, 27 May 2024 16:19:00 +0800 Subject: [PATCH 32/38] docs(Judger): :memo: update development documents --- judger/src/filesystem/adapter/error.rs | 7 ++ judger/src/filesystem/adapter/fuse.rs | 20 ++-- judger/src/filesystem/adapter/handle.rs | 8 +- judger/src/filesystem/adapter/mod.rs | 2 +- judger/src/filesystem/adapter/template.rs | 11 +- judger/src/filesystem/dev.md | 118 ++++++++++++++++++++++ judger/src/filesystem/entry/mod.rs | 18 ++-- judger/src/filesystem/entry/ro.rs | 2 +- judger/src/filesystem/entry/tar.rs | 32 +----- judger/src/filesystem/handle.rs | 2 +- judger/src/filesystem/resource.rs | 3 +- judger/src/filesystem/table.rs | 2 + judger/src/language/builder.rs | 36 ++++++- judger/src/language/plugin.rs | 39 +++---- judger/src/language/spec.rs | 2 +- judger/src/language/stage/compile.rs | 6 ++ judger/src/language/stage/judge.rs | 16 ++- judger/src/language/stage/mod.rs | 17 +++- judger/src/language/stage/run.rs | 15 ++- judger/src/language/stage/stream.rs | 46 +++++++++ judger/src/main.rs | 1 - judger/src/sandbox/dev.md | 15 +++ judger/src/sandbox/monitor/mem_cpu.rs | 6 +- judger/src/server.rs | 41 +++++++- 24 files changed, 367 insertions(+), 98 deletions(-) create mode 100644 judger/src/filesystem/dev.md create mode 100644 judger/src/language/stage/stream.rs create mode 100644 judger/src/sandbox/dev.md diff --git a/judger/src/filesystem/adapter/error.rs b/judger/src/filesystem/adapter/error.rs index 2df5faa5..d56395a2 100644 --- a/judger/src/filesystem/adapter/error.rs +++ b/judger/src/filesystem/adapter/error.rs @@ -1,3 +1,10 @@ +/// Error occurred in the filesystem adapter. +/// +/// It's only used to manage the error in a centralized way. +/// +/// User shouldn't rely on this error to as value in another error, +/// and should always call [`Into::>::into`] +/// immediately after the error is returned. #[derive(thiserror::Error, Debug)] pub enum FuseError { #[error("not a readable file")] diff --git a/judger/src/filesystem/adapter/fuse.rs b/judger/src/filesystem/adapter/fuse.rs index 0dea7782..cc9016ac 100644 --- a/judger/src/filesystem/adapter/fuse.rs +++ b/judger/src/filesystem/adapter/fuse.rs @@ -5,9 +5,9 @@ use spin::Mutex; use tokio::io::{AsyncRead, AsyncSeek}; use tokio::sync::Mutex as AsyncMutex; -use crate::filesystem::entry::{Entry, TarTree, BLOCKSIZE}; +use crate::filesystem::entry::{Entry, BLOCKSIZE}; use crate::filesystem::resource::Resource; -use crate::filesystem::table::to_internal_path; +use crate::filesystem::table::{to_internal_path, AdjTable}; use super::{error::FuseError, handle::HandleTable, reply::*}; use fuse3::{ @@ -15,13 +15,16 @@ use fuse3::{ Result as FuseResult, *, }; +/// A asynchorized stream from vector type VecStream = tokio_stream::Iter>; + +// filesystem is an adapter, it should not contain any business logic. pub struct Filesystem where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { handle_table: HandleTable>>, - tree: Mutex>, + tree: Mutex>>, resource: Arc, } @@ -30,15 +33,16 @@ where F: AsyncRead + AsyncSeek + Unpin + Send + Sync + 'static, { /// Create a new filesystem - pub(super) fn new(tree: TarTree, fs_size: u64) -> Self { + pub(super) fn new(tree: AdjTable>, fs_size: u64) -> Self { Self { handle_table: HandleTable::new(), tree: Mutex::new(tree), resource: Arc::new(Resource::new(fs_size)), } } - /// Mount the filesystem to a path - pub async fn mount_with_path( + /// Mount the filesystem to a path, + /// return a raw handle from `libfuse` + pub async fn raw_mount_with_path( self, path: impl AsRef + Clone, ) -> std::io::Result { @@ -53,13 +57,13 @@ where .mount_with_unprivileged(self, path.as_ref()) .await } - /// Insert a file by path + /// Insert a file by path before actual mounts. pub fn insert_by_path(&self, path: impl AsRef, content: Vec) { let mut tree = self.tree.lock(); tree.insert_by_path( to_internal_path(path.as_ref()), || Entry::Directory, - Entry::new_file_with_content(content), + Entry::new_file_with_vec(content), ); } } diff --git a/judger/src/filesystem/adapter/handle.rs b/judger/src/filesystem/adapter/handle.rs index 2a0e8d65..124c2c1a 100644 --- a/judger/src/filesystem/adapter/handle.rs +++ b/judger/src/filesystem/adapter/handle.rs @@ -5,6 +5,8 @@ use std::{ use spin::Mutex; +pub type FileHandle = u64; +/// Lookup table for file handles pub struct HandleTable { handle_generator: AtomicU64, table: Mutex>>, @@ -19,7 +21,7 @@ impl HandleTable { } } /// Add an entry to the table - pub fn add(&self, entry: E) -> u64 { + pub fn add(&self, entry: E) -> FileHandle { let handle = self .handle_generator .fetch_add(1, std::sync::atomic::Ordering::AcqRel); @@ -28,12 +30,12 @@ impl HandleTable { handle } /// Get an entry from the table - pub fn get(&self, handle: u64) -> Option> { + pub fn get(&self, handle: FileHandle) -> Option> { log::trace!("get handle: {}", handle); self.table.lock().get(&handle).cloned() } /// Remove an entry from the table - pub fn remove(&self, handle: u64) -> Option> { + pub fn remove(&self, handle: FileHandle) -> Option> { log::trace!("deallocate handle: {}", handle); self.table.lock().remove(&handle) } diff --git a/judger/src/filesystem/adapter/mod.rs b/judger/src/filesystem/adapter/mod.rs index c2d2e379..60a098de 100644 --- a/judger/src/filesystem/adapter/mod.rs +++ b/judger/src/filesystem/adapter/mod.rs @@ -24,7 +24,7 @@ mod test { log::info!("mounting test tarball in .temp ..."); let template = Template::new("plugins/rlua-54.lang").await.unwrap(); let filesystem = template.as_filesystem(1024 * 1024 * 1024); - let mut mount_handle = filesystem.mount_with_path("./.temp/5").await.unwrap(); + let mut mount_handle = filesystem.raw_mount_with_path("./.temp/5").await.unwrap(); let handle = &mut mount_handle; tokio::select! { diff --git a/judger/src/filesystem/adapter/template.rs b/judger/src/filesystem/adapter/template.rs index 28e7c54e..4adb9d4c 100644 --- a/judger/src/filesystem/adapter/template.rs +++ b/judger/src/filesystem/adapter/template.rs @@ -5,11 +5,14 @@ use tokio::{ io::{AsyncRead, AsyncSeek}, }; -use crate::filesystem::entry::TarTree; +use crate::filesystem::{ + entry::{Entry, TarTree}, + table::AdjTable, +}; use super::fuse::Filesystem; -pub struct Template(TarTree) +pub struct Template(AdjTable>) where F: AsyncRead + AsyncSeek + Unpin + Send + 'static; @@ -23,7 +26,7 @@ where } /// read a file by path pub async fn read_by_path(&self, path: impl AsRef) -> Option> { - self.0.read_by_path(path).await + self.read_by_path(path).await } } @@ -31,6 +34,6 @@ impl Template { /// Create a new template from a tar file pub async fn new(path: impl AsRef + Clone) -> std::io::Result { let tree = TarTree::new(path).await?; - Ok(Self(tree)) + Ok(Self(tree.0)) } } diff --git a/judger/src/filesystem/dev.md b/judger/src/filesystem/dev.md new file mode 100644 index 00000000..e981d9b9 --- /dev/null +++ b/judger/src/filesystem/dev.md @@ -0,0 +1,118 @@ +## Module Layout + +- `table.rs`: adjacency table + - Tree data structure on vector + - Inode allocation by `MIN_ID + index` + - Not `MT-Safe` +- `handle.rs`: Mount Handle + - NewType wrapper for dropping +- `adapter` module: adapter between internal data structure(tree-like) and `libfuse` + - `error.rs`: a centralized way to handle error + - `fuse.rs`: adaptor between internal data structure and `libfuse` + - `reply.rs`: collection of constructor for replay from `libfuse` + - `handle.rs`: file handle table + - `template.rs`: A NewType wrapper to force user explicitly clone(`as_filesystem`) filesystem +- `entry` module: collection of single file + - `tar.rs`: a NewType wrapper for `Tree` + - `ro.rs`: read only normal file(mapped from tar ball) + - `rw.rs`: read/write normal file(in memory) +- `resource.rs`: Resource counter, much like `semaphore` +- `mkdtemp.rs`: a safe wrapper around `libc::mkdtemp` + +## Prerequisite knowledge + +### Filesystem in Userspace + +> FUSE, or Filesystem in Userspace, is a software interface that allows non-privileged users to create their own file systems in Linux without modifying the kernel. It acts as a bridge between the kernel's virtual filesystem layer and user-space programs. + +Traditionally, we have to develop a dedicated kernel module for a filesystem. + +FUSE workaround this problem by providing connection, to set up a FUSE, program need to... + +1. acquire a FUSE connection(similar to unix socket). +2. poll the socket until a connection(similar to a new connection on tcp socket) +3. read that connection to acquire an OPCODE +4. follow OPCODE to parse the request + +example of OPCODE: `READ`, `LOOKUP`, `OPEN`, `RMDIR`... + +[list](https://github.com/libfuse/libfuse/blob/6476b1c3ccde2fc4e8755858c96debf55aa0574b/lib/fuse_lowlevel.c#L2619) of OPCODE + +In this project, we use `fuse3`, which is a wrapper over `libfuse-sys`. + +To get started, you can follow [main.rs](https://github.com/Sherlock-Holo/fuse3/blob/master/examples/src/memfs/main.rs) from `fuse3`'s example. + +#### `INODE` + +`INODE` is an id generate by filesystem, program providing that can set `INODE` to whatever you want, but make sure it's unique for same file(dictionary). + +You can get inode with `stat` +``` +❯ stat . + File: . + Size: 128 Blocks: 0 IO Block: 4096 directory +Device: 2ah/42d Inode: 13553287 Links: 1 +Access: (0775/drwxrwxr-x) Uid: ( 1000/ eason) Gid: ( 1000/ eason) +Access: 2024-04-28 19:44:43.208376257 +0800 +Modify: 2024-05-20 21:39:42.300855512 +0800 +Change: 2024-05-20 21:39:42.300855512 +0800 + Birth: 2024-04-28 19:44:43.208376257 +0800 +``` + +Note that zero `INODE` means unspecified(null in C's language). + +#### `File Handle` + +In the context of filesystem of libc, you might be familiar with `file descriptor`, `file descriptor` is a `uint64` generate secquetially by kernel(unique for each process). + +`File handle` is similar to `file descriptor`, but it sit between kernel and FUSE provider, generated by FUSE provider and unique for each FUSE connection. + +When a file open, FUSE provider generate a `uint64`, and file handle is pass as parameter for later operations. + +FUSE provider should implement a way to retrieve file's session by `file handle`. + +> file's session includes `bytes readed`, `open flag`... + +Note that zero `File Handle` means unspecified(null in C's language), generating a 0 `File Handle` is not allowed. + +#### OPCODE `READDIR` + +> Read directory. + +similar to what `ls -a` provide, list dictionary including `.` and `..` + +parameters: +1. parent `INODE`(could be unspecified, unspecified is root) +2. offset +3. size + +return: list of file(dictionary) + +example(ignore the fact that many file is missing): + +``` +❯ ls / +❯ ls -a / +. .. boot etc lib lib64 media +``` + +| offset | size | output | +| ---- | ---- | --- | +| 0 | 1 | `.` | +| 2 | 3 | `etc`, `lib`, `lib64` | + +#### OPCODE `OPEN` + +> Open a file. Open flags (with the exception of O_CREAT, O_EXCL, O_NOCTTY and O_TRUNC) are available in flags + +parameters: +1. file `INODE`(could be unspecified, unspecified is root) +2. flag + +return: File Handle + +`O_CREAT` should be handle by kernel instead. + +### mkdtemp + +> See `man mkdtemp` diff --git a/judger/src/filesystem/entry/mod.rs b/judger/src/filesystem/entry/mod.rs index 055f979e..d107e1e7 100644 --- a/judger/src/filesystem/entry/mod.rs +++ b/judger/src/filesystem/entry/mod.rs @@ -5,10 +5,10 @@ mod tar; use self::{ro::TarBlock, rw::MemBlock}; use bytes::Bytes; use fuse3::FileType; -use std::{ffi::OsString, ops::Deref, sync::Arc}; +use std::{ffi::OsString, sync::Arc}; use tokio::{ io::{AsyncRead, AsyncSeek}, - sync::{Mutex, OwnedMutexGuard}, + sync::Mutex, }; use super::resource::Resource; @@ -25,12 +25,6 @@ pub trait FuseWriteTrait { async fn write(&mut self, offset: u64, data: &[u8]) -> std::io::Result; } -async fn clone_arc(arc: &Arc>) -> Arc> { - let inner = arc.deref(); - let lock = inner.lock().await; - Arc::new(Mutex::new(lock.deref().clone())) -} - /// Entry in the filesystem /// /// cloning the entry would clone file state @@ -66,13 +60,16 @@ impl Entry where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { + /// create a new file entry with empty content pub fn new_file() -> Self { Self::MemFile(MemBlock::default()) } - pub fn new_file_with_content(content: Vec) -> Self { + /// create a new file entry with content + pub fn new_file_with_vec(content: Vec) -> Self { Self::MemFile(MemBlock::new(content)) } - pub fn kind(&self) -> FileType { + /// get kind of the file + pub(super) fn kind(&self) -> FileType { match self { Self::SymLink(_) => FileType::Symlink, Self::HardLink(_) => FileType::RegularFile, @@ -81,6 +78,7 @@ where Self::MemFile(_) => FileType::RegularFile, } } + /// get size of the file pub fn get_size(&self) -> u64 { match self { Self::SymLink(x) => x.len() as u64, diff --git a/judger/src/filesystem/entry/ro.rs b/judger/src/filesystem/entry/ro.rs index 7ad570af..541ffe4b 100644 --- a/judger/src/filesystem/entry/ro.rs +++ b/judger/src/filesystem/entry/ro.rs @@ -86,7 +86,7 @@ where { async fn read(&mut self, offset: u64, size: u32) -> std::io::Result { let size = size.min(self.size - self.cursor) as usize; - let size=size.min(BLOCKSIZE*MAX_READ_BLK); + let size = size.min(BLOCKSIZE * MAX_READ_BLK); let mut lock = self.file.lock().await; let seek_from = self.get_seek_from(offset).ok_or(io::Error::new( diff --git a/judger/src/filesystem/entry/tar.rs b/judger/src/filesystem/entry/tar.rs index a8abcab3..d7bcc2e8 100644 --- a/judger/src/filesystem/entry/tar.rs +++ b/judger/src/filesystem/entry/tar.rs @@ -1,11 +1,4 @@ -use std::{ - ffi::OsString, - io::Read, - ops::{Deref, DerefMut}, - os::unix::ffi::OsStringExt, - path::Path, - sync::Arc, -}; +use std::{ffi::OsString, io::Read, os::unix::ffi::OsStringExt, path::Path, sync::Arc}; #[cfg(test)] use std::io::Cursor; @@ -22,7 +15,7 @@ use crate::filesystem::table::{to_internal_path, AdjTable}; use super::{ro::TarBlock, Entry}; -pub struct TarTree(AdjTable>) +pub struct TarTree(pub AdjTable>) where F: AsyncRead + AsyncSeek + Unpin + Send + 'static; @@ -35,26 +28,6 @@ where } } -impl DerefMut for TarTree -where - F: AsyncRead + AsyncSeek + Unpin + Send + 'static, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Deref for TarTree -where - F: AsyncRead + AsyncSeek + Unpin + Send + 'static, -{ - type Target = AdjTable>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - impl Default for TarTree where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, @@ -142,6 +115,7 @@ mod test { macro_rules! assert_kind { ($tree:expr,$path:expr, $kind:ident) => {{ let node = $tree + .0 .get_by_path(to_internal_path(Path::new($path))) .unwrap(); let entry = node; diff --git a/judger/src/filesystem/handle.rs b/judger/src/filesystem/handle.rs index 227a6385..ca048cd5 100644 --- a/judger/src/filesystem/handle.rs +++ b/judger/src/filesystem/handle.rs @@ -29,7 +29,7 @@ where { pub async fn mount(self) -> std::io::Result { let mountpoint = MkdTemp::new(); - let handle = self.mount_with_path(mountpoint.get_path()).await?; + let handle = self.raw_mount_with_path(mountpoint.get_path()).await?; Ok(MountHandle(Some(handle), Some(mountpoint))) } } diff --git a/judger/src/filesystem/resource.rs b/judger/src/filesystem/resource.rs index e86f618e..4d7a0d73 100644 --- a/judger/src/filesystem/resource.rs +++ b/judger/src/filesystem/resource.rs @@ -1,8 +1,7 @@ use std::sync::atomic::{AtomicU64, Ordering}; - /// A resource counter /// -/// unlike `Semaphore`, the resource is not reusable +/// unlike [`tokio::sync::Semaphore`], the resource is not reusable pub struct Resource(AtomicU64); impl Resource { diff --git a/judger/src/filesystem/table.rs b/judger/src/filesystem/table.rs index 5c43f3ca..6be08c46 100644 --- a/judger/src/filesystem/table.rs +++ b/judger/src/filesystem/table.rs @@ -29,6 +29,8 @@ struct Node { /// the ability to allocate id up to [`MAX_ID_CAPACITY`] /// /// The table has ability to store a multiple disconnected tree +/// +/// Note that cloning the table would actually clone the WHOLE tree #[derive(Clone)] pub struct AdjTable { by_id: Vec>, diff --git a/judger/src/language/builder.rs b/judger/src/language/builder.rs index e727a1dc..d53f3e50 100644 --- a/judger/src/language/builder.rs +++ b/judger/src/language/builder.rs @@ -1,4 +1,6 @@ -use grpc::judger::{JudgeResponse, JudgerCode}; +use grpc::judger::{ + exec_result as execute_response, ExecResult as ExecuteResponse, JudgeResponse, JudgerCode, Log, +}; use super::stage::{AssertionMode, StatusCode}; @@ -36,10 +38,40 @@ impl From for JudgeResponse { } pub struct ExecuteResult { + pub status: StatusCode, pub time: u64, pub memory: u64, pub output: Vec, - pub code: i32, +} + +impl From for ExecuteResponse { + fn from(value: ExecuteResult) -> Self { + macro_rules! execute_log { + ($msg:expr) => { + execute_response::Result::Log(Log { + level: 4, + msg: $msg.to_string(), + }) + }; + } + let result = match value.status { + StatusCode::Accepted => execute_response::Result::Output(value.output), + StatusCode::WrongAnswer => execute_log!("Wrong Answer"), + StatusCode::RuntimeError => { + execute_log!("Runtime Error, maybe program return non-zero code") + } + StatusCode::TimeLimitExceeded | StatusCode::RealTimeLimitExceeded => { + execute_log!("Time Limit Exceeded") + } + StatusCode::MemoryLimitExceeded => execute_log!("Memory Limit Exceeded"), + StatusCode::OutputLimitExceeded => execute_log!("Output Limit Exceeded"), + StatusCode::CompileError => execute_log!("Compile Error"), + _ => execute_log!("System Error"), + }; + ExecuteResponse { + result: Some(result), + } + } } pub struct JudgeArgBuilder { diff --git a/judger/src/language/plugin.rs b/judger/src/language/plugin.rs index 5bd91d77..119837a8 100644 --- a/judger/src/language/plugin.rs +++ b/judger/src/language/plugin.rs @@ -145,7 +145,6 @@ where ) -> Pin> + Send>> { let compiler = trys!(self.as_compiler(args.source).await); let maybe_runner = trys!(compiler.compile().await); - log::debug!("runner created"); let mut runner = trys!(maybe_runner, Ok(JudgeResult::compile_error())); let mem_cpu = (args.mem, args.cpu); @@ -153,36 +152,30 @@ where let mut io = args.input.into_iter().zip(args.output.into_iter()); Box::pin(try_stream! { while let Some((input,output))=io.next(){ - let judger = runner.run(mem_cpu.clone(), input).await?; - let status = judger.get_result(&output, mode); - log::trace!("status: {:?}", status); - - let stat = judger.stat(); - yield JudgeResult { - status, - time: stat.cpu.total, - memory: stat.memory.total, - }; - if status!=StatusCode::Accepted{ + let judger = runner.judge(mem_cpu.clone(), input).await?; + + yield judger.get_result(&output, mode); + if judger.get_code(&output, mode)!=StatusCode::Accepted{ break; } } }) } - pub async fn execute(&self, args: ExecuteArgs) -> Result> { + pub async fn execute(&self, args: ExecuteArgs) -> Result { let compiler = self.as_compiler(args.source).await?; - Ok(match compiler.compile().await? { + let maybe_runner = compiler.compile().await?; + match maybe_runner { Some(mut runner) => { - let judger = runner.run((args.mem, args.cpu), args.input).await?; - - todo!("stream output"); - - let stat = judger.stat(); - - Some(todo!()) + let executor = runner.stream((args.mem, args.cpu), args.input).await?; + Ok(executor.get_result()) } - None => None, - }) + None => Ok(ExecuteResult { + status: StatusCode::CompileError, + time: 0, + memory: 0, + output: Vec::new(), + }), + } } pub fn get_memory_reserved(&self, mem: u64) -> u64 { self.spec.get_memory_reserved_size(mem) diff --git a/judger/src/language/spec.rs b/judger/src/language/spec.rs index e8ee10a9..d7f48914 100644 --- a/judger/src/language/spec.rs +++ b/judger/src/language/spec.rs @@ -273,7 +273,7 @@ impl Default for RawJudge { memory_multiplier: Some(1.0), cpu_multiplier: Some(1.0), walltime: Some(360e9 as u64), - output: Some(1024*1024*16), + output: Some(1024 * 1024 * 16), } } } diff --git a/judger/src/language/stage/compile.rs b/judger/src/language/stage/compile.rs index d192fc07..86aef0c7 100644 --- a/judger/src/language/stage/compile.rs +++ b/judger/src/language/stage/compile.rs @@ -10,6 +10,11 @@ use crate::{ use super::Runner; +/// First stage of language processing, compile the source code +/// +/// Note that by compile, we doesn't mean the traditional compile process +/// it could be any process that prepare the code to be ready for execution, +/// or do nothing(like python) pub struct Compiler { spec: Arc, handle: MountHandle, @@ -37,6 +42,7 @@ impl Compiler { } } +/// Context for compile stage struct CompileCtx { spec: Arc, path: PathBuf, diff --git a/judger/src/language/stage/judge.rs b/judger/src/language/stage/judge.rs index 07205faf..805a9303 100644 --- a/judger/src/language/stage/judge.rs +++ b/judger/src/language/stage/judge.rs @@ -1,12 +1,13 @@ use std::sync::Arc; use crate::{ - language::spec::Spec, + language::{spec::Spec, JudgeResult}, sandbox::{Corpse, MonitorKind, Stat}, }; use super::{AssertionMode, StatusCode}; +/// The third stage of language processing, compare the output pub struct Judger { spec: Arc, corpse: Corpse, @@ -90,11 +91,11 @@ impl Judger { StatusCode::Accepted } - pub fn get_result(&self, output: &[u8], mode: AssertionMode) -> StatusCode { + pub fn get_code(&self, output: &[u8], mode: AssertionMode) -> StatusCode { match self.corpse.status() { Ok(status) => match status.success() { true => self.assert_output(output, mode), - false => StatusCode::WrongAnswer, + false => StatusCode::RuntimeError, }, Err(reason) => match reason { MonitorKind::Cpu => StatusCode::TimeLimitExceeded, @@ -104,4 +105,13 @@ impl Judger { }, } } + pub fn get_result(&self, output: &[u8], mode: AssertionMode) -> JudgeResult { + let status = self.get_code(output, mode); + let stat = self.stat(); + JudgeResult { + status, + time: stat.cpu.total, + memory: stat.memory.total, + } + } } diff --git a/judger/src/language/stage/mod.rs b/judger/src/language/stage/mod.rs index 52987dd9..4bd29616 100644 --- a/judger/src/language/stage/mod.rs +++ b/judger/src/language/stage/mod.rs @@ -1,11 +1,17 @@ +//! collection of steps for judge and execute + mod compile; mod judge; mod run; +mod stream; pub use compile::Compiler; use grpc::{judger::JudgeMatchRule, judger::JudgerCode}; pub use run::Runner; +/// internal status code, use to decouple the grpc status code +/// +/// Status code is commonly use in OJ, it include example such as: AC, WA... #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum StatusCode { Accepted, @@ -19,10 +25,20 @@ pub enum StatusCode { SystemError, } +/// internal assertion mode, use to decouple the grpc status code +/// +/// Assertion mode reperesent the way to compare the output #[derive(Clone, Copy)] pub enum AssertionMode { + /// Skip single space and newline + /// + /// `a b`, and `a\nb\n` are the same SkipSpace, + /// Skip continous space and newline + /// + /// `a b`, `ab ` and `ab` are the same SkipContinousSpace, + /// Exact match Exact, } @@ -45,7 +61,6 @@ impl From for AssertionMode { impl From for JudgerCode { fn from(value: StatusCode) -> Self { - match value { StatusCode::Accepted => Self::Ac, StatusCode::WrongAnswer => Self::Wa, diff --git a/judger/src/language/stage/run.rs b/judger/src/language/stage/run.rs index 46421f46..2474969d 100644 --- a/judger/src/language/stage/run.rs +++ b/judger/src/language/stage/run.rs @@ -7,8 +7,9 @@ use crate::{ Result, }; -use super::judge::Judger; +use super::{judge::Judger, stream::Streamer}; +/// Second stage of the language process, run the compiled code pub struct Runner { filesystem: MountHandle, spec: Arc, @@ -18,7 +19,7 @@ impl Runner { pub fn new(filesystem: MountHandle, spec: Arc) -> Self { Self { filesystem, spec } } - pub async fn run(&mut self, (mem, cpu): (u64, u64), input: Vec) -> Result { + pub async fn judge(&mut self, (mem, cpu): (u64, u64), input: Vec) -> Result { let ctx = RunCtx { spec: self.spec.clone(), path: self.filesystem.get_path().to_path_buf(), @@ -28,6 +29,16 @@ impl Runner { let corpse = process.wait(input).await?; Ok(Judger::new(self.spec.clone(), corpse)) } + pub async fn stream(&mut self, (mem, cpu): (u64, u64), input: Vec) -> Result { + let ctx = RunCtx { + spec: self.spec.clone(), + path: self.filesystem.get_path().to_path_buf(), + limit: self.spec.get_judge_limit(cpu, mem), + }; + let process = Process::new(ctx)?; + let corpse = process.wait(input).await?; + Ok(Streamer::new(self.spec.clone(), corpse)) + } } struct RunCtx { diff --git a/judger/src/language/stage/stream.rs b/judger/src/language/stage/stream.rs new file mode 100644 index 00000000..99c1bd67 --- /dev/null +++ b/judger/src/language/stage/stream.rs @@ -0,0 +1,46 @@ +use std::sync::Arc; + +use crate::{ + language::{ + spec::{self, Spec}, + ExecuteResult, + }, + sandbox::{Corpse, MonitorKind}, +}; + +use super::StatusCode; + +/// Third stage of language processing, stream execution result +pub struct Streamer { + spec: Arc, + corpse: Corpse, +} + +impl Streamer { + pub fn new(spec: Arc, corpse: Corpse) -> Self { + Self { spec, corpse } + } + pub fn get_code(&self) -> StatusCode { + match self.corpse.status() { + Ok(status) => match status.success() { + true => StatusCode::Accepted, + false => StatusCode::RuntimeError, + }, + Err(reason) => match reason { + MonitorKind::Cpu => StatusCode::TimeLimitExceeded, + MonitorKind::Memory => StatusCode::MemoryLimitExceeded, + MonitorKind::Output => StatusCode::OutputLimitExceeded, + MonitorKind::Walltime => StatusCode::RealTimeLimitExceeded, + }, + } + } + pub fn get_result(&self) -> ExecuteResult { + let stat = self.corpse.stat(); + ExecuteResult { + status: self.get_code(), + time: stat.cpu.total, + memory: stat.memory.total, + output: self.corpse.stdout().to_vec(), + } + } +} diff --git a/judger/src/main.rs b/judger/src/main.rs index a12896f7..3eec9fc2 100644 --- a/judger/src/main.rs +++ b/judger/src/main.rs @@ -26,7 +26,6 @@ async fn main() { default_panic(info); std::process::exit(1); })); - #[cfg(debug_assertions)] log::warn!("running debug build"); diff --git a/judger/src/sandbox/dev.md b/judger/src/sandbox/dev.md new file mode 100644 index 00000000..f4fc3897 --- /dev/null +++ b/judger/src/sandbox/dev.md @@ -0,0 +1,15 @@ +## Module Layout + +## Prerequisite knowledge + +### Control Group(linux) + +> In Linux, control groups (cgroups for short) act like a resource manager for your system. It lets you organize processes into groups and set limits on how much CPU, memory, network bandwidth, or other resources they can use. + +> cgroup is abbr for control group + +In practice, linux kernel expose cgroup's interface by vfs. + +To get started, you can follow [it article](https://access.redhat.com/documentation/zh-tw/red_hat_enterprise_linux/6/html/resource_management_guide/sec-creating_cgroups) from red hat to create one. + +In this project, we use `cgroups_rs`, which is an abstraction over underlying vfs. diff --git a/judger/src/sandbox/monitor/mem_cpu.rs b/judger/src/sandbox/monitor/mem_cpu.rs index 2bce248e..2643d8ce 100644 --- a/judger/src/sandbox/monitor/mem_cpu.rs +++ b/judger/src/sandbox/monitor/mem_cpu.rs @@ -46,14 +46,14 @@ impl Drop for Monitor { // FIXME: use explicit control flow // currently is controlled by dropping order, and it can be broken // if one of the thread panics - match self.cgroup.v2(){ + match self.cgroup.v2() { true => { self.cgroup.kill().expect("cgroup.kill does not exist"); self.cgroup.delete().unwrap(); - }, + } false => { self.cgroup.set_release_agent("").unwrap(); - }, + } } } } diff --git a/judger/src/server.rs b/judger/src/server.rs index 9e03f63a..b4806b35 100644 --- a/judger/src/server.rs +++ b/judger/src/server.rs @@ -10,7 +10,7 @@ use uuid::Uuid; use crate::{ error::{ClientError, Error}, - language::{JudgeArgBuilder, Map}, + language::{ExecuteArgBuilder, JudgeArgBuilder, Map}, CONFIG, }; @@ -121,7 +121,7 @@ impl Judger for Server { })) } - type ExecStream = Pin> + Send>>; + type ExecStream = tokio_stream::Once>; async fn exec( &self, @@ -129,6 +129,41 @@ impl Judger for Server { ) -> Result, tonic::Status> { let payload = check_secret(req)?; - todo!() + let memory = payload.memory; + let cpu = payload.time; + + let source = payload.code; + let input = payload.input; + + let uuid = + Uuid::from_str(&payload.lang_uid).map_err(|_| ClientError::InvaildLanguageUuid)?; + + let plugin = self + .plugins + .get(&uuid) + .ok_or(ClientError::InvaildLanguageUuid)?; + + let resource: u32 = plugin + .get_memory_reserved(payload.memory) + .try_into() + .map_err(|_| Error::Platform)?; + + let permit = self + .semaphore + .clone() + .acquire_many_owned(resource) + .await + .map_err(|_| ClientError::ImpossibleMemoryRequirement)?; + + let args = ExecuteArgBuilder::new() + .cpu(cpu) + .mem(memory) + .source(source) + .input(input) + .build(); + + let result = plugin.execute(args).await?; + drop(permit); + Ok(Response::new(tokio_stream::once(Ok(result.into())))) } } From 0cbf25b9f509963068c34affa10b704ff635ccc1 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Thu, 30 May 2024 01:35:53 +0800 Subject: [PATCH 33/38] feat(Judger): :construction: new plugin c-11, cpp-11 --- judger/.vscode/settings.json | 8 --- judger/plugins/c-11/.gitignore | 4 -- judger/plugins/c-11/Dockerfile | 6 +-- judger/plugins/c-11/build.sh | 3 +- judger/plugins/c-11/compile.c | 44 --------------- judger/plugins/c-11/execute.sh | 2 - judger/plugins/c-11/spec.toml | 23 +++----- judger/plugins/cpp-11/.gitignore | 4 -- judger/plugins/cpp-11/Dockerfile | 6 +-- judger/plugins/cpp-11/build.sh | 3 +- judger/plugins/cpp-11/compile.c | 44 --------------- judger/plugins/cpp-11/execute.sh | 2 - judger/plugins/cpp-11/spec.toml | 21 +++----- judger/plugins/export-all.sh | 7 --- judger/plugins/rlua-54/Dockerfile | 2 + judger/plugins/rlua-54/build.sh | 5 +- judger/src/filesystem/adapter/error.rs | 8 ++- judger/src/filesystem/adapter/fuse.rs | 65 ++++++++++++++++++----- judger/src/filesystem/adapter/mod.rs | 5 +- judger/src/filesystem/adapter/reply.rs | 16 ++++-- judger/src/filesystem/adapter/template.rs | 11 +++- judger/src/filesystem/dev.md | 3 +- judger/src/filesystem/entry/mod.rs | 16 ++++-- judger/src/filesystem/entry/tar.rs | 8 +-- judger/src/filesystem/handle.rs | 5 ++ judger/src/filesystem/table.rs | 38 ++++++++++--- judger/src/main.rs | 2 +- judger/src/sandbox/process/process.rs | 5 +- 28 files changed, 163 insertions(+), 203 deletions(-) delete mode 100644 judger/.vscode/settings.json delete mode 100644 judger/plugins/c-11/.gitignore delete mode 100644 judger/plugins/c-11/compile.c delete mode 100755 judger/plugins/c-11/execute.sh delete mode 100644 judger/plugins/cpp-11/.gitignore delete mode 100644 judger/plugins/cpp-11/compile.c delete mode 100755 judger/plugins/cpp-11/execute.sh delete mode 100644 judger/plugins/export-all.sh diff --git a/judger/.vscode/settings.json b/judger/.vscode/settings.json deleted file mode 100644 index 1e668822..00000000 --- a/judger/.vscode/settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "files.watcherExclude": { - "plugins/**": true - }, - "rust-analyzer.files.excludeDirs": [ - "plugins","nsjail-docker" - ], -} \ No newline at end of file diff --git a/judger/plugins/c-11/.gitignore b/judger/plugins/c-11/.gitignore deleted file mode 100644 index 1d2da528..00000000 --- a/judger/plugins/c-11/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/rootfs -/src.cpp -/src.out -/compile \ No newline at end of file diff --git a/judger/plugins/c-11/Dockerfile b/judger/plugins/c-11/Dockerfile index b8740088..ea22b00d 100644 --- a/judger/plugins/c-11/Dockerfile +++ b/judger/plugins/c-11/Dockerfile @@ -1,6 +1,2 @@ FROM gcc:13 -WORKDIR / -COPY compile.c . -COPY execute.sh / -RUN gcc compile.c -o compile -RUN rm compile.c \ No newline at end of file +COPY spec.toml / \ No newline at end of file diff --git a/judger/plugins/c-11/build.sh b/judger/plugins/c-11/build.sh index d20006c4..3b4303b2 100644 --- a/judger/plugins/c-11/build.sh +++ b/judger/plugins/c-11/build.sh @@ -1,3 +1,4 @@ mkdir -p rootfs docker build -t c-11-mdoj-plugin . -docker export $(docker create c-11-mdoj-plugin) | tar -C rootfs -xvf - > /dev/null \ No newline at end of file +docker export $(docker create c-11-mdoj-plugin) > c-11.lang +mv c-11.lang .. \ No newline at end of file diff --git a/judger/plugins/c-11/compile.c b/judger/plugins/c-11/compile.c deleted file mode 100644 index 43f2085d..00000000 --- a/judger/plugins/c-11/compile.c +++ /dev/null @@ -1,44 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#define handle(x,e) { \ - if (e == x) \ - { \ - printf("4: %m\n", errno); \ - fflush(stdout); \ - return 1; \ - } \ -} -#define CC "/usr/local/bin/g++" -#define SRC "/src/src.cpp" -#define OUT "/src/src.out" -#define MAX_SIZE 131072 - -int main() -{ - FILE *source = fopen(SRC, "w"); - handle(source,NULL); - - printf("1: success create file!\n"); - - char *code = malloc(MAX_SIZE * sizeof(char)); - size_t len = fread(code, sizeof(char), MAX_SIZE, stdin); - - fwrite(code, sizeof(char), len, source); - fclose(source); - - char *args[] = {CC, "-x", "c", SRC, "-lm", "-o", OUT, NULL}; - int pid, status; - - handle(chdir("/tmp"),-1); - handle(execvp(CC, args),-1); - printf("1: success execv!\n"); - - handle(wait(NULL),-1); - printf("0: success!\n"); - return 0; -} \ No newline at end of file diff --git a/judger/plugins/c-11/execute.sh b/judger/plugins/c-11/execute.sh deleted file mode 100755 index 0fb7318a..00000000 --- a/judger/plugins/c-11/execute.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -/src/src.out \ No newline at end of file diff --git a/judger/plugins/c-11/spec.toml b/judger/plugins/c-11/spec.toml index ffce0419..3602b8f6 100644 --- a/judger/plugins/c-11/spec.toml +++ b/judger/plugins/c-11/spec.toml @@ -1,21 +1,12 @@ -# memory in byte, time in microsecond +file = "/code.c" +fs_limit = 3145728 info = "gcc 13.2.0 (G++)" -extension = "c" # same extension means same language -name = "c-11" # must be same as dictionary name -uid = "7daff707-26b5-4153-90ae-9858b9fd9619" # be sure it's unique +extension = "c" +name = "c-11" +id = "7daff707-26b5-4153-90ae-9858b9fd9619" [compile] -lockdown = true -command = ["/compile"] -kernel_mem = 67108864 -user_mem = 268435456 -rt_time = 1000000 -cpu_time = 10000000 -total_time = 10000000 +command = ["/usr/bin/cc","-x", "c", "code.c", "-lm", "-o", "execute"] [judge] -command = ["/execute.sh"] -kernel_mem = 67108864 -multiplier_memory = 1 # user_mem -rt_time = 1000000 -multiplier_cpu = 1 # cpu_time +command = ["/execute"] diff --git a/judger/plugins/cpp-11/.gitignore b/judger/plugins/cpp-11/.gitignore deleted file mode 100644 index 1d2da528..00000000 --- a/judger/plugins/cpp-11/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/rootfs -/src.cpp -/src.out -/compile \ No newline at end of file diff --git a/judger/plugins/cpp-11/Dockerfile b/judger/plugins/cpp-11/Dockerfile index b8740088..ea22b00d 100644 --- a/judger/plugins/cpp-11/Dockerfile +++ b/judger/plugins/cpp-11/Dockerfile @@ -1,6 +1,2 @@ FROM gcc:13 -WORKDIR / -COPY compile.c . -COPY execute.sh / -RUN gcc compile.c -o compile -RUN rm compile.c \ No newline at end of file +COPY spec.toml / \ No newline at end of file diff --git a/judger/plugins/cpp-11/build.sh b/judger/plugins/cpp-11/build.sh index ff033dae..4d063f35 100644 --- a/judger/plugins/cpp-11/build.sh +++ b/judger/plugins/cpp-11/build.sh @@ -1,3 +1,4 @@ mkdir -p rootfs docker build -t cpp-11-mdoj-plugin . -docker export $(docker create cpp-11-mdoj-plugin) | tar -C rootfs -xvf - > /dev/null \ No newline at end of file +docker export $(docker create cpp-11-mdoj-plugin) > cpp-11.lang +mv cpp-11.lang .. \ No newline at end of file diff --git a/judger/plugins/cpp-11/compile.c b/judger/plugins/cpp-11/compile.c deleted file mode 100644 index dabe1dfe..00000000 --- a/judger/plugins/cpp-11/compile.c +++ /dev/null @@ -1,44 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#define handle(x,e) { \ - if (e == x) \ - { \ - printf("4: %m\n", errno); \ - fflush(stdout); \ - return 1; \ - } \ -} -#define CC "/usr/local/bin/g++" -#define SRC "/src/src.cpp" -#define OUT "/src/src.out" -#define MAX_SIZE 131072 - -int main() -{ - FILE *source = fopen(SRC, "w"); - handle(source,NULL); - - printf("1: success create file!\n"); - - char *code = malloc(MAX_SIZE * sizeof(char)); - size_t len = fread(code, sizeof(char), MAX_SIZE, stdin); - - fwrite(code, sizeof(char), len, source); - fclose(source); - - char *args[] = {CC, SRC, "-lm", "-o", OUT, NULL}; - int pid, status; - - handle(chdir("/tmp"),-1); - handle(execvp(CC, args),-1); - printf("1: success execv!\n"); - - handle(wait(NULL),-1); - printf("0: success!\n"); - return 0; -} \ No newline at end of file diff --git a/judger/plugins/cpp-11/execute.sh b/judger/plugins/cpp-11/execute.sh deleted file mode 100755 index 0fb7318a..00000000 --- a/judger/plugins/cpp-11/execute.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -/src/src.out \ No newline at end of file diff --git a/judger/plugins/cpp-11/spec.toml b/judger/plugins/cpp-11/spec.toml index 87c997be..f0073f92 100644 --- a/judger/plugins/cpp-11/spec.toml +++ b/judger/plugins/cpp-11/spec.toml @@ -1,21 +1,12 @@ -# memory in byte, time in microsecond +file = "/code.cpp" +fs_limit = 3145728 info = "gcc 13.2.0 (G++)" -extension = "cpp" # same extension means same language +extension = "cpp" name = "cpp-11" # must be same as dictionary name -uid = "8a9e1daf-ff89-42c3-b011-bf6fb4bd8b26" # be sure it's unique +id = "8a9e1daf-ff89-42c3-b011-bf6fb4bd8b26" # be sure it's unique [compile] -lockdown = true -command = ["/compile"] -kernel_mem = 67108864 -user_mem = 268435456 -rt_time = 1000000 -cpu_time = 10000000 -total_time = 10000000 +command = ["/usr/local/bin/gcc","code.cpp", "-lm", "-o", "execute"] [judge] -command = ["/execute.sh"] -kernel_mem = 67108864 -multiplier_memory = 1 # user_mem -rt_time = 1000000 -multiplier_cpu = 1 # cpu_time +command = ["/execute"] diff --git a/judger/plugins/export-all.sh b/judger/plugins/export-all.sh deleted file mode 100644 index 77c32e79..00000000 --- a/judger/plugins/export-all.sh +++ /dev/null @@ -1,7 +0,0 @@ -for entry in ./* -do - if [ -f "$entry"/spec.toml ]; then - echo "exporting plugin $entry" - sh -c "tar -czvf \"../plugins-out/$entry.tar.gz\" \"$entry/spec.toml\" \"$entry/rootfs\"" - fi -done \ No newline at end of file diff --git a/judger/plugins/rlua-54/Dockerfile b/judger/plugins/rlua-54/Dockerfile index 7b68530c..86cdeb6c 100644 --- a/judger/plugins/rlua-54/Dockerfile +++ b/judger/plugins/rlua-54/Dockerfile @@ -17,4 +17,6 @@ FROM scratch WORKDIR / COPY --from=builder /usr/local/cargo/bin/rlua-54 / +COPY sepc.toml / + CMD ["/rlua-54/rlua-54"] diff --git a/judger/plugins/rlua-54/build.sh b/judger/plugins/rlua-54/build.sh index 8b99ef6f..2a4d8d89 100644 --- a/judger/plugins/rlua-54/build.sh +++ b/judger/plugins/rlua-54/build.sh @@ -1,3 +1,4 @@ mkdir -p rootfs -docker build --build-arg ARCH=$(uname -m) -t rlua-54-mdoj-plugin . -docker export $(docker create rlua-54-mdoj-plugin) | tar -C rootfs -xvf - > /dev/null +docker build -t rlua-54-mdoj-plugin . +docker export $(docker create rlua-54-mdoj-plugin) > rlua-54.lang +mv rlua-54.lang .. \ No newline at end of file diff --git a/judger/src/filesystem/adapter/error.rs b/judger/src/filesystem/adapter/error.rs index d56395a2..ad55b48a 100644 --- a/judger/src/filesystem/adapter/error.rs +++ b/judger/src/filesystem/adapter/error.rs @@ -27,6 +27,10 @@ pub enum FuseError { Underlaying, #[error("invalid path")] InvalidPath, + #[error("permission deny")] + PermissionDeny, + #[error("invalid argument")] + InvialdArg, } impl From for fuse3::Errno { @@ -42,9 +46,11 @@ impl From for fuse3::Errno { libc::ENOMEM } FuseError::InvalidPath | FuseError::InvaildIno => libc::ENOENT, + FuseError::PermissionDeny => libc::EACCES, + FuseError::InvialdArg => libc::EINVAL, _ => { log::warn!("FUSE driver broken: {}", value); - libc::ENOMEM + libc::EINVAL } } .into() diff --git a/judger/src/filesystem/adapter/fuse.rs b/judger/src/filesystem/adapter/fuse.rs index cc9016ac..133b099c 100644 --- a/judger/src/filesystem/adapter/fuse.rs +++ b/judger/src/filesystem/adapter/fuse.rs @@ -1,5 +1,6 @@ use std::{ffi::OsStr, num::NonZeroU32, path::Path, sync::Arc}; +use bytes::Bytes; use futures_core::Future; use spin::Mutex; use tokio::io::{AsyncRead, AsyncSeek}; @@ -51,7 +52,7 @@ where let mut mount_options = MountOptions::default(); - mount_options.uid(uid).gid(gid); + mount_options.uid(uid).gid(gid).force_readdir_plus(true); Session::new(mount_options) .mount_with_unprivileged(self, path.as_ref()) @@ -154,7 +155,7 @@ where let fh = self .handle_table .add(AsyncMutex::new(node.get_value().clone())); - Ok(ReplyOpen { fh, flags: 0 }) + Ok(ReplyOpen { fh, flags }) } } fn open( @@ -172,7 +173,7 @@ where let fh = self .handle_table .add(AsyncMutex::new(node.get_value().clone())); - Ok(ReplyOpen { fh, flags: 0 }) + Ok(ReplyOpen { fh, flags }) } } fn readdir<'a>( @@ -207,13 +208,13 @@ where .into_iter() .chain( node.children() - .map(|inode| { + .filter_map(|inode| { let node = tree.get(inode).unwrap(); - dir_entry( - node.get_name().to_os_string(), + Some(dir_entry( + node.get_name()?.to_os_string(), node.get_value(), inode as u64, - ) + )) }) .map(Ok), ) @@ -264,15 +265,15 @@ where .chain( node.children() .enumerate() - .map(|(offset, inode)| { + .filter_map(|(offset, inode)| { let node = tree.get(inode).unwrap(); - dir_entry_plus( + Some(dir_entry_plus( &req, - node.get_name().to_os_string(), + node.get_name()?.to_os_string(), node.get_value(), inode as u64, (offset + 3) as i64, - ) + )) }) .map(Ok), ) @@ -403,6 +404,7 @@ where Ok(reply_attr(&req, node.get_value(), inode)) } } + // open and create fd fn create( &self, req: Request, @@ -418,9 +420,13 @@ where return Err(FuseError::NotDir.into()); } let mut node = parent_node.insert(name.to_os_string(), Entry::new_file()); + let fh = self + .handle_table + .add(AsyncMutex::new(node.get_value().clone())); - // FIXME: append mode - Ok(reply_created(&req, node.get_value())) + let inode = node.get_id() as u64; + let entry = node.get_value(); + Ok(reply_created(&req, entry, fh, flags, inode)) } } fn mkdir( @@ -442,6 +448,39 @@ where Ok(reply_entry(&req, node.get_value(), ino)) } } + fn readlink( + &self, + req: Request, + inode: Inode, + ) -> impl Future> + Send { + async move { + let tree = self.tree.lock(); + let node = tree.get(inode as usize).ok_or(FuseError::InvaildIno)?; + let link = node + .get_value() + .get_symlink() + .ok_or(FuseError::InvialdArg)?; + Ok(ReplyData { + data: Bytes::copy_from_slice(link.as_encoded_bytes()), + }) + } + } + fn unlink( + &self, + req: Request, + parent: Inode, + name: &OsStr, + ) -> impl Future> + Send { + async move { + let mut tree = self.tree.lock(); + let mut parent_node = tree.get_mut(parent as usize).ok_or(FuseError::InvaildIno)?; + if parent_node.get_value().kind() != FileType::Directory { + return Err(FuseError::NotDir.into()); + } + parent_node.remove_by_component(name); + Ok(()) + } + } } #[cfg(test)] diff --git a/judger/src/filesystem/adapter/mod.rs b/judger/src/filesystem/adapter/mod.rs index 60a098de..c3358432 100644 --- a/judger/src/filesystem/adapter/mod.rs +++ b/judger/src/filesystem/adapter/mod.rs @@ -17,14 +17,15 @@ mod test { #[ignore = "not meant to be tested"] async fn test_mount() { Builder::from_default_env() - .filter_level(log::LevelFilter::Trace) + .filter_level(log::LevelFilter::Info) + .filter_module("tracing::span::active", log::LevelFilter::Trace) .try_init() .ok(); log::info!("mounting test tarball in .temp ..."); let template = Template::new("plugins/rlua-54.lang").await.unwrap(); let filesystem = template.as_filesystem(1024 * 1024 * 1024); - let mut mount_handle = filesystem.raw_mount_with_path("./.temp/5").await.unwrap(); + let mut mount_handle = filesystem.raw_mount_with_path("./.temp/28").await.unwrap(); let handle = &mut mount_handle; tokio::select! { diff --git a/judger/src/filesystem/adapter/reply.rs b/judger/src/filesystem/adapter/reply.rs index 5592a96f..0d95f92f 100644 --- a/judger/src/filesystem/adapter/reply.rs +++ b/judger/src/filesystem/adapter/reply.rs @@ -8,7 +8,7 @@ use tokio::io::{AsyncRead, AsyncSeek}; use crate::filesystem::{entry::Entry, entry::BLOCKSIZE}; -const TTL: Duration = Duration::from_secs(1); +const TTL: Duration = Duration::from_secs(0); pub fn dir_entry_plus( req: &Request, @@ -86,15 +86,21 @@ where } } -pub fn reply_created(req: &Request, entry: &Entry) -> ReplyCreated +pub fn reply_created( + req: &Request, + entry: &Entry, + fh: u64, + flags: u32, + inode: u64, +) -> ReplyCreated where F: AsyncRead + AsyncSeek + Send + Unpin + 'static, { ReplyCreated { ttl: TTL, - attr: file_attr(req, entry, 0), + attr: file_attr(req, entry, inode), generation: 0, - fh: 0, - flags: 0, + fh, + flags, } } diff --git a/judger/src/filesystem/adapter/template.rs b/judger/src/filesystem/adapter/template.rs index 4adb9d4c..0822a64a 100644 --- a/judger/src/filesystem/adapter/template.rs +++ b/judger/src/filesystem/adapter/template.rs @@ -7,7 +7,7 @@ use tokio::{ use crate::filesystem::{ entry::{Entry, TarTree}, - table::AdjTable, + table::{to_internal_path, AdjTable}, }; use super::fuse::Filesystem; @@ -26,7 +26,14 @@ where } /// read a file by path pub async fn read_by_path(&self, path: impl AsRef) -> Option> { - self.read_by_path(path).await + let paths = to_internal_path(path.as_ref()); + let node = self.0.get_by_path(paths)?; + node.get_value() + .assume_tar_file() + .expect("expect spec.toml") + .read_all() + .await + .ok() } } diff --git a/judger/src/filesystem/dev.md b/judger/src/filesystem/dev.md index e981d9b9..cafdf6dd 100644 --- a/judger/src/filesystem/dev.md +++ b/judger/src/filesystem/dev.md @@ -3,7 +3,7 @@ - `table.rs`: adjacency table - Tree data structure on vector - Inode allocation by `MIN_ID + index` - - Not `MT-Safe` + - Should be use behind lock - `handle.rs`: Mount Handle - NewType wrapper for dropping - `adapter` module: adapter between internal data structure(tree-like) and `libfuse` @@ -91,7 +91,6 @@ return: list of file(dictionary) example(ignore the fact that many file is missing): ``` -❯ ls / ❯ ls -a / . .. boot etc lib lib64 media ``` diff --git a/judger/src/filesystem/entry/mod.rs b/judger/src/filesystem/entry/mod.rs index d107e1e7..0667ac54 100644 --- a/judger/src/filesystem/entry/mod.rs +++ b/judger/src/filesystem/entry/mod.rs @@ -5,7 +5,10 @@ mod tar; use self::{ro::TarBlock, rw::MemBlock}; use bytes::Bytes; use fuse3::FileType; -use std::{ffi::OsString, sync::Arc}; +use std::{ + ffi::{OsStr, OsString}, + sync::Arc, +}; use tokio::{ io::{AsyncRead, AsyncSeek}, sync::Mutex, @@ -78,6 +81,12 @@ where Self::MemFile(_) => FileType::RegularFile, } } + pub(super) fn get_symlink(&self) -> Option<&OsStr> { + match self { + Self::SymLink(x) => Some(&*x), + _ => None, + } + } /// get size of the file pub fn get_size(&self) -> u64 { match self { @@ -95,10 +104,9 @@ where _ => None, } } - pub async fn read_all(&self) -> Option> { + pub fn assume_tar_file(&self) -> Option<&TarBlock> { match self { - Self::TarFile(block) => Some(block.read_all().await.expect("tar ball corrupted")), - Self::MemFile(block) => None, + Entry::TarFile(x) => Some(x), _ => None, } } diff --git a/judger/src/filesystem/entry/tar.rs b/judger/src/filesystem/entry/tar.rs index d7bcc2e8..8152fc71 100644 --- a/judger/src/filesystem/entry/tar.rs +++ b/judger/src/filesystem/entry/tar.rs @@ -43,10 +43,6 @@ impl TarTree where F: AsyncRead + AsyncSeek + Unpin + Send + 'static, { - pub async fn read_by_path(&self, path: impl AsRef) -> Option> { - let node = self.0.get_by_path(to_internal_path(path.as_ref()))?; - Some(node.get_value().read_all().await.unwrap()) - } async fn parse_entry( &mut self, entry: tar::Entry<'_, R>, @@ -63,8 +59,8 @@ where entry.link_name_bytes().unwrap().into_owned(), )), EntryType::Directory => Entry::Directory, - _ => { - log::warn!("unsupported entry type: {:?}", entry.header().entry_type()); + x => { + log::warn!("unsupported entry type: {:?}", x); return Ok(()); } }; diff --git a/judger/src/filesystem/handle.rs b/judger/src/filesystem/handle.rs index ca048cd5..bdbb65ff 100644 --- a/judger/src/filesystem/handle.rs +++ b/judger/src/filesystem/handle.rs @@ -17,6 +17,11 @@ impl Drop for MountHandle { let handle = self.0.take().unwrap(); let mountpoint = self.1.take().unwrap(); tokio::spawn(async move { + #[cfg(debug_assertions)] + { + log::warn!("debug mode: wait for 120s before drop mountpoint"); + tokio::time::sleep(tokio::time::Duration::from_secs(120)).await; + } handle.unmount().await.unwrap(); drop(mountpoint); }); diff --git a/judger/src/filesystem/table.rs b/judger/src/filesystem/table.rs index 6be08c46..bad412ec 100644 --- a/judger/src/filesystem/table.rs +++ b/judger/src/filesystem/table.rs @@ -112,7 +112,7 @@ impl AdjTable { where F: FnMut() -> V, { - let mut idx = self.get_first().idx; + let mut idx: usize = self.get_first().idx; for name in path { if self.by_id[idx].children.contains_key(&name) { idx = self.by_id[idx].children[&name]; @@ -123,6 +123,8 @@ impl AdjTable { value: default_value(), children: BTreeMap::new(), }); + // FIXME! + idx = new_idx; self.by_id[idx].children.insert(name, new_idx); } } @@ -173,6 +175,7 @@ pub struct NodeWrapper<'a, V> { impl<'a, V> NodeWrapper<'a, V> { /// get id of the node pub fn get_id(&self) -> usize { + debug_assert!(self.idx < self.table.by_id.len()); self.idx + ID_MIN } /// check if the node is root @@ -202,17 +205,16 @@ impl<'a, V> NodeWrapper<'a, V> { &self.table.by_id[self.idx].value } /// get name of the node - pub fn get_name(&self) -> &OsStr { - if self.is_root() { + pub fn get_name(&self) -> Option<&OsStr> { + Some(if self.is_root() { OsStr::new("/") } else { self.table.by_id[self.table.by_id[self.idx].parent_idx] .children .iter() - .find(|(_, &idx)| idx == self.idx) - .unwrap() + .find(|(_, &idx)| idx == self.idx)? .0 - } + }) } /// get node by component pub fn get_by_component(&self, component: &OsStr) -> Option> { @@ -270,6 +272,15 @@ impl<'a, V> NodeWrapperMut<'a, V> { None } } + /// remove children node by component + /// + /// note that it won't remove the node itself, only the edge + pub fn remove_by_component(&mut self, component: &OsStr) -> bool { + self.table.by_id[self.idx] + .children + .remove(component) + .is_some() + } /// get children nodes' id pub fn children(&mut self) -> impl Iterator + '_ { self.table.by_id[self.idx] @@ -281,6 +292,8 @@ impl<'a, V> NodeWrapperMut<'a, V> { #[cfg(test)] mod test { + use std::os::unix::ffi::OsStrExt; + use super::*; #[test] fn test_adj_table() { @@ -313,4 +326,17 @@ mod test { assert_eq!(l4, 5); assert_eq!(table.get(l4).unwrap().get_value(), &10); } + #[test] + fn parent_child_insert() { + let mut table = super::AdjTable::new(); + let mut root = table.insert_root(0); // inode 1 + assert_eq!(root.get_id(), 1); + let mut a = root.insert(OsStr::new("a").into(), 1); // inode 2 + assert_eq!(a.get_id(), 2); + let c = a.insert(OsStr::new("c").into(), 3); // inode 3 + assert_eq!(c.get_id(), 3); + let mut b = root.insert(OsStr::new("b").into(), 2); // inode 4 + assert_eq!(b.get_id(), 4); + assert_eq!(b.get_value(), &2); + } } diff --git a/judger/src/main.rs b/judger/src/main.rs index 3eec9fc2..b9a82e67 100644 --- a/judger/src/main.rs +++ b/judger/src/main.rs @@ -16,7 +16,7 @@ type Result = std::result::Result; async fn main() { // FIXME: use CONFIG for logging env_logger::Builder::from_default_env() - .filter_level(log::LevelFilter::Trace) + .filter_level(log::LevelFilter::Debug) .try_init() .ok(); diff --git a/judger/src/sandbox/process/process.rs b/judger/src/sandbox/process/process.rs index 5be31f92..3c5f05cb 100644 --- a/judger/src/sandbox/process/process.rs +++ b/judger/src/sandbox/process/process.rs @@ -96,7 +96,7 @@ impl Process { let root = self.fs.get_path(); // FIXME: check spec before unwrap let jail = self.context.get_args().next().unwrap(); - let unjailed = [jail, root.as_ref().as_os_str()].join(OsStr::new("")); + let unjailed = [root.as_ref().as_os_str(), jail].join(OsStr::new("")); let unjailed = PathBuf::from(unjailed); let mut ancestors = unjailed.ancestors(); @@ -109,7 +109,10 @@ impl Process { cmd.kill_on_drop(true); cmd.stdin(Stdio::piped()); cmd.stdout(Stdio::piped()); + #[cfg(not(debug_assertions))] cmd.stderr(Stdio::null()); + #[cfg(debug_assertions)] + cmd.stderr(Stdio::inherit()); cmd.env("PATH", self.get_env()); let arg_factory = ArgFactory::default() From 9bb41b6a5fed38420a29748f556e2824366f0ee6 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Fri, 31 May 2024 15:48:55 +0800 Subject: [PATCH 34/38] feat(Judger): :sparkles: impl ability of having file hole in MemBlock --- judger/src/filesystem/adapter/error.rs | 4 ++++ judger/src/filesystem/adapter/fuse.rs | 29 +++++++++++++++++++++--- judger/src/filesystem/adapter/mod.rs | 4 ++-- judger/src/filesystem/entry/mod.rs | 19 ++++++++++++++++ judger/src/filesystem/entry/rw.rs | 31 +++++++++++++++++++++----- judger/src/filesystem/table.rs | 19 +++++++++------- judger/src/sandbox/monitor/mem_cpu.rs | 4 +--- 7 files changed, 89 insertions(+), 21 deletions(-) diff --git a/judger/src/filesystem/adapter/error.rs b/judger/src/filesystem/adapter/error.rs index ad55b48a..02106eae 100644 --- a/judger/src/filesystem/adapter/error.rs +++ b/judger/src/filesystem/adapter/error.rs @@ -31,6 +31,8 @@ pub enum FuseError { PermissionDeny, #[error("invalid argument")] InvialdArg, + #[error("Already exist")] + AlreadyExist } impl From for fuse3::Errno { @@ -48,8 +50,10 @@ impl From for fuse3::Errno { FuseError::InvalidPath | FuseError::InvaildIno => libc::ENOENT, FuseError::PermissionDeny => libc::EACCES, FuseError::InvialdArg => libc::EINVAL, + FuseError::AlreadyExist => libc::EEXIST, _ => { log::warn!("FUSE driver broken: {}", value); + panic!("test"); libc::EINVAL } } diff --git a/judger/src/filesystem/adapter/fuse.rs b/judger/src/filesystem/adapter/fuse.rs index 133b099c..ea53a18f 100644 --- a/judger/src/filesystem/adapter/fuse.rs +++ b/judger/src/filesystem/adapter/fuse.rs @@ -326,6 +326,19 @@ where .map(|written| ReplyWrite { written })?) } } + fn flush( + &self, + req: Request, + inode: Inode, + fh: u64, + lock_owner: u64, + ) -> impl Future> + Send { + async move { + let node = self.handle_table.get(fh).ok_or(FuseError::HandleNotFound)?; + Entry::flush(node).await.ok_or(FuseError::Unimplemented); + Ok(()) + } + } fn access( &self, req: Request, @@ -419,10 +432,18 @@ where if parent_node.get_value().kind() != FileType::Directory { return Err(FuseError::NotDir.into()); } - let mut node = parent_node.insert(name.to_os_string(), Entry::new_file()); + let mut node = parent_node + .insert(name.to_os_string(), Entry::new_file()) + .ok_or(FuseError::AlreadyExist)?; + + let mut entry=node.get_value().clone(); + if flags&u32::from_ne_bytes(libc::O_APPEND.to_ne_bytes()) != 0 { + entry.set_append().await; + } + let fh = self .handle_table - .add(AsyncMutex::new(node.get_value().clone())); + .add(AsyncMutex::new(entry)); let inode = node.get_id() as u64; let entry = node.get_value(); @@ -443,7 +464,9 @@ where if parent_node.get_value().kind() != FileType::Directory { return Err(FuseError::NotDir.into()); } - let mut node = parent_node.insert(name.to_os_string(), Entry::Directory); + let mut node = parent_node + .insert(name.to_os_string(), Entry::Directory) + .ok_or(FuseError::AlreadyExist)?; let ino = node.get_id() as u64; Ok(reply_entry(&req, node.get_value(), ino)) } diff --git a/judger/src/filesystem/adapter/mod.rs b/judger/src/filesystem/adapter/mod.rs index c3358432..4cc87527 100644 --- a/judger/src/filesystem/adapter/mod.rs +++ b/judger/src/filesystem/adapter/mod.rs @@ -17,7 +17,7 @@ mod test { #[ignore = "not meant to be tested"] async fn test_mount() { Builder::from_default_env() - .filter_level(log::LevelFilter::Info) + .filter_level(log::LevelFilter::Debug) .filter_module("tracing::span::active", log::LevelFilter::Trace) .try_init() .ok(); @@ -25,7 +25,7 @@ mod test { log::info!("mounting test tarball in .temp ..."); let template = Template::new("plugins/rlua-54.lang").await.unwrap(); let filesystem = template.as_filesystem(1024 * 1024 * 1024); - let mut mount_handle = filesystem.raw_mount_with_path("./.temp/28").await.unwrap(); + let mut mount_handle = filesystem.raw_mount_with_path("./.temp/12").await.unwrap(); let handle = &mut mount_handle; tokio::select! { diff --git a/judger/src/filesystem/entry/mod.rs b/judger/src/filesystem/entry/mod.rs index 0667ac54..129fc1dd 100644 --- a/judger/src/filesystem/entry/mod.rs +++ b/judger/src/filesystem/entry/mod.rs @@ -28,6 +28,10 @@ pub trait FuseWriteTrait { async fn write(&mut self, offset: u64, data: &[u8]) -> std::io::Result; } +pub trait FuseFlushTrait { + async fn flush(&mut self) -> std::io::Result<()>; +} + /// Entry in the filesystem /// /// cloning the entry would clone file state @@ -110,6 +114,14 @@ where _ => None, } } + pub async fn set_append(&mut self) { + match self { + Entry::MemFile(x) => x.set_append().await, + _ => { + // FIXME: copy on write + } + } + } pub async fn write( self_: Arc>, offset: u64, @@ -126,4 +138,11 @@ where _ => None, } } + pub async fn flush(self_: Arc>) -> Option> { + let mut lock = self_.lock().await; + match &mut *lock { + Self::MemFile(block) => Some(block.flush().await), + _ => None, + } + } } diff --git a/judger/src/filesystem/entry/rw.rs b/judger/src/filesystem/entry/rw.rs index 1b3c32df..4bec742b 100644 --- a/judger/src/filesystem/entry/rw.rs +++ b/judger/src/filesystem/entry/rw.rs @@ -1,7 +1,7 @@ use std::{io, ops::Deref, sync::Arc}; use tokio::sync::Mutex; -use super::{FuseReadTrait, FuseWriteTrait, BLOCKSIZE}; +use super::{FuseFlushTrait, FuseReadTrait, FuseWriteTrait, BLOCKSIZE}; /// A block in memory /// @@ -12,6 +12,9 @@ use super::{FuseReadTrait, FuseWriteTrait, BLOCKSIZE}; #[derive(Default, Debug)] pub struct MemBlock { data: Arc>>, + /// when file is in read mode, cursor is the position of the next byte to read + /// + /// when file is in write mode, cursor at of the write buffer(append) cursor: usize, write_buffer: Vec, } @@ -24,6 +27,9 @@ impl MemBlock { write_buffer: Vec::new(), } } + pub async fn set_append(&mut self){ + self.cursor=self.data.lock().await.len(); + } pub fn get_size(&self) -> u64 { self.data.try_lock().map(|x| x.len()).unwrap_or_default() as u64 } @@ -45,24 +51,39 @@ impl FuseReadTrait for MemBlock { } impl FuseWriteTrait for MemBlock { async fn write(&mut self, offset: u64, data: &[u8]) -> std::io::Result { + // FIXME: file hole may cause OOM let mut locked = self.data.lock().await; - if locked.len() < offset as usize { + if self.cursor as usize > locked.len() { return Err(io::Error::new( io::ErrorKind::UnexpectedEof, "mem block out of bound", )); } - locked.resize(offset as usize, 0); - locked.extend_from_slice(data); + let new_size=self.cursor+offset as usize+data.len(); + if locked.len() < new_size { + locked.resize(new_size, 0); + } + for i in 0..data.len() { + locked[self.cursor + offset as usize + i] = data[i]; + } Ok(data.len() as u32) } } +impl FuseFlushTrait for MemBlock { + async fn flush(&mut self) -> std::io::Result<()> { + let mut locked = self.data.lock().await; + locked.extend_from_slice(&self.write_buffer); + self.write_buffer.clear(); + Ok(()) + } +} + impl Clone for MemBlock { fn clone(&self) -> Self { Self { data: self.data.clone(), - cursor: self.cursor.clone(), + cursor: 0, write_buffer: self.write_buffer.clone(), } } diff --git a/judger/src/filesystem/table.rs b/judger/src/filesystem/table.rs index bad412ec..5cd60ab2 100644 --- a/judger/src/filesystem/table.rs +++ b/judger/src/filesystem/table.rs @@ -236,7 +236,10 @@ pub struct NodeWrapperMut<'a, V> { impl<'a, V> NodeWrapperMut<'a, V> { /// insert a node by component - pub fn insert(&mut self, component: OsString, value: V) -> NodeWrapperMut { + pub fn insert(&mut self, component: OsString, value: V) -> Option> { + if self.table.by_id[self.idx].children.contains_key(&component) { + return None; + } let idx = self.table.by_id.len(); self.table.by_id.push(Node { parent_idx: self.idx, @@ -244,10 +247,10 @@ impl<'a, V> NodeWrapperMut<'a, V> { children: BTreeMap::new(), }); self.table.by_id[self.idx].children.insert(component, idx); - NodeWrapperMut { + Some(NodeWrapperMut { table: self.table, idx, - } + }) } /// get id of the node pub fn get_id(&self) -> usize { @@ -300,9 +303,9 @@ mod test { let mut table = super::AdjTable::new(); let mut root = table.insert_root(0); root.insert(OsStr::new("a").into(), 1); - let mut b = root.insert(OsStr::new("b").into(), 2); + let mut b = root.insert(OsStr::new("b").into(), 2).unwrap(); - let c = b.insert(OsStr::new("c").into(), 3); + let c = b.insert(OsStr::new("c").into(), 3).unwrap(); assert_eq!(c.get_id(), 4); assert_eq!(b.children().collect::>(), vec![4]); @@ -331,11 +334,11 @@ mod test { let mut table = super::AdjTable::new(); let mut root = table.insert_root(0); // inode 1 assert_eq!(root.get_id(), 1); - let mut a = root.insert(OsStr::new("a").into(), 1); // inode 2 + let mut a = root.insert(OsStr::new("a").into(), 1).unwrap(); // inode 2 assert_eq!(a.get_id(), 2); - let c = a.insert(OsStr::new("c").into(), 3); // inode 3 + let c = a.insert(OsStr::new("c").into(), 3).unwrap(); // inode 3 assert_eq!(c.get_id(), 3); - let mut b = root.insert(OsStr::new("b").into(), 2); // inode 4 + let mut b = root.insert(OsStr::new("b").into(), 2).unwrap(); // inode 4 assert_eq!(b.get_id(), 4); assert_eq!(b.get_value(), &2); } diff --git a/judger/src/sandbox/monitor/mem_cpu.rs b/judger/src/sandbox/monitor/mem_cpu.rs index 2643d8ce..ac12a199 100644 --- a/judger/src/sandbox/monitor/mem_cpu.rs +++ b/judger/src/sandbox/monitor/mem_cpu.rs @@ -115,8 +115,6 @@ impl super::Monitor for Monitor { reason } fn poll_exhaust(&mut self) -> Option { - debug_assert!(self.cgroup.tasks().is_empty()); - let wrapper = wrapper::CgroupWrapper::new(&self.cgroup); if wrapper.oom() { @@ -133,7 +131,7 @@ impl super::Monitor for Monitor { /// it is only guaranteed to below limitation provided + [`MONITOR_ACCURACY`]. async fn stat(self) -> Self::Resource { // there should be no process left - debug_assert!(self.cgroup.tasks().is_empty()); + // debug_assert!(self.cgroup.tasks().is_empty()); // poll once more to get final stat let wrapper = wrapper::CgroupWrapper::new(&self.cgroup); (wrapper.memory(), wrapper.cpu()) From 13892cdec04ef5e7c25500190585e48ea0dec35d Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Fri, 31 May 2024 16:26:47 +0800 Subject: [PATCH 35/38] fix(Judger): :bug: fix rlua-54.lang --- .github/workflows/judger.yml | 2 - Cargo.lock | 66 +++++++++ Cargo.toml | 2 +- judger/Dockerfile | 4 - judger/justfile | 33 +---- judger/plugins/rlua-54/.dockerignore | 3 +- judger/plugins/rlua-54/spec.toml | 6 +- judger/plugins/rlua-54/src/compile.rs | 8 - judger/plugins/rlua-54/src/execute.rs | 91 ------------ judger/plugins/rlua-54/src/main.rs | 119 +++++++++++---- judger/plugins/rlua-54/src/violate.rs | 33 ----- judger/src/filesystem/adapter/error.rs | 2 +- judger/src/filesystem/adapter/fuse.rs | 8 +- judger/src/filesystem/entry/rw.rs | 8 +- judger/src/language/spec/compile.rs | 1 + judger/src/language/spec/judge.rs | 1 + judger/src/language/{spec.rs => spec/mod.rs} | 148 +------------------ judger/src/language/spec/raw.rs | 142 ++++++++++++++++++ 18 files changed, 327 insertions(+), 350 deletions(-) delete mode 100644 judger/plugins/rlua-54/src/compile.rs delete mode 100644 judger/plugins/rlua-54/src/execute.rs delete mode 100644 judger/plugins/rlua-54/src/violate.rs create mode 100644 judger/src/language/spec/compile.rs create mode 100644 judger/src/language/spec/judge.rs rename judger/src/language/{spec.rs => spec/mod.rs} (55%) create mode 100644 judger/src/language/spec/raw.rs diff --git a/.github/workflows/judger.yml b/.github/workflows/judger.yml index 773050f5..d549502d 100644 --- a/.github/workflows/judger.yml +++ b/.github/workflows/judger.yml @@ -45,8 +45,6 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: build nsjail run: cd judger && just build-nsjail - - name: build rlua-54 - run: cd judger && just release-docker - name: build judger uses: docker/build-push-action@v5 with: diff --git a/Cargo.lock b/Cargo.lock index 14e08ba3..e85f589e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -896,6 +896,16 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bstr" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.15.3" @@ -4572,6 +4582,62 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rlua" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3120d9610f84b17da849f5cc8c089bb74299285515bb4a6114550bbc39ddb1e7" +dependencies = [ + "bitflags 2.4.2", + "bstr", + "libc", + "num-traits", + "rlua-lua51-sys", + "rlua-lua53-sys", + "rlua-lua54-sys", +] + +[[package]] +name = "rlua-54" +version = "0.1.0" +dependencies = [ + "libc", + "rlua", +] + +[[package]] +name = "rlua-lua51-sys" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8f4636b03fa54b5c154415f1d9333cec7a7e3bd6bc64c092a2196e488cfd52" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "rlua-lua53-sys" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c930c9de241450277dec8617cfa540eaed711db7b0fd894316d7d7c0b6b8f4a4" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "rlua-lua54-sys" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aafabafe1895cb4a2be81a56d7ff3d46bf4b5d2f9cfdbea2ed404cdabe96474" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "rsa" version = "0.9.6" diff --git a/Cargo.toml b/Cargo.toml index 9edd09ce..11a7e08c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,8 @@ members = [ "testsuit", "backend/migration", "grpc", + "judger/plugins/rlua-54", ] -exclude = ["judger/plugins/rlua-54"] [workspace.dependencies] prost = "0.12.3" diff --git a/judger/Dockerfile b/judger/Dockerfile index 061f2454..82f3213c 100644 --- a/judger/Dockerfile +++ b/judger/Dockerfile @@ -29,9 +29,5 @@ COPY --from=builder /usr/local/cargo/bin/judger / COPY judger/nsjail-3.1 / -WORKDIR /plugins/rlua-54 -COPY judger/plugins/rlua-54/rootfs rootfs -COPY judger/plugins/rlua-54/spec.toml . - WORKDIR / CMD ["/judger"] \ No newline at end of file diff --git a/judger/justfile b/judger/justfile index 3c846dd4..647660e6 100644 --- a/judger/justfile +++ b/judger/justfile @@ -1,15 +1,8 @@ -release-docker: - cd plugins/rlua-54 && sh ./build.sh - # sudo docker build --build-arg ARCH=$(uname -m) -f ./Dockerfile -t mdoj-judger .. - build-plugin: - mkdir -p config plugins-out cd plugins && sh build-all.sh + mkdir -p config plugins-out + mv plugins/*.lang plugins-out/ -release-plugin: - just build-plugin - cd plugins && sh export-all.sh - build-nsjail: sh ./build-nsjail.sh cp nsjail-docker/output/*-linux-musl/nsjail-* . @@ -18,27 +11,9 @@ prepare: just build-nsjail just build-plugin -clean: - sudo rm -rf .temp - cargo clean +clean-nsjail: + sudo rm -rf ./nsjail-3.1 docker images rm nsjail-3.1-$(uname -m)-linux-musl docker images rm protobuf-3.21.1-$(uname -m)-linux-musl docker images rm libnl-3.2.25-$(uname -m)-linux-musl docker images rm musl-cross-make-$(uname -m)-linux-musl - -test: - sudo rm -rf .temp/* - mkdir -p .temp - cargo test --no-fail-fast -- --test-threads 1 - -run: - sudo rm -rf .temp/* - cargo run - -run-release: - sudo rm -rf .temp/* - cargo run --release - -ci-test: - just ci-test - just test diff --git a/judger/plugins/rlua-54/.dockerignore b/judger/plugins/rlua-54/.dockerignore index 9f7ba04d..c41cc9e3 100644 --- a/judger/plugins/rlua-54/.dockerignore +++ b/judger/plugins/rlua-54/.dockerignore @@ -1,2 +1 @@ -/target -/rootfs \ No newline at end of file +/target \ No newline at end of file diff --git a/judger/plugins/rlua-54/spec.toml b/judger/plugins/rlua-54/spec.toml index fe5a2223..784d6a1f 100644 --- a/judger/plugins/rlua-54/spec.toml +++ b/judger/plugins/rlua-54/spec.toml @@ -1,12 +1,12 @@ fs_limit = 3145728 -file = "/src/code.txt" +file = "/code.lua" info = "A lightweight Lua 5.4 runtime build for both secure sandboxing and modj-sandbox test" extension = "lua" # same extension means same language name = "rlua-54" id = "1c41598f-e253-4f81-9ef5-d50bf1e4e74f" # be sure it's unique [compile] -command = ["/rlua-54","compile"] +command = ["/rlua-54","skip"] [judge] -command = ["/rlua-54","execute"] +command = ["/rlua-54"] diff --git a/judger/plugins/rlua-54/src/compile.rs b/judger/plugins/rlua-54/src/compile.rs deleted file mode 100644 index b2e44ad1..00000000 --- a/judger/plugins/rlua-54/src/compile.rs +++ /dev/null @@ -1,8 +0,0 @@ -use std::{ - fs, - io::{stdin, Read}, -}; - -pub fn compile() { - -} diff --git a/judger/plugins/rlua-54/src/execute.rs b/judger/plugins/rlua-54/src/execute.rs deleted file mode 100644 index 90aad3dd..00000000 --- a/judger/plugins/rlua-54/src/execute.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::{ - fs, - io::{stdin, BufRead, Read}, -}; - -use rlua::{prelude::*, Context, Lua, ToLua, Value, Variadic}; - -fn lua_write(_: Context, strings: Variadic) -> rlua::Result { - for s in strings { - print!("{}", String::from_utf8_lossy(s.as_bytes())); - } - Ok(true) -} - -fn lua_read(ctx: Context, string: String) -> rlua::Result { - match string.as_str() { - "*all" | "*a" => { - let mut buf = Vec::new(); - stdin().lock().read_to_end(&mut buf).unwrap(); - let s = ctx.create_string(&buf)?; - s.to_lua(ctx) - } - "*line" | "*l" => { - let mut buf = Vec::new(); - stdin().lock().read_until(b'\n', &mut buf).ok(); - let s = ctx.create_string(&buf)?; - s.to_lua(ctx) - } - "*number" | "*n" => { - let mut reader = stdin().lock(); - let mut is_float = false; - let mut result: Vec = Vec::new(); - - loop { - let mut buf = vec![0; 1]; - if reader.read_exact(&mut buf).is_ok() { - let b = buf[0]; - match b { - b'0'..=b'9' => result.push(b), - b'.' => { - if is_float { - break; - } - is_float = true; - result.push(b); - } - _ => break, - } - } - } - - String::from_utf8(result) - .unwrap() - .parse::() - .unwrap() - .to_lua(ctx) - } - _ => match string.parse::() { - Ok(n) => { - let mut buf = vec![0; n]; - stdin().read_exact(&mut buf).unwrap(); - let s = ctx.create_string(&buf)?; - s.to_lua(ctx) - } - Err(_) => Ok(Value::Nil), - }, - } -} - -pub fn execute() { - let lua = Lua::new(); - - lua.context(|ctx| { - let printf = ctx.create_function(lua_write).unwrap(); - let write = ctx.create_function(lua_write).unwrap(); - let read = ctx.create_function(lua_read).unwrap(); - - let io_table= ctx.create_table().unwrap(); - io_table.set("write", write).unwrap(); - io_table.set("read", read).unwrap(); - - let globals = ctx.globals(); - globals.set("printf", printf).unwrap(); - globals.set("io", io_table).unwrap(); - - let source = fs::read(crate::LUA_SRC).unwrap(); - let code = ctx.load(&source); - - code.exec().unwrap(); - }); -} diff --git a/judger/plugins/rlua-54/src/main.rs b/judger/plugins/rlua-54/src/main.rs index a8ab3f5d..8983e706 100644 --- a/judger/plugins/rlua-54/src/main.rs +++ b/judger/plugins/rlua-54/src/main.rs @@ -1,29 +1,96 @@ -use std::process::ExitCode; - -mod compile; -mod execute; -mod violate; -const LUA_SRC: &str = "/src/code.txt"; - -fn main() -> ExitCode { - let args: Vec = std::env::args().collect(); - - let cmd=args.get(1).unwrap().as_str(); - - match cmd { - "compile" => compile::compile(), - "execute" => execute::execute(), - "violate" => match args.get(2).unwrap().as_str(){ - "cpu" => violate::cpu(), - "mem" => violate::mem(), - "disk" => violate::disk(), - "net" => violate::net(), - "syscall" => violate::syscall(), - _ => println!("3: Invalid command"), +const LUA_SRC: &str = "/code.lua"; +use std::{ + env::{args, args_os}, fs, io::{stdin, BufRead, Read}, process::exit +}; + +use rlua::{prelude::*, Context, Lua, ToLua, Value, Variadic}; + +fn lua_write(_: Context, strings: Variadic) -> rlua::Result { + for s in strings { + print!("{}", String::from_utf8_lossy(s.as_bytes())); + } + Ok(true) +} + +fn lua_read(ctx: Context, string: String) -> rlua::Result { + match string.as_str() { + "*all" | "*a" => { + let mut buf = Vec::new(); + stdin().lock().read_to_end(&mut buf).unwrap(); + let s = ctx.create_string(&buf)?; + s.to_lua(ctx) + } + "*line" | "*l" => { + let mut buf = Vec::new(); + stdin().lock().read_until(b'\n', &mut buf).ok(); + let s = ctx.create_string(&buf)?; + s.to_lua(ctx) + } + "*number" | "*n" => { + let mut reader = stdin().lock(); + let mut is_float = false; + let mut result: Vec = Vec::new(); + + loop { + let mut buf = vec![0; 1]; + if reader.read_exact(&mut buf).is_ok() { + let b = buf[0]; + match b { + b'0'..=b'9' => result.push(b), + b'.' => { + if is_float { + break; + } + is_float = true; + result.push(b); + } + _ => break, + } + } + } + + String::from_utf8(result) + .unwrap() + .parse::() + .unwrap() + .to_lua(ctx) + } + _ => match string.parse::() { + Ok(n) => { + let mut buf = vec![0; n]; + stdin().read_exact(&mut buf).unwrap(); + let s = ctx.create_string(&buf)?; + s.to_lua(ctx) + } + Err(_) => Ok(Value::Nil), }, - "hello" => println!("hello world"), - _ => println!("4: Invalid command: \"{}\"", cmd), - }; + } +} + +pub fn main() { + if args().len()!=1{ + return; + } + let lua = Lua::new(); + lua.context(|ctx| { + let printf = ctx.create_function(lua_write).unwrap(); + let write = ctx.create_function(lua_write).unwrap(); + let read = ctx.create_function(lua_read).unwrap(); + + let io_table= ctx.create_table().unwrap(); + io_table.set("write", write).unwrap(); + io_table.set("read", read).unwrap(); + + let globals = ctx.globals(); + globals.set("printf", printf).unwrap(); + globals.set("io", io_table).unwrap(); + + let source = fs::read(crate::LUA_SRC).unwrap(); + let code = ctx.load(&source); - ExitCode::from(0) + if let Err(err)=code.exec(){ + eprintln!("{}", err); + exit(1); + } + }); } diff --git a/judger/plugins/rlua-54/src/violate.rs b/judger/plugins/rlua-54/src/violate.rs deleted file mode 100644 index 360f731e..00000000 --- a/judger/plugins/rlua-54/src/violate.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::{net::TcpStream, fs::File, io::Write, ffi::CString}; - -pub fn cpu(){ - loop{}; -} - -pub fn mem(){ - let mut v = Vec::new(); - loop{ - v.push(0); - } -} - -pub fn disk(){ - let mut f = File::create("file.txt").unwrap(); - loop{ - f.write_all(b"Disk").unwrap(); - } -} - -pub fn net(){ - let mut stream = TcpStream::connect("8.8.8.8").unwrap(); - loop{ - stream.write(b"Net").unwrap(); - } -} - -pub fn syscall(){ - unsafe{ - let path=CString::new("/boot").unwrap(); - libc::umount2(path.as_ptr(), libc::MNT_DETACH); - } -} \ No newline at end of file diff --git a/judger/src/filesystem/adapter/error.rs b/judger/src/filesystem/adapter/error.rs index 02106eae..f3c4d76e 100644 --- a/judger/src/filesystem/adapter/error.rs +++ b/judger/src/filesystem/adapter/error.rs @@ -32,7 +32,7 @@ pub enum FuseError { #[error("invalid argument")] InvialdArg, #[error("Already exist")] - AlreadyExist + AlreadyExist, } impl From for fuse3::Errno { diff --git a/judger/src/filesystem/adapter/fuse.rs b/judger/src/filesystem/adapter/fuse.rs index ea53a18f..543f4554 100644 --- a/judger/src/filesystem/adapter/fuse.rs +++ b/judger/src/filesystem/adapter/fuse.rs @@ -436,14 +436,12 @@ where .insert(name.to_os_string(), Entry::new_file()) .ok_or(FuseError::AlreadyExist)?; - let mut entry=node.get_value().clone(); - if flags&u32::from_ne_bytes(libc::O_APPEND.to_ne_bytes()) != 0 { + let mut entry = node.get_value().clone(); + if flags & u32::from_ne_bytes(libc::O_APPEND.to_ne_bytes()) != 0 { entry.set_append().await; } - let fh = self - .handle_table - .add(AsyncMutex::new(entry)); + let fh = self.handle_table.add(AsyncMutex::new(entry)); let inode = node.get_id() as u64; let entry = node.get_value(); diff --git a/judger/src/filesystem/entry/rw.rs b/judger/src/filesystem/entry/rw.rs index 4bec742b..db85c2d4 100644 --- a/judger/src/filesystem/entry/rw.rs +++ b/judger/src/filesystem/entry/rw.rs @@ -13,7 +13,7 @@ use super::{FuseFlushTrait, FuseReadTrait, FuseWriteTrait, BLOCKSIZE}; pub struct MemBlock { data: Arc>>, /// when file is in read mode, cursor is the position of the next byte to read - /// + /// /// when file is in write mode, cursor at of the write buffer(append) cursor: usize, write_buffer: Vec, @@ -27,8 +27,8 @@ impl MemBlock { write_buffer: Vec::new(), } } - pub async fn set_append(&mut self){ - self.cursor=self.data.lock().await.len(); + pub async fn set_append(&mut self) { + self.cursor = self.data.lock().await.len(); } pub fn get_size(&self) -> u64 { self.data.try_lock().map(|x| x.len()).unwrap_or_default() as u64 @@ -59,7 +59,7 @@ impl FuseWriteTrait for MemBlock { "mem block out of bound", )); } - let new_size=self.cursor+offset as usize+data.len(); + let new_size = self.cursor + offset as usize + data.len(); if locked.len() < new_size { locked.resize(new_size, 0); } diff --git a/judger/src/language/spec/compile.rs b/judger/src/language/spec/compile.rs new file mode 100644 index 00000000..95f4f2d2 --- /dev/null +++ b/judger/src/language/spec/compile.rs @@ -0,0 +1 @@ +pub struct Judge {} diff --git a/judger/src/language/spec/judge.rs b/judger/src/language/spec/judge.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/judger/src/language/spec/judge.rs @@ -0,0 +1 @@ + diff --git a/judger/src/language/spec.rs b/judger/src/language/spec/mod.rs similarity index 55% rename from judger/src/language/spec.rs rename to judger/src/language/spec/mod.rs index d7f48914..b5573305 100644 --- a/judger/src/language/spec.rs +++ b/judger/src/language/spec/mod.rs @@ -1,11 +1,16 @@ -use std::{ffi::OsString, str::FromStr, time::Duration}; +use std::{ffi::OsString, time::Duration}; use grpc::judger::LangInfo; -use serde::Deserialize; use uuid::Uuid; use crate::sandbox::{Cpu, Memory, Stat}; +use self::raw::Raw; + +mod compile; +mod judge; +mod raw; + pub struct CpuFactor { kernel: u64, user: u64, @@ -138,142 +143,3 @@ impl Spec { } } } - -#[derive(Deserialize)] -pub struct Raw { - fs_limit: Option, - file: String, - info: String, - extension: String, - name: String, - id: Uuid, - compile: RawCompile, - judge: RawJudge, -} - -impl<'a> From<&'a Raw> for LangInfo { - fn from(value: &'a Raw) -> Self { - LangInfo { - lang_uid: value.id.to_string(), - lang_name: value.name.clone(), - info: value.info.clone(), - lang_ext: value.extension.clone(), - } - } -} - -impl Raw { - pub fn fill(&mut self) { - if self.fs_limit.is_none() { - self.fs_limit = Some(67108864); - } - self.compile.fill(); - self.judge.fill(); - } -} - -#[derive(Deserialize)] -struct RawCompile { - command: Vec, - kernel_mem: Option, - memory: Option, - user_mem: Option, - rt_time: Option, - cpu_time: Option, - time: Option, - output_limit: Option, - walltime: Option, -} - -impl RawCompile { - fn fill(&mut self) { - let template = Self::default(); - macro_rules! try_fill { - ($f:ident) => { - if self.$f.is_none(){ - self.$f=template.$f; - } - }; - ($f:ident,$($e:ident),+) => { - try_fill!($f); - try_fill!($($e),+); - } - } - try_fill!( - kernel_mem, - user_mem, - rt_time, - cpu_time, - time, - output_limit, - walltime, - memory - ); - } -} - -impl Default for RawCompile { - fn default() -> Self { - Self { - command: Vec::new(), - kernel_mem: Some(268435456), - memory: Some(268435456), - user_mem: Some(8589934592), - rt_time: Some(7e8 as u64), - cpu_time: Some(10e9 as u64), - time: Some(10e9 as u64), - output_limit: Some(33554432), - walltime: Some(260e9 as u64), - } - } -} - -#[derive(Deserialize)] -struct RawJudge { - command: Vec, - kernel_mem: Option, - rt_time: Option, - memory_multiplier: Option, - cpu_multiplier: Option, - walltime: Option, - output: Option, -} - -impl RawJudge { - fn fill(&mut self) { - let template = Self::default(); - macro_rules! try_fill { - ($f:ident) => { - if self.$f.is_none(){ - self.$f=template.$f; - } - }; - ($f:ident,$($e:ident),+) => { - try_fill!($f); - try_fill!($($e),+); - } - } - try_fill!( - kernel_mem, - rt_time, - memory_multiplier, - cpu_multiplier, - walltime, - output - ); - } -} - -impl Default for RawJudge { - fn default() -> Self { - Self { - command: Vec::new(), - kernel_mem: Some(268435456), - rt_time: Some(7e8 as u64), - memory_multiplier: Some(1.0), - cpu_multiplier: Some(1.0), - walltime: Some(360e9 as u64), - output: Some(1024 * 1024 * 16), - } - } -} diff --git a/judger/src/language/spec/raw.rs b/judger/src/language/spec/raw.rs new file mode 100644 index 00000000..3467e990 --- /dev/null +++ b/judger/src/language/spec/raw.rs @@ -0,0 +1,142 @@ +use grpc::judger::LangInfo; +use serde::Deserialize; +use uuid::Uuid; + +#[derive(Deserialize)] +pub struct Raw { + pub fs_limit: Option, + pub file: String, + pub info: String, + pub extension: String, + pub name: String, + pub id: Uuid, + pub compile: RawCompile, + pub judge: RawJudge, +} + +impl<'a> From<&'a Raw> for LangInfo { + fn from(value: &'a Raw) -> Self { + LangInfo { + lang_uid: value.id.to_string(), + lang_name: value.name.clone(), + info: value.info.clone(), + lang_ext: value.extension.clone(), + } + } +} + +impl Raw { + pub fn fill(&mut self) { + if self.fs_limit.is_none() { + self.fs_limit = Some(67108864); + } + self.compile.fill(); + self.judge.fill(); + } +} + +#[derive(Deserialize)] +pub struct RawCompile { + pub command: Vec, + pub kernel_mem: Option, + pub memory: Option, + pub user_mem: Option, + pub rt_time: Option, + pub cpu_time: Option, + pub time: Option, + pub output_limit: Option, + pub walltime: Option, +} + +impl RawCompile { + fn fill(&mut self) { + let template = Self::default(); + macro_rules! try_fill { + ($f:ident) => { + if self.$f.is_none(){ + self.$f=template.$f; + } + }; + ($f:ident,$($e:ident),+) => { + try_fill!($f); + try_fill!($($e),+); + } + } + try_fill!( + kernel_mem, + user_mem, + rt_time, + cpu_time, + time, + output_limit, + walltime, + memory + ); + } +} + +impl Default for RawCompile { + fn default() -> Self { + Self { + command: Vec::new(), + kernel_mem: Some(268435456), + memory: Some(268435456), + user_mem: Some(8589934592), + rt_time: Some(7e8 as u64), + cpu_time: Some(10e9 as u64), + time: Some(10e9 as u64), + output_limit: Some(33554432), + walltime: Some(260e9 as u64), + } + } +} + +#[derive(Deserialize)] +pub struct RawJudge { + pub command: Vec, + pub kernel_mem: Option, + pub rt_time: Option, + pub memory_multiplier: Option, + pub cpu_multiplier: Option, + pub walltime: Option, + pub output: Option, +} + +impl RawJudge { + fn fill(&mut self) { + let template = Self::default(); + macro_rules! try_fill { + ($f:ident) => { + if self.$f.is_none(){ + self.$f=template.$f; + } + }; + ($f:ident,$($e:ident),+) => { + try_fill!($f); + try_fill!($($e),+); + } + } + try_fill!( + kernel_mem, + rt_time, + memory_multiplier, + cpu_multiplier, + walltime, + output + ); + } +} + +impl Default for RawJudge { + fn default() -> Self { + Self { + command: Vec::new(), + kernel_mem: Some(268435456), + rt_time: Some(7e8 as u64), + memory_multiplier: Some(1.0), + cpu_multiplier: Some(1.0), + walltime: Some(360e9 as u64), + output: Some(1024 * 1024 * 16), + } + } +} From f46964192b0b5b97dbd81e5d8546924e69e6bef9 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Fri, 31 May 2024 16:28:20 +0800 Subject: [PATCH 36/38] cargo fmt --- judger/plugins/rlua-54/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/judger/plugins/rlua-54/src/main.rs b/judger/plugins/rlua-54/src/main.rs index 8983e706..d44b1c3f 100644 --- a/judger/plugins/rlua-54/src/main.rs +++ b/judger/plugins/rlua-54/src/main.rs @@ -1,6 +1,6 @@ const LUA_SRC: &str = "/code.lua"; use std::{ - env::{args, args_os}, fs, io::{stdin, BufRead, Read}, process::exit + env::args, fs, io::{stdin, BufRead, Read}, process::exit }; use rlua::{prelude::*, Context, Lua, ToLua, Value, Variadic}; From babbcfa2371e499b0a5c77bacf07149fed4c1f52 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Fri, 31 May 2024 16:35:58 +0800 Subject: [PATCH 37/38] fix deps for rust 1.75 --- Cargo.lock | 1329 +++++++++++++++--------------- backend/src/controller/crypto.rs | 2 +- 2 files changed, 650 insertions(+), 681 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e85f589e..7963f2d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "bytes", "futures-core", "futures-sink", @@ -29,7 +29,7 @@ dependencies = [ "actix-service", "actix-utils", "actix-web", - "bitflags 2.4.2", + "bitflags 2.5.0", "bytes", "derive_more", "futures-core", @@ -44,17 +44,17 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.6.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d223b13fd481fc0d1f83bb12659ae774d9e3601814c68a0bc539731698cca743" +checksum = "4eb9843d84c775696c37d9a418bbb01b932629d01870722c0f13eb3f95e2536d" dependencies = [ "actix-codec", "actix-rt", "actix-service", "actix-utils", "ahash 0.8.11", - "base64", - "bitflags 2.4.2", + "base64 0.22.1", + "bitflags 2.5.0", "brotli", "bytes", "bytestring", @@ -88,18 +88,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] name = "actix-router" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22475596539443685426b6bdadb926ad0ecaefdfc5fb05e5e3441f15463c511" +checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" dependencies = [ "bytestring", + "cfg-if", "http 0.2.12", "regex", + "regex-lite", "serde", "tracing", ] @@ -126,7 +128,7 @@ dependencies = [ "futures-core", "futures-util", "mio", - "socket2 0.5.6", + "socket2 0.5.7", "tokio", "tracing", ] @@ -154,9 +156,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.5.1" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a6556ddebb638c2358714d853257ed226ece6023ef9364f23f0c70737ea984" +checksum = "b1cf67dadb19d7c95e5a299e2dda24193b89d5d4f33a3b9800888ede9e19aa32" dependencies = [ "actix-codec", "actix-http", @@ -183,11 +185,12 @@ dependencies = [ "once_cell", "pin-project-lite", "regex", + "regex-lite", "serde", "serde_json", "serde_urlencoded", "smallvec", - "socket2 0.5.6", + "socket2 0.5.7", "time", "url", ] @@ -201,14 +204,14 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] name = "addr2line" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ "gimli", ] @@ -245,9 +248,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -275,9 +278,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "android-tzdata" @@ -296,47 +299,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -344,9 +348,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "arrayvec" @@ -377,28 +381,26 @@ dependencies = [ [[package]] name = "async-channel" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ "concurrent-queue", - "event-listener 5.2.0", - "event-listener-strategy 0.5.0", + "event-listener-strategy 0.5.2", "futures-core", "pin-project-lite", ] [[package]] name = "async-executor" -version = "1.8.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c" +checksum = "c8828ec6e544c02b0d6691d21ed9f9218d0384a82542855073c2a3f58304aaf0" dependencies = [ - "async-lock 3.3.0", "async-task", "concurrent-queue", - "fastrand 2.0.1", - "futures-lite 2.2.0", + "fastrand 2.1.0", + "futures-lite 2.3.0", "slab", ] @@ -408,12 +410,12 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ - "async-channel 2.2.0", + "async-channel 2.3.1", "async-executor", - "async-io 2.3.1", + "async-io 2.3.2", "async-lock 3.3.0", "blocking", - "futures-lite 2.2.0", + "futures-lite 2.3.0", "once_cell", "tokio", ] @@ -440,18 +442,18 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f97ab0c5b00a7cdbe5a371b9a782ee7be1316095885c8a4ea1daf490eb0ef65" +checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" dependencies = [ "async-lock 3.3.0", "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.2.0", + "futures-lite 2.3.0", "parking", - "polling 3.5.0", - "rustix 0.38.31", + "polling 3.7.0", + "rustix 0.38.34", "slab", "tracing", "windows-sys 0.52.0", @@ -490,13 +492,13 @@ dependencies = [ [[package]] name = "async-recursion" -version = "1.0.5" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -545,24 +547,24 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] name = "async-task" -version = "4.7.0" +version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -580,33 +582,25 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "atomic-write-file" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8204db279bf648d64fe845bd8840f78b39c8132ed4d6a4194c3b10d4b4cfb0b" -dependencies = [ - "nix 0.28.0", - "rand", -] - [[package]] name = "attribute-derive" -version = "0.8.1" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c94f43ede6f25dab1dea046bff84d85dea61bd49aba7a9011ad66c0d449077b" +checksum = "8b48808b337d6b74c15ff9becfc0e139fe2b4e2b224d670a0ecdb46b0b2d3d9b" dependencies = [ "attribute-derive-macro", + "derive-where", + "manyhow", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] name = "attribute-derive-macro" -version = "0.8.1" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b409e2b2d2dc206d2c0ad3575a93f001ae21a1593e2d0c69b69c308e63f3b422" +checksum = "5b19cbd63850ecff821c413e12846a67ec9f4ce7309c70959b94ecf9b2575ee2" dependencies = [ "collection_literals", "interpolator", @@ -615,14 +609,14 @@ dependencies = [ "proc-macro2", "quote", "quote-use", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "axum" @@ -673,7 +667,7 @@ dependencies = [ name = "backend" version = "0.1.0" dependencies = [ - "base64", + "base64 0.21.7", "bincode", "blake2", "chrono", @@ -695,7 +689,7 @@ dependencies = [ "opentelemetry_sdk", "paste", "postcard", - "prost 0.12.3", + "prost 0.12.6", "prost-types", "quick_cache", "rand", @@ -724,9 +718,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" dependencies = [ "addr2line", "cc", @@ -749,6 +743,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.6.0" @@ -798,9 +798,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" dependencies = [ "serde", ] @@ -837,25 +837,22 @@ dependencies = [ [[package]] name = "blocking" -version = "1.5.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" dependencies = [ - "async-channel 2.2.0", - "async-lock 3.3.0", + "async-channel 2.3.1", "async-task", - "fastrand 2.0.1", "futures-io", - "futures-lite 2.2.0", + "futures-lite 2.3.0", "piper", - "tracing", ] [[package]] name = "borsh" -version = "1.3.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f58b559fd6448c6e2fd0adb5720cd98a2506594cafa4737ff98c396f3e82f667" +checksum = "dbe5b10e214954177fb1dc9fbd20a1a2608fe99e6c832033bdc7cea287a20d77" dependencies = [ "borsh-derive", "cfg_aliases", @@ -863,23 +860,23 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.3.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aadb5b6ccbd078890f6d7003694e33816e6b784358f18e15e7e6d9f065a57cd" +checksum = "d7a8646f94ab393e43e8b35a2558b1624bed28b97ee09c5d15456e3c9463f46d" dependencies = [ "once_cell", "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", "syn_derive", ] [[package]] name = "brotli" -version = "3.4.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -888,9 +885,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.5.1" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -908,9 +905,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.15.3" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytecheck" @@ -921,7 +918,6 @@ dependencies = [ "bytecheck_derive", "ptr_meta", "simdutf8", - "uuid", ] [[package]] @@ -990,18 +986,19 @@ checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" [[package]] name = "camino" -version = "1.1.6" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" [[package]] name = "cc" -version = "1.0.90" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" dependencies = [ "jobserver", "libc", + "once_cell", ] [[package]] @@ -1031,9 +1028,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.35" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -1041,7 +1038,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -1073,9 +1070,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.2" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -1090,19 +1087,19 @@ dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.0", + "strsim 0.11.1", ] [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -1125,15 +1122,15 @@ checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "concurrent-queue" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] @@ -1144,11 +1141,12 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be" dependencies = [ + "convert_case 0.6.0", "lazy_static", "nom", "pathdiff", "serde", - "toml 0.8.10", + "toml 0.8.13", ] [[package]] @@ -1228,9 +1226,9 @@ dependencies = [ [[package]] name = "cookie" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cd91cf61412820176e137621345ee43b3f4423e589e7ae4e50d601d93e35ef8" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ "percent-encoding", "time", @@ -1264,9 +1262,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.0.1" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" dependencies = [ "crc-catalog", ] @@ -1279,18 +1277,18 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ "crossbeam-utils", ] @@ -1306,9 +1304,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -1360,12 +1358,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" dependencies = [ - "darling_core 0.20.8", - "darling_macro 0.20.8", + "darling_core 0.20.9", + "darling_macro 0.20.9", ] [[package]] @@ -1384,16 +1382,16 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim 0.10.0", - "syn 2.0.52", + "strsim 0.11.1", + "syn 2.0.66", ] [[package]] @@ -1409,13 +1407,13 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ - "darling_core 0.20.8", + "darling_core 0.20.9", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -1425,7 +1423,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", @@ -1433,9 +1431,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "default-struct-builder" @@ -1443,17 +1441,17 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fa90da96b8fd491f5754d1f7a731f73921e3b7aa0ce333c821a0e43666ac14" dependencies = [ - "darling 0.20.8", + "darling 0.20.9", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] name = "der" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "pem-rfc7468", @@ -1489,7 +1487,7 @@ checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -1577,9 +1575,9 @@ dependencies = [ [[package]] name = "either" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" dependencies = [ "serde", ] @@ -1618,9 +1616,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] @@ -1631,10 +1629,10 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -1658,18 +1656,19 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erased-serde" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388979d208a049ffdfb22fa33b9c81942215b940910bccfe258caeb25d125cb3" +checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" dependencies = [ "serde", + "typeid", ] [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1705,9 +1704,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.2.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ "concurrent-queue", "parking", @@ -1726,11 +1725,11 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ - "event-listener 5.2.0", + "event-listener 5.3.1", "pin-project-lite", ] @@ -1755,9 +1754,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "ff" @@ -1777,16 +1776,10 @@ checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.4.1", "windows-sys 0.52.0", ] -[[package]] -name = "finl_unicode" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" - [[package]] name = "fixedbitset" version = "0.4.2" @@ -1795,9 +1788,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -1862,7 +1855,7 @@ dependencies = [ "leptos_actix", "leptos_meta", "leptos_router", - "prost 0.12.3", + "prost 0.12.6", "pulldown-cmark", "serde", "serde_qs", @@ -1889,9 +1882,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "fuse3" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a1df9aa50861c978c49522864d986093bb773558d431ea54c9e258ad3d6ce28" +checksum = "02ca1b211677ee014a10b94ab6aea31e622ad1ea35f48b1670ac492a4f88b1af" dependencies = [ "async-notify", "bincode", @@ -1906,7 +1899,7 @@ dependencies = [ "tokio", "tracing", "trait-make", - "which 6.0.1", + "which", ] [[package]] @@ -1985,11 +1978,11 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ - "fastrand 2.0.1", + "fastrand 2.1.0", "futures-core", "futures-io", "parking", @@ -2004,7 +1997,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -2065,9 +2058,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", @@ -2078,9 +2071,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "glob" @@ -2099,11 +2092,11 @@ dependencies = [ "gloo-events", "gloo-file", "gloo-history", - "gloo-net 0.5.0", + "gloo-net", "gloo-render", "gloo-storage", "gloo-timers 0.3.0", - "gloo-utils 0.2.0", + "gloo-utils", "gloo-worker", ] @@ -2113,7 +2106,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a17868f56b4a24f677b17c8cb69958385102fa879418052d60b50bc1727e261" dependencies = [ - "gloo-utils 0.2.0", + "gloo-utils", "js-sys", "serde", "wasm-bindgen", @@ -2160,7 +2153,7 @@ checksum = "903f432be5ba34427eac5e16048ef65604a82061fe93789f2212afc73d8617d6" dependencies = [ "getrandom", "gloo-events", - "gloo-utils 0.2.0", + "gloo-utils", "serde", "serde-wasm-bindgen", "serde_urlencoded", @@ -2169,26 +2162,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "gloo-net" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9902a044653b26b99f7e3693a42f171312d9be8b26b5697bd1e43ad1f8a35e10" -dependencies = [ - "futures-channel", - "futures-core", - "futures-sink", - "gloo-utils 0.1.7", - "js-sys", - "pin-project", - "serde", - "serde_json", - "thiserror", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "gloo-net" version = "0.5.0" @@ -2198,7 +2171,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-sink", - "gloo-utils 0.2.0", + "gloo-utils", "http 0.2.12", "js-sys", "pin-project", @@ -2226,7 +2199,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbc8031e8c92758af912f9bc08fbbadd3c6f3cfcbf6b64cdf3d6a81f0139277a" dependencies = [ - "gloo-utils 0.2.0", + "gloo-utils", "js-sys", "serde", "serde_json", @@ -2259,19 +2232,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "gloo-utils" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" -dependencies = [ - "js-sys", - "serde", - "serde_json", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "gloo-utils" version = "0.2.0" @@ -2293,7 +2253,7 @@ checksum = "085f262d7604911c8150162529cefab3782e91adb20202e8658f7275d2aefe5d" dependencies = [ "bincode", "futures", - "gloo-utils 0.2.0", + "gloo-utils", "gloo-worker-macros", "js-sys", "pinned", @@ -2313,7 +2273,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -2351,7 +2311,7 @@ dependencies = [ name = "grpc" version = "0.1.0" dependencies = [ - "prost 0.12.3", + "prost 0.12.6", "prost-wkt", "prost-wkt-build", "prost-wkt-types", @@ -2362,9 +2322,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -2372,7 +2332,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.2.5", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -2381,9 +2341,9 @@ dependencies = [ [[package]] name = "half" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ "cfg-if", "crunchy", @@ -2406,9 +2366,9 @@ checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash 0.8.11", "allocator-api2", @@ -2420,7 +2380,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -2432,6 +2392,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -2446,9 +2412,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hickory-proto" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "091a6fbccf4860009355e3efc52ff4acf37a63489aad7435372d44ceeb6fbbcf" +checksum = "07698b8420e2f0d6447a436ba999ec85d8fbf2a398bbd737b82cac4a2e96e512" dependencies = [ "async-trait", "cfg-if", @@ -2470,9 +2436,9 @@ dependencies = [ [[package]] name = "hickory-resolver" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35b8f021164e6a984c9030023544c57789c51760065cd510572fedcfb04164e8" +checksum = "28757f23aa75c98f254cf0405e6d8c25b831b32921b050a66692427679b1f243" dependencies = [ "cfg-if", "futures-util", @@ -2616,7 +2582,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.6", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -2632,7 +2598,7 @@ dependencies = [ "futures-util", "http 0.2.12", "hyper", - "rustls 0.21.10", + "rustls 0.21.12", "tokio", "tokio-rustls 0.24.1", ] @@ -2710,12 +2676,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -2749,14 +2715,14 @@ checksum = "0122b7114117e64a63ac49f752a5ca4624d534c7b1c7de796ac196381cd2d947" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] name = "instant" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", ] @@ -2799,7 +2765,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2 0.5.6", + "socket2 0.5.7", "widestring", "windows-sys 0.48.0", "winreg", @@ -2823,19 +2789,16 @@ dependencies = [ ] [[package]] -name = "itertools" -version = "0.10.5" +name = "is_terminal_polyfill" +version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" [[package]] name = "itertools" -version = "0.11.0" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] @@ -2851,15 +2814,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.28" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] @@ -2888,9 +2851,9 @@ dependencies = [ "lazy_static", "libc", "log", - "prost 0.12.3", + "prost 0.12.6", "prost-types", - "rustix 0.38.31", + "rustix 0.38.34", "serde", "spin 0.9.8", "tar", @@ -2946,9 +2909,9 @@ dependencies = [ [[package]] name = "leptos" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d079555ff18158a1ed28d2a8ac529b4cb5904490384064346eb2d321addde6" +checksum = "20f79fe71c41f5a0506c273f6698a1971bb994ef52a88aeaf4eccb159fcd1e11" dependencies = [ "cfg-if", "leptos_config", @@ -2966,18 +2929,18 @@ dependencies = [ [[package]] name = "leptos-use" -version = "0.10.4" +version = "0.10.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf01f0fa065f39b536cfe25c1b19d856aeb3f0b40dfa629b824f68e88c7f094a" +checksum = "3272d90b77cdbb99e9060f90eb6f5738e56128b2f912db57a50efb006a26e262" dependencies = [ "actix-web", "async-trait", "cfg-if", - "cookie 0.18.0", + "cookie 0.18.1", "default-struct-builder", "futures-util", "gloo-timers 0.3.0", - "gloo-utils 0.2.0", + "gloo-utils", "http 0.2.12", "js-sys", "lazy_static", @@ -2994,9 +2957,9 @@ dependencies = [ [[package]] name = "leptos_actix" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edb4789a15864a26d695038f42dcdf1d1c32d3a1f537751ce177c97f4f5e3dd" +checksum = "5be151f99f7fb3a220152d976d23f815da93ae5879cfe8e6fa921455d1ed597c" dependencies = [ "actix-http", "actix-web", @@ -3016,9 +2979,9 @@ dependencies = [ [[package]] name = "leptos_config" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d80b4ed5f0447996b9a28879002f995d3770687630f568be41307f362f84cb7" +checksum = "a3caa62f62e8e575051305ed6ac5648dc695f202c7220a98aca21cf4e9a978cf" dependencies = [ "config", "regex", @@ -3029,9 +2992,9 @@ dependencies = [ [[package]] name = "leptos_dom" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4b4da3cb6a4dde22e68717482a4b926fb5dd182c12461b27efa37764b29d9a" +checksum = "96e84abb02efd711f0842ff3e444292bfa9963811c37e7be3980a052628ed63b" dependencies = [ "async-recursion", "cfg-if", @@ -3039,7 +3002,7 @@ dependencies = [ "futures", "getrandom", "html-escape", - "indexmap 2.2.5", + "indexmap 2.2.6", "itertools 0.12.1", "js-sys", "leptos_reactive", @@ -3059,27 +3022,27 @@ dependencies = [ [[package]] name = "leptos_hot_reload" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb051c7b3bce8368ee30fb57e7b14cdcd573019ea6cd1858b9c697a3519ea099" +checksum = "c4ee917deba2522a7f22ca826df84a8800d66ac918e58b489875e1f4fb8bc6b8" dependencies = [ "anyhow", "camino", - "indexmap 2.2.5", + "indexmap 2.2.6", "parking_lot", "proc-macro2", "quote", "rstml", "serde", - "syn 2.0.52", + "syn 2.0.66", "walkdir", ] [[package]] name = "leptos_integration_utils" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff00799857159434d31b6bd1898e21c63f69f39289621da5a554fcab1c3e7300" +checksum = "a1f504afe3e2ac30ca15ba9b74d27243e8919e93d1f78bad32e5e8ec23eaca4b" dependencies = [ "futures", "leptos", @@ -3091,9 +3054,9 @@ dependencies = [ [[package]] name = "leptos_macro" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82c33c8baa07a36c1f0d6149af821be885e6863779bcb24954bf865ad8402b4" +checksum = "31197c2c624c405bec5f1dc8dd5d903a6030d1f0b8e362a01a3a215fcbad5051" dependencies = [ "attribute-derive", "cfg-if", @@ -3107,19 +3070,19 @@ dependencies = [ "quote", "rstml", "server_fn_macro", - "syn 2.0.52", + "syn 2.0.66", "tracing", "uuid", ] [[package]] name = "leptos_meta" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b9dac59a2f88f5235dbe17cfa81b738a6f47238a64e4f23b921f1a90a9bf11" +checksum = "a00900e82a4ca892828db93fce1d4c009480ff3959406e6965aa937c8bab7403" dependencies = [ "cfg-if", - "indexmap 2.2.5", + "indexmap 2.2.6", "leptos", "tracing", "wasm-bindgen", @@ -3128,18 +3091,17 @@ dependencies = [ [[package]] name = "leptos_reactive" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93bdcebc9822cc22a72cc9528dd794e1396152c75749ee09959f8272a8c99657" +checksum = "057de706568ce8f1f223ae69f796c10ad0563ad270d10717e70c2b2d22eefa60" dependencies = [ - "base64", + "base64 0.22.1", "cfg-if", "futures", - "indexmap 2.2.5", + "indexmap 2.2.6", "js-sys", "paste", "pin-project", - "rkyv", "rustc-hash", "self_cell", "serde", @@ -3156,13 +3118,13 @@ dependencies = [ [[package]] name = "leptos_router" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9460a5dc184fa05d8eb635b687ad3220d02d2d23d6f49c3bf146aa71e427f423" +checksum = "d7fcc2a95a20c8f41adb39770e65c48ffe33cd9503b83669c54edd9b33ba8aa8" dependencies = [ "cached", "cfg-if", - "gloo-net 0.2.6", + "gloo-net", "itertools 0.12.1", "js-sys", "lazy_static", @@ -3188,9 +3150,9 @@ dependencies = [ [[package]] name = "leptos_server" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "654b6ff6a24e79977641b5214452373b1e12fdf4c8a563fadf656c139694b4b9" +checksum = "8f197d9cbf7db3a09a5d6c561ad0547ad6bf4326bc6bc454171d5f6ee94f745a" dependencies = [ "inventory", "lazy_static", @@ -3204,9 +3166,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.154" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libm" @@ -3227,12 +3189,9 @@ dependencies = [ [[package]] name = "line-wrap" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" -dependencies = [ - "safemem", -] +checksum = "dd1bc4d24ad230d21fb898d1116b1801d7adfc449d42026475862ab48b11e70e" [[package]] name = "linear-map" @@ -3258,9 +3217,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "local-channel" @@ -3281,9 +3240,9 @@ checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -3304,7 +3263,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a83fb7698b3643a0e34f9ae6f2e8f0178c0fd42f8b59d493aa271ff3a5bf21" dependencies = [ - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -3318,21 +3277,21 @@ dependencies = [ [[package]] name = "manyhow" -version = "0.8.1" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516b76546495d933baa165075b95c0a15e8f7ef75e53f56b19b7144d80fd52bd" +checksum = "f91ea592d76c0b6471965708ccff7e6a5d277f676b90ab31f4d3f3fc77fade64" dependencies = [ "manyhow-macros", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] name = "manyhow-macros" -version = "0.8.1" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ba072c0eadade3160232e70893311f1f8903974488096e2eb8e48caba2f0cf1" +checksum = "c64621e2c08f2576e4194ea8be11daf24ac01249a4f53cd8befcbb7077120ead" dependencies = [ "proc-macro-utils", "proc-macro2", @@ -3372,9 +3331,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "memoffset" @@ -3422,9 +3381,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" dependencies = [ "adler", ] @@ -3443,17 +3402,16 @@ dependencies = [ [[package]] name = "multimap" -version = "0.8.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" [[package]] name = "native-tls" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ - "lazy_static", "libc", "log", "openssl", @@ -3483,7 +3441,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cfg-if", "cfg_aliases", "libc", @@ -3524,11 +3482,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -3567,9 +3524,9 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -3578,9 +3535,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -3604,9 +3561,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" -version = "0.32.2" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" dependencies = [ "memchr", ] @@ -3623,7 +3580,7 @@ version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cfg-if", "foreign-types", "libc", @@ -3640,7 +3597,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -3651,9 +3608,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.101" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", @@ -3669,7 +3626,7 @@ checksum = "1e32339a5dc40459130b3bd269e9892439f55b33e772d2a9d402a789baaf4e8a" dependencies = [ "futures-core", "futures-sink", - "indexmap 2.2.5", + "indexmap 2.2.6", "js-sys", "once_cell", "pin-project-lite", @@ -3789,11 +3746,11 @@ version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec4c6225c69b4ca778c0aea097321a64c421cf4577b331c61b229267edabb6f8" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -3816,9 +3773,9 @@ checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -3826,22 +3783,22 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.1", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pathdiff" @@ -3866,12 +3823,12 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "petgraph" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.2.5", + "indexmap 2.2.6", ] [[package]] @@ -3891,14 +3848,14 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -3919,12 +3876,12 @@ dependencies = [ [[package]] name = "piper" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +checksum = "464db0c665917b13ebb5d453ccdec4add5658ee1adc7affc7677615356a8afaf" dependencies = [ "atomic-waker", - "fastrand 2.0.1", + "fastrand 2.1.0", "futures-io", ] @@ -3957,12 +3914,12 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "plist" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5699cc8a63d1aa2b1ee8e12b9ad70ac790d65788cd36101fa37f87ea46c4cef" +checksum = "d9d34169e64b3c7a80c8621a48adaf44e0cf62c78a9b25dd9dd35f1881a17cf9" dependencies = [ - "base64", - "indexmap 2.2.5", + "base64 0.21.7", + "indexmap 2.2.6", "line-wrap", "quick-xml", "serde", @@ -3987,14 +3944,15 @@ dependencies = [ [[package]] name = "polling" -version = "3.5.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24f040dee2588b4963afb4e420540439d126f73fdacf4a9c486a96d840bac3c9" +checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" dependencies = [ "cfg-if", "concurrent-queue", + "hermit-abi", "pin-project-lite", - "rustix 0.38.31", + "rustix 0.38.34", "tracing", "windows-sys 0.52.0", ] @@ -4040,12 +3998,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.16" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -4104,9 +4062,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" dependencies = [ "unicode-ident", ] @@ -4119,7 +4077,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", "version_check", "yansi", ] @@ -4136,34 +4094,33 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes", - "prost-derive 0.12.3", + "prost-derive 0.12.6", ] [[package]] name = "prost-build" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes", - "heck", - "itertools 0.11.0", + "heck 0.5.0", + "itertools 0.12.1", "log", "multimap", "once_cell", "petgraph", "prettyplease", - "prost 0.12.3", + "prost 0.12.6", "prost-types", "regex", - "syn 2.0.52", + "syn 2.0.66", "tempfile", - "which 4.4.2", ] [[package]] @@ -4181,35 +4138,35 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.11.0", + "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] name = "prost-types" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" dependencies = [ - "prost 0.12.3", + "prost 0.12.6", ] [[package]] name = "prost-wkt" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d8ef9c3f0f1dab910d2b7e2c24a8e4322e122eba6d7a1921eeebcebbc046c40" +checksum = "5fb7ec2850c138ebaa7ab682503b5d08c3cb330343e9c94776612928b6ddb53f" dependencies = [ "chrono", "inventory", - "prost 0.12.3", + "prost 0.12.6", "serde", "serde_derive", "serde_json", @@ -4218,12 +4175,12 @@ dependencies = [ [[package]] name = "prost-wkt-build" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b31cae9a54ca84fee1504740a82eebf2479532905e106f63ca0c3bc8d780321" +checksum = "598b7365952c2ed4e32902de0533653aafbe5ae3da436e8e2335c7d375a1cef3" dependencies = [ - "heck", - "prost 0.12.3", + "heck 0.5.0", + "prost 0.12.6", "prost-build", "prost-types", "quote", @@ -4231,12 +4188,12 @@ dependencies = [ [[package]] name = "prost-wkt-types" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435be4a8704091b4c5fb1d79799de7f2dbff53af05edf29385237f8cf7ab37ee" +checksum = "1a8eadc2381640a49c1fbfb9f4a857794b4e5bf5a2cbc2d858cfdb74f64dcd22" dependencies = [ "chrono", - "prost 0.12.3", + "prost 0.12.6", "prost-build", "prost-types", "prost-wkt", @@ -4269,11 +4226,11 @@ dependencies = [ [[package]] name = "pulldown-cmark" -version = "0.10.0" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce76ce678ffc8e5675b22aa1405de0b7037e2fdf8913fea40d1926c6fe1e6e7" +checksum = "76979bea66e7875e7509c4ec5300112b316af87fa7a252ca91c448b32dfe3993" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "getopts", "memchr", "pulldown-cmark-escape", @@ -4282,15 +4239,15 @@ dependencies = [ [[package]] name = "pulldown-cmark-escape" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d8f9aa0e3cbcfaf8bf00300004ee3b72f74770f9cbac93f6928771f613276b" +checksum = "bd348ff538bc9caeda7ee8cad2d1d48236a1f443c1fa3913c6a02fe0043b1dd3" [[package]] name = "quanta" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ca0b7bac0b97248c40bb77288fc52029cf1459c0461ea1b05ee32ccf011de2c" +checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" dependencies = [ "crossbeam-utils", "libc", @@ -4318,46 +4275,46 @@ dependencies = [ [[package]] name = "quick_cache" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58c20af3800cee5134b79a3bd4a3d4b583c16ccfa5f53338f46400851a5b3819" +checksum = "b1380629287ed1247c1e0fcc6d43efdcec508b65382c9ab775cc8f3df7ca07b0" dependencies = [ "ahash 0.8.11", "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "parking_lot", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "quote-use" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7b5abe3fe82fdeeb93f44d66a7b444dedf2e4827defb0a8e69c437b2de2ef94" +checksum = "b393938dcaab992375d7b3df7887fa98cc91c2f3590598251e7c609e2b788139" dependencies = [ "quote", "quote-use-macros", - "syn 2.0.52", ] [[package]] name = "quote-use-macros" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ea44c7e20f16017a76a245bb42188517e13d16dcb1aa18044bc406cdc3f4af" +checksum = "71d8772387900c205780e2c240cfe4dd01355ab4f96a503d99bdf34ad73180ef" dependencies = [ "derive-where", + "proc-macro-utils", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -4407,11 +4364,11 @@ dependencies = [ [[package]] name = "raw-cpuid" -version = "11.0.1" +version = "11.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d86a7c4638d42c44551f4791a20e687dbb4c3de1f33c43dd71e355cd429def1" +checksum = "e29830cbb1290e404f24c73af91c5d8d631ce7e128691e9477556b540cd01ecd" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", ] [[package]] @@ -4423,16 +4380,25 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", +] + [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", "regex-automata 0.4.6", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -4452,9 +4418,15 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] +[[package]] +name = "regex-lite" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b661b2f27137bdbc16f00eda72866a92bb28af1753ffbd56744fb6e2e9cd8e" + [[package]] name = "regex-syntax" version = "0.6.29" @@ -4463,9 +4435,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "rend" @@ -4478,11 +4450,11 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.24" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ - "base64", + "base64 0.21.7", "bytes", "encoding_rs", "futures-core", @@ -4500,7 +4472,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.10", + "rustls 0.21.12", "rustls-pemfile 1.0.4", "serde", "serde_json", @@ -4588,7 +4560,7 @@ version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3120d9610f84b17da849f5cc8c089bb74299285515bb4a6114550bbc39ddb1e7" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "bstr", "libc", "num-traits", @@ -4667,16 +4639,16 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.52", + "syn 2.0.66", "syn_derive", "thiserror", ] [[package]] name = "rust_decimal" -version = "1.34.3" +version = "1.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39449a79f45e8da28c57c341891b69a183044b29518bb8f86dbac9df60bb7df" +checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" dependencies = [ "arrayvec", "borsh", @@ -4690,9 +4662,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" @@ -4725,22 +4697,22 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", - "linux-raw-sys 0.4.13", + "linux-raw-sys 0.4.14", "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.21.10" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring", @@ -4750,14 +4722,14 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.2" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", "ring", "rustls-pki-types", - "rustls-webpki 0.102.2", + "rustls-webpki 0.102.4", "subtle", "zeroize", ] @@ -4768,24 +4740,24 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64", + "base64 0.21.7", ] [[package]] name = "rustls-pemfile" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f48172685e6ff52a556baa527774f61fcaa884f59daf3375c62a3f1cd2549dab" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64", + "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.3.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ede67b28608b4c60685c7d54122d4400d90f62b40caee7700e700380a390fa8" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustls-webpki" @@ -4799,9 +4771,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.2" +version = "0.102.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" dependencies = [ "ring", "rustls-pki-types", @@ -4810,21 +4782,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" - -[[package]] -name = "ryu" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] -name = "safemem" -version = "0.3.3" +name = "ryu" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -4866,18 +4832,18 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bd3534a9978d0aa7edd2808dc1f8f31c4d0ecd31ddf71d997b3c98e9f3c9114" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] name = "sea-orm" -version = "0.12.14" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6632f499b80cc6aaa781b302e4c9fae663e0e3dcf2640e9d80034d5b10731efe" +checksum = "c8814e37dc25de54398ee62228323657520b7f29713b8e238649385dbe473ee0" dependencies = [ "async-stream", "async-trait", @@ -4903,9 +4869,9 @@ dependencies = [ [[package]] name = "sea-orm-cli" -version = "0.12.14" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465ea2308d4716837e9af4a2cff8e14c28135867a580bb93e9e03d408a3a6afb" +checksum = "620bc560062ae251b1366bde43b3f1508445cab5c2c8cbdb397034638ab1b357" dependencies = [ "async-std", "chrono", @@ -4923,37 +4889,37 @@ dependencies = [ [[package]] name = "sea-orm-codegen" -version = "0.12.14" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "515fb555cbbe586cd2c251a39fbc6d0e52a84b353dd63c4320205553b865ac81" +checksum = "6edc65d76c9a0d693611b8dafac12802406a426410f95beb63ae1ce69354a703" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "sea-query", - "syn 2.0.52", + "syn 2.0.66", "tracing", ] [[package]] name = "sea-orm-macros" -version = "0.12.14" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec13bfb4c4aef208f68dbea970dd40d13830c868aa8dcb4e106b956e6bb4f2fa" +checksum = "5e115c6b078e013aa963cc2d38c196c2c40b05f03d0ac872fe06b6e0d5265603" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "sea-bae", - "syn 2.0.52", + "syn 2.0.66", "unicode-ident", ] [[package]] name = "sea-orm-migration" -version = "0.12.14" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac734b6e5610c2764056cc8495fbc293cd1c8ebe084fdfb74c3b0cdaaff9bb92" +checksum = "ee8269bc6ff71afd6b78aa4333ac237a69eebd2cdb439036291e64fb4b8db23c" dependencies = [ "async-trait", "clap", @@ -5006,10 +4972,10 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25a82fcb49253abcb45cdcb2adf92956060ec0928635eb21b4f7a6d8f25ab0bc" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", "thiserror", ] @@ -5032,7 +4998,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f686050f76bffc4f635cda8aea6df5548666b830b52387e8bc7de11056d11e" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "syn 1.0.109", @@ -5061,11 +5027,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.2" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "core-foundation", "core-foundation-sys", "libc", @@ -5074,9 +5040,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -5084,15 +5050,15 @@ dependencies = [ [[package]] name = "self_cell" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bf37232d3bb9a2c4e641ca2a11d83b5062066f88df7fed36c28772046d65ba" +checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" [[package]] name = "semver" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "send_wrapper" @@ -5105,9 +5071,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.197" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] @@ -5125,20 +5091,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -5158,9 +5124,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" dependencies = [ "serde", ] @@ -5198,9 +5164,9 @@ dependencies = [ [[package]] name = "server_fn" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2955da1dc5fcd970c182ebf1089af6c5f19051e1f286a21f7b96490a49b7a531" +checksum = "536a5b959673643ee01e59ae41bf01425482c8070dee95d7061ee2d45296b59c" dependencies = [ "actix-web", "bytes", @@ -5208,7 +5174,7 @@ dependencies = [ "const_format", "dashmap", "futures", - "gloo-net 0.5.0", + "gloo-net", "http 1.1.0", "inventory", "js-sys", @@ -5229,26 +5195,26 @@ dependencies = [ [[package]] name = "server_fn_macro" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfdd051ef905fdb3da20942b0c52d536158d7489a724e14cc2fd47323e7ca91" +checksum = "064dd9b256e78bf2886774f265cc34d2aefdd05b430c58c78a69eceef21b5e60" dependencies = [ "const_format", "convert_case 0.6.0", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", "xxhash-rust", ] [[package]] name = "server_fn_macro_default" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "060af1def72353a779fcc184c53e1965d3055a38b9e827f2259b2bff2d9c371e" +checksum = "f4ad11700cbccdbd313703916eb8c97301ee423c4a06e5421b77956fdcb36a9f" dependencies = [ "server_fn_macro", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -5284,9 +5250,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -5328,9 +5294,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" @@ -5344,9 +5310,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -5399,9 +5365,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf" +checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" dependencies = [ "sqlx-core", "sqlx-macros", @@ -5412,9 +5378,9 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd" +checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" dependencies = [ "ahash 0.8.11", "async-io 1.13.0", @@ -5426,7 +5392,6 @@ dependencies = [ "chrono", "crc", "crossbeam-queue", - "dotenvy", "either", "event-listener 2.5.3", "futures-channel", @@ -5436,7 +5401,7 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap 2.2.5", + "indexmap 2.2.6", "log", "memchr", "native-tls", @@ -5444,7 +5409,7 @@ dependencies = [ "paste", "percent-encoding", "rust_decimal", - "rustls 0.21.10", + "rustls 0.21.12", "rustls-pemfile 1.0.4", "serde", "serde_json", @@ -5463,9 +5428,9 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5" +checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" dependencies = [ "proc-macro2", "quote", @@ -5476,15 +5441,14 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841" +checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" dependencies = [ "async-std", - "atomic-write-file", "dotenvy", "either", - "heck", + "heck 0.4.1", "hex", "once_cell", "proc-macro2", @@ -5504,14 +5468,14 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4" +checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" dependencies = [ "atoi", - "base64", + "base64 0.21.7", "bigdecimal", - "bitflags 2.4.2", + "bitflags 2.5.0", "byteorder", "bytes", "chrono", @@ -5551,14 +5515,14 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24" +checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" dependencies = [ "atoi", - "base64", + "base64 0.21.7", "bigdecimal", - "bitflags 2.4.2", + "bitflags 2.5.0", "byteorder", "chrono", "crc", @@ -5582,7 +5546,6 @@ dependencies = [ "rust_decimal", "serde", "serde_json", - "sha1", "sha2", "smallvec", "sqlx-core", @@ -5596,9 +5559,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490" +checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" dependencies = [ "atoi", "chrono", @@ -5628,13 +5591,13 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stringprep" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" dependencies = [ - "finl_unicode", "unicode-bidi", "unicode-normalization", + "unicode-properties", ] [[package]] @@ -5645,9 +5608,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" @@ -5674,9 +5637,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -5692,7 +5655,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -5714,7 +5677,7 @@ dependencies = [ "fnv", "once_cell", "plist", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", "serde", "serde_derive", "serde_json", @@ -5768,8 +5731,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", - "fastrand 2.0.1", - "rustix 0.38.31", + "fastrand 2.1.0", + "rustix 0.38.34", "windows-sys 0.52.0", ] @@ -5787,7 +5750,7 @@ name = "testsuit" version = "0.1.0" dependencies = [ "async-std", - "base64", + "base64 0.21.7", "chrono", "clap", "futures", @@ -5798,7 +5761,7 @@ dependencies = [ "indicatif-log-bridge", "log", "pretty_env_logger", - "prost 0.12.3", + "prost 0.12.6", "prost-types", "serde", "thiserror", @@ -5812,22 +5775,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -5862,9 +5825,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", @@ -5883,9 +5846,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", @@ -5908,9 +5871,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.36.0" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", @@ -5920,7 +5883,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.6", + "socket2 0.5.7", "tokio-macros", "windows-sys 0.48.0", ] @@ -5937,13 +5900,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -5952,7 +5915,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.10", + "rustls 0.21.12", "tokio", ] @@ -5962,16 +5925,16 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ - "rustls 0.22.2", + "rustls 0.22.4", "rustls-pki-types", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -5981,16 +5944,15 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -6007,21 +5969,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.10" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" +checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.6", + "toml_edit 0.22.13", ] [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" dependencies = [ "serde", ] @@ -6032,7 +5994,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", @@ -6045,22 +6007,22 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "toml_datetime", "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.22.6" +version = "0.22.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" +checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.5", + "winnow 0.6.9", ] [[package]] @@ -6071,7 +6033,7 @@ checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" dependencies = [ "async-trait", "axum", - "base64", + "base64 0.21.7", "bytes", "futures-core", "futures-util", @@ -6100,7 +6062,7 @@ dependencies = [ "async-stream", "async-trait", "axum", - "base64", + "base64 0.21.7", "bytes", "h2", "http 0.2.12", @@ -6109,8 +6071,8 @@ dependencies = [ "hyper-timeout", "percent-encoding", "pin-project", - "prost 0.12.3", - "rustls-pemfile 2.1.1", + "prost 0.12.6", + "rustls-pemfile 2.1.2", "rustls-pki-types", "tokio", "tokio-rustls 0.25.0", @@ -6131,7 +6093,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -6140,7 +6102,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc3b0e1cedbf19fdfb78ef3d672cb9928e0a91a9cb4629cc0c916e8cff8aaaa1" dependencies = [ - "base64", + "base64 0.21.7", "bytes", "http 0.2.12", "http-body", @@ -6160,7 +6122,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79bb296fba9974fbc7ea2f1364a661c0e7acb7ebfca648343e5a4ac44ec9a0ec" dependencies = [ - "base64", + "base64 0.21.7", "byteorder", "bytes", "futures-util", @@ -6204,7 +6166,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "bytes", "futures-core", "futures-util", @@ -6249,7 +6211,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -6343,7 +6305,7 @@ checksum = "96cbd06a7b648f1603e60d75d9ed295d096b340d30e9f9324f4b512b5d40cd92" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -6354,24 +6316,30 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typed-builder" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444d8748011b93cb168770e8092458cb0f8854f931ff82fdf6ddfbd72a9c933e" +checksum = "77739c880e00693faef3d65ea3aad725f196da38b22fdc7ea6ded6e1ce4d3add" dependencies = [ "typed-builder-macro", ] [[package]] name = "typed-builder-macro" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "563b3b88238ec95680aef36bdece66896eaa7ce3c0f1b4f39d38fb2435261352" +checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] +[[package]] +name = "typeid" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "059d83cc991e7a42fc37bd50941885db0888e34209f8cfd9aab07ddec03bc9cf" + [[package]] name = "typenum" version = "1.17.0" @@ -6399,7 +6367,7 @@ checksum = "ac73887f47b9312552aa90ef477927ff014d63d1920ca8037c6c1951eab64bb1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -6432,6 +6400,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-properties" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" + [[package]] name = "unicode-segmentation" version = "1.11.0" @@ -6440,9 +6414,9 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" [[package]] name = "unicode-xid" @@ -6493,9 +6467,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ "getrandom", "rand", @@ -6506,13 +6480,13 @@ dependencies = [ [[package]] name = "uuid-macro-internal" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abb14ae1a50dad63eaa768a458ef43d298cd1bd44951677bd10b732a9ba2a2d" +checksum = "9881bea7cbe687e36c9ab3b778c36cd0487402e270304e8b1296d5085303c1a2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -6529,9 +6503,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126e423afe2dd9ac52142e7e9d5ce4135d7e13776c529d27fd6bc49f19e3280b" +checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" [[package]] name = "vcpkg" @@ -6547,9 +6521,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "waker-fn" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" [[package]] name = "walkdir" @@ -6603,7 +6577,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", "wasm-bindgen-shared", ] @@ -6637,7 +6611,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6687,18 +6661,6 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix 0.38.31", -] - [[package]] name = "which" version = "6.0.1" @@ -6707,25 +6669,25 @@ checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7" dependencies = [ "either", "home", - "rustix 0.38.31", + "rustix 0.38.34", "winsafe", ] [[package]] name = "whoami" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fec781d48b41f8163426ed18e8fc2864c12937df9ce54c88ede7bd47270893e" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" dependencies = [ - "redox_syscall", + "redox_syscall 0.4.1", "wasite", ] [[package]] name = "widestring" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" [[package]] name = "winapi" @@ -6745,11 +6707,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -6764,7 +6726,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -6782,7 +6744,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -6802,17 +6764,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -6823,9 +6786,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -6835,9 +6798,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -6847,9 +6810,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -6859,9 +6828,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -6871,9 +6840,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -6883,9 +6852,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -6895,9 +6864,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" @@ -6910,9 +6879,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.5" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" +checksum = "86c949fede1d13936a99f14fafd3e76fd642b556dd2ce96287fbe2e0151bfac6" dependencies = [ "memchr", ] @@ -6949,8 +6918,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" dependencies = [ "libc", - "linux-raw-sys 0.4.13", - "rustix 0.38.31", + "linux-raw-sys 0.4.14", + "rustix 0.38.34", ] [[package]] @@ -6970,59 +6939,59 @@ dependencies = [ [[package]] name = "yansi" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c2861d76f58ec8fc95708b9b1e417f7b12fd72ad33c01fa6886707092dea0d3" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] name = "zstd" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110" +checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.0.0" +version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e" +checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" +version = "2.0.10+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" dependencies = [ "cc", "pkg-config", diff --git a/backend/src/controller/crypto.rs b/backend/src/controller/crypto.rs index bf6f4499..434aaffb 100644 --- a/backend/src/controller/crypto.rs +++ b/backend/src/controller/crypto.rs @@ -63,7 +63,7 @@ impl CryptoController { #[tracing::instrument(name = "crypto_hash_controller", level = "debug", skip_all)] pub fn hash(&self, src: &str) -> Vec { let mut hasher = Blake2b512::new(); - hasher.update(&[src.as_bytes(), self.salt.as_slice()].concat()); + hasher.update([src.as_bytes(), self.salt.as_slice()].concat()); let hashed = hasher.finalize(); hashed.to_vec() From 7bcdbd4aabbf99a6727a4ec302738e0ed9606944 Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Fri, 31 May 2024 16:43:51 +0800 Subject: [PATCH 38/38] cargo fmt --- judger/plugins/rlua-54/src/main.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/judger/plugins/rlua-54/src/main.rs b/judger/plugins/rlua-54/src/main.rs index d44b1c3f..e020e9aa 100644 --- a/judger/plugins/rlua-54/src/main.rs +++ b/judger/plugins/rlua-54/src/main.rs @@ -1,6 +1,9 @@ const LUA_SRC: &str = "/code.lua"; use std::{ - env::args, fs, io::{stdin, BufRead, Read}, process::exit + env::args, + fs, + io::{stdin, BufRead, Read}, + process::exit, }; use rlua::{prelude::*, Context, Lua, ToLua, Value, Variadic}; @@ -68,7 +71,7 @@ fn lua_read(ctx: Context, string: String) -> rlua::Result { } pub fn main() { - if args().len()!=1{ + if args().len() != 1 { return; } let lua = Lua::new(); @@ -77,7 +80,7 @@ pub fn main() { let write = ctx.create_function(lua_write).unwrap(); let read = ctx.create_function(lua_read).unwrap(); - let io_table= ctx.create_table().unwrap(); + let io_table = ctx.create_table().unwrap(); io_table.set("write", write).unwrap(); io_table.set("read", read).unwrap(); @@ -88,7 +91,7 @@ pub fn main() { let source = fs::read(crate::LUA_SRC).unwrap(); let code = ctx.load(&source); - if let Err(err)=code.exec(){ + if let Err(err) = code.exec() { eprintln!("{}", err); exit(1); }