From 61125214e7b220337f109456b0f885f883279d25 Mon Sep 17 00:00:00 2001 From: benthecarman Date: Tue, 21 Apr 2026 13:50:30 -0500 Subject: [PATCH] Add FilesystemStoreV2Error for v1 data detection FilesystemStoreV2::new previously returned io::Error with ErrorKind::InvalidData when the data directory contained top-level files left behind by FilesystemStore (v1). That forced us to match on an error that could potentially be given by our normal io calls. This adds a dedicated FilesystemStoreV2Error enum with a V1DataDetected(PathBuf) so we can distinguish between normal io errors and an old V1 fs store. --- lightning-persister/src/fs_store/v2.rs | 68 ++++++++++++++++++++------ 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/lightning-persister/src/fs_store/v2.rs b/lightning-persister/src/fs_store/v2.rs index 773b22ac3fb..2f79cae0da3 100644 --- a/lightning-persister/src/fs_store/v2.rs +++ b/lightning-persister/src/fs_store/v2.rs @@ -10,6 +10,7 @@ use lightning::util::persist::{ use std::fs; use std::path::PathBuf; use std::time::UNIX_EPOCH; +use std::{error, fmt, io}; #[cfg(feature = "tokio")] use core::future::Future; @@ -17,6 +18,48 @@ use core::future::Future; use lightning::util::persist::{KVStore, PaginatedKVStore}; use std::sync::Arc; +/// An error returned when constructing a [`FilesystemStoreV2`]. +#[derive(Debug)] +pub enum FilesystemStoreV2Error { + /// The data directory contains a file at the top level, indicating it was previously used + /// by [`FilesystemStore`] (v1). Contains the path of the offending file. + /// + /// [`FilesystemStore`]: crate::fs_store::v1::FilesystemStore + V1DataDetected(PathBuf), + /// An I/O error occurred while inspecting the data directory. + Io(io::Error), +} + +impl fmt::Display for FilesystemStoreV2Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::V1DataDetected(path) => write!( + f, + "Found file `{}` in the top-level data directory. \ + This indicates the directory was previously used by FilesystemStore (v1). \ + Please migrate your data or use a different directory.", + path.display() + ), + Self::Io(err) => write!(f, "{}", err), + } + } +} + +impl error::Error for FilesystemStoreV2Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + Self::V1DataDetected(_) => None, + Self::Io(err) => Some(err), + } + } +} + +impl From for FilesystemStoreV2Error { + fn from(err: io::Error) -> Self { + Self::Io(err) + } +} + /// A [`KVStore`] and [`KVStoreSync`] implementation that writes to and reads from the file system. /// /// This is version 2 of the filesystem store which provides: @@ -53,25 +96,18 @@ pub struct FilesystemStoreV2 { impl FilesystemStoreV2 { /// Constructs a new [`FilesystemStoreV2`]. /// - /// Returns an error if the data directory already exists and contains files at the top level, - /// which would indicate it was previously used by a [`FilesystemStore`] (v1). The v2 store - /// expects only directories (namespaces) at the top level. + /// Returns [`FilesystemStoreV2Error::V1DataDetected`] if the data directory already exists + /// and contains files at the top level, which would indicate it was previously used by a + /// [`FilesystemStore`] (v1). The v2 store expects only directories (namespaces) at the top + /// level. /// /// [`FilesystemStore`]: crate::fs_store::v1::FilesystemStore - pub fn new(data_dir: PathBuf) -> std::io::Result { + pub fn new(data_dir: PathBuf) -> Result { if data_dir.exists() { for entry in fs::read_dir(&data_dir)? { let entry = entry?; if entry.file_type()?.is_file() { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!( - "Found file `{}` in the top-level data directory. \ - This indicates the directory was previously used by FilesystemStore (v1). \ - Please migrate your data or use a different directory.", - entry.path().display() - ), - )); + return Err(FilesystemStoreV2Error::V1DataDetected(entry.path())); } } } @@ -667,10 +703,10 @@ mod tests { // V2 construction should fail match FilesystemStoreV2::new(temp_path.clone()) { - Err(err) => { - assert_eq!(err.kind(), std::io::ErrorKind::InvalidData); - assert!(err.to_string().contains("FilesystemStore (v1)")); + Err(FilesystemStoreV2Error::V1DataDetected(path)) => { + assert_eq!(path, temp_path.join("some_key")); }, + Err(err) => panic!("Expected V1DataDetected, got {:?}", err), Ok(_) => panic!("Expected error for directory with top-level files"), }