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