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
//! Stage3 archive remapping
//!
//! This module converts a Gentoo stage3 archive (tar.xz) from PAX format to GNU tar format, with
//! GIDs and UIDs mapped according to configuration.

use super::headers::NewHeader;
use super::ArchiveEntity;
use crate::GlobalsFinal;
use anyhow::Result as AResult;
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::path::Path;
use tar::{Archive, Builder};
use xz2::{read::XzDecoder, write::XzEncoder};

const MB: u64 = 2u64.pow(20);

pub(super) fn convert(src: &Path, dst: &Path, globals: &GlobalsFinal) -> AResult<()> {
    // Create a source archive read and decode stream
    let src = File::open(src)?;
    let src = BufReader::new(src);
    let src = XzDecoder::new(src);
    let mut src = Archive::new(src);

    // Create a destination encode and write stream
    let mut dst = if globals.dry_run() {
        None
    } else {
        let dst = File::create(dst)?;
        let dst = BufWriter::new(dst);
        let dst = XzEncoder::new(dst, 9);
        Some(Builder::new(dst))
    };

    // Counters for progress tracking
    let mut sigma = 0;
    let mut count = 0;
    let mut typecount = HashMap::new();
    // Create a progress bar
    let bar = globals.spinner();

    // Convert each header to destination format. Send along with data to be written.
    for entry in src.entries()? {
        let mut entry = entry?;
        // Identify type of header
        let entity = ArchiveEntity::identify(&mut entry)?;

        {
            // Update counters & progress indicators
            count += 1;
            let delta = entry.size();
            sigma += delta;
            let sigma = sigma / MB;
            let delta = delta / MB;

            let msg = format!("[#:{count:>5}, Δ:{delta:>3} MiB, Σ:{sigma:>4} MiB] {entity}");
            bar.set_message(msg);
            bar.tick();

            let entity = format!("{entity}");
            let x = 1i32 + typecount.get(&entity).unwrap_or(&0);
            typecount.insert(entity, x);
        }

        // Write new header and data to file
        match NewHeader::create(&entity, globals)? {
            NewHeader::Primary { mut header, path } => {
                if let Some(ref mut dst) = dst {
                    dst.append_data(&mut header, path, &mut entry)?;
                }
            }
            NewHeader::Link {
                mut header,
                path,
                target,
            } => {
                if let Some(ref mut dst) = dst {
                    dst.append_link(&mut header, path, target)?;
                }
            }
        }
    }
    // Finish and close the stream
    if let Some(ref mut dst) = dst {
        dst.finish()?;
    }

    // Finishing messages and statistics
    bar.finish();
    log::info!("Remapping completed. Flushing buffers");
    for (stype, count) in typecount.iter() {
        log::info!("- {stype:<16} : {count:>5}");
    }

    Ok(())
}