#pragma once #include #include #include #include #include // 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; };