use std::{
fs::{create_dir, rename},
path::PathBuf,
};
use anyhow::Result;
use btrfsutil::subvolume::{SnapshotFlags, Subvolume};
use file_owner::{owner_group, set_owner_group};
use sudo::escalate_if_needed;
use thiserror::Error;
use uuid::Uuid;
use crate::{
paths::{table_snapshot_path, table_snapshots_path},
XATTR_COMMIT_MESSAGE, XATTR_HEAD,
};
#[derive(Error, Debug)]
pub enum CommitErr {
#[error("Filesystem is not BTRFS, or some other error occurred")]
FilesystemIsNotBtrfs,
#[error("Could not escalate privileges")]
CouldNotEscalatePrivileges,
}
pub fn commit(dir: &PathBuf, message: &Option<String>, verbose: bool) -> Result<Uuid> {
escalate_if_needed().map_err(|_e| CommitErr::CouldNotEscalatePrivileges)?;
let subvol = Subvolume::get(dir.as_path()).map_err(|e| {
eprintln!("LibError: {}", e);
CommitErr::FilesystemIsNotBtrfs
})?;
let snapshots_dir = table_snapshots_path(dir);
if !snapshots_dir.exists() {
create_dir(&snapshots_dir)?;
let (owner, group) = owner_group(dir)?;
set_owner_group(&snapshots_dir, owner, group)?;
}
let snapshot_initial_path = {
let mut p = snapshots_dir.clone();
p.push(subvol.id().to_string());
p
};
let flags = {
let mut f = SnapshotFlags::empty();
f.set(SnapshotFlags::READ_ONLY, true);
f
};
if let Some(message) = message.as_ref() {
xattr::set(dir, XATTR_COMMIT_MESSAGE.to_osstring(), message.as_bytes())?;
}
let snap = subvol.snapshot(snapshot_initial_path.as_path(), Some(flags), None)?;
let uuid = snap.info().unwrap().uuid;
let snapshot_path = table_snapshot_path(dir, uuid);
rename(snapshot_initial_path, snapshot_path)?;
xattr::set(dir, XATTR_HEAD.to_osstring(), uuid.to_string().as_bytes())?;
xattr::remove(dir, XATTR_COMMIT_MESSAGE.to_osstring())?;
if verbose {
println!("Committed snapshot {}", uuid);
}
Ok(uuid)
}