urma-node 0.6.0

Optional URMA archive node: Litecoin follower, record archive, libp2p overlay and loopback service
Documentation
use crate::{
    install::{
        Error,
        account::{Account, Ids},
    },
    platform::Paths,
};
use std::{
    fs::{self, DirBuilder, File, OpenOptions},
    io::ErrorKind,
    os::unix::fs::{DirBuilderExt, MetadataExt, OpenOptionsExt, fchown},
    path::{Path, PathBuf},
};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Kind {
    File,
    Directory,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LogWriter {
    Root,
    Service,
}

pub struct Ownership;

impl Ownership {
    const LOG_MODE: u32 = 0o750;
    const PROCESS: &'static str = "/proc/self";
}

pub fn installer() -> Result<u32, Error> {
    Ok(fs::metadata(Ownership::PROCESS)?.uid())
}

fn reopen(path: &Path, kind: Kind) -> Result<File, Error> {
    let flags = match kind {
        Kind::File => libc::O_NOFOLLOW | libc::O_NONBLOCK,
        Kind::Directory => libc::O_NOFOLLOW | libc::O_NONBLOCK | libc::O_DIRECTORY,
    };
    Ok(OpenOptions::new()
        .read(true)
        .custom_flags(flags)
        .open(path)?)
}

fn hand(path: &Path, kind: Kind, owner: Option<u32>, gid: u32) -> Result<(), Error> {
    let installer = installer()?;
    let handle = reopen(path, kind)?;
    let meta = handle.metadata()?;
    let intact = match kind {
        Kind::File => meta.is_file() && meta.nlink() == 1,
        Kind::Directory => meta.is_dir(),
    };
    if !intact || meta.uid() != installer {
        return Err(Error::Failed(format!(
            "{} changed after it was created; refusing to hand it to {}",
            path.display(),
            Account::NAME
        )));
    }
    fchown(&handle, owner, Some(gid))?;
    Ok(())
}

pub fn hand_over(created: &[PathBuf], paths: &Paths, ids: Ids) -> Result<(), Error> {
    let order = [
        (&paths.token, Kind::File),
        (&paths.peer_key, Kind::File),
        (&paths.data, Kind::Directory),
        (&paths.state_dir, Kind::Directory),
    ];
    for (path, kind) in order {
        if created.contains(path) {
            hand(path, kind, Some(ids.uid), ids.gid)?;
        }
    }
    Ok(())
}

pub fn log_directory(path: &Path, writer: LogWriter, ids: Ids) -> Result<(), Error> {
    match fs::symlink_metadata(path) {
        Ok(meta) => match meta.is_dir() {
            true => Ok(()),
            false => Err(Error::Failed(format!(
                "{} exists but is not a directory",
                path.display()
            ))),
        },
        Err(error) if error.kind() == ErrorKind::NotFound => {
            let Some(parent) = path.parent() else {
                return Err(Error::Failed(format!("{} has no parent", path.display())));
            };
            fs::create_dir_all(parent)?;
            DirBuilder::new().mode(Ownership::LOG_MODE).create(path)?;
            let owner = match writer {
                LogWriter::Root => None,
                LogWriter::Service => Some(ids.uid),
            };
            hand(path, Kind::Directory, owner, ids.gid)
        }
        Err(error) => Err(error.into()),
    }
}