347 lines
13 KiB
C++
347 lines
13 KiB
C++
/*
|
||
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 ARIO’s 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;
|
||
}
|