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

Tar Archive Handler (cpp, written by Claude Code)

envgap__claude-code__cpp-t2-38

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

01 / FAILURE SIGNATURE

As the study recorded it

BZip2 not installed; std::chrono::clock_cast not supported in GCC 11
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-t2 #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)

include(FetchContent)

# Find bzip2
find_package(BZip2 REQUIRED)

# Fetch indicators library
FetchContent_Declare(
    indicators
    GIT_REPOSITORY https://github.com/p-ranav/indicators.git
    GIT_TAG v2.3
)
FetchContent_MakeAvailable(indicators)

add_executable(tar_handler main.cpp)

target_link_libraries(tar_handler
    PRIVATE
        BZip2::BZip2
        indicators::indicators
)

install(TARGETS tar_handler DESTINATION bin)
main.cpp
/**
 * Tar Archive Handler
 * Creates/extracts TAR archives with bzip2 compression and progress indicators.
 * Uses bzip2 for compression and indicators for progress display.
 */

#include <indicators/progress_bar.hpp>
#include <indicators/cursor_control.hpp>
#include <bzlib.h>

#include <iostream>
#include <fstream>
#include <filesystem>
#include <vector>
#include <string>
#include <cstring>
#include <iomanip>
#include <sstream>
#include <algorithm>
#include <cstdint>
#include <ctime>
#include <array>

namespace fs = std::filesystem;

static const size_t TAR_BLOCK_SIZE = 512;
static const size_t BZ_BUF_SIZE = 65536;

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();
}

struct TarHeader {
    char name[100];
    char mode[8];
    char uid[8];
    char gid[8];
    char size[12];
    char mtime[12];
    char checksum[8];
    char typeflag;
    char linkname[100];
    char magic[6];
    char version[2];
    char uname[32];
    char gname[32];
    char devmajor[8];
    char devminor[8];
    char prefix[155];
    char padding[12];
};

static_assert(sizeof(TarHeader) == TAR_BLOCK_SIZE, "TarHeader must be 512 bytes");

void setOctal(char* field, size_t fieldSize, uintmax_t value) {
    std::ostringstream oss;
    oss << std::oct << value;
    std::string s = oss.str();
    size_t maxLen = fieldSize - 1;
    if (s.size() > maxLen) s = s.substr(s.size() - maxLen);
    std::memset(field, '0', maxLen);
    std::memcpy(field + maxLen - s.size(), s.c_str(), s.size());
    field[maxLen] = '\0';
}

uintmax_t parseOctal(const char* field, size_t fieldSize) {
    std::string s(field, fieldSize);
    s.erase(std::remove(s.begin(), s.end(), '\0'), s.end());
    s.erase(std::remove(s.begin(), s.end(), ' '), s.end());
    if (s.empty()) return 0;
    return std::stoull(s, nullptr, 8);
}

void computeChecksum(TarHeader& hdr) {
    std::memset(hdr.checksum, ' ', 8);
    unsigned int sum = 0;
    const unsigned char* raw = reinterpret_cast<const unsigned char*>(&hdr);
    for (size_t i = 0; i < TAR_BLOCK_SIZE; i++) sum += raw[i];
    setOctal(hdr.checksum, 7, sum);
    hdr.checksum[7] = ' ';
}

void collectEntries(const fs::path& root, const fs::path& base,
                    std::vector<std::pair<fs::path, std::string>>& entries) {
    if (fs::is_regular_file(root)) {
        entries.emplace_back(root, root.filename().generic_string());
    } else if (fs::is_directory(root)) {
        for (auto& e : fs::recursive_directory_iterator(root)) {
            if (e.is_regular_file()) {
                std::string rel = fs::relative(e.path(), base).generic_string();
                entries.emplace_back(e.path(), rel);
            }
        }
    }
}

std::vector<uint8_t> bzip2Compress(const std::vector<uint8_t>& input) {
    unsigned int destLen = static_cast<unsigned int>(input.size() + input.size() / 100 + 1000);
    std::vector<uint8_t> output(destLen);
    int ret = BZ2_bzBuffToBuffCompress(
        reinterpret_cast<char*>(output.data()), &destLen,
        const_cast<char*>(reinterpret_cast<const char*>(input.data())),
        static_cast<unsigned int>(input.size()), 9, 0, 30);
    if (ret != BZ_OK) throw std::runtime_error("bzip2 compression failed: " + std::to_string(ret));
    output.resize(destLen);
    return output;
}

