Skip to content

Commit c309ccd

Browse files
authored
Merge pull request #1088 from tankyleo/2026-09-private-node-storage
Create private node directories and create private SQLite database files
2 parents b1337d2 + 9d0d0ef commit c309ccd

5 files changed

Lines changed: 143 additions & 21 deletions

File tree

‎src/builder.rs‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99
use std::collections::HashMap;
1010
use std::convert::TryInto;
1111
use std::default::Default;
12+
use std::fmt;
1213
#[cfg(feature = "unified-payments")]
1314
use std::net::ToSocketAddrs;
1415
#[cfg(feature = "storage-filesystem")]
1516
use std::path::PathBuf;
1617
use std::sync::{Arc, Mutex, Once, RwLock};
1718
use std::time::SystemTime;
18-
use std::{fmt, fs};
1919

2020
use bdk_wallet::template::Bip84;
2121
use bdk_wallet::{KeychainKind, Wallet as BdkWallet};
@@ -72,6 +72,8 @@ use crate::gossip::GossipSource;
7272
use crate::io::fs_store::open_or_migrate_fs_store;
7373
#[cfg(feature = "storage-sqlite")]
7474
use crate::io::sqlite_store::SqliteStore;
75+
#[cfg(feature = "storage-sqlite")]
76+
use crate::io::utils::create_dir_all_private;
7577
use crate::io::utils::{
7678
read_all_objects, read_event_queue, read_external_pathfinding_scores_from_cache,
7779
read_n_objects, read_network_graph, read_node_metrics, read_output_sweeper, read_peer_info,
@@ -692,7 +694,7 @@ impl NodeBuilder {
692694
pub fn build(&self, node_entropy: NodeEntropy) -> Result<Node, BuildError> {
693695
let logger = setup_logger(&self.log_writer_config, &self.config)?;
694696
let storage_dir_path = self.config.storage_dir_path.clone();
695-
fs::create_dir_all(storage_dir_path.clone())
697+
create_dir_all_private(storage_dir_path.as_ref())
696698
.map_err(|_| BuildError::StoragePathAccessFailed)?;
697699
let kv_store = SqliteStore::new(
698700
storage_dir_path.into(),

‎src/io/fs_store.rs‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use lightning::util::persist::migrate_kv_store_data_async;
1212
use lightning_persister::fs_store::v1::FilesystemStore;
1313
use lightning_persister::fs_store::v2::{FilesystemStoreV2, FilesystemStoreV2Error};
1414

15+
use crate::io::utils::create_dir_all_private;
1516
use crate::BuildError;
1617

1718
/// Opens a [`FilesystemStoreV2`], automatically migrating from v1 format if necessary.
@@ -23,10 +24,11 @@ pub(crate) async fn open_or_migrate_fs_store(
2324
storage_dir_path: PathBuf,
2425
) -> Result<FilesystemStoreV2, BuildError> {
2526
let parent_dir = storage_dir_path.parent().ok_or(BuildError::StoragePathAccessFailed)?;
26-
fs::create_dir_all(parent_dir).map_err(|_| BuildError::StoragePathAccessFailed)?;
27+
create_dir_all_private(parent_dir).map_err(|_| BuildError::StoragePathAccessFailed)?;
2728
recover_incomplete_fs_store_migration(&storage_dir_path)?;
2829
if !storage_dir_path.exists() {
29-
fs::create_dir_all(&storage_dir_path).map_err(|_| BuildError::StoragePathAccessFailed)?;
30+
create_dir_all_private(&storage_dir_path)
31+
.map_err(|_| BuildError::StoragePathAccessFailed)?;
3032
}
3133

3234
match FilesystemStoreV2::new(storage_dir_path.clone()) {
@@ -36,7 +38,7 @@ pub(crate) async fn open_or_migrate_fs_store(
3638
let v1_store = FilesystemStore::new(storage_dir_path.clone());
3739

3840
let v2_dir = fs_store_sibling_path(&storage_dir_path, "fs_store_v2_migrating");
39-
fs::create_dir_all(&v2_dir).map_err(|_| BuildError::StoragePathAccessFailed)?;
41+
create_dir_all_private(&v2_dir).map_err(|_| BuildError::StoragePathAccessFailed)?;
4042
let v2_store = FilesystemStoreV2::new(v2_dir.clone())
4143
.map_err(|_| BuildError::KVStoreSetupFailed)?;
4244

‎src/io/sqlite_store/mod.rs‎

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@
77

88
//! Objects related to [`SqliteStore`] live here.
99
use std::collections::HashMap;
10+
#[cfg(test)]
1011
use std::fs;
12+
#[cfg(unix)]
13+
use std::fs::OpenOptions;
1114
use std::future::Future;
15+
#[cfg(unix)]
16+
use std::os::unix::fs::OpenOptionsExt;
1217
use std::path::PathBuf;
1318
use std::sync::atomic::{AtomicI64, AtomicU64, Ordering};
1419
use std::sync::{Arc, Mutex};
@@ -20,7 +25,7 @@ use lightning::util::persist::{
2025
use lightning_types::string::PrintableString;
2126
use rusqlite::{named_params, Connection};
2227

23-
use crate::io::utils::check_namespace_key_validity;
28+
use crate::io::utils::{check_namespace_key_validity, create_dir_all_private};
2429

2530
mod migrations;
2631

@@ -58,6 +63,8 @@ impl SqliteStore {
5863
/// If not already existing, a new SQLite database will be created in the given `data_dir` under the
5964
/// given `db_file_name` (or the default to [`DEFAULT_SQLITE_DB_FILE_NAME`] if set to `None`).
6065
///
66+
/// SQLite's `:memory:` database name and `file:` URI filenames are not supported.
67+
///
6168
/// Similarly, the given `kv_table_name` will be used or default to [`DEFAULT_KV_TABLE_NAME`].
6269
pub fn new(
6370
data_dir: PathBuf, db_file_name: Option<String>, kv_table_name: Option<String>,
@@ -232,18 +239,37 @@ impl SqliteStoreInner {
232239
data_dir: PathBuf, db_file_name: Option<String>, kv_table_name: Option<String>,
233240
) -> io::Result<Self> {
234241
let db_file_name = db_file_name.unwrap_or(DEFAULT_SQLITE_DB_FILE_NAME.to_string());
242+
let mut db_file_path = data_dir.clone();
243+
db_file_path.push(&db_file_name);
244+
if db_file_name == ":memory:"
245+
|| db_file_name.starts_with("file:")
246+
|| db_file_path.to_string_lossy().starts_with("file:")
247+
{
248+
return Err(io::Error::new(
249+
io::ErrorKind::InvalidInput,
250+
"SQLite :memory: and file: database names are not supported",
251+
));
252+
}
235253
let kv_table_name = kv_table_name.unwrap_or(DEFAULT_KV_TABLE_NAME.to_string());
236254

237-
fs::create_dir_all(data_dir.clone()).map_err(|e| {
255+
create_dir_all_private(&data_dir).map_err(|e| {
238256
let msg = format!(
239257
"Failed to create database destination directory {}: {}",
240258
data_dir.display(),
241259
e
242260
);
243261
io::Error::new(io::ErrorKind::Other, msg)
244262
})?;
245-
let mut db_file_path = data_dir.clone();
246-
db_file_path.push(db_file_name);
263+
#[cfg(unix)]
264+
match OpenOptions::new().create_new(true).write(true).mode(0o600).open(&db_file_path) {
265+
Ok(_) => {},
266+
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {},
267+
Err(e) => {
268+
let msg =
269+
format!("Failed to create database file {}: {}", db_file_path.display(), e);
270+
return Err(io::Error::new(io::ErrorKind::Other, msg));
271+
},
272+
}
247273

248274
let mut connection = Connection::open(db_file_path.clone()).map_err(|e| {
249275
let msg =
@@ -700,6 +726,50 @@ mod tests {
700726
}
701727
}
702728

729+
#[cfg(unix)]
730+
#[test]
731+
fn creates_private_database_storage() {
732+
use std::os::unix::fs::PermissionsExt;
733+
734+
let mut data_dir = random_storage_path();
735+
data_dir.push("creates_private_database_storage");
736+
let db_file_name = "test_db";
737+
let db_file_path = data_dir.join(db_file_name);
738+
let _store = SqliteStore::new(
739+
data_dir.clone(),
740+
Some(db_file_name.to_string()),
741+
Some("test_table".to_string()),
742+
)
743+
.unwrap();
744+
745+
let dir_mode = data_dir.metadata().unwrap().permissions().mode();
746+
let file_mode = db_file_path.metadata().unwrap().permissions().mode();
747+
assert_eq!(dir_mode & 0o077, 0);
748+
assert_eq!(file_mode & 0o077, 0);
749+
}
750+
751+
#[test]
752+
fn rejects_sqlite_pseudo_filenames() {
753+
for (data_dir, db_file_name) in [
754+
(random_storage_path(), ":memory:"),
755+
(random_storage_path(), "file:/tmp/node.db?mode=rwc"),
756+
(PathBuf::from("file:."), "test_db"),
757+
] {
758+
let result = SqliteStore::new(
759+
data_dir.clone(),
760+
Some(db_file_name.to_string()),
761+
Some("test_table".to_string()),
762+
);
763+
let error = match result {
764+
Ok(_) => panic!("SQLite pseudo-filename was accepted"),
765+
Err(e) => e,
766+
};
767+
768+
assert_eq!(error.kind(), io::ErrorKind::InvalidInput);
769+
assert!(!data_dir.exists());
770+
}
771+
}
772+
703773
#[tokio::test]
704774
async fn read_write_remove_list_persist() {
705775
let mut temp_path = random_storage_path();

‎src/io/utils.rs‎

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::io::Write;
1010
use std::num::NonZeroUsize;
1111
use std::ops::Deref;
1212
#[cfg(unix)]
13-
use std::os::unix::fs::OpenOptionsExt;
13+
use std::os::unix::fs::{DirBuilderExt, OpenOptionsExt};
1414
use std::path::Path;
1515
use std::sync::Arc;
1616

@@ -52,6 +52,14 @@ use crate::{Error, EventQueue, NodeMetrics, PersistedNodeMetrics};
5252

5353
pub const EXTERNAL_PATHFINDING_SCORES_CACHE_KEY: &str = "external_pathfinding_scores_cache";
5454

55+
pub(crate) fn create_dir_all_private(path: &Path) -> std::io::Result<()> {
56+
let mut builder = fs::DirBuilder::new();
57+
builder.recursive(true);
58+
#[cfg(unix)]
59+
builder.mode(0o700);
60+
builder.create(path)
61+
}
62+
5563
pub(crate) fn read_or_generate_seed_file(
5664
keys_seed_path: &str,
5765
) -> std::io::Result<[u8; WALLET_KEYS_SEED_LEN]> {
@@ -75,7 +83,7 @@ pub(crate) fn read_or_generate_seed_file(
7583
})?;
7684

7785
if let Some(parent_dir) = Path::new(&keys_seed_path).parent() {
78-
fs::create_dir_all(parent_dir)?;
86+
create_dir_all_private(parent_dir)?;
7987
}
8088

8189
#[cfg(unix)]
@@ -761,8 +769,25 @@ pub(crate) async fn read_bdk_wallet_change_set(
761769

762770
#[cfg(test)]
763771
mod tests {
764-
use super::read_or_generate_seed_file;
765772
use super::test_utils::random_storage_path;
773+
use super::{create_dir_all_private, read_or_generate_seed_file};
774+
775+
#[cfg(unix)]
776+
#[test]
777+
fn creates_private_directories() {
778+
use std::os::unix::fs::PermissionsExt;
779+
780+
let base_path = random_storage_path();
781+
let nested_path = base_path.join("parent").join("child");
782+
create_dir_all_private(&nested_path).unwrap();
783+
784+
for path in [&base_path, &base_path.join("parent"), &nested_path] {
785+
let mode = path.metadata().unwrap().permissions().mode();
786+
assert_eq!(mode & 0o077, 0);
787+
}
788+
789+
std::fs::remove_dir_all(base_path).unwrap();
790+
}
766791

767792
#[test]
768793
fn generated_seed_is_readable() {

‎src/logger.rs‎

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
use core::fmt;
1111
use std::fs;
1212
use std::io::Write;
13+
#[cfg(unix)]
14+
use std::os::unix::fs::OpenOptionsExt;
1315
use std::path::Path;
1416
use std::sync::Arc;
1517

@@ -22,6 +24,16 @@ pub(crate) use lightning::util::logger::{Logger as LdkLogger, Record as LdkRecor
2224
pub(crate) use lightning::{log_bytes, log_debug, log_error, log_info, log_trace, log_warn};
2325
use log::{Level as LogFacadeLevel, Record as LogFacadeRecord};
2426

27+
use crate::io::utils::create_dir_all_private;
28+
29+
fn open_log_file(file_path: &str) -> std::io::Result<fs::File> {
30+
let mut options = fs::OpenOptions::new();
31+
options.create(true).append(true);
32+
#[cfg(unix)]
33+
options.mode(0o600);
34+
options.open(file_path)
35+
}
36+
2537
/// A unit of logging output with metadata to enable filtering `module_path`,
2638
/// `file`, and `line` to inform on log's source.
2739
#[cfg(not(feature = "uniffi"))]
@@ -208,10 +220,7 @@ impl LogWriter for Writer {
208220
context,
209221
);
210222

211-
fs::OpenOptions::new()
212-
.create(true)
213-
.append(true)
214-
.open(file_path)
223+
open_log_file(file_path)
215224
.expect("Failed to open log file")
216225
.write_all(log.as_bytes())
217226
.expect("Failed to write to log file")
@@ -261,14 +270,11 @@ impl Logger {
261270
/// are the path to the log file, and the log level.
262271
pub fn new_fs_writer(file_path: String, max_log_level: LogLevel) -> Result<Self, ()> {
263272
if let Some(parent_dir) = Path::new(&file_path).parent() {
264-
fs::create_dir_all(parent_dir)
273+
create_dir_all_private(parent_dir)
265274
.map_err(|e| eprintln!("ERROR: Failed to create log parent directory: {}", e))?;
266275

267276
// make sure the file exists.
268-
fs::OpenOptions::new()
269-
.create(true)
270-
.append(true)
271-
.open(&file_path)
277+
open_log_file(&file_path)
272278
.map_err(|e| eprintln!("ERROR: Failed to open log file: {}", e))?;
273279
}
274280

@@ -308,6 +314,23 @@ mod tests {
308314
use std::sync::Mutex;
309315

310316
use super::*;
317+
#[cfg(unix)]
318+
use crate::io::test_utils::random_storage_path;
319+
320+
#[cfg(unix)]
321+
#[test]
322+
fn creates_private_log_file() {
323+
use std::os::unix::fs::PermissionsExt;
324+
325+
let log_dir = random_storage_path();
326+
let log_path = log_dir.join("ldk_node.log");
327+
let _logger =
328+
Logger::new_fs_writer(log_path.to_str().unwrap().to_string(), LogLevel::Info).unwrap();
329+
330+
let mode = log_path.metadata().unwrap().permissions().mode();
331+
assert_eq!(mode & 0o077, 0);
332+
fs::remove_dir_all(log_dir).unwrap();
333+
}
311334

312335
/// A minimal log facade logger that captures log output for testing.
313336
struct TestLogger {

0 commit comments

Comments
 (0)