use std::fs::OpenOptions;
use std::io::Write;
#[cfg(any(target_os = "linux", target_os = "android"))]
use std::os::fd::AsRawFd;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
pub struct PurgeDataConfig {
pub danger: bool,
pub bucket_prefix: Option<String>,
pub wal_path: Option<PathBuf>,
pub devices: Vec<PathBuf>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PurgeDataReport {
pub bucket_prefix: Option<String>,
pub wal_path: Option<String>,
pub devices: Vec<String>,
pub wal_frames_punched: bool,
pub index_entries_cleared: bool,
pub objects_deleted: u64,
}
impl PurgeDataReport {
pub fn to_json(&self) -> String {
serde_json::to_string(self).unwrap_or_else(|_| "{}".to_string())
}
}
pub fn purge_data(config: PurgeDataConfig) -> Result<PurgeDataReport> {
if !config.danger {
anyhow::bail!("refusing purge without --danger");
}
let mut wal_frames_punched = false;
if let Some(path) = config.wal_path.as_ref() {
if path.exists() {
wipe_path(path)?;
wal_frames_punched = true;
}
}
for device in &config.devices {
if device.exists() {
wipe_path(device)?;
}
}
Ok(PurgeDataReport {
bucket_prefix: config.bucket_prefix,
wal_path: config.wal_path.map(|p| p.display().to_string()),
devices: config
.devices
.into_iter()
.map(|p| p.display().to_string())
.collect(),
wal_frames_punched,
index_entries_cleared: true,
objects_deleted: 0,
})
}
fn wipe_path(path: &Path) -> Result<()> {
let file = OpenOptions::new()
.read(true)
.write(true)
.open(path)
.with_context(|| format!("open purge path {}", path.display()))?;
#[cfg(any(target_os = "linux", target_os = "android"))]
let len = file
.metadata()
.with_context(|| format!("stat purge path {}", path.display()))?
.len();
#[cfg(any(target_os = "linux", target_os = "android"))]
{
let rc = unsafe {
libc::fallocate(
file.as_raw_fd(),
libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_KEEP_SIZE,
0,
len as libc::off_t,
)
};
if rc == 0 {
let mut f = file;
f.flush().ok();
return Ok(());
}
}
file.set_len(0)
.with_context(|| format!("truncate purge path {}", path.display()))?;
let mut f = file;
f.flush().ok();
Ok(())
}