diff --git a/bin/u_agent/src/lib.rs b/bin/u_agent/src/lib.rs index eeb1b0d..946afc9 100644 --- a/bin/u_agent/src/lib.rs +++ b/bin/u_agent/src/lib.rs @@ -8,14 +8,18 @@ extern crate log; extern crate env_logger; use std::env; +use std::panic; +use std::sync::Arc; use tokio::time::{sleep, Duration}; use u_lib::{ api::ClientHandler, builder::JobBuilder, cache::JobCache, + errors::ErrChan, executor::pop_completed, messaging::Reportable, models::AssignedJob, + UError, UID, //daemonize }; @@ -26,12 +30,12 @@ pub async fn process_request(job_requests: Vec, client: &ClientHand if job_requests.len() > 0 { for jr in &job_requests { if !JobCache::contains(&jr.job_id) { - info!("Fetching job: {}", &jr.job_id); + debug!("Fetching job: {}", &jr.job_id); let fetched_job = loop { match client.get_jobs(Some(jr.job_id)).await { Ok(mut result) => break result.pop().unwrap(), Err(err) => { - error!("{:?} \nretrying...", err); + debug!("{:?} \nretrying...", err); sleep(Duration::from_secs(ITERATION_LATENCY)).await; } } @@ -39,7 +43,7 @@ pub async fn process_request(job_requests: Vec, client: &ClientHand JobCache::insert(fetched_job); } } - info!( + debug!( "Scheduling jobs: {}", job_requests .iter() @@ -50,41 +54,56 @@ pub async fn process_request(job_requests: Vec, client: &ClientHand let mut builder = JobBuilder::from_request(job_requests); let errors = builder.pop_errors(); if errors.len() > 0 { - error!( - "Some errors encountered: \n{}", - errors - .iter() - .map(|j| j.to_string()) - .collect::>() - .join("\n") - ); + errors.into_iter().for_each(ErrChan::send) } builder.unwrap_one().spawn().await; } } -pub async fn run_forever() { - //daemonize(); - env_logger::init(); - let arg_ip = env::args().nth(1); - let client = ClientHandler::new(arg_ip.as_deref()); - info!("Connecting to the server"); +async fn error_reporting(client: Arc) { + loop { + let err = ErrChan::recv(); + debug!("Error encountered: {:?}", err); + 'retry: for _ in 0..3 { + match client.report(&[Reportable::Error(err.to_string())]).await { + Ok(_) => break 'retry, + Err(e) => { + debug!("Reporting error: {:?}", e); + sleep(Duration::from_secs(10)).await; + } + } + } + } +} + +async fn do_stuff(client: Arc) -> ! { loop { match client.get_personal_jobs(Some(*UID)).await { Ok(resp) => { let job_requests = resp.into_builtin_vec(); process_request(job_requests, &client).await; } - Err(err) => { - error!("{:?}", err); - } + Err(err) => ErrChan::send(err), } let result: Vec = pop_completed().await.into_iter().collect(); if result.len() > 0 { if let Err(err) = client.report(&result).await { - error!("{:?}", err); + ErrChan::send(err) } } sleep(Duration::from_secs(ITERATION_LATENCY)).await; } } + +pub async fn run_forever() { + //daemonize(); + env_logger::init(); + let arg_ip = env::args().nth(1); + let client = Arc::new(ClientHandler::new(arg_ip.as_deref())); + let _cli = client.clone(); + panic::set_hook(Box::new(|panic_info| { + ErrChan::send(UError::Panic(panic_info.to_string())) + })); + tokio::spawn(error_reporting(_cli)); + do_stuff(client).await; +} diff --git a/bin/u_server/src/filters.rs b/bin/u_server/src/filters.rs index 126f11b..20ba803 100644 --- a/bin/u_server/src/filters.rs +++ b/bin/u_server/src/filters.rs @@ -70,7 +70,11 @@ pub fn make_filters() -> impl Filter .and(warp::path("report")) .and(get_content::>().and_then(Endpoints::report)); - let auth_token = format!("Bearer {}", env::var("ADMIN_AUTH_TOKEN").unwrap()).into_boxed_str(); + let auth_token = format!( + "Bearer {}", + env::var("ADMIN_AUTH_TOKEN").expect("No auth token provided") + ) + .into_boxed_str(); let auth_header = warp::header::exact("authorization", Box::leak(auth_token)); let auth_zone = (get_agents diff --git a/bin/u_server/src/handlers.rs b/bin/u_server/src/handlers.rs index d769206..cdf9e47 100644 --- a/bin/u_server/src/handlers.rs +++ b/bin/u_server/src/handlers.rs @@ -5,7 +5,7 @@ use serde::Serialize; use u_lib::{ messaging::{AsMsg, BaseMessage, Reportable}, models::*, - utils::OneOrVec, + utils::{OneOrVec, Stripped}, ULocalError, }; use uuid::Uuid; @@ -162,6 +162,11 @@ impl Endpoints { } Reportable::Error(e) => { let err = AgentError::from_msg(e, id); + warn!( + "{} reported an error: {}", + err.agent_id, + Stripped(&err.msg.as_str()) + ); UDB::lock_db().report_error(&err).unwrap(); } Reportable::Dummy => (), diff --git a/bin/u_server/src/u_server.rs b/bin/u_server/src/u_server.rs index e246bca..71d8185 100644 --- a/bin/u_server/src/u_server.rs +++ b/bin/u_server/src/u_server.rs @@ -1,22 +1,24 @@ #[macro_use] extern crate log; -#[macro_use] +#[cfg_attr(test, macro_use)] extern crate mockall; -#[macro_use] +#[cfg_attr(test, macro_use)] extern crate mockall_double; -// because of linking errors +// due to linking errors extern crate openssl; -#[macro_use] +// don't touch anything extern crate diesel; -// +// in this block + mod db; mod filters; mod handlers; use db::UDB; use filters::make_filters; +use std::path::PathBuf; use u_lib::{config::MASTER_PORT, models::*, utils::init_env}; use warp::Filter; @@ -28,7 +30,7 @@ fn prefill_jobs() { .with_alias("agent_hello") .build() .unwrap(); - UDB::lock_db().insert_jobs(&[agent_hello]).ok(); + UDB::lock_db().insert_jobs(&[agent_hello]).unwrap(); } fn init_logger() { @@ -60,11 +62,12 @@ fn init_all() { pub async fn serve() { init_all(); let routes = make_filters(); + let certs_dir = PathBuf::from("certs"); warp::serve(routes.with(warp::log("warp"))) .tls() - .cert_path("./certs/server.crt") - .key_path("./certs/server.key") - .client_auth_required_path("./certs/ca.crt") + .cert_path(certs_dir.join("server.crt")) + .key_path(certs_dir.join("server.key")) + .client_auth_required_path(certs_dir.join("ca.crt")) .run(([0, 0, 0, 0], MASTER_PORT)) .await; } diff --git a/integration/tests/helpers/panel.rs b/integration/tests/helpers/panel.rs index 41008f9..2c61245 100644 --- a/integration/tests/helpers/panel.rs +++ b/integration/tests/helpers/panel.rs @@ -2,7 +2,7 @@ use serde::de::DeserializeOwned; use serde_json::from_slice; use shlex::split; use std::process::{Command, Output}; -use u_lib::{datatypes::DataResult, messaging::AsMsg}; +use u_lib::{datatypes::DataResult, messaging::AsMsg, utils::VecDisplay}; const PANEL_BINARY: &str = "/u_panel"; @@ -21,8 +21,13 @@ impl Panel { pub fn output_argv(args: &[&str]) -> PanelResult { let result = Self::run(args); - assert!(result.status.success()); - from_slice(&result.stdout).map_err(|e| e.to_string()) + from_slice(&result.stdout).map_err(|e| { + eprintln!( + "Failed to decode panel response: '{}'", + VecDisplay(result.stdout) + ); + e.to_string() + }) } pub fn output(args: impl Into) -> PanelResult { @@ -39,7 +44,7 @@ impl Panel { fn status_is_ok(data: PanelResult) -> T { match data.unwrap() { DataResult::Ok(r) => r, - DataResult::Err(e) => panic!("Panel failed with erroneous status: {}", e), + DataResult::Err(e) => panic!("Panel failed: {}", e), } } diff --git a/lib/u_api_proc_macro/src/lib.rs b/lib/u_api_proc_macro/src/lib.rs index 46f1efa..f3d3a94 100644 --- a/lib/u_api_proc_macro/src/lib.rs +++ b/lib/u_api_proc_macro/src/lib.rs @@ -69,7 +69,7 @@ pub fn api_route(args: TokenStream, item: TokenStream) -> TokenStream { Ok(_) => Ok(()), Err(e) => Err(UError::from(e)) }; - match is_success { + let result = match is_success { Ok(_) => response.json::>() .await .map(|msg| msg.into_inner()) @@ -82,14 +82,14 @@ pub fn api_route(args: TokenStream, item: TokenStream) -> TokenStream { Err(UError::NetError(err_src, _)) => Err( UError::NetError( err_src, - response.text().await.unwrap() + response.text().await? ) ), _ => unreachable!() - } + }; + Ok(result?) } }; - //eprintln!("#!#! RESULT:\n{}", q); q.into() } diff --git a/lib/u_lib/Cargo.toml b/lib/u_lib/Cargo.toml index 5d56eff..6d66582 100644 --- a/lib/u_lib/Cargo.toml +++ b/lib/u_lib/Cargo.toml @@ -28,10 +28,13 @@ strum = { version = "0.20", features = ["derive"] } once_cell = "1.7.2" shlex = "1.0.0" u_api_proc_macro = { version = "*", path = "../u_api_proc_macro" } +crossbeam = "0.8.1" +backtrace = "0.3.61" [dependencies.diesel] version = "1.4.5" features = ["postgres", "uuid"] [dev-dependencies] -test-case = "1.1.0" \ No newline at end of file +test-case = "1.1.0" +rstest = "0.11" diff --git a/lib/u_lib/src/builder.rs b/lib/u_lib/src/builder.rs index 4075a24..726d74a 100644 --- a/lib/u_lib/src/builder.rs +++ b/lib/u_lib/src/builder.rs @@ -1,9 +1,4 @@ -use crate::{ - UError, UResult, cache::JobCache, executor::{Waiter, DynFut}, - models::{Agent, AssignedJob, JobMeta, JobType}, - messaging::Reportable, - utils::{CombinedResult, OneOrVec} -}; +use crate::{UError, UErrorBt, UResult, cache::JobCache, executor::{Waiter, DynFut}, messaging::Reportable, models::{Agent, AssignedJob, JobMeta, JobType}, utils::{CombinedResult, OneOrVec}}; use guess_host_triple::guess_host_triple; use std::collections::HashMap; @@ -15,11 +10,11 @@ impl JobBuilder { pub fn from_request(job_requests: impl OneOrVec) -> CombinedResult { let job_requests = job_requests.into_vec(); let mut prepared: Vec = vec![]; - let mut result = CombinedResult::new(); + let mut result = CombinedResult::::new(); for req in job_requests { let job_meta = JobCache::get(&req.job_id); if job_meta.is_none() { - result.err(UError::NoJob(req.job_id)); + result.err(UError::NoJob(req.job_id).into_bt()); continue; } let job_meta = job_meta.unwrap(); @@ -29,12 +24,12 @@ impl JobBuilder { JobType::Shell => { let meta = JobCache::get(&req.job_id).ok_or(UError::NoJob(req.job_id))?; let curr_platform = guess_host_triple().unwrap_or("unknown").to_string(); - //extend platform checking (partial check) + //TODO: extend platform checking (partial check) if meta.platform != curr_platform { return Err(UError::InsuitablePlatform( meta.platform.clone(), curr_platform, - )); + ).into()); } let job = AssignedJob::new(req.job_id, Some(&req)); prepared.push(Box::pin(job.run())) @@ -211,7 +206,7 @@ mod tests { if let Some(p) = payload { job = job.with_payload(p); } - let job = job.build().unwrap(); + let job = job.build().unwrap(); let job_result = JobBuilder::from_meta(job).unwrap_one().wait_one().await; let result = unwrap_enum!(job_result, Reportable::Assigned); let result = result.to_string_result(); @@ -298,7 +293,7 @@ mod tests { job = job.with_payload(p); } let err = job.build().unwrap_err(); - let err_msg = unwrap_enum!(err, UError::JobArgsError); + let err_msg = unwrap_enum!(err.err, UError::JobArgsError); assert!(err_msg.contains(err_str)); Ok(()) } diff --git a/lib/u_lib/src/cache.rs b/lib/u_lib/src/cache.rs index a486906..3af1724 100644 --- a/lib/u_lib/src/cache.rs +++ b/lib/u_lib/src/cache.rs @@ -1,4 +1,5 @@ use crate::models::JobMeta; +use lazy_static::lazy_static; use std::{ collections::HashMap, ops::Deref, diff --git a/lib/u_lib/src/config.rs b/lib/u_lib/src/config.rs index 88fc795..1fd815c 100644 --- a/lib/u_lib/src/config.rs +++ b/lib/u_lib/src/config.rs @@ -1,3 +1,4 @@ +use lazy_static::lazy_static; use uuid::Uuid; pub const MASTER_SERVER: &str = "127.0.0.1"; //Ipv4Addr::new(3,9,16,40) diff --git a/lib/u_lib/src/datatypes.rs b/lib/u_lib/src/datatypes.rs index d980734..983747f 100644 --- a/lib/u_lib/src/datatypes.rs +++ b/lib/u_lib/src/datatypes.rs @@ -1,4 +1,4 @@ -use crate::UError; +use crate::UErrorBt; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] @@ -6,5 +6,5 @@ use serde::{Deserialize, Serialize}; #[serde(tag = "status", content = "data")] pub enum DataResult { Ok(M), - Err(UError), + Err(UErrorBt), } diff --git a/lib/u_lib/src/errors/chan.rs b/lib/u_lib/src/errors/chan.rs new file mode 100644 index 0000000..cf78fd0 --- /dev/null +++ b/lib/u_lib/src/errors/chan.rs @@ -0,0 +1,28 @@ +use crate::UErrorBt; +use crossbeam::channel::{self, Receiver, Sender}; +use once_cell::sync::OnceCell; + +type ChanError = UErrorBt; +static ERR_CHAN: OnceCell = OnceCell::new(); + +pub struct ErrChan { + tx: Sender, + rx: Receiver, +} + +impl ErrChan { + fn get() -> &'static Self { + ERR_CHAN.get_or_init(|| { + let (tx, rx) = channel::bounded(20); + Self { tx, rx } + }) + } + + pub fn send(msg: impl Into) { + Self::get().tx.send(msg.into()).unwrap() + } + + pub fn recv() -> ChanError { + Self::get().rx.recv().unwrap() + } +} diff --git a/lib/u_lib/src/errors/mod.rs b/lib/u_lib/src/errors/mod.rs new file mode 100644 index 0000000..c910a5a --- /dev/null +++ b/lib/u_lib/src/errors/mod.rs @@ -0,0 +1,5 @@ +mod chan; +mod variants; + +pub use chan::*; +pub use variants::*; diff --git a/lib/u_lib/src/errors.rs b/lib/u_lib/src/errors/variants.rs similarity index 55% rename from lib/u_lib/src/errors.rs rename to lib/u_lib/src/errors/variants.rs index 5f46197..2b2570d 100644 --- a/lib/u_lib/src/errors.rs +++ b/lib/u_lib/src/errors/variants.rs @@ -1,16 +1,39 @@ +use backtrace::Backtrace as CrateBacktrace; use diesel::result::Error as DslError; use reqwest::Error as ReqError; use serde::{Deserialize, Serialize}; +use std::fmt; use thiserror::Error; use uuid::Uuid; -pub type UResult = std::result::Result; +pub type UResult = std::result::Result; pub type ULocalResult = std::result::Result; +#[derive(Error, Debug, Serialize, Deserialize, Clone)] +pub struct UErrorBt { + pub err: UError, + pub backtrace: String, +} + +impl fmt::Display for UErrorBt { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}\nBACKTRACE: \n{:?}", self.err, self.backtrace) + } +} + +impl From for UErrorBt { + fn from(err: UError) -> UErrorBt { + UErrorBt { + err, + backtrace: format!("{:?}", CrateBacktrace::new()), + } + } +} + #[derive(Error, Debug, Serialize, Deserialize, Clone)] pub enum UError { - #[error("Error: {0}")] - Raw(String), + #[error("Runtime error: {0}")] + Runtime(String), #[error("Connection error: {0}. Body: {1}")] NetError(String, String), @@ -33,11 +56,26 @@ pub enum UError { #[error("Job {0} doesn't exist")] NoJob(Uuid), - #[error("Error opening {0}: {1}")] + #[error("Error while processing {0}: {1}")] FSError(String, String), #[error("Wrong auth token")] WrongToken, + + #[error("Panicked: {0}")] + Panic(String), +} + +impl UError { + pub fn into_bt(self) -> UErrorBt { + UErrorBt::from(self) + } +} + +impl From for UErrorBt { + fn from(e: ReqError) -> Self { + UError::from(e).into_bt() + } } impl From for UError { diff --git a/lib/u_lib/src/executor.rs b/lib/u_lib/src/executor.rs index 4155cf1..dc4bc79 100644 --- a/lib/u_lib/src/executor.rs +++ b/lib/u_lib/src/executor.rs @@ -55,12 +55,12 @@ impl Waiter { tx.send(fid).await.unwrap(); result }; - let handle = JoinInfo { + let handler = JoinInfo { handle: spawn(task_wrapper), completed: false, collectable, }; - FUT_RESULTS.lock().await.insert(fid, handle); + FUT_RESULTS.lock().await.insert(fid, handler); } self } @@ -147,9 +147,9 @@ mod tests { }) }; assert_eq!(0, *val.lock().await); - spawn(async {}).await.ok(); + spawn(async {}).await.unwrap(); assert_eq!(5, *val.lock().await); - t.await.ok(); + t.await.unwrap(); assert_eq!(5, *val.lock().await); } } diff --git a/lib/u_lib/src/lib.rs b/lib/u_lib/src/lib.rs index 55f4e1e..c614b47 100644 --- a/lib/u_lib/src/lib.rs +++ b/lib/u_lib/src/lib.rs @@ -11,16 +11,13 @@ pub mod models; pub mod utils; pub use config::UID; -pub use errors::{UError, ULocalError, ULocalResult, UResult}; +pub use errors::{UError, UErrorBt, ULocalError, ULocalResult, UResult}; pub mod schema_exports { pub use crate::models::{Agentstate, Jobstate, Jobtype}; pub use diesel::sql_types::*; } -#[macro_use] -extern crate lazy_static; - #[macro_use] extern crate diesel; diff --git a/lib/u_lib/src/models/errors.rs b/lib/u_lib/src/models/error.rs similarity index 100% rename from lib/u_lib/src/models/errors.rs rename to lib/u_lib/src/models/error.rs diff --git a/lib/u_lib/src/models/jobs/assigned.rs b/lib/u_lib/src/models/jobs/assigned.rs index a4a0d35..a75d88c 100644 --- a/lib/u_lib/src/models/jobs/assigned.rs +++ b/lib/u_lib/src/models/jobs/assigned.rs @@ -1,6 +1,7 @@ use super::JobState; use crate::{ cache::JobCache, + errors::UError, messaging::Reportable, models::{schema::*, JobOutput}, utils::{systime_to_string, TempFile}, @@ -78,16 +79,22 @@ impl AssignedJob { pub async fn run(mut self) -> Reportable { let (argv, _payload) = { let meta = JobCache::get(&self.job_id).unwrap(); - let extracted_payload = meta - .payload - .as_ref() - .and_then(|p| TempFile::write_exec(p).ok()); - let argv = if let Some(ref p) = &extracted_payload { - meta.argv.replace("{}", &p.get_path()) + if let Some(ref payload) = meta.payload { + let extracted_payload = match TempFile::write_exec(payload) { + Ok(p) => p, + Err(e) => { + return Reportable::Error( + UError::Runtime(e.to_string()).into_bt().to_string(), + ) + } + }; + ( + meta.argv.replace("{}", &extracted_payload.get_path()), + Some(extracted_payload), + ) } else { - meta.argv.clone() - }; - (argv, extracted_payload) + (meta.argv.clone(), None) + } }; let mut split_cmd = shlex::split(&argv).unwrap().into_iter(); let cmd = split_cmd.nth(0).unwrap(); diff --git a/lib/u_lib/src/models/jobs/meta.rs b/lib/u_lib/src/models/jobs/meta.rs index 4cb07cc..79bce50 100644 --- a/lib/u_lib/src/models/jobs/meta.rs +++ b/lib/u_lib/src/models/jobs/meta.rs @@ -1,9 +1,10 @@ use super::JobType; -use crate::{models::schema::*, UError, UResult}; +use crate::{models::schema::*, utils::Stripped, UError, UResult}; use diesel::{Identifiable, Insertable, Queryable}; use guess_host_triple::guess_host_triple; use serde::{Deserialize, Serialize}; use std::fmt; +use std::str::from_utf8; use uuid::Uuid; #[derive(Serialize, Deserialize, Clone, Debug, Queryable, Identifiable, Insertable)] @@ -41,20 +42,12 @@ impl fmt::Display for JobMeta { out += &format!("\nPlatform: {}", self.platform); if let Some(ref payload) = self.payload { if self.exec_type == JobType::Shell { - let (pld_len, large) = { - let pl = payload.len(); - if pl > 20 { - (20, true) - } else { - (pl, false) - } + let payload = if let Ok(str_payload) = from_utf8(payload) { + Stripped(&str_payload).to_string() + } else { + Stripped(&payload).to_string() }; - let pld_beginning = &payload[..pld_len]; - out += &format!( - "\nPayload: {}{}", - String::from_utf8_lossy(pld_beginning), - if large { "" } else { " <...>" } - ); + out += &format!("\nPayload: {}", payload); } } write!(f, "{}", out) @@ -118,14 +111,15 @@ impl JobMetaBuilder { shlex::split(&inner.argv).ok_or(UError::JobArgsError("Shlex failed".into()))?; let empty_err = UError::JobArgsError("Empty argv".into()); if argv_parts.get(0).ok_or(empty_err.clone())?.len() == 0 { - return Err(empty_err); + return Err(empty_err.into()); } match inner.payload.as_ref() { Some(_) => { if !inner.argv.contains("{}") { return Err(UError::JobArgsError( "Argv contains no executable placeholder".into(), - )); + ) + .into()); } else { () } @@ -135,7 +129,8 @@ impl JobMetaBuilder { return Err(UError::JobArgsError( "No payload provided, but argv contains executable placeholder" .into(), - )); + ) + .into()); } else { () } diff --git a/lib/u_lib/src/models/mod.rs b/lib/u_lib/src/models/mod.rs index bd44781..3178567 100644 --- a/lib/u_lib/src/models/mod.rs +++ b/lib/u_lib/src/models/mod.rs @@ -1,6 +1,6 @@ mod agent; -mod errors; +mod error; mod jobs; pub mod schema; -pub use crate::models::{agent::*, errors::*, jobs::*}; +pub use crate::models::{agent::*, error::*, jobs::*}; diff --git a/lib/u_lib/src/utils/combined_result.rs b/lib/u_lib/src/utils/combined_result.rs index 2baf35f..aa0968d 100644 --- a/lib/u_lib/src/utils/combined_result.rs +++ b/lib/u_lib/src/utils/combined_result.rs @@ -1,7 +1,7 @@ use crate::utils::OneOrVec; -use crate::UError; +use crate::UErrorBt; -pub struct CombinedResult { +pub struct CombinedResult { ok: Vec, err: Vec, } diff --git a/lib/u_lib/src/utils/hexlify.rs b/lib/u_lib/src/utils/fmt/hexlify.rs similarity index 52% rename from lib/u_lib/src/utils/hexlify.rs rename to lib/u_lib/src/utils/fmt/hexlify.rs index d3a0d14..69bda69 100644 --- a/lib/u_lib/src/utils/hexlify.rs +++ b/lib/u_lib/src/utils/fmt/hexlify.rs @@ -10,3 +10,15 @@ impl<'a> fmt::LowerHex for Hexlify<'a> { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_hexlify() { + let data = b"\x5a\x6b\x23\x4f\xa3\x7f\x9e"; + let result = "5a6b234fa37f9e"; + assert_eq!(format!("{:x}", Hexlify(data)), result); + } +} diff --git a/lib/u_lib/src/utils/fmt/mod.rs b/lib/u_lib/src/utils/fmt/mod.rs new file mode 100644 index 0000000..aa14890 --- /dev/null +++ b/lib/u_lib/src/utils/fmt/mod.rs @@ -0,0 +1,5 @@ +mod hexlify; +mod stripped; + +pub use hexlify::*; +pub use stripped::*; diff --git a/lib/u_lib/src/utils/fmt/stripped.rs b/lib/u_lib/src/utils/fmt/stripped.rs new file mode 100644 index 0000000..8681a97 --- /dev/null +++ b/lib/u_lib/src/utils/fmt/stripped.rs @@ -0,0 +1,80 @@ +use std::fmt; +use std::iter::Iterator; +use std::slice::Iter as SliceIter; +use std::str::Chars; + +const MAX_DATA_LEN: usize = 200; + +pub trait Strippable { + //TODO: waiting for stabilizing GATs + type Item: fmt::Display; + type TypeIter: Iterator; + + fn length(&self) -> usize; + fn iterator(&self) -> Self::TypeIter; +} + +impl<'a> Strippable for &'a str { + type Item = char; + type TypeIter = Chars<'a>; + + fn length(&self) -> usize { + self.len() + } + + fn iterator(&self) -> Self::TypeIter { + self.chars() + } +} + +impl<'a> Strippable for &'a Vec { + type Item = &'a u8; + type TypeIter = SliceIter<'a, u8>; + + fn length(&self) -> usize { + self.len() + } + + fn iterator(&self) -> Self::TypeIter { + self.iter() + } +} +pub struct Stripped<'inner, Inner: Strippable + 'inner>(pub &'inner Inner); + +impl<'inner, Inner: Strippable + 'inner> Stripped<'inner, Inner> { + fn iter(&self) -> Inner::TypeIter { + self.0.iterator() + } + + fn placeholder(&self) -> &'static str { + if self.0.length() >= MAX_DATA_LEN { + " <...>" + } else { + "" + } + } +} + +impl<'inner, Inner: Strippable + 'inner> fmt::Display for Stripped<'inner, Inner> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let placeholder = self.placeholder(); + for c in self.iter().take(MAX_DATA_LEN - placeholder.len()) { + write!(f, "{}", c)?; + } + write!(f, "{}", placeholder) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rstest::*; + + #[rstest] + #[case("abc", 3)] + #[case("abcde".repeat(50), MAX_DATA_LEN)] + fn test_strip(#[case] input: impl Into, #[case] result_len: usize) { + let s = input.into(); + assert_eq!(Stripped(&s.as_str()).to_string().len(), result_len); + } +} diff --git a/lib/u_lib/src/utils/misc.rs b/lib/u_lib/src/utils/misc.rs index 375a6ad..3025f24 100644 --- a/lib/u_lib/src/utils/misc.rs +++ b/lib/u_lib/src/utils/misc.rs @@ -22,7 +22,7 @@ impl OneOrVec for Vec { #[macro_export] macro_rules! unwrap_enum { - ($src:ident, $t:path) => { + ($src:expr, $t:path) => { if let $t(result) = $src { result } else { diff --git a/lib/u_lib/src/utils/mod.rs b/lib/u_lib/src/utils/mod.rs index 0082b16..98853c2 100644 --- a/lib/u_lib/src/utils/mod.rs +++ b/lib/u_lib/src/utils/mod.rs @@ -1,6 +1,6 @@ mod combined_result; mod conv; -mod hexlify; +mod fmt; mod misc; mod storage; mod tempfile; @@ -8,7 +8,7 @@ mod vec_display; pub use combined_result::*; pub use conv::*; -pub use hexlify::*; +pub use fmt::*; pub use misc::*; pub use storage::*; pub use tempfile::*; diff --git a/lib/u_lib/src/utils/tempfile.rs b/lib/u_lib/src/utils/tempfile.rs index e76baa8..209ea46 100644 --- a/lib/u_lib/src/utils/tempfile.rs +++ b/lib/u_lib/src/utils/tempfile.rs @@ -1,3 +1,4 @@ +use crate::{UError, UResult}; use std::{env::temp_dir, fs, ops::Drop, os::unix::fs::PermissionsExt, path::PathBuf}; use uuid::Uuid; @@ -17,16 +18,17 @@ impl TempFile { Self { path } } - pub fn write_all(&self, data: &[u8]) -> Result<(), String> { - fs::write(&self.path, data).map_err(|e| e.to_string()) + pub fn write_all(&self, data: &[u8]) -> UResult<()> { + fs::write(&self.path, data).map_err(|e| UError::FSError(self.get_path(), e.to_string()))?; + Ok(()) } - pub fn write_exec(data: &[u8]) -> Result { + pub fn write_exec(data: &[u8]) -> UResult { let this = Self::new(); let path = this.get_path(); - this.write_all(data).map_err(|e| (path.clone(), e))?; + this.write_all(data)?; let perms = fs::Permissions::from_mode(0o555); - fs::set_permissions(&path, perms).map_err(|e| (path, e.to_string()))?; + fs::set_permissions(&path, perms).map_err(|e| UError::FSError(path, e.to_string()))?; Ok(this) } } diff --git a/scripts/cargo_musl.sh b/scripts/cargo_musl.sh index 0a8b3aa..e4149ef 100755 --- a/scripts/cargo_musl.sh +++ b/scripts/cargo_musl.sh @@ -4,6 +4,7 @@ source $(dirname $0)/rootdir.sh #set ROOTDIR ARGS=$@ docker run \ --env-file $ROOTDIR/.env \ + --env-file $ROOTDIR/.env.private \ -v $ROOTDIR:/volume \ -v cargo-cache:/root/.cargo/registry \ -w /volume \