From 9b6fe4aceb31bea25827cb0c5dbaec6b4fd30427 Mon Sep 17 00:00:00 2001 From: Wolfgang Bumiller Date: Mon, 4 Oct 2021 11:25:56 +0200 Subject: [PATCH] add proxmox-time crate Signed-off-by: Wolfgang Bumiller --- Cargo.toml | 1 + Makefile | 1 + proxmox-time/Cargo.toml | 12 ++ proxmox-time/debian/changelog | 5 + proxmox-time/debian/control | 34 +++ proxmox-time/debian/copyright | 16 ++ proxmox-time/debian/debcargo.toml | 7 + .../time/mod.rs => proxmox-time/src/lib.rs | 200 +++++++++++------- .../time => proxmox-time/src}/tm_editor.rs | 3 +- proxmox/src/tools/mod.rs | 1 - 10 files changed, 205 insertions(+), 75 deletions(-) create mode 100644 proxmox-time/Cargo.toml create mode 100644 proxmox-time/debian/changelog create mode 100644 proxmox-time/debian/control create mode 100644 proxmox-time/debian/copyright create mode 100644 proxmox-time/debian/debcargo.toml rename proxmox/src/tools/time/mod.rs => proxmox-time/src/lib.rs (72%) rename {proxmox/src/tools/time => proxmox-time/src}/tm_editor.rs (99%) diff --git a/Cargo.toml b/Cargo.toml index 9208737d..20c4bca8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "proxmox-http", "proxmox-sortable-macro", "proxmox-tfa", + "proxmox-time", "proxmox-uuid", ] exclude = [ diff --git a/Makefile b/Makefile index caf25dc8..08468cdc 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ CRATES = \ proxmox-http \ proxmox-sortable-macro \ proxmox-tfa \ + proxmox-time \ proxmox-uuid # By default we just run checks: diff --git a/proxmox-time/Cargo.toml b/proxmox-time/Cargo.toml new file mode 100644 index 00000000..691cd82a --- /dev/null +++ b/proxmox-time/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "proxmox-time" +version = "1.0.0" +authors = ["Proxmox Support Team "] +edition = "2018" +license = "AGPL-3" +description = "time utilities and TmEditor" + +exclude = [ "debian" ] + +[dependencies] +libc = { version = "0.2", features = [ "extra_traits" ] } diff --git a/proxmox-time/debian/changelog b/proxmox-time/debian/changelog new file mode 100644 index 00000000..a301119d --- /dev/null +++ b/proxmox-time/debian/changelog @@ -0,0 +1,5 @@ +rust-proxmox-time (1.0.0-1) stable; urgency=medium + + * initial split out of `librust-proxmox-dev` + + -- Proxmox Support Team Mon, 04 Oct 2021 11:38:53 +0200 diff --git a/proxmox-time/debian/control b/proxmox-time/debian/control new file mode 100644 index 00000000..ba8abaee --- /dev/null +++ b/proxmox-time/debian/control @@ -0,0 +1,34 @@ +Source: rust-proxmox-time +Section: rust +Priority: optional +Build-Depends: debhelper (>= 12), + dh-cargo (>= 24), + cargo:native , + rustc:native , + libstd-rust-dev , + librust-libc-0.2+default-dev , + librust-libc-0.2+extra-traits-dev +Maintainer: Proxmox Support Team +Standards-Version: 4.5.1 +Vcs-Git: git://git.proxmox.com/git/proxmox.git +Vcs-Browser: https://git.proxmox.com/?p=proxmox.git +Rules-Requires-Root: no + +Package: librust-proxmox-time-dev +Architecture: any +Multi-Arch: same +Depends: + ${misc:Depends}, + librust-libc-0.2+default-dev, + librust-libc-0.2+extra-traits-dev +Provides: + librust-proxmox-time+default-dev (= ${binary:Version}), + librust-proxmox-time-1-dev (= ${binary:Version}), + librust-proxmox-time-1+default-dev (= ${binary:Version}), + librust-proxmox-time-1.0-dev (= ${binary:Version}), + librust-proxmox-time-1.0+default-dev (= ${binary:Version}), + librust-proxmox-time-1.0.0-dev (= ${binary:Version}), + librust-proxmox-time-1.0.0+default-dev (= ${binary:Version}) +Description: Time utilities and TmEditor - Rust source code + This package contains the source for the Rust proxmox-time crate, packaged by + debcargo for use with cargo and dh-cargo. diff --git a/proxmox-time/debian/copyright b/proxmox-time/debian/copyright new file mode 100644 index 00000000..5661ef60 --- /dev/null +++ b/proxmox-time/debian/copyright @@ -0,0 +1,16 @@ +Copyright (C) 2021 Proxmox Server Solutions GmbH + +This software is written by Proxmox Server Solutions GmbH + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . diff --git a/proxmox-time/debian/debcargo.toml b/proxmox-time/debian/debcargo.toml new file mode 100644 index 00000000..b7864cdb --- /dev/null +++ b/proxmox-time/debian/debcargo.toml @@ -0,0 +1,7 @@ +overlay = "." +crate_src_path = ".." +maintainer = "Proxmox Support Team " + +[source] +vcs_git = "git://git.proxmox.com/git/proxmox.git" +vcs_browser = "https://git.proxmox.com/?p=proxmox.git" diff --git a/proxmox/src/tools/time/mod.rs b/proxmox-time/src/lib.rs similarity index 72% rename from proxmox/src/tools/time/mod.rs rename to proxmox-time/src/lib.rs index 31174bdf..62e68d7f 100644 --- a/proxmox/src/tools/time/mod.rs +++ b/proxmox-time/src/lib.rs @@ -1,9 +1,61 @@ -use anyhow::{bail, format_err, Error}; use std::ffi::{CStr, CString}; +use std::fmt; +use std::io; mod tm_editor; pub use tm_editor::*; +#[derive(Debug)] +pub struct Error { + msg: String, + cause: Option, +} + +impl Error { + const fn new(msg: String) -> Self { + Self { + msg, + cause: None, + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match &self.cause { + Some(cause) => write!(f, "{}: {}", self.msg, cause), + None => f.write_str(&self.msg), + } + } +} + +impl std::error::Error for Error {} + +/// Bail with io::Error::last_os_error called before any formatting is done. +macro_rules! io_bail { + ($($msg:tt)+) => { + { + let cause = std::io::Error::last_os_error(); // this must happen first + let msg = format!($($msg)+); + return Err(Error { cause: Some(cause), msg }); + } + } +} + +/// Format a textual error. +macro_rules! format_err { + ($($msg:tt)+) => { + Error::new(format!($($msg)+)) + } +} + +/// Bail with just a textual error. +macro_rules! bail { + ($($msg:tt)+) => { + return Err(format_err!($($msg)+)); + } +} + /// Safe bindings to libc timelocal /// /// We set tm_isdst to -1. @@ -27,7 +79,7 @@ pub fn timegm(t: &mut libc::tm) -> Result { let epoch = unsafe { libc::timegm(t) }; if epoch == -1 { - bail!("libc::timegm failed for {:?}", t); + io_bail!("libc::timegm failed for {:?}", t); } Ok(epoch) } @@ -54,7 +106,7 @@ pub fn localtime(epoch: i64) -> Result { unsafe { if libc::localtime_r(&epoch, &mut result).is_null() { - bail!("libc::localtime failed for '{}'", epoch); + io_bail!("libc::localtime failed for '{}'", epoch); } } @@ -67,7 +119,7 @@ pub fn gmtime(epoch: i64) -> Result { unsafe { if libc::gmtime_r(&epoch, &mut result).is_null() { - bail!("libc::gmtime failed for '{}'", epoch); + io_bail!("libc::gmtime failed for '{}'", epoch); } } @@ -123,7 +175,8 @@ extern "C" { /// Safe bindings to libc strftime pub fn strftime(format: &str, t: &libc::tm) -> Result { - let format = CString::new(format)?; + let format = CString::new(format) + .map_err(|err| format_err!("{}", err))?; let mut buf = vec![0u8; 8192]; let res = unsafe { @@ -134,13 +187,17 @@ pub fn strftime(format: &str, t: &libc::tm) -> Result { t as *const libc::tm, ) }; + if res == !0 { // -1,, it's unsigned + io_bail!("strftime failed"); + } + let len = res as usize; - let len = nix::errno::Errno::result(res).map(|r| r as usize)?; if len == 0 { - bail!("strftime: result len is 0 (string too large)"); + return Err(Error::new(format!("strftime: result len is 0 (string too large)"))); }; - let c_str = CStr::from_bytes_with_nul(&buf[..len + 1])?; + let c_str = CStr::from_bytes_with_nul(&buf[..len + 1]) + .map_err(|err| format_err!("{}", err))?; let str_slice: &str = c_str.to_str().unwrap(); Ok(str_slice.to_owned()) } @@ -202,6 +259,14 @@ pub fn epoch_to_rfc3339(epoch: i64) -> Result { /// Parse RFC3339 into Unix epoch pub fn parse_rfc3339(input_str: &str) -> Result { + parse_rfc3339_do(input_str) + .map_err(|mut err| { + err.msg = format!("failed to parse rfc3339 timestamp ({:?}) - {}", input_str, err); + err + }) +} + +fn parse_rfc3339_do(input_str: &str) -> Result { let input = input_str.as_bytes(); let expect = |pos: usize, c: u8| { @@ -219,76 +284,67 @@ pub fn parse_rfc3339(input_str: &str) -> Result { Ok(digit - 48) }; - let check_max = |i: i32, max: i32| { + fn check_max(i: i32, max: i32) -> Result { if i > max { bail!("value too large ({} > {})", i, max); } Ok(i) + } + + if input.len() < 20 || input.len() > 25 { + bail!("timestamp of unexpected length"); + } + + let tz = input[19]; + + match tz { + b'Z' => { + if input.len() != 20 { + bail!("unexpected length in UTC timestamp"); + } + } + b'+' | b'-' => { + if input.len() != 25 { + bail!("unexpected length in timestamp"); + } + } + _ => bail!("unexpected timezone indicator"), + } + + let mut tm = TmEditor::new(true); + + tm.set_year(digit(0)? * 1000 + digit(1)? * 100 + digit(2)? * 10 + digit(3)?)?; + expect(4, b'-')?; + tm.set_mon(check_max(digit(5)? * 10 + digit(6)?, 12)?)?; + expect(7, b'-')?; + tm.set_mday(check_max(digit(8)? * 10 + digit(9)?, 31)?)?; + + expect(10, b'T')?; + + tm.set_hour(check_max(digit(11)? * 10 + digit(12)?, 23)?)?; + expect(13, b':')?; + tm.set_min(check_max(digit(14)? * 10 + digit(15)?, 59)?)?; + expect(16, b':')?; + tm.set_sec(check_max(digit(17)? * 10 + digit(18)?, 60)?)?; + + let epoch = tm.into_epoch()?; + if tz == b'Z' { + return Ok(epoch); + } + + let hours = check_max(digit(20)? * 10 + digit(21)?, 23)?; + expect(22, b':')?; + let mins = check_max(digit(23)? * 10 + digit(24)?, 59)?; + + let offset = (hours * 3600 + mins * 60) as i64; + + let epoch = match tz { + b'+' => epoch - offset, + b'-' => epoch + offset, + _ => unreachable!(), // already checked above }; - crate::try_block!({ - if input.len() < 20 || input.len() > 25 { - bail!("timestamp of unexpected length"); - } - - let tz = input[19]; - - match tz { - b'Z' => { - if input.len() != 20 { - bail!("unexpected length in UTC timestamp"); - } - } - b'+' | b'-' => { - if input.len() != 25 { - bail!("unexpected length in timestamp"); - } - } - _ => bail!("unexpected timezone indicator"), - } - - let mut tm = TmEditor::new(true); - - tm.set_year(digit(0)? * 1000 + digit(1)? * 100 + digit(2)? * 10 + digit(3)?)?; - expect(4, b'-')?; - tm.set_mon(check_max(digit(5)? * 10 + digit(6)?, 12)?)?; - expect(7, b'-')?; - tm.set_mday(check_max(digit(8)? * 10 + digit(9)?, 31)?)?; - - expect(10, b'T')?; - - tm.set_hour(check_max(digit(11)? * 10 + digit(12)?, 23)?)?; - expect(13, b':')?; - tm.set_min(check_max(digit(14)? * 10 + digit(15)?, 59)?)?; - expect(16, b':')?; - tm.set_sec(check_max(digit(17)? * 10 + digit(18)?, 60)?)?; - - let epoch = tm.into_epoch()?; - if tz == b'Z' { - return Ok(epoch); - } - - let hours = check_max(digit(20)? * 10 + digit(21)?, 23)?; - expect(22, b':')?; - let mins = check_max(digit(23)? * 10 + digit(24)?, 59)?; - - let offset = (hours * 3600 + mins * 60) as i64; - - let epoch = match tz { - b'+' => epoch - offset, - b'-' => epoch + offset, - _ => unreachable!(), // already checked above - }; - - Ok(epoch) - }) - .map_err(|err| { - format_err!( - "failed to parse rfc3339 timestamp ({:?}) - {}", - input_str, - err - ) - }) + Ok(epoch) } #[test] diff --git a/proxmox/src/tools/time/tm_editor.rs b/proxmox-time/src/tm_editor.rs similarity index 99% rename from proxmox/src/tools/time/tm_editor.rs rename to proxmox-time/src/tm_editor.rs index 52ebf27e..dc7b6aae 100644 --- a/proxmox/src/tools/time/tm_editor.rs +++ b/proxmox-time/src/tm_editor.rs @@ -1,6 +1,5 @@ -use anyhow::Error; - use super::{gmtime, localtime, timegm, timelocal}; +use super::Error; /// Safely Manipulate Date and Time pub struct TmEditor { diff --git a/proxmox/src/tools/mod.rs b/proxmox/src/tools/mod.rs index c7aa2c09..17a5b6bd 100644 --- a/proxmox/src/tools/mod.rs +++ b/proxmox/src/tools/mod.rs @@ -16,7 +16,6 @@ pub mod io; pub mod mmap; pub mod parse; pub mod serde; -pub mod time; pub mod vec; pub mod systemd;