/* 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 [-e ] [-d ] [-a ] // // This example serves as both a reference for ARIO/ELFIO usage and a foundation for building custom archive management tools. // #include #include #include #include #include #include #include using namespace ARIO; using namespace ELFIO; //------------------------------------------------------------------------------ // Simple command line parser for: // arioso -e -d -a struct CommandLineOptions { std::string archive_name; ///< Name of the archive file std::vector extract_files; ///< List of files to extract std::vector delete_files; ///< List of files to delete std::vector add_files; ///< List of files to add }; //------------------------------------------------------------------------------ static CommandLineOptions parse_args( int argc, char** argv ) { if ( argc < 2 ) { std::cerr << "Usage: " << argv[0] << " [-e ] [-d ] [-a ]" << std::endl; return {}; } CommandLineOptions opts; opts.archive_name = argv[1]; std::vector* 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 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& 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( input ) ), std::istreambuf_iterator() ); 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 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; }