From: Wolfgang Bumiller <w.bumiller@proxmox.com>
To: pmg-devel@lists.proxmox.com
Subject: [pmg-devel] [PATCH log-tracker] drop 'time' dependency
Date: Tue, 4 Jan 2022 14:57:23 +0100 [thread overview]
Message-ID: <20220104135723.111515-1-w.bumiller@proxmox.com> (raw)
We're using a very old version of it and the functions we
actually use are available in glibc anyway.
The only difference I found was that the result of
glibc's `strptime()`'s `%s` does *not* want an additional
`.to_local()` call anymore...
... which was weird anyway.
As a minor optimization I pass the format string as `&CStr`
to avoid the memcpy.
(The CString::new() call in `strptime()` only happens during
parameter parsing)
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
---
Cargo.toml | 1 -
src/main.rs | 75 ++++++++---------------
src/time.rs | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 194 insertions(+), 51 deletions(-)
create mode 100644 src/time.rs
diff --git a/Cargo.toml b/Cargo.toml
index a975d39..7c31157 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,4 +14,3 @@ anyhow = "1"
clap = "2.32"
flate2 = "1.0"
libc = "0.2.48"
-time = "0.1.42"
diff --git a/src/main.rs b/src/main.rs
index fb06463..bf9783b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -15,6 +15,9 @@ use libc::time_t;
use clap::{App, Arg};
+mod time;
+use time::{Tm, CAL_MTOD};
+
fn main() -> Result<(), Error> {
let matches = App::new(clap::crate_name!())
.version(clap::crate_version!())
@@ -114,7 +117,7 @@ fn main() -> Result<(), Error> {
)
.get_matches();
- let mut parser = Parser::new();
+ let mut parser = Parser::new()?;
parser.handle_args(matches)?;
println!("# LogReader: {}", std::process::id());
@@ -144,12 +147,12 @@ fn main() -> Result<(), Error> {
println!(
"# Start: {} ({})",
- time::strftime("%F %T", &parser.start_tm)?,
+ time::strftime(c_str!("%F %T"), &parser.start_tm)?,
parser.options.start
);
println!(
"# End: {} ({})",
- time::strftime("%F %T", &parser.end_tm)?,
+ time::strftime(c_str!("%F %T"), &parser.end_tm)?,
parser.options.end
);
@@ -1743,10 +1746,10 @@ struct Parser {
}
impl Parser {
- fn new() -> Self {
- let ltime = time::now();
+ fn new() -> Result<Self, Error> {
+ let ltime = Tm::now_local()?;
- Self {
+ Ok(Self {
sentries: HashMap::new(),
fentries: HashMap::new(),
qentries: HashMap::new(),
@@ -1760,12 +1763,12 @@ impl Parser {
count: 0,
buffered_stdout: BufWriter::with_capacity(4 * 1024 * 1024, std::io::stdout()),
options: Options::default(),
- start_tm: time::empty_tm(),
- end_tm: time::empty_tm(),
+ start_tm: Tm::zero(),
+ end_tm: Tm::zero(),
ctime: 0,
string_match: false,
lines: 0,
- }
+ })
}
fn free_sentry(&mut self, sentry_pid: u64) {
@@ -1976,40 +1979,38 @@ impl Parser {
}
if let Some(start) = args.value_of("start") {
- if let Ok(res) = time::strptime(start, "%F %T") {
- self.options.start = mkgmtime(&res);
+ if let Ok(res) = time::strptime(start, c_str!("%F %T")) {
+ self.options.start = res.as_utc_to_epoch();
self.start_tm = res;
- } else if let Ok(res) = time::strptime(start, "%s") {
- let res = res.to_local();
- self.options.start = mkgmtime(&res);
+ } else if let Ok(res) = time::strptime(start, c_str!("%s")) {
+ self.options.start = res.as_utc_to_epoch();
self.start_tm = res;
} else {
bail!("failed to parse start time");
}
} else {
- let mut ltime = time::now();
+ let mut ltime = Tm::now_local()?;
ltime.tm_sec = 0;
ltime.tm_min = 0;
ltime.tm_hour = 0;
- self.options.start = mkgmtime(<ime);
+ self.options.start = ltime.as_utc_to_epoch();
self.start_tm = ltime;
}
if let Some(end) = args.value_of("end") {
- if let Ok(res) = time::strptime(end, "%F %T") {
- self.options.end = mkgmtime(&res);
+ if let Ok(res) = time::strptime(end, c_str!("%F %T")) {
+ self.options.end = res.as_utc_to_epoch();
self.end_tm = res;
- } else if let Ok(res) = time::strptime(end, "%s") {
- let res = res.to_local();
- self.options.end = mkgmtime(&res);
+ } else if let Ok(res) = time::strptime(end, c_str!("%s")) {
+ self.options.end = res.as_utc_to_epoch();
self.end_tm = res;
} else {
bail!("failed to parse end time");
}
} else {
- let ltime = time::now();
- self.options.end = mkgmtime(<ime);
- self.end_tm = ltime;
+ // FIXME: just use libc::time(NULL) here
+ self.options.end = unsafe { libc::time(std::ptr::null_mut()) };
+ self.end_tm = Tm::at_local(self.options.end)?;
}
if self.options.end < self.options.start {
@@ -2171,30 +2172,6 @@ fn get_or_create_fentry(
}
}
-fn mkgmtime(tm: &time::Tm) -> time_t {
- let mut res: time_t;
-
- let mut year = tm.tm_year as i64 + 1900;
- let mon = tm.tm_mon;
-
- res = (year - 1970) * 365 + CAL_MTOD[mon as usize];
-
- if mon <= 1 {
- year -= 1;
- }
-
- res += (year - 1968) / 4;
- res -= (year - 1900) / 100;
- res += (year - 1600) / 400;
-
- res += (tm.tm_mday - 1) as i64;
- res = res * 24 + tm.tm_hour as i64;
- res = res * 60 + tm.tm_min as i64;
- res = res * 60 + tm.tm_sec as i64;
-
- res
-}
-
const LOGFILES: [&str; 32] = [
"/var/log/syslog",
"/var/log/syslog.1",
@@ -2397,8 +2374,6 @@ fn parse_time<'a>(
Some((ltime, data))
}
-const CAL_MTOD: [i64; 12] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
-
type ByteSlice<'a> = &'a [u8];
/// Parse Host, Service and PID at the beginning of data. Returns a tuple of (host, service, pid, remaining_text).
fn parse_host_service_pid(data: &[u8]) -> Option<(ByteSlice, ByteSlice, u64, ByteSlice)> {
diff --git a/src/time.rs b/src/time.rs
new file mode 100644
index 0000000..5851496
--- /dev/null
+++ b/src/time.rs
@@ -0,0 +1,169 @@
+//! Time support library.
+//!
+//! Contains wrappers for `strftime`, `strptime`, `libc::localtime_r`.
+
+use std::ffi::{CStr, CString};
+use std::fmt;
+use std::mem::MaybeUninit;
+
+use anyhow::{bail, format_err, Error};
+
+/// Calender month index to *non-leap-year* day-of-the-year.
+pub const CAL_MTOD: [i64; 12] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
+
+/// Shortcut for generating an `&'static CStr`.
+#[macro_export]
+macro_rules! c_str {
+ ($data:expr) => {{
+ let bytes = concat!($data, "\0");
+ unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(bytes.as_bytes()) }
+ }};
+}
+
+// Wrapper around `libc::tm` providing `Debug` and some helper methods.
+pub struct Tm(libc::tm);
+
+impl std::ops::Deref for Tm {
+ type Target = libc::tm;
+
+ fn deref(&self) -> &libc::tm {
+ &self.0
+ }
+}
+
+impl std::ops::DerefMut for Tm {
+ fn deref_mut(&mut self) -> &mut libc::tm {
+ &mut self.0
+ }
+}
+
+// (or add libc feature 'extra-traits' but that's the only struct we need it for...)
+impl fmt::Debug for Tm {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("Tm")
+ .field("tm_sec", &self.tm_sec)
+ .field("tm_min", &self.tm_min)
+ .field("tm_hour", &self.tm_hour)
+ .field("tm_mday", &self.tm_mday)
+ .field("tm_mon", &self.tm_mon)
+ .field("tm_year", &self.tm_year)
+ .field("tm_wday", &self.tm_wday)
+ .field("tm_yday", &self.tm_yday)
+ .field("tm_isdst", &self.tm_isdst)
+ .field("tm_gmtoff", &self.tm_gmtoff)
+ .field("tm_zone", &self.tm_zone)
+ .finish()
+ }
+}
+
+/// These are now exposed via `libc`, but they're part of glibc.
+mod imports {
+ extern "C" {
+ pub fn strftime(
+ s: *mut libc::c_char,
+ max: libc::size_t,
+ format: *const libc::c_char,
+ tm: *const libc::tm,
+ ) -> libc::size_t;
+
+ pub fn strptime(
+ s: *const libc::c_char,
+ format: *const libc::c_char,
+ tm: *mut libc::tm,
+ ) -> *const libc::c_char;
+ }
+}
+
+impl Tm {
+ /// A zero-initialized time struct.
+ #[inline(always)]
+ pub fn zero() -> Self {
+ unsafe { std::mem::zeroed() }
+ }
+
+ /// Get the current time in the local timezone.
+ pub fn now_local() -> Result<Self, Error> {
+ Self::at_local(unsafe { libc::time(std::ptr::null_mut()) })
+ }
+
+ /// Get a local time from a unix time stamp.
+ pub fn at_local(time: libc::time_t) -> Result<Self, Error> {
+ let mut out = MaybeUninit::<libc::tm>::uninit();
+ Ok(Self(unsafe {
+ if libc::localtime_r(&time, out.as_mut_ptr()).is_null() {
+ bail!("failed to convert timestamp to local time");
+ }
+ out.assume_init()
+ }))
+ }
+
+ /// Assume this is an UTC time and convert it to a unix time stamp.
+ ///
+ /// Equivalent to `timegm(3)`, the gmt equivalent of `mktime(3)`.
+ pub fn as_utc_to_epoch(&self) -> libc::time_t {
+ let mut year = self.0.tm_year as i64 + 1900;
+ let mon = self.0.tm_mon;
+
+ let mut res: libc::time_t = (year - 1970) * 365 + CAL_MTOD[mon as usize];
+
+ if mon <= 1 {
+ year -= 1;
+ }
+
+ res += (year - 1968) / 4;
+ res -= (year - 1900) / 100;
+ res += (year - 1600) / 400;
+
+ res += (self.0.tm_mday - 1) as i64;
+ res = res * 24 + self.0.tm_hour as i64;
+ res = res * 60 + self.0.tm_min as i64;
+ res = res * 60 + self.0.tm_sec as i64;
+
+ res
+ }
+}
+
+/// Wrapper around `strftime(3)` to format time strings.
+pub fn strftime(format: &CStr, tm: &Tm) -> Result<String, Error> {
+ let mut buf = MaybeUninit::<[u8; 64]>::uninit();
+
+ let size = unsafe {
+ imports::strftime(
+ buf.as_mut_ptr() as *mut libc::c_char,
+ 64,
+ format.as_ptr(),
+ &tm.0,
+ )
+ };
+ if size == 0 {
+ bail!("failed to format time");
+ }
+ let size = size as usize;
+
+ let buf = unsafe { buf.assume_init() };
+
+ std::str::from_utf8(&buf[..size])
+ .map_err(|_| format_err!("formatted time was not valid utf-8"))
+ .map(str::to_string)
+}
+
+/// Wrapper around `strptime(3)` to parse time strings.
+pub fn strptime(time: &str, format: &CStr) -> Result<Tm, Error> {
+ let mut out = MaybeUninit::<libc::tm>::uninit();
+
+ let time = CString::new(time).map_err(|_| format_err!("time string contains nul bytes"))?;
+
+ let end = unsafe {
+ imports::strptime(
+ time.as_ptr() as *const libc::c_char,
+ format.as_ptr(),
+ out.as_mut_ptr(),
+ )
+ };
+
+ if end.is_null() {
+ bail!("failed to parse time string {:?}", time);
+ }
+
+ Ok(Tm(unsafe { out.assume_init() }))
+}
--
2.30.2
next reply other threads:[~2022-01-04 13:57 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-01-04 13:57 Wolfgang Bumiller [this message]
2022-01-05 19:01 ` Stoiko Ivanov
2022-02-14 18:09 ` Stoiko Ivanov
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20220104135723.111515-1-w.bumiller@proxmox.com \
--to=w.bumiller@proxmox.com \
--cc=pmg-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.