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" name = "format_table"
version = "0.1.1" 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]] [[package]]
name = "futures" name = "futures"
version = "0.3.32" version = "0.3.32"
@@ -1502,6 +1526,7 @@ dependencies = [
"chrono", "chrono",
"crc-fast", "crc-fast",
"format_table", "format_table",
"fundu-systemd",
"futures", "futures",
"garage_api_admin", "garage_api_admin",
"garage_api_common", "garage_api_common",
@@ -1528,7 +1553,6 @@ dependencies = [
"opentelemetry", "opentelemetry",
"opentelemetry-otlp", "opentelemetry-otlp",
"opentelemetry-prometheus", "opentelemetry-prometheus",
"parse_duration",
"serde_json", "serde_json",
"sha1 0.10.6", "sha1 0.10.6",
"sha2 0.10.9", "sha2 0.10.9",
@@ -1749,7 +1773,6 @@ dependencies = [
"garage_util", "garage_util",
"hex", "hex",
"http 1.4.0", "http 1.4.0",
"parse_duration",
"rand 0.9.4", "rand 0.9.4",
"serde", "serde",
"serde_bytes", "serde_bytes",
@@ -1849,6 +1872,8 @@ dependencies = [
"blake2", "blake2",
"bytesize", "bytesize",
"chrono", "chrono",
"fundu",
"fundu-systemd",
"futures", "futures",
"garage_db", "garage_db",
"garage_net", "garage_net",
@@ -3223,41 +3248,6 @@ dependencies = [
"windows-sys 0.61.2", "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]] [[package]]
name = "num-conv" name = "num-conv"
version = "0.2.1" version = "0.2.1"
@@ -3273,29 +3263,6 @@ dependencies = [
"num-traits", "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]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@@ -3464,17 +3431,6 @@ dependencies = [
"windows-link", "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]] [[package]]
name = "password-hash" name = "password-hash"
version = "0.5.0" version = "0.5.0"
+2 -1
View File
@@ -53,6 +53,8 @@ cfg-if = "1.0"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
crc-fast = "1.9" crc-fast = "1.9"
crypto-common = "0.1" crypto-common = "0.1"
fundu = "2.0"
fundu-systemd = "0.3"
gethostname = "1.1" gethostname = "1.1"
git-version = "0.3" git-version = "0.3"
hex = "0.4" hex = "0.4"
@@ -68,7 +70,6 @@ mktemp = "0.5"
nix = { version = "0.31", default-features = false, features = ["fs"] } nix = { version = "0.31", default-features = false, features = ["fs"] }
nom = "8.0" nom = "8.0"
parking_lot = "0.12" parking_lot = "0.12"
parse_duration = "2.1"
paste = "1.0" paste = "1.0"
pin-project = "1.1" pin-project = "1.1"
pnet_datalink = "0.35" 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, 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. or in [`metadata_snapshots_dir`](#metadata_snapshots_dir) if it is set.
This parameter can take any duration string that can be parsed by 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 Snapshots can allow to recover from situations where the metadata DB file is
corrupted, for instance after an unclean shutdown. See [this corrupted, for instance after an unclean shutdown. See [this
+1 -1
View File
@@ -39,8 +39,8 @@ backtrace.workspace = true
bytes.workspace = true bytes.workspace = true
bytesize.workspace = true bytesize.workspace = true
chrono.workspace = true chrono.workspace = true
fundu-systemd.workspace = true
timeago.workspace = true timeago.workspace = true
parse_duration.workspace = true
hex.workspace = true hex.workspace = true
tracing.workspace = true tracing.workspace = true
tracing-subscriber.workspace = true tracing-subscriber.workspace = true
+1 -1
View File
@@ -393,7 +393,7 @@ impl Cli {
&self, &self,
opt: CleanupIncompleteUploadsOpt, opt: CleanupIncompleteUploadsOpt,
) -> Result<(), Error> { ) -> 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")?; .ok_or_message("Invalid duration passed for --older-than parameter")?;
for b in opt.buckets.iter() { 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> { pub fn parse_expires_in(expires_in: &Option<String>) -> Result<Option<DateTime<Utc>>, Error> {
expires_in expires_in
.as_ref() .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() .transpose()
.ok_or_message("Invalid duration passed for --expires-in parameter") .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")] #[structopt(default_value = "Unnamed key")]
pub name: String, pub name: String,
/// Set an expiration time for the access key /// 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")] #[structopt(long = "expires-in")]
pub expires_in: Option<String>, pub expires_in: Option<String>,
} }
@@ -505,7 +505,7 @@ pub struct KeySetOpt {
pub key_pattern: String, pub key_pattern: String,
/// Set an expiration time for the access key /// 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")] #[structopt(long = "expires-in")]
pub expires_in: Option<String>, pub expires_in: Option<String>,
/// Set the access key to never expire /// Set the access key to never expire
@@ -616,7 +616,7 @@ pub enum AdminTokenOperation {
pub struct AdminTokenCreateOp { pub struct AdminTokenCreateOp {
/// Set a name for the token /// Set a name for the token
pub name: Option<String>, 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) /// format)
#[structopt(long = "expires-in")] #[structopt(long = "expires-in")]
pub expires_in: Option<String>, pub expires_in: Option<String>,
@@ -638,7 +638,7 @@ pub struct AdminTokenSetOp {
/// Name or prefix of the ID of the token to modify /// Name or prefix of the ID of the token to modify
pub api_token: String, 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) /// format)
#[structopt(long = "expires-in")] #[structopt(long = "expires-in")]
pub expires_in: Option<String>, pub expires_in: Option<String>,
-1
View File
@@ -30,7 +30,6 @@ thiserror.workspace = true
hex.workspace = true hex.workspace = true
http.workspace = true http.workspace = true
base64.workspace = true base64.workspace = true
parse_duration.workspace = true
tracing.workspace = true tracing.workspace = true
rand.workspace = true rand.workspace = true
zstd.workspace = true zstd.workspace = true
+1 -1
View File
@@ -304,7 +304,7 @@ impl Garage {
self.k2v.spawn_workers(bg); self.k2v.spawn_workers(bg);
if let Some(itv) = self.config.metadata_auto_snapshot_interval.as_deref() { 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`")?; .ok_or_message("Invalid `metadata_auto_snapshot_interval`")?;
if interval < std::time::Duration::from_secs(600) { if interval < std::time::Duration::from_secs(600) {
return Err(Error::Message( return Err(Error::Message(
+2
View File
@@ -22,6 +22,8 @@ arc-swap.workspace = true
async-trait.workspace = true async-trait.workspace = true
blake2.workspace = true blake2.workspace = true
bytesize.workspace = true bytesize.workspace = true
fundu.workspace = true
fundu-systemd.workspace = true
thiserror.workspace = true thiserror.workspace = true
hexdump.workspace = true hexdump.workspace = true
xxhash-rust.workspace = true xxhash-rust.workspace = true
+14 -1
View File
@@ -1,6 +1,9 @@
//! Module containing helper functions to manipulate time //! Module containing helper functions to manipulate time
use chrono::{SecondsFormat, TimeZone, Utc}; 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 /// Returns milliseconds since UNIX Epoch
pub fn now_msec() -> u64 { 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(); let timestamp = Utc.timestamp_opt(secs, nanos).unwrap();
timestamp.to_rfc3339_opts(SecondsFormat::Millis, true) 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()))
})
}