genpac 0.1.0

Sandbox for Gentoo ebuild development using bubblewrap
// Copyright (C) 2023 Gokul Das B
// SPDX-License-Identifier: GPL-3.0-or-later
//! Btrfs snapshots backend
//!
//! This module uses the 'btrfs' cli tool, rather than using [btrfs ioctl
//! interface](https://btrfs.readthedocs.io/en/latest/btrfs-ioctl.html) directly. This is due to the
//! fact that ioctl interface seems to be changing, while the btrfs tool tracks these changes and
//! presents a stable UI for us.

use super::Snapshot;
use super::{BackendMarker, BackendOps};
use crate::global::ChrootVerified;
use crate::LogCommand;
use anyhow::Result as AResult;
use std::path::Path;
use std::process::Command;

pub(super) struct BtrfsMarker;
impl BackendMarker for BtrfsMarker {}
pub(super) type BtrfsBackend = Snapshot<BtrfsMarker>;

impl BackendOps for BtrfsBackend {
    fn make_blank(&mut self) -> AResult<()> {
        let target = self.target.take().unwrap().verify(false)?;
        log::info!("Creating blank chroot {target}");
        let target: &Path = &target;
        let mut cmd = Command::new("btrfs");
        cmd.args(["subvolume", "create"]).arg(target);
        execcmd(&mut cmd, self.dry_run)
    }

    fn duplicate(&mut self, source: ChrootVerified) -> AResult<()> {
        let target = self.target.take().unwrap().verify(false)?;
        log::info!("Snapshotting {source} to create chroot {target}");
        let source: &Path = &source;
        let target: &Path = &target;
        let mut cmd = Command::new("btrfs");
        cmd.args(["subvolume", "snapshot"]).arg(source).arg(target);
        execcmd(&mut cmd, self.dry_run)
    }

    fn delete(&mut self) -> AResult<()> {
        let target = self.target.take().unwrap().verify(true)?;
        log::info!("Deleting chroot {target}");
        let target: &Path = &target;
        let mut cmd = Command::new("btrfs");
        cmd.args(["subvolume", "delete"]).arg(target);
        execcmd(&mut cmd, self.dry_run)
    }
}

fn execcmd(cmd: &mut Command, dry_run: bool) -> AResult<()> {
    const CMDFAIL: &str = "Btrfs command failed";
    cmd.log("Command");
    // Hoping for a short circuit here. Dont want command run in a dry run!
    if dry_run || cmd.status()?.success() {
        Ok(())
    } else {
        log::error!("{CMDFAIL}");
        anyhow::bail!(CMDFAIL)
    }
}