use std::fs::File;
use std::path::Path;
#[cfg(any(target_os = "linux", target_os = "android"))]
use ferrosys::ext::DirectorySource;
use ferrosys::ext::{
ArchiveSource, FormatOptions, FormatPlan, Layout, Profile, Reader, Source, TreeBuilder,
};
use crate::args::{Contents, FormatArgs, Size, Stream};
use crate::dest::Destination;
use crate::json::Obj;
use crate::{Error, emit, render};
pub fn run(args: FormatArgs) -> Result<(), Error> {
let options = options(&args);
let plan = match &args.contents {
None => plan(TreeBuilder::new(), &args, options)?,
Some(Contents::Tar(Stream::Std)) => {
let stdin = std::io::stdin();
plan(ArchiveSource::from_reader(stdin.lock())?, &args, options)?
}
Some(Contents::Tar(Stream::File(path))) => {
plan(ArchiveSource::from_path(path)?, &args, options)?
}
Some(Contents::Dir(path)) => from_dir(path, &args, options)?,
};
if args.dry_run {
return report(&args, plan.layout(), None);
}
let mut dest = open_destination(&args.out, args.atomic)?;
let layout = plan.write_to(dest.file())?;
let usage = Usage::of(dest.written())?;
dest.commit()?;
report(&args, &layout, Some(usage))
}
fn open_destination(out: &Path, atomic: bool) -> Result<Destination, Error> {
let not_regular = || Error::NotARegularFile(out.display().to_string());
match std::fs::metadata(out) {
Ok(meta) if !meta.file_type().is_file() => return Err(not_regular()),
Ok(_) | Err(_) => {}
}
let mut dest = Destination::open(out, atomic)?;
let meta = dest
.file()
.metadata()
.map_err(|e| Error::io(dest.written(), e))?;
if !meta.file_type().is_file() {
return Err(not_regular());
}
Ok(dest)
}
fn plan(
source: impl Source,
args: &FormatArgs,
options: FormatOptions,
) -> Result<FormatPlan, Error> {
Ok(match args.size {
Size::Bytes(bytes) => FormatPlan::new(source, bytes, options)?,
Size::Fit(slack) => FormatPlan::fit(source, options, slack)?,
})
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn from_dir(path: &Path, args: &FormatArgs, options: FormatOptions) -> Result<FormatPlan, Error> {
let mut source = DirectorySource::from_path(path)?;
if let Some((uid, gid)) = args.owner {
source = source.owner(uid, gid);
}
plan(source, args, options)
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
fn from_dir(
_path: &Path,
_args: &FormatArgs,
_options: FormatOptions,
) -> Result<FormatPlan, Error> {
Err(Error::NoDirectorySource)
}
fn report(args: &FormatArgs, layout: &Layout, usage: Option<Usage>) -> Result<(), Error> {
if args.json {
emit(receipt(args, layout, usage).as_bytes())
} else {
eprint!("{}", summary(args, layout, usage));
Ok(())
}
}
#[derive(Clone, Copy)]
struct Usage {
free_blocks: u64,
free_inodes: u32,
}
impl Usage {
fn of(path: &Path) -> Result<Self, Error> {
let file = File::open(path).map_err(|e| Error::io(path, e))?;
let reader = Reader::open(file).map_err(|source| Error::NotExt {
path: path.display().to_string(),
source,
})?;
let sb = reader.superblock();
Ok(Self {
free_blocks: sb.free_blocks_count,
free_inodes: sb.free_inodes_count,
})
}
}
fn options(args: &FormatArgs) -> FormatOptions {
let mut o = FormatOptions::new(args.uuid, args.time, args.hash_seed);
o.feature = args.feature;
o.errors = args.errors;
o.inodes = args.inodes;
o.reserved = args.reserved;
o.volume_name = args.volume_name;
o.grow = args.grow;
o.journal = args.journal;
o.fixed_time = args.fixed_time;
o.hash_version = args.hash_version;
o.hash_signedness = args.hash_signedness;
o
}
fn summary(args: &FormatArgs, layout: &Layout, usage: Option<Usage>) -> String {
let mut s = String::new();
let mut line = |k: &str, v: String| {
s.push_str(&format!("{k:<24}{v}\n"));
};
if let Some(label) = render::label(&args.volume_name) {
line("Filesystem volume name:", label);
}
line("Filesystem UUID:", render::uuid(&args.uuid));
line(
"Filesystem profile:",
Profile::of(args.feature).name().to_string(),
);
line("Filesystem features:", args.feature.names().join(" "));
line("Block size:", layout.block_size.to_string());
line("Inode size:", args.feature.inode_size.to_string());
line("Block count:", layout.total_blocks.to_string());
line("Inode count:", layout.total_inodes.to_string());
if let Some(u) = usage {
line("Free blocks:", u.free_blocks.to_string());
line("Free inodes:", u.free_inodes.to_string());
}
line("Reserved blocks:", layout.reserved_blocks.to_string());
line("Blocks per group:", layout.blocks_per_group.to_string());
line("Inodes per group:", layout.inodes_per_group.to_string());
line("Block groups:", layout.group_count.to_string());
line(
"Reserved GDT blocks:",
layout.reserved_gdt_blocks.to_string(),
);
line(
"Grows online to:",
format!("{} blocks", layout.max_grow_blocks),
);
line("Filesystem created:", render::iso8601(args.time.secs));
s
}
fn receipt(args: &FormatArgs, layout: &Layout, usage: Option<Usage>) -> String {
let mut out = String::new();
let mut o = Obj::new(&mut out);
o.u64("schema", crate::json::SCHEMA_VERSION);
o.str("uuid", &render::uuid(&args.uuid));
let label = &args.volume_name;
let label_end = label.iter().position(|&b| b == 0).unwrap_or(label.len());
o.bytes("volume_name", &label[..label_end]);
o.i64("created", args.time.secs);
o.str("profile", Profile::of(args.feature).name());
o.strings("features", &args.feature.names());
o.u64("block_size", u64::from(layout.block_size));
o.u64("inode_size", u64::from(args.feature.inode_size));
o.u64("blocks", layout.total_blocks);
o.u64("inodes", u64::from(layout.total_inodes));
match usage {
Some(u) => {
o.u64("free_blocks", u.free_blocks);
o.u64("free_inodes", u64::from(u.free_inodes));
}
None => {
o.raw("free_blocks", "null");
o.raw("free_inodes", "null");
}
}
o.u64("blocks_per_group", u64::from(layout.blocks_per_group));
o.u64("inodes_per_group", u64::from(layout.inodes_per_group));
o.u64("groups", u64::from(layout.group_count));
o.u64("first_data_block", u64::from(layout.first_data_block));
o.u64("gdt_blocks", u64::from(layout.gdt_blocks));
o.u64("reserved_gdt_blocks", u64::from(layout.reserved_gdt_blocks));
o.u64("flex_bg_size", u64::from(layout.flex_bg_size));
o.u64("max_grow_blocks", layout.max_grow_blocks);
o.u64("reserved_blocks", layout.reserved_blocks);
o.bool("written", usage.is_some());
o.end();
out.push('\n');
out
}