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/.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/Cargo.lock b/Cargo.lock index 4deb4a5f..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", @@ -477,15 +479,26 @@ 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" +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]] @@ -534,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]] @@ -569,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", @@ -604,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" @@ -662,7 +667,7 @@ dependencies = [ name = "backend" version = "0.1.0" dependencies = [ - "base64", + "base64 0.21.7", "bincode", "blake2", "chrono", @@ -684,8 +689,8 @@ dependencies = [ "opentelemetry_sdk", "paste", "postcard", - "prost 0.12.3", - "prost-types 0.12.3", + "prost 0.12.6", + "prost-types", "quick_cache", "rand", "rand_hc", @@ -702,7 +707,7 @@ dependencies = [ "tokio-stream", "toml 0.7.8", "tonic 0.11.0", - "tonic-build 0.11.0", + "tonic-build", "tonic-web", "tower-http", "tracing", @@ -713,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", @@ -738,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" @@ -787,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", ] @@ -826,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", @@ -852,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", @@ -877,19 +885,29 @@ 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", ] +[[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" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytecheck" @@ -900,7 +918,6 @@ dependencies = [ "bytecheck_derive", "ptr_meta", "simdutf8", - "uuid", ] [[package]] @@ -922,9 +939,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" @@ -969,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]] @@ -1010,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", @@ -1020,7 +1038,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -1052,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", @@ -1069,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]] @@ -1104,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", ] @@ -1123,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]] @@ -1207,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", @@ -1243,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", ] @@ -1258,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", ] @@ -1285,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" @@ -1317,6 +1336,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" @@ -1329,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]] @@ -1353,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]] @@ -1378,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]] @@ -1394,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", @@ -1402,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" @@ -1412,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", @@ -1458,7 +1487,7 @@ checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -1546,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", ] @@ -1587,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", ] @@ -1600,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]] @@ -1627,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", @@ -1674,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", @@ -1695,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", ] @@ -1724,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" @@ -1739,10 +1769,16 @@ dependencies = [ ] [[package]] -name = "finl_unicode" -version = "1.2.0" +name = "filetime" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.4.1", + "windows-sys 0.52.0", +] [[package]] name = "fixedbitset" @@ -1752,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", @@ -1819,7 +1855,7 @@ dependencies = [ "leptos_actix", "leptos_meta", "leptos_router", - "prost 0.12.3", + "prost 0.12.6", "pulldown-cmark", "serde", "serde_qs", @@ -1828,7 +1864,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 +1880,28 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "fuse3" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02ca1b211677ee014a10b94ab6aea31e622ad1ea35f48b1670ac492a4f88b1af" +dependencies = [ + "async-notify", + "bincode", + "bytes", + "cstr", + "futures-channel", + "futures-util", + "libc", + "nix 0.28.0", + "serde", + "slab", + "tokio", + "tracing", + "trait-make", + "which", +] + [[package]] name = "futures" version = "0.3.30" @@ -1920,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", @@ -1939,7 +1997,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -2000,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", @@ -2013,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" @@ -2034,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", ] @@ -2048,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", @@ -2095,7 +2153,7 @@ checksum = "903f432be5ba34427eac5e16048ef65604a82061fe93789f2212afc73d8617d6" dependencies = [ "getrandom", "gloo-events", - "gloo-utils 0.2.0", + "gloo-utils", "serde", "serde-wasm-bindgen", "serde_urlencoded", @@ -2104,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" @@ -2133,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", @@ -2161,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", @@ -2194,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" @@ -2228,7 +2253,7 @@ checksum = "085f262d7604911c8150162529cefab3782e91adb20202e8658f7275d2aefe5d" dependencies = [ "bincode", "futures", - "gloo-utils 0.2.0", + "gloo-utils", "gloo-worker-macros", "js-sys", "pinned", @@ -2248,7 +2273,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -2286,20 +2311,20 @@ dependencies = [ name = "grpc" version = "0.1.0" dependencies = [ - "prost 0.12.3", + "prost 0.12.6", "prost-wkt", "prost-wkt-build", "prost-wkt-types", "serde", "tonic 0.11.0", - "tonic-build 0.11.0", + "tonic-build", ] [[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", @@ -2307,7 +2332,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.2.5", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -2316,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", @@ -2341,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", @@ -2355,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]] @@ -2367,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" @@ -2381,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", @@ -2405,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", @@ -2551,7 +2582,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.6", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -2567,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", ] @@ -2645,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]] @@ -2684,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", ] @@ -2734,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", @@ -2758,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", ] @@ -2786,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", ] @@ -2812,23 +2840,29 @@ dependencies = [ name = "judger" version = "0.1.0" dependencies = [ + "async-stream", + "bytes", "cgroups-rs", "derive_builder", "env_logger", + "fuse3", "futures-core", + "grpc", + "lazy_static", + "libc", "log", - "prost 0.12.3", - "prost-types 0.12.3", - "rustix 0.38.31", + "prost 0.12.6", + "prost-types", + "rustix 0.38.34", "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", @@ -2875,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", @@ -2895,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", @@ -2923,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", @@ -2945,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", @@ -2958,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", @@ -2968,7 +3002,7 @@ dependencies = [ "futures", "getrandom", "html-escape", - "indexmap 2.2.5", + "indexmap 2.2.6", "itertools 0.12.1", "js-sys", "leptos_reactive", @@ -2988,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", @@ -3020,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", @@ -3030,25 +3064,25 @@ dependencies = [ "html-escape", "itertools 0.12.1", "leptos_hot_reload", - "prettyplease 0.2.16", + "prettyplease", "proc-macro-error", "proc-macro2", "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", @@ -3057,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", @@ -3085,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", @@ -3117,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", @@ -3133,9 +3166,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libm" @@ -3156,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" @@ -3187,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" @@ -3210,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", @@ -3233,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]] @@ -3247,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", @@ -3301,9 +3331,18 @@ 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" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] [[package]] name = "migration" @@ -3342,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", ] @@ -3363,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", @@ -3403,10 +3441,11 @@ 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", + "memoffset", ] [[package]] @@ -3443,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", ] @@ -3486,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", @@ -3497,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", @@ -3523,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", ] @@ -3542,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", @@ -3559,7 +3597,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -3570,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", @@ -3588,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", @@ -3708,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]] @@ -3735,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", @@ -3745,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" @@ -3785,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]] @@ -3810,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" @@ -3838,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", ] @@ -3876,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", @@ -3906,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", ] @@ -3959,22 +3998,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.1.25" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 1.0.109", -] - -[[package]] -name = "prettyplease" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" -dependencies = [ - "proc-macro2", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -4033,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", ] @@ -4048,7 +4077,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", "version_check", "yansi", ] @@ -4065,56 +4094,33 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" -dependencies = [ - "bytes", - "prost-derive 0.12.3", -] - -[[package]] -name = "prost-build" -version = "0.11.9" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" 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", + "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 0.2.16", - "prost 0.12.3", - "prost-types 0.12.3", + "prettyplease", + "prost 0.12.6", + "prost-types", "regex", - "syn 2.0.52", + "syn 2.0.66", "tempfile", - "which", ] [[package]] @@ -4132,44 +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", -] - -[[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", + "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", @@ -4178,27 +4175,27 @@ 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", - "prost-build 0.12.3", - "prost-types 0.12.3", + "heck 0.5.0", + "prost 0.12.6", + "prost-build", + "prost-types", "quote", ] [[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-build 0.12.3", - "prost-types 0.12.3", + "prost 0.12.6", + "prost-build", + "prost-types", "prost-wkt", "prost-wkt-build", "regex", @@ -4229,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", @@ -4242,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", @@ -4278,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]] @@ -4367,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]] @@ -4383,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]] @@ -4412,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" @@ -4423,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" @@ -4438,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", @@ -4460,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", @@ -4542,6 +4554,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.5.0", + "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" @@ -4571,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", @@ -4594,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" @@ -4629,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", @@ -4654,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", ] @@ -4672,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" @@ -4703,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", @@ -4714,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" @@ -4770,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", @@ -4807,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", @@ -4827,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", @@ -4910,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", ] @@ -4936,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", @@ -4965,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", @@ -4978,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", @@ -4988,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" @@ -5009,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", ] @@ -5029,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", @@ -5062,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", ] @@ -5102,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", @@ -5112,7 +5174,7 @@ dependencies = [ "const_format", "dashmap", "futures", - "gloo-net 0.5.0", + "gloo-net", "http 1.1.0", "inventory", "js-sys", @@ -5133,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]] @@ -5188,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", ] @@ -5232,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" @@ -5248,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", @@ -5303,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", @@ -5316,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", @@ -5330,7 +5392,6 @@ dependencies = [ "chrono", "crc", "crossbeam-queue", - "dotenvy", "either", "event-listener 2.5.3", "futures-channel", @@ -5340,7 +5401,7 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap 2.2.5", + "indexmap 2.2.6", "log", "memchr", "native-tls", @@ -5348,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", @@ -5367,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", @@ -5380,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", @@ -5408,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", @@ -5455,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", @@ -5486,7 +5546,6 @@ dependencies = [ "rust_decimal", "serde", "serde_json", - "sha1", "sha2", "smallvec", "sqlx-core", @@ -5500,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", @@ -5532,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]] @@ -5549,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" @@ -5578,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", @@ -5596,7 +5655,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -5618,7 +5677,7 @@ dependencies = [ "fnv", "once_cell", "plist", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", "serde", "serde_derive", "serde_json", @@ -5654,6 +5713,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" @@ -5661,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", ] @@ -5680,7 +5750,7 @@ name = "testsuit" version = "0.1.0" dependencies = [ "async-std", - "base64", + "base64 0.21.7", "chrono", "clap", "futures", @@ -5691,13 +5761,13 @@ dependencies = [ "indicatif-log-bridge", "log", "pretty_env_logger", - "prost 0.12.3", - "prost-types 0.12.3", + "prost 0.12.6", + "prost-types", "serde", "thiserror", "toml 0.7.8", "tonic 0.11.0", - "tonic-build 0.11.0", + "tonic-build", "tonic-web", "tower", "uuid", @@ -5705,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]] @@ -5755,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", @@ -5776,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", @@ -5801,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", @@ -5813,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", ] @@ -5830,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]] @@ -5845,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", ] @@ -5855,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", @@ -5874,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]] @@ -5900,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", ] @@ -5925,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", @@ -5938,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]] @@ -5964,7 +6033,7 @@ checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" dependencies = [ "async-trait", "axum", - "base64", + "base64 0.21.7", "bytes", "futures-core", "futures-util", @@ -5993,7 +6062,7 @@ dependencies = [ "async-stream", "async-trait", "axum", - "base64", + "base64 0.21.7", "bytes", "h2", "http 0.2.12", @@ -6002,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", @@ -6014,30 +6083,17 @@ 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", + "syn 2.0.66", ] [[package]] @@ -6046,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", @@ -6066,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", @@ -6110,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", @@ -6155,7 +6211,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -6241,6 +6297,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.66", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -6249,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" @@ -6294,7 +6367,7 @@ checksum = "ac73887f47b9312552aa90ef477927ff014d63d1920ca8037c6c1951eab64bb1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", ] [[package]] @@ -6327,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" @@ -6335,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" @@ -6388,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", @@ -6401,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]] @@ -6424,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" @@ -6442,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" @@ -6498,7 +6577,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", "wasm-bindgen-shared", ] @@ -6532,7 +6611,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.66", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6584,31 +6663,31 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "which" -version = "4.4.2" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7" dependencies = [ "either", "home", - "once_cell", - "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" @@ -6628,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]] @@ -6647,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]] @@ -6665,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]] @@ -6685,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]] @@ -6706,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" @@ -6718,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" @@ -6730,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" @@ -6742,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" @@ -6754,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" @@ -6766,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" @@ -6778,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" @@ -6793,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", ] @@ -6810,6 +6896,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" @@ -6819,6 +6911,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.14", + "rustix 0.38.34", +] + [[package]] name = "xxhash-rust" version = "0.8.10" @@ -6836,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/Cargo.toml b/Cargo.toml index 8550e674..11a7e08c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "testsuit", "backend/migration", "grpc", + "judger/plugins/rlua-54", ] [workspace.dependencies] 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() 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/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..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.to_string() + self.trace_id, self.span_id, self.log_id ) } } diff --git a/frontend/src/components/select.rs b/frontend/src/components/select.rs index bb12262d..416ca614 100644 --- a/frontend/src/components/select.rs +++ b/frontend/src/components/select.rs @@ -1,7 +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..71b3dc26 100644 --- a/frontend/src/config.rs +++ b/frontend/src/config.rs @@ -1,5 +1,3 @@ -use std::sync::OnceLock; - use cfg_if::cfg_if; use leptos::*; use leptos_use::{utils::JsonCodec, *}; 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..1e1594b7 100644 --- a/frontend/src/main.rs +++ b/frontend/src/main.rs @@ -1,5 +1,3 @@ -use anyhow::Result; - #[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 d0c8d502..1e0cb2b6 100644 --- a/frontend/src/pages/login.rs +++ b/frontend/src/pages/login.rs @@ -49,15 +49,12 @@ pub fn Login() -> impl IntoView { }); let error_msg = move || { - submit.value()() - .map(|r| r.err()) - .flatten() - .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 a1d71601..4f317f71 100644 --- a/frontend/src/pages/problems.rs +++ b/frontend/src/pages/problems.rs @@ -1,12 +1,10 @@ -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}, diff --git a/judger/Cargo.toml b/judger/Cargo.toml index cd43421f..81047fae 100644 --- a/judger/Cargo.toml +++ b/judger/Cargo.toml @@ -6,19 +6,33 @@ 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" prost = { workspace = true } prost-types = { workspace = true } thiserror = "1.0.40" toml = { workspace = true } derive_builder = { workspace = true } +tar = "0.4.40" +grpc = { path = "../grpc" } +lazy_static = "1.4.0" +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"] [dependencies.rustix] version = "0.38.28" features = ["process", "thread"] + [dependencies.uuid] version = "1.6.1" features = ["serde"] @@ -32,6 +46,7 @@ features = [ "fs", "io-util", "parking_lot", + "signal" ] # TODO migrate to 10 @@ -55,6 +70,3 @@ features = ["mutex", "spin_mutex"] tempfile = "3.8.0" tower = "0.4.13" tower-layer = "0.3.2" - -[build-dependencies] -tonic-build = "0.9.2" 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/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/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/.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/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/.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/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/plugins/rlua-54/spec.toml b/judger/plugins/rlua-54/spec.toml index 72e99341..784d6a1f 100644 --- a/judger/plugins/rlua-54/spec.toml +++ b/judger/plugins/rlua-54/spec.toml @@ -1,21 +1,12 @@ -# memory in byte, time in microsecond +fs_limit = 3145728 +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" # 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] -lockdown = true -command = ["/rlua-54","compile"] -kernel_mem = 67108864 -user_mem = 268435456 -rt_time = 1000000 -cpu_time = 10000000 -total_time = 10000000 +command = ["/rlua-54","skip"] [judge] -command = ["/rlua-54","execute"] -kernel_mem = 67108864 -multiplier_memory = 6 # user_mem -rt_time = 1000000 -multiplier_cpu = 3 # cpu_time +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 1f24c94f..00000000 --- a/judger/plugins/rlua-54/src/compile.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::{ - fs, - io::{stdin, Read}, -}; - -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/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..e020e9aa 100644 --- a/judger/plugins/rlua-54/src/main.rs +++ b/judger/plugins/rlua-54/src/main.rs @@ -1,29 +1,99 @@ -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, + 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/config.rs b/judger/src/config.rs new file mode 100644 index 00000000..3db8ba58 --- /dev/null +++ b/judger/src/config.rs @@ -0,0 +1,108 @@ +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> { + 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, +} + +fn default_ratio_cpu() -> f64 { + 1.0 +} +fn default_ratio_memory() -> f64 { + 1.0 +} + +#[derive(Serialize, Deserialize, Default)] +pub struct Ratio { + #[serde(default = "default_ratio_cpu")] + pub cpu: f64, + #[serde(default = "default_ratio_memory")] + pub memory: f64, +} + +fn default_log() -> u8 { + 1 +} + +fn default_ratio() -> Ratio { + Ratio { + cpu: 1.0, + memory: 1024.0, + } +} + +fn default_memory() -> u64 { + 1024 * 1024 * 1024 +} + +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)] + pub accounting: Accounting, + #[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, + #[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/error.rs b/judger/src/error.rs new file mode 100644 index 00000000..808ab4ad --- /dev/null +++ b/judger/src/error.rs @@ -0,0 +1,44 @@ +use tonic::Status; + +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), + #[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/filesystem/adapter/error.rs b/judger/src/filesystem/adapter/error.rs new file mode 100644 index 00000000..f3c4d76e --- /dev/null +++ b/judger/src/filesystem/adapter/error.rs @@ -0,0 +1,62 @@ +/// 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")] + 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, + #[error("permission deny")] + PermissionDeny, + #[error("invalid argument")] + InvialdArg, + #[error("Already exist")] + AlreadyExist, +} + +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, + FuseError::Eof => libc::EOF, + FuseError::OutOfPermit => { + log::info!("out of resource"); + libc::ENOMEM + } + 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 + } + } + .into() + } +} diff --git a/judger/src/filesystem/adapter/fuse.rs b/judger/src/filesystem/adapter/fuse.rs new file mode 100644 index 00000000..543f4554 --- /dev/null +++ b/judger/src/filesystem/adapter/fuse.rs @@ -0,0 +1,579 @@ +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}; +use tokio::sync::Mutex as AsyncMutex; + +use crate::filesystem::entry::{Entry, BLOCKSIZE}; +use crate::filesystem::resource::Resource; +use crate::filesystem::table::{to_internal_path, AdjTable}; + +use super::{error::FuseError, handle::HandleTable, reply::*}; +use fuse3::{ + raw::{reply::*, *}, + 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>>, + resource: Arc, +} + +impl Filesystem +where + F: AsyncRead + AsyncSeek + Unpin + Send + Sync + 'static, +{ + /// Create a new filesystem + 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, + /// return a raw handle from `libfuse` + pub async fn raw_mount_with_path( + 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).force_readdir_plus(true); + + Session::new(mount_options) + .mount_with_unprivileged(self, path.as_ref()) + .await + } + /// 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_vec(content), + ); + } +} + +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 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, node.get_value(), node.get_id() as u64)) + } + } + 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, + 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(AsyncMutex::new(node.get_value().clone())); + Ok(ReplyOpen { fh, flags }) + } + } + fn open( + &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::IsDir.into()); + } + let fh = self + .handle_table + .add(AsyncMutex::new(node.get_value().clone())); + Ok(ReplyOpen { fh, flags }) + } + } + 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_first()); + + 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() + .filter_map(|inode| { + let node = tree.get(inode).unwrap(); + Some(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_first()); + + 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, + 2, + )), + ] + .into_iter() + .chain( + node.children() + .enumerate() + .filter_map(|(offset, inode)| { + let node = tree.get(inode).unwrap(); + Some(dir_entry_plus( + &req, + 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 session = self.handle_table.get(fh).ok_or(FuseError::HandleNotFound)?; + let mut lock = session.lock().await; + Ok(lock + .read(offset, size) + .await + .ok_or(Into::::into(FuseError::IsDir))? + .map(|data| ReplyData { data })?) + } + } + fn write( + &self, + req: Request, + inode: u64, + fh: u64, + offset: u64, + data: &[u8], + write_flags: u32, + flags: u32, + ) -> impl Future> + Send { + async move { + let session = self + .handle_table + .get(fh) + .ok_or(FuseError::HandleNotFound) + .unwrap(); + + Ok(Entry::write(session, offset, data, &self.resource) + .await + .ok_or_else(|| Into::::into(FuseError::IsDir))? + .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, + inode: u64, + mask: u32, + ) -> impl Future> + Send { + // FIXME: only allow current user to access + 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 node = tree.get(inode as usize).ok_or(FuseError::InvaildIno)?; + // FIXME: unsure about the inode + Ok(reply_attr(&req, node.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)) + } + } + // open and create fd + 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()) + .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(entry)); + + let inode = node.get_id() as u64; + let entry = node.get_value(); + Ok(reply_created(&req, entry, fh, flags, inode)) + } + } + 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) + .ok_or(FuseError::AlreadyExist)?; + let ino = node.get_id() as u64; + 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)] +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/handle.rs b/judger/src/filesystem/adapter/handle.rs new file mode 100644 index 00000000..124c2c1a --- /dev/null +++ b/judger/src/filesystem/adapter/handle.rs @@ -0,0 +1,42 @@ +use std::{ + collections::BTreeMap, + sync::{atomic::AtomicU64, Arc}, +}; + +use spin::Mutex; + +pub type FileHandle = u64; +/// Lookup table for file handles +pub struct HandleTable { + handle_generator: AtomicU64, + table: Mutex>>, +} + +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) -> FileHandle { + let handle = self + .handle_generator + .fetch_add(1, std::sync::atomic::Ordering::AcqRel); + log::trace!("allocate handle: {}", handle); + self.table.lock().insert(handle, Arc::new(entry)); + handle + } + /// Get an entry from the table + 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: 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 new file mode 100644 index 00000000..4cc87527 --- /dev/null +++ b/judger/src/filesystem/adapter/mod.rs @@ -0,0 +1,38 @@ +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::Debug) + .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/12").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/adapter/reply.rs b/judger/src/filesystem/adapter/reply.rs new file mode 100644 index 00000000..0d95f92f --- /dev/null +++ b/judger/src/filesystem/adapter/reply.rs @@ -0,0 +1,106 @@ +use std::{ffi::OsString, time::Duration}; + +use fuse3::{ + raw::{reply::*, Request}, + Timestamp, +}; +use tokio::io::{AsyncRead, AsyncSeek}; + +use crate::filesystem::{entry::Entry, entry::BLOCKSIZE}; + +const TTL: Duration = Duration::from_secs(0); + +pub fn dir_entry_plus( + req: &Request, + name: OsString, + entry: &Entry, + inode: u64, + offset: i64, +) -> DirectoryEntryPlus +where + F: AsyncRead + AsyncSeek + Send + Unpin + 'static, +{ + DirectoryEntryPlus { + inode, + generation: 0, + kind: entry.kind(), + name, + offset, + attr: file_attr(req, entry, inode), + entry_ttl: TTL, + attr_ttl: TTL, + } +} + +pub fn dir_entry(name: OsString, entry: &Entry, inode: u64) -> DirectoryEntry +where + F: AsyncRead + AsyncSeek + Send + Unpin + 'static, +{ + DirectoryEntry { + inode, + kind: entry.kind(), + name, + offset: 1, + } +} + +pub fn reply_attr(req: &Request, entry: &Entry, inode: u64) -> ReplyAttr +where + F: AsyncRead + AsyncSeek + Send + Unpin + 'static, +{ + ReplyAttr { + ttl: TTL, + attr: file_attr(req, &entry, inode), + } +} + +pub fn reply_entry(req: &Request, entry: &Entry, inode: u64) -> ReplyEntry +where + F: AsyncRead + AsyncSeek + Send + Unpin + 'static, +{ + ReplyEntry { + ttl: TTL, + attr: file_attr(req, &entry, inode), + generation: 0, + } +} + +pub fn file_attr(req: &Request, entry: &Entry, inode: u64) -> FileAttr +where + F: AsyncRead + AsyncSeek + Send + Unpin + 'static, +{ + FileAttr { + 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(), + perm: (libc::S_IRWXO | libc::S_IRWXG | libc::S_IRWXU) as u16, + nlink: 1, + uid: req.uid, + gid: req.gid, + rdev: 179 << 16 + 02, + blksize: BLOCKSIZE as u32, + } +} + +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, inode), + generation: 0, + fh, + flags, + } +} diff --git a/judger/src/filesystem/adapter/template.rs b/judger/src/filesystem/adapter/template.rs new file mode 100644 index 00000000..0822a64a --- /dev/null +++ b/judger/src/filesystem/adapter/template.rs @@ -0,0 +1,46 @@ +use std::path::Path; + +use tokio::{ + fs::File, + io::{AsyncRead, AsyncSeek}, +}; + +use crate::filesystem::{ + entry::{Entry, TarTree}, + table::{to_internal_path, AdjTable}, +}; + +use super::fuse::Filesystem; + +pub struct Template(AdjTable>) +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static; + +impl Template +where + F: AsyncRead + AsyncSeek + Unpin + Send + Sync + 'static, +{ + /// use template to create a filesystem + pub fn as_filesystem(&self, permit: u64) -> Filesystem { + Filesystem::new(self.0.clone(), permit) + } + /// read a file by path + pub async fn read_by_path(&self, path: impl AsRef) -> Option> { + 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() + } +} + +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.0)) + } +} diff --git a/judger/src/filesystem/dev.md b/judger/src/filesystem/dev.md new file mode 100644 index 00000000..cafdf6dd --- /dev/null +++ b/judger/src/filesystem/dev.md @@ -0,0 +1,117 @@ +## Module Layout + +- `table.rs`: adjacency table + - Tree data structure on vector + - Inode allocation by `MIN_ID + index` + - Should be use behind lock +- `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 -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 new file mode 100644 index 00000000..129fc1dd --- /dev/null +++ b/judger/src/filesystem/entry/mod.rs @@ -0,0 +1,148 @@ +mod ro; +mod rw; +mod tar; + +use self::{ro::TarBlock, rw::MemBlock}; +use bytes::Bytes; +use fuse3::FileType; +use std::{ + ffi::{OsStr, OsString}, + sync::Arc, +}; +use tokio::{ + io::{AsyncRead, AsyncSeek}, + sync::Mutex, +}; + +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; +} + +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 +#[derive(Debug, Default)] +pub enum Entry +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ + SymLink(OsString), + HardLink(u64), + #[default] + Directory, + TarFile(TarBlock), + MemFile(MemBlock), +} + +impl Clone for Entry +where + F: AsyncRead + AsyncSeek + Unpin + Send + '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::TarFile(arg0) => Self::TarFile(arg0.clone()), + Self::MemFile(arg0) => Self::MemFile(arg0.clone()), + } + } +} + +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()) + } + /// create a new file entry with content + pub fn new_file_with_vec(content: Vec) -> Self { + Self::MemFile(MemBlock::new(content)) + } + /// get kind of the file + pub(super) 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(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 { + Self::SymLink(x) => x.len() as u64, + Self::HardLink(_) => 0, + Self::Directory => 0, + Self::TarFile(x) => x.get_size() as u64, + Self::MemFile(x) => x.get_size(), + } + } + pub async fn read(&mut self, offset: u64, size: u32) -> Option> { + match self { + Self::TarFile(block) => Some(Ok(block.read(offset, size).await.unwrap())), + Self::MemFile(block) => Some(block.read(offset, size).await), + _ => None, + } + } + pub fn assume_tar_file(&self) -> Option<&TarBlock> { + match self { + Entry::TarFile(x) => Some(x), + _ => 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, + data: &[u8], + resource: &Resource, + ) -> 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) => Some(Err(std::io::Error::from(std::io::ErrorKind::Other))), + _ => 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/ro.rs b/judger/src/filesystem/entry/ro.rs new file mode 100644 index 00000000..541ffe4b --- /dev/null +++ b/judger/src/filesystem/entry/ro.rs @@ -0,0 +1,198 @@ +//! collection of entry +//! +//! In tar file, structure is like this: +//! | type | content | ... +//! +//! And we map each type of content to BTreeMap + +use std::{ + io::{self, SeekFrom}, + sync::Arc, +}; +use tokio::{ + io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt}, + sync::Mutex, +}; + +use super::{FuseReadTrait, BLOCKSIZE, MAX_READ_BLK}; + +/// 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 + F: AsyncRead + AsyncSeek + Unpin, +{ + file: Arc>, + start: u64, + size: u32, + cursor: u32, +} + +impl TarBlock +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + pub fn new(file: Arc>, start: u64, size: u32) -> Self { + log::trace!("new block: start={}, size={}", start, size); + Self { + file, + start, + size, + cursor: 0, + } + } + #[inline] + pub fn get_size(&self) -> u32 { + self.size + } + 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 { + file: Arc::new(Mutex::new(file)), + start, + size, + cursor: 0, + } + } + #[inline] + 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) -> u32 { + self.size - self.cursor + } +} + +impl FuseReadTrait for TarBlock +where + F: AsyncRead + AsyncSeek + Unpin + 'static, +{ + 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( + io::ErrorKind::UnexpectedEof, + "tar block out of bound", + ))?; + lock.seek(seek_from).await?; + + let mut buf = vec![0_u8; size]; + + if let Err(err) = lock.read_exact(&mut buf).await { + log::warn!("tarball change at runtime, result in error: {}", err); + } + + Ok(bytes::Bytes::from(buf)) + } +} + +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: 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 new file mode 100644 index 00000000..db85c2d4 --- /dev/null +++ b/judger/src/filesystem/entry/rw.rs @@ -0,0 +1,303 @@ +use std::{io, ops::Deref, sync::Arc}; +use tokio::sync::Mutex; + +use super::{FuseFlushTrait, FuseReadTrait, FuseWriteTrait, 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>>, + /// 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, +} + +impl MemBlock { + pub fn new(data: Vec) -> Self { + Self { + data: Arc::new(Mutex::new(data)), + cursor: 0, + 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 + } +} + +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", + )); + } + 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 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 self.cursor as usize > locked.len() { + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "mem block out of bound", + )); + } + 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: 0, + write_buffer: self.write_buffer.clone(), + } + } +} + +// 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 +// } +// } + +// 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 { +// 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 +// } +// } + +// 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())) +// } + +// 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 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 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 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 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 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 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 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 new file mode 100644 index 00000000..8152fc71 --- /dev/null +++ b/judger/src/filesystem/entry/tar.rs @@ -0,0 +1,133 @@ +use std::{ffi::OsString, io::Read, os::unix::ffi::OsStringExt, path::Path, sync::Arc}; + +#[cfg(test)] +use std::io::Cursor; +use tar::{Archive, EntryType}; +#[cfg(test)] +use tokio::io::BufReader; +use tokio::{ + fs::File, + io::{AsyncRead, AsyncSeek, Result}, + sync::Mutex, +}; + +use crate::filesystem::table::{to_internal_path, AdjTable}; + +use super::{ro::TarBlock, Entry}; + +pub struct TarTree(pub AdjTable>) +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static; + +impl Clone for TarTree +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Default for TarTree +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ + fn default() -> Self { + let mut tree = AdjTable::new(); + tree.insert_root(Entry::Directory); + Self(tree) + } +} + +impl TarTree +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ + async fn parse_entry( + &mut self, + entry: tar::Entry<'_, R>, + file: &Arc>, + ) -> Result<()> { + let path = entry.path()?; + let entry = match entry.header().entry_type() { + EntryType::Regular | EntryType::Continuous => { + let start = entry.raw_file_position(); + let size = entry.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(), + )), + EntryType::Directory => Entry::Directory, + x => { + log::warn!("unsupported entry type: {:?}", x); + return 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 { + 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; + + 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; + assert_eq!(entry.get_value().kind(), FileType::$kind); + }}; + } + + #[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); + } +} diff --git a/judger/src/filesystem/handle.rs b/judger/src/filesystem/handle.rs new file mode 100644 index 00000000..bdbb65ff --- /dev/null +++ b/judger/src/filesystem/handle.rs @@ -0,0 +1,40 @@ +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 { + #[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); + }); + } +} + +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.raw_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 new file mode 100644 index 00000000..84793854 --- /dev/null +++ b/judger/src/filesystem/mkdtemp.rs @@ -0,0 +1,52 @@ +use std::{ + ffi::{CString, OsStr}, + os::unix::ffi::OsStrExt, + path::{Path, PathBuf}, +}; + +use tokio::fs::remove_dir; + +/// A safe wrapper around [`libc::mkdtemp`] +pub struct MkdTemp(PathBuf); + +impl Drop for MkdTemp { + fn drop(&mut self) { + tokio::spawn(remove_dir(self.0.clone())); + } +} + +impl MkdTemp { + /// Create a new MkdTemp + pub fn new() -> Self { + Self(unsafe { Self::new_inner("/tmp/mdoj-fs-runtime-XXXXXX") }) + } + pub unsafe fn new_inner(template: &str) -> PathBuf { + let template = CString::new(template).unwrap(); + libc::mkdtemp(template.as_ptr() as *mut _); + 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() + } +} + +#[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 new file mode 100644 index 00000000..3393101d --- /dev/null +++ b/judger/src/filesystem/mod.rs @@ -0,0 +1,11 @@ +//! Filesystem module that is mountable(actuallly mount and +//! is accessible for user in this operation system) +mod adapter; +mod entry; +mod handle; +mod mkdtemp; +mod resource; +mod table; + +pub use adapter::{Filesystem, Template}; +pub use handle::MountHandle; diff --git a/judger/src/filesystem/resource.rs b/judger/src/filesystem/resource.rs new file mode 100644 index 00000000..4d7a0d73 --- /dev/null +++ b/judger/src/filesystem/resource.rs @@ -0,0 +1,21 @@ +use std::sync::atomic::{AtomicU64, Ordering}; +/// A resource counter +/// +/// unlike [`tokio::sync::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 { + None + } else { + Some(()) + } + } +} diff --git a/judger/src/filesystem/table.rs b/judger/src/filesystem/table.rs new file mode 100644 index 00000000..5cd60ab2 --- /dev/null +++ b/judger/src/filesystem/table.rs @@ -0,0 +1,345 @@ +use std::{ + collections::BTreeMap, + ffi::{OsStr, OsString}, + path::{Component, Path}, +}; + +const ID_MIN: usize = 1; +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), + Component::RootDir | Component::CurDir | Component::ParentDir => None, + Component::Normal(x) => Some(x), + }) + // .collect::>() +} + +/// A node on adjacency table +#[derive(Clone)] +struct Node { + parent_idx: usize, + value: V, + 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 +/// +/// Note that cloning the table would actually clone the WHOLE tree +#[derive(Clone)] +pub struct AdjTable { + by_id: Vec>, +} + +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 { + parent_idx: 0, + value, + children: BTreeMap::new(), + }); + NodeWrapperMut { table: self, idx } + } + /// 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 { + 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; + } + Some(NodeWrapper { + table: self, + 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; + } + Some(NodeWrapperMut { + table: self, + 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_first().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 }) + } + /// 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, + mut default_value: F, + ) -> NodeWrapperMut + where + F: FnMut() -> V, + { + 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]; + } else { + let new_idx = self.by_id.len(); + self.by_id.push(Node { + parent_idx: idx, + value: default_value(), + children: BTreeMap::new(), + }); + // FIXME! + idx = new_idx; + self.by_id[idx].children.insert(name, new_idx); + } + } + 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, + mut default_value: F, + value: V, + ) -> NodeWrapperMut + where + F: FnMut() -> V, + { + let mut idx = self.get_first().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: BTreeMap::new(), + }); + self.by_id[idx].children.insert(seg.to_os_string(), new_idx); + idx = new_idx; + } + } + self.by_id[idx].value = value; + NodeWrapperMut { table: self, idx } + } +} + +pub struct NodeWrapper<'a, V> { + table: &'a AdjTable, + idx: usize, +} + +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 + pub fn is_root(&self) -> bool { + self.idx == 0 + } + /// get parent node + 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, + }) + } + /// 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) -> 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)? + .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 { + table: self.table, + idx, + }) + } else { + None + } + } +} + +pub struct NodeWrapperMut<'a, V> { + table: &'a mut AdjTable, + idx: usize, +} + +impl<'a, V> NodeWrapperMut<'a, V> { + /// insert a node by component + 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, + value, + children: BTreeMap::new(), + }); + self.table.by_id[self.idx].children.insert(component, idx); + Some(NodeWrapperMut { + table: self.table, + idx, + }) + } + /// get id of the node + pub fn get_id(&self) -> usize { + NodeWrapper { + table: self.table, + idx: self.idx, + } + .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 { + table: self.table, + idx, + }) + } else { + 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] + .children + .iter() + .map(|(_, &idx)| idx + ID_MIN) + } +} + +#[cfg(test)] +mod test { + use std::os::unix::ffi::OsStrExt; + + 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).unwrap(); + + let c = b.insert(OsStr::new("c").into(), 3).unwrap(); + + 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_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(); + let l4 = table.get(l3).unwrap().children().next().unwrap(); + 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).unwrap(); // inode 2 + assert_eq!(a.get_id(), 2); + 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).unwrap(); // inode 4 + assert_eq!(b.get_id(), 4); + assert_eq!(b.get_value(), &2); + } +} 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/language/builder.rs b/judger/src/language/builder.rs new file mode 100644 index 00000000..d53f3e50 --- /dev/null +++ b/judger/src/language/builder.rs @@ -0,0 +1,173 @@ +use grpc::judger::{ + exec_result as execute_response, ExecResult as ExecuteResponse, JudgeResponse, JudgerCode, Log, +}; + +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, +} + +impl From for JudgeResponse { + fn from(value: JudgeResult) -> Self { + JudgeResponse { + status: Into::::into(value.status) as i32, + time: value.time, + memory: value.memory, + accuracy: 0, // FIXME: accuracy + } + } +} + +pub struct ExecuteResult { + pub status: StatusCode, + pub time: u64, + pub memory: u64, + pub output: Vec, +} + +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 { + 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: impl Iterator>) -> Self { + self.input = Some(input.collect()); + self + } + pub fn output(mut self, output: impl Iterator>) -> Self { + self.output = Some(output.collect()); + 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/mod.rs b/judger/src/language/mod.rs new file mode 100644 index 00000000..f0488862 --- /dev/null +++ b/judger/src/language/mod.rs @@ -0,0 +1,7 @@ +mod builder; +mod plugin; +mod spec; +mod stage; + +pub use builder::*; +pub use plugin::{Map, Plugin}; diff --git a/judger/src/language/plugin.rs b/judger/src/language/plugin.rs new file mode 100644 index 00000000..119837a8 --- /dev/null +++ b/judger/src/language/plugin.rs @@ -0,0 +1,183 @@ +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}, + io::{AsyncRead, AsyncSeek}, +}; +use uuid::Uuid; + +use crate::{filesystem::*, sandbox::Stat}; + +use super::{ + builder::*, + spec::Spec, + 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>> { + 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(); + 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); + } + } + Ok(plugins) +} + +pub struct Map(BTreeMap>) +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static; + +impl Map { + 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)) + } + pub fn get(&self, id: &Uuid) -> Option> { + self.0.get(id).cloned() + } + pub fn iter(&self) -> impl Iterator> { + self.0.values() + } +} + +impl JudgeResult { + fn compile_error() -> Self { + Self { + status: StatusCode::CompileError, + time: 0, + memory: 0, + } + } +} + +pub struct Plugin +where + F: AsyncRead + AsyncSeek + Unpin + Send + 'static, +{ + pub(super) spec: 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 { + 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!( + "sepc.toml not found in plugin {}", + path.as_ref().display() + )); + let spec = Arc::new(Spec::from_str(&spec_source.to_string_lossy())); + + Ok(Self { spec, template }) + } +} + +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?)) + } + 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.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 { + let compiler = self.as_compiler(args.source).await?; + let maybe_runner = compiler.compile().await?; + match maybe_runner { + Some(mut runner) => { + let executor = runner.stream((args.mem, args.cpu), args.input).await?; + Ok(executor.get_result()) + } + 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/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/mod.rs b/judger/src/language/spec/mod.rs new file mode 100644 index 00000000..b5573305 --- /dev/null +++ b/judger/src/language/spec/mod.rs @@ -0,0 +1,145 @@ +use std::{ffi::OsString, time::Duration}; + +use grpc::judger::LangInfo; +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, + 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: Stat, + judge_cpu_factor: CpuFactor, + judge_mem_factor: MemFactor, + judge_limit: (u64, Duration), + pub compile_command: Vec, + pub judge_command: Vec, + pub file: OsString, + pub info: LangInfo, +} + +impl Spec { + pub fn get_judge_limit(&self, cpu: u64, mem: u64) -> Stat { + 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 { + 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 { + info: LangInfo::from(&raw), + id: raw.id, + fs_limit: raw.fs_limit.unwrap(), + compile_limit: Stat { + cpu: Cpu { + kernel: raw.compile.rt_time.unwrap(), + user: raw.compile.cpu_time.unwrap(), + total: raw.compile.time.unwrap(), + }, + memory: Memory { + kernel: raw.compile.kernel_mem.unwrap(), + user: raw.compile.user_mem.unwrap(), + total: raw.compile.memory.unwrap(), + }, + output: raw.compile.output_limit.unwrap(), + walltime: Duration::from_nanos(raw.compile.walltime.unwrap()), + }, + 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(), + 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.output.unwrap(), + Duration::from_nanos(raw.judge.walltime.unwrap()), + ), + } + } +} 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), + } + } +} diff --git a/judger/src/language/stage/compile.rs b/judger/src/language/stage/compile.rs new file mode 100644 index 00000000..86aef0c7 --- /dev/null +++ b/judger/src/language/stage/compile.rs @@ -0,0 +1,74 @@ +use core::time; +use std::{path::PathBuf, sync::Arc, time::Duration}; + +use crate::{ + filesystem::MountHandle, + language::spec::Spec, + sandbox::{Context, Limit, Process}, + Result, +}; + +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, +} + +impl Compiler { + pub fn new(spec: Arc, handle: MountHandle) -> Self { + Self { spec, handle } + } + pub async fn compile(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::trace!("compile failed, corpse: {:?}", corpse); + // tokio::time::sleep(Duration::from_secs(600)).await; + return Ok(None); + } + + let runner = Runner::new(self.handle, self.spec); + Ok(Some(runner)) + } +} + +/// Context for compile stage +struct CompileCtx { + spec: Arc, + path: PathBuf, +} + +impl Limit for CompileCtx { + fn get_cpu(&mut self) -> crate::sandbox::Cpu { + self.spec.compile_limit.cpu.clone() + } + fn get_memory(&mut self) -> crate::sandbox::Memory { + self.spec.compile_limit.memory.clone() + } + fn get_output(&mut self) -> u64 { + self.spec.compile_limit.output + } + fn get_walltime(&mut self) -> Duration { + self.spec.compile_limit.walltime + } +} + +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..805a9303 --- /dev/null +++ b/judger/src/language/stage/judge.rs @@ -0,0 +1,117 @@ +use std::sync::Arc; + +use crate::{ + 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, +} + +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 { + 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_code(&self, output: &[u8], mode: AssertionMode) -> StatusCode { + match self.corpse.status() { + Ok(status) => match status.success() { + true => self.assert_output(output, mode), + 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, 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 new file mode 100644 index 00000000..4bd29616 --- /dev/null +++ b/judger/src/language/stage/mod.rs @@ -0,0 +1,76 @@ +//! 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, + WrongAnswer, + RuntimeError, + TimeLimitExceeded, + MemoryLimitExceeded, + OutputLimitExceeded, + RealTimeLimitExceeded, + CompileError, + 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, +} + +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, + } + } +} + +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/language/stage/run.rs b/judger/src/language/stage/run.rs new file mode 100644 index 00000000..2474969d --- /dev/null +++ b/judger/src/language/stage/run.rs @@ -0,0 +1,73 @@ +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, stream::Streamer}; + +/// Second stage of the language process, run the compiled code +pub struct Runner { + filesystem: MountHandle, + spec: Arc, +} + +impl Runner { + pub fn new(filesystem: MountHandle, spec: Arc) -> Self { + Self { filesystem, spec } + } + 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(), + limit: self.spec.get_judge_limit(cpu, mem), + }; + let process = Process::new(ctx)?; + 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 { + 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/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 17fc3614..b9a82e67 100644 --- a/judger/src/main.rs +++ b/judger/src/main.rs @@ -1,30 +1,40 @@ -use std::sync::Arc; +mod config; +mod error; +mod filesystem; +mod language; +mod sandbox; +mod server; -use grpc::prelude::judger_server::JudgerServer; -use init::config::CONFIG; +pub use config::CONFIG; -pub mod grpc; -pub mod init; -pub mod langs; -pub mod sandbox; -pub mod server; -#[cfg(test)] -pub mod tests; +use grpc::judger::judger_server::JudgerServer; +use server::Server; + +type Result = std::result::Result; #[tokio::main] async fn main() { - init::new().await; + // FIXME: use CONFIG for logging + env_logger::Builder::from_default_env() + .filter_level(log::LevelFilter::Debug) + .try_init() + .ok(); - let config = CONFIG.get().unwrap(); - let addr = config.runtime.bind.parse().unwrap(); + 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); + })); - log::info!("Server started"); + #[cfg(debug_assertions)] + log::warn!("running debug build"); - let server = server::Server::new().await; + let server = Server::new().await.unwrap(); tonic::transport::Server::builder() - .add_service(JudgerServer::new(Arc::new(server))) - .serve(addr) + .add_service(JudgerServer::new(server)) + .serve(CONFIG.address) .await .unwrap(); } 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/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/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/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 bac99a13..5e529bd8 100644 --- a/judger/src/sandbox/mod.rs +++ b/judger/src/sandbox/mod.rs @@ -1,50 +1,58 @@ -pub(super) mod container; -pub(super) mod daemon; -pub(super) mod process; -pub(super) mod utils; +mod error; +mod macro_; +mod monitor; +mod process; -use thiserror::Error; +use std::{ + ffi::OsStr, + path::{Path, PathBuf}, + time::Duration, +}; -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; +pub use self::monitor::{Cpu, Memory, Stat}; +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 get_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_output(&mut self) -> u64; + fn get_walltime(&mut self) -> Duration { + Duration::from_secs(60 * 30) + } } -#[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, +pub trait Filesystem { + fn get_path(&mut self) -> impl AsRef + Send; } -// const NICE: i32 = 18; +impl Filesystem for PathBuf { + fn get_path(&mut self) -> impl AsRef + Send { + self.as_path().iter() + } +} -#[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, +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 new file mode 100644 index 00000000..7201084b --- /dev/null +++ b/judger/src/sandbox/monitor/hier.rs @@ -0,0 +1,38 @@ +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, +} + +lazy_static::lazy_static! { + pub static ref MONITER_KIND: MonitorKind = { + let kind=match crate::CONFIG.accounting { + Accounting::Auto =>match hierarchies::auto().v2(){ + true=>MonitorKind::Cpu, + false=>MonitorKind::CpuAcct + }, + 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 { + /// get the hierarchy(cgroup v1/v2) of monitor + pub fn heir(&self) -> Box { + match self { + MonitorKind::Cpu => hierarchies::auto(), + MonitorKind::CpuAcct => Box::new(hierarchies::V1::new()), + } + } +} diff --git a/judger/src/sandbox/monitor/mem_cpu.rs b/judger/src/sandbox/monitor/mem_cpu.rs new file mode 100644 index 00000000..ac12a199 --- /dev/null +++ b/judger/src/sandbox/monitor/mem_cpu.rs @@ -0,0 +1,141 @@ +use crate::async_loop; + +use super::{stat::*, *}; +use cgroups_rs::{cgroup_builder::CgroupBuilder, Cgroup}; +use std::sync::{atomic::Ordering, Arc}; +use tokio::{select, time::*}; + +/// maximum allow time deviation for cpu monitor +pub const MONITOR_ACCURACY: Duration = Duration::from_millis(80); + +const CG_PATH_COUNTER: AtomicUsize = AtomicUsize::new(0); + +async fn monitor(cgroup: Arc, cpu: Cpu) -> MonitorKind { + let wrapper = wrapper::CgroupWrapper::new(&cgroup); + + let oom_signal = wrapper.oom_signal(); + + let cpu_future = async_loop!({ + if Cpu::out_of_resources(&cpu, wrapper.cpu()) { + break; + } + }); + + select! { + _ = cpu_future=>{ + return MonitorKind::Cpu; + }, + _ = oom_signal.wait()=>{ + return MonitorKind::Memory; + } + } +} + +/// monitor resource of cpu and memory +pub struct Monitor { + cgroup: Arc, + cpu: Cpu, + monitor_task: Option>, +} + +impl Drop for Monitor { + fn drop(&mut self) { + if let Some(monitor_task) = &self.monitor_task { + monitor_task.abort(); + } + // 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(); + } + } + } +} + +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() + .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())?, + ); + // 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, + 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`) + /// 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`]. + /// + /// This method is cancellation safe + async fn wait_exhaust(&mut self) -> MonitorKind { + 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 + } + fn poll_exhaust(&mut self) -> Option { + 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`]. + 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.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..667be203 --- /dev/null +++ b/judger/src/sandbox/monitor/mod.rs @@ -0,0 +1,176 @@ +//! 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; + +use std::{fmt::Display, sync::atomic::AtomicUsize, time::Duration}; + +pub use stat::*; +use tokio::io::AsyncRead; + +use hier::*; + +use self::output::Output; + +use super::Error; + +lazy_static::lazy_static! { + pub static ref CGROUP_V2:bool=hier::MONITER_KIND.heir().v2(); +} + +pub trait Monitor { + type Resource; + /// wait for exhuast of resource + /// + /// This function is cancel safe. + async fn wait_exhaust(&mut self) -> MonitorKind { + // 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; + } + 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; +} + +/// 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", + } + ) + } +} + +/// 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, Error> { + 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..5cac3c7c --- /dev/null +++ b/judger/src/sandbox/monitor/output.rs @@ -0,0 +1,92 @@ +use std::{pin::Pin, task::*}; + +use futures_core::Future; +use tokio::io::*; + +use crate::sandbox::monitor::MonitorKind; + +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 { + log::info!("Output limit: {}", limit); + 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/monitor/stat.rs b/judger/src/sandbox/monitor/stat.rs new file mode 100644 index 00000000..1de40700 --- /dev/null +++ b/judger/src/sandbox/monitor/stat.rs @@ -0,0 +1,162 @@ +use std::{ + ops::{Add, AddAssign, Div, Mul}, + time::Duration, +}; + +use cgroups_rs::cpuacct::CpuAcct; + +use super::output::Output; + +pub type MemAndCpu = (Memory, Cpu); + +/// statistics of resource usage +#[derive(Clone, Default, Debug)] +pub struct Stat { + pub memory: Memory, + pub cpu: Cpu, + pub output: Output, + 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, Default, Debug)] +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; + + 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) + } +} + +/// cpu usage(in nanoseconds) +#[derive(Clone, Default, Debug)] +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; + + 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 + } + + 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, 42973408000); + assert_eq!(cpu.user, 115998852000); + 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..cad038d2 --- /dev/null +++ b/judger/src/sandbox/monitor/walltime.rs @@ -0,0 +1,40 @@ +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/monitor/wrapper.rs b/judger/src/sandbox/monitor/wrapper.rs new file mode 100644 index 00000000..fbf396b7 --- /dev/null +++ b/judger/src/sandbox/monitor/wrapper.rs @@ -0,0 +1,90 @@ +use crate::async_loop; + +use super::{hier::*, stat::*}; +use cgroups_rs::{cpu::CpuController, cpuacct::CpuAcctController, memory::MemController, Cgroup}; +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> { + cgroup: &'a Cgroup, +} + +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::CpuAcct => { + let controller: &CpuAcctController = self.cgroup.controller_of().unwrap(); + Cpu::from_acct(controller.cpuacct()) + } + MonitorKind::Cpu => { + let controller: &CpuController = self.cgroup.controller_of().unwrap(); + let raw: &str = &controller.cpu().stat; + Cpu::from_raw(raw) + } + } + } + /// get an receiver(synchronize) for oom event + pub fn oom_signal(&self) -> OOMSignal { + let controller = self.cgroup.controller_of::().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 { + let controller = self.cgroup.controller_of::().unwrap(); + let kusage = controller.kmem_stat(); + + let kernel = kusage.max_usage_in_bytes; + let user = controller.memory_stat().max_usage_in_bytes; + let total = kernel + user; + + Memory { + kernel, + user, + 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.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/process/corpse.rs b/judger/src/sandbox/process/corpse.rs new file mode 100644 index 00000000..c71e6cc2 --- /dev/null +++ b/judger/src/sandbox/process/corpse.rs @@ -0,0 +1,42 @@ +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, + /// 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 + } + 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 new file mode 100644 index 00000000..8dcf3834 --- /dev/null +++ b/judger/src/sandbox/process/mod.rs @@ -0,0 +1,23 @@ +//! 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; + +use super::*; + +pub use corpse::*; +pub use process::*; diff --git a/judger/src/sandbox/process/nsjail.rs b/judger/src/sandbox/process/nsjail.rs new file mode 100644 index 00000000..1b1c98c6 --- /dev/null +++ b/judger/src/sandbox/process/nsjail.rs @@ -0,0 +1,135 @@ +use std::{ + borrow::Cow, + ffi::{OsStr, OsString}, + ops::Deref, + os::unix::ffi::OsStrExt, + path::Path, +}; + +pub static NSJAIL_PATH: &str = "./nsjail-3.1"; + +pub trait Argument { + fn get_args(self) -> impl Iterator>; +} + +/// factory pattern for conbinating arguments +#[derive(Default)] +pub struct ArgFactory { + args: Vec>, +} + +impl ArgFactory { + pub fn add(mut self, arg: impl Argument) -> Self { + self.args.extend(arg.get_args()); + self + } + + pub fn build(self) -> Vec { + self.args.into_iter().map(|x| x.into_owned()).collect() + } +} + +/// base auguments for nsjail +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"--env")), + Cow::Borrowed(OsStr::from_bytes( + b"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + )), + ] + .into_iter() + } +} + +/// arguments for setting cgroup +pub struct CGroupMountArg<'a> { + pub cg_name: &'a str, +} + +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"--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")), + 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() + } +} + +/// arguments for setting cgroup version +pub struct CGroupVersionArg; + +impl Argument for CGroupVersionArg { + 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() + } +} + +/// arguments for rootfs mount +pub struct MountArg<'a> { + 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)), + ] + .into_iter() + } +} + +/// arguments for launching inner process +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..3c5f05cb --- /dev/null +++ b/judger/src/sandbox/process/process.rs @@ -0,0 +1,172 @@ +use super::{corpse::Corpse, error::Error, monitor::*, nsjail::*, Context, Filesystem}; +use std::{ + ffi::{OsStr, OsString}, + os::unix::ffi::OsStrExt, + path::{Path, PathBuf}, + process::Stdio, +}; +use tokio::{ + io::{self, AsyncWriteExt, DuplexStream}, + process::*, + time, +}; +/// A unlaunched process that is mounted with a filesystem +struct MountedProcess { + context: C, + fs: C::FS, +} + +impl MountedProcess { + fn new(mut context: C) -> Self { + Self { + fs: context.get_fs(), + context, + } + } +} + +/// a monitored process +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(); + 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, + } + } +} + +/// A running process +pub struct Process { + fs: C::FS, + context: C, + monitor: StatMonitor, + 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) + } + 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 = [root.as_ref().as_os_str(), jail].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); + 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() + .add(BaseArg) + .add(CGroupVersionArg) + .add(CGroupMountArg { + cg_name: self.monitor.get_cg_path(), + }) + .add(MountArg { + rootfs: self.fs.get_path().as_ref(), + }) + .add(InnerProcessArg { + inner_args: self.context.get_args(), + }); + + let args = arg_factory.build(); + + log::trace!("spawn process with args: {:?}", args); + cmd.args(args); + + 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()?; + + let mut stdin = process.stdin.take().unwrap(); + tokio::spawn(async move { stdin.write_all(&input).await }); + + let stdout = process.stdout.take().unwrap(); + 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); + } + }); + + 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?) + } + }; + // 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, + reason: monitor.poll_exhaust(), + stdout: monitor.take_buffer(), + stat: monitor.stat().await, + }) + } +} 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 index 7270162d..b4806b35 100644 --- a/judger/src/server.rs +++ b/judger/src/server.rs @@ -1,68 +1,29 @@ -use std::{pin::Pin, sync::Arc}; - -use spin::Mutex; -use tokio::sync::mpsc::*; -use tokio_stream::wrappers::ReceiverStream; -use tonic::{Response, Status}; +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::{ - init::config::CONFIG, - langs::prelude::{ArtifactFactory, CompileLog}, + error::{ClientError, Error}, + language::{ExecuteArgBuilder, JudgeArgBuilder, Map}, + CONFIG, }; -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, - })), - } - } -} +const PLUGIN_PATH: &str = "plugins"; -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() { + 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 ", config.secret.as_ref().unwrap()] - .concat() - .into_bytes(); + let secret = ["basic ", secret].concat().into_bytes(); let vaild = header .as_bytes() .iter() @@ -76,252 +37,133 @@ fn check_secret(req: tonic::Request) -> Result { Err(Status::permission_denied("Invalid secret")) } -/// Server to serve JudgerSet pub struct Server { - factory: ArtifactFactory, - running: Mutex, + semaphore: Arc, + plugins: Map, } 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?; - } + 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 }) } - - Ok(()) } #[tonic::async_trait] -impl Judger for Arc { - type JudgeStream = - Pin> + Send>>; +impl Judger for Server { + type JudgeStream = Pin> + Send>>; - /// see judger.proto async fn judge( &self, - req: tonic::Request, + req: 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(); - }; + 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); - }); - - 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(); - + let list = self + .plugins + .iter() + .map(|v| v.get_info().clone()) + .collect::>(); Ok(Response::new(JudgeInfo { - langs: Langs { list: modules }, - memory: config.platform.available_memory, - accuracy: accuracy(), - cpu_factor: config.platform.cpu_time_multiplier as f32, + memory: CONFIG.memory, + accuracy: 0, // FIXME: accuracy + langs: Langs { list }, + cpu_factor: CONFIG.ratio.cpu as f32, })) } - type ExecStream = Pin> + Send>>; + type ExecStream = tokio_stream::Once>; - /// see judger.proto async fn exec( &self, - req: tonic::Request, + req: 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)))) + 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())))) } } 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; -} 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 diff --git a/judger/test/nested.tar b/judger/test/nested.tar new file mode 100644 index 00000000..71b83b18 Binary files /dev/null and b/judger/test/nested.tar differ diff --git a/judger/test/single_file.tar b/judger/test/single_file.tar new file mode 100644 index 00000000..0d29253d Binary files /dev/null and b/judger/test/single_file.tar differ