diff --git a/kernel/gzip.cc b/kernel/gzip.cc new file mode 100644 index 00000000000..d69d5764edb --- /dev/null +++ b/kernel/gzip.cc @@ -0,0 +1,109 @@ +#include "kernel/yosys_common.h" +#include "kernel/log.h" +#include "kernel/gzip.h" +#include +#include +#include +#include +#include + +YOSYS_NAMESPACE_BEGIN + + +#ifdef YOSYS_ENABLE_ZLIB + +PRIVATE_NAMESPACE_BEGIN + +using namespace Zlib; + +static const size_t GZ_BUFFER_SIZE = 8192; +static void decompress_gzip(const std::string &filename, std::stringstream &out) +{ + char buffer[GZ_BUFFER_SIZE]; + int bytes_read; + gzFile gzf = gzopen(filename.c_str(), "rb"); + while(!gzeof(gzf)) { + bytes_read = gzread(gzf, reinterpret_cast(buffer), GZ_BUFFER_SIZE); + out.write(buffer, bytes_read); + } + gzclose(gzf); +} + +PRIVATE_NAMESPACE_END + +gzip_ostream::gzip_ostream() : std::ostream(nullptr) { + rdbuf(&outbuf); +} + +bool gzip_ostream::open(const std::string &filename) { + return outbuf.open(filename); +} + +gzip_ostream::gzip_streambuf::gzip_streambuf() { + setp(buffer, buffer + buffer_size - 1); +} + +bool gzip_ostream::gzip_streambuf::open(const std::string &filename) { + gzf = gzopen(filename.c_str(), "wb"); + return gzf != nullptr; +} + +int gzip_ostream::gzip_streambuf::sync() { + int num = pptr() - pbase(); + if (num > 0) { + if (gzwrite(gzf, reinterpret_cast(pbase()), num) != num) { + return -1; + } + pbump(-num); + } + return 0; +} + +gzip_ostream::gzip_streambuf::~gzip_streambuf() { + if (gzf) { + sync(); + gzclose(gzf); + } +} + +#endif // YOSYS_ENABLE_ZLIB + + +// Takes a successfully opened ifstream. If it's gzipped, returns an istream +// over a buffer of the file fully decompressed in memory. Otherwise, +// returns the original ifstream, rewound to the start. +std::istream* uncompressed(std::ifstream* f, const std::string filename) { + if (!f) + return nullptr; + // Check for gzip magic + unsigned char magic[3]; + int n = 0; + while (n < 3) + { + int c = f->get(); + if (c != EOF) { + magic[n] = (unsigned char) c; + } + n++; + } + if (n == 3 && magic[0] == 0x1f && magic[1] == 0x8b) { +#ifdef YOSYS_ENABLE_ZLIB + log("Found gzip magic in file `%s', decompressing using zlib.\n", filename.c_str()); + if (magic[2] != 8) + log_cmd_error("gzip file `%s' uses unsupported compression type %02x\n", + filename.c_str(), unsigned(magic[2])); + delete f; + std::stringstream *df = new std::stringstream(); + decompress_gzip(filename, *df); + return df; +#else + log_cmd_error("File `%s' is a gzip file, but Yosys is compiled without zlib.\n", filename.c_str()); +#endif // YOSYS_ENABLE_ZLIB + } else { + f->clear(); + f->seekg(0, std::ios::beg); + return f; + } +} + +YOSYS_NAMESPACE_END diff --git a/kernel/gzip.h b/kernel/gzip.h index 5bc517d06ee..61558590142 100644 --- a/kernel/gzip.h +++ b/kernel/gzip.h @@ -36,6 +36,76 @@ class gzip_ostream : public std::ostream { gzip_streambuf outbuf; // The stream buffer instance }; + +/* +An input stream that uses zlib to read gzip-compressed data from a file, +buffering the decompressed data internally using a stringbuf. +*/ +class gzip_istream final : public std::istream { +public: + gzip_istream() : std::istream(&inbuf) {} + + bool open(const std::string& filename) { + return inbuf.open(filename); + } + +private: + class gzip_streambuf final : public std::streambuf { + public: + gzip_streambuf() : gzf(nullptr) {} + + bool open(const std::string& filename) { + if (gzf) { + Zlib::gzclose(gzf); + } + gzf = Zlib::gzopen(filename.c_str(), "rb"); + if (!gzf) { + return false; + } + // Set up the get buffer + setg(buffer, // beginning of buffer + buffer, // current position + buffer); // end position (initially empty) + return true; + } + virtual ~gzip_streambuf() { + if (gzf) { + Zlib::gzclose(gzf); + } + } + + protected: + // Called when the buffer is empty and more input is needed + virtual int_type underflow() override { + if (gzf == nullptr) { + return traits_type::eof(); + } + int bytes_read = Zlib::gzread(gzf, buffer, buffer_size); + if (bytes_read <= 0) { + // An error occurred during reading + int err; + const char* error_msg = Zlib::gzerror(gzf, &err); + if (err != Z_STREAM_END) + log_error("%s", error_msg); + return traits_type::eof(); + } + + // Reset buffer pointers + setg(buffer, // beginning of buffer + buffer, // current position + buffer + bytes_read); // end position + return traits_type::to_int_type(buffer[0]); + } + + private: + static const int buffer_size = 4096; + char buffer[buffer_size]; + Zlib::gzFile gzf; + }; + + gzip_streambuf inbuf; // The stream buffer instance +}; + #endif // YOSYS_ENABLE_ZLIB std::istream* uncompressed(std::ifstream* f, const std::string filename);