std::vector<uint8_t> bzip2Decompress(const std::vector<uint8_t>& input) {
    unsigned int destLen = static_cast<unsigned int>(input.size() * 10);
    std::vector<uint8_t> output;
    int ret;
    do {
        output.resize(destLen);
        unsigned int tryLen = destLen;
        ret = BZ2_bzBuffToBuffDecompress(
            reinterpret_cast<char*>(output.data()), &tryLen,
            const_cast<char*>(reinterpret_cast<const char*>(input.data())),
            static_cast<unsigned int>(input.size()), 0, 0);
        if (ret == BZ_OUTBUFF_FULL) {
            destLen *= 2;
        } else if (ret == BZ_OK) {
            output.resize(tryLen);
        } else {
            throw std::runtime_error("bzip2 decompression failed: " + std::to_string(ret));
        }
    } while (ret == BZ_OUTBUFF_FULL);
    return output;
}

int createArchive(const std::string& archivePath, const std::vector<std::string>& inputs,
                  bool useBzip2, bool verbose) {
    std::vector<std::pair<fs::path, std::string>> entries;
    for (auto& inp : inputs) {
        fs::path p(inp);
        if (!fs::exists(p)) {
            std::cerr << "Warning: '" << inp << "' not found, skipping." << std::endl;
            continue;
        }
        collectEntries(p, p.parent_path(), entries);
    }

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

    indicators::ProgressBar bar{
        indicators::option::BarWidth{40},
        indicators::option::Start{"["},
        indicators::option::Fill{"="},
        indicators::option::Lead{">"},
        indicators::option::Remainder{" "},
        indicators::option::End{"]"},
        indicators::option::PostfixText{"Creating archive..."},
        indicators::option::ShowPercentage{true},
        indicators::option::ShowElapsedTime{true}
    };

    std::vector<uint8_t> tarData;
    uintmax_t totalOrigSize = 0;
    int fileCount = 0;

    for (size_t i = 0; i < entries.size(); i++) {
        auto& [fullPath, arcName] = entries[i];
        auto fsize = fs::file_size(fullPath);
        auto perms = fs::status(fullPath).permissions();
        auto ftime = fs::last_write_time(fullPath);
        auto sctp = std::chrono::time_point_cast<std::chrono::seconds>(
            std::chrono::clock_cast<std::chrono::system_clock>(ftime));
        time_t mtime = sctp.time_since_epoch().count();

        TarHeader hdr;
        std::memset(&hdr, 0, sizeof(hdr));
        std::strncpy(hdr.name, arcName.c_str(), 99);
        setOctal(hdr.mode, 8, static_cast<uintmax_t>(perms) & 0777);
        setOctal(hdr.uid, 8, 0);
        setOctal(hdr.gid, 8, 0);
        setOctal(hdr.size, 12, fsize);
        setOctal(hdr.mtime, 12, static_cast<uintmax_t>(mtime));
        hdr.typeflag = '0';
        std::memcpy(hdr.magic, "ustar", 5);
        hdr.magic[5] = '\0';
        hdr.version[0] = '0'; hdr.version[1] = '0';
        computeChecksum(hdr);

        const uint8_t* hdrBytes = reinterpret_cast<const uint8_t*>(&hdr);
        tarData.insert(tarData.end(), hdrBytes, hdrBytes + TAR_BLOCK_SIZE);

        std::ifstream ifs(fullPath, std::ios::binary);
        std::vector<uint8_t> fileData((std::istreambuf_iterator<char>(ifs)),
                                       std::istreambuf_iterator<char>());
        tarData.insert(tarData.end(), fileData.begin(), fileData.end());

        size_t remainder = TAR_BLOCK_SIZE - (fileData.size() % TAR_BLOCK_SIZE);
        if (remainder < TAR_BLOCK_SIZE) {
            tarData.insert(tarData.end(), remainder, 0);
        }

        totalOrigSize += fsize;
        fileCount++;

        float progress = static_cast<float>(i + 1) / entries.size() * 100.0f;
        bar.set_progress(static_cast<size_t>(progress));
        bar.set_option(indicators::option::PostfixText{arcName});

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

    // End-of-archive marker (two 512-byte zero blocks)
    tarData.insert(tarData.end(), TAR_BLOCK_SIZE * 2, 0);

    std::ofstream ofs(archivePath, std::ios::binary);
    if (useBzip2) {
        auto compressed = bzip2Compress(tarData);
        ofs.write(reinterpret_cast<const char*>(compressed.data()), compressed.size());
    } else {
        ofs.write(reinterpret_cast<const char*>(tarData.data()), tarData.size());
    }
    ofs.close();

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

    std::cout << "\nArchive created: " << archivePath << std::endl;
    std::cout << "  Files: " << fileCount << std::endl;
    std::cout << "  Original size: " << formatSize(totalOrigSize) << std::endl;
    std::cout << "  Archive size: " << formatSize(archiveSize) << std::endl;
    std::cout << "  Compression: " << (useBzip2 ? "bzip2" : "none") << 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);

    std::ifstream ifs(archivePath, std::ios::binary);
    std::vector<uint8_t> rawData((std::istreambuf_iterator<char>(ifs)),
                                  std::istreambuf_iterator<char>());
    ifs.close();

    bool isBz2 = archivePath.ends_with(".bz2") || archivePath.ends_with(".tbz2");
    std::vector<uint8_t> tarData;
    if (isBz2) {
        tarData = bzip2Decompress(rawData);
    } else {
        tarData = std::move(rawData);
    }

    size_t pos = 0;
    int count = 0;
    size_t totalEntries = 0;

    // Count entries first for progress bar
    {
        size_t p = 0;
        while (p + TAR_BLOCK_SIZE <= tarData.size()) {
            const TarHeader* h = reinterpret_cast<const TarHeader*>(tarData.data() + p);
            if (h->name[0] == '\0') break;
            totalEntries++;
            uintmax_t sz = parseOctal(h->size, 12);
            size_t blocks = (sz + TAR_BLOCK_SIZE - 1) / TAR_BLOCK_SIZE;
            p += TAR_BLOCK_SIZE + blocks * TAR_BLOCK_SIZE;
        }
    }

    indicators::ProgressBar bar{
        indicators::option::BarWidth{40},
        indicators::option::Start{"["},
        indicators::option::Fill{"#"},
        indicators::option::Lead{">"},
        indicators::option::Remainder{" "},
        indicators::option::End{"]"},
        indicators::option::PostfixText{"Extracting..."},
        indicators::option::ShowPercentage{true}
    };

    size_t idx = 0;
    while (pos + TAR_BLOCK_SIZE <= tarData.size()) {
        const TarHeader* hdr = reinterpret_cast<const TarHeader*>(tarData.data() + pos);
        if (hdr->name[0] == '\0') break;

        std::string name(hdr->name);
        uintmax_t fileSize = parseOctal(hdr->size, 12);
        pos += TAR_BLOCK_SIZE;

        if (!filter.empty() && name.find(filter) == std::string::npos) {
            size_t blocks = (fileSize + TAR_BLOCK_SIZE - 1) / TAR_BLOCK_SIZE;
            pos += blocks * TAR_BLOCK_SIZE;
            idx++;
            continue;
        }

        fs::path outPath = fs::path(outputDir) / name;
        fs::create_directories(outPath.parent_path());

        if (hdr->typeflag == '5') {
            fs::create_directories(outPath);
        } else {
            std::ofstream ofs(outPath, std::ios::binary);
            if (fileSize > 0 && pos + fileSize <= tarData.size()) {
                ofs.write(reinterpret_cast<const char*>(tarData.data() + pos), fileSize);
            }
            ofs.close();
            count++;

            if (verbose) {
                std::cout << "  " << name << " (" << formatSize(fileSize) << ")" << std::endl;
            }
        }

        size_t blocks = (fileSize + TAR_BLOCK_SIZE - 1) / TAR_BLOCK_SIZE;
        pos += blocks * TAR_BLOCK_SIZE;
        idx++;

        if (totalEntries > 0) {
            float progress = static_cast<float>(idx) / totalEntries * 100.0f;
            bar.set_progress(static_cast<size_t>(progress));
            bar.set_option(indicators::option::PostfixText{name});
        }
    }

    std::cout << "\nExtracted " << 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;
    }

    std::ifstream ifs(archivePath, std::ios::binary);
    std::vector<uint8_t> rawData((std::istreambuf_iterator<char>(ifs)),
                                  std::istreambuf_iterator<char>());
    ifs.close();

    bool isBz2 = archivePath.ends_with(".bz2") || archivePath.ends_with(".tbz2");
    std::vector<uint8_t> tarData;
    if (isBz2) {
        tarData = bzip2Decompress(rawData);
    } else {
        tarData = std::move(rawData);
    }

    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;
    }

    size_t pos = 0;
    uintmax_t totalSize = 0;
    int fileCount = 0, dirCount = 0;

    while (pos + TAR_BLOCK_SIZE <= tarData.size()) {
        const TarHeader* hdr = reinterpret_cast<const TarHeader*>(tarData.data() + pos);
        if (hdr->name[0] == '\0') break;

        std::string name(hdr->name);
        uintmax_t fileSize = parseOctal(hdr->size, 12);
        uintmax_t mtime = parseOctal(hdr->mtime, 12);
        bool isDir = (hdr->typeflag == '5');

        if (isDir) dirCount++; else fileCount++;
        totalSize += fileSize;

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

        pos += TAR_BLOCK_SIZE;
        size_t blocks = (fileSize + TAR_BLOCK_SIZE - 1) / TAR_BLOCK_SIZE;
        pos += blocks * TAR_BLOCK_SIZE;
    }

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

