|
1 | 1 | //! Objects related to [`FilesystemStore`] live here. |
2 | 2 | use crate::utils::{check_namespace_key_validity, is_valid_kvstore_str}; |
3 | 3 |
|
4 | | -use lightning::util::persist::KVStore; |
| 4 | +use lightning::util::persist::{KVStore, MigratableKVStore}; |
5 | 5 | use lightning::util::string::PrintableString; |
6 | 6 |
|
7 | 7 | use std::collections::HashMap; |
@@ -316,96 +316,187 @@ impl KVStore for FilesystemStore { |
316 | 316 | let entry = entry?; |
317 | 317 | let p = entry.path(); |
318 | 318 |
|
319 | | - if let Some(ext) = p.extension() { |
320 | | - #[cfg(target_os = "windows")] |
321 | | - { |
322 | | - // Clean up any trash files lying around. |
323 | | - if ext == "trash" { |
324 | | - fs::remove_file(p).ok(); |
325 | | - continue; |
326 | | - } |
327 | | - } |
328 | | - if ext == "tmp" { |
329 | | - continue; |
330 | | - } |
| 319 | + if !dir_entry_is_key(&p)? { |
| 320 | + continue; |
331 | 321 | } |
332 | 322 |
|
333 | | - let metadata = p.metadata()?; |
| 323 | + let key = get_key_from_dir_entry(&p, &prefixed_dest)?; |
334 | 324 |
|
335 | | - // We allow the presence of directories in the empty primary namespace and just skip them. |
336 | | - if metadata.is_dir() { |
337 | | - continue; |
| 325 | + keys.push(key); |
| 326 | + } |
| 327 | + |
| 328 | + self.garbage_collect_locks(); |
| 329 | + |
| 330 | + Ok(keys) |
| 331 | + } |
| 332 | +} |
| 333 | + |
| 334 | +fn dir_entry_is_key(p: &Path) -> Result<bool, lightning::io::Error> { |
| 335 | + if let Some(ext) = p.extension() { |
| 336 | + #[cfg(target_os = "windows")] |
| 337 | + { |
| 338 | + // Clean up any trash files lying around. |
| 339 | + if ext == "trash" { |
| 340 | + fs::remove_file(p).ok(); |
| 341 | + return Ok(false); |
338 | 342 | } |
| 343 | + } |
| 344 | + if ext == "tmp" { |
| 345 | + return Ok(false); |
| 346 | + } |
| 347 | + } |
339 | 348 |
|
340 | | - // If we otherwise don't find a file at the given path something went wrong. |
341 | | - if !metadata.is_file() { |
| 349 | + let metadata = p.metadata().map_err(|e| { |
| 350 | + let msg = format!( |
| 351 | + "Failed to list keys at path {}: {}", |
| 352 | + PrintableString(p.to_str().unwrap_or_default()), |
| 353 | + e |
| 354 | + ); |
| 355 | + lightning::io::Error::new(lightning::io::ErrorKind::Other, msg) |
| 356 | + })?; |
| 357 | + |
| 358 | + // We allow the presence of directories in the empty primary namespace and just skip them. |
| 359 | + if metadata.is_dir() { |
| 360 | + return Ok(false); |
| 361 | + } |
| 362 | + |
| 363 | + // If we otherwise don't find a file at the given path something went wrong. |
| 364 | + if !metadata.is_file() { |
| 365 | + debug_assert!( |
| 366 | + false, |
| 367 | + "Failed to list keys at path {}: file couldn't be accessed.", |
| 368 | + PrintableString(p.to_str().unwrap_or_default()) |
| 369 | + ); |
| 370 | + let msg = format!( |
| 371 | + "Failed to list keys at path {}: file couldn't be accessed.", |
| 372 | + PrintableString(p.to_str().unwrap_or_default()) |
| 373 | + ); |
| 374 | + return Err(lightning::io::Error::new(lightning::io::ErrorKind::Other, msg)); |
| 375 | + } |
| 376 | + |
| 377 | + Ok(true) |
| 378 | +} |
| 379 | + |
| 380 | +fn get_key_from_dir_entry(p: &Path, base_path: &Path) -> Result<String, lightning::io::Error> { |
| 381 | + match p.strip_prefix(&base_path) { |
| 382 | + Ok(stripped_path) => { |
| 383 | + if let Some(relative_path) = stripped_path.to_str() { |
| 384 | + if is_valid_kvstore_str(relative_path) { |
| 385 | + return Ok(relative_path.to_string()); |
| 386 | + } else { |
| 387 | + debug_assert!( |
| 388 | + false, |
| 389 | + "Failed to list keys of path {}: file path is not valid key", |
| 390 | + PrintableString(p.to_str().unwrap_or_default()) |
| 391 | + ); |
| 392 | + let msg = format!( |
| 393 | + "Failed to list keys of path {}: file path is not valid key", |
| 394 | + PrintableString(p.to_str().unwrap_or_default()) |
| 395 | + ); |
| 396 | + return Err(lightning::io::Error::new(lightning::io::ErrorKind::Other, msg)); |
| 397 | + } |
| 398 | + } else { |
342 | 399 | debug_assert!( |
343 | 400 | false, |
344 | | - "Failed to list keys of {}/{}: file couldn't be accessed.", |
345 | | - PrintableString(primary_namespace), |
346 | | - PrintableString(secondary_namespace) |
| 401 | + "Failed to list keys of path {}: file path is not valid UTF-8", |
| 402 | + PrintableString(p.to_str().unwrap_or_default()) |
347 | 403 | ); |
348 | 404 | let msg = format!( |
349 | | - "Failed to list keys of {}/{}: file couldn't be accessed.", |
350 | | - PrintableString(primary_namespace), |
351 | | - PrintableString(secondary_namespace) |
| 405 | + "Failed to list keys of path {}: file path is not valid UTF-8", |
| 406 | + PrintableString(p.to_str().unwrap_or_default()) |
352 | 407 | ); |
353 | 408 | return Err(lightning::io::Error::new(lightning::io::ErrorKind::Other, msg)); |
354 | 409 | } |
| 410 | + }, |
| 411 | + Err(e) => { |
| 412 | + debug_assert!( |
| 413 | + false, |
| 414 | + "Failed to list keys of path {}: {}", |
| 415 | + PrintableString(p.to_str().unwrap_or_default()), |
| 416 | + e |
| 417 | + ); |
| 418 | + let msg = format!( |
| 419 | + "Failed to list keys of path {}: {}", |
| 420 | + PrintableString(p.to_str().unwrap_or_default()), |
| 421 | + e |
| 422 | + ); |
| 423 | + return Err(lightning::io::Error::new(lightning::io::ErrorKind::Other, msg)); |
| 424 | + }, |
| 425 | + } |
| 426 | +} |
| 427 | + |
| 428 | +impl MigratableKVStore for FilesystemStore { |
| 429 | + fn list_all_keys(&self) -> Result<Vec<(String, String, String)>, lightning::io::Error> { |
| 430 | + let prefixed_dest = &self.data_dir; |
| 431 | + if !prefixed_dest.exists() { |
| 432 | + return Ok(Vec::new()); |
| 433 | + } |
| 434 | + |
| 435 | + let mut keys = Vec::new(); |
| 436 | + |
| 437 | + 'primary_loop: for primary_entry in fs::read_dir(prefixed_dest)? { |
| 438 | + let primary_path = primary_entry?.path(); |
| 439 | + |
| 440 | + if dir_entry_is_key(&primary_path)? { |
| 441 | + let primary_namespace = String::new(); |
| 442 | + let secondary_namespace = String::new(); |
| 443 | + let key = get_key_from_dir_entry(&primary_path, prefixed_dest)?; |
| 444 | + keys.push((primary_namespace, secondary_namespace, key)); |
| 445 | + continue 'primary_loop; |
| 446 | + } |
| 447 | + |
| 448 | + // The primary_entry is actually also a directory. |
| 449 | + 'secondary_loop: for secondary_entry in fs::read_dir(&primary_path)? { |
| 450 | + let secondary_path = secondary_entry?.path(); |
| 451 | + |
| 452 | + if dir_entry_is_key(&secondary_path)? { |
| 453 | + let primary_namespace = get_key_from_dir_entry(&primary_path, prefixed_dest)?; |
| 454 | + let secondary_namespace = String::new(); |
| 455 | + let key = get_key_from_dir_entry(&secondary_path, &primary_path)?; |
| 456 | + keys.push((primary_namespace, secondary_namespace, key)); |
| 457 | + continue 'secondary_loop; |
| 458 | + } |
355 | 459 |
|
356 | | - match p.strip_prefix(&prefixed_dest) { |
357 | | - Ok(stripped_path) => { |
358 | | - if let Some(relative_path) = stripped_path.to_str() { |
359 | | - if is_valid_kvstore_str(relative_path) { |
360 | | - keys.push(relative_path.to_string()) |
361 | | - } |
| 460 | + // The secondary_entry is actually also a directory. |
| 461 | + for tertiary_entry in fs::read_dir(&secondary_path)? { |
| 462 | + let tertiary_entry = tertiary_entry?; |
| 463 | + let tertiary_path = tertiary_entry.path(); |
| 464 | + |
| 465 | + if dir_entry_is_key(&tertiary_path)? { |
| 466 | + let primary_namespace = |
| 467 | + get_key_from_dir_entry(&primary_path, prefixed_dest)?; |
| 468 | + let secondary_namespace = |
| 469 | + get_key_from_dir_entry(&secondary_path, &primary_path)?; |
| 470 | + let key = get_key_from_dir_entry(&tertiary_path, &secondary_path)?; |
| 471 | + keys.push((primary_namespace, secondary_namespace, key)); |
362 | 472 | } else { |
363 | 473 | debug_assert!( |
364 | 474 | false, |
365 | | - "Failed to list keys of {}/{}: file path is not valid UTF-8", |
366 | | - PrintableString(primary_namespace), |
367 | | - PrintableString(secondary_namespace) |
| 475 | + "Failed to list keys of path {}: only two levels of namespaces are supported", |
| 476 | + PrintableString(tertiary_path.to_str().unwrap_or_default()) |
368 | 477 | ); |
369 | 478 | let msg = format!( |
370 | | - "Failed to list keys of {}/{}: file path is not valid UTF-8", |
371 | | - PrintableString(primary_namespace), |
372 | | - PrintableString(secondary_namespace) |
| 479 | + "Failed to list keys of path {}: only two levels of namespaces are supported", |
| 480 | + PrintableString(tertiary_path.to_str().unwrap_or_default()) |
373 | 481 | ); |
374 | 482 | return Err(lightning::io::Error::new( |
375 | 483 | lightning::io::ErrorKind::Other, |
376 | 484 | msg, |
377 | 485 | )); |
378 | 486 | } |
379 | | - }, |
380 | | - Err(e) => { |
381 | | - debug_assert!( |
382 | | - false, |
383 | | - "Failed to list keys of {}/{}: {}", |
384 | | - PrintableString(primary_namespace), |
385 | | - PrintableString(secondary_namespace), |
386 | | - e |
387 | | - ); |
388 | | - let msg = format!( |
389 | | - "Failed to list keys of {}/{}: {}", |
390 | | - PrintableString(primary_namespace), |
391 | | - PrintableString(secondary_namespace), |
392 | | - e |
393 | | - ); |
394 | | - return Err(lightning::io::Error::new(lightning::io::ErrorKind::Other, msg)); |
395 | | - }, |
| 487 | + } |
396 | 488 | } |
397 | 489 | } |
398 | | - |
399 | | - self.garbage_collect_locks(); |
400 | | - |
401 | 490 | Ok(keys) |
402 | 491 | } |
403 | 492 | } |
404 | 493 |
|
405 | 494 | #[cfg(test)] |
406 | 495 | mod tests { |
407 | 496 | use super::*; |
408 | | - use crate::test_utils::{do_read_write_remove_list_persist, do_test_store}; |
| 497 | + use crate::test_utils::{ |
| 498 | + do_read_write_remove_list_persist, do_test_data_migration, do_test_store, |
| 499 | + }; |
409 | 500 |
|
410 | 501 | use bitcoin::Txid; |
411 | 502 |
|
@@ -438,6 +529,19 @@ mod tests { |
438 | 529 | do_read_write_remove_list_persist(&fs_store); |
439 | 530 | } |
440 | 531 |
|
| 532 | + #[test] |
| 533 | + fn test_data_migration() { |
| 534 | + let mut source_temp_path = std::env::temp_dir(); |
| 535 | + source_temp_path.push("test_data_migration_source"); |
| 536 | + let mut source_store = FilesystemStore::new(source_temp_path); |
| 537 | + |
| 538 | + let mut target_temp_path = std::env::temp_dir(); |
| 539 | + target_temp_path.push("test_data_migration_target"); |
| 540 | + let mut target_store = FilesystemStore::new(target_temp_path); |
| 541 | + |
| 542 | + do_test_data_migration(&mut source_store, &mut target_store); |
| 543 | + } |
| 544 | + |
441 | 545 | #[test] |
442 | 546 | fn test_if_monitors_is_not_dir() { |
443 | 547 | let store = FilesystemStore::new("test_monitors_is_not_dir".into()); |
|
0 commit comments