nexus-gateway 0.0.1-alpha

S3-compatible object gateway and embedded Nexus console.
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(())
}