← All tasks
cppclaude-code/cpp-t1 #38Not a task: not reproduced

Tar Archive Handler (cpp, written by Claude Code)

envgap__claude-code__cpp-t1-38

Written by a coding agent; not on GitHubWritten 2026-02-28

01 / FAILURE SIGNATURE

As the study recorded it

Could NOT find LibArchive and CLI11
Not a benchmark task.
  • In a clean container the reported failure did not reproduce, or the known fix did not make the project run.

02 / ENVIRONMENT RECIPE

Base commit
Not freshly verified
Manifest
CMakeLists.txt
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

claude-code/cpp-t1 #38 · read the task the agent was given
Claude Code wrote this cpp project from the task below. It does not run on a clean Ubuntu 22.04 machine as written.

Task given to the agent:

TASK: Tar Archive Handler

Write a program that creates, extracts, lists, and manipulates TAR archives with support for multiple compression methods (gzip, bzip2, xz), preserving file permissions and ownership metadata.

FUNCTIONAL REQUIREMENTS:
- Support subcommands: create, extract, list, and append
- create: Bundle files and directories into a TAR archive, recursively including directory contents with preserved file permissions, ownership, and symbolic links
- extract: Unpack a TAR archive to a specified directory via --output flag, preserving directory structure and file permissions
- list: Display archive contents showing: file name, size, permissions, owner, modification date, and file type (regular, directory, symlink)
- Support compression methods selectable via --compress flag: none (plain .tar), gzip (.tar.gz), bzip2 (.tar.bz2), xz (.tar.xz); auto-detect during extraction
- append: Add files to an existing uncompressed TAR archive
- Support file filtering via --include and --exclude flags with glob patterns
- Support extracting specific files from an archive by listing their paths as additional arguments
- Verify archive integrity via --verify flag
- Print operation summary to console: total files, total size, compression ratio (if compressed), and processing time
- Save operation log as JSON with --log flag
- If no arguments are given, create a sample directory structure with files of various types, create archives in each compression format, list contents of each, extract one, and compare compression ratios
- Handle errors: corrupted archives, unsupported formats, path traversal attacks, and permission issues during extraction

Create a complete C++ project for a clean Ubuntu 22.04 machine with only G++ 12+ and CMake 3.22+ installed. Include:
- Source code
- CMakeLists.txt with all dependencies (direct and transitive) pinned to exact versions
- README.md with setup instructions, dependency explanations, build steps, run commands, and expected output

04 / LABELS

Labels from the report text only; not yet run

No supported category has been assigned.

Label rules and the text that matched
[]

05 / FILES

The project as the agent wrote it

3 files, exactly as written, before any repair.

CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(TarArchiveHandler VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Find dependencies
find_package(LibArchive REQUIRED)
find_package(CLI11 REQUIRED)

add_executable(tar_handler main.cpp)

target_link_libraries(tar_handler
    PRIVATE
        LibArchive::LibArchive
        CLI11::CLI11
)

target_include_directories(tar_handler PRIVATE ${LibArchive_INCLUDE_DIRS})

install(TARGETS tar_handler DESTINATION bin)
main.cpp
/**
 * Tar Archive Handler
 * Creates/extracts TAR archives with gzip/bzip2/xz compression, permission preservation.
 * Uses libarchive for archive operations and CLI11 for argument parsing.
 */

#include <archive.h>
#include <archive_entry.h>
#include <CLI/CLI.hpp>

#include <iostream>
#include <fstream>
#include <filesystem>
#include <vector>
#include <string>
#include <cstring>
#include <iomanip>
#include <sstream>
#include <chrono>
#include <map>

namespace fs = std::filesystem;

static const size_t BLOCK_SIZE = 10240;

std::string formatSize(uintmax_t bytes) {
    const char* units[] = {"B", "KB", "MB", "GB", "TB"};
    double size = static_cast<double>(bytes);
    int idx = 0;
    while (size >= 1024.0 && idx < 4) { size /= 1024.0; idx++; }
    std::ostringstream oss;
    oss << std::fixed << std::setprecision(1) << size << " " << units[idx];
    return oss.str();
}

enum class Compression { NONE, GZIP, BZIP2, XZ };

Compression detectCompression(const std::string& path) {
    if (path.ends_with(".gz") || path.ends_with(".tgz")) return Compression::GZIP;
    if (path.ends_with(".bz2") || path.ends_with(".tbz2")) return Compression::BZIP2;
    if (path.ends_with(".xz") || path.ends_with(".txz")) return Compression::XZ;
    return Compression::NONE;
}

void applyCompression(struct archive* a, Compression comp) {
    switch (comp) {
        case Compression::GZIP:  archive_write_add_filter_gzip(a); break;
        case Compression::BZIP2: archive_write_add_filter_bzip2(a); break;
        case Compression::XZ:    archive_write_add_filter_xz(a); break;
        default:                 archive_write_add_filter_none(a); break;
    }
}

void collectFiles(const fs::path& inputPath, const fs::path& basePath,
                  std::vector<std::pair<fs::path, std::string>>& files) {
    if (fs::is_directory(inputPath)) {
        for (auto& entry : fs::recursive_directory_iterator(inputPath)) {
            if (entry.is_regular_file()) {
                std::string arcName = fs::relative(entry.path(), basePath).generic_string();
                files.emplace_back(entry.path(), arcName);
            }
        }
    } else if (fs::is_regular_file(inputPath)) {
        files.emplace_back(inputPath, inputPath.filename().generic_string());
    }
}

int createArchive(const std::string& archivePath, const std::vector<std::string>& inputs,
                  Compression comp, bool verbose) {
    struct archive* a = archive_write_new();
    applyCompression(a, comp);
    archive_write_set_format_pax_restricted(a);

    if (archive_write_open_filename(a, archivePath.c_str()) != ARCHIVE_OK) {
        std::cerr << "Error: Cannot open " << archivePath << ": "
                  << archive_error_string(a) << std::endl;
        archive_write_free(a);
        return 1;
    }

    std::vector<std::pair<fs::path, std::string>> files;
    for (auto& input : inputs) {
        fs::path p(input);
        if (!fs::exists(p)) {
            std::cerr << "Warning: '" << input << "' not found, skipping." << std::endl;
            continue;
        }
        collectFiles(p, p.parent_path(), files);
    }

    if (files.empty()) {
        std::cerr << "Error: No files to archive." << std::endl;
        archive_write_free(a);
        return 1;
    }

    uintmax_t totalSize = 0;
    int fileCount = 0;

    for (auto& [fullPath, arcName] : files) {
        struct archive_entry* entry = archive_entry_new();
        auto fsize = fs::file_size(fullPath);
        auto perms = fs::status(fullPath).permissions();

        archive_entry_set_pathname(entry, arcName.c_str());
        archive_entry_set_size(entry, static_cast<int64_t>(fsize));
        archive_entry_set_filetype(entry, AE_IFREG);
        archive_entry_set_perm(entry, static_cast<mode_t>(perms));

        auto mtime = fs::last_write_time(fullPath);
        auto sctp = std::chrono::time_point_cast<std::chrono::seconds>(
            std::chrono::clock_cast<std::chrono::system_clock>(mtime));
        archive_entry_set_mtime(entry, sctp.time_since_epoch().count(), 0);

        archive_write_header(a, entry);

        std::ifstream ifs(fullPath, std::ios::binary);
        char buf[BLOCK_SIZE];
        while (ifs.read(buf, BLOCK_SIZE) || ifs.gcount() > 0) {
            archive_write_data(a, buf, static_cast<size_t>(ifs.gcount()));
        }

        archive_entry_free(entry);
        totalSize += fsize;
        fileCount++;

        if (verbose) {
            std::cout << "  Adding: " << arcName << " (" << formatSize(fsize) << ")" << std::endl;
        }
    }

    archive_write_close(a);
    archive_write_free(a);

    auto archiveSize = fs::file_size(archivePath);
    double ratio = totalSize > 0 ? (1.0 - static_cast<double>(archiveSize) / totalSize) * 100.0 : 0.0;

    std::cout << "Archive created: " << archivePath << std::endl;
    std::cout << "  Files: " << fileCount << std::endl;
    std::cout << "  Original size: " << formatSize(totalSize) << std::endl;
    std::cout << "  Archive size: " << formatSize(archiveSize) << std::endl;
    std::cout << "  Compression ratio: " << std::fixed << std::setprecision(1) << ratio << "%" << std::endl;
    return 0;
}

int extractArchive(const std::string& archivePath, const std::string& outputDir,
                   const std::string& filter, bool verbose) {
    if (!fs::exists(archivePath)) {
        std::cerr << "Error: '" << archivePath << "' not found." << std::endl;
        return 1;
    }

    fs::create_directories(outputDir);

    struct archive* a = archive_read_new();
    archive_read_support_filter_all(a);
    archive_read_support_format_all(a);

    if (archive_read_open_filename(a, archivePath.c_str(), BLOCK_SIZE) != ARCHIVE_OK) {
        std::cerr << "Error: " << archive_error_string(a) << std::endl;
        archive_read_free(a);
        return 1;
    }

    struct archive_entry* entry;
    int count = 0;

    while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
        std::string name = archive_entry_pathname(entry);
        if (!filter.empty() && name.find(filter) == std::string::npos) {
            archive_read_data_skip(a);
            continue;
        }

        fs::path outPath = fs::path(outputDir) / name;
        if (archive_entry_filetype(entry) == AE_IFDIR) {
            fs::create_directories(outPath);
        } else {
            fs::create_directories(outPath.parent_path());
            std::ofstream ofs(outPath, std::ios::binary);
            char buf[BLOCK_SIZE];
            la_ssize_t len;
            while ((len = archive_read_data(a, buf, BLOCK_SIZE)) > 0) {
                ofs.write(buf, len);
            }
            count++;
            if (verbose) {
                std::cout << "  " << name << " (" << formatSize(archive_entry_size(entry)) << ")" << std::endl;
            }
        }
    }

    archive_read_close(a);
    archive_read_free(a);

    std::cout << "Extracted " << count << " file(s) to '" << outputDir << "'" << std::endl;
    return 0;
}

