|
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