From fb273dd54f77dfd9ae5da1e7078b860014a8fb85 Mon Sep 17 00:00:00 2001 From: "re:fi.64" Date: Sat, 26 Sep 2026 18:06:21 -0500 Subject: [PATCH] Fix race when querying a devnode major/minor before it exists Devnode points to the symlink, which is created asynchronously by udev, so when this code runs it might not actually exist yet. --- src/service.rs | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/service.rs b/src/service.rs index e6eac1d..203710b 100644 --- a/src/service.rs +++ b/src/service.rs @@ -44,13 +44,33 @@ impl IntoTonicResult for OperationResult { } } +fn major_minor_from_stat(stat: rustix::fs::Stat) -> (u32, u32) { + ( + rustix::fs::major(stat.st_rdev), + rustix::fs::minor(stat.st_rdev), + ) +} + fn devnode_major_minor(path: &str) -> tonic::Result<(u32, u32)> { let stat = rustix::fs::stat(path) .map_err(|e| tonic::Status::internal(format!("stat {path}: {e:?}")))?; - Ok(( - rustix::fs::major(stat.st_rdev), - rustix::fs::minor(stat.st_rdev), - )) + Ok(major_minor_from_stat(stat)) +} + +async fn devnode_major_minor_retry_missing(path: &str) -> tonic::Result<(u32, u32)> { + for _ in 0..50 { + match rustix::fs::stat(path) { + Ok(stat) => return Ok(major_minor_from_stat(stat)), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + tokio::time::sleep(Duration::from_millis(100)).await; + } + Err(e) => return Err(tonic::Status::internal(format!("stat {path}: {e:?}"))), + } + } + + Err(tonic::Status::deadline_exceeded(format!( + "waiting for devnode {path}" + ))) } fn get_size_bytes_unsigned(size_bytes: i64) -> tonic::Result { @@ -415,7 +435,7 @@ impl lvmd::lv_service_server::LvService for Service { info!("retrieving devnode information"); let devnode = fs.proxy.devnode().await.into_tonic_result()?; - let (dev_major, dev_minor) = devnode_major_minor(&devnode)?; + let (dev_major, dev_minor) = devnode_major_minor_retry_missing(&devnode).await?; #[allow(deprecated)] Ok(tonic::Response::new(lvmd::CreateLvResponse { @@ -595,7 +615,7 @@ impl lvmd::lv_service_server::LvService for Service { info!("retrieving devnode information"); let devnode = snapshot.proxy.devnode().await.into_tonic_result()?; - let (dev_major, dev_minor) = devnode_major_minor(&devnode)?; + let (dev_major, dev_minor) = devnode_major_minor_retry_missing(&devnode).await?; #[allow(deprecated)] Ok(tonic::Response::new(lvmd::CreateLvSnapshotResponse { -- 2.51.2