git repos / sago_web_git

src/archive.h

browsing at commit = 9881d02ac31dadb3794f4728eb6cef4afe5721d3

raw · blame · history

#pragma once

#include <cstdint>
#include <cstdio>
#include <ctime>
#include <stdexcept>
#include <string>

// Thrown for any tar/gzip writing failure.
class ArchiveError : public std::runtime_error {
public:
    explicit ArchiveError(const std::string &what) : std::runtime_error(what) {}
};

// Streams a gzip-compressed tar archive to a file. Entry paths longer than the
// ustar limit are emitted via the GNU long-name extension, which modern tar
// implementations understand. Not thread safe: use one writer per archive.
class TarGzWriter {
public:
    explicit TarGzWriter(const std::string &path);
    ~TarGzWriter();
    TarGzWriter(const TarGzWriter &) = delete;
    TarGzWriter &operator=(const TarGzWriter &) = delete;

    void add_file(const std::string &path, uint32_t mode, std::time_t mtime,
                  const char *data, uint64_t size);
    void add_symlink(const std::string &path, const std::string &target,
                     std::time_t mtime);
    void add_dir(const std::string &path, std::time_t mtime);

    // Writes the terminating zero blocks and flushes the gzip stream. Must be
    // called for the archive to be valid; the destructor does not call it so a
    // half-built archive is never silently completed.
    void finish();

private:
    void write_header(const std::string &path, uint32_t mode, std::time_t mtime,
                      uint64_t size, char typeflag, const std::string &linkname);
    void write_gnu_long(const std::string &value, char typeflag);
    void write_padding(uint64_t size); // pads body to a 512-byte boundary
    void deflate_chunk(const void *data, size_t size, int flush);

    FILE *file_ = nullptr;
    void *stream_ = nullptr; // z_stream*, type-erased to keep zlib out of the header
    bool finished_ = false;
};