#[doc(hidden)]
pub mod btree;
pub mod catalog;
pub mod data_dir;
pub mod dir_lock;
#[doc(hidden)]
pub mod disk;
pub mod error;
#[doc(hidden)]
pub mod format;
#[doc(hidden)]
pub mod heap;
#[doc(hidden)]
pub mod page;
pub mod pj1;
#[doc(hidden)]
pub mod row;
pub mod stored_json_path;
pub mod table;
pub mod types;
pub mod view;
#[doc(hidden)]
pub mod wal;
use std::io;
use std::path::Path;
pub fn create_data_dir_secure(data_dir: &Path) -> io::Result<()> {
#[cfg(unix)]
{
use std::os::unix::fs::{DirBuilderExt, PermissionsExt};
std::fs::DirBuilder::new()
.recursive(true)
.mode(0o700)
.create(data_dir)?;
let current = std::fs::metadata(data_dir)?.permissions().mode() & 0o7777;
if current == 0o700 {
return Ok(());
}
if current == 0o000 {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!(
"{}: data directory mode is 0000 (the owner has no access); refusing to widen it, set it to 0700 to use it",
data_dir.display()
),
));
}
tracing::warn!(
data_dir = %data_dir.display(),
old_mode = format!("{current:04o}"),
new_mode = "0700",
"changing data directory mode from {current:04o} to 0700"
);
std::fs::set_permissions(data_dir, std::fs::Permissions::from_mode(0o700))?;
Ok(())
}
#[cfg(not(unix))]
{
std::fs::create_dir_all(data_dir)
}
}
pub fn validate_data_dir_read_only(data_dir: &Path) -> io::Result<()> {
let meta = std::fs::metadata(data_dir)?;
if !meta.is_dir() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("{} is not a directory", data_dir.display()),
));
}
Ok(())
}
#[cfg(all(test, unix))]
mod tests {
use super::*;
use std::os::unix::fs::PermissionsExt;
fn mode_of(dir: &Path) -> u32 {
std::fs::metadata(dir).unwrap().permissions().mode() & 0o7777
}
#[derive(Clone, Default)]
struct RecordedWarnings(std::sync::Arc<std::sync::Mutex<Vec<String>>>);
impl RecordedWarnings {
fn messages(&self) -> Vec<String> {
self.0.lock().unwrap_or_else(|e| e.into_inner()).clone()
}
}
impl tracing::Subscriber for RecordedWarnings {
fn enabled(&self, metadata: &tracing::Metadata<'_>) -> bool {
*metadata.level() <= tracing::Level::WARN
}
fn new_span(&self, _: &tracing::span::Attributes<'_>) -> tracing::span::Id {
tracing::span::Id::from_u64(1)
}
fn record(&self, _: &tracing::span::Id, _: &tracing::span::Record<'_>) {}
fn record_follows_from(&self, _: &tracing::span::Id, _: &tracing::span::Id) {}
fn event(&self, event: &tracing::Event<'_>) {
struct Render(String);
impl tracing::field::Visit for Render {
fn record_debug(
&mut self,
field: &tracing::field::Field,
value: &dyn std::fmt::Debug,
) {
self.0.push_str(&format!(" {}={value:?}", field.name()));
}
}
let mut render = Render(format!("{}", event.metadata().level()));
event.record(&mut render);
self.0
.lock()
.unwrap_or_else(|e| e.into_inner())
.push(render.0);
}
fn enter(&self, _: &tracing::span::Id) {}
fn exit(&self, _: &tracing::span::Id) {}
}
#[test]
fn widening_a_data_directory_mode_is_announced() {
let dir = tempfile::tempdir().unwrap();
std::fs::set_permissions(dir.path(), std::fs::Permissions::from_mode(0o555)).unwrap();
let recorded = RecordedWarnings::default();
tracing::subscriber::with_default(recorded.clone(), || {
create_data_dir_secure(dir.path()).expect("a 0555 directory is still usable");
});
assert_eq!(mode_of(dir.path()), 0o700);
let warnings = recorded.messages();
assert_eq!(
warnings.len(),
1,
"widening a directory must emit exactly one warning, got: {warnings:?}"
);
let warning = &warnings[0];
for expected in ["WARN", "0555", "0700"] {
assert!(
warning.contains(expected),
"the warning must name {expected}, got: {warning}"
);
}
}
#[test]
fn a_directory_already_at_0700_is_announced_to_nobody() {
let dir = tempfile::tempdir().unwrap();
std::fs::set_permissions(dir.path(), std::fs::Permissions::from_mode(0o700)).unwrap();
let recorded = RecordedWarnings::default();
tracing::subscriber::with_default(recorded.clone(), || {
create_data_dir_secure(dir.path()).unwrap();
});
assert!(
recorded.messages().is_empty(),
"a directory that was already 0700 was not changed and must not warn, got: {:?}",
recorded.messages()
);
}
#[test]
fn a_zero_mode_data_directory_is_refused_rather_than_widened() {
let dir = tempfile::tempdir().unwrap();
std::fs::set_permissions(dir.path(), std::fs::Permissions::from_mode(0o000)).unwrap();
let error = create_data_dir_secure(dir.path())
.expect_err("a 0000 directory must be refused, not widened");
assert_eq!(error.kind(), io::ErrorKind::PermissionDenied);
assert!(
error.to_string().contains("0000"),
"the refusal must name the mode, got: {error}"
);
assert_eq!(mode_of(dir.path()), 0o000, "the mode must be left alone");
std::fs::set_permissions(dir.path(), std::fs::Permissions::from_mode(0o700)).unwrap();
}
#[test]
fn a_directory_already_at_0700_is_left_alone() {
let dir = tempfile::tempdir().unwrap();
std::fs::set_permissions(dir.path(), std::fs::Permissions::from_mode(0o700)).unwrap();
create_data_dir_secure(dir.path()).unwrap();
assert_eq!(mode_of(dir.path()), 0o700);
}
}