Files
ircolib/examples/arioso/arioso.cpp
T
iris a67f311330 Squashed 'external/ELFIO/' content from commit 94f7706
git-subtree-dir: external/ELFIO
git-subtree-split: 94f7706b5325b2ad9872e4481278278592cf86c9
2026-05-11 11:41:03 +02:00

347 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
Copyright (C) 2025-present by Serge Lamikhov-Center
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
//------------------------------------------------------------------------------
// arioso.cpp
//
// This example demonstrates how to use the ARIO library (with optional ELFIO integration)
// to manage UNIX archive (.ar) files from the command line. It provides a practical tool
// for extracting, deleting, and adding files to an archive, similar to the standard 'ar' utility.
//
// Purpose:
// - Showcase ARIOs API for reading, modifying, and writing UNIX archive files.
// - Illustrate integration with ELFIO for symbol extraction from ELF object files.
//
// Abilities:
// - Extraction: Extracts specified files from the archive to the current directory.
// - Deletion: Removes specified files from the archive.
// - Addition: Adds new files to the archive, collecting and storing global symbols if the file is an ELF object.
// - Archive update: Safely writes changes to the archive using a temporary file for atomic updates.
// - Command-line interface: Accepts commands in the form:
// arioso <archive> [-e <files...>] [-d <files...>] [-a <files...>]
//
// This example serves as both a reference for ARIO/ELFIO usage and a foundation for building custom archive management tools.
//
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <filesystem>
#include <ario/ario.hpp>
#include <elfio/elfio.hpp>
using namespace ARIO;
using namespace ELFIO;
//------------------------------------------------------------------------------
// Simple command line parser for:
// arioso -e <files...> -d <files...> -a <files...>
struct CommandLineOptions
{
std::string archive_name; ///< Name of the archive file
std::vector<std::string> extract_files; ///< List of files to extract
std::vector<std::string> delete_files; ///< List of files to delete
std::vector<std::string> add_files; ///< List of files to add
};
//------------------------------------------------------------------------------
static CommandLineOptions parse_args( int argc, char** argv )
{
if ( argc < 2 ) {
std::cerr
<< "Usage: " << argv[0]
<< " <archive> [-e <files...>] [-d <files...>] [-a <files...>]"
<< std::endl;
return {};
}
CommandLineOptions opts;
opts.archive_name = argv[1];
std::vector<std::string>* current = nullptr;
for ( int i = 2; i < argc; ++i ) {
std::string arg = argv[i];
if ( arg == "-e" ) {
current = &opts.extract_files;
}
else if ( arg == "-d" ) {
current = &opts.delete_files;
}
else if ( arg == "-a" ) {
current = &opts.add_files;
}
else if ( current ) {
current->emplace_back( arg );
}
else {
std::cerr << "Unknown argument or missing option: " << arg
<< std::endl;
}
}
return opts;
}
//------------------------------------------------------------------------------
// This function would contain the logic to extract the member data to a file
static ario::Result extract_member( const ario::Member& member )
{
std::cout << "Extracting member: " << member << ", Size: " << member.size
<< " bytes" << std::endl;
std::filesystem::path output_path =
std::filesystem::current_path() / member.name;
std::ofstream output_file( output_path, std::ios::binary );
if ( !output_file ) {
return { " Failed to create output file : " + output_path.string() };
}
output_file.write( member.data().c_str(), member.size );
if ( output_file.fail() ) {
return { "Failed to write member data to file: " +
output_path.string() };
}
return {}; // Return success
}
//------------------------------------------------------------------------------
// Extract all members from the command line extraction list.
// The search should be done by member's name. Exact match is expected
static int extract_members( const CommandLineOptions& opts,
const ARIO::ario& archive )
{
for ( const auto& file_name : opts.extract_files ) {
const auto& pmember = std::find( archive.members.begin(),
archive.members.end(), file_name );
if ( pmember != archive.members.end() ) {
auto result = extract_member( *pmember );
if ( !result.ok() ) {
std::cerr << "Error extracting member '" << file_name
<< "': " << result.what() << std::endl;
}
}
else {
std::cerr << "Member '" << file_name << "' not found in the library"
<< std::endl;
return 1;
}
}
return 0;
}
//------------------------------------------------------------------------------
// Copy members from the source archive to the target archive
static int copy_members( const CommandLineOptions& opts,
const ARIO::ario& archive,
ARIO::ario& target_archive )
{
for ( const auto& member : archive.members ) {
if ( std::find( opts.delete_files.begin(), opts.delete_files.end(),
member.name ) != opts.delete_files.end() ) {
std::cout << "Removing member: " << member << std::endl;
continue; // Skip this member
}
auto result = target_archive.add_member( member, member.data() );
if ( !result.ok() ) {
std::cerr << "Error adding member '" << member.name
<< "': " << result.what() << std::endl;
return 3;
}
// Copy member symbols
std::vector<std::string> symbols;
archive.get_symbols_for_member( member, symbols );
target_archive.add_symbols_for_member( target_archive.members.back(),
symbols );
}
return 0;
}
//------------------------------------------------------------------------------
// Collect global symbols from an ELF file
static void
collect_elf_global_symbols( const ELFIO::elfio& elf,
std::vector<std::string>& gathered_symbols )
{
for ( const auto& sec : elf.sections ) {
// Look for the symbol table section
if ( sec->get_type() == SHT_SYMTAB ) {
// Access the symbols in the symbol table
symbol_section_accessor symbols( elf, sec.get() );
std::string name;
Elf64_Addr value;
Elf_Xword size;
unsigned char bind = 0, type = 0;
Elf_Half section_index;
unsigned char other;
// Iterate over all sections in the ELF file and gather all symbols
// that are functions or objects, and have global binding
for ( Elf_Xword i = 0; i < symbols.get_symbols_num(); ++i ) {
// Extract symbol properties
symbols.get_symbol( i, name, value, size, bind, type,
section_index, other );
// For each global function or object symbol, check that the archive symbol table can find it
if ( ( type == STT_FUNC || type == STT_OBJECT ||
type == STT_TLS || type == STT_COMMON ) &&
bind == STB_GLOBAL ) {
gathered_symbols.emplace_back( name );
}
}
}
}
}
//------------------------------------------------------------------------------
// Add a new member to the archive
static ario::Result add_new_member( ario& archive,
const std::string_view& file_name )
{
std::filesystem::path full_file_path = file_name;
if ( !std::filesystem::exists( full_file_path ) ) {
return { "File does not exist: " + full_file_path.string() };
}
ario::Member new_member;
new_member.name = full_file_path.filename().string();
new_member.uid = 0;
new_member.gid = 0;
new_member.mode = 0644;
std::ifstream input( full_file_path, std::ios::binary );
if ( !input ) {
return { "Failed to open file: " + full_file_path.string() };
}
const std::string data( ( std::istreambuf_iterator<char>( input ) ),
std::istreambuf_iterator<char>() );
const auto result = archive.add_member( new_member, data );
if ( !result.ok() ) {
return result;
}
elfio elf;
if ( elf.load( full_file_path.string() ) ) {
std::vector<std::string> gathered_symbols;
collect_elf_global_symbols( elf, gathered_symbols );
archive.add_symbols_for_member( archive.members.back(),
gathered_symbols );
}
return {}; // Return success
}
//------------------------------------------------------------------------------
// Add members from the command line addition list
static int add_new_members( const CommandLineOptions& opts,
ARIO::ario& target_archive )
{
for ( const auto& file_name : opts.add_files ) {
std::cout << "Adding member: " << file_name << std::endl;
auto result = add_new_member( target_archive, file_name );
if ( !result.ok() ) {
std::cerr << "Error adding member '" << file_name
<< "': " << result.what() << std::endl;
return 3;
}
}
return 0;
}
//------------------------------------------------------------------------------
// Save the new archive to a file
static int save_new_archive( ARIO::ario& target_archive,
const std::string& archive_name )
{
// Create a temporary filename for the archive
auto temp_dir = std::filesystem::temp_directory_path();
std::string temp_filename;
do {
// Generate a random filename
temp_filename = "ario_tmp_" + std::to_string( std::rand() ) + ".ar";
} while ( std::filesystem::exists( temp_dir / temp_filename ) );
std::filesystem::path temp_path = temp_dir / temp_filename;
target_archive.save( temp_path.string() );
// Move temporary file to the original one
std::error_code ec;
std::filesystem::rename( temp_path, archive_name, ec );
if ( ec ) {
std::cerr << "Error renaming temporary file: " << ec.message()
<< std::endl;
return 4;
}
return 0;
}
//------------------------------------------------------------------------------
int main( int argc, char** argv )
{
auto opts = parse_args( argc, argv );
// Open existing library or create a new one. In the last case, the library will be empty.
ario archive;
const auto result = archive.load( opts.archive_name );
if ( !result.ok() ) {
std::cerr << "Error loading archive: " << result.what() << std::endl;
return 1;
}
// Extract members from the archive
int retVal = extract_members( opts, archive );
if ( retVal != 0 )
return retVal;
// Check if there are no files to delete or add
if ( opts.delete_files.empty() && opts.add_files.empty() ) {
// No files to delete or add. Exiting
return 0;
}
// Create a new (empty) target archive
ario target_archive;
// Copy members not included to the command line deletion list
retVal = copy_members( opts, archive, target_archive );
if ( retVal != 0 )
return retVal;
// Add new members from the command line addition list
retVal = add_new_members( opts, target_archive );
if ( retVal != 0 )
return retVal;
// Save the new archive
retVal = save_new_archive( target_archive, opts.archive_name );
if ( retVal != 0 )
return retVal;
return 0;
}