forked from proxmox-mirrors/proxmox
add proxmox-time crate
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
7db0a3c6df
commit
9b6fe4aceb
@ -6,6 +6,7 @@ members = [
|
||||
"proxmox-http",
|
||||
"proxmox-sortable-macro",
|
||||
"proxmox-tfa",
|
||||
"proxmox-time",
|
||||
"proxmox-uuid",
|
||||
]
|
||||
exclude = [
|
||||
|
1
Makefile
1
Makefile
@ -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
12
proxmox-time/Cargo.toml
Normal 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" ] }
|
5
proxmox-time/debian/changelog
Normal file
5
proxmox-time/debian/changelog
Normal 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
|
34
proxmox-time/debian/control
Normal file
34
proxmox-time/debian/control
Normal 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.
|
16
proxmox-time/debian/copyright
Normal file
16
proxmox-time/debian/copyright
Normal 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/>.
|
7
proxmox-time/debian/debcargo.toml
Normal file
7
proxmox-time/debian/debcargo.toml
Normal 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"
|
@ -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]
|
@ -1,6 +1,5 @@
|
||||
use anyhow::Error;
|
||||
|
||||
use super::{gmtime, localtime, timegm, timelocal};
|
||||
use super::Error;
|
||||
|
||||
/// Safely Manipulate Date and Time
|
||||
pub struct TmEditor {
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user