use anyhow::{Context, Result};
use std::path::Path;
use std::process::Command;
const BTRFS_SUPER_MAGIC: i64 = 0x9123683E;
pub fn is_btrfs(path: &Path) -> bool {
let output = Command::new("stat")
.args(["-f", "-c", "%t", path.to_str().unwrap_or("")])
.output();
match output {
Ok(output) if output.status.success() => {
let fs_type = String::from_utf8_lossy(&output.stdout).trim().to_string();
let expected = format!("{:x}", BTRFS_SUPER_MAGIC);
fs_type.to_lowercase() == expected
}
_ => {
is_btrfs_from_mounts(path)
}
}
}
fn is_btrfs_from_mounts(path: &Path) -> bool {
let Ok(mounts) = std::fs::read_to_string("/proc/mounts") else {
return false;
};
let path = match path.canonicalize() {
Ok(p) => p,
Err(_) => {
if let Some(parent) = path.parent() {
match parent.canonicalize() {
Ok(p) => p,
Err(_) => return false,
}
} else {
return false;
}
}
};
let path_str = path.to_string_lossy();
let mut mount_entries: Vec<(&str, &str)> = mounts
.lines()
.filter_map(|line| {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 3 {
Some((parts[1], parts[2])) } else {
None
}
})
.collect();
mount_entries.sort_by(|a, b| b.0.len().cmp(&a.0.len()));
for (mount_point, fs_type) in mount_entries {
if path_str.starts_with(mount_point) || mount_point == "/" {
return fs_type == "btrfs";
}
}
false
}
pub fn disable_cow(path: &Path) -> Result<()> {
let output = Command::new("chattr")
.args(["+C", path.to_str().unwrap_or("")])
.output()
.context("Failed to run chattr command")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
if !stderr.contains("Operation not supported")
&& !stderr.contains("Inappropriate ioctl")
{
anyhow::bail!("chattr +C failed: {}", stderr.trim());
}
}
Ok(())
}
pub fn setup_vm_directory(path: &Path) -> Result<bool> {
std::fs::create_dir_all(path)
.with_context(|| format!("Failed to create directory {:?}", path))?;
if is_btrfs(path) {
match disable_cow(path) {
Ok(()) => return Ok(true),
Err(e) => {
eprintln!("Warning: Could not disable copy-on-write: {}", e);
}
}
}
Ok(false)
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn test_is_btrfs_nonexistent() {
let result = is_btrfs(&PathBuf::from("/nonexistent/path/12345"));
assert!(result == true || result == false);
}
#[test]
fn test_is_btrfs_root() {
let _result = is_btrfs(&PathBuf::from("/"));
}
}