Drop parse_duration for fundu-systemd (#1246) (#1468)

`parse_duration` is no longer maintained upstream; `fundu-system` seems like the best option to provide most of the functionality provided by parse_duration and minimize deps.

Fixes #1246

Dep diffstat: +3 -6

## Caveats
- I've done basically no testing of this PR beyond `cargo test`
- See my comment in #1246 for regression risk; this is a breaking change. Is there a document I should update to make note of this? Should I rebase this for `next-v3` instead?
- In theory CVE-2021-29932 is fixed by this PR as `fundu-systemd` doesn't support exponents, but I've done no verification that this is really the case beyond the info in #1246

Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/1468
Reviewed-by: Alex <lx@deuxfleurs.fr>
This commit is contained in:
Wesley Hershberger
2026-06-17 13:57:21 +00:00
committed by Alex
parent 1d1456f1d6
commit 76aae3cb96
11 changed files with 54 additions and 83 deletions
+1 -1
View File
@@ -39,8 +39,8 @@ backtrace.workspace = true
bytes.workspace = true
bytesize.workspace = true
chrono.workspace = true
fundu-systemd.workspace = true
timeago.workspace = true
parse_duration.workspace = true
hex.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
+1 -1
View File
@@ -393,7 +393,7 @@ impl Cli {
&self,
opt: CleanupIncompleteUploadsOpt,
) -> Result<(), Error> {
let older_than = parse_duration::parse::parse(&opt.older_than)
let older_than = garage_util::time::parse_duration(&opt.older_than)
.ok_or_message("Invalid duration passed for --older-than parameter")?;
for b in opt.buckets.iter() {
+1 -1
View File
@@ -169,7 +169,7 @@ pub fn table_list_abbr<T: IntoIterator<Item = S>, S: AsRef<str>>(values: T) -> S
pub fn parse_expires_in(expires_in: &Option<String>) -> Result<Option<DateTime<Utc>>, Error> {
expires_in
.as_ref()
.map(|x| parse_duration::parse::parse(x).map(|dur| Utc::now() + dur))
.map(|x| garage_util::time::parse_duration(x).map(|dur| Utc::now() + dur))
.transpose()
.ok_or_message("Invalid duration passed for --expires-in parameter")
}
+4 -4
View File
@@ -494,7 +494,7 @@ pub struct KeyNewOpt {
#[structopt(default_value = "Unnamed key")]
pub name: String,
/// Set an expiration time for the access key
/// (see `docs.rs/parse_duration` for date format)
/// (see `docs.rs/fundu-systemd` for date format)
#[structopt(long = "expires-in")]
pub expires_in: Option<String>,
}
@@ -505,7 +505,7 @@ pub struct KeySetOpt {
pub key_pattern: String,
/// Set an expiration time for the access key
/// (see `docs.rs/parse_duration` for date format)
/// (see `docs.rs/fundu-systemd` for date format)
#[structopt(long = "expires-in")]
pub expires_in: Option<String>,
/// Set the access key to never expire
@@ -616,7 +616,7 @@ pub enum AdminTokenOperation {
pub struct AdminTokenCreateOp {
/// Set a name for the token
pub name: Option<String>,
/// Set an expiration time for the token (see `docs.rs/parse_duration` for date
/// Set an expiration time for the token (see `docs.rs/fundu-systemd` for date
/// format)
#[structopt(long = "expires-in")]
pub expires_in: Option<String>,
@@ -638,7 +638,7 @@ pub struct AdminTokenSetOp {
/// Name or prefix of the ID of the token to modify
pub api_token: String,
/// Set an expiration time for the token (see `docs.rs/parse_duration` for date
/// Set an expiration time for the token (see `docs.rs/fundu-systemd` for date
/// format)
#[structopt(long = "expires-in")]
pub expires_in: Option<String>,
-1
View File
@@ -30,7 +30,6 @@ thiserror.workspace = true
hex.workspace = true
http.workspace = true
base64.workspace = true
parse_duration.workspace = true
tracing.workspace = true
rand.workspace = true
zstd.workspace = true
+1 -1
View File
@@ -304,7 +304,7 @@ impl Garage {
self.k2v.spawn_workers(bg);
if let Some(itv) = self.config.metadata_auto_snapshot_interval.as_deref() {
let interval = parse_duration::parse(itv)
let interval = garage_util::time::parse_duration(itv)
.ok_or_message("Invalid `metadata_auto_snapshot_interval`")?;
if interval < std::time::Duration::from_secs(600) {
return Err(Error::Message(
+2
View File
@@ -22,6 +22,8 @@ arc-swap.workspace = true
async-trait.workspace = true
blake2.workspace = true
bytesize.workspace = true
fundu.workspace = true
fundu-systemd.workspace = true
thiserror.workspace = true
hexdump.workspace = true
xxhash-rust.workspace = true
+14 -1
View File
@@ -1,6 +1,9 @@
//! Module containing helper functions to manipulate time
use chrono::{SecondsFormat, TimeZone, Utc};
use std::time::{SystemTime, UNIX_EPOCH};
use std::convert::TryInto;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use crate::error::Error;
/// Returns milliseconds since UNIX Epoch
pub fn now_msec() -> u64 {
@@ -28,3 +31,13 @@ pub fn msec_to_rfc3339(msecs: u64) -> String {
let timestamp = Utc.timestamp_opt(secs, nanos).unwrap();
timestamp.to_rfc3339_opts(SecondsFormat::Millis, true)
}
/// Parse a systemd-style duration using fundu
pub fn parse_duration(s: &str) -> Result<Duration, Error> {
fundu_systemd::parse(s, Some(fundu::TimeUnit::Second), None)
.map_err(|err| Error::Message(err.to_string()))
.and_then(|dur| {
dur.try_into()
.map_err(|err: fundu::TryFromDurationError| Error::Message(err.to_string()))
})
}