From a9b13ddfe043dc5557db248e2ca64cae5ced3c9b Mon Sep 17 00:00:00 2001 From: Eason <30045503+Eason0729@users.noreply.github.com> Date: Sat, 24 Aug 2024 11:33:38 +0800 Subject: [PATCH] Add tag and clippy (#73) * add tag * manual clippy * manual clippy * remove unused TYPE_NUMBER * manual clippy * manual clippy * manual clippy * cargo fmt --- backend/src/controller/judger/mod.rs | 13 +- backend/src/endpoint/announcement.rs | 4 +- backend/src/endpoint/chat.rs | 2 +- backend/src/endpoint/mod.rs | 2 +- backend/src/endpoint/problem.rs | 34 ++- backend/src/endpoint/testcase.rs | 2 +- backend/src/endpoint/token.rs | 2 - backend/src/entity/announcement.rs | 8 - backend/src/entity/chat.rs | 2 - backend/src/entity/contest.rs | 4 - backend/src/entity/education.rs | 6 - backend/src/entity/problem.rs | 151 +++++++--- backend/src/entity/submit.rs | 4 - backend/src/entity/testcase.rs | 4 - backend/src/entity/token.rs | 6 +- backend/src/entity/user.rs | 4 - backend/src/entity/util/filter.rs | 6 +- backend/src/entity/util/helper.rs | 60 ++-- backend/src/entity/util/paginator.rs | 1 - backend/src/util/auth.rs | 8 +- backend/src/util/bound.rs | 28 +- backend/src/util/error.rs | 2 +- grpc/proto/backend.proto | 11 +- grpc/proto/judger.proto | 16 ++ judger/src/config.rs | 3 + judger/src/filesystem/adapter/fuse.rs | 395 +++++++++++++------------- judger/src/filesystem/entry/ro.rs | 79 +----- judger/src/filesystem/entry/rw.rs | 223 +-------------- judger/src/filesystem/mod.rs | 2 +- judger/src/filesystem/table.rs | 28 +- judger/src/language/mod.rs | 2 +- judger/src/language/plugin.rs | 4 +- judger/src/language/spec/compile.rs | 1 - judger/src/language/spec/mod.rs | 2 - judger/src/language/stage/compile.rs | 4 +- judger/src/language/stage/judge.rs | 4 +- judger/src/language/stage/mod.rs | 2 + judger/src/language/stage/run.rs | 2 +- judger/src/language/stage/stream.rs | 9 +- judger/src/sandbox/monitor/mem_cpu.rs | 3 +- 40 files changed, 496 insertions(+), 647 deletions(-) diff --git a/backend/src/controller/judger/mod.rs b/backend/src/controller/judger/mod.rs index e1a9dd31..99709cad 100644 --- a/backend/src/controller/judger/mod.rs +++ b/backend/src/controller/judger/mod.rs @@ -27,9 +27,9 @@ pub enum Error { #[error("judger temporarily unavailable")] JudgerResourceExhausted, #[error("`{0}`")] - JudgerError(Status), + Judger(Status), #[error("`{0}`")] - JudgerProto(&'static str), + JudgerProtoChanged(&'static str), #[error("payload.`{0}` is not a vaild argument")] BadArgument(&'static str), #[error("language not found")] @@ -50,7 +50,7 @@ impl From for Error { fn from(value: Status) -> Self { match value.code() { tonic::Code::ResourceExhausted => Error::JudgerResourceExhausted, - _ => Error::JudgerError(value), + _ => Error::Judger(value), } } } @@ -61,8 +61,8 @@ impl From for Status { Error::JudgerResourceExhausted => Status::resource_exhausted("no available judger"), Error::BadArgument(x) => Status::invalid_argument(format!("bad argument: {}", x)), Error::LangNotFound => Status::not_found("languaue not found"), - Error::JudgerError(x) => report_internal!(info, "`{}`", x), - Error::JudgerProto(x) => report_internal!(info, "`{}`", x), + Error::Judger(x) => report_internal!(info, "`{}`", x), + Error::JudgerProtoChanged(x) => report_internal!(info, "`{}`", x), Error::Database(x) => report_internal!(warn, "{}", x), Error::TransportLayer(x) => report_internal!(info, "{}", x), Error::RateLimit => Status::resource_exhausted("resource limit imposed by backend"), @@ -92,6 +92,7 @@ impl From for SubmitStatus { } } +#[allow(dead_code)] pub struct PlaygroundPayload { pub input: Vec, pub code: Vec, @@ -138,7 +139,7 @@ impl Judger { .next() .in_current_span() .await - .ok_or(Error::JudgerProto("Expected as many case as inputs"))??; + .ok_or(Error::JudgerProtoChanged("Expected as many case as inputs"))??; total_memory += res.memory; total_time += res.time; total_score += score; diff --git a/backend/src/endpoint/announcement.rs b/backend/src/endpoint/announcement.rs index 2c664f93..374235d2 100644 --- a/backend/src/endpoint/announcement.rs +++ b/backend/src/endpoint/announcement.rs @@ -26,7 +26,7 @@ impl<'a> From> for AnnouncementFullInfo { } } -impl<'a> WithAuthTrait for Model {} +impl WithAuthTrait for Model {} impl From for AnnouncementInfo { fn from(value: Model) -> Self { @@ -276,7 +276,7 @@ impl Announcement for ArcServer { contest.ok_or(Error::NotInDB)?; let mut model = model.ok_or(Error::NotInDB)?.into_active_model(); - if let ActiveValue::Set(Some(v)) = model.contest_id { + if let ActiveValue::Set(Some(_)) = model.contest_id { return Err(Error::AlreadyExist("announcement already linked")); } diff --git a/backend/src/endpoint/chat.rs b/backend/src/endpoint/chat.rs index 23576658..21398947 100644 --- a/backend/src/endpoint/chat.rs +++ b/backend/src/endpoint/chat.rs @@ -19,7 +19,7 @@ impl<'a> From> for ChatInfo { } } -impl<'a> WithAuthTrait for Model {} +impl WithAuthTrait for Model {} #[tonic::async_trait] impl Chat for ArcServer { diff --git a/backend/src/endpoint/mod.rs b/backend/src/endpoint/mod.rs index cc485465..3357c4b8 100644 --- a/backend/src/endpoint/mod.rs +++ b/backend/src/endpoint/mod.rs @@ -16,7 +16,7 @@ mod user; use crate::NonZeroU32; use grpc::backend::{Id, Order, *}; -use sea_orm::*; +use sea_orm::{Value, *}; use std::ops::Deref; use tonic::*; use tracing::*; diff --git a/backend/src/endpoint/problem.rs b/backend/src/endpoint/problem.rs index 4d08581e..94136704 100644 --- a/backend/src/endpoint/problem.rs +++ b/backend/src/endpoint/problem.rs @@ -1,8 +1,8 @@ use super::*; use grpc::backend::problem_server::*; -use sea_orm::sea_query::Expr; +use std::sync::Arc; -use crate::entity::{contest, problem::Paginator, problem::*, testcase}; +use crate::entity::{contest, problem::Paginator, problem::*}; impl<'a> From> for ProblemFullInfo { fn from(value: WithAuth<'a, Model>) -> Self { @@ -10,7 +10,7 @@ impl<'a> From> for ProblemFullInfo { let writable = Entity::writable(&model, value.0); ProblemFullInfo { content: model.content.clone(), - tags: model.tags.clone(), + tags: vec![], difficulty: model.difficulty, public: model.public, time: model.time as u64, @@ -64,6 +64,8 @@ impl Problem for ArcServer { let start_from_end = create.order == Order::Descend as i32; if let Some(text) = query.text { Paginator::new_text(text, start_from_end) + } else if !query.tags.is_empty() { + Paginator::new_tag(query.tags.into_iter(), start_from_end) } else if let Some(sort) = query.sort_by { Paginator::new_sort(sort.try_into().unwrap_or_default(), start_from_end) } else if let Some(parent) = query.contest_id { @@ -129,16 +131,31 @@ impl Problem for ArcServer { model.user_id = ActiveValue::Set(user_id); fill_active_model!( - model, req.info, title, difficulty, time, memory, tags, content, match_rule, order + model, req.info, title, difficulty, time, memory, content, match_rule, order ); + let txn = self + .db + .begin_with_config(Some(IsolationLevel::ReadCommitted), None) + .await?; + let model = model - .save(self.db.deref()) + .save(&txn) .instrument(info_span!("save").or_current()) .await .map_err(Into::::into)?; - let id = *model.id.as_ref(); + let txn = Arc::new(txn); + + let id = model.id.unwrap(); + + insert_tag(txn.clone(), req.info.tags.into_iter(), id).await?; + + Arc::try_unwrap(txn) + .unwrap() + .commit() + .await + .map_err(|_| Error::Retry)?; info!(count.problem = 1, id = id); @@ -170,8 +187,9 @@ impl Problem for ArcServer { .into_active_model(); fill_exist_active_model!( - model, req.info, title, difficulty, time, memory, tags, content, match_rule, order + model, req.info, title, difficulty, time, memory, content, match_rule, order ); + // FIXME: fill tag model .update(self.db.deref()) @@ -236,7 +254,7 @@ impl Problem for ArcServer { let contest: contest::IdModel = contest.ok_or(Error::NotInDB)?; let mut model = model.ok_or(Error::NotInDB)?.into_active_model(); - if let ActiveValue::Set(Some(v)) = model.contest_id { + if let ActiveValue::Set(Some(_)) = model.contest_id { return Err(Error::AlreadyExist("problem already linked")); } diff --git a/backend/src/endpoint/testcase.rs b/backend/src/endpoint/testcase.rs index 8aed017b..11f501d1 100644 --- a/backend/src/endpoint/testcase.rs +++ b/backend/src/endpoint/testcase.rs @@ -198,7 +198,7 @@ impl Testcase for ArcServer { let mut model = model.ok_or(Error::NotInDB)?.into_active_model(); // FIXME: use is_set(), be sure to know what Option in sea_orm said - if let ActiveValue::Set(Some(v)) = model.problem_id { + if let ActiveValue::Set(Some(_)) = model.problem_id { return Err(Error::AlreadyExist("testcase already linked")); } diff --git a/backend/src/endpoint/token.rs b/backend/src/endpoint/token.rs index d3be66a0..803a24d1 100644 --- a/backend/src/endpoint/token.rs +++ b/backend/src/endpoint/token.rs @@ -8,8 +8,6 @@ use grpc::backend::token_server::*; use crate::entity::token::{Paginator, *}; use crate::{entity::user, util::rate_limit::RateLimit}; -const TOKEN_LIMIT: u64 = 16; - impl From for String { fn from(value: Model) -> Self { base64::Engine::encode( diff --git a/backend/src/entity/announcement.rs b/backend/src/entity/announcement.rs index 5a501fa0..6ac7081c 100644 --- a/backend/src/entity/announcement.rs +++ b/backend/src/entity/announcement.rs @@ -117,8 +117,6 @@ impl PagerData for PagerTrait { impl Source for PagerTrait { const ID: ::Column = Column::Id; type Entity = Entity; - const TYPE_NUMBER: u8 = 4; - async fn filter( auth: &Auth, _data: &Self::Data, @@ -140,8 +138,6 @@ impl PagerData for TextPagerTrait { impl Source for TextPagerTrait { const ID: ::Column = Column::Id; type Entity = Entity; - const TYPE_NUMBER: u8 = 4; - async fn filter( auth: &Auth, data: &Self::Data, @@ -163,8 +159,6 @@ impl PagerData for ParentPagerTrait { impl Source for ParentPagerTrait { const ID: ::Column = Column::Id; type Entity = Entity; - const TYPE_NUMBER: u8 = 8; - async fn filter( auth: &Auth, data: &Self::Data, @@ -202,8 +196,6 @@ impl PagerData for ColPagerTrait { impl Source for ColPagerTrait { const ID: ::Column = Column::Id; type Entity = Entity; - const TYPE_NUMBER: u8 = 8; - async fn filter( auth: &Auth, _data: &Self::Data, diff --git a/backend/src/entity/chat.rs b/backend/src/entity/chat.rs index 62611ec1..599b8024 100644 --- a/backend/src/entity/chat.rs +++ b/backend/src/entity/chat.rs @@ -87,8 +87,6 @@ impl PagerData for ParentPagerTrait { impl Source for ParentPagerTrait { const ID: ::Column = Column::Id; type Entity = Entity; - const TYPE_NUMBER: u8 = 8; - async fn filter( auth: &Auth, data: &Self::Data, diff --git a/backend/src/entity/contest.rs b/backend/src/entity/contest.rs index 9b2f1066..3ac2f91e 100644 --- a/backend/src/entity/contest.rs +++ b/backend/src/entity/contest.rs @@ -220,8 +220,6 @@ impl PagerData for TextPagerTrait { impl Source for TextPagerTrait { const ID: ::Column = Column::Id; type Entity = Entity; - const TYPE_NUMBER: u8 = 4; - async fn filter( auth: &Auth, data: &Self::Data, @@ -243,8 +241,6 @@ impl PagerData for ColPagerTrait { impl Source for ColPagerTrait { const ID: ::Column = Column::Id; type Entity = Entity; - const TYPE_NUMBER: u8 = 8; - async fn filter( auth: &Auth, _data: &Self::Data, diff --git a/backend/src/entity/education.rs b/backend/src/entity/education.rs index 4be38061..8d78ac9b 100644 --- a/backend/src/entity/education.rs +++ b/backend/src/entity/education.rs @@ -104,8 +104,6 @@ impl PagerData for PagerTrait { impl Source for PagerTrait { const ID: ::Column = Column::Id; type Entity = Entity; - const TYPE_NUMBER: u8 = 4; - async fn filter( auth: &Auth, _data: &Self::Data, @@ -127,8 +125,6 @@ impl PagerData for TextPagerTrait { impl Source for TextPagerTrait { const ID: ::Column = Column::Id; type Entity = Entity; - const TYPE_NUMBER: u8 = 4; - async fn filter( auth: &Auth, data: &Self::Data, @@ -150,8 +146,6 @@ impl PagerData for ParentPagerTrait { impl Source for ParentPagerTrait { const ID: ::Column = Column::Id; type Entity = Entity; - const TYPE_NUMBER: u8 = 8; - async fn filter( auth: &Auth, data: &Self::Data, diff --git a/backend/src/entity/problem.rs b/backend/src/entity/problem.rs index 9f33f985..c8b7d339 100644 --- a/backend/src/entity/problem.rs +++ b/backend/src/entity/problem.rs @@ -1,9 +1,11 @@ -use std::ops::Deref; - use super::*; -use crate::{entity::util::helper::tag_cond, union}; +use crate::union; use grpc::backend::list_problem_request::Sort; -use sea_orm::Statement; +use sea_orm::{ActiveValue, QuerySelect, Statement}; +use sea_query::JoinType; +use std::ops::Deref; +use std::sync::Arc; +use tokio::task::JoinSet; use tracing::{instrument, Instrument}; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] @@ -22,7 +24,6 @@ pub struct Model { pub time: i64, pub difficulty: u32, pub public: bool, - pub tags: String, pub title: String, pub content: String, #[sea_orm(column_type = "Time")] @@ -61,7 +62,7 @@ pub struct IdModel { } impl IdModel { - /// create new model with only id(foreign and pirmary), useful for query + /// create new model with only ids(foreign and primary), useful for query /// /// Be careful never save it pub fn upgrade(self) -> Model { @@ -76,7 +77,6 @@ impl IdModel { time: Default::default(), difficulty: Default::default(), public: self.public, - tags: Default::default(), title: Default::default(), content: Default::default(), create_at: Default::default(), @@ -117,37 +117,37 @@ pub enum Relation { User, } -impl Related for Entity { +impl Related for Entity { fn to() -> RelationDef { Relation::Education.def() } } -impl Related for Entity { +impl Related for Entity { fn to() -> RelationDef { Relation::Submit.def() } } -impl Related for Entity { +impl Related for Entity { fn to() -> RelationDef { Relation::Chat.def() } } -impl Related for Entity { +impl Related for Entity { fn to() -> RelationDef { Relation::Testcase.def() } } -impl Related for Entity { +impl Related for Entity { fn to() -> RelationDef { Relation::User.def() } } -impl Related for Entity { +impl Related for Entity { fn to() -> RelationDef { Relation::Contest.def() } @@ -164,8 +164,71 @@ impl Related for Entity { impl ActiveModelBehavior for ActiveModel {} +pub async fn insert_tag( + db: Arc, + tags: impl Iterator, + id: i32, +) -> Result<(), DbErr> { + let tags = tags.collect::>(); + let mut tag_ids = Vec::new(); + let mut set = JoinSet::new(); + for tag in tags.clone() { + let db = db.clone(); + set.spawn(async move { + tag::ActiveModel { + name: ActiveValue::set(tag), + ..Default::default() + } + .save(db.deref()) + .await + .map(|x| Some(x.id.unwrap())) + }); + } + for tag in tags { + let db = db.clone(); + set.spawn(async move { + tag::Entity::find() + .filter(tag::Column::Name.eq(tag)) + .one(db.deref()) + .await + .map(|x| x.map(|x| x.id)) + }); + } + while let Some(Ok(x)) = set.join_next().await { + if let Ok(Some(x)) = x { + tag_ids.push(x); + } + } + + tag_ids.sort_unstable(); + tag_ids.dedup(); + + tag_problem::Entity::delete_many() + .filter(tag_problem::Column::ProblemId.eq(id)) + .exec(db.deref()) + .await?; + + let mut set = JoinSet::new(); + for tag_id in tag_ids { + let db = db.clone(); + set.spawn(async move { + tag_problem::ActiveModel { + tag_id: ActiveValue::set(tag_id), + problem_id: ActiveValue::set(id), + ..Default::default() + } + .save(db.deref()) + .await + }); + } + while let Some(Ok(x)) = set.join_next().await { + x?; + } + Ok(()) +} + #[async_trait] -impl super::ParentalTrait for Entity { +impl ParentalTrait for Entity { #[instrument(skip_all, level = "info")] async fn related_read_by_id( auth: &Auth, @@ -212,7 +275,7 @@ impl super::ParentalTrait for Entity { } } -impl super::Filter for Entity { +impl Filter for Entity { fn read_filter(query: S, auth: &Auth) -> Result { Ok(match auth.perm() { RoleLv::Guest => query.filter(Column::Public.eq(true)), @@ -253,9 +316,7 @@ impl Reflect for PartialModel { } } -struct PagerTrait; - -struct TextPagerTrait; +pub struct TextPagerTrait; impl PagerData for TextPagerTrait { type Data = String; @@ -265,26 +326,18 @@ impl PagerData for TextPagerTrait { impl Source for TextPagerTrait { const ID: ::Column = Column::Id; type Entity = Entity; - const TYPE_NUMBER: u8 = 4; - async fn filter( auth: &Auth, data: &Self::Data, _db: &DatabaseConnection, ) -> Result, Error> { - Entity::read_filter(Entity::find(), auth).map(|x| { - x.filter( - Column::Title - .contains(data) - .or(tag_cond(Column::Tags, data.as_str())), - ) - }) + Entity::read_filter(Entity::find(), auth).map(|x| x.filter(Column::Title.contains(data))) } } type TextPaginator = UninitPaginator>; -struct ParentPagerTrait; +pub struct ParentPagerTrait; impl PagerData for ParentPagerTrait { type Data = (i32, f32); @@ -294,8 +347,6 @@ impl PagerData for ParentPagerTrait { impl Source for ParentPagerTrait { const ID: ::Column = Column::Id; type Entity = Entity; - const TYPE_NUMBER: u8 = 8; - async fn filter( auth: &Auth, data: &Self::Data, @@ -321,9 +372,9 @@ impl SortSource for ParentPagerTrait { } } -type ParentPaginator = UninitPaginator>; +pub type ParentPaginator = UninitPaginator>; -struct ColPagerTrait; +pub struct ColPagerTrait; impl PagerData for ColPagerTrait { type Data = (Sort, String); @@ -333,8 +384,6 @@ impl PagerData for ColPagerTrait { impl Source for ColPagerTrait { const ID: ::Column = Column::Id; type Entity = Entity; - const TYPE_NUMBER: u8 = 8; - async fn filter( auth: &Auth, _data: &Self::Data, @@ -375,11 +424,40 @@ impl SortSource for ColPagerTrait { type ColPaginator = UninitPaginator>; +pub struct TagPagerTrait; + +impl PagerData for TagPagerTrait { + type Data = Vec; +} + +#[async_trait] +impl Source for TagPagerTrait { + const ID: ::Column = Column::Id; + type Entity = Entity; + async fn filter( + auth: &Auth, + data: &Self::Data, + _: &DatabaseConnection, + ) -> Result, Error> { + let len = data.len(); + let query = problem::Entity::find() + .join(JoinType::Join, problem::Relation::TagProblem.def()) + .join(JoinType::Join, tag::Relation::TagProblem.def().rev()) + .filter(tag::Column::Name.is_in(data)) + .group_by(problem::Column::Id) + .having(Expr::col(tag::Column::Name).count_distinct().eq(len as i32)); + Entity::read_filter(query, auth) + } +} + +type TagPaginator = UninitPaginator>; + #[derive(serde::Serialize, serde::Deserialize)] pub enum Paginator { Text(TextPaginator), Parent(ParentPaginator), Col(ColPaginator), + Tag(TagPaginator), } impl WithAuthTrait for Paginator {} @@ -401,6 +479,9 @@ impl Paginator { start_from_end, )) } + pub fn new_tag(tag: impl Iterator, start_from_end: bool) -> Self { + Self::Tag(TagPaginator::new(tag.collect(), start_from_end)) + } pub fn new(start_from_end: bool) -> Self { Self::new_sort(Sort::SubmitCount, start_from_end) } @@ -414,6 +495,7 @@ impl<'a, 'b> WithDB<'a, WithAuth<'b, Paginator>> { Paginator::Text(ref mut x) => x.fetch(size, offset, auth, db).await, Paginator::Parent(ref mut x) => x.fetch(size, offset, auth, db).await, Paginator::Col(ref mut x) => x.fetch(size, offset, auth, db).await, + Paginator::Tag(ref mut x) => x.fetch(size, offset, auth, db).await, } } pub async fn remain(&self) -> Result { @@ -423,6 +505,7 @@ impl<'a, 'b> WithDB<'a, WithAuth<'b, Paginator>> { Paginator::Text(x) => x.remain(auth, db).await, Paginator::Parent(x) => x.remain(auth, db).await, Paginator::Col(x) => x.remain(auth, db).await, + Paginator::Tag(x) => x.remain(auth, db).await, } } } diff --git a/backend/src/entity/submit.rs b/backend/src/entity/submit.rs index d57d524a..7fdb4519 100644 --- a/backend/src/entity/submit.rs +++ b/backend/src/entity/submit.rs @@ -126,8 +126,6 @@ impl PagerData for ParentPagerTrait { impl Source for ParentPagerTrait { const ID: ::Column = Column::Id; type Entity = Entity; - const TYPE_NUMBER: u8 = 8; - async fn filter( auth: &Auth, data: &Self::Data, @@ -164,8 +162,6 @@ impl PagerData for ColPagerTrait { impl Source for ColPagerTrait { const ID: ::Column = Column::Id; type Entity = Entity; - const TYPE_NUMBER: u8 = 8; - async fn filter( auth: &Auth, _data: &Self::Data, diff --git a/backend/src/entity/testcase.rs b/backend/src/entity/testcase.rs index c67e3316..60231590 100644 --- a/backend/src/entity/testcase.rs +++ b/backend/src/entity/testcase.rs @@ -108,8 +108,6 @@ impl PagerData for ParentPagerTrait { impl Source for ParentPagerTrait { const ID: ::Column = Column::Id; type Entity = Entity; - const TYPE_NUMBER: u8 = 8; - async fn filter( auth: &Auth, data: &Self::Data, @@ -146,8 +144,6 @@ impl PagerData for ColPagerTrait { impl Source for ColPagerTrait { const ID: ::Column = Column::Id; type Entity = Entity; - const TYPE_NUMBER: u8 = 8; - async fn filter( auth: &Auth, _data: &Self::Data, diff --git a/backend/src/entity/token.rs b/backend/src/entity/token.rs index ba67d1d0..30647ed8 100644 --- a/backend/src/entity/token.rs +++ b/backend/src/entity/token.rs @@ -49,7 +49,7 @@ impl Filter for Entity { Self::read_filter(query, auth) } fn writable(model: &Self::Model, auth: &Auth) -> bool { - auth.perm() == RoleLv::Root + Some(model.id) == auth.user_id() || auth.perm() == RoleLv::Root } } @@ -78,8 +78,6 @@ impl PagerData for ColPagerTrait { impl Source for ColPagerTrait { const ID: ::Column = Column::Id; type Entity = Entity; - const TYPE_NUMBER: u8 = 8; - async fn filter( auth: &Auth, _data: &Self::Data, @@ -91,7 +89,7 @@ impl Source for ColPagerTrait { #[async_trait] impl SortSource for ColPagerTrait { - fn sort_col(data: &Self::Data) -> impl ColumnTrait { + fn sort_col(_: &Self::Data) -> impl ColumnTrait { Column::Expiry } fn get_val(data: &Self::Data) -> impl Into + Clone + Send { diff --git a/backend/src/entity/user.rs b/backend/src/entity/user.rs index ff8c7129..707c7418 100644 --- a/backend/src/entity/user.rs +++ b/backend/src/entity/user.rs @@ -183,8 +183,6 @@ impl PagerData for TextPagerTrait { impl Source for TextPagerTrait { const ID: ::Column = Column::Id; type Entity = Entity; - const TYPE_NUMBER: u8 = 4; - async fn filter( auth: &Auth, data: &Self::Data, @@ -206,8 +204,6 @@ impl PagerData for ColPagerTrait { impl Source for ColPagerTrait { const ID: ::Column = Column::Id; type Entity = Entity; - const TYPE_NUMBER: u8 = 8; - async fn filter( auth: &Auth, _data: &Self::Data, diff --git a/backend/src/entity/util/filter.rs b/backend/src/entity/util/filter.rs index 3d8bc629..d392b3f9 100644 --- a/backend/src/entity/util/filter.rs +++ b/backend/src/entity/util/filter.rs @@ -20,6 +20,8 @@ where Self: EntityTrait, { /// shortcut for empty `find` with read filter applied + #[allow(dead_code)] + // FIXME: this function is partially unused fn read_find(auth: &Auth) -> Result, Error> { Self::read_filter(Self::find(), auth) } @@ -45,7 +47,7 @@ where { Self::write_filter(Self::find_by_id(id), auth) } - fn writable(model: &Self::Model, auth: &Auth) -> bool { + fn writable(_: &Self::Model, _: &Auth) -> bool { false } } @@ -58,7 +60,9 @@ macro_rules! impl_filter_with_auth { paste::paste! { pub trait []{ type Entity: EntityTrait; + #[allow(dead_code)] fn read(&self)->Result<$t, Error>; + #[allow(dead_code)] fn write(&self)->Result<$t,Error>; } diff --git a/backend/src/entity/util/helper.rs b/backend/src/entity/util/helper.rs index 4ea4d855..e28eca56 100644 --- a/backend/src/entity/util/helper.rs +++ b/backend/src/entity/util/helper.rs @@ -4,14 +4,11 @@ use std::ops::Deref; +use crate::util::error::Error; use sea_orm::sea_query::*; use sea_orm::*; use tracing::instrument; -use crate::util::error::Error; - -const MAX_TAG: usize = 16; - pub(super) trait Paginate { /// Apply pagination effect on a Select(sea_orm) /// @@ -19,21 +16,21 @@ pub(super) trait Paginate { fn apply(self, query: Select) -> Select; } -/// aggregate conditions for tags(separated by whitespace) with or -/// -/// return a `Expr::val("1").eq("0")` if tags is empty -#[inline] -pub fn tag_cond(col: impl ColumnTrait, tags: &str) -> SimpleExpr { - let mut expr = Expr::val("1").eq("0"); - for tag in tags - .split_whitespace() - .take(MAX_TAG) - .filter(|&x| !x.is_empty()) - { - expr = expr.or(col.contains(tag)); - } - expr -} +// /// aggregate conditions for tags(separated by whitespace) with or +// /// +// /// return a `Expr::val("1").eq("0")` if tags is empty +// #[inline] +// pub fn tag_cond(col: impl ColumnTrait, tags: &str) -> SimpleExpr { +// let mut expr = Expr::val("1").eq("0"); +// for tag in tags +// .split_whitespace() +// .take(MAX_TAG) +// .filter(|&x| !x.is_empty()) +// { +// expr = expr.or(col.contains(tag)); +// } +// expr +// } /// bool to order #[inline] @@ -208,3 +205,28 @@ pub(super) fn to_inner_size_offset(size: u64, offset: i64) -> Option<(i64, u64)> false => Some((size as i64, offset as u64)), } } + +// /// construct query with tag(specified by `tags`) +// /// +// /// It actually builds sql queries like this +// /// ```sql +// /// SELECT * FROM posts +// /// JOIN post_tags ON post_id = post_tags.post_id +// /// JOIN tag ON post_tags.tag_id = tag_id +// /// WHERE tag.name IN ('tag1', 'tag2', 'tag3') +// /// GROUP BY id +// /// HAVING COUNT(DISTINCT t.name) = 3; +// ///``` +// pub(super) fn tag_select( +// tags: &[impl AsRef], +// ) -> Select { +// use super::super::*; +// +// let len=tags.len(); +// problem::Entity::find() +// .join(JoinType::Join, problem::Relation::TagProblem.def()) +// .join(JoinType::Join, tag::Relation::TagProblem.def()) +// .filter(tag::Column::Name.is_in(tags)) +// .group_by(problem::Column::Id) +// .having(Expr::col(tag::Column::Name).count_distinct().eq(len as i32)) +// } diff --git a/backend/src/entity/util/paginator.rs b/backend/src/entity/util/paginator.rs index 7af82bb2..5072f357 100644 --- a/backend/src/entity/util/paginator.rs +++ b/backend/src/entity/util/paginator.rs @@ -65,7 +65,6 @@ where { const ID: ::Column; type Entity: EntityTrait; - const TYPE_NUMBER: u8; /// filter reconstruction async fn filter( auth: &Auth, diff --git a/backend/src/util/auth.rs b/backend/src/util/auth.rs index a08ffa57..488e49fc 100644 --- a/backend/src/util/auth.rs +++ b/backend/src/util/auth.rs @@ -86,7 +86,7 @@ impl RoleLv { /// /// The difference between [`Auth`] and [`RoleLv`] is that /// [`Auth`] contain user id, and [`RoleLv`] is just permmision -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Auth { Guest, User((i32, RoleLv)), @@ -121,15 +121,15 @@ impl Auth { } } /// destruct the Auth into user id and permission level - pub fn into_inner(&self) -> Option<(i32, RoleLv)> { + pub fn into_inner(self) -> Option<(i32, RoleLv)> { match self { - Auth::User(x) => Some(*x), + Auth::User(x) => Some(x), _ => None, } } /// short hand for `self.into_inner().ok_or(Error::PermissionDeny)` pub fn assume_login(&self) -> Result<(i32, RoleLv), Error> { - self.into_inner().ok_or(Error::PermissionDeny( + self.clone().into_inner().ok_or(Error::PermissionDeny( "Only signed in user is allow in this endpoint", )) } diff --git a/backend/src/util/bound.rs b/backend/src/util/bound.rs index 2c46a871..cd047dfc 100644 --- a/backend/src/util/bound.rs +++ b/backend/src/util/bound.rs @@ -74,12 +74,36 @@ macro_rules! list_paginator_request { }; } -list_paginator_request!(Problem); list_paginator_request!(Announcement); list_paginator_request!(Contest); list_paginator_request!(Education); list_paginator_request!(User); +impl BoundCheck for ListProblemRequest { + fn check(&self) -> bool { + if self.size == 0 + || self.size >= i32::MAX as u64 + || self.offset.unsigned_abs() >= i32::MAX as u64 + { + true + } else if let Some(x) = &self.request { + (match x { + list_problem_request::Request::Create(x) => x + .query + .as_ref() + .map(|x| { + x.tags.len() * 20 + + x.tags.iter().map(String::len).sum::() + + x.text.as_ref().map(String::len).unwrap_or_default() + }) + .unwrap_or_default(), + list_problem_request::Request::Paginator(x) => x.len(), + } > 512) + } else { + false + } + } +} impl BoundCheck for ListTokenRequest { fn check(&self) -> bool { if self.size == 0 @@ -192,7 +216,7 @@ impl BoundCheck for UpdateProblemRequest { .map(String::len) .unwrap_or_default() > 128 - || self.info.tags.as_ref().map(String::len).unwrap_or_default() > 1024 + || self.info.tags.iter().map(String::len).sum::() > 1024 || self .info .content diff --git a/backend/src/util/error.rs b/backend/src/util/error.rs index 9c9d4497..42da227c 100644 --- a/backend/src/util/error.rs +++ b/backend/src/util/error.rs @@ -86,7 +86,7 @@ impl From for Status { tracing::trace!("database_notfound"); Status::out_of_range("") } - Error::InvaildUUID(err) => { + Error::InvaildUUID(_) => { Status::invalid_argument("Invaild request_id(should be a client generated UUIDv4)") } Error::Unreachable(x) => report_internal!(error, "{}", x), diff --git a/grpc/proto/backend.proto b/grpc/proto/backend.proto index b3c1669a..69b53d8b 100644 --- a/grpc/proto/backend.proto +++ b/grpc/proto/backend.proto @@ -94,12 +94,10 @@ message CreateProblemRequest { required uint64 time = 4; // in byte (8 bits) required uint64 memory = 5; - // tags split by unicode whitespace - // https://doc.rust-lang.org/std/primitive.str.html#method.split_whitespace - required string tags = 6; required string content = 7; required MatchRule match_rule = 9; required float order = 10; + repeated string tags = 11; }; required Info info = 1; // can prevent duplicate request. @@ -123,12 +121,10 @@ message UpdateProblemRequest { optional uint64 time = 4; // in byte (8 bits) optional uint64 memory = 5; - // tags split by unicode whitespace - // https://doc.rust-lang.org/std/primitive.str.html#method.split_whitespace - optional string tags = 6; optional string content = 7; optional MatchRule match_rule = 10; optional float order = 11; + repeated string tags = 12; }; required Info info = 1; required int32 id = 2; @@ -157,7 +153,7 @@ message ProblemFullInfo { required ProblemInfo info = 1; required string content = 2; // tags are split by ascii whitespace - required string tags = 8; + repeated string tags = 11; // 0 - 500 easy // 500 - 1000 easy+ // 1000 - 1500 medium @@ -187,6 +183,7 @@ message ListProblemRequest { optional int32 contest_id = 1; optional Sort sort_by = 2; optional string text = 3; + repeated string tags = 4; } message Create { required Order order = 1; diff --git a/grpc/proto/judger.proto b/grpc/proto/judger.proto index 57526720..66d26492 100644 --- a/grpc/proto/judger.proto +++ b/grpc/proto/judger.proto @@ -5,19 +5,24 @@ package oj.judger; import "google/protobuf/empty.proto"; message JudgeRequest { + // lanuguage uuid required string lang_uid = 1; + // user submitted code required bytes code = 2; // memory limit in byte required uint64 memory = 3; // time limit in nanosecond required uint64 time = 4; + // maching rule, see JudgeMatchRule required JudgeMatchRule rule = 5; // len must > 0 repeated TestIO tests = 6; } message ExecRequest { + // language uuid required string lang_uid = 1; + // user submitted code required bytes code = 2; // memory limit in byte required uint64 memory = 3; @@ -28,7 +33,9 @@ message ExecRequest { } message Log{ + // Log severity required uint32 level = 1; + // message required string msg = 2; } @@ -40,6 +47,11 @@ message ExecResult { } } +// part of testcase +// +// an testcase in judger is a collection of input/output pair +// +// TestIO is a single input/out pair message TestIO { required bytes input = 1; required bytes output = 2; @@ -89,6 +101,10 @@ enum JudgeMatchRule { SkipSNL = 2; } +// @deprecated +// list of languages +// +// will be replaced by embedding `repeated`/`stream` directly in response message Langs { repeated LangInfo list = 1; } // info of a language(extension) diff --git a/judger/src/config.rs b/judger/src/config.rs index c813e88f..de949af8 100644 --- a/judger/src/config.rs +++ b/judger/src/config.rs @@ -32,11 +32,13 @@ lazy_static::lazy_static! { pub static ref CONFIG: Config=Config::default(); } +/// method to load cpu usage from control group #[derive(Serialize, Deserialize, Debug, Default)] #[serde(rename_all = "camelCase")] pub enum Accounting { #[default] Auto, + #[allow(clippy::enum_variant_names)] CpuAccounting, Cpu, } @@ -48,6 +50,7 @@ fn default_ratio_memory() -> f64 { 1.0 } +/// Ratio for resource multiplier #[derive(Serialize, Deserialize, Default)] #[serde(deny_unknown_fields)] pub struct Ratio { diff --git a/judger/src/filesystem/adapter/fuse.rs b/judger/src/filesystem/adapter/fuse.rs index 8d98c378..84154d23 100644 --- a/judger/src/filesystem/adapter/fuse.rs +++ b/judger/src/filesystem/adapter/fuse.rs @@ -1,7 +1,6 @@ use std::{ffi::OsStr, num::NonZeroU32, path::Path, sync::Arc}; use bytes::Bytes; -use futures_core::Future; use spin::Mutex; use tokio::io::{AsyncRead, AsyncSeek}; use tokio::sync::Mutex as AsyncMutex; @@ -16,7 +15,7 @@ use fuse3::{ Result as FuseResult, *, }; -/// A asynchorized stream from vector +/// A synchronized stream from vector type VecStream = tokio_stream::Iter>; // filesystem @@ -69,19 +68,15 @@ where } } -impl fuse3::raw::Filesystem for Filesystem +impl 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; - async fn init(&self, _: Request) -> FuseResult { Ok(ReplyInit { max_write: NonZeroU32::new(BLOCKSIZE as u32).unwrap(), }) } - async fn destroy(&self, _: Request) {} async fn lookup(&self, req: Request, parent: u64, name: &OsStr) -> FuseResult { @@ -93,18 +88,130 @@ where // FIXME: unsure about the inode Ok(reply_entry(&req, node.get_value(), node.get_id() as u64)) } + async fn forget(&self, _: Request, _: u64, _: u64) {} - async fn release( + + async fn getattr( + &self, + req: Request, + inode: u64, + _: Option, + _: u32, + ) -> FuseResult { + let tree = self.tree.lock(); + let node = tree.get(inode as usize).ok_or(FuseError::InvalidIno)?; + // FIXME: unsure about the inode + Ok(reply_attr(&req, node.get_value(), inode)) + } + async fn setattr( + &self, + req: Request, + inode: Inode, + _: Option, + _: SetAttr, + ) -> FuseResult { + let tree = self.tree.lock(); + let node = tree.get(inode as usize).ok_or(FuseError::InvalidIno)?; + Ok(reply_attr(&req, node.get_value(), inode)) + } + async fn readlink(&self, _: Request, inode: Inode) -> FuseResult { + let tree = self.tree.lock(); + let node = tree.get(inode as usize).ok_or(FuseError::InvalidIno)?; + let link = node + .get_value() + .get_symlink() + .ok_or(FuseError::InvalidArg)?; + Ok(ReplyData { + data: Bytes::copy_from_slice(link.as_encoded_bytes()), + }) + } + async fn mkdir( + &self, + req: Request, + parent: u64, + name: &OsStr, + _: u32, + _: u32, + ) -> FuseResult { + let mut tree = self.tree.lock(); + let mut parent_node = tree.get_mut(parent as usize).ok_or(FuseError::InvalidIno)?; + 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)) + } + async fn unlink(&self, _: Request, parent: Inode, name: &OsStr) -> FuseResult<()> { + let mut tree = self.tree.lock(); + let mut parent_node = tree.get_mut(parent as usize).ok_or(FuseError::InvalidIno)?; + if parent_node.get_value().kind() != FileType::Directory { + return Err(FuseError::NotDir.into()); + } + parent_node.remove_by_component(name); + Ok(()) + } + async fn open(&self, _: Request, inode: u64, flags: u32) -> FuseResult { + // ignore write permission, because some application may open files + // with write permission but never write + let tree = self.tree.lock(); + let node = tree.get(inode as usize).ok_or(FuseError::InvalidIno)?; + if node.get_value().kind() == FileType::Directory { + return Err(FuseError::IsDir.into()); + } + let mut entry = node.get_value().clone(); + entry.set_append().await; + let fh = self.handle_table.add(AsyncMutex::new(entry)); + Ok(ReplyOpen { fh, flags }) + } + async fn read( &self, _: Request, _: u64, fh: u64, - _: u32, + offset: u64, + size: u32, + ) -> FuseResult { + let session = self.handle_table.get(fh).ok_or(FuseError::HandleNotFound)?; + let mut lock = session.lock().await; + + if lock.kind() != FileType::RegularFile { + return Err(FuseError::IsDir.into()); + } + + Ok(lock + .read(offset, size) + .await + .map(|data| ReplyData { data })?) + } + async fn write( + &self, + _: Request, _: u64, - _: bool, - ) -> FuseResult<()> { - self.handle_table.remove(fh); - Ok(()) + fh: u64, + offset: u64, + data: &[u8], + _: u32, + _: u32, + ) -> FuseResult { + let session = self + .handle_table + .get(fh) + .ok_or(FuseError::HandleNotFound) + .unwrap(); + let mut lock = session.lock().await; + + if lock.kind() != FileType::RegularFile { + return Err(FuseError::IsDir.into()); + } + + Ok(lock + .write(offset, data, &self.resource) + .await + .map(|written| ReplyWrite { written }) + .ok_or(FuseError::Unimplemented)?) } async fn statfs(&self, _: Request, _: u64) -> FuseResult { let tree = self.tree.lock(); @@ -119,6 +226,28 @@ where frsize: BLOCKSIZE as u32, }) } + async fn release( + &self, + _: Request, + _: u64, + fh: u64, + _: u32, + _: u64, + _: bool, + ) -> FuseResult<()> { + self.handle_table.remove(fh); + Ok(()) + } + async fn fsync(&self, _: Request, _: u64, _: u64, _: bool) -> FuseResult<()> { + Ok(()) + } + async fn flush(&self, _: Request, _: Inode, fh: u64, _: u64) -> FuseResult<()> { + let node = self.handle_table.get(fh).ok_or(FuseError::HandleNotFound)?; + // The result is intentionally ignored, from the behavior of `ld`, + // we know that ld actually flush readonly file. + Entry::flush(node).await; + Ok(()) + } async fn opendir(&self, _: Request, inode: u64, flags: u32) -> FuseResult { let tree = self.tree.lock(); let node = tree.get(inode as usize).ok_or(FuseError::InvalidIno)?; @@ -130,19 +259,7 @@ where .add(AsyncMutex::new(node.get_value().clone())); Ok(ReplyOpen { fh, flags }) } - async fn open(&self, _: Request, inode: u64, flags: u32) -> FuseResult { - // ignore write permission, because some application may open files - // with write permission but never write - let tree = self.tree.lock(); - let node = tree.get(inode as usize).ok_or(FuseError::InvalidIno)?; - if node.get_value().kind() == FileType::Directory { - return Err(FuseError::IsDir.into()); - } - let mut entry = node.get_value().clone(); - entry.set_append().await; - let fh = self.handle_table.add(AsyncMutex::new(entry)); - Ok(ReplyOpen { fh, flags }) - } + type DirEntryStream<'a>=VecStream> where Self: 'a; async fn readdir( &self, _: Request, @@ -191,6 +308,59 @@ where entries: tokio_stream::iter(entries), }) } + async fn fsyncdir(&self, _: Request, _: u64, _: u64, _: bool) -> FuseResult<()> { + Ok(()) + } + async fn access(&self, _: Request, _: u64, _: u32) -> FuseResult<()> { + // FIXME: only allow current user to access + Ok(()) + } + async fn create( + &self, + req: Request, + parent: u64, + name: &OsStr, + _: u32, + flags: u32, + ) -> FuseResult { + let mut tree = self.tree.lock(); + let mut parent_node = tree.get_mut(parent as usize).ok_or(FuseError::InvalidIno)?; + 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 entry = node.get_value().clone(); + let fh = self.handle_table.add(AsyncMutex::new(entry)); + let inode = node.get_id() as u64; + + Ok(reply_created(&req, node.get_value(), fh, flags, inode)) + } + async fn interrupt(&self, _: Request, _: u64) -> FuseResult<()> { + Ok(()) + } + async fn fallocate( + &self, + _: Request, + inode: u64, + _: u64, + _: u64, + _: u64, + _: u32, + ) -> FuseResult<()> { + let tree = self.tree.lock(); + let node = tree.get(inode as usize).ok_or(FuseError::InvalidIno)?; + + match node.get_value().kind() { + FileType::Directory | FileType::NamedPipe | FileType::CharDevice => { + Err(FuseError::IsDir.into()) + } + _ => Ok(()), + } + } + type DirEntryPlusStream<'a>=VecStream> where Self: 'a; async fn readdirplus( &self, req: Request, @@ -247,177 +417,6 @@ where entries: tokio_stream::iter(entries), }) } - async fn read( - &self, - _: Request, - _: u64, - fh: u64, - offset: u64, - size: u32, - ) -> FuseResult { - let session = self.handle_table.get(fh).ok_or(FuseError::HandleNotFound)?; - let mut lock = session.lock().await; - - if lock.kind() != FileType::RegularFile { - return Err(FuseError::IsDir.into()); - } - - Ok(lock - .read(offset, size) - .await - .map(|data| ReplyData { data })?) - } - async fn write( - &self, - _: Request, - _: u64, - fh: u64, - offset: u64, - data: &[u8], - _: u32, - _: u32, - ) -> FuseResult { - let session = self - .handle_table - .get(fh) - .ok_or(FuseError::HandleNotFound) - .unwrap(); - let mut lock = session.lock().await; - - if lock.kind() != FileType::RegularFile { - return Err(FuseError::IsDir.into()); - } - - Ok(lock - .write(offset, data, &self.resource) - .await - .map(|written| ReplyWrite { written }) - .ok_or(FuseError::Unimplemented)?) - } - async fn flush(&self, _: Request, _: Inode, fh: u64, _: u64) -> FuseResult<()> { - let node = self.handle_table.get(fh).ok_or(FuseError::HandleNotFound)?; - // The result is intentionally ignored, from the behavior of `ld`, - // we know that ld actually flush readonly file. - Entry::flush(node).await.ok_or(FuseError::Unimplemented); - Ok(()) - } - async fn access(&self, _: Request, _: u64, _: u32) -> FuseResult<()> { - // FIXME: only allow current user to access - Ok(()) - } - async fn fsync(&self, _: Request, _: u64, _: u64, _: bool) -> FuseResult<()> { - Ok(()) - } - async fn fsyncdir(&self, _: Request, _: u64, _: u64, _: bool) -> FuseResult<()> { - Ok(()) - } - async fn fallocate( - &self, - _: Request, - inode: u64, - _: u64, - _: u64, - _: u64, - _: u32, - ) -> FuseResult<()> { - let tree = self.tree.lock(); - let node = tree.get(inode as usize).ok_or(FuseError::InvalidIno)?; - - match node.get_value().kind() { - FileType::Directory | FileType::NamedPipe | FileType::CharDevice => { - Err(FuseError::IsDir.into()) - } - _ => Ok(()), - } - } - async fn interrupt(&self, _: Request, _: u64) -> FuseResult<()> { - Ok(()) - } - async fn getattr( - &self, - req: Request, - inode: u64, - _: Option, - _: u32, - ) -> FuseResult { - let tree = self.tree.lock(); - let node = tree.get(inode as usize).ok_or(FuseError::InvalidIno)?; - // FIXME: unsure about the inode - Ok(reply_attr(&req, node.get_value(), inode)) - } - async fn setattr( - &self, - req: Request, - inode: Inode, - _: Option, - _: SetAttr, - ) -> FuseResult { - let tree = self.tree.lock(); - let node = tree.get(inode as usize).ok_or(FuseError::InvalidIno)?; - Ok(reply_attr(&req, node.get_value(), inode)) - } - async fn create( - &self, - req: Request, - parent: u64, - name: &OsStr, - _: u32, - flags: u32, - ) -> FuseResult { - let mut tree = self.tree.lock(); - let mut parent_node = tree.get_mut(parent as usize).ok_or(FuseError::InvalidIno)?; - 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 entry = node.get_value().clone(); - let fh = self.handle_table.add(AsyncMutex::new(entry)); - let inode = node.get_id() as u64; - - Ok(reply_created(&req, node.get_value(), fh, flags, inode)) - } - async fn mkdir( - &self, - req: Request, - parent: u64, - name: &OsStr, - _: u32, - _: u32, - ) -> FuseResult { - let mut tree = self.tree.lock(); - let mut parent_node = tree.get_mut(parent as usize).ok_or(FuseError::InvalidIno)?; - 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)) - } - async fn readlink(&self, _: Request, inode: Inode) -> FuseResult { - let tree = self.tree.lock(); - let node = tree.get(inode as usize).ok_or(FuseError::InvalidIno)?; - let link = node - .get_value() - .get_symlink() - .ok_or(FuseError::InvalidArg)?; - Ok(ReplyData { - data: Bytes::copy_from_slice(link.as_encoded_bytes()), - }) - } - async fn unlink(&self, _: Request, parent: Inode, name: &OsStr) -> FuseResult<()> { - let mut tree = self.tree.lock(); - let mut parent_node = tree.get_mut(parent as usize).ok_or(FuseError::InvalidIno)?; - if parent_node.get_value().kind() != FileType::Directory { - return Err(FuseError::NotDir.into()); - } - parent_node.remove_by_component(name); - Ok(()) - } } #[cfg(test)] @@ -437,6 +436,7 @@ mod test { use super::Filesystem; + #[allow(clippy::declare_interior_mutable_const)] const UNIQUE_COUNTER: AtomicU64 = AtomicU64::new(0); async fn nested_tar() -> Filesystem { @@ -445,6 +445,7 @@ mod test { } fn spawn_request() -> Request { Request { + #[allow(clippy::declare_interior_mutable_const)] unique: UNIQUE_COUNTER.fetch_add(1, Ordering::AcqRel), uid: 1000, gid: 1000, diff --git a/judger/src/filesystem/entry/ro.rs b/judger/src/filesystem/entry/ro.rs index 541ffe4b..a0b7d069 100644 --- a/judger/src/filesystem/entry/ro.rs +++ b/judger/src/filesystem/entry/ro.rs @@ -19,7 +19,7 @@ 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 +/// except that it doesn't share the same underlying file session /// by cloning(Reads, writes, and seeks would **not** affect both /// [`TarBlock`] instances simultaneously.) #[derive(Debug)] @@ -50,7 +50,7 @@ where pub fn get_size(&self) -> u32 { self.size } - pub async fn read_all(&self) -> std::io::Result> { + pub async fn read_all(&self) -> 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]; @@ -58,6 +58,7 @@ where Ok(buf) } #[cfg(test)] + #[allow(dead_code)] fn from_raw(file: F, start: u64, size: u32) -> Self { Self { file: Arc::new(Mutex::new(file)), @@ -71,20 +72,16 @@ where if self.cursor > self.size { None } else { - Some(SeekFrom::Start(self.start + offset + (self.cursor) as u64)) + 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 { + async fn read(&mut self, offset: u64, size: u32) -> io::Result { let size = size.min(self.size - self.cursor) as usize; let size = size.min(BLOCKSIZE * MAX_READ_BLK); @@ -130,69 +127,3 @@ where } } } - -// #[cfg(test)] -// mod test { -// use std::io::Cursor; - -// use tokio::{fs::File, io::BufReader}; - -// use super::*; -// #[tokio::test] -// async fn file_io() { -// let file = File::open("test/single_file.tar").await.unwrap(); -// let mut block = TarBlock::new(Arc::new(Mutex::new(file)), 512, 11); -// let mut buf = [0_u8; 11]; -// block.read_exact(&mut buf).await.unwrap(); -// assert_eq!(buf, *b"hello world"); -// } -// #[tokio::test] -// async fn normal_read() { -// let underlying = BufReader::new(Cursor::new(b"111hello world111")); -// let mut block = TarBlock::from_raw(underlying, 3, 11); - -// let mut buf = [0_u8; 11]; -// block.read_exact(&mut buf).await.unwrap(); - -// assert_eq!(buf, *b"hello world"); -// } -// #[tokio::test] -// async fn end_of_file_read() { -// let underlying = BufReader::new(Cursor::new(b"111hello world")); -// let mut block = TarBlock::from_raw(underlying, 3, 11); - -// let mut buf = [0_u8; 11]; -// block.read_exact(&mut buf).await.unwrap(); - -// assert_eq!( -// block.read_u8().await.unwrap_err().kind(), -// io::ErrorKind::UnexpectedEof -// ); -// } -// #[tokio::test] -// async fn multi_sequential_read() { -// let underlying = BufReader::new(Cursor::new(b"111hello world111")); -// let mut block = TarBlock::from_raw(underlying, 3, 11); - -// for c in b"hello world" { -// assert_eq!(block.read_u8().await.unwrap(), *c); -// } -// } -// #[tokio::test(flavor = "multi_thread", worker_threads = 8)] -// async fn multi_reader_read() { -// let underlying = BufReader::new(Cursor::new(b"111hello world111")); -// let underlying = Arc::new(Mutex::new(underlying)); -// let block = TarBlock::new(underlying, 3, 11); - -// for _ in 0..30 { -// let mut block = block.clone(); -// tokio::spawn(async move { -// for _ in 0..400 { -// let mut buf = [0_u8; 11]; -// block.read_exact(&mut buf).await.unwrap(); -// assert_eq!(buf, *b"hello world"); -// } -// }); -// } -// } -// } diff --git a/judger/src/filesystem/entry/rw.rs b/judger/src/filesystem/entry/rw.rs index a11a7f49..2af9d701 100644 --- a/judger/src/filesystem/entry/rw.rs +++ b/judger/src/filesystem/entry/rw.rs @@ -1,12 +1,12 @@ use spin::Mutex; use std::{io, ops::Deref, sync::Arc}; -use super::{FuseFlushTrait, FuseReadTrait, FuseWriteTrait, BLOCKSIZE}; +use super::{FuseFlushTrait, FuseReadTrait, FuseWriteTrait}; /// A block in memory /// /// Note that [`MemBlock`] behavior like [`tokio::fs::File`], -/// except that it dones't shares the same underlying file session +/// except that it doesn't share the same underlying file session /// by cloning(Reads, writes, and seeks would **not** affect both /// [`MemBlock`] instances simultaneously.) #[derive(Default, Debug)] @@ -36,7 +36,7 @@ impl MemBlock { } impl FuseReadTrait for MemBlock { - async fn read(&mut self, offset: u64, size: u32) -> std::io::Result { + async fn read(&mut self, offset: u64, size: u32) -> io::Result { let locked = self.data.lock(); if locked.len() < offset as usize { return Err(io::Error::new( @@ -50,7 +50,7 @@ impl FuseReadTrait for MemBlock { } } impl FuseWriteTrait for MemBlock { - async fn write(&mut self, offset: u64, data: &[u8]) -> std::io::Result { + async fn write(&mut self, offset: u64, data: &[u8]) -> io::Result { // FIXME: file hole may cause OOM let mut locked = self.data.lock(); if self.cursor > locked.len() { @@ -71,7 +71,7 @@ impl FuseWriteTrait for MemBlock { } impl FuseFlushTrait for MemBlock { - async fn flush(&mut self) -> std::io::Result<()> { + async fn flush(&mut self) -> io::Result<()> { let mut locked = self.data.lock(); locked.extend_from_slice(&self.write_buffer); self.write_buffer.clear(); @@ -88,216 +88,3 @@ impl Clone for MemBlock { } } } - -// 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/mod.rs b/judger/src/filesystem/mod.rs index 289ba14c..7ecfe462 100644 --- a/judger/src/filesystem/mod.rs +++ b/judger/src/filesystem/mod.rs @@ -7,5 +7,5 @@ mod mkdtemp; mod resource; mod table; -pub use adapter::{Filesystem, Template}; +pub use adapter::Template; pub use handle::MountHandle; diff --git a/judger/src/filesystem/table.rs b/judger/src/filesystem/table.rs index ff18f478..f6ca1d43 100644 --- a/judger/src/filesystem/table.rs +++ b/judger/src/filesystem/table.rs @@ -1,3 +1,4 @@ +use std::collections::btree_map::Entry; use std::{ collections::BTreeMap, ffi::{OsStr, OsString}, @@ -110,18 +111,21 @@ impl AdjTable { { 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); + match self.by_id[idx].children.entry(name.clone()) { + Entry::Vacant(_) => { + 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); + } + Entry::Occupied(x) => { + idx = *x.get(); + } } } NodeWrapperMut { table: self, idx } diff --git a/judger/src/language/mod.rs b/judger/src/language/mod.rs index 8b4b8570..4de3d884 100644 --- a/judger/src/language/mod.rs +++ b/judger/src/language/mod.rs @@ -4,4 +4,4 @@ mod spec; mod stage; pub use builder::*; -pub use plugin::{Plugin, PluginMap}; +pub use plugin::PluginMap; diff --git a/judger/src/language/plugin.rs b/judger/src/language/plugin.rs index be9f6378..5fd23d53 100644 --- a/judger/src/language/plugin.rs +++ b/judger/src/language/plugin.rs @@ -116,7 +116,7 @@ impl Plugin { let spec_source = template .read_by_path("spec.toml") .await - .unwrap_or_else(|| panic!("sepc.toml not found in plugin {}", path.as_ref().display())); + .unwrap_or_else(|| panic!("spec.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 }) @@ -143,7 +143,7 @@ where } /// judge /// - /// The porcess can be described as: + /// The process can be described as: /// 1. compile the source code /// 2. run the compiled code /// 3. compare the output diff --git a/judger/src/language/spec/compile.rs b/judger/src/language/spec/compile.rs index 95f4f2d2..e69de29b 100644 --- a/judger/src/language/spec/compile.rs +++ b/judger/src/language/spec/compile.rs @@ -1 +0,0 @@ -pub struct Judge {} diff --git a/judger/src/language/spec/mod.rs b/judger/src/language/spec/mod.rs index 2d49a913..f5d6e5ec 100644 --- a/judger/src/language/spec/mod.rs +++ b/judger/src/language/spec/mod.rs @@ -7,8 +7,6 @@ use crate::sandbox::{Cpu, Memory, Stat}; use self::raw::Raw; -mod compile; -mod judge; mod raw; /// multiplier for cpu limit diff --git a/judger/src/language/stage/compile.rs b/judger/src/language/stage/compile.rs index bcee02fc..59499316 100644 --- a/judger/src/language/stage/compile.rs +++ b/judger/src/language/stage/compile.rs @@ -11,9 +11,9 @@ use super::Runner; /// First stage, compile the source code /// -/// Note that by compile, we doesn't mean the traditional compile process +/// Note that by compiles, we don't mean the traditional compile process /// it could be any process that prepare the code to be ready for execution, -/// or do nothing(iterpreted language) +/// or do nothing(interpreted language) pub struct Compiler { spec: Arc, handle: MountHandle, diff --git a/judger/src/language/stage/judge.rs b/judger/src/language/stage/judge.rs index 1cb734b8..fbd104d8 100644 --- a/judger/src/language/stage/judge.rs +++ b/judger/src/language/stage/judge.rs @@ -28,7 +28,7 @@ impl Judger { let input = self.corpse.stdout(); match mode { AssertionMode::SkipSpace => { - // skip space and newline, continous space and single space is consider different + // skip space and newline, continuous space and single space is consider different let output = output.iter().map(|x| match x { b'\n' | b' ' => b' ', x => *x, @@ -44,7 +44,7 @@ impl Judger { } } AssertionMode::SkipContinuousSpace => { - // skip space and newline, continous space is consider same + // skip space and newline, continuous space is consider same let output = output.iter().map(|x| match x { b'\n' | b' ' => b' ', x => *x, diff --git a/judger/src/language/stage/mod.rs b/judger/src/language/stage/mod.rs index 8af73ddc..93ce110c 100644 --- a/judger/src/language/stage/mod.rs +++ b/judger/src/language/stage/mod.rs @@ -1,4 +1,6 @@ //! collection of steps for judge and execute +//! +//! It's chain or responsibility mod compile; mod judge; diff --git a/judger/src/language/stage/run.rs b/judger/src/language/stage/run.rs index bd674dc6..298c9183 100644 --- a/judger/src/language/stage/run.rs +++ b/judger/src/language/stage/run.rs @@ -37,7 +37,7 @@ impl Runner { }; let process = Process::new(ctx)?; let corpse = process.wait(input).await?; - Ok(Streamer::new(self.spec.clone(), corpse)) + Ok(Streamer::new(corpse)) } } diff --git a/judger/src/language/stage/stream.rs b/judger/src/language/stage/stream.rs index 8b33b0dd..32a967e1 100644 --- a/judger/src/language/stage/stream.rs +++ b/judger/src/language/stage/stream.rs @@ -1,7 +1,5 @@ -use std::sync::Arc; - use crate::{ - language::{spec::Spec, ExecuteResult}, + language::ExecuteResult, sandbox::{Corpse, MonitorKind}, }; @@ -9,13 +7,12 @@ use super::StatusCode; /// Third stage of exec, stream execution result to client pub struct Streamer { - spec: Arc, corpse: Corpse, } impl Streamer { - pub fn new(spec: Arc, corpse: Corpse) -> Self { - Self { spec, corpse } + pub fn new(corpse: Corpse) -> Self { + Self { corpse } } pub fn get_code(&self) -> StatusCode { match self.corpse.status() { diff --git a/judger/src/sandbox/monitor/mem_cpu.rs b/judger/src/sandbox/monitor/mem_cpu.rs index 0cbe87ad..003fc617 100644 --- a/judger/src/sandbox/monitor/mem_cpu.rs +++ b/judger/src/sandbox/monitor/mem_cpu.rs @@ -110,8 +110,7 @@ impl super::Monitor for Monitor { /// /// This method is cancellation safe async fn wait_exhaust(&mut self) -> MonitorKind { - let reason = self.monitor_task.as_mut().unwrap().await.unwrap(); - reason + self.monitor_task.as_mut().unwrap().await.unwrap() } fn poll_exhaust(&mut self) -> Option { let wrapper = wrapper::CgroupWrapper::new(&self.cgroup);