add proxmox-time crate

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2021-10-04 11:25:56 +02:00
parent 7db0a3c6df
commit 9b6fe4aceb
10 changed files with 205 additions and 75 deletions

View File

@ -6,6 +6,7 @@ members = [
"proxmox-http",
"proxmox-sortable-macro",
"proxmox-tfa",
"proxmox-time",
"proxmox-uuid",
]
exclude = [

View File

@ -7,6 +7,7 @@ CRATES = \
proxmox-http \
proxmox-sortable-macro \
proxmox-tfa \
proxmox-time \
proxmox-uuid
# By default we just run checks:

12
proxmox-time/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "proxmox-time"
version = "1.0.0"
authors = ["Proxmox Support Team <support@proxmox.com>"]
edition = "2018"
license = "AGPL-3"
description = "time utilities and TmEditor"
exclude = [ "debian" ]
[dependencies]
libc = { version = "0.2", features = [ "extra_traits" ] }

View File

@ -0,0 +1,5 @@
rust-proxmox-time (1.0.0-1) stable; urgency=medium
* initial split out of `librust-proxmox-dev`
-- Proxmox Support Team <support@proxmox.com> Mon, 04 Oct 2021 11:38:53 +0200

View File

@ -0,0 +1,34 @@
Source: rust-proxmox-time
Section: rust
Priority: optional
Build-Depends: debhelper (>= 12),
dh-cargo (>= 24),
cargo:native <!nocheck>,
rustc:native <!nocheck>,
libstd-rust-dev <!nocheck>,
librust-libc-0.2+default-dev <!nocheck>,
librust-libc-0.2+extra-traits-dev <!nocheck>
Maintainer: Proxmox Support Team <support@proxmox.com>
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.

View File

@ -0,0 +1,16 @@
Copyright (C) 2021 Proxmox Server Solutions GmbH
This software is written by Proxmox Server Solutions GmbH <support@proxmox.com>
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 <http://www.gnu.org/licenses/>.

View File

@ -0,0 +1,7 @@
overlay = "."
crate_src_path = ".."
maintainer = "Proxmox Support Team <support@proxmox.com>"
[source]
vcs_git = "git://git.proxmox.com/git/proxmox.git"
vcs_browser = "https://git.proxmox.com/?p=proxmox.git"

View File

@ -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<io::Error>,
}
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<i64, Error> {
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<libc::tm, Error> {
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<libc::tm, Error> {
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<String, Error> {
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<String, Error> {
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<String, Error> {
/// Parse RFC3339 into Unix epoch
pub fn parse_rfc3339(input_str: &str) -> Result<i64, Error> {
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<i64, Error> {
let input = input_str.as_bytes();
let expect = |pos: usize, c: u8| {
@ -219,76 +284,67 @@ pub fn parse_rfc3339(input_str: &str) -> Result<i64, Error> {
Ok(digit - 48)
};
let check_max = |i: i32, max: i32| {
fn check_max(i: i32, max: i32) -> Result<i32, Error> {
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]

View File

@ -1,6 +1,5 @@
use anyhow::Error;
use super::{gmtime, localtime, timegm, timelocal};
use super::Error;
/// Safely Manipulate Date and Time
pub struct TmEditor {

View File

@ -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;