int main(int argc, char* argv[]) {
    if (argc < 2) {
        std::cout << "Tar Archive Handler (bzip2 + indicators)\n"
                  << "Usage:\n"
                  << "  tar_handler create <archive> <file1> [file2...] [--bzip2] [-v]\n"
                  << "  tar_handler extract <archive> [-o dir] [-f filter] [-v]\n"
                  << "  tar_handler list <archive> [-d]\n";
        return 1;
    }

    std::string command = argv[1];
    bool verbose = false, detailed = false, useBzip2 = false;
    std::string archivePath, outputDir = ".", filter;
    std::vector<std::string> files;

    for (int i = 2; i < argc; i++) {
        std::string arg = argv[i];
        if (arg == "-v" || arg == "--verbose") verbose = true;
        else if (arg == "-d" || arg == "--detailed") detailed = true;
        else if (arg == "--bzip2") useBzip2 = true;
        else if ((arg == "-o" || arg == "--output") && i + 1 < argc) outputDir = argv[++i];
        else if ((arg == "-f" || arg == "--filter") && i + 1 < argc) filter = argv[++i];
        else if (archivePath.empty()) archivePath = arg;
        else files.push_back(arg);
    }

    indicators::show_console_cursor(false);

    int result = 0;
    if (command == "create") {
        result = createArchive(archivePath, files, useBzip2, verbose);
    } else if (command == "extract") {
        result = extractArchive(archivePath, outputDir, filter, verbose);
    } else if (command == "list") {
        result = listArchive(archivePath, detailed);
    } else {
        std::cerr << "Unknown command: " << command << std::endl;
        result = 1;
    }

    indicators::show_console_cursor(true);
    return result;
}
README.md
# Tar Archive Handler (C++ - bzip2 + indicators)

A command-line tool for creating and extracting TAR archives with bzip2 compression and visual progress indicators. Implements the TAR format manually with bzip2 compression support.

## Dependencies

- **bzip2**: High-quality block-sorting file compressor library
- **indicators**: Activity indicators and progress bars for modern C++

## Building

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

## Usage

```bash
# Create a bzip2-compressed tar archive with progress
./tar_handler create archive.tar.bz2 file1.txt dir1/ --bzip2 -v

# Create uncompressed tar archive
./tar_handler create archive.tar file1.txt

# Extract archive with progress bar
./tar_handler extract archive.tar.bz2 -o output_dir/ -v

# List archive contents
./tar_handler list archive.tar.bz2 -d
```

## Features

- Manual TAR format implementation with POSIX headers
- bzip2 compression and decompression
- Visual progress bars during create and extract operations
- Permission preservation via TAR headers
- Detailed listing with file types, sizes, and timestamps
- Recursive directory traversal for archive creation