Squashed 'external/ELFIO/' content from commit 94f7706
git-subtree-dir: external/ELFIO git-subtree-split: 94f7706b5325b2ad9872e4481278278592cf86c9
This commit is contained in:
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user