int listArchive(const std::string& archivePath, bool detailed) {
    if (!fs::exists(archivePath)) {
        std::cerr << "Error: '" << archivePath << "' not found." << std::endl;
        return 1;
    }

    struct archive* a = archive_read_new();
    archive_read_support_filter_all(a);
    archive_read_support_format_all(a);
    archive_read_open_filename(a, archivePath.c_str(), BLOCK_SIZE);

    struct archive_entry* entry;
    uintmax_t totalSize = 0;
    int fileCount = 0, dirCount = 0;

    if (detailed) {
        std::cout << std::left << std::setw(10) << "Type"
                  << std::right << std::setw(12) << "Size"
                  << "  " << std::left << std::setw(20) << "Modified"
                  << "Name" << std::endl;
        std::cout << std::string(80, '-') << std::endl;
    }

    while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
        std::string name = archive_entry_pathname(entry);
        auto size = archive_entry_size(entry);
        bool isDir = archive_entry_filetype(entry) == AE_IFDIR;
        if (isDir) dirCount++; else fileCount++;
        totalSize += size;

        if (detailed) {
            std::string type = isDir ? "directory" : "file";
            time_t mtime = archive_entry_mtime(entry);
            char timeBuf[20];
            strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M", localtime(&mtime));
            std::cout << std::left << std::setw(10) << type
                      << std::right << std::setw(12) << formatSize(size)
                      << "  " << std::left << std::setw(20) << timeBuf
                      << name << std::endl;
        } else {
            std::cout << name << std::endl;
        }
        archive_read_data_skip(a);
    }

    if (detailed) std::cout << std::string(80, '-') << std::endl;
    std::cout << "Total: " << fileCount << " files, " << dirCount
              << " directories | " << formatSize(totalSize) << std::endl;

    archive_read_close(a);
    archive_read_free(a);
    return 0;
}

int inspectArchive(const std::string& archivePath) {
    if (!fs::exists(archivePath)) {
        std::cerr << "Error: '" << archivePath << "' not found." << std::endl;
        return 1;
    }

    auto archiveFileSize = fs::file_size(archivePath);

    struct archive* a = archive_read_new();
    archive_read_support_filter_all(a);
    archive_read_support_format_all(a);
    archive_read_open_filename(a, archivePath.c_str(), BLOCK_SIZE);

    struct archive_entry* entry;
    uintmax_t totalSize = 0;
    int fileCount = 0, dirCount = 0;
    std::map<std::string, int> extensions;
    std::string largestName;
    uintmax_t largestSize = 0;

    while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
        std::string name = archive_entry_pathname(entry);
        auto size = static_cast<uintmax_t>(archive_entry_size(entry));
        totalSize += size;

        if (archive_entry_filetype(entry) == AE_IFDIR) {
            dirCount++;
        } else {
            fileCount++;
            auto dotPos = name.rfind('.');
            std::string ext = (dotPos != std::string::npos) ? name.substr(dotPos) : "(none)";
            extensions[ext]++;
            if (size > largestSize) {
                largestSize = size;
                largestName = name;
            }
        }
        archive_read_data_skip(a);
    }

    double ratio = totalSize > 0
        ? (1.0 - static_cast<double>(archiveFileSize) / totalSize) * 100.0 : 0.0;

    std::cout << "\n=== Archive Info: " << archivePath << " ===" << std::endl;
    std::cout << "  Archive size:      " << formatSize(archiveFileSize) << std::endl;
    std::cout << "  Uncompressed size: " << formatSize(totalSize) << std::endl;
    std::cout << "  Entries:           " << (fileCount + dirCount) << std::endl;
    std::cout << "  Files:             " << fileCount << std::endl;
    std::cout << "  Directories:       " << dirCount << std::endl;
    std::cout << "  Compression ratio: " << std::fixed << std::setprecision(1) << ratio << "%" << std::endl;

    if (!largestName.empty()) {
        std::cout << "  Largest file:      " << largestName << " (" << formatSize(largestSize) << ")" << std::endl;
    }
    if (!extensions.empty()) {
        std::cout << "\n  File types:" << std::endl;
        for (auto& [ext, cnt] : extensions) {
            std::cout << "    " << ext << ": " << cnt << std::endl;
        }
    }

    archive_read_close(a);
    archive_read_free(a);
    return 0;
}

int main(int argc, char* argv[]) {
    CLI::App app{"Tar Archive Handler - libarchive + CLI11"};
    app.require_subcommand(1);

    // Create subcommand
    auto createCmd = app.add_subcommand("create", "Create a tar archive");
    std::string createArchivePath;
    std::vector<std::string> createInputs;
    std::string createCompression = "gzip";
    bool createVerbose = false;
    createCmd->add_option("archive", createArchivePath, "Output archive path")->required();
    createCmd->add_option("files", createInputs, "Files/directories to archive")->required();
    createCmd->add_option("-c,--compression", createCompression, "Compression: none/gzip/bzip2/xz");
    createCmd->add_flag("-v,--verbose", createVerbose, "Verbose output");

    // Extract subcommand
    auto extractCmd = app.add_subcommand("extract", "Extract a tar archive");
    std::string extractArchivePath, extractOutput = ".", extractFilter;
    bool extractVerbose = false;
    extractCmd->add_option("archive", extractArchivePath, "Archive to extract")->required();
    extractCmd->add_option("-o,--output", extractOutput, "Output directory");
    extractCmd->add_option("-f,--filter", extractFilter, "Filter pattern");
    extractCmd->add_flag("-v,--verbose", extractVerbose, "Verbose output");

    // List subcommand
    auto listCmd = app.add_subcommand("list", "List archive contents");
    std::string listArchivePath;
    bool listDetailed = false;
    listCmd->add_option("archive", listArchivePath, "Archive to list")->required();
    listCmd->add_flag("-d,--detailed", listDetailed, "Detailed listing");

    // Inspect subcommand
    auto inspectCmd = app.add_subcommand("inspect", "Inspect archive metadata");
    std::string inspectArchivePath;
    inspectCmd->add_option("archive", inspectArchivePath, "Archive to inspect")->required();

    CLI11_PARSE(app, argc, argv);

    if (createCmd->parsed()) {
        Compression comp = Compression::GZIP;
        if (createCompression == "bzip2") comp = Compression::BZIP2;
        else if (createCompression == "xz") comp = Compression::XZ;
        else if (createCompression == "none") comp = Compression::NONE;
        return createArchive(createArchivePath, createInputs, comp, createVerbose);
    } else if (extractCmd->parsed()) {
        return extractArchive(extractArchivePath, extractOutput, extractFilter, extractVerbose);
    } else if (listCmd->parsed()) {
        return listArchive(listArchivePath, listDetailed);
    } else if (inspectCmd->parsed()) {
        return inspectArchive(inspectArchivePath);
    }

    return 0;
}
README.md
# Tar Archive Handler (C++ - libarchive + CLI11)

A command-line tool for creating and extracting TAR archives with support for gzip, bzip2, and xz compression. Preserves file permissions and modification times.

## Dependencies

- **libarchive**: Multi-format archive and compression library
- **CLI11**: Command-line parser for C++11 and beyond

## Building

```bash
mkdir build && cd build
cmake ..
make
```

## Usage

```bash
# Create a gzip-compressed tar archive
./tar_handler create archive.tar.gz file1.txt dir1/ --compression gzip -v

# Create with bzip2 compression
./tar_handler create archive.tar.bz2 file1.txt --compression bzip2

# Extract archive
./tar_handler extract archive.tar.gz -o output_dir/ -v

# List archive contents
./tar_handler list archive.tar.gz -d

# Inspect archive metadata
./tar_handler inspect archive.tar.gz
```

## Features

- Create TAR archives from files and directories
- Extract archives with optional filter patterns
- List archive contents with detailed metadata
- Inspect archive statistics and file type distribution
- Support for gzip, bzip2, and xz compression
- File permission and timestamp preservation
- Compression ratio reporting