From c9a71a7176f13cbd86c8270c523c0e506b2ab424 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 30 Jul 2014 22:26:24 +0200 Subject: [PATCH] fs/GzipOutputStream: new class wrapping zlib --- Makefile.am | 17 +++++ configure.ac | 15 ++++ src/fs/output/GzipOutputStream.cxx | 106 +++++++++++++++++++++++++++++ src/fs/output/GzipOutputStream.hxx | 71 +++++++++++++++++++ test/run_gzip.cxx | 79 +++++++++++++++++++++ 5 files changed, 288 insertions(+) create mode 100644 src/fs/output/GzipOutputStream.cxx create mode 100644 src/fs/output/GzipOutputStream.hxx create mode 100644 test/run_gzip.cxx diff --git a/Makefile.am b/Makefile.am index c69b33020..cb85dd5ae 100644 --- a/Makefile.am +++ b/Makefile.am @@ -521,6 +521,13 @@ libfs_a_SOURCES = \ src/fs/StandardDirectory.cxx src/fs/StandardDirectory.hxx \ src/fs/CheckFile.cxx src/fs/CheckFile.hxx \ src/fs/DirectoryReader.hxx +libfs_a_CPPFLAGS = $(AM_CPPFLAGS) $(ZLIB_CFLAGS) + +if HAVE_ZLIB +libfs_a_SOURCES += \ + src/fs/output/GzipOutputStream.cxx src/fs/output/GzipOutputStream.hxx +FS_LIBS += $(ZLIB_LIBS) +endif # Storage library @@ -1596,6 +1603,16 @@ test_visit_archive_SOURCES = test/visit_archive.cxx \ endif +if HAVE_ZLIB + +noinst_PROGRAMS += test/run_gzip +test_run_gzip_LDADD = \ + libutil.a \ + $(FS_LIBS) +test_run_gzip_SOURCES = test/run_gzip.cxx + +endif + test_dump_text_file_LDADD = \ $(INPUT_LIBS) \ $(ARCHIVE_LIBS) \ diff --git a/configure.ac b/configure.ac index dbbb5a5ac..a5b875229 100644 --- a/configure.ac +++ b/configure.ac @@ -329,6 +329,11 @@ AC_ARG_ENABLE(audiofile, [enable audiofile support (WAV and others)]),, enable_audiofile=auto) +AC_ARG_ENABLE(zlib, + AS_HELP_STRING([--enable-zlib], + [enable zlib support (default: auto)]),, + enable_zlib=auto) + AC_ARG_ENABLE(bzip2, AS_HELP_STRING([--enable-bzip2], [enable bzip2 archive support (default: auto)]),, @@ -1031,6 +1036,16 @@ fi AM_CONDITIONAL(ENABLE_ISO9660_TEST, test x$MKISOFS != xno) +dnl ---------------------------------- zlib --------------------------------- + +MPD_AUTO_PKG(zlib, ZLIB, [zlib], + [zlib support], [zlib not found]) + +AM_CONDITIONAL(HAVE_ZLIB, test x$enable_zlib = xyes) +if test x$enable_zlib = xyes; then + AC_DEFINE(HAVE_ZLIB, 1, [Define to enable zlib support]) +fi + dnl ---------------------------------- libbz2 --------------------------------- MPD_AUTO_LIB(bzip2, BZ2, bz2, BZ2_bzDecompressInit, [-lbz2], [], diff --git a/src/fs/output/GzipOutputStream.cxx b/src/fs/output/GzipOutputStream.cxx new file mode 100644 index 000000000..d05645d9e --- /dev/null +++ b/src/fs/output/GzipOutputStream.cxx @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "GzipOutputStream.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +const Domain zlib_domain("zlib"); + +GzipOutputStream::GzipOutputStream(OutputStream &_next, Error &error) + :next(_next) +{ + z.next_in = nullptr; + z.avail_in = 0; + z.zalloc = Z_NULL; + z.zfree = Z_NULL; + z.opaque = Z_NULL; + + constexpr int windowBits = 15; + constexpr int gzip_encoding = 16; + + int result = deflateInit2(&z, Z_DEFAULT_COMPRESSION, Z_DEFLATED, + windowBits | gzip_encoding, + 8, Z_DEFAULT_STRATEGY); + if (result != Z_OK) { + z.opaque = this; + error.Set(zlib_domain, result, zError(result)); + } +} + +GzipOutputStream::~GzipOutputStream() +{ + if (IsDefined()) + deflateEnd(&z); +} + +bool +GzipOutputStream::Flush(Error &error) +{ + /* no more input */ + z.next_in = nullptr; + z.avail_in = 0; + + while (true) { + Bytef output[4096]; + z.next_out = output; + z.avail_out = sizeof(output); + + int result = deflate(&z, Z_FINISH); + if (z.next_out > output && + !next.Write(output, z.next_out - output, error)) + return false; + + if (result == Z_STREAM_END) + return true; + else if (result != Z_OK) { + error.Set(zlib_domain, result, zError(result)); + return false; + } + } +} + +bool +GzipOutputStream::Write(const void *_data, size_t size, Error &error) +{ + /* zlib's API requires non-const input pointer */ + void *data = const_cast(_data); + + z.next_in = reinterpret_cast(data); + z.avail_in = size; + + while (z.avail_in > 0) { + Bytef output[4096]; + z.next_out = output; + z.avail_out = sizeof(output); + + int result = deflate(&z, Z_NO_FLUSH); + if (result != Z_OK) { + error.Set(zlib_domain, result, zError(result)); + return false; + } + + if (z.next_out > output && + !next.Write(output, z.next_out - output, error)) + return false; + } + + return true; +} diff --git a/src/fs/output/GzipOutputStream.hxx b/src/fs/output/GzipOutputStream.hxx new file mode 100644 index 000000000..4f6d8b357 --- /dev/null +++ b/src/fs/output/GzipOutputStream.hxx @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_GZIP_OUTPUT_STREAM_HXX +#define MPD_GZIP_OUTPUT_STREAM_HXX + +#include "check.h" +#include "OutputStream.hxx" + +#include +#include + +class Error; +class Domain; + +extern const Domain zlib_domain; + +/** + * A filter that compresses data written to it using zlib, forwarding + * compressed data in the "gzip" format. + * + * Don't forget to call Flush() before destructing this object. + */ +class GzipOutputStream final : public OutputStream { + OutputStream &next; + + z_stream z; + +public: + /** + * Construct the filter. Call IsDefined() to check whether + * the constructor has succeeded. If not, #error will hold + * information about the failure. + */ + GzipOutputStream(OutputStream &_next, Error &error); + ~GzipOutputStream(); + + /** + * Check whether the constructor has succeeded. + */ + bool IsDefined() const { + return z.opaque == nullptr; + } + + /** + * Finish the file and write all data remaining in zlib's + * output buffer. + */ + bool Flush(Error &error); + + /* virtual methods from class OutputStream */ + bool Write(const void *data, size_t size, Error &error) override; +}; + +#endif diff --git a/test/run_gzip.cxx b/test/run_gzip.cxx new file mode 100644 index 000000000..11382a9fc --- /dev/null +++ b/test/run_gzip.cxx @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "fs/output/GzipOutputStream.hxx" +#include "fs/output/StdioOutputStream.hxx" +#include "util/Error.hxx" + +#include +#include +#include + +static bool +Copy(OutputStream &dest, int src, Error &error) +{ + while (true) { + char buffer[4096]; + ssize_t nbytes = read(src, buffer, sizeof(buffer)); + if (nbytes <= 0) { + if (nbytes < 0) { + error.SetErrno(); + return false; + } else + return true; + } + + if (!dest.Write(buffer, nbytes, error)) + return false; + } +} + +static bool +CopyGzip(OutputStream &_dest, int src, Error &error) +{ + GzipOutputStream dest(_dest, error); + return dest.IsDefined() && + Copy(dest, src, error) && + dest.Flush(error); +} + +static bool +CopyGzip(FILE *_dest, int src, Error &error) +{ + StdioOutputStream dest(_dest); + return CopyGzip(dest, src, error); +} + +int +main(int argc, gcc_unused char **argv) +{ + if (argc != 1) { + fprintf(stderr, "Usage: run_gzip\n"); + return EXIT_FAILURE; + } + + Error error; + if (!CopyGzip(stdout, STDIN_FILENO, error)) { + fprintf(stderr, "%s\n", error.GetMessage()); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +}