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
Generated
+27 -71
View File
@@ -1399,6 +1399,30 @@ dependencies = [
name = "format_table"
version = "0.1.1"
[[package]]
name = "fundu"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ce12752fc64f35be3d53e0a57017cd30970f0cffd73f62c791837d8845badbd"
dependencies = [
"fundu-core",
]
[[package]]
name = "fundu-core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e463452e2d8b7600d38dcea1ed819773a57f0d710691bfc78db3961bd3f4c3ba"
[[package]]
name = "fundu-systemd"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8641ae8f534f853a552beb3a9f12cc72fd1343260964af82cf09fda66e3d94e"
dependencies = [
"fundu",
]
[[package]]
name = "futures"
version = "0.3.32"
@@ -1502,6 +1526,7 @@ dependencies = [
"chrono",
"crc-fast",
"format_table",
"fundu-systemd",
"futures",
"garage_api_admin",
"garage_api_common",
@@ -1528,7 +1553,6 @@ dependencies = [
"opentelemetry",
"opentelemetry-otlp",
"opentelemetry-prometheus",
"parse_duration",
"serde_json",
"sha1 0.10.6",
"sha2 0.10.9",
@@ -1749,7 +1773,6 @@ dependencies = [
"garage_util",
"hex",
"http 1.4.0",
"parse_duration",
"rand 0.9.4",
"serde",
"serde_bytes",
@@ -1849,6 +1872,8 @@ dependencies = [
"blake2",
"bytesize",
"chrono",
"fundu",
"fundu-systemd",
"futures",
"garage_db",
"garage_net",
@@ -3223,41 +3248,6 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "num"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-conv"
version = "0.2.1"
@@ -3273,29 +3263,6 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
dependencies = [
"autocfg",
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -3464,17 +3431,6 @@ dependencies = [
"windows-link",
]
[[package]]
name = "parse_duration"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7037e5e93e0172a5a96874380bf73bc6ecef022e26fa25f2be26864d6b3ba95d"
dependencies = [
"lazy_static",
"num",
"regex",
]
[[package]]
name = "password-hash"
version = "0.5.0"
+2 -1
View File
@@ -53,6 +53,8 @@ cfg-if = "1.0"
chrono = { version = "0.4", features = ["serde"] }
crc-fast = "1.9"
crypto-common = "0.1"
fundu = "2.0"
fundu-systemd = "0.3"
gethostname = "1.1"
git-version = "0.3"
hex = "0.4"
@@ -68,7 +70,6 @@ mktemp = "0.5"
nix = { version = "0.31", default-features = false, features = ["fs"] }
nom = "8.0"
parking_lot = "0.12"
parse_duration = "2.1"
paste = "1.0"
pin-project = "1.1"
pnet_datalink = "0.35"
+1 -1
View File
@@ -448,7 +448,7 @@ If this value is set, Garage will automatically take a snapshot of the metadata
DB file at a regular interval and save it in the metadata directory,
or in [`metadata_snapshots_dir`](#metadata_snapshots_dir) if it is set.
This parameter can take any duration string that can be parsed by
the [`parse_duration`](https://docs.rs/parse_duration/latest/parse_duration/#syntax) crate.
the [`fundu_systemd`](https://docs.rs/fundu-systemd) crate.
Snapshots can allow to recover from situations where the metadata DB file is
corrupted, for instance after an unclean shutdown. See [this
+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()))
})
}