portable-network-archive 0.34.0

Portable-Network-Archive cli
Documentation
use crate::{
    cli::{PasswordArgs, SolidEntriesTransformStrategy, SolidEntriesTransformStrategyArgs},
    command::{
        Command, ask_password,
        core::{
            SplitArchiveReader, TransformStrategyKeepSolid, TransformStrategyUnSolid,
            collect_split_archives,
        },
    },
    ext::*,
    utils::env::NamedTempFile,
};
use clap::{Parser, ValueHint};
use pna::{NormalEntry, RawChunk, prelude::*};
use std::{io, path::PathBuf};

#[derive(Parser, Clone, Eq, PartialEq, Hash, Debug)]
pub(crate) struct MigrateCommand {
    #[command(flatten)]
    transform_strategy: SolidEntriesTransformStrategyArgs,
    #[command(flatten)]
    password: PasswordArgs,
    #[arg(short = 'f', long = "file", value_hint = ValueHint::FilePath)]
    archive: PathBuf,
    #[arg(long, help = "Output file path", value_hint = ValueHint::AnyPath)]
    output: PathBuf,
}

impl Command for MigrateCommand {
    #[inline]
    fn execute(self, _ctx: &crate::cli::GlobalContext) -> anyhow::Result<()> {
        migrate_metadata(self)
    }
}

#[hooq::hooq(anyhow)]
fn migrate_metadata(args: MigrateCommand) -> anyhow::Result<()> {
    let password = ask_password(args.password)?;

    let mut source = SplitArchiveReader::new(collect_split_archives(&args.archive)?)?;

    let output_path = args.output;
    let mut temp_file =
        NamedTempFile::new(|| output_path.parent().unwrap_or_else(|| ".".as_ref()))?;

    match args.transform_strategy.strategy() {
        SolidEntriesTransformStrategy::UnSolid => source.transform_entries(
            temp_file.as_file_mut(),
            password.as_deref(),
            #[hooq::skip_all]
            |entry| Ok(Some(convert_entry_to_owner_facet(entry?)?)),
            TransformStrategyUnSolid,
        ),
        SolidEntriesTransformStrategy::KeepSolid => source.transform_entries(
            temp_file.as_file_mut(),
            password.as_deref(),
            #[hooq::skip_all]
            |entry| Ok(Some(convert_entry_to_owner_facet(entry?)?)),
            TransformStrategyKeepSolid,
        ),
    }?;

    drop(source);

    temp_file.persist(output_path)?;
    Ok(())
}

#[inline]
fn convert_entry_to_owner_facet<T>(entry: NormalEntry<T>) -> io::Result<NormalEntry<T>>
where
    T: Clone,
    RawChunk<T>: Chunk,
    RawChunk<T>: From<RawChunk>,
{
    let keep_private_chunks = [crate::chunk::faCl, crate::chunk::faCe];
    let acls = entry.acl()?;
    let mut acl = vec![];
    for (platform, entries) in acls {
        acl.push(RawChunk::from_data(crate::chunk::faCl, platform.to_bytes()).into());
        for ace in entries {
            acl.push(RawChunk::from_data(crate::chunk::faCe, ace.to_bytes()).into());
        }
    }
    acl.extend(
        entry
            .extra_chunks()
            .iter()
            .filter(|it| !keep_private_chunks.contains(&it.ty()))
            .cloned(),
    );

    let own = crate::ext::ResolvedOwnership::from_metadata(entry.metadata());
    let src = entry.metadata();
    let created = src.created();
    let modified = src.modified();
    let accessed = src.accessed();
    let link_target_type = src.link_target_type();
    let new_meta = pna::Metadata::new()
        .with_created(created)
        .with_modified(modified)
        .with_accessed(accessed)
        .with_link_target_type(link_target_type)
        .with_owner_uid(own.uid.map(pna::OwnerUid::from))
        .with_owner_gid(own.gid.map(pna::OwnerGid::from))
        .with_owner_user_name(
            own.uname
                .as_deref()
                .and_then(crate::command::core::permission::owner_name_opt),
        )
        .with_owner_group_name(
            own.gname
                .as_deref()
                .and_then(crate::command::core::permission::owner_group_name_opt),
        )
        .with_permission_mode(own.mode.map(pna::PermissionMode::from))
        .with_owner_user_sid(
            own.user_sid
                .map(|s| pna::OwnerUserSid::new(s).expect("rescued sid within owner-facet bound")),
        )
        .with_owner_group_sid(
            own.group_sid
                .map(|s| pna::OwnerGroupSid::new(s).expect("rescued sid within owner-facet bound")),
        );
    Ok(entry.with_metadata(new_meta).with_extra_chunks(acl))
}