use clap::Parser;
use std::path::PathBuf;
use uuid::Uuid;
#[derive(Parser, Debug)]
#[command(name = "mkfs.btrfs", version)]
pub struct Arguments {
#[arg(short = 'd', long = "data", value_name = "PROFILE")]
pub data_profile: Option<Profile>,
#[arg(short = 'm', long = "metadata", value_name = "PROFILE")]
pub metadata_profile: Option<Profile>,
#[arg(short = 'M', long)]
pub mixed: bool,
#[arg(short = 'L', long = "label", value_name = "LABEL")]
pub label: Option<String>,
#[arg(short = 'n', long, value_name = "SIZE")]
pub nodesize: Option<SizeArg>,
#[arg(short = 's', long, value_name = "SIZE")]
pub sectorsize: Option<SizeArg>,
#[arg(short = 'b', long = "byte-count", value_name = "SIZE")]
pub byte_count: Option<SizeArg>,
#[arg(long = "checksum", alias = "csum", value_name = "TYPE")]
pub checksum: Option<ChecksumArg>,
#[arg(
short = 'O',
long = "features",
alias = "runtime-features",
short_alias = 'R',
value_name = "LIST",
value_delimiter = ','
)]
pub features: Vec<FeatureArg>,
#[arg(short = 'U', long = "uuid", value_name = "UUID")]
pub filesystem_uuid: Option<Uuid>,
#[arg(long = "device-uuid", value_name = "UUID")]
pub device_uuid: Option<Uuid>,
#[arg(short = 'f', long)]
pub force: bool,
#[arg(short = 'K', long)]
pub nodiscard: bool,
#[arg(short = 'r', long = "rootdir", value_name = "DIR")]
pub rootdir: Option<PathBuf>,
#[arg(short = 'u', long = "subvol", value_name = "TYPE:SUBDIR")]
pub subvol: Vec<SubvolArg>,
#[arg(long = "inode-flags", value_name = "FLAGS:PATH")]
pub inode_flags: Vec<InodeFlagsArg>,
#[arg(long = "compress", value_name = "ALGO[:LEVEL]")]
pub compress: Option<CompressArg>,
#[arg(long)]
pub reflink: bool,
#[arg(long)]
pub shrink: bool,
#[arg(short = 'q', long)]
pub quiet: bool,
#[arg(short = 'v', long)]
pub verbose: bool,
#[arg(required = true)]
pub devices: Vec<PathBuf>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SizeArg(pub u64);
impl std::str::FromStr for SizeArg {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim();
let (num_str, suffix) = match s.find(|c: char| c.is_alphabetic()) {
Some(i) => (&s[..i], &s[i..]),
None => (s, ""),
};
let base: u64 =
num_str.parse().map_err(|e| format!("invalid size: {e}"))?;
let multiplier = match suffix.to_lowercase().as_str() {
"" => 1u64,
"k" | "kib" => 1 << 10,
"m" | "mib" => 1 << 20,
"g" | "gib" => 1 << 30,
"t" | "tib" => 1 << 40,
"p" | "pib" => 1 << 50,
"e" | "eib" => 1 << 60,
_ => return Err(format!("unknown size suffix: {suffix}")),
};
base.checked_mul(multiplier)
.map(SizeArg)
.ok_or_else(|| format!("size overflow: {s}"))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Profile {
Single,
Dup,
Raid0,
Raid1,
Raid1c3,
Raid1c4,
Raid5,
Raid6,
Raid10,
}
impl std::str::FromStr for Profile {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"single" => Ok(Profile::Single),
"dup" => Ok(Profile::Dup),
"raid0" => Ok(Profile::Raid0),
"raid1" => Ok(Profile::Raid1),
"raid1c3" => Ok(Profile::Raid1c3),
"raid1c4" => Ok(Profile::Raid1c4),
"raid5" => Ok(Profile::Raid5),
"raid6" => Ok(Profile::Raid6),
"raid10" => Ok(Profile::Raid10),
_ => Err(format!("unknown profile: {s}")),
}
}
}
impl std::fmt::Display for Profile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Profile::Single => write!(f, "single"),
Profile::Dup => write!(f, "DUP"),
Profile::Raid0 => write!(f, "RAID0"),
Profile::Raid1 => write!(f, "RAID1"),
Profile::Raid1c3 => write!(f, "RAID1C3"),
Profile::Raid1c4 => write!(f, "RAID1C4"),
Profile::Raid5 => write!(f, "RAID5"),
Profile::Raid6 => write!(f, "RAID6"),
Profile::Raid10 => write!(f, "RAID10"),
}
}
}
impl Profile {
pub fn block_group_flag(self) -> u64 {
use btrfs_disk::raw;
match self {
Profile::Single => 0,
Profile::Dup => raw::BTRFS_BLOCK_GROUP_DUP as u64,
Profile::Raid0 => raw::BTRFS_BLOCK_GROUP_RAID0 as u64,
Profile::Raid1 => raw::BTRFS_BLOCK_GROUP_RAID1 as u64,
Profile::Raid1c3 => raw::BTRFS_BLOCK_GROUP_RAID1C3 as u64,
Profile::Raid1c4 => raw::BTRFS_BLOCK_GROUP_RAID1C4 as u64,
Profile::Raid5 => raw::BTRFS_BLOCK_GROUP_RAID5 as u64,
Profile::Raid6 => raw::BTRFS_BLOCK_GROUP_RAID6 as u64,
Profile::Raid10 => raw::BTRFS_BLOCK_GROUP_RAID10 as u64,
}
}
pub fn num_stripes(self, n_devices: usize) -> u16 {
match self {
Profile::Single => 1,
Profile::Dup | Profile::Raid1 => 2,
Profile::Raid1c3 => 3,
Profile::Raid1c4 => 4,
Profile::Raid0 => n_devices as u16,
Profile::Raid5 | Profile::Raid6 | Profile::Raid10 => {
n_devices as u16
}
}
}
pub fn min_devices(self) -> usize {
match self {
Profile::Single | Profile::Dup => 1,
Profile::Raid0 | Profile::Raid1 | Profile::Raid5 => 2,
Profile::Raid1c3 | Profile::Raid6 => 3,
Profile::Raid1c4 | Profile::Raid10 => 4,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChecksumArg {
Crc32c,
Xxhash,
Sha256,
Blake2,
}
impl std::str::FromStr for ChecksumArg {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"crc32c" => Ok(ChecksumArg::Crc32c),
"xxhash" | "xxhash64" => Ok(ChecksumArg::Xxhash),
"sha256" => Ok(ChecksumArg::Sha256),
"blake2" | "blake2b" => Ok(ChecksumArg::Blake2),
_ => Err(format!("unknown checksum type: {s}")),
}
}
}
impl std::fmt::Display for ChecksumArg {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ChecksumArg::Crc32c => write!(f, "crc32c"),
ChecksumArg::Xxhash => write!(f, "xxhash"),
ChecksumArg::Sha256 => write!(f, "sha256"),
ChecksumArg::Blake2 => write!(f, "blake2"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FeatureArg {
pub feature: Feature,
pub enabled: bool,
}
impl std::str::FromStr for FeatureArg {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (enabled, name) = if let Some(rest) = s.strip_prefix('^') {
(false, rest)
} else {
(true, s)
};
let feature = name.parse::<Feature>()?;
Ok(FeatureArg { feature, enabled })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Feature {
MixedBg,
Extref,
Raid56,
SkinnyMetadata,
NoHoles,
Zoned,
Quota,
FreeSpaceTree,
BlockGroupTree,
RaidStripeTree,
Squota,
ListAll,
}
impl std::str::FromStr for Feature {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().replace('_', "-").as_str() {
"mixed-bg" => Ok(Feature::MixedBg),
"extref" => Ok(Feature::Extref),
"raid56" => Ok(Feature::Raid56),
"skinny-metadata" => Ok(Feature::SkinnyMetadata),
"no-holes" => Ok(Feature::NoHoles),
"zoned" => Ok(Feature::Zoned),
"quota" => Ok(Feature::Quota),
"free-space-tree" | "fst" => Ok(Feature::FreeSpaceTree),
"block-group-tree" | "bgt" => Ok(Feature::BlockGroupTree),
"raid-stripe-tree" => Ok(Feature::RaidStripeTree),
"squota" => Ok(Feature::Squota),
"list-all" => Ok(Feature::ListAll),
_ => Err(format!("unknown feature: {s}")),
}
}
}
impl std::fmt::Display for Feature {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Feature::MixedBg => write!(f, "mixed-bg"),
Feature::Extref => write!(f, "extref"),
Feature::Raid56 => write!(f, "raid56"),
Feature::SkinnyMetadata => write!(f, "skinny-metadata"),
Feature::NoHoles => write!(f, "no-holes"),
Feature::Zoned => write!(f, "zoned"),
Feature::Quota => write!(f, "quota"),
Feature::FreeSpaceTree => write!(f, "free-space-tree"),
Feature::BlockGroupTree => write!(f, "block-group-tree"),
Feature::RaidStripeTree => write!(f, "raid-stripe-tree"),
Feature::Squota => write!(f, "squota"),
Feature::ListAll => write!(f, "list-all"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SubvolArg {
pub subvol_type: SubvolType,
pub path: PathBuf,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SubvolType {
#[default]
Rw,
Ro,
Default,
DefaultRo,
}
impl std::str::FromStr for SubvolArg {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with("./") {
return Ok(SubvolArg {
subvol_type: SubvolType::Rw,
path: PathBuf::from(s),
});
}
if let Some((prefix, rest)) = s.split_once(':') {
let subvol_type = match prefix {
"rw" => SubvolType::Rw,
"ro" => SubvolType::Ro,
"default" => SubvolType::Default,
"default-ro" => SubvolType::DefaultRo,
_ => {
return Ok(SubvolArg {
subvol_type: SubvolType::Rw,
path: PathBuf::from(s),
});
}
};
if rest.is_empty() {
return Err("subvolume path cannot be empty".to_string());
}
Ok(SubvolArg {
subvol_type,
path: PathBuf::from(rest),
})
} else {
Ok(SubvolArg {
subvol_type: SubvolType::Rw,
path: PathBuf::from(s),
})
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InodeFlagsArg {
pub nodatacow: bool,
pub nodatasum: bool,
pub path: PathBuf,
}
impl std::str::FromStr for InodeFlagsArg {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (flags_str, path) = s
.split_once(':')
.ok_or_else(|| "expected FLAGS:PATH format".to_string())?;
if path.is_empty() {
return Err("path cannot be empty".to_string());
}
let mut nodatacow = false;
let mut nodatasum = false;
for flag in flags_str.split(',') {
match flag.trim() {
"nodatacow" => nodatacow = true,
"nodatasum" => nodatasum = true,
other => return Err(format!("unknown inode flag: {other}")),
}
}
Ok(InodeFlagsArg {
nodatacow,
nodatasum,
path: PathBuf::from(path),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CompressArg {
pub algorithm: CompressAlgorithm,
pub level: Option<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompressAlgorithm {
No,
Zstd,
Lzo,
Zlib,
}
impl std::str::FromStr for CompressArg {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (algo_str, level) = if let Some((a, l)) = s.split_once(':') {
let level: u32 =
l.parse().map_err(|e| format!("invalid level: {e}"))?;
(a, Some(level))
} else {
(s, None)
};
let algorithm = match algo_str.to_lowercase().as_str() {
"no" | "none" => CompressAlgorithm::No,
"zstd" => CompressAlgorithm::Zstd,
"lzo" => CompressAlgorithm::Lzo,
"zlib" => CompressAlgorithm::Zlib,
_ => {
return Err(format!(
"unknown compression algorithm: {algo_str}"
));
}
};
if level.is_some() && algorithm == CompressAlgorithm::No {
return Err(
"compression level not valid with 'no' algorithm".to_string()
);
}
if let Some(l) = level {
match algorithm {
CompressAlgorithm::Zstd if l > 15 => {
return Err(format!("zstd level must be 1..15, got {l}"));
}
CompressAlgorithm::Zlib if l > 9 => {
return Err(format!("zlib level must be 1..9, got {l}"));
}
CompressAlgorithm::Lzo if level.is_some() => {
return Err(
"lzo does not support compression levels".to_string()
);
}
_ => {}
}
}
Ok(CompressArg { algorithm, level })
}
}