diff options
Diffstat (limited to 'src/lib-compression')
-rw-r--r-- | src/lib-compression/Makefile.am | 61 | ||||
-rw-r--r-- | src/lib-compression/Makefile.in | 976 | ||||
-rw-r--r-- | src/lib-compression/bench-compression.c | 168 | ||||
-rw-r--r-- | src/lib-compression/compression.c | 250 | ||||
-rw-r--r-- | src/lib-compression/compression.h | 55 | ||||
-rw-r--r-- | src/lib-compression/iostream-lz4.h | 30 | ||||
-rw-r--r-- | src/lib-compression/iostream-zstd-private.h | 35 | ||||
-rw-r--r-- | src/lib-compression/istream-bzlib.c | 231 | ||||
-rw-r--r-- | src/lib-compression/istream-decompress.c | 258 | ||||
-rw-r--r-- | src/lib-compression/istream-lz4.c | 281 | ||||
-rw-r--r-- | src/lib-compression/istream-lzma.c | 264 | ||||
-rw-r--r-- | src/lib-compression/istream-zlib.c | 431 | ||||
-rw-r--r-- | src/lib-compression/istream-zlib.h | 11 | ||||
-rw-r--r-- | src/lib-compression/istream-zstd.c | 268 | ||||
-rw-r--r-- | src/lib-compression/ostream-bzlib.c | 307 | ||||
-rw-r--r-- | src/lib-compression/ostream-lz4.c | 249 | ||||
-rw-r--r-- | src/lib-compression/ostream-zlib.c | 392 | ||||
-rw-r--r-- | src/lib-compression/ostream-zlib.h | 26 | ||||
-rw-r--r-- | src/lib-compression/ostream-zstd.c | 243 | ||||
-rw-r--r-- | src/lib-compression/test-compression.c | 1122 |
20 files changed, 5658 insertions, 0 deletions
diff --git a/src/lib-compression/Makefile.am b/src/lib-compression/Makefile.am new file mode 100644 index 0000000..f689b0c --- /dev/null +++ b/src/lib-compression/Makefile.am @@ -0,0 +1,61 @@ +noinst_LTLIBRARIES = libcompression.la + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + $(ZSTD_CFLAGS) + +libcompression_la_SOURCES = \ + compression.c \ + istream-decompress.c \ + istream-lzma.c \ + istream-lz4.c \ + istream-zlib.c \ + istream-bzlib.c \ + istream-zstd.c \ + ostream-lz4.c \ + ostream-zlib.c \ + ostream-bzlib.c \ + ostream-zstd.c +libcompression_la_LIBADD = \ + $(COMPRESS_LIBS) + +pkginc_libdir = $(pkgincludedir) +pkginc_lib_HEADERS = \ + compression.h \ + iostream-lz4.h \ + istream-zlib.h \ + ostream-zlib.h + +noinst_HEADERS = \ + iostream-zstd-private.h + +pkglib_LTLIBRARIES = libdovecot-compression.la +libdovecot_compression_la_SOURCES = +libdovecot_compression_la_LIBADD = libcompression.la ../lib-dovecot/libdovecot.la $(COMPRESS_LIBS) +libdovecot_compression_la_DEPENDENCIES = libcompression.la ../lib-dovecot/libdovecot.la +libdovecot_compression_la_LDFLAGS = -export-dynamic + +test_programs = \ + test-compression + +noinst_PROGRAMS = $(test_programs) bench-compression + +test_libs = \ + $(noinst_LTLIBRARIES) \ + ../lib-test/libtest.la \ + ../lib/liblib.la +test_deps = $(test_libs) + +test_compression_SOURCES = test-compression.c +test_compression_LDADD = $(test_libs) +test_compression_DEPENDENCIES = $(test_deps) + +bench_compression_SOURCES = bench-compression.c +bench_compression_LDADD = $(test_libs) +bench_compression_DEPENDENCIES = $(test_deps) + +check-local: + for bin in $(test_programs); do \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done diff --git a/src/lib-compression/Makefile.in b/src/lib-compression/Makefile.in new file mode 100644 index 0000000..7badd65 --- /dev/null +++ b/src/lib-compression/Makefile.in @@ -0,0 +1,976 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +noinst_PROGRAMS = $(am__EXEEXT_1) bench-compression$(EXEEXT) +subdir = src/lib-compression +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(pkginc_lib_HEADERS) $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__EXEEXT_1 = test-compression$(EXEEXT) +PROGRAMS = $(noinst_PROGRAMS) +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(pkglibdir)" \ + "$(DESTDIR)$(pkginc_libdir)" +LTLIBRARIES = $(noinst_LTLIBRARIES) $(pkglib_LTLIBRARIES) +am__DEPENDENCIES_1 = +libcompression_la_DEPENDENCIES = $(am__DEPENDENCIES_1) +am_libcompression_la_OBJECTS = compression.lo istream-decompress.lo \ + istream-lzma.lo istream-lz4.lo istream-zlib.lo \ + istream-bzlib.lo istream-zstd.lo ostream-lz4.lo \ + ostream-zlib.lo ostream-bzlib.lo ostream-zstd.lo +libcompression_la_OBJECTS = $(am_libcompression_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +am_libdovecot_compression_la_OBJECTS = +libdovecot_compression_la_OBJECTS = \ + $(am_libdovecot_compression_la_OBJECTS) +libdovecot_compression_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(libdovecot_compression_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_bench_compression_OBJECTS = bench-compression.$(OBJEXT) +bench_compression_OBJECTS = $(am_bench_compression_OBJECTS) +am_test_compression_OBJECTS = test-compression.$(OBJEXT) +test_compression_OBJECTS = $(am_test_compression_OBJECTS) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/bench-compression.Po \ + ./$(DEPDIR)/compression.Plo ./$(DEPDIR)/istream-bzlib.Plo \ + ./$(DEPDIR)/istream-decompress.Plo ./$(DEPDIR)/istream-lz4.Plo \ + ./$(DEPDIR)/istream-lzma.Plo ./$(DEPDIR)/istream-zlib.Plo \ + ./$(DEPDIR)/istream-zstd.Plo ./$(DEPDIR)/ostream-bzlib.Plo \ + ./$(DEPDIR)/ostream-lz4.Plo ./$(DEPDIR)/ostream-zlib.Plo \ + ./$(DEPDIR)/ostream-zstd.Plo ./$(DEPDIR)/test-compression.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libcompression_la_SOURCES) \ + $(libdovecot_compression_la_SOURCES) \ + $(bench_compression_SOURCES) $(test_compression_SOURCES) +DIST_SOURCES = $(libcompression_la_SOURCES) \ + $(libdovecot_compression_la_SOURCES) \ + $(bench_compression_SOURCES) $(test_compression_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +noinst_LTLIBRARIES = libcompression.la +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + $(ZSTD_CFLAGS) + +libcompression_la_SOURCES = \ + compression.c \ + istream-decompress.c \ + istream-lzma.c \ + istream-lz4.c \ + istream-zlib.c \ + istream-bzlib.c \ + istream-zstd.c \ + ostream-lz4.c \ + ostream-zlib.c \ + ostream-bzlib.c \ + ostream-zstd.c + +libcompression_la_LIBADD = \ + $(COMPRESS_LIBS) + +pkginc_libdir = $(pkgincludedir) +pkginc_lib_HEADERS = \ + compression.h \ + iostream-lz4.h \ + istream-zlib.h \ + ostream-zlib.h + +noinst_HEADERS = \ + iostream-zstd-private.h + +pkglib_LTLIBRARIES = libdovecot-compression.la +libdovecot_compression_la_SOURCES = +libdovecot_compression_la_LIBADD = libcompression.la ../lib-dovecot/libdovecot.la $(COMPRESS_LIBS) +libdovecot_compression_la_DEPENDENCIES = libcompression.la ../lib-dovecot/libdovecot.la +libdovecot_compression_la_LDFLAGS = -export-dynamic +test_programs = \ + test-compression + +test_libs = \ + $(noinst_LTLIBRARIES) \ + ../lib-test/libtest.la \ + ../lib/liblib.la + +test_deps = $(test_libs) +test_compression_SOURCES = test-compression.c +test_compression_LDADD = $(test_libs) +test_compression_DEPENDENCIES = $(test_deps) +bench_compression_SOURCES = bench-compression.c +bench_compression_LDADD = $(test_libs) +bench_compression_DEPENDENCIES = $(test_deps) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-compression/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/lib-compression/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +install-pkglibLTLIBRARIES: $(pkglib_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkglibdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkglibdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(pkglibdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(pkglibdir)"; \ + } + +uninstall-pkglibLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(pkglibdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(pkglibdir)/$$f"; \ + done + +clean-pkglibLTLIBRARIES: + -test -z "$(pkglib_LTLIBRARIES)" || rm -f $(pkglib_LTLIBRARIES) + @list='$(pkglib_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libcompression.la: $(libcompression_la_OBJECTS) $(libcompression_la_DEPENDENCIES) $(EXTRA_libcompression_la_DEPENDENCIES) + $(AM_V_CCLD)$(LINK) $(libcompression_la_OBJECTS) $(libcompression_la_LIBADD) $(LIBS) + +libdovecot-compression.la: $(libdovecot_compression_la_OBJECTS) $(libdovecot_compression_la_DEPENDENCIES) $(EXTRA_libdovecot_compression_la_DEPENDENCIES) + $(AM_V_CCLD)$(libdovecot_compression_la_LINK) -rpath $(pkglibdir) $(libdovecot_compression_la_OBJECTS) $(libdovecot_compression_la_LIBADD) $(LIBS) + +bench-compression$(EXEEXT): $(bench_compression_OBJECTS) $(bench_compression_DEPENDENCIES) $(EXTRA_bench_compression_DEPENDENCIES) + @rm -f bench-compression$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(bench_compression_OBJECTS) $(bench_compression_LDADD) $(LIBS) + +test-compression$(EXEEXT): $(test_compression_OBJECTS) $(test_compression_DEPENDENCIES) $(EXTRA_test_compression_DEPENDENCIES) + @rm -f test-compression$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_compression_OBJECTS) $(test_compression_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bench-compression.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/compression.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-bzlib.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-decompress.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-lz4.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-lzma.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-zlib.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-zstd.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ostream-bzlib.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ostream-lz4.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ostream-zlib.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ostream-zstd.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-compression.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) + @$(NORMAL_INSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ + done + +uninstall-pkginc_libHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-local +check: check-am +all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(pkglibdir)" "$(DESTDIR)$(pkginc_libdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ + clean-noinstPROGRAMS clean-pkglibLTLIBRARIES mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/bench-compression.Po + -rm -f ./$(DEPDIR)/compression.Plo + -rm -f ./$(DEPDIR)/istream-bzlib.Plo + -rm -f ./$(DEPDIR)/istream-decompress.Plo + -rm -f ./$(DEPDIR)/istream-lz4.Plo + -rm -f ./$(DEPDIR)/istream-lzma.Plo + -rm -f ./$(DEPDIR)/istream-zlib.Plo + -rm -f ./$(DEPDIR)/istream-zstd.Plo + -rm -f ./$(DEPDIR)/ostream-bzlib.Plo + -rm -f ./$(DEPDIR)/ostream-lz4.Plo + -rm -f ./$(DEPDIR)/ostream-zlib.Plo + -rm -f ./$(DEPDIR)/ostream-zstd.Plo + -rm -f ./$(DEPDIR)/test-compression.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-pkginc_libHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-pkglibLTLIBRARIES + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/bench-compression.Po + -rm -f ./$(DEPDIR)/compression.Plo + -rm -f ./$(DEPDIR)/istream-bzlib.Plo + -rm -f ./$(DEPDIR)/istream-decompress.Plo + -rm -f ./$(DEPDIR)/istream-lz4.Plo + -rm -f ./$(DEPDIR)/istream-lzma.Plo + -rm -f ./$(DEPDIR)/istream-zlib.Plo + -rm -f ./$(DEPDIR)/istream-zstd.Plo + -rm -f ./$(DEPDIR)/ostream-bzlib.Plo + -rm -f ./$(DEPDIR)/ostream-lz4.Plo + -rm -f ./$(DEPDIR)/ostream-zlib.Plo + -rm -f ./$(DEPDIR)/ostream-zstd.Plo + -rm -f ./$(DEPDIR)/test-compression.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-pkginc_libHEADERS uninstall-pkglibLTLIBRARIES + +.MAKE: check-am install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \ + check-local clean clean-generic clean-libtool \ + clean-noinstLTLIBRARIES clean-noinstPROGRAMS \ + clean-pkglibLTLIBRARIES cscopelist-am ctags ctags-am distclean \ + distclean-compile distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-pkginc_libHEADERS \ + install-pkglibLTLIBRARIES install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-pkginc_libHEADERS uninstall-pkglibLTLIBRARIES + +.PRECIOUS: Makefile + + +check-local: + for bin in $(test_programs); do \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/lib-compression/bench-compression.c b/src/lib-compression/bench-compression.c new file mode 100644 index 0000000..655079d --- /dev/null +++ b/src/lib-compression/bench-compression.c @@ -0,0 +1,168 @@ +/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "buffer.h" +#include "istream.h" +#include "ostream.h" +#include "randgen.h" +#include "time-util.h" +#include "strnum.h" +#include "compression.h" + +#include <stdio.h> +#include <unistd.h> +#include <time.h> + +/** + * Generates semi-compressible data in blocks of given size, to mimic emails + * remotely and then compresses and decompresses it using each algorithm. + * It measures the time spent on this giving some estimate how well the data + * compressed and how long it took. + */ + +static void bench_compression_speed(const struct compression_handler *handler, + unsigned int level, unsigned long block_count) +{ + struct istream *is = i_stream_create_file("decompressed.bin", 1024); + struct ostream *os = o_stream_create_file("compressed.bin", 0, 0644, 0); + struct ostream *os_compressed = handler->create_ostream(os, level); + o_stream_unref(&os); + + const unsigned char *data; + uint64_t ts_0, ts_1; + size_t siz; + double compression_speed, decompression_speed; + + ts_0 = i_nanoseconds(); + + while (i_stream_read_more(is, &data, &siz) > 0) { + o_stream_nsend(os_compressed, data, siz); + i_stream_skip(is, siz); + } + + if (is->stream_errno != 0) + printf("Error: %s\n", i_stream_get_error(is)); + + i_assert(o_stream_finish(os_compressed) == 1); + o_stream_unref(&os_compressed); + i_stream_unref(&is); + + ts_1 = i_nanoseconds(); + + /* check ratio */ + struct stat st_1, st_2; + if (stat("decompressed.bin", &st_1) != 0) + i_fatal("stat(decompressed.bin): %m"); + if (stat("compressed.bin", &st_2) != 0) + i_fatal("stat(compressed.bin): %m"); + + double ratio = (double)st_2.st_size / (double)st_1.st_size; + + compression_speed = ((double)(ts_1-ts_0))/((double)block_count); + compression_speed /= 1000.0L; + + is = i_stream_create_file("compressed.bin", 1024); + os = o_stream_create_file("decompressed.bin", 0, 0644, 0); + struct istream *is_decompressed = handler->create_istream(is); + i_stream_unref(&is); + + ts_0 = i_nanoseconds(); + + while (i_stream_read_more(is_decompressed, &data, &siz) > 0) { + o_stream_nsend(os, data, siz); + i_stream_skip(is_decompressed, siz); + } + + if (is_decompressed->stream_errno != 0) + printf("Error: %s\n", i_stream_get_error(is_decompressed)); + + i_assert(o_stream_finish(os) == 1); + o_stream_unref(&os); + i_stream_unref(&is_decompressed); + + ts_1 = i_nanoseconds(); + + decompression_speed = ((double)(ts_1 - ts_0))/((double)block_count); + decompression_speed /= 1000.0L; + + printf("%s\n", handler->name); + printf("\tCompression: %0.02lf us/block\n\tSpace Saving: %0.02lf%%\n", + compression_speed, (1.0-ratio)*100.0); + printf("\tDecompression: %0.02lf us/block\n\n", decompression_speed); + +} + +static void print_usage(const char *prog) +{ + fprintf(stderr, "Usage: %s block_size count level\n", prog); + fprintf(stderr, "Runs with 1000 8k blocks using level 6 if nothing given\n"); + lib_exit(1); +} + +int main(int argc, const char *argv[]) +{ + unsigned int level = 6; + lib_init(); + + unsigned long block_size = 8192UL; + unsigned long block_count = 1000UL; + + if (argc >= 3) { + if (str_to_ulong(argv[1], &block_size) < 0 || + str_to_ulong(argv[2], &block_count) < 0) { + fprintf(stderr, "Invalid parameters\n"); + print_usage(argv[0]); + } + if (argc == 4 && + str_to_uint(argv[3], &level) < 0) { + fprintf(stderr, "Invalid parameters\n"); + print_usage(argv[0]); + } + if (argc > 4) { + print_usage(argv[0]); + } + } else if (argc != 1) { + print_usage(argv[0]); + } + + unsigned char buf[block_size]; + printf("Input data is %lu blocks of %lu bytes\n\n", block_count, block_size); + + time_t t0 = time(NULL); + + /* create plaintext file */ + struct ostream *os = o_stream_create_file("decompressed.bin", 0, 0644, 0); + for (unsigned long r = 0; r < block_count; r++) { + time_t t1 = time(NULL); + if (t1 - t0 >= 1) { + printf("Building block %8lu / %-8lu\r", r, block_count); + fflush(stdout); + t0 = t1; + } + for (size_t i = 0; i < sizeof(buf); i++) { + if (i_rand_limit(3) == 0) + buf[i] = i_rand_limit(4); + else + buf[i] = i; + } + o_stream_nsend(os, buf, sizeof(buf)); + } + + i_assert(o_stream_finish(os) == 1); + o_stream_unref(&os); + + printf("Input data constructed \n"); + + for (unsigned int i = 0; compression_handlers[i].name != NULL; i++) T_BEGIN { + if (compression_handlers[i].create_istream != NULL && + compression_handlers[i].create_ostream != NULL) { + bench_compression_speed(&compression_handlers[i], level, + block_count); + } + } T_END; + + i_unlink("decompressed.bin"); + i_unlink("compressed.bin"); + + lib_deinit(); +} diff --git a/src/lib-compression/compression.c b/src/lib-compression/compression.c new file mode 100644 index 0000000..e562e73 --- /dev/null +++ b/src/lib-compression/compression.c @@ -0,0 +1,250 @@ +/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "istream.h" +#include "istream-zlib.h" +#include "ostream-zlib.h" +#include "iostream-lz4.h" +#include "compression.h" + +#ifndef HAVE_ZLIB +# define i_stream_create_gz NULL +# define o_stream_create_gz NULL +# define i_stream_create_deflate NULL +# define o_stream_create_deflate NULL +# define compression_get_min_level_gz NULL +# define compression_get_default_level_gz NULL +# define compression_get_max_level_gz NULL +#endif +#ifndef HAVE_BZLIB +# define i_stream_create_bz2 NULL +# define o_stream_create_bz2 NULL +# define compression_get_min_level_bz2 NULL +# define compression_get_default_level_bz2 NULL +# define compression_get_max_level_bz2 NULL +#endif +#ifndef HAVE_LZMA +# define i_stream_create_lzma NULL +#endif +#ifndef HAVE_LZ4 +# define i_stream_create_lz4 NULL +# define o_stream_create_lz4 NULL +# define compression_get_min_level_lz4 NULL +# define compression_get_default_level_lz4 NULL +# define compression_get_max_level_lz4 NULL +#endif +#ifndef HAVE_ZSTD +# define i_stream_create_zstd NULL +# define o_stream_create_zstd NULL +# define compression_get_min_level_zstd NULL +# define compression_get_default_level_zstd NULL +# define compression_get_max_level_zstd NULL +#endif + +static bool is_compressed_zlib(struct istream *input) +{ + const unsigned char *data; + size_t size; + + /* Peek in to the stream and see if it looks like it's compressed + (based on its header). This also means that users can try to exploit + security holes in the uncompression library by APPENDing a specially + crafted mail. So let's hope zlib is free of holes. */ + if (i_stream_read_bytes(input, &data, &size, 2) <= 0) + return FALSE; + i_assert(size >= 2); + + return data[0] == 31 && data[1] == 139; +} + +static bool is_compressed_bzlib(struct istream *input) +{ + const unsigned char *data; + size_t size; + + if (i_stream_read_bytes(input, &data, &size, 4) <= 0) + return FALSE; + if (memcmp(data, "BZh", 3) != 0) + return FALSE; + if (data[3] < '1' || data[3] > '9') + return FALSE; + /* The above is enough to be considered as the bzlib magic. + Normally it's followed by data header beginning with 0x31. However, + with empty compressed files it's followed by 0x17. */ + return TRUE; +} + +static bool is_compressed_xz(struct istream *input) +{ + const unsigned char *data; + size_t size; + + if (i_stream_read_bytes(input, &data, &size, 6) <= 0) + return FALSE; + return memcmp(data, "\xfd\x37\x7a\x58\x5a\x00", 6) == 0; +} + +static bool is_compressed_lz4(struct istream *input) +{ + const unsigned char *data; + size_t size; + + if (i_stream_read_bytes(input, &data, &size, IOSTREAM_LZ4_MAGIC_LEN) <= 0) + return FALSE; + /* there is no standard LZ4 header, so we've created our own */ + return memcmp(data, IOSTREAM_LZ4_MAGIC, IOSTREAM_LZ4_MAGIC_LEN) == 0; +} + +#define ZSTD_MAGICNUMBER 0xFD2FB528 /* valid since v0.8.0 */ +static bool is_compressed_zstd(struct istream *input) +{ + const unsigned char *data; + size_t size = 0; + + if (i_stream_read_bytes(input, &data, &size, sizeof(uint32_t)) <= 0) + return FALSE; + i_assert(size >= sizeof(uint32_t)); + + return le32_to_cpu_unaligned(data) == ZSTD_MAGICNUMBER; +} + +int compression_lookup_handler(const char *name, + const struct compression_handler **handler_r) +{ + unsigned int i; + + for (i = 0; compression_handlers[i].name != NULL; i++) { + if (strcmp(name, compression_handlers[i].name) == 0) { + if (compression_handlers[i].create_istream == NULL || + compression_handlers[i].create_ostream == NULL) { + /* Handler is known but not compiled in */ + return 0; + } + (*handler_r) = &compression_handlers[i]; + return 1; + } + } + return -1; +} + +const struct compression_handler * +compression_detect_handler(struct istream *input) +{ + unsigned int i; + + for (i = 0; compression_handlers[i].name != NULL; i++) { + if (compression_handlers[i].is_compressed != NULL && + compression_handlers[i].is_compressed(input)) + return &compression_handlers[i]; + } + return NULL; +} + +int compression_lookup_handler_from_ext(const char *path, + const struct compression_handler **handler_r) +{ + unsigned int i; + size_t len, path_len = strlen(path); + + for (i = 0; compression_handlers[i].name != NULL; i++) { + if (compression_handlers[i].ext == NULL) + continue; + + len = strlen(compression_handlers[i].ext); + if (path_len > len && + strcmp(path + path_len - len, compression_handlers[i].ext) == 0) { + if (compression_handlers[i].create_istream == NULL || + compression_handlers[i].create_ostream == NULL) { + /* Handler is known but not compiled in */ + return 0; + } + (*handler_r) = &compression_handlers[i]; + return 1; + } + } + return -1; +} + +static int compression_get_min_level_unsupported(void) +{ + return -1; +} + +static int compression_get_default_level_unsupported(void) +{ + return -1; +} + +static int compression_get_max_level_unsupported(void) +{ + return -1; +} + +const struct compression_handler compression_handlers[] = { + { + .name = "gz", + .ext = ".gz", + .is_compressed = is_compressed_zlib, + .create_istream = i_stream_create_gz, + .create_ostream = o_stream_create_gz, + .get_min_level = compression_get_min_level_gz, + .get_default_level = compression_get_default_level_gz, + .get_max_level = compression_get_max_level_gz, + }, + { + .name = "bz2", + .ext = ".bz2", + .is_compressed = is_compressed_bzlib, + .create_istream = i_stream_create_bz2, + .create_ostream = o_stream_create_bz2, + .get_min_level = compression_get_min_level_bz2, + .get_default_level = compression_get_default_level_bz2, + .get_max_level = compression_get_max_level_bz2, + }, + { + .name = "deflate", + .ext = NULL, + .is_compressed = NULL, + .create_istream = i_stream_create_deflate, + .create_ostream = o_stream_create_deflate, + .get_min_level = compression_get_min_level_gz, + .get_default_level = compression_get_default_level_gz, + .get_max_level = compression_get_max_level_gz, + }, + { + .name = "xz", + .ext = ".xz", + .is_compressed = is_compressed_xz, + .create_istream = i_stream_create_lzma, + .create_ostream = NULL, + .get_min_level = compression_get_min_level_unsupported, + .get_default_level = compression_get_default_level_unsupported, + .get_max_level = compression_get_max_level_unsupported, + }, + { + .name = "lz4", + .ext = ".lz4", + .is_compressed = is_compressed_lz4, + .create_istream = i_stream_create_lz4, + .create_ostream = o_stream_create_lz4, + .get_min_level = compression_get_min_level_lz4, /* does not actually support any of this */ + .get_default_level = compression_get_default_level_lz4, + .get_max_level = compression_get_max_level_lz4, + }, + { + .name = "zstd", + .ext = ".zstd", + .is_compressed = is_compressed_zstd, + .create_istream = i_stream_create_zstd, + .create_ostream = o_stream_create_zstd, + .get_min_level = compression_get_min_level_zstd, + .get_default_level = compression_get_default_level_zstd, + .get_max_level = compression_get_max_level_zstd, + }, + { + .name = "unsupported", + }, + { + .name = NULL, + } +}; diff --git a/src/lib-compression/compression.h b/src/lib-compression/compression.h new file mode 100644 index 0000000..60d58bf --- /dev/null +++ b/src/lib-compression/compression.h @@ -0,0 +1,55 @@ +#ifndef COMPRESSION_H +#define COMPRESSION_H + +enum istream_decompress_flags { + /* If stream isn't detected to be compressed, return it as passthrough + istream. */ + ISTREAM_DECOMPRESS_FLAG_TRY = BIT(0), +}; + +/* Compressed input is always detected once at maximum this many bytes have + been read. This value must be smaller than a typical istream max buffer + size. */ +#define COMPRESSION_HDR_MAX_SIZE 128 + +struct compression_handler { + const char *name; + const char *ext; + bool (*is_compressed)(struct istream *input); + struct istream *(*create_istream)(struct istream *input); + struct ostream *(*create_ostream)(struct ostream *output, int level); + /* returns minimum level */ + int (*get_min_level)(void); + /* the default can be -1 (e.g. gz), so the return value of this has to + be used as-is. */ + int (*get_default_level)(void); + /* returns maximum level */ + int (*get_max_level)(void); +}; + +extern const struct compression_handler compression_handlers[]; + +/* Returns 1 if compression handler was found and is usable, 0 if support isn't + compiled in, -1 if unknown. */ +int compression_lookup_handler(const char *name, + const struct compression_handler **handler_r); +/* Detect handler by looking at the first few bytes of the input stream. */ +const struct compression_handler * +compression_detect_handler(struct istream *input); +/* Lookup handler based on filename extension in the path, returns the same + * values as compression_lookup_handler. */ +int compression_lookup_handler_from_ext(const char *path, + const struct compression_handler **handler_r); + +/* Automatically detect the compression format. Note that using tee-istream as + one of the parent streams is dangerous here: A decompression istream may + have to read a lot of data (e.g. 8 kB isn't enough) before it returns even + the first byte as output. If the other tee children aren't read forward, + this can cause an infinite loop when i_stream_read() is always returning 0. + This is why ISTREAM_DECOMPRESS_FLAG_TRY should be used instead of attempting + to implement similar functionality with tee-istream. */ +struct istream * +i_stream_create_decompress(struct istream *input, + enum istream_decompress_flags flags); + +#endif diff --git a/src/lib-compression/iostream-lz4.h b/src/lib-compression/iostream-lz4.h new file mode 100644 index 0000000..a0897f5 --- /dev/null +++ b/src/lib-compression/iostream-lz4.h @@ -0,0 +1,30 @@ +#ifndef IOSTREAM_LZ4_H +#define IOSTREAM_LZ4_H + +/* + Dovecot's LZ4 compressed files contain: + + IOSTREAM_LZ4_HEADER + n x (4 byte big-endian: compressed chunk length, compressed chunk) +*/ + +#define IOSTREAM_LZ4_MAGIC "Dovecot-LZ4\x0d\x2a\x9b\xc5" +#define IOSTREAM_LZ4_MAGIC_LEN (sizeof(IOSTREAM_LZ4_MAGIC)-1) + +struct iostream_lz4_header { + unsigned char magic[IOSTREAM_LZ4_MAGIC_LEN]; + /* OSTREAM_LZ4_CHUNK_SIZE in big-endian */ + unsigned char max_uncompressed_chunk_size[4]; +}; + +/* How large chunks we're buffering into memory before compressing them */ +#define OSTREAM_LZ4_CHUNK_SIZE (1024*64) +/* How large chunks we allow in input data before returning a failure. + This must be at least OSTREAM_LZ4_CHUNK_SIZE, but for future compatibility + should be somewhat higher (but not too high to avoid wasting memory for + corrupted files). */ +#define ISTREAM_LZ4_CHUNK_SIZE (1024*1024) + +#define IOSTREAM_LZ4_CHUNK_PREFIX_LEN 4 /* big-endian size of chunk */ + +#endif diff --git a/src/lib-compression/iostream-zstd-private.h b/src/lib-compression/iostream-zstd-private.h new file mode 100644 index 0000000..e430bbc --- /dev/null +++ b/src/lib-compression/iostream-zstd-private.h @@ -0,0 +1,35 @@ +#ifndef IOSTREAM_ZSTD_PRIVATE_H +#define IOSTREAM_ZSTD_PRIVATE_H 1 + +/* a horrible hack to fix issues when the installed libzstd is lot + newer than what we were compiled against. */ +static inline ZSTD_ErrorCode zstd_version_errcode(ZSTD_ErrorCode err) +{ +#if ZSTD_VERSION_NUMBER < 10301 + if (ZSTD_versionNumber() > 10300) { + /* reinterpret them */ + if (err == 10) + return ZSTD_error_prefix_unknown; + if (err == 32) + return ZSTD_error_dictionary_wrong; + if (err == 62) + return ZSTD_error_init_missing; + if (err == 64) + return ZSTD_error_memory_allocation; + return ZSTD_error_GENERIC; + } +#endif + return err; +} + +static inline void zstd_version_check(void) +{ + /* error codes were pinned on 1.3.1, so we only care about + versions before that. */ + if (ZSTD_VERSION_NUMBER < 10301 || ZSTD_versionNumber() < 10301) + if (ZSTD_versionNumber() / 100 != ZSTD_VERSION_NUMBER / 100) + i_warning("zstd: Compiled against %u, but %u installed!", + ZSTD_VERSION_NUMBER, ZSTD_versionNumber()); +} + +#endif diff --git a/src/lib-compression/istream-bzlib.c b/src/lib-compression/istream-bzlib.c new file mode 100644 index 0000000..aae1027 --- /dev/null +++ b/src/lib-compression/istream-bzlib.c @@ -0,0 +1,231 @@ +/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" + +#ifdef HAVE_BZLIB + +#include "istream-private.h" +#include "istream-zlib.h" +#include <bzlib.h> + +#define CHUNK_SIZE (1024*64) + +struct bzlib_istream { + struct istream_private istream; + + bz_stream zs; + uoff_t eof_offset; + struct stat last_parent_statbuf; + + bool hdr_read:1; + bool marked:1; + bool zs_closed:1; +}; + +static void i_stream_bzlib_close(struct iostream_private *stream, + bool close_parent) +{ + struct bzlib_istream *zstream = (struct bzlib_istream *)stream; + + if (!zstream->zs_closed) { + (void)BZ2_bzDecompressEnd(&zstream->zs); + zstream->zs_closed = TRUE; + } + if (close_parent) + i_stream_close(zstream->istream.parent); +} + +static void bzlib_read_error(struct bzlib_istream *zstream, const char *error) +{ + io_stream_set_error(&zstream->istream.iostream, + "bzlib.read(%s): %s at %"PRIuUOFF_T, + i_stream_get_name(&zstream->istream.istream), error, + i_stream_get_absolute_offset(&zstream->istream.istream)); +} + +static ssize_t i_stream_bzlib_read(struct istream_private *stream) +{ + struct bzlib_istream *zstream = (struct bzlib_istream *)stream; + const unsigned char *data; + uoff_t high_offset; + size_t size, out_size; + int ret; + + high_offset = stream->istream.v_offset + (stream->pos - stream->skip); + if (zstream->eof_offset == high_offset) { + stream->istream.eof = TRUE; + return -1; + } + + if (!zstream->marked) { + if (!i_stream_try_alloc(stream, CHUNK_SIZE, &out_size)) + return -2; /* buffer full */ + } else { + /* try to avoid compressing, so we can quickly seek backwards */ + if (!i_stream_try_alloc_avoid_compress(stream, CHUNK_SIZE, &out_size)) + return -2; /* buffer full */ + } + + if (i_stream_read_more(stream->parent, &data, &size) < 0) { + if (stream->parent->stream_errno != 0) { + stream->istream.stream_errno = + stream->parent->stream_errno; + } else { + i_assert(stream->parent->eof); + bzlib_read_error(zstream, "unexpected EOF"); + if (!zstream->hdr_read) + stream->istream.stream_errno = EINVAL; + else + stream->istream.stream_errno = EPIPE; + } + return -1; + } + if (size == 0) { + /* no more input */ + i_assert(!stream->istream.blocking); + return 0; + } + + zstream->zs.next_in = (char *)data; + zstream->zs.avail_in = size; + + zstream->zs.next_out = (char *)stream->w_buffer + stream->pos; + zstream->zs.avail_out = out_size; + ret = BZ2_bzDecompress(&zstream->zs); + zstream->hdr_read = TRUE; + + out_size -= zstream->zs.avail_out; + stream->pos += out_size; + + i_stream_skip(stream->parent, size - zstream->zs.avail_in); + + switch (ret) { + case BZ_OK: + break; + case BZ_PARAM_ERROR: + i_unreached(); + case BZ_DATA_ERROR: + bzlib_read_error(zstream, "corrupted data"); + stream->istream.stream_errno = EINVAL; + return -1; + case BZ_DATA_ERROR_MAGIC: + bzlib_read_error(zstream, + "wrong magic in header (not bz2 file?)"); + stream->istream.stream_errno = EINVAL; + return -1; + case BZ_MEM_ERROR: + i_fatal_status(FATAL_OUTOFMEM, "bzlib.read(%s): Out of memory", + i_stream_get_name(&stream->istream)); + case BZ_STREAM_END: + zstream->eof_offset = stream->istream.v_offset + + (stream->pos - stream->skip); + stream->cached_stream_size = zstream->eof_offset; + if (out_size == 0) { + stream->istream.eof = TRUE; + return -1; + } + break; + default: + i_fatal("BZ2_bzDecompress() failed with %d", ret); + } + if (out_size == 0) { + /* read more input */ + return i_stream_bzlib_read(stream); + } + return out_size; +} + +static void i_stream_bzlib_init(struct bzlib_istream *zstream) +{ + int ret; + + ret = BZ2_bzDecompressInit(&zstream->zs, 0, 0); + switch (ret) { + case BZ_OK: + break; + case BZ_MEM_ERROR: + i_fatal_status(FATAL_OUTOFMEM, "bzlib: Out of memory"); + case BZ_CONFIG_ERROR: + i_fatal("Wrong bzlib library version (broken compilation)"); + case BZ_PARAM_ERROR: + i_fatal("bzlib: Invalid parameters"); + default: + i_fatal("BZ2_bzDecompressInit() failed with %d", ret); + } +} + +static void i_stream_bzlib_reset(struct bzlib_istream *zstream) +{ + struct istream_private *stream = &zstream->istream; + + i_stream_seek(stream->parent, stream->parent_start_offset); + zstream->eof_offset = UOFF_T_MAX; + zstream->zs.next_in = NULL; + zstream->zs.avail_in = 0; + + stream->parent_expected_offset = stream->parent_start_offset; + stream->skip = stream->pos = 0; + stream->istream.v_offset = 0; + stream->high_pos = 0; + + (void)BZ2_bzDecompressEnd(&zstream->zs); + i_stream_bzlib_init(zstream); +} + +static void +i_stream_bzlib_seek(struct istream_private *stream, uoff_t v_offset, bool mark) +{ + struct bzlib_istream *zstream = (struct bzlib_istream *) stream; + + if (i_stream_nonseekable_try_seek(stream, v_offset)) + return; + + /* have to seek backwards - reset state and retry */ + i_stream_bzlib_reset(zstream); + if (!i_stream_nonseekable_try_seek(stream, v_offset)) + i_unreached(); + + if (mark) + zstream->marked = TRUE; +} + +static void i_stream_bzlib_sync(struct istream_private *stream) +{ + struct bzlib_istream *zstream = (struct bzlib_istream *) stream; + const struct stat *st; + + if (i_stream_stat(stream->parent, FALSE, &st) == 0) { + if (memcmp(&zstream->last_parent_statbuf, + st, sizeof(*st)) == 0) { + /* a compressed file doesn't change unexpectedly, + don't clear our caches unnecessarily */ + return; + } + zstream->last_parent_statbuf = *st; + } + i_stream_bzlib_reset(zstream); +} + +struct istream *i_stream_create_bz2(struct istream *input) +{ + struct bzlib_istream *zstream; + + zstream = i_new(struct bzlib_istream, 1); + zstream->eof_offset = UOFF_T_MAX; + + i_stream_bzlib_init(zstream); + + zstream->istream.iostream.close = i_stream_bzlib_close; + zstream->istream.max_buffer_size = input->real_stream->max_buffer_size; + zstream->istream.read = i_stream_bzlib_read; + zstream->istream.seek = i_stream_bzlib_seek; + zstream->istream.sync = i_stream_bzlib_sync; + + zstream->istream.istream.readable_fd = FALSE; + zstream->istream.istream.blocking = input->blocking; + zstream->istream.istream.seekable = input->seekable; + + return i_stream_create(&zstream->istream, input, + i_stream_get_fd(input), 0); +} +#endif diff --git a/src/lib-compression/istream-decompress.c b/src/lib-compression/istream-decompress.c new file mode 100644 index 0000000..2021a01 --- /dev/null +++ b/src/lib-compression/istream-decompress.c @@ -0,0 +1,258 @@ +/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "istream-private.h" +#include "compression.h" + +struct decompress_istream { + struct istream_private istream; + struct istream *compressed_input; + struct istream *decompressed_input; + enum istream_decompress_flags flags; +}; + +static void copy_compressed_input_error(struct decompress_istream *zstream) +{ + struct istream_private *stream = &zstream->istream; + + stream->istream.stream_errno = zstream->compressed_input->stream_errno; + stream->istream.eof = zstream->compressed_input->eof; + if (zstream->compressed_input->stream_errno != 0) { + io_stream_set_error(&stream->iostream, "%s", + i_stream_get_error(&zstream->compressed_input->real_stream->istream)); + } +} + +static void copy_decompressed_input_error(struct decompress_istream *zstream) +{ + struct istream_private *stream = &zstream->istream; + + stream->istream.stream_errno = zstream->decompressed_input->stream_errno; + stream->istream.eof = zstream->decompressed_input->eof; + if (zstream->decompressed_input->stream_errno != 0) { + io_stream_set_error(&stream->iostream, "%s", + i_stream_get_error(&zstream->decompressed_input->real_stream->istream)); + } +} + +static void +i_stream_decompress_close(struct iostream_private *_stream, bool close_parent) +{ + struct istream_private *stream = + container_of(_stream, struct istream_private, iostream); + struct decompress_istream *zstream = + container_of(stream, struct decompress_istream, istream); + + if (zstream->decompressed_input != NULL) + i_stream_close(zstream->decompressed_input); + if (close_parent) + i_stream_close(zstream->compressed_input); +} + +static void +i_stream_decompress_destroy(struct iostream_private *_stream) +{ + struct istream_private *stream = + container_of(_stream, struct istream_private, iostream); + struct decompress_istream *zstream = + container_of(stream, struct decompress_istream, istream); + + i_stream_unref(&zstream->decompressed_input); + i_stream_unref(&zstream->compressed_input); +} + +static int +i_stream_decompress_not_compressed(struct decompress_istream *zstream) +{ + if ((zstream->flags & ISTREAM_DECOMPRESS_FLAG_TRY) == 0) { + zstream->istream.istream.stream_errno = EINVAL; + io_stream_set_error(&zstream->istream.iostream, + "Stream isn't compressed"); + return -1; + } else { + zstream->decompressed_input = zstream->compressed_input; + i_stream_ref(zstream->decompressed_input); + return 1; + } +} + +static int i_stream_decompress_detect(struct decompress_istream *zstream) +{ + const struct compression_handler *handler; + ssize_t ret; + + ret = i_stream_read(zstream->compressed_input); + handler = compression_detect_handler(zstream->compressed_input); + if (handler == NULL) { + switch (ret) { + case -1: + if (zstream->compressed_input->stream_errno != 0) { + copy_compressed_input_error(zstream); + return -1; + } + /* fall through */ + case -2: + /* we've read a full buffer or we reached EOF - + the stream isn't compressed */ + return i_stream_decompress_not_compressed(zstream); + case 0: + return 0; + default: + if (!zstream->istream.istream.blocking) + return 0; + return i_stream_decompress_detect(zstream); + } + } + if (handler->create_istream == NULL) { + zstream->istream.istream.stream_errno = EINVAL; + io_stream_set_error(&zstream->istream.iostream, + "Compression handler %s not supported", handler->name); + return -1; + } + + zstream->decompressed_input = + handler->create_istream(zstream->compressed_input); + return 1; +} + +static ssize_t i_stream_decompress_read(struct istream_private *stream) +{ + struct decompress_istream *zstream = + container_of(stream, struct decompress_istream, istream); + ssize_t ret; + size_t pos; + + if (zstream->decompressed_input == NULL) { + if ((ret = i_stream_decompress_detect(zstream)) <= 0) + return ret; + } + + i_stream_seek(zstream->decompressed_input, stream->istream.v_offset); + stream->pos -= stream->skip; + stream->skip = 0; + + stream->buffer = i_stream_get_data(zstream->decompressed_input, &pos); + if (pos > stream->pos) + ret = 0; + else do { + ret = i_stream_read_memarea(zstream->decompressed_input); + copy_decompressed_input_error(zstream); + stream->buffer = i_stream_get_data(zstream->decompressed_input, + &pos); + } while (pos <= stream->pos && ret > 0); + if (ret == -2) + return -2; + + if (pos <= stream->pos) + ret = ret == 0 ? 0 : -1; + else + ret = (ssize_t)(pos - stream->pos); + stream->pos = pos; + i_assert(ret != -1 || stream->istream.eof || + stream->istream.stream_errno != 0); + return ret; +} + +static void i_stream_decompress_reset(struct istream_private *stream) +{ + stream->skip = stream->pos = 0; + stream->istream.v_offset = 0; + stream->istream.eof = FALSE; +} + +static void +i_stream_decompress_seek(struct istream_private *stream, + uoff_t v_offset, bool mark) +{ + struct decompress_istream *zstream = + container_of(stream, struct decompress_istream, istream); + + if (zstream->decompressed_input == NULL) { + if (!i_stream_nonseekable_try_seek(stream, v_offset)) + i_panic("seeking backwards before detecting compression format"); + } else { + i_stream_decompress_reset(stream); + stream->istream.v_offset = v_offset; + if (mark) + i_stream_seek_mark(zstream->decompressed_input, v_offset); + else + i_stream_seek(zstream->decompressed_input, v_offset); + copy_decompressed_input_error(zstream); + } +} + +static void i_stream_decompress_sync(struct istream_private *stream) +{ + struct decompress_istream *zstream = + container_of(stream, struct decompress_istream, istream); + + i_stream_decompress_reset(stream); + if (zstream->decompressed_input != NULL) + i_stream_sync(zstream->decompressed_input); +} + +static int i_stream_decompress_stat(struct istream_private *stream, bool exact) +{ + struct decompress_istream *zstream = + container_of(stream, struct decompress_istream, istream); + const struct stat *st; + + if (!exact) { + if (i_stream_stat(zstream->compressed_input, exact, &st) < 0) { + copy_compressed_input_error(zstream); + return -1; + } + stream->statbuf = *st; + return 0; + } + if (zstream->decompressed_input == NULL) { + (void)i_stream_read(&stream->istream); + if (zstream->decompressed_input == NULL) { + if (stream->istream.stream_errno == 0) { + zstream->istream.istream.stream_errno = EINVAL; + io_stream_set_error(&zstream->istream.iostream, + "Stream compression couldn't be detected during stat"); + } + return -1; + } + } + + if (i_stream_stat(zstream->decompressed_input, exact, &st) < 0) { + copy_decompressed_input_error(zstream); + return -1; + } + i_stream_decompress_reset(stream); + stream->statbuf = *st; + return 0; +} + +struct istream * +i_stream_create_decompress(struct istream *input, + enum istream_decompress_flags flags) +{ + struct decompress_istream *zstream; + + zstream = i_new(struct decompress_istream, 1); + zstream->compressed_input = input; + zstream->flags = flags; + i_stream_ref(input); + + zstream->istream.iostream.close = i_stream_decompress_close; + zstream->istream.iostream.destroy = i_stream_decompress_destroy; + zstream->istream.max_buffer_size = input->real_stream->max_buffer_size; + zstream->istream.read = i_stream_decompress_read; + zstream->istream.seek = i_stream_decompress_seek; + zstream->istream.sync = i_stream_decompress_sync; + zstream->istream.stat = i_stream_decompress_stat; + + zstream->istream.istream.readable_fd = FALSE; + zstream->istream.istream.blocking = input->blocking; + zstream->istream.istream.seekable = input->seekable; + + struct istream *ret = i_stream_create(&zstream->istream, NULL, + i_stream_get_fd(input), 0); + /* input isn't used as our parent istream, so need to copy the stream + name to preserve it. */ + i_stream_set_name(ret, i_stream_get_name(input)); + return ret; +} diff --git a/src/lib-compression/istream-lz4.c b/src/lib-compression/istream-lz4.c new file mode 100644 index 0000000..24d539a --- /dev/null +++ b/src/lib-compression/istream-lz4.c @@ -0,0 +1,281 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" + +#ifdef HAVE_LZ4 + +#include "buffer.h" +#include "istream-private.h" +#include "istream-zlib.h" +#include "iostream-lz4.h" +#include <lz4.h> + +struct lz4_istream { + struct istream_private istream; + + struct stat last_parent_statbuf; + + buffer_t *chunk_buf; + uint32_t chunk_size, chunk_left, max_uncompressed_chunk_size; + + bool marked:1; + bool header_read:1; +}; + +static void i_stream_lz4_close(struct iostream_private *stream, + bool close_parent) +{ + struct lz4_istream *zstream = (struct lz4_istream *)stream; + + buffer_free(&zstream->chunk_buf); + if (close_parent) + i_stream_close(zstream->istream.parent); +} + +static void lz4_read_error(struct lz4_istream *zstream, const char *error) +{ + io_stream_set_error(&zstream->istream.iostream, + "lz4.read(%s): %s at %"PRIuUOFF_T, + i_stream_get_name(&zstream->istream.istream), error, + i_stream_get_absolute_offset(&zstream->istream.istream)); +} + +static int i_stream_lz4_read_header(struct lz4_istream *zstream) +{ + const struct iostream_lz4_header *hdr; + const unsigned char *data; + size_t size; + int ret; + + ret = i_stream_read_bytes(zstream->istream.parent, &data, + &size, sizeof(*hdr)); + size = I_MIN(size, sizeof(*hdr)); + buffer_append(zstream->chunk_buf, data, size); + i_stream_skip(zstream->istream.parent, size); + if (ret < 0 || (ret == 0 && zstream->istream.istream.eof)) { + i_assert(ret != -2); + if (zstream->istream.istream.stream_errno == 0) { + lz4_read_error(zstream, "missing header (not lz4 file?)"); + zstream->istream.istream.stream_errno = EINVAL; + } else + zstream->istream.istream.stream_errno = + zstream->istream.parent->stream_errno; + return ret; + } + if (zstream->chunk_buf->used < sizeof(*hdr)) { + i_assert(!zstream->istream.istream.blocking); + return 0; + } + + hdr = zstream->chunk_buf->data; + if (ret == 0 || memcmp(hdr->magic, IOSTREAM_LZ4_MAGIC, + IOSTREAM_LZ4_MAGIC_LEN) != 0) { + lz4_read_error(zstream, "wrong magic in header (not lz4 file?)"); + zstream->istream.istream.stream_errno = EINVAL; + return -1; + } + zstream->max_uncompressed_chunk_size = + be32_to_cpu_unaligned(hdr->max_uncompressed_chunk_size); + buffer_set_used_size(zstream->chunk_buf, 0); + if (zstream->max_uncompressed_chunk_size > ISTREAM_LZ4_CHUNK_SIZE) { + lz4_read_error(zstream, t_strdup_printf( + "lz4 max chunk size too large (%u > %u)", + zstream->max_uncompressed_chunk_size, + ISTREAM_LZ4_CHUNK_SIZE)); + zstream->istream.istream.stream_errno = EINVAL; + return -1; + } + return 1; +} + +static int i_stream_lz4_read_chunk_header(struct lz4_istream *zstream) +{ + int ret; + size_t size; + const unsigned char *data; + struct istream_private *stream = &zstream->istream; + + i_assert(zstream->chunk_buf->used <= IOSTREAM_LZ4_CHUNK_PREFIX_LEN); + ret = i_stream_read_more(stream->parent, &data, &size); + size = I_MIN(size, IOSTREAM_LZ4_CHUNK_PREFIX_LEN - zstream->chunk_buf->used); + buffer_append(zstream->chunk_buf, data, size); + i_stream_skip(stream->parent, size); + if (ret < 0) { + i_assert(ret != -2); + stream->istream.stream_errno = stream->parent->stream_errno; + if (stream->istream.stream_errno == 0) { + stream->istream.eof = TRUE; + stream->cached_stream_size = + stream->istream.v_offset + + stream->pos - stream->skip; + } + return ret; + } + i_assert(ret != 0 || !stream->istream.blocking); + if (ret == 0) + return ret; + if (zstream->chunk_buf->used < IOSTREAM_LZ4_CHUNK_PREFIX_LEN) + return 0; + zstream->chunk_size = zstream->chunk_left = + be32_to_cpu_unaligned(zstream->chunk_buf->data); + if (zstream->chunk_size == 0 || + zstream->chunk_size > ISTREAM_LZ4_CHUNK_SIZE) { + lz4_read_error(zstream, t_strdup_printf( + "invalid lz4 chunk size: %u", zstream->chunk_size)); + stream->istream.stream_errno = EINVAL; + return -1; + } + buffer_set_used_size(zstream->chunk_buf, 0); + return 1; +} + +static ssize_t i_stream_lz4_read(struct istream_private *stream) +{ + struct lz4_istream *zstream = (struct lz4_istream *)stream; + const unsigned char *data; + size_t size; + int ret; + + /* if we already have max_buffer_size amount of data, fail here */ + if (stream->pos - stream->skip >= i_stream_get_max_buffer_size(&stream->istream)) + return -2; + + if (!zstream->header_read) { + if ((ret = i_stream_lz4_read_header(zstream)) <= 0) { + stream->istream.eof = TRUE; + return ret; + } + zstream->header_read = TRUE; + } + + if (zstream->chunk_left == 0) { + while ((ret = i_stream_lz4_read_chunk_header(zstream)) == 0) { + if (!stream->istream.blocking) + return 0; + } + if (ret < 0) + return ret; + } + + /* read the whole compressed chunk into memory */ + while (zstream->chunk_left > 0 && + (ret = i_stream_read_more(zstream->istream.parent, &data, &size)) > 0) { + if (size > zstream->chunk_left) + size = zstream->chunk_left; + buffer_append(zstream->chunk_buf, data, size); + i_stream_skip(zstream->istream.parent, size); + zstream->chunk_left -= size; + } + if (zstream->chunk_left > 0) { + if (ret == -1 && zstream->istream.parent->stream_errno == 0) { + lz4_read_error(zstream, "truncated lz4 chunk"); + stream->istream.stream_errno = EPIPE; + return -1; + } + zstream->istream.istream.stream_errno = + zstream->istream.parent->stream_errno; + i_assert(ret != 0 || !stream->istream.blocking); + return ret; + } + /* if we already have max_buffer_size amount of data, fail here */ + if (stream->pos - stream->skip >= i_stream_get_max_buffer_size(&stream->istream)) + return -2; + if (i_stream_get_data_size(zstream->istream.parent) > 0) { + /* Parent stream was only partially consumed. Set the stream's + IO as pending to avoid hangs. */ + i_stream_set_input_pending(&zstream->istream.istream, TRUE); + } + /* allocate enough space for the old data and the new + decompressed chunk. we don't know the original compressed size, + so just allocate the max amount of memory. */ + void *dest = i_stream_alloc(stream, zstream->max_uncompressed_chunk_size); + ret = LZ4_decompress_safe(zstream->chunk_buf->data, dest, + zstream->chunk_buf->used, + zstream->max_uncompressed_chunk_size); + i_assert(ret <= (int)zstream->max_uncompressed_chunk_size); + if (ret < 0) { + lz4_read_error(zstream, "corrupted lz4 chunk"); + stream->istream.stream_errno = EINVAL; + return -1; + } + i_assert(ret > 0); + stream->pos += ret; + i_assert(stream->pos <= stream->buffer_size); + + /* we are going to get next chunk after this, so reset here + so we can reuse the chunk buf for reading next buffer prefix */ + if (zstream->chunk_left == 0) + buffer_set_used_size(zstream->chunk_buf, 0); + + return ret; +} + +static void i_stream_lz4_reset(struct lz4_istream *zstream) +{ + struct istream_private *stream = &zstream->istream; + + i_stream_seek(stream->parent, stream->parent_start_offset); + zstream->header_read = FALSE; + zstream->chunk_size = zstream->chunk_left = 0; + + stream->parent_expected_offset = stream->parent_start_offset; + stream->skip = stream->pos = 0; + stream->istream.v_offset = 0; + buffer_set_used_size(zstream->chunk_buf, 0); +} + +static void +i_stream_lz4_seek(struct istream_private *stream, uoff_t v_offset, bool mark) +{ + struct lz4_istream *zstream = (struct lz4_istream *) stream; + + if (i_stream_nonseekable_try_seek(stream, v_offset)) + return; + + /* have to seek backwards - reset state and retry */ + i_stream_lz4_reset(zstream); + if (!i_stream_nonseekable_try_seek(stream, v_offset)) + i_unreached(); + + if (mark) + zstream->marked = TRUE; +} + +static void i_stream_lz4_sync(struct istream_private *stream) +{ + struct lz4_istream *zstream = (struct lz4_istream *) stream; + const struct stat *st; + + if (i_stream_stat(stream->parent, FALSE, &st) == 0) { + if (memcmp(&zstream->last_parent_statbuf, + st, sizeof(*st)) == 0) { + /* a compressed file doesn't change unexpectedly, + don't clear our caches unnecessarily */ + return; + } + zstream->last_parent_statbuf = *st; + } + i_stream_lz4_reset(zstream); +} + +struct istream *i_stream_create_lz4(struct istream *input) +{ + struct lz4_istream *zstream; + + zstream = i_new(struct lz4_istream, 1); + + zstream->istream.iostream.close = i_stream_lz4_close; + zstream->istream.max_buffer_size = input->real_stream->max_buffer_size; + zstream->istream.read = i_stream_lz4_read; + zstream->istream.seek = i_stream_lz4_seek; + zstream->istream.sync = i_stream_lz4_sync; + + zstream->istream.istream.readable_fd = FALSE; + zstream->istream.istream.blocking = input->blocking; + zstream->istream.istream.seekable = input->seekable; + zstream->chunk_buf = buffer_create_dynamic(default_pool, 1024); + + return i_stream_create(&zstream->istream, input, + i_stream_get_fd(input), 0); +} +#endif diff --git a/src/lib-compression/istream-lzma.c b/src/lib-compression/istream-lzma.c new file mode 100644 index 0000000..7b0c2a6 --- /dev/null +++ b/src/lib-compression/istream-lzma.c @@ -0,0 +1,264 @@ +/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" + +#ifdef HAVE_LZMA + +#include "istream-private.h" +#include "istream-zlib.h" +#include <lzma.h> + +#define CHUNK_SIZE (1024*64) + +#define LZMA_MEMORY_LIMIT (1024*1024*80) + +struct lzma_istream { + struct istream_private istream; + + lzma_stream strm; + uoff_t eof_offset; + struct stat last_parent_statbuf; + + bool hdr_read:1; + bool marked:1; + bool strm_closed:1; +}; + +static void i_stream_lzma_close(struct iostream_private *stream, + bool close_parent) +{ + struct lzma_istream *zstream = (struct lzma_istream *)stream; + + if (!zstream->strm_closed) { + lzma_end(&zstream->strm); + zstream->strm_closed = TRUE; + } + if (close_parent) + i_stream_close(zstream->istream.parent); +} + +static void lzma_read_error(struct lzma_istream *zstream, const char *error) +{ + io_stream_set_error(&zstream->istream.iostream, + "lzma.read(%s): %s at %"PRIuUOFF_T, + i_stream_get_name(&zstream->istream.istream), error, + i_stream_get_absolute_offset(&zstream->istream.istream)); +} + +static int lzma_handle_error(struct lzma_istream *zstream, lzma_ret lzma_err) +{ + struct istream_private *stream = &zstream->istream; + switch (lzma_err) { + case LZMA_OK: + break; + case LZMA_DATA_ERROR: + case LZMA_BUF_ERROR: + lzma_read_error(zstream, "corrupted data"); + stream->istream.stream_errno = EINVAL; + return -1; + case LZMA_FORMAT_ERROR: + lzma_read_error(zstream, "wrong magic in header (not xz file?)"); + stream->istream.stream_errno = EINVAL; + return -1; + case LZMA_OPTIONS_ERROR: + lzma_read_error(zstream, "Unsupported xz options"); + stream->istream.stream_errno = EIO; + return -1; + case LZMA_MEM_ERROR: + i_fatal_status(FATAL_OUTOFMEM, "lzma.read(%s): Out of memory", + i_stream_get_name(&stream->istream)); + case LZMA_STREAM_END: + break; + default: + lzma_read_error(zstream, t_strdup_printf( + "lzma_code() failed with %d", lzma_err)); + stream->istream.stream_errno = EIO; + return -1; + } + return 0; +} + +static void lzma_stream_end(struct lzma_istream *zstream) +{ + zstream->eof_offset = zstream->istream.istream.v_offset + + (zstream->istream.pos - zstream->istream.skip); + zstream->istream.cached_stream_size = zstream->eof_offset; +} + +static ssize_t i_stream_lzma_read(struct istream_private *stream) +{ + struct lzma_istream *zstream = (struct lzma_istream *)stream; + const unsigned char *data; + uoff_t high_offset; + size_t size, out_size; + lzma_ret ret; + + high_offset = stream->istream.v_offset + (stream->pos - stream->skip); + if (zstream->eof_offset == high_offset) { + stream->istream.eof = TRUE; + return -1; + } + + if (!zstream->marked) { + if (!i_stream_try_alloc(stream, CHUNK_SIZE, &out_size)) + return -2; /* buffer full */ + } else { + /* try to avoid compressing, so we can quickly seek backwards */ + if (!i_stream_try_alloc_avoid_compress(stream, CHUNK_SIZE, &out_size)) + return -2; /* buffer full */ + } + + if (i_stream_read_more(stream->parent, &data, &size) < 0) { + if (stream->parent->stream_errno != 0) { + stream->istream.stream_errno = + stream->parent->stream_errno; + } else { + i_assert(stream->parent->eof); + lzma_stream_end(zstream); + ret = lzma_code(&zstream->strm, LZMA_FINISH); + if (lzma_handle_error(zstream, ret) < 0) + ; + else if (!zstream->hdr_read) { + lzma_read_error(zstream, "file too small (not xz file?)"); + stream->istream.stream_errno = EINVAL; + } else if (ret != LZMA_STREAM_END) { + lzma_read_error(zstream, "unexpected EOF"); + stream->istream.stream_errno = EPIPE; + } + stream->istream.eof = TRUE; + } + return -1; + } + if (size == 0) { + /* no more input */ + i_assert(!stream->istream.blocking); + return 0; + } + + zstream->strm.next_in = data; + zstream->strm.avail_in = size; + + zstream->strm.next_out = stream->w_buffer + stream->pos; + zstream->strm.avail_out = out_size; + if (!zstream->hdr_read && size > LZMA_STREAM_HEADER_SIZE) + zstream->hdr_read = TRUE; + ret = lzma_code(&zstream->strm, LZMA_RUN); + + out_size -= zstream->strm.avail_out; + stream->pos += out_size; + + size_t bytes_consumed = size - zstream->strm.avail_in; + i_stream_skip(stream->parent, bytes_consumed); + if (i_stream_get_data_size(stream->parent) > 0 && + (bytes_consumed > 0 || out_size > 0)) { + /* Parent stream was only partially consumed. Set the stream's + IO as pending to avoid hangs. */ + i_stream_set_input_pending(&stream->istream, TRUE); + } + + if (lzma_handle_error(zstream, ret) < 0) { + return -1; + } else if (ret == LZMA_STREAM_END) { + lzma_stream_end(zstream); + if (out_size == 0) { + stream->istream.eof = TRUE; + return -1; + } + } + if (out_size == 0) { + /* read more input */ + return i_stream_lzma_read(stream); + } + return out_size; +} + +static void i_stream_lzma_init(struct lzma_istream *zstream) +{ + lzma_ret ret; + + ret = lzma_stream_decoder(&zstream->strm, LZMA_MEMORY_LIMIT, + LZMA_CONCATENATED); + switch (ret) { + case LZMA_OK: + break; + case LZMA_MEM_ERROR: + i_fatal_status(FATAL_OUTOFMEM, "lzma: Out of memory"); + default: + i_fatal("lzma_stream_decoder() failed with ret=%d", ret); + } +} + +static void i_stream_lzma_reset(struct lzma_istream *zstream) +{ + struct istream_private *stream = &zstream->istream; + + i_stream_seek(stream->parent, stream->parent_start_offset); + zstream->eof_offset = UOFF_T_MAX; + zstream->strm.next_in = NULL; + zstream->strm.avail_in = 0; + + stream->parent_expected_offset = stream->parent_start_offset; + stream->skip = stream->pos = 0; + stream->istream.v_offset = 0; + + lzma_end(&zstream->strm); + i_stream_lzma_init(zstream); +} + +static void +i_stream_lzma_seek(struct istream_private *stream, uoff_t v_offset, bool mark) +{ + struct lzma_istream *zstream = (struct lzma_istream *) stream; + + if (i_stream_nonseekable_try_seek(stream, v_offset)) + return; + + /* have to seek backwards - reset state and retry */ + i_stream_lzma_reset(zstream); + if (!i_stream_nonseekable_try_seek(stream, v_offset)) + i_unreached(); + + if (mark) + zstream->marked = TRUE; +} + +static void i_stream_lzma_sync(struct istream_private *stream) +{ + struct lzma_istream *zstream = (struct lzma_istream *) stream; + const struct stat *st; + + if (i_stream_stat(stream->parent, FALSE, &st) == 0) { + if (memcmp(&zstream->last_parent_statbuf, + st, sizeof(*st)) == 0) { + /* a compressed file doesn't change unexpectedly, + don't clear our caches unnecessarily */ + return; + } + zstream->last_parent_statbuf = *st; + } + i_stream_lzma_reset(zstream); +} + +struct istream *i_stream_create_lzma(struct istream *input) +{ + struct lzma_istream *zstream; + + zstream = i_new(struct lzma_istream, 1); + zstream->eof_offset = UOFF_T_MAX; + + i_stream_lzma_init(zstream); + + zstream->istream.iostream.close = i_stream_lzma_close; + zstream->istream.max_buffer_size = input->real_stream->max_buffer_size; + zstream->istream.read = i_stream_lzma_read; + zstream->istream.seek = i_stream_lzma_seek; + zstream->istream.sync = i_stream_lzma_sync; + + zstream->istream.istream.readable_fd = FALSE; + zstream->istream.istream.blocking = input->blocking; + zstream->istream.istream.seekable = input->seekable; + + return i_stream_create(&zstream->istream, input, + i_stream_get_fd(input), 0); +} +#endif diff --git a/src/lib-compression/istream-zlib.c b/src/lib-compression/istream-zlib.c new file mode 100644 index 0000000..3a975c3 --- /dev/null +++ b/src/lib-compression/istream-zlib.c @@ -0,0 +1,431 @@ +/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" + +#ifdef HAVE_ZLIB + +#include "crc32.h" +#include "istream-private.h" +#include "istream-zlib.h" +#include <zlib.h> + +#define CHUNK_SIZE (1024*64) + +#define GZ_HEADER_MIN_SIZE 10 +#define GZ_TRAILER_SIZE 8 + +#define GZ_MAGIC1 0x1f +#define GZ_MAGIC2 0x8b +#define GZ_FLAG_FHCRC 0x02 +#define GZ_FLAG_FEXTRA 0x04 +#define GZ_FLAG_FNAME 0x08 +#define GZ_FLAG_FCOMMENT 0x10 + +struct zlib_istream { + struct istream_private istream; + + z_stream zs; + uoff_t eof_offset; + size_t prev_size; + uint32_t crc32; + struct stat last_parent_statbuf; + + bool gz:1; + bool marked:1; + bool header_read:1; + bool trailer_read:1; + bool zs_closed:1; + bool starting_concated_output:1; +}; + +static void i_stream_zlib_init(struct zlib_istream *zstream); + +static void i_stream_zlib_close(struct iostream_private *stream, + bool close_parent) +{ + struct zlib_istream *zstream = (struct zlib_istream *)stream; + + if (!zstream->zs_closed) { + (void)inflateEnd(&zstream->zs); + zstream->zs_closed = TRUE; + } + if (close_parent) + i_stream_close(zstream->istream.parent); +} + +static void zlib_read_error(struct zlib_istream *zstream, const char *error) +{ + io_stream_set_error(&zstream->istream.iostream, + "zlib.read(%s): %s at %"PRIuUOFF_T, + i_stream_get_name(&zstream->istream.istream), error, + i_stream_get_absolute_offset(&zstream->istream.istream)); +} + +static int i_stream_zlib_read_header(struct istream_private *stream) +{ + struct zlib_istream *zstream = (struct zlib_istream *)stream; + const unsigned char *data; + size_t size; + unsigned int pos, fextra_size; + int ret; + + ret = i_stream_read_bytes(stream->parent, &data, &size, + zstream->prev_size + 1); + if (size == zstream->prev_size) { + stream->istream.stream_errno = stream->parent->stream_errno; + if (ret == -1 && stream->istream.stream_errno == 0) { + zlib_read_error(zstream, "missing gz header"); + stream->istream.stream_errno = EINVAL; + } + if (ret == -2) { + zlib_read_error(zstream, "gz header is too large"); + stream->istream.stream_errno = EINVAL; + ret = -1; + } + return ret; + } + zstream->prev_size = size; + + if (size < GZ_HEADER_MIN_SIZE) + return 0; + pos = GZ_HEADER_MIN_SIZE; + + if (data[0] != GZ_MAGIC1 || data[1] != GZ_MAGIC2) { + /* missing gzip magic header */ + zlib_read_error(zstream, "wrong magic in header (not gz file?)"); + stream->istream.stream_errno = EINVAL; + return -1; + } + if ((data[3] & GZ_FLAG_FEXTRA) != 0) { + if (pos + 2 > size) + return 0; + + fextra_size = le16_to_cpu_unaligned(&data[pos]); + pos += 2; + if (pos + fextra_size > size) + return 0; + pos += fextra_size; + } + if ((data[3] & GZ_FLAG_FNAME) != 0) { + do { + if (pos == size) + return 0; + } while (data[pos++] != '\0'); + } + if ((data[3] & GZ_FLAG_FCOMMENT) != 0) { + do { + if (pos == size) + return 0; + } while (data[pos++] != '\0'); + } + if ((data[3] & GZ_FLAG_FHCRC) != 0) { + if (pos + 2 > size) + return 0; + pos += 2; + } + i_stream_skip(stream->parent, pos); + zstream->prev_size = 0; + return 1; +} + +static int i_stream_zlib_read_trailer(struct zlib_istream *zstream) +{ + struct istream_private *stream = &zstream->istream; + const unsigned char *data; + size_t size; + int ret; + + ret = i_stream_read_bytes(stream->parent, &data, &size, + GZ_TRAILER_SIZE); + if (size == zstream->prev_size) { + stream->istream.stream_errno = stream->parent->stream_errno; + if (ret == -1 && stream->istream.stream_errno == 0) { + zlib_read_error(zstream, "missing gz trailer"); + stream->istream.stream_errno = EINVAL; + } + return ret; + } + zstream->prev_size = size; + + if (size < GZ_TRAILER_SIZE) + return 0; + + if (le32_to_cpu_unaligned(data) != zstream->crc32) { + zlib_read_error(zstream, "gz trailer has wrong CRC value"); + stream->istream.stream_errno = EINVAL; + return -1; + } + i_stream_skip(stream->parent, GZ_TRAILER_SIZE); + zstream->prev_size = 0; + zstream->trailer_read = TRUE; + return 1; +} + +static ssize_t i_stream_zlib_read(struct istream_private *stream) +{ + struct zlib_istream *zstream = (struct zlib_istream *)stream; + const unsigned char *data; + uoff_t high_offset; + size_t size, out_size; + int ret; + + high_offset = stream->istream.v_offset + (stream->pos - stream->skip); + if (zstream->eof_offset == high_offset) { + /* zlib library returned EOF. */ + if (!zstream->gz) { + /* deflate - ignore if there's still more data */ + stream->istream.eof = TRUE; + return -1; + } + /* gz format - read the trailer */ + if (!zstream->trailer_read) { + do { + ret = i_stream_zlib_read_trailer(zstream); + } while (ret == 0 && stream->istream.blocking); + if (ret <= 0) + return ret; + } + /* See if there's another concatenated gz stream. */ + if (i_stream_read_eof(stream->parent)) { + /* EOF or error */ + stream->istream.stream_errno = + stream->parent->stream_errno; + stream->istream.eof = TRUE; + return -1; + } + /* Multiple gz streams concatenated together */ + zstream->starting_concated_output = TRUE; + } + if (zstream->starting_concated_output) { + /* make sure there actually is something in parent stream. + we don't want to reset the stream unless we actually see + some concated output. */ + ret = i_stream_read_more(stream->parent, &data, &size); + if (ret <= 0) { + if (ret == 0) + return 0; + if (stream->parent->stream_errno != 0) { + stream->istream.stream_errno = + stream->parent->stream_errno; + } + stream->istream.eof = TRUE; + return -1; + } + + /* gzip file with concatenated content */ + stream->cached_stream_size = UOFF_T_MAX; + zstream->eof_offset = UOFF_T_MAX; + zstream->header_read = FALSE; + zstream->trailer_read = FALSE; + zstream->crc32 = 0; + zstream->starting_concated_output = FALSE; + + (void)inflateEnd(&zstream->zs); + i_stream_zlib_init(zstream); + } + + if (!zstream->header_read) { + do { + ret = i_stream_zlib_read_header(stream); + } while (ret == 0 && stream->istream.blocking); + if (ret <= 0) + return ret; + zstream->header_read = TRUE; + } + + if (!zstream->marked) { + if (!i_stream_try_alloc(stream, CHUNK_SIZE, &out_size)) + return -2; /* buffer full */ + } else { + /* try to avoid compressing, so we can quickly seek backwards */ + if (!i_stream_try_alloc_avoid_compress(stream, CHUNK_SIZE, &out_size)) + return -2; /* buffer full */ + } + + if (i_stream_read_more(stream->parent, &data, &size) < 0) { + if (stream->parent->stream_errno != 0) { + stream->istream.stream_errno = + stream->parent->stream_errno; + } else { + i_assert(stream->parent->eof); + zlib_read_error(zstream, "unexpected EOF"); + stream->istream.stream_errno = EPIPE; + } + return -1; + } + if (size == 0) { + /* no more input */ + i_assert(!stream->istream.blocking); + return 0; + } + + zstream->zs.next_in = (void *)data; + zstream->zs.avail_in = size; + + zstream->zs.next_out = stream->w_buffer + stream->pos; + zstream->zs.avail_out = out_size; + ret = inflate(&zstream->zs, Z_SYNC_FLUSH); + + out_size -= zstream->zs.avail_out; + zstream->crc32 = crc32_data_more(zstream->crc32, + stream->w_buffer + stream->pos, + out_size); + stream->pos += out_size; + + size_t bytes_consumed = size - zstream->zs.avail_in; + i_stream_skip(stream->parent, bytes_consumed); + if (i_stream_get_data_size(stream->parent) > 0 && + (bytes_consumed > 0 || out_size > 0)) { + /* Parent stream was only partially consumed. Set the stream's + IO as pending to avoid hangs. */ + i_stream_set_input_pending(&stream->istream, TRUE); + } + + switch (ret) { + case Z_OK: + break; + case Z_NEED_DICT: + zlib_read_error(zstream, "can't read file without dict"); + stream->istream.stream_errno = EIO; + return -1; + case Z_DATA_ERROR: + zlib_read_error(zstream, "corrupted data"); + stream->istream.stream_errno = EINVAL; + return -1; + case Z_MEM_ERROR: + i_fatal_status(FATAL_OUTOFMEM, "zlib.read(%s): Out of memory", + i_stream_get_name(&stream->istream)); + case Z_STREAM_END: + zstream->eof_offset = stream->istream.v_offset + + (stream->pos - stream->skip); + stream->cached_stream_size = zstream->eof_offset; + zstream->zs.avail_in = 0; + + if (!zstream->trailer_read) { + /* try to read and verify the trailer, we might not + be called again. */ + if (i_stream_zlib_read_trailer(zstream) < 0) + return -1; + } + break; + default: + i_fatal("inflate() failed with %d", ret); + } + if (out_size == 0) { + /* read more input */ + return i_stream_zlib_read(stream); + } + return out_size; +} + +static void i_stream_zlib_init(struct zlib_istream *zstream) +{ + int ret; + + ret = inflateInit2(&zstream->zs, -15); + switch (ret) { + case Z_OK: + break; + case Z_MEM_ERROR: + i_fatal_status(FATAL_OUTOFMEM, "zlib: Out of memory"); + case Z_VERSION_ERROR: + i_fatal("Wrong zlib library version (broken compilation)"); + case Z_STREAM_ERROR: + i_fatal("zlib: Invalid parameters"); + default: + i_fatal("inflateInit() failed with %d", ret); + } + zstream->header_read = !zstream->gz; + zstream->trailer_read = !zstream->gz; +} + +static void i_stream_zlib_reset(struct zlib_istream *zstream) +{ + struct istream_private *stream = &zstream->istream; + + i_stream_seek(stream->parent, stream->parent_start_offset); + zstream->eof_offset = UOFF_T_MAX; + zstream->crc32 = 0; + + zstream->zs.next_in = NULL; + zstream->zs.avail_in = 0; + + stream->parent_expected_offset = stream->parent_start_offset; + stream->skip = stream->pos = 0; + stream->istream.v_offset = 0; + stream->high_pos = 0; + zstream->prev_size = 0; + + (void)inflateEnd(&zstream->zs); + i_stream_zlib_init(zstream); +} + +static void +i_stream_zlib_seek(struct istream_private *stream, uoff_t v_offset, bool mark) +{ + struct zlib_istream *zstream = (struct zlib_istream *) stream; + + if (i_stream_nonseekable_try_seek(stream, v_offset)) + return; + + /* have to seek backwards - reset state and retry */ + i_stream_zlib_reset(zstream); + if (!i_stream_nonseekable_try_seek(stream, v_offset)) + i_unreached(); + + if (mark) + zstream->marked = TRUE; +} + +static void i_stream_zlib_sync(struct istream_private *stream) +{ + struct zlib_istream *zstream = (struct zlib_istream *) stream; + const struct stat *st; + + if (i_stream_stat(stream->parent, FALSE, &st) == 0) { + if (memcmp(&zstream->last_parent_statbuf, + st, sizeof(*st)) == 0) { + /* a compressed file doesn't change unexpectedly, + don't clear our caches unnecessarily */ + return; + } + zstream->last_parent_statbuf = *st; + } + i_stream_zlib_reset(zstream); +} + +static struct istream * +i_stream_create_zlib(struct istream *input, bool gz) +{ + struct zlib_istream *zstream; + + zstream = i_new(struct zlib_istream, 1); + zstream->eof_offset = UOFF_T_MAX; + zstream->gz = gz; + + i_stream_zlib_init(zstream); + + zstream->istream.iostream.close = i_stream_zlib_close; + zstream->istream.max_buffer_size = input->real_stream->max_buffer_size; + zstream->istream.read = i_stream_zlib_read; + zstream->istream.seek = i_stream_zlib_seek; + zstream->istream.sync = i_stream_zlib_sync; + + zstream->istream.istream.readable_fd = FALSE; + zstream->istream.istream.blocking = input->blocking; + zstream->istream.istream.seekable = input->seekable; + + return i_stream_create(&zstream->istream, input, + i_stream_get_fd(input), 0); +} + +struct istream *i_stream_create_gz(struct istream *input) +{ + return i_stream_create_zlib(input, TRUE); +} + +struct istream *i_stream_create_deflate(struct istream *input) +{ + return i_stream_create_zlib(input, FALSE); +} +#endif diff --git a/src/lib-compression/istream-zlib.h b/src/lib-compression/istream-zlib.h new file mode 100644 index 0000000..041fbf3 --- /dev/null +++ b/src/lib-compression/istream-zlib.h @@ -0,0 +1,11 @@ +#ifndef ISTREAM_ZLIB_H +#define ISTREAM_ZLIB_H + +struct istream *i_stream_create_gz(struct istream *input); +struct istream *i_stream_create_deflate(struct istream *input); +struct istream *i_stream_create_bz2(struct istream *input); +struct istream *i_stream_create_lzma(struct istream *input); +struct istream *i_stream_create_lz4(struct istream *input); +struct istream *i_stream_create_zstd(struct istream *input); + +#endif diff --git a/src/lib-compression/istream-zstd.c b/src/lib-compression/istream-zstd.c new file mode 100644 index 0000000..9e059bc --- /dev/null +++ b/src/lib-compression/istream-zstd.c @@ -0,0 +1,268 @@ +/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ + +#include "lib.h" + +#ifdef HAVE_ZSTD + +#include "buffer.h" +#include "istream-private.h" +#include "istream-zlib.h" + +#include "zstd.h" +#include "zstd_errors.h" +#include "iostream-zstd-private.h" + +#ifndef HAVE_ZSTD_GETERRORCODE +ZSTD_ErrorCode ZSTD_getErrorCode(size_t functionResult) +{ + ssize_t errcode = (ssize_t)functionResult; + if (errcode < 0) + return -errcode; + return ZSTD_error_no_error; +} +#endif + +struct zstd_istream { + struct istream_private istream; + + ZSTD_DStream *dstream; + ZSTD_inBuffer input; + ZSTD_outBuffer output; + + struct stat last_parent_statbuf; + + /* ZSTD input size */ + size_t input_size; + + /* storage for frames */ + buffer_t *frame_buffer; + + /* storage for data */ + buffer_t *data_buffer; + + bool hdr_read:1; + bool marked:1; + bool zs_closed:1; + /* is there data remaining */ + bool remain:1; +}; + +static void i_stream_zstd_init(struct zstd_istream *zstream) +{ + zstream->dstream = ZSTD_createDStream(); + if (zstream->dstream == NULL) + i_fatal_status(FATAL_OUTOFMEM, "zstd: Out of memory"); + ZSTD_initDStream(zstream->dstream); + zstream->input_size = ZSTD_DStreamInSize(); + if (zstream->frame_buffer == NULL) + zstream->frame_buffer = buffer_create_dynamic(default_pool, ZSTD_DStreamInSize()); + else + buffer_set_used_size(zstream->frame_buffer, 0); + if (zstream->data_buffer == NULL) + zstream->data_buffer = buffer_create_dynamic(default_pool, ZSTD_DStreamOutSize()); + else + buffer_set_used_size(zstream->data_buffer, 0); + zstream->zs_closed = FALSE; +} + +static void i_stream_zstd_deinit(struct zstd_istream *zstream, bool reuse_buffers) +{ + (void)ZSTD_freeDStream(zstream->dstream); + zstream->dstream = NULL; + if (!reuse_buffers) { + buffer_free(&zstream->frame_buffer); + buffer_free(&zstream->data_buffer); + } + zstream->zs_closed = TRUE; + i_zero(&zstream->input); +} + +static void i_stream_zstd_close(struct iostream_private *stream, + bool close_parent) +{ + struct istream_private *_istream = + container_of(stream, struct istream_private, iostream); + struct zstd_istream *zstream = + container_of(_istream, struct zstd_istream, istream); + if (!zstream->zs_closed) + i_stream_zstd_deinit(zstream, FALSE); + buffer_free(&zstream->frame_buffer); + if (close_parent) + i_stream_close(zstream->istream.parent); +} + +static void i_stream_zstd_read_error(struct zstd_istream *zstream, size_t err) +{ + ZSTD_ErrorCode errcode = zstd_version_errcode(ZSTD_getErrorCode(err)); + const char *error = ZSTD_getErrorName(err); + if (errcode == ZSTD_error_memory_allocation) + i_fatal_status(FATAL_OUTOFMEM, "zstd.read(%s): Out of memory", + i_stream_get_name(&zstream->istream.istream)); + else if (errcode == ZSTD_error_prefix_unknown || +#if HAVE_DECL_ZSTD_ERROR_PARAMETER_UNSUPPORTED == 1 + errcode == ZSTD_error_parameter_unsupported || +#endif + + errcode == ZSTD_error_dictionary_wrong || + errcode == ZSTD_error_init_missing) + zstream->istream.istream.stream_errno = EINVAL; + else + zstream->istream.istream.stream_errno = EIO; + + io_stream_set_error(&zstream->istream.iostream, + "zstd.read(%s): %s at %"PRIuUOFF_T, + i_stream_get_name(&zstream->istream.istream), error, + i_stream_get_absolute_offset(&zstream->istream.istream)); +} + +static ssize_t i_stream_zstd_read(struct istream_private *stream) +{ + struct zstd_istream *zstream = + container_of(stream, struct zstd_istream, istream); + const unsigned char *data; + size_t size; + + if (stream->istream.eof) + return -1; + + for (;;) { + if (zstream->data_buffer->used > 0) { + if (!i_stream_try_alloc(stream, stream->max_buffer_size, &size)) + return -2; + size = I_MIN(zstream->data_buffer->used, size); + memcpy(PTR_OFFSET(stream->w_buffer,stream->pos), + zstream->data_buffer->data, size); + stream->pos += size; + buffer_delete(zstream->data_buffer, 0, size); + return size; + } + + /* see if we can get more */ + if (zstream->input.pos == zstream->input.size) { + ssize_t ret; + buffer_set_used_size(zstream->frame_buffer, 0); + /* need to read more */ + if ((ret = i_stream_read_more(stream->parent, &data, &size)) < 0) { + stream->istream.stream_errno = + stream->parent->stream_errno; + stream->istream.eof = stream->parent->eof; + if (stream->istream.stream_errno != 0) + ; + else if (!zstream->hdr_read) + stream->istream.stream_errno = EINVAL; + else if (zstream->remain) + /* truncated data */ + stream->istream.stream_errno = EPIPE; + return ret; + } + if (ret == 0) + return 0; + buffer_append(zstream->frame_buffer, data, size); + /* NOTE: All of the parent stream input is skipped + over here. This is why there's no need to call + i_stream_set_input_pending() here like with other + compression istreams. */ + i_stream_skip(stream->parent, size); + zstream->input.src = zstream->frame_buffer->data; + zstream->input.size = zstream->frame_buffer->used; + zstream->input.pos = 0; + } + + i_assert(zstream->input.size > 0); + i_assert(zstream->data_buffer->used == 0); + zstream->output.dst = buffer_append_space_unsafe(zstream->data_buffer, + ZSTD_DStreamOutSize()); + zstream->output.pos = 0; + zstream->output.size = ZSTD_DStreamOutSize(); + + size_t zret = ZSTD_decompressStream(zstream->dstream, &zstream->output, + &zstream->input); + if (ZSTD_isError(zret) != 0) { + i_stream_zstd_read_error(zstream, zret); + return -1; + } + /* ZSTD magic number is 4 bytes, but it's only defined after v0.8 */ + if (!zstream->hdr_read && zstream->input.size > 4) + zstream->hdr_read = TRUE; + zstream->remain = zret > 0; + buffer_set_used_size(zstream->data_buffer, zstream->output.pos); + } + i_unreached(); +} + +static void i_stream_zstd_reset(struct zstd_istream *zstream) +{ + struct istream_private *stream = &zstream->istream; + + i_stream_seek(stream->parent, stream->parent_start_offset); + stream->parent_expected_offset = stream->parent_start_offset; + stream->skip = stream->pos = 0; + stream->istream.v_offset = 0; + stream->high_pos = 0; + + i_stream_zstd_deinit(zstream, TRUE); + i_stream_zstd_init(zstream); +} + +static void +i_stream_zstd_seek(struct istream_private *stream, uoff_t v_offset, bool mark) +{ + struct zstd_istream *zstream = + container_of(stream, struct zstd_istream, istream); + + if (i_stream_nonseekable_try_seek(stream, v_offset)) + return; + + /* have to seek backwards - reset state and retry */ + i_stream_zstd_reset(zstream); + if (!i_stream_nonseekable_try_seek(stream, v_offset)) + i_unreached(); + + if (mark) + zstream->marked = TRUE; +} + +static void i_stream_zstd_sync(struct istream_private *stream) +{ + struct zstd_istream *zstream = + container_of(stream, struct zstd_istream, istream); + const struct stat *st; + + if (i_stream_stat(stream->parent, FALSE, &st) == 0) { + if (memcmp(&zstream->last_parent_statbuf, + st, sizeof(*st)) == 0) { + /* a compressed file doesn't change unexpectedly, + don't clear our caches unnecessarily */ + return; + } + zstream->last_parent_statbuf = *st; + } + i_stream_zstd_reset(zstream); +} + +struct istream * +i_stream_create_zstd(struct istream *input) +{ + struct zstd_istream *zstream; + + zstd_version_check(); + + zstream = i_new(struct zstd_istream, 1); + + i_stream_zstd_init(zstream); + + zstream->istream.iostream.close = i_stream_zstd_close; + zstream->istream.max_buffer_size = input->real_stream->max_buffer_size; + zstream->istream.read = i_stream_zstd_read; + zstream->istream.seek = i_stream_zstd_seek; + zstream->istream.sync = i_stream_zstd_sync; + + zstream->istream.istream.readable_fd = FALSE; + zstream->istream.istream.blocking = input->blocking; + zstream->istream.istream.seekable = input->seekable; + + return i_stream_create(&zstream->istream, input, + i_stream_get_fd(input), 0); +} + +#endif diff --git a/src/lib-compression/ostream-bzlib.c b/src/lib-compression/ostream-bzlib.c new file mode 100644 index 0000000..4d072b2 --- /dev/null +++ b/src/lib-compression/ostream-bzlib.c @@ -0,0 +1,307 @@ +/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" + +#ifdef HAVE_BZLIB + +#include "ostream-private.h" +#include "ostream-zlib.h" +#include <bzlib.h> + +#define CHUNK_SIZE (1024*64) + +struct bzlib_ostream { + struct ostream_private ostream; + bz_stream zs; + + char outbuf[CHUNK_SIZE]; + unsigned int outbuf_offset, outbuf_used; + + bool flushed:1; +}; + +/* in bzlib, level is actually block size. From bzlib manual: + + The block size affects both the compression ratio achieved, + and the amount of memory needed for compression and decompression. + + BlockSize 1 through BlockSize 9 specify the block size to be 100,000 bytes + through 900,000 bytes respectively. The default is to use the maximum block + size. + + Larger block sizes give rapidly diminishing marginal returns. + Most of the compression comes from the first two or three hundred k of + block size, a fact worth bearing in mind when using bzip2 on small machines. + It is also important to appreciate that the decompression memory + requirement is set at compression time by the choice of block size. + + * In general, try and use the largest block size memory constraints + allow, since that maximises the compression achieved. + * Compression and decompression speed are virtually unaffected by block + size. + + Another significant point applies to files which fit in a single block - + that means most files you'd encounter using a large block size. The + amount of real memory touched is proportional to the size of the file, + since the file is smaller than a block. For example, compressing a file + 20,000 bytes long with the flag BlockSize 9 will cause the compressor to + allocate around 7600k of memory, but only touch 400k + 20000 * 8 = 560 kbytes + of it. Similarly, the decompressor will allocate 3700k but only + touch 100k + 20000 * 4 = 180 kbytes. +*/ + +int compression_get_min_level_bz2(void) +{ + return 1; +} + +int compression_get_default_level_bz2(void) +{ + /* default is maximum level */ + return 9; +} + +int compression_get_max_level_bz2(void) +{ + return 9; +} + +static void o_stream_bzlib_close(struct iostream_private *stream, + bool close_parent) +{ + struct bzlib_ostream *zstream = (struct bzlib_ostream *)stream; + + (void)BZ2_bzCompressEnd(&zstream->zs); + if (close_parent) + o_stream_close(zstream->ostream.parent); +} + +static int o_stream_zlib_send_outbuf(struct bzlib_ostream *zstream) +{ + ssize_t ret; + size_t size; + + if (zstream->outbuf_used == 0) + return 1; + + size = zstream->outbuf_used - zstream->outbuf_offset; + i_assert(size > 0); + ret = o_stream_send(zstream->ostream.parent, + zstream->outbuf + zstream->outbuf_offset, size); + if (ret < 0) { + o_stream_copy_error_from_parent(&zstream->ostream); + return -1; + } + if ((size_t)ret != size) { + zstream->outbuf_offset += ret; + return 0; + } + zstream->outbuf_offset = 0; + zstream->outbuf_used = 0; + return 1; +} + +static ssize_t +o_stream_bzlib_send_chunk(struct bzlib_ostream *zstream, + const void *data, size_t size) +{ + bz_stream *zs = &zstream->zs; + int ret; + + i_assert(zstream->outbuf_used == 0); + + zs->next_in = (void *)data; + zs->avail_in = size; + while (zs->avail_in > 0) { + if (zs->avail_out == 0) { + /* previous block was compressed. send it and start + compression for a new block. */ + zs->next_out = zstream->outbuf; + zs->avail_out = sizeof(zstream->outbuf); + + zstream->outbuf_used = sizeof(zstream->outbuf); + if ((ret = o_stream_zlib_send_outbuf(zstream)) < 0) + return -1; + if (ret == 0) { + /* parent stream's buffer full */ + break; + } + } + + switch ((ret = BZ2_bzCompress(zs, BZ_RUN))) { + case BZ_RUN_OK: + break; + case BZ_MEM_ERROR: + i_fatal_status(FATAL_OUTOFMEM, "bzip2.write(%s): Out of memory", + o_stream_get_name(&zstream->ostream.ostream)); + default: + i_fatal("BZ2_bzCompress() failed with %d", ret); + } + } + size -= zs->avail_in; + + zstream->flushed = FALSE; + return size; +} + +static int o_stream_bzlib_send_flush(struct bzlib_ostream *zstream, bool final) +{ + bz_stream *zs = &zstream->zs; + size_t len; + bool done = FALSE; + int ret; + + i_assert(zs->avail_in == 0); + + if (zstream->flushed) { + i_assert(zstream->outbuf_used == 0); + return 1; + } + + if ((ret = o_stream_flush_parent_if_needed(&zstream->ostream)) <= 0) + return ret; + if ((ret = o_stream_zlib_send_outbuf(zstream)) <= 0) + return ret; + + /* do not attempt to finish the stream early */ + if (!final) + return 1; + + i_assert(zstream->outbuf_used == 0); + do { + len = sizeof(zstream->outbuf) - zs->avail_out; + if (len != 0) { + zs->next_out = zstream->outbuf; + zs->avail_out = sizeof(zstream->outbuf); + + zstream->outbuf_used = len; + if ((ret = o_stream_zlib_send_outbuf(zstream)) <= 0) + return ret; + if (done) + break; + } + + ret = BZ2_bzCompress(zs, BZ_FINISH); + switch (ret) { + case BZ_RUN_OK: + case BZ_FLUSH_OK: + case BZ_STREAM_END: + done = TRUE; + break; + case BZ_FINISH_OK: + break; + case BZ_MEM_ERROR: + i_fatal_status(FATAL_OUTOFMEM, "bzip2.write(%s): Out of memory", + o_stream_get_name(&zstream->ostream.ostream)); + default: + i_fatal("BZ2_bzCompress() failed with %d", ret); + } + } while (zs->avail_out != sizeof(zstream->outbuf)); + + if (final) + zstream->flushed = TRUE; + i_assert(zstream->outbuf_used == 0); + return 1; +} + +static int o_stream_bzlib_flush(struct ostream_private *stream) +{ + struct bzlib_ostream *zstream = (struct bzlib_ostream *)stream; + int ret; + if ((ret = o_stream_bzlib_send_flush(zstream, stream->finished)) < 0) + return -1; + else if (ret > 0) + return o_stream_flush_parent(stream); + return ret; +} + +static size_t +o_stream_bzlib_get_buffer_used_size(const struct ostream_private *stream) +{ + const struct bzlib_ostream *zstream = + (const struct bzlib_ostream *)stream; + + /* outbuf has already compressed data that we're trying to send to the + parent stream. We're not including bzlib's internal compression + buffer size. */ + return (zstream->outbuf_used - zstream->outbuf_offset) + + o_stream_get_buffer_used_size(stream->parent); +} + +static size_t +o_stream_bzlib_get_buffer_avail_size(const struct ostream_private *stream) +{ + /* FIXME: not correct - this is counting compressed size, which may be + too larger than uncompressed size in some situations. Fixing would + require some kind of additional buffering. */ + return o_stream_get_buffer_avail_size(stream->parent); +} + +static ssize_t +o_stream_bzlib_sendv(struct ostream_private *stream, + const struct const_iovec *iov, unsigned int iov_count) +{ + struct bzlib_ostream *zstream = (struct bzlib_ostream *)stream; + ssize_t ret, bytes = 0; + unsigned int i; + + if ((ret = o_stream_zlib_send_outbuf(zstream)) <= 0) { + /* error / we still couldn't flush existing data to + parent stream. */ + return ret; + } + + for (i = 0; i < iov_count; i++) { + ret = o_stream_bzlib_send_chunk(zstream, iov[i].iov_base, + iov[i].iov_len); + if (ret < 0) + return -1; + bytes += ret; + if ((size_t)ret != iov[i].iov_len) + break; + } + stream->ostream.offset += bytes; + + /* avail_in!=0 check is used to detect errors. if it's non-zero here + it simply means we didn't send all the data */ + zstream->zs.avail_in = 0; + return bytes; +} + +struct ostream *o_stream_create_bz2(struct ostream *output, int level) +{ + struct bzlib_ostream *zstream; + int ret; + + i_assert(level >= 1 && level <= 9); + + zstream = i_new(struct bzlib_ostream, 1); + zstream->ostream.sendv = o_stream_bzlib_sendv; + zstream->ostream.flush = o_stream_bzlib_flush; + zstream->ostream.get_buffer_used_size = + o_stream_bzlib_get_buffer_used_size; + zstream->ostream.get_buffer_avail_size = + o_stream_bzlib_get_buffer_avail_size; + zstream->ostream.iostream.close = o_stream_bzlib_close; + + ret = BZ2_bzCompressInit(&zstream->zs, level, 0, 0); + switch (ret) { + case BZ_OK: + break; + case BZ_MEM_ERROR: + i_fatal_status(FATAL_OUTOFMEM, + "bzlib: Out of memory"); + case BZ_CONFIG_ERROR: + i_fatal("Wrong bzlib library version (broken compilation)"); + case BZ_PARAM_ERROR: + i_fatal("bzlib: Invalid parameters"); + default: + i_fatal("BZ2_bzCompressInit() failed with %d", ret); + } + + zstream->zs.next_out = zstream->outbuf; + zstream->zs.avail_out = sizeof(zstream->outbuf); + return o_stream_create(&zstream->ostream, output, + o_stream_get_fd(output)); +} +#endif diff --git a/src/lib-compression/ostream-lz4.c b/src/lib-compression/ostream-lz4.c new file mode 100644 index 0000000..360f475 --- /dev/null +++ b/src/lib-compression/ostream-lz4.c @@ -0,0 +1,249 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" + +#ifdef HAVE_LZ4 + +#include "ostream-private.h" +#include "ostream-zlib.h" +#include "iostream-lz4.h" +#include <lz4.h> + +#define CHUNK_SIZE OSTREAM_LZ4_CHUNK_SIZE + +struct lz4_ostream { + struct ostream_private ostream; + + unsigned char compressbuf[CHUNK_SIZE]; + unsigned int compressbuf_offset; + + /* chunk size, followed by compressed data */ + unsigned char outbuf[IOSTREAM_LZ4_CHUNK_PREFIX_LEN + + LZ4_COMPRESSBOUND(CHUNK_SIZE)]; + unsigned int outbuf_offset, outbuf_used; +}; + +/* There is no actual compression level in LZ4, so for legacy + reasons we allow 1-9 to avoid breaking anyone's config. */ +int compression_get_min_level_lz4(void) +{ + return 1; +} + +int compression_get_default_level_lz4(void) +{ + return 1; +} + +int compression_get_max_level_lz4(void) +{ + return 9; +} + +static void o_stream_lz4_close(struct iostream_private *stream, + bool close_parent) +{ + struct lz4_ostream *zstream = (struct lz4_ostream *)stream; + + if (close_parent) + o_stream_close(zstream->ostream.parent); +} + +static int o_stream_lz4_send_outbuf(struct lz4_ostream *zstream) +{ + ssize_t ret; + size_t size; + + if (zstream->outbuf_used == 0) + return 1; + + size = zstream->outbuf_used - zstream->outbuf_offset; + i_assert(size > 0); + ret = o_stream_send(zstream->ostream.parent, + zstream->outbuf + zstream->outbuf_offset, size); + if (ret < 0) { + o_stream_copy_error_from_parent(&zstream->ostream); + return -1; + } + if ((size_t)ret != size) { + zstream->outbuf_offset += ret; + return 0; + } + zstream->outbuf_offset = 0; + zstream->outbuf_used = 0; + return 1; +} + +static int o_stream_lz4_compress(struct lz4_ostream *zstream) +{ + uint32_t chunk_size; + int ret; + + if (zstream->compressbuf_offset == 0) + return 1; + if ((ret = o_stream_lz4_send_outbuf(zstream)) <= 0) + return ret; + + i_assert(zstream->outbuf_offset == 0); + i_assert(zstream->outbuf_used == 0); + +#if defined(HAVE_LZ4_COMPRESS_DEFAULT) + int max_dest_size = LZ4_compressBound(zstream->compressbuf_offset); + i_assert(max_dest_size >= 0); + if (max_dest_size == 0) { + io_stream_set_error(&zstream->ostream.iostream, + "lz4-compress: input size %u too large (> %u)", + zstream->compressbuf_offset, LZ4_MAX_INPUT_SIZE); + zstream->ostream.ostream.stream_errno = EINVAL; + return -1; + } + ret = LZ4_compress_default((void *)zstream->compressbuf, + (void *)(zstream->outbuf + + IOSTREAM_LZ4_CHUNK_PREFIX_LEN), + zstream->compressbuf_offset, + max_dest_size); +#else + ret = LZ4_compress((void *)zstream->compressbuf, + (void *)(zstream->outbuf + + IOSTREAM_LZ4_CHUNK_PREFIX_LEN), + zstream->compressbuf_offset); +#endif /* defined(HAVE_LZ4_COMPRESS_DEFAULT) */ + i_assert(ret > 0 && (unsigned int)ret <= sizeof(zstream->outbuf) - + IOSTREAM_LZ4_CHUNK_PREFIX_LEN); + zstream->outbuf_used = IOSTREAM_LZ4_CHUNK_PREFIX_LEN + ret; + chunk_size = zstream->outbuf_used - IOSTREAM_LZ4_CHUNK_PREFIX_LEN; + zstream->outbuf[0] = (chunk_size & 0xff000000) >> 24; + zstream->outbuf[1] = (chunk_size & 0x00ff0000) >> 16; + zstream->outbuf[2] = (chunk_size & 0x0000ff00) >> 8; + zstream->outbuf[3] = (chunk_size & 0x000000ff); + zstream->compressbuf_offset = 0; + return 1; +} + +static ssize_t +o_stream_lz4_send_chunk(struct lz4_ostream *zstream, + const void *data, size_t size) +{ + size_t max_size; + ssize_t added_bytes = 0; + int ret; + + i_assert(zstream->outbuf_used == 0); + + do { + max_size = I_MIN(size, sizeof(zstream->compressbuf) - + zstream->compressbuf_offset); + memcpy(zstream->compressbuf + zstream->compressbuf_offset, + data, max_size); + zstream->compressbuf_offset += max_size; + + data = CONST_PTR_OFFSET(data, max_size); + size -= max_size; + added_bytes += max_size; + + if (zstream->compressbuf_offset == sizeof(zstream->compressbuf)) { + ret = o_stream_lz4_compress(zstream); + if (ret <= 0) + return added_bytes != 0 ? added_bytes : ret; + } + } while (size > 0); + + return added_bytes; +} + +static int o_stream_lz4_flush(struct ostream_private *stream) +{ + struct lz4_ostream *zstream = (struct lz4_ostream *)stream; + + if (o_stream_lz4_compress(zstream) < 0) + return -1; + if (o_stream_lz4_send_outbuf(zstream) < 0) + return -1; + + return o_stream_flush_parent(stream); +} + +static size_t +o_stream_lz4_get_buffer_used_size(const struct ostream_private *stream) +{ + const struct lz4_ostream *zstream = + (const struct lz4_ostream *)stream; + + /* outbuf has already compressed data that we're trying to send to the + parent stream. compressbuf isn't included in the return value, + because it needs to be filled up or flushed. */ + return (zstream->outbuf_used - zstream->outbuf_offset) + + o_stream_get_buffer_used_size(stream->parent); +} + +static size_t +o_stream_lz4_get_buffer_avail_size(const struct ostream_private *stream) +{ + const struct lz4_ostream *zstream = + (const struct lz4_ostream *)stream; + + /* We're only guaranteed to accept data to compressbuf. The parent + stream might have space, but since compressed data gets written + there it's not really known how much we can actually write there. */ + return sizeof(zstream->compressbuf) - zstream->compressbuf_offset; +} + +static ssize_t +o_stream_lz4_sendv(struct ostream_private *stream, + const struct const_iovec *iov, unsigned int iov_count) +{ + struct lz4_ostream *zstream = (struct lz4_ostream *)stream; + ssize_t ret, bytes = 0; + unsigned int i; + + if ((ret = o_stream_lz4_send_outbuf(zstream)) <= 0) { + /* error / we still couldn't flush existing data to + parent stream. */ + return ret; + } + + for (i = 0; i < iov_count; i++) { + ret = o_stream_lz4_send_chunk(zstream, iov[i].iov_base, + iov[i].iov_len); + if (ret < 0) + return -1; + bytes += ret; + if ((size_t)ret != iov[i].iov_len) + break; + } + stream->ostream.offset += bytes; + return bytes; +} + +struct ostream *o_stream_create_lz4(struct ostream *output, int level) +{ + struct iostream_lz4_header *hdr; + struct lz4_ostream *zstream; + + i_assert(level >= 1 && level <= 9); + + zstream = i_new(struct lz4_ostream, 1); + zstream->ostream.sendv = o_stream_lz4_sendv; + zstream->ostream.flush = o_stream_lz4_flush; + zstream->ostream.get_buffer_used_size = + o_stream_lz4_get_buffer_used_size; + zstream->ostream.get_buffer_avail_size = + o_stream_lz4_get_buffer_avail_size; + zstream->ostream.iostream.close = o_stream_lz4_close; + + i_assert(sizeof(zstream->outbuf) >= sizeof(*hdr)); + hdr = (void *)zstream->outbuf; + memcpy(hdr->magic, IOSTREAM_LZ4_MAGIC, sizeof(hdr->magic)); + hdr->max_uncompressed_chunk_size[0] = + (OSTREAM_LZ4_CHUNK_SIZE & 0xff000000) >> 24; + hdr->max_uncompressed_chunk_size[1] = + (OSTREAM_LZ4_CHUNK_SIZE & 0x00ff0000) >> 16; + hdr->max_uncompressed_chunk_size[2] = + (OSTREAM_LZ4_CHUNK_SIZE & 0x0000ff00) >> 8; + hdr->max_uncompressed_chunk_size[3] = + (OSTREAM_LZ4_CHUNK_SIZE & 0x000000ff); + zstream->outbuf_used = sizeof(*hdr); + return o_stream_create(&zstream->ostream, output, + o_stream_get_fd(output)); +} +#endif diff --git a/src/lib-compression/ostream-zlib.c b/src/lib-compression/ostream-zlib.c new file mode 100644 index 0000000..7267917 --- /dev/null +++ b/src/lib-compression/ostream-zlib.c @@ -0,0 +1,392 @@ +/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" + +#ifdef HAVE_ZLIB + +#include "crc32.h" +#include "ostream-private.h" +#include "ostream-zlib.h" +#include <zlib.h> + +#define CHUNK_SIZE (1024*32) +#define ZLIB_OS_CODE 0x03 /* Unix */ + +struct zlib_ostream { + struct ostream_private ostream; + z_stream zs; + + unsigned char gz_header[10]; + unsigned char outbuf[CHUNK_SIZE]; + unsigned int outbuf_offset, outbuf_used; + unsigned int header_bytes_left; + + uint32_t crc, bytes32; + + bool gz:1; + bool flushed:1; +}; + +int compression_get_min_level_gz(void) +{ + return Z_NO_COMPRESSION; +} + +int compression_get_default_level_gz(void) +{ + return Z_DEFAULT_COMPRESSION; +} + +int compression_get_max_level_gz(void) +{ + return Z_BEST_COMPRESSION; +} + +static void o_stream_zlib_close(struct iostream_private *stream, + bool close_parent) +{ + struct zlib_ostream *zstream = (struct zlib_ostream *)stream; + + i_assert(zstream->ostream.finished || + zstream->ostream.ostream.stream_errno != 0 || + zstream->ostream.error_handling_disabled); + (void)deflateEnd(&zstream->zs); + if (close_parent) + o_stream_close(zstream->ostream.parent); +} + +static int o_stream_zlib_send_gz_header(struct zlib_ostream *zstream) +{ + size_t header_send_offset = + sizeof(zstream->gz_header) - zstream->header_bytes_left; + ssize_t ret; + + i_assert(zstream->header_bytes_left <= sizeof(zstream->gz_header)); + ret = o_stream_send(zstream->ostream.parent, + zstream->gz_header + header_send_offset, + zstream->header_bytes_left); + if (ret < 0) { + o_stream_copy_error_from_parent(&zstream->ostream); + return -1; + } + i_assert((size_t)ret <= zstream->header_bytes_left); + zstream->header_bytes_left -= ret; + return zstream->header_bytes_left == 0 ? 1 : 0; +} + +static int o_stream_zlib_lsb_uint32(struct ostream *output, uint32_t num) +{ + unsigned char buf[sizeof(uint32_t)]; + unsigned int i; + + for (i = 0; i < sizeof(buf); i++) { + buf[i] = num & 0xff; + num >>= 8; + } + if (o_stream_send(output, buf, sizeof(buf)) != sizeof(buf)) + return -1; + return 0; +} + +static int o_stream_zlib_send_gz_trailer(struct zlib_ostream *zstream) +{ + struct ostream *output = zstream->ostream.parent; + + if (!zstream->gz) + return 0; + + if (o_stream_zlib_lsb_uint32(output, zstream->crc) < 0 || + o_stream_zlib_lsb_uint32(output, zstream->bytes32) < 0) { + o_stream_copy_error_from_parent(&zstream->ostream); + return -1; + } + return 0; +} + +static int o_stream_zlib_send_outbuf(struct zlib_ostream *zstream) +{ + ssize_t ret; + size_t size; + + if (zstream->outbuf_used == 0) + return 1; + + size = zstream->outbuf_used - zstream->outbuf_offset; + i_assert(size > 0); + ret = o_stream_send(zstream->ostream.parent, + zstream->outbuf + zstream->outbuf_offset, size); + if (ret < 0) { + o_stream_copy_error_from_parent(&zstream->ostream); + return -1; + } + if ((size_t)ret != size) { + zstream->outbuf_offset += ret; + return 0; + } + zstream->outbuf_offset = 0; + zstream->outbuf_used = 0; + return 1; +} + +static ssize_t +o_stream_zlib_send_chunk(struct zlib_ostream *zstream, + const void *data, size_t size) +{ + z_stream *zs = &zstream->zs; + int ret, flush; + + i_assert(zstream->outbuf_used == 0); + + flush = zstream->ostream.corked || zstream->gz ? + Z_NO_FLUSH : Z_SYNC_FLUSH; + + if (zstream->header_bytes_left > 0) { + if ((ret = o_stream_zlib_send_gz_header(zstream)) <= 0) + return ret; + } + + zs->next_in = (void *)data; + zs->avail_in = size; + while (zs->avail_in > 0) { + if (zs->avail_out == 0) { + /* previous block was compressed. send it and start + compression for a new block. */ + zs->next_out = zstream->outbuf; + zs->avail_out = sizeof(zstream->outbuf); + + zstream->outbuf_used = sizeof(zstream->outbuf); + if ((ret = o_stream_zlib_send_outbuf(zstream)) < 0) + return -1; + if (ret == 0) { + /* parent stream's buffer full */ + break; + } + } + + ret = deflate(zs, flush); + switch (ret) { + case Z_OK: + case Z_BUF_ERROR: + break; + case Z_MEM_ERROR: + i_fatal_status(FATAL_OUTOFMEM, "zlib: Out of memory"); + case Z_STREAM_ERROR: + i_assert(zstream->gz); + i_panic("zlib.write(%s) failed: Can't write more data to .gz after flushing", + o_stream_get_name(&zstream->ostream.ostream)); + default: + i_panic("zlib.write(%s) failed with unexpected code %d", + o_stream_get_name(&zstream->ostream.ostream), ret); + } + } + size -= zs->avail_in; + + zstream->crc = crc32_data_more(zstream->crc, data, size); + zstream->bytes32 += size; + zstream->flushed = FALSE; + return size; +} + +static int +o_stream_zlib_send_flush(struct zlib_ostream *zstream, bool final) +{ + z_stream *zs = &zstream->zs; + size_t len; + bool done = FALSE; + int ret, flush; + + i_assert(zs->avail_in == 0); + + if (zstream->flushed) { + i_assert(zstream->outbuf_used == 0); + return 1; + } + + if ((ret = o_stream_flush_parent_if_needed(&zstream->ostream)) <= 0) + return ret; + if (zstream->header_bytes_left > 0) { + if ((ret = o_stream_zlib_send_gz_header(zstream)) <= 0) + return ret; + } + + if ((ret = o_stream_zlib_send_outbuf(zstream)) <= 0) + return ret; + + flush = final ? Z_FINISH : + (!zstream->gz ? Z_SYNC_FLUSH : Z_NO_FLUSH); + + i_assert(zstream->outbuf_used == 0); + do { + len = sizeof(zstream->outbuf) - zs->avail_out; + if (len != 0) { + zs->next_out = zstream->outbuf; + zs->avail_out = sizeof(zstream->outbuf); + + zstream->outbuf_used = len; + if ((ret = o_stream_zlib_send_outbuf(zstream)) <= 0) + return ret; + if (done) + break; + } + + switch (deflate(zs, flush)) { + case Z_OK: + case Z_BUF_ERROR: + break; + case Z_STREAM_END: + done = TRUE; + break; + case Z_MEM_ERROR: + i_fatal_status(FATAL_OUTOFMEM, "zlib: Out of memory"); + default: + i_unreached(); + } + } while (zs->avail_out != sizeof(zstream->outbuf)); + + if (final) { + if (o_stream_zlib_send_gz_trailer(zstream) < 0) + return -1; + } + if (final) + zstream->flushed = TRUE; + i_assert(zstream->outbuf_used == 0); + return 1; +} + +static int o_stream_zlib_flush(struct ostream_private *stream) +{ + struct zlib_ostream *zstream = (struct zlib_ostream *)stream; + int ret; + if ((ret = o_stream_zlib_send_flush(zstream, stream->finished)) < 0) + return -1; + else if (ret > 0) + return o_stream_flush_parent(stream); + return ret; +} + +static size_t +o_stream_zlib_get_buffer_used_size(const struct ostream_private *stream) +{ + const struct zlib_ostream *zstream = + (const struct zlib_ostream *)stream; + + /* outbuf has already compressed data that we're trying to send to the + parent stream. We're not including zlib's internal compression + buffer size. */ + return (zstream->outbuf_used - zstream->outbuf_offset) + + o_stream_get_buffer_used_size(stream->parent); +} + +static size_t +o_stream_zlib_get_buffer_avail_size(const struct ostream_private *stream) +{ + /* FIXME: not correct - this is counting compressed size, which may be + too larger than uncompressed size in some situations. Fixing would + require some kind of additional buffering. */ + return o_stream_get_buffer_avail_size(stream->parent); +} + +static ssize_t +o_stream_zlib_sendv(struct ostream_private *stream, + const struct const_iovec *iov, unsigned int iov_count) +{ + struct zlib_ostream *zstream = (struct zlib_ostream *)stream; + ssize_t ret, bytes = 0; + unsigned int i; + + if ((ret = o_stream_zlib_send_outbuf(zstream)) <= 0) { + /* error / we still couldn't flush existing data to + parent stream. */ + return ret; + } + + for (i = 0; i < iov_count; i++) { + ret = o_stream_zlib_send_chunk(zstream, iov[i].iov_base, + iov[i].iov_len); + if (ret < 0) + return -1; + bytes += ret; + if ((size_t)ret != iov[i].iov_len) + break; + } + stream->ostream.offset += bytes; + + if (!zstream->ostream.corked && i == iov_count) { + if (o_stream_zlib_send_flush(zstream, FALSE) < 0) + return -1; + } + /* avail_in!=0 check is used to detect errors. if it's non-zero here + it simply means we didn't send all the data */ + zstream->zs.avail_in = 0; + return bytes; +} + +static void o_stream_zlib_init_gz_header(struct zlib_ostream *zstream, + int level, int strategy) +{ + unsigned char *hdr = zstream->gz_header; + + hdr[0] = 0x1f; + hdr[1] = 0x8b; + hdr[2] = Z_DEFLATED; + hdr[8] = level == 9 ? 2 : + (strategy >= Z_HUFFMAN_ONLY || + (level != Z_DEFAULT_COMPRESSION && level < 2) ? 4 : 0); + hdr[9] = ZLIB_OS_CODE; + i_assert(sizeof(zstream->gz_header) == 10); +} + +static struct ostream * +o_stream_create_zlib(struct ostream *output, int level, bool gz) +{ + const int strategy = Z_DEFAULT_STRATEGY; + struct zlib_ostream *zstream; + int ret; + + /* accepted range is 0..9 and -1 is default compression */ + i_assert(level >= -1 && level <= 9); + + zstream = i_new(struct zlib_ostream, 1); + zstream->ostream.sendv = o_stream_zlib_sendv; + zstream->ostream.flush = o_stream_zlib_flush; + zstream->ostream.get_buffer_used_size = + o_stream_zlib_get_buffer_used_size; + zstream->ostream.get_buffer_avail_size = + o_stream_zlib_get_buffer_avail_size; + zstream->ostream.iostream.close = o_stream_zlib_close; + zstream->crc = 0; + zstream->gz = gz; + if (gz) + zstream->header_bytes_left = sizeof(zstream->gz_header); + + o_stream_zlib_init_gz_header(zstream, level, strategy); + ret = deflateInit2(&zstream->zs, level, Z_DEFLATED, -15, 8, strategy); + switch (ret) { + case Z_OK: + break; + case Z_MEM_ERROR: + i_fatal_status(FATAL_OUTOFMEM, "deflateInit(): Out of memory"); + case Z_VERSION_ERROR: + i_fatal("Wrong zlib library version (broken compilation)"); + case Z_STREAM_ERROR: + i_fatal("Invalid compression level %d", level); + default: + i_fatal("deflateInit() failed with %d", ret); + } + + zstream->zs.next_out = zstream->outbuf; + zstream->zs.avail_out = sizeof(zstream->outbuf); + return o_stream_create(&zstream->ostream, output, + o_stream_get_fd(output)); +} + +struct ostream *o_stream_create_gz(struct ostream *output, int level) +{ + return o_stream_create_zlib(output, level, TRUE); +} + +struct ostream *o_stream_create_deflate(struct ostream *output, int level) +{ + return o_stream_create_zlib(output, level, FALSE); +} +#endif diff --git a/src/lib-compression/ostream-zlib.h b/src/lib-compression/ostream-zlib.h new file mode 100644 index 0000000..ac038b5 --- /dev/null +++ b/src/lib-compression/ostream-zlib.h @@ -0,0 +1,26 @@ +#ifndef OSTREAM_ZLIB_H +#define OSTREAM_ZLIB_H + +struct ostream *o_stream_create_gz(struct ostream *output, int level); +struct ostream *o_stream_create_deflate(struct ostream *output, int level); +struct ostream *o_stream_create_bz2(struct ostream *output, int level); +struct ostream *o_stream_create_lz4(struct ostream *output, int level); +struct ostream *o_stream_create_zstd(struct ostream *output, int level); + +int compression_get_min_level_gz(void); +int compression_get_default_level_gz(void); +int compression_get_max_level_gz(void); + +int compression_get_min_level_bz2(void); +int compression_get_default_level_bz2(void); +int compression_get_max_level_bz2(void); + +int compression_get_min_level_lz4(void); +int compression_get_default_level_lz4(void); +int compression_get_max_level_lz4(void); + +int compression_get_min_level_zstd(void); +int compression_get_default_level_zstd(void); +int compression_get_max_level_zstd(void); + +#endif diff --git a/src/lib-compression/ostream-zstd.c b/src/lib-compression/ostream-zstd.c new file mode 100644 index 0000000..e40380b --- /dev/null +++ b/src/lib-compression/ostream-zstd.c @@ -0,0 +1,243 @@ +/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ + +#include "lib.h" + +#ifdef HAVE_ZSTD + +#include "ostream.h" +#include "ostream-private.h" +#include "ostream-zlib.h" + +#include "zstd.h" +#include "zstd_errors.h" +#include "iostream-zstd-private.h" + +struct zstd_ostream { + struct ostream_private ostream; + + ZSTD_CStream *cstream; + ZSTD_outBuffer output; + + unsigned char *outbuf; + + bool flushed:1; + bool closed:1; + bool finished:1; +}; + +int compression_get_min_level_zstd(void) +{ +#if HAVE_DECL_ZSTD_MINCLEVEL == 1 + return ZSTD_minCLevel(); +#else + return 1; +#endif +} + +int compression_get_default_level_zstd(void) +{ +#ifdef ZSTD_CLEVEL_DEFAULT + return ZSTD_CLEVEL_DEFAULT; +#else + /* Documentation says 3 is default */ + return 3; +#endif +} + +int compression_get_max_level_zstd(void) +{ + return ZSTD_maxCLevel(); +} + +static void o_stream_zstd_write_error(struct zstd_ostream *zstream, size_t err) +{ + ZSTD_ErrorCode errcode = zstd_version_errcode(ZSTD_getErrorCode(err)); + const char *error = ZSTD_getErrorName(err); + if (errcode == ZSTD_error_memory_allocation) + i_fatal_status(FATAL_OUTOFMEM, "zstd.write(%s): Out of memory", + o_stream_get_name(&zstream->ostream.ostream)); + else if (errcode == ZSTD_error_prefix_unknown || +#if HAVE_DECL_ZSTD_ERROR_PARAMETER_UNSUPPORTED == 1 + errcode == ZSTD_error_parameter_unsupported || +#endif + errcode == ZSTD_error_dictionary_wrong || + errcode == ZSTD_error_init_missing) + zstream->ostream.ostream.stream_errno = EINVAL; + else + zstream->ostream.ostream.stream_errno = EIO; + + io_stream_set_error(&zstream->ostream.iostream, + "zstd.write(%s): %s at %"PRIuUOFF_T, + o_stream_get_name(&zstream->ostream.ostream), error, + zstream->ostream.ostream.offset); +} + +static ssize_t o_stream_zstd_send_outbuf(struct zstd_ostream *zstream) +{ + ssize_t ret; + /* nothing to send */ + if (zstream->output.pos == 0) + return 1; + ret = o_stream_send(zstream->ostream.parent, zstream->output.dst, + zstream->output.pos); + if (ret < 0) { + o_stream_copy_error_from_parent(&zstream->ostream); + return -1; + } else { + memmove(zstream->outbuf, zstream->outbuf+ret, zstream->output.pos-ret); + zstream->output.pos -= ret; + } + if (zstream->output.pos > 0) + return 0; + return 1; +} + +static ssize_t +o_stream_zstd_sendv(struct ostream_private *stream, + const struct const_iovec *iov, unsigned int iov_count) +{ + struct zstd_ostream *zstream = + container_of(stream, struct zstd_ostream, ostream); + ssize_t total = 0; + size_t ret; + + for (unsigned int i = 0; i < iov_count; i++) { + /* does it actually fit there */ + ZSTD_inBuffer input = { + .src = iov[i].iov_base, + .pos = 0, + .size = iov[i].iov_len + }; + bool flush_attempted = FALSE; + for (;;) { + size_t prev_pos = input.pos; + ret = ZSTD_compressStream(zstream->cstream, &zstream->output, + &input); + if (ZSTD_isError(ret) != 0) { + o_stream_zstd_write_error(zstream, ret); + return -1; + } + size_t new_input_size = input.pos - prev_pos; + if (new_input_size == 0 && flush_attempted) { + /* non-blocking output buffer full */ + return total; + } + stream->ostream.offset += new_input_size; + total += new_input_size; + if (input.pos == input.size) + break; + /* output buffer full. try to flush it. */ + if (o_stream_zstd_send_outbuf(zstream) < 0) + return -1; + flush_attempted = TRUE; + } + } + if (o_stream_zstd_send_outbuf(zstream) < 0) + return -1; + return total; +} + +static int o_stream_zstd_send_flush(struct zstd_ostream *zstream, bool final) +{ + int ret; + + if (zstream->flushed) { + i_assert(zstream->output.pos == 0); + return 1; + } + + if ((ret = o_stream_flush_parent_if_needed(&zstream->ostream)) <= 0) + return ret; + + if (zstream->output.pos == 0) + ZSTD_flushStream(zstream->cstream, &zstream->output); + + if ((ret = o_stream_zstd_send_outbuf(zstream)) <= 0) + return ret; + + if (!final) + return 1; + + if (!zstream->finished) { + ret = ZSTD_endStream(zstream->cstream, &zstream->output); + if (ZSTD_isError(ret) != 0) { + o_stream_zstd_write_error(zstream, ret); + return -1; + } + zstream->finished = TRUE; + } + + if ((ret = o_stream_zstd_send_outbuf(zstream)) <= 0) + return ret; + + if (final) + zstream->flushed = TRUE; + i_assert(zstream->output.pos == 0); + return 1; +} + +static int o_stream_zstd_flush(struct ostream_private *stream) +{ + struct zstd_ostream *zstream = + container_of(stream, struct zstd_ostream, ostream); + + int ret; + if ((ret = o_stream_zstd_send_flush(zstream, stream->finished)) < 0) + return -1; + else if (ret > 0) + return o_stream_flush_parent(stream); + return ret; +} + +static void o_stream_zstd_close(struct iostream_private *stream, + bool close_parent) +{ + struct ostream_private *_ostream = + container_of(stream, struct ostream_private, iostream); + struct zstd_ostream *zstream = + container_of(_ostream, struct zstd_ostream, ostream); + + i_assert(zstream->ostream.finished || + zstream->ostream.ostream.stream_errno != 0 || + zstream->ostream.error_handling_disabled); + if (zstream->cstream != NULL) { + ZSTD_freeCStream(zstream->cstream); + zstream->cstream = NULL; + } + i_free(zstream->outbuf); + i_zero(&zstream->output); + if (close_parent) + o_stream_close(zstream->ostream.parent); +} + +struct ostream * +o_stream_create_zstd(struct ostream *output, int level) +{ + struct zstd_ostream *zstream; + size_t ret; + + i_assert(level >= compression_get_min_level_zstd() && + level <= compression_get_max_level_zstd()); + + zstd_version_check(); + + zstream = i_new(struct zstd_ostream, 1); + zstream->ostream.sendv = o_stream_zstd_sendv; + zstream->ostream.flush = o_stream_zstd_flush; + zstream->ostream.iostream.close = o_stream_zstd_close; + zstream->cstream = ZSTD_createCStream(); + if (zstream->cstream == NULL) + i_fatal_status(FATAL_OUTOFMEM, "zstd: Out of memory"); + ret = ZSTD_initCStream(zstream->cstream, level); + if (ZSTD_isError(ret) != 0) + o_stream_zstd_write_error(zstream, ret); + else { + zstream->outbuf = i_malloc(ZSTD_CStreamOutSize()); + zstream->output.dst = zstream->outbuf; + zstream->output.size = ZSTD_CStreamOutSize(); + } + return o_stream_create(&zstream->ostream, output, + o_stream_get_fd(output)); +} + +#endif diff --git a/src/lib-compression/test-compression.c b/src/lib-compression/test-compression.c new file mode 100644 index 0000000..f690c7b --- /dev/null +++ b/src/lib-compression/test-compression.c @@ -0,0 +1,1122 @@ +/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "buffer.h" +#include "istream.h" +#include "iostream-temp.h" +#include "ostream.h" +#include "sha1.h" +#include "randgen.h" +#include "test-common.h" +#include "compression.h" +#include "iostream-lz4.h" + +#include "hex-binary.h" + +#include <unistd.h> +#include <fcntl.h> + +static void test_compression_handler_detect(const struct compression_handler *handler) +{ + const unsigned char test_data[] = {'h','e','l','l','o',' ', + 'w','o','r','l','d','\n'}; + const unsigned char *data; + size_t size; + buffer_t *buffer; + struct ostream *test_output; + struct ostream *output; + + struct istream *test_input; + struct istream *input; + + /* write some amount of data */ + test_begin(t_strdup_printf("compression handler %s (detect)", handler->name)); + + buffer = buffer_create_dynamic(default_pool, 1024); + + test_output = test_ostream_create(buffer); + output = handler->create_ostream(test_output, 1); + o_stream_unref(&test_output); + + /* write data at once */ + test_assert(o_stream_send(output, test_data, sizeof(test_data)) == sizeof(test_data)); + test_assert(o_stream_finish(output) == 1); + o_stream_unref(&output); + + test_input = test_istream_create_data(buffer->data, buffer->used); + handler = compression_detect_handler(test_input); + i_stream_seek(test_input, 0); + test_assert(handler != NULL); + if (handler != NULL) { + input = handler->create_istream(test_input); + i_stream_unref(&test_input); + + test_assert(i_stream_read_more(input, &data, &size) > 0); + test_assert(size == sizeof(test_data) && + memcmp(data, test_data, size) == 0); + + i_stream_unref(&input); + } else { + i_stream_unref(&test_input); + } + + buffer_free(&buffer); + test_end(); +} + +static void +test_compression_handler_short(const struct compression_handler *handler, + bool autodetect) +{ + const unsigned char *data; + size_t len, size; + buffer_t *test_data; + buffer_t *buffer; + struct ostream *test_output; + struct ostream *output; + + struct istream *test_input; + struct istream *input; + + /* write some amount of data */ + test_begin(t_strdup_printf("compression handler %s (small, autodetect=%s)", + handler->name, autodetect ? "yes" : "no")); + len = i_rand_minmax(1, 1024); + test_data = buffer_create_dynamic(default_pool, len); + random_fill(buffer_append_space_unsafe(test_data, len), len); + buffer_set_used_size(test_data, len); + buffer_append(test_data, "hello. world.\n", 14); + + buffer = buffer_create_dynamic(default_pool, 1024); + test_output = test_ostream_create(buffer); + output = handler->create_ostream(test_output, 1); + o_stream_unref(&test_output); + + /* write data at once */ + test_assert(o_stream_send(output, test_data->data, test_data->used) == (ssize_t)test_data->used); + test_assert(o_stream_finish(output) == 1); + o_stream_unref(&output); + + /* read data at once */ + test_input = test_istream_create_data(buffer->data, buffer->used); + input = !autodetect ? handler->create_istream(test_input) : + i_stream_create_decompress(test_input, 0); + i_stream_unref(&test_input); + + test_assert(i_stream_read_more(input, &data, &size) > 0); + test_assert(size == test_data->used && + memcmp(data, test_data->data, size) ==0); + + i_stream_unref(&input); + + buffer_free(&buffer); + buffer_free(&test_data); + + test_end(); +} + +static void +test_compression_handler_empty(const struct compression_handler *handler, + bool autodetect) +{ + buffer_t *buffer; + struct ostream *test_output; + struct ostream *output; + + struct istream *test_input; + struct istream *input; + + /* create stream and finish it without writing anything */ + test_begin(t_strdup_printf("compression handler %s (empty, autodetect=%s)", + handler->name, autodetect ? "yes" : "no")); + buffer = buffer_create_dynamic(default_pool, 128); + test_output = test_ostream_create(buffer); + output = handler->create_ostream(test_output, 1); + o_stream_unref(&test_output); + test_assert(o_stream_finish(output) == 1); + o_stream_unref(&output); + + /* read the input */ + test_input = test_istream_create_data(buffer->data, buffer->used); + input = !autodetect ? handler->create_istream(test_input) : + i_stream_create_decompress(test_input, 0); + i_stream_unref(&test_input); + + test_assert(i_stream_read(input) == -1); + test_assert(i_stream_get_data_size(input) == 0); + i_stream_unref(&input); + + buffer_free(&buffer); + + test_end(); +} + +static void +test_compression_handler_seek(const struct compression_handler *handler, + bool autodetect) +{ + const unsigned char *data,*ptr; + size_t len, size, pos; + buffer_t *test_data; + buffer_t *buffer; + struct ostream *test_output; + struct ostream *output; + + struct istream *test_input; + struct istream *input; + + /* write some amount of data */ + test_begin(t_strdup_printf("compression handler %s (seek, autodetect=%s)", + handler->name, autodetect ? "yes" : "no")); + len = i_rand_minmax(1024, 2048); + test_data = buffer_create_dynamic(default_pool, len); + random_fill(buffer_append_space_unsafe(test_data, len), len); + buffer_set_used_size(test_data, len); + buffer_append(test_data, "hello. world.\n", 14); + + buffer = buffer_create_dynamic(default_pool, 1024); + test_output = test_ostream_create(buffer); + output = handler->create_ostream(test_output, 1); + o_stream_unref(&test_output); + + /* write data at once */ + test_assert(o_stream_send(output, test_data->data, test_data->used) == (ssize_t)test_data->used); + test_assert(o_stream_finish(output) == 1); + o_stream_unref(&output); + + test_input = test_istream_create_data(buffer->data, buffer->used); + input = !autodetect ? handler->create_istream(test_input) : + i_stream_create_decompress(test_input, 0); + i_stream_unref(&test_input); + + /* seek forward */ + i_stream_seek(input, test_data->used - 14); /* should read 'hello. world.\n' */ + + test_assert(i_stream_read_more(input, &data, &size) > 0); + test_assert(size >= 14 && memcmp(data, "hello. world.\n", 14) == 0); + i_stream_skip(input, size); + + ptr = test_data->data; + + /* seek to random positions and see that we get correct data */ + for (unsigned int i = 0; i < 1000; i++) { + pos = i_rand_limit(test_data->used); + i_stream_seek(input, pos); + size = 0; + test_assert_idx(i_stream_read_more(input, &data, &size) > 0, i); + test_assert_idx(size > 0 && memcmp(data,ptr+pos,size) == 0, i); + } + + i_stream_unref(&input); + + buffer_free(&buffer); + buffer_free(&test_data); + + test_end(); +} + +static void +test_compression_handler_reset(const struct compression_handler *handler, + bool autodetect) +{ + const unsigned char *data; + size_t len, size; + buffer_t *test_data; + buffer_t *buffer; + struct ostream *test_output; + struct ostream *output; + + struct istream *test_input; + struct istream *input; + + /* write some amount of data */ + test_begin(t_strdup_printf("compression handler %s (reset, autodetect=%s)", + handler->name, autodetect ? "yes" : "no")); + len = i_rand_minmax(1024, 2048); + test_data = buffer_create_dynamic(default_pool, len); + random_fill(buffer_append_space_unsafe(test_data, len), len); + buffer_set_used_size(test_data, len); + buffer_append(test_data, "hello. world.\n", 14); + + buffer = buffer_create_dynamic(default_pool, 1024); + test_output = test_ostream_create(buffer); + output = handler->create_ostream(test_output, 1); + o_stream_unref(&test_output); + + /* write data at once */ + test_assert(o_stream_send(output, test_data->data, test_data->used) == (ssize_t)test_data->used); + test_assert(o_stream_finish(output) == 1); + o_stream_unref(&output); + + test_input = test_istream_create_data(buffer->data, buffer->used); + input = !autodetect ? handler->create_istream(test_input) : + i_stream_create_decompress(test_input, 0); + i_stream_unref(&test_input); + + /* seek forward */ + i_stream_seek(input, test_data->used - 14); /* should read 'hello. world.\n' */ + + test_assert(i_stream_read_more(input, &data, &size) > 0); + test_assert(size >= 14 && memcmp(data, "hello. world.\n", 14) == 0); + i_stream_skip(input, size); + + /* reset */ + i_stream_sync(input); + + /* see that we still get data, at start */ + size = 0; + test_assert(i_stream_read_more(input, &data, &size) > 0); + test_assert(size > 0 && memcmp(data, test_data->data, size) == 0); + + i_stream_unref(&input); + + buffer_free(&buffer); + buffer_free(&test_data); + + test_end(); +} + +static void +test_compression_handler(const struct compression_handler *handler, + bool autodetect) +{ + const char *path = "test-compression.tmp"; + struct istream *file_input, *input; + struct ostream *file_output, *output; + unsigned char buf[IO_BLOCK_SIZE]; + const unsigned char *data; + size_t size; + uoff_t stream_size; + struct sha1_ctxt sha1; + unsigned char output_sha1[SHA1_RESULTLEN], input_sha1[SHA1_RESULTLEN]; + unsigned int i; + int fd; + ssize_t ret; + + test_begin(t_strdup_printf("compression handler %s (autodetect=%s)", + handler->name, autodetect ? "yes" : "no")); + + /* write compressed data */ + fd = open(path, O_TRUNC | O_CREAT | O_RDWR, 0600); + if (fd == -1) + i_fatal("creat(%s) failed: %m", path); + file_output = o_stream_create_fd_file(fd, 0, FALSE); + output = handler->create_ostream(file_output, 1); + sha1_init(&sha1); + + /* 1) write lots of easily compressible data */ + memset(buf, 0, sizeof(buf)); + for (i = 0; i < 1024*1024*4 / sizeof(buf); i++) { + sha1_loop(&sha1, buf, sizeof(buf)); + test_assert(o_stream_send(output, buf, sizeof(buf)) == sizeof(buf)); + } + + /* 2) write uncompressible data */ + for (i = 0; i < 1024*128 / sizeof(buf); i++) { + random_fill(buf, sizeof(buf)); + sha1_loop(&sha1, buf, sizeof(buf)); + test_assert(o_stream_send(output, buf, sizeof(buf)) == sizeof(buf)); + } + /* make sure the input size isn't multiple of something simple */ + random_fill(buf, sizeof(buf)); + sha1_loop(&sha1, buf, sizeof(buf) - 5); + test_assert(o_stream_send(output, buf, sizeof(buf) - 5) == sizeof(buf) - 5); + + /* 3) write semi-compressible data */ + for (i = 0; i < sizeof(buf); i++) { + if (i_rand_limit(3) == 0) + buf[i] = i_rand_limit(4); + else + buf[i] = i & UCHAR_MAX; + } + for (i = 0; i < 1024*128 / sizeof(buf); i++) { + sha1_loop(&sha1, buf, sizeof(buf)); + test_assert(o_stream_send(output, buf, sizeof(buf)) == sizeof(buf)); + } + + test_assert(o_stream_finish(output) > 0); + uoff_t uncompressed_size = output->offset; + o_stream_destroy(&output); + uoff_t compressed_size = file_output->offset; + o_stream_destroy(&file_output); + sha1_result(&sha1, output_sha1); + + /* read and uncompress the data */ + file_input = i_stream_create_fd(fd, IO_BLOCK_SIZE); + input = !autodetect ? handler->create_istream(file_input) : + i_stream_create_decompress(file_input, 0); + + test_assert(i_stream_get_size(input, FALSE, &stream_size) == 1); + test_assert(stream_size == compressed_size); + + test_assert(i_stream_get_size(input, TRUE, &stream_size) == 1); + test_assert(stream_size == uncompressed_size); + + sha1_init(&sha1); + for (bool seeked = FALSE;;) { + sha1_init(&sha1); + while ((ret = i_stream_read_more(input, &data, &size)) > 0) { + sha1_loop(&sha1, data, size); + i_stream_skip(input, size); + } + test_assert(ret == -1); + test_assert(input->stream_errno == 0); + sha1_result(&sha1, input_sha1); + test_assert(memcmp(input_sha1, output_sha1, sizeof(input_sha1)) == 0); + if (seeked) + break; + seeked = TRUE; + i_stream_seek(input, 1); + (void)i_stream_read(input); + i_stream_seek(input, 0); + } + i_stream_destroy(&input); + i_stream_destroy(&file_input); + + i_unlink(path); + i_close_fd(&fd); + + test_end(); +} + +static void +test_compression_handler_partial_parent_write(const struct compression_handler *handler, + bool autodetect) +{ + test_begin(t_strdup_printf("compression handler %s (partial parent writes, autodetect=%s)", + handler->name, autodetect ? "yes" : "no")); + + int ret; + buffer_t *buffer = t_buffer_create(64); + buffer_t *compressed_data = t_buffer_create(256); + struct ostream *os = test_ostream_create_nonblocking(buffer, 64); + struct ostream *os_compressed = handler->create_ostream(os, 9); + o_stream_unref(&os); + + unsigned char input_buffer[64]; + /* create unlikely compressible data */ + random_fill(input_buffer, sizeof(input_buffer)); + + for (unsigned int i = 0; i < 10; i++) { + /* write it to stream */ + test_assert_idx(o_stream_send(os_compressed, input_buffer, sizeof(input_buffer)) == sizeof(input_buffer), i); + + while ((ret = o_stream_flush(os_compressed)) == 0) { + /* flush buffer */ + if (buffer->used > 0) + buffer_append(compressed_data, buffer->data, buffer->used); + buffer_set_used_size(buffer, 0); + } + if (buffer->used > 0) + buffer_append(compressed_data, buffer->data, buffer->used); + buffer_set_used_size(buffer, 0); + test_assert_idx(ret == 1, i); + } + test_assert(o_stream_finish(os_compressed) == 1); + o_stream_unref(&os_compressed); + if (buffer->used > 0) + buffer_append(compressed_data, buffer->data, buffer->used); + + struct istream *is = test_istream_create_data(compressed_data->data, compressed_data->used); + struct istream *is_decompressed = + !autodetect ? handler->create_istream(is) : + i_stream_create_decompress(is, 0); + i_stream_unref(&is); + + const unsigned char *data; + size_t siz; + buffer_t *decompressed_data = t_buffer_create(sizeof(input_buffer)*10); + + while(i_stream_read_more(is_decompressed, &data, &siz) > 0) { + buffer_append(decompressed_data, data, siz); + i_stream_skip(is_decompressed, siz); + } + test_assert(decompressed_data->used == sizeof(input_buffer)*10); + for(siz = 0; siz < decompressed_data->used; siz+=sizeof(input_buffer)) { + test_assert(decompressed_data->used - siz >= sizeof(input_buffer) && + memcmp(CONST_PTR_OFFSET(decompressed_data->data, siz), + input_buffer, sizeof(input_buffer)) == 0); + } + + i_stream_unref(&is_decompressed); + + test_end(); +} + +static void +test_compression_handler_random_io(const struct compression_handler *handler, + bool autodetect) +{ + unsigned char in_buf[8192]; + size_t in_buf_size; + buffer_t *enc_buf, *dec_buf; + unsigned int i, j; + int ret; + + enc_buf = buffer_create_dynamic(default_pool, sizeof(in_buf)); + dec_buf = buffer_create_dynamic(default_pool, sizeof(in_buf)); + + test_begin(t_strdup_printf("compression handler %s (random I/O, autodetect=%s)", + handler->name, autodetect ? "yes" : "no")); + + for (i = 0; !test_has_failed() && i < 300; i++) { + struct istream *input1, *input2; + struct ostream *output1, *output2; + struct istream *top_input; + const unsigned char *data; + size_t size, in_pos, out_pos; + + /* Initialize test data (semi-compressible) */ + in_buf_size = i_rand_limit(sizeof(in_buf)); + for (j = 0; j < in_buf_size; j++) { + if (i_rand_limit(3) == 0) + in_buf[j] = i_rand_limit(256); + else + in_buf[j] = (unsigned char)j; + } + + /* Reset encode output buffer */ + buffer_set_used_size(enc_buf, 0); + + /* Create input stream for test data */ + input1 = test_istream_create_data(in_buf, in_buf_size); + i_stream_set_name(input1, "[data]"); + + /* Create output stream for compressed data */ + output1 = test_ostream_create_nonblocking(enc_buf, + i_rand_minmax(1, 512)); + + /* Create compressor output stream */ + output2 = handler->create_ostream(output1, i_rand_minmax(1, 6)); + + /* Compress the data incrementally */ + in_pos = out_pos = 0; + ret = 0; + test_istream_set_size(input1, in_pos); + while (ret == 0) { + enum ostream_send_istream_result res; + + res = o_stream_send_istream(output2, input1); + switch(res) { + case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: + case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: + ret = -1; + break; + case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: + out_pos += i_rand_limit(512); + test_ostream_set_max_output_size( + output1, out_pos); + break; + case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: + in_pos += i_rand_limit(512); + if (in_pos > in_buf_size) + in_pos = in_buf_size; + test_istream_set_size(input1, in_pos); + break; + case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: + /* finish it */ + ret = o_stream_finish(output2); + break; + } + } + + /* Clean up */ + i_stream_unref(&input1); + o_stream_unref(&output1); + o_stream_unref(&output2); + + /* Reset decode output buffer */ + buffer_set_used_size(dec_buf, 0); + + /* Create input stream for compressed data */ + input1 = i_stream_create_from_buffer(enc_buf); + i_stream_set_name(input1, "[compressed-data]"); + + /* Create decompressor stream */ + input2 = !autodetect ? handler->create_istream(input1) : + i_stream_create_decompress(input1, 0); + i_stream_set_name(input2, "[decompressor]"); + + /* Assign random buffer sizes */ + i_stream_set_max_buffer_size(input2, i_rand_minmax(1, 512)); + + /* Read the outer stream in full with random increments. */ + top_input = input2; + while ((ret = i_stream_read_more( + top_input, &data, &size)) > 0) { + size_t ch = i_rand_limit(512); + + size = I_MIN(size, ch); + buffer_append(dec_buf, data, size); + i_stream_skip(top_input, size); + } + if (ret < 0 && top_input->stream_errno == 0) { + data = i_stream_get_data(top_input, &size); + if (size > 0) { + buffer_append(dec_buf, data, size); + i_stream_skip(top_input, size); + } + } + + /* Assert stream status */ + test_assert_idx(ret < 0 && top_input->stream_errno == 0, i); + /* Assert input/output equality */ + test_assert_idx(dec_buf->used == in_buf_size && + memcmp(in_buf, dec_buf->data, in_buf_size) == 0, + i); + + if (top_input->stream_errno != 0) { + i_error("%s: %s", i_stream_get_name(input1), + i_stream_get_error(input1)); + i_error("%s: %s", i_stream_get_name(input2), + i_stream_get_error(input2)); + } + + if (test_has_failed()) { + i_info("Test parameters: size=%zu", + in_buf_size); + } + + /* Clean up */ + i_stream_unref(&input1); + i_stream_unref(&input2); + } + test_end(); + + buffer_free(&enc_buf); + buffer_free(&dec_buf); +} + +static void +test_compression_handler_large_random_io(const struct compression_handler *handler, + bool autodetect) +{ +#define RANDOMNESS_SIZE (1024*1024) + unsigned char *randomness; + struct istream *input, *dec_input; + struct ostream *temp_output, *output; + const unsigned char *data; + size_t size; + int ret; + + test_begin(t_strdup_printf("compression handler %s (large random io, autodetect=%s)", + handler->name, autodetect ? "yes" : "no")); + randomness = i_malloc(RANDOMNESS_SIZE); + random_fill(randomness, RANDOMNESS_SIZE); + + /* write 1 MB of randomness to buffer */ + input = i_stream_create_from_data(randomness, RANDOMNESS_SIZE); + + temp_output = iostream_temp_create(".temp.", 0); + output = handler->create_ostream(temp_output, i_rand_minmax(1, 6)); + + switch (o_stream_send_istream(output, input)) { + case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: + case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: + test_assert(FALSE); + break; + case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: + case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: + i_unreached(); + case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: + test_assert(o_stream_finish(output) == 1); + break; + } + test_assert(output->offset == RANDOMNESS_SIZE); + test_assert(output->stream_errno == 0); + i_stream_unref(&input); + + input = iostream_temp_finish(&temp_output, SIZE_MAX); + o_stream_unref(&output); + + /* verify that reading the input works */ + + dec_input = !autodetect ? handler->create_istream(input) : + i_stream_create_decompress(input, 0); + + while ((ret = i_stream_read_more(dec_input, &data, &size)) > 0) { + test_assert(memcmp(data, randomness + dec_input->v_offset, size) == 0); + i_stream_skip(dec_input, size); + } + test_assert(ret == -1); + test_assert(dec_input->v_offset == RANDOMNESS_SIZE); + test_assert(dec_input->stream_errno == 0); + + i_stream_unref(&dec_input); + i_stream_unref(&input); + i_free(randomness); + test_end(); +} + +static void +test_compression_handler_errors(const struct compression_handler *handler, + bool autodetect) +{ + test_begin(t_strdup_printf("compression handler %s (errors, autodetect=%s)", + handler->name, autodetect ? "yes" : "no")); + + /* test that zero stream reading errors out */ + struct istream *is = test_istream_create(""); + struct istream *input = + !autodetect ? handler->create_istream(is) : + i_stream_create_decompress(is, 0); + i_stream_unref(&is); + test_assert(i_stream_read(input) == -1 && input->eof); + i_stream_unref(&input); + + /* test that garbage isn't considered valid */ + is = test_istream_create("dedededededededededededededede" + "dedededeededdedededededededede" + "dedededededededededededededede"); + is->blocking = TRUE; + input = !autodetect ? handler->create_istream(is) : + i_stream_create_decompress(is, 0); + i_stream_unref(&is); + test_assert(i_stream_read(input) == -1 && input->eof); + i_stream_unref(&input); + + /* test that truncated data is not considered valid */ + buffer_t *odata = buffer_create_dynamic(pool_datastack_create(), 65535); + unsigned char buf[IO_BLOCK_SIZE]; + struct ostream *os = test_ostream_create(odata); + struct ostream *output = handler->create_ostream(os, 1); + o_stream_unref(&os); + + for (unsigned int i = 0; i < 10; i++) { + random_fill(buf, sizeof(buf)); + test_assert(o_stream_send(output, buf, sizeof(buf)) == sizeof(buf)); + } + + test_assert(o_stream_finish(output) == 1); + o_stream_unref(&output); + + /* truncate buffer */ + is = test_istream_create_data(odata->data, odata->used - sizeof(buf)*2 - 1); + input = !autodetect ? handler->create_istream(is) : + i_stream_create_decompress(is, 0); + i_stream_unref(&is); + + const unsigned char *data ATTR_UNUSED; + size_t size; + while (i_stream_read_more(input, &data, &size) > 0) + i_stream_skip(input, size); + + test_assert(input->stream_errno == EPIPE); + i_stream_unref(&input); + + /* Cannot do the next check if we don't know if it's compressed. */ + if (handler->is_compressed != NULL) { + /* test incrementally reading up to 32 bytes of plaintext data + that should not match any handlers' header */ + for (size_t i = 0; i < 32; i++) { + is = test_istream_create_data("dededededededededededededededede", i); + input = !autodetect ? handler->create_istream(is) : + i_stream_create_decompress(is, 0); + i_stream_unref(&is); + while (i_stream_read_more(input, &data, &size) >= 0) { + test_assert_idx(size == 0, i); + i_stream_skip(input, size); + } + test_assert_idx(input->stream_errno == EINVAL, i); + i_stream_unref(&input); + } + } + + test_end(); +} + +static void test_compression_int(bool autodetect) +{ + unsigned int i; + + for (i = 0; compression_handlers[i].name != NULL; i++) { + if (compression_handlers[i].create_istream != NULL && + compression_handlers[i].create_ostream != NULL && + (!autodetect || + compression_handlers[i].is_compressed != NULL)) T_BEGIN { + if (compression_handlers[i].is_compressed != NULL && + !autodetect) + test_compression_handler_detect(&compression_handlers[i]); + test_compression_handler_short(&compression_handlers[i], autodetect); + test_compression_handler_empty(&compression_handlers[i], autodetect); + test_compression_handler(&compression_handlers[i], autodetect); + test_compression_handler_seek(&compression_handlers[i], autodetect); + test_compression_handler_reset(&compression_handlers[i], autodetect); + test_compression_handler_partial_parent_write(&compression_handlers[i], autodetect); + test_compression_handler_random_io(&compression_handlers[i], autodetect); + test_compression_handler_large_random_io(&compression_handlers[i], autodetect); + test_compression_handler_errors(&compression_handlers[i], autodetect); + } T_END; + } +} + +static void test_compression(void) +{ + test_compression_int(FALSE); + test_compression_int(TRUE); +} + +static void test_istream_decompression_try(void) +{ + const char *tests[] = { + "", + "1", + "12", + "12345678901234567890123456789012345678901234567890", + }; + struct istream *is, *input; + const unsigned char *data; + size_t size; + + test_begin("istream-decompression try"); + + for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) { + size_t test_len = strlen(tests[i]); + is = i_stream_create_from_data(tests[i], test_len); + input = i_stream_create_decompress(is, ISTREAM_DECOMPRESS_FLAG_TRY); + i_stream_unref(&is); + + ssize_t ret = i_stream_read(input); + test_assert_idx((test_len == 0 && ret == -1) || + (test_len > 0 && ret == (ssize_t)test_len), i); + data = i_stream_get_data(input, &size); + test_assert_idx(size == test_len && + memcmp(data, tests[i], size) == 0, i); + + i_stream_skip(input, size); + test_assert_idx(i_stream_read(input) == -1, i); + test_assert_idx(input->stream_errno == 0, i); + i_stream_unref(&input); + } + test_end(); +} + +static void test_gz(const char *str1, const char *str2, bool autodetect) +{ + const struct compression_handler *gz; + struct ostream *buf_output, *output; + struct istream *test_input, *input; + buffer_t *buf = t_buffer_create(512); + + if (compression_lookup_handler("gz", &gz) <= 0 ) + return; /* not compiled in or unkown*/ + + /* write concated output */ + buf_output = o_stream_create_buffer(buf); + o_stream_set_finish_via_child(buf_output, FALSE); + + output = gz->create_ostream(buf_output, 6); + o_stream_nsend_str(output, str1); + test_assert(o_stream_finish(output) > 0); + o_stream_destroy(&output); + + if (str2[0] != '\0') { + output = gz->create_ostream(buf_output, 6); + o_stream_nsend_str(output, "world"); + test_assert(o_stream_finish(output) > 0); + o_stream_destroy(&output); + } + + o_stream_destroy(&buf_output); + + /* read concated input */ + const unsigned char *data; + size_t size; + test_input = test_istream_create_data(buf->data, buf->used); + test_istream_set_allow_eof(test_input, FALSE); + input = !autodetect ? gz->create_istream(test_input) : + i_stream_create_decompress(test_input, 0); + for (size_t i = 0; i <= buf->used; i++) { + test_istream_set_size(test_input, i); + test_assert(i_stream_read(input) >= 0); + } + test_istream_set_allow_eof(test_input, TRUE); + test_assert(i_stream_read(input) == -1); + test_assert(input->stream_errno == 0); + + data = i_stream_get_data(input, &size); + test_assert(size == strlen(str1)+strlen(str2) && + memcmp(data, str1, strlen(str1)) == 0 && + memcmp(data+strlen(str1), str2, strlen(str2)) == 0); + i_stream_unref(&input); + i_stream_unref(&test_input); +} + +static void test_gz_concat(void) +{ + test_begin("gz concat (autodetect=no)"); + test_gz("hello", "world", FALSE); + test_end(); + + test_begin("gz concat (autodetect=yes)"); + test_gz("hello", "world", TRUE); + test_end(); +} + +static void test_gz_no_concat(void) +{ + test_begin("gz no concat (autodetect=no)"); + test_gz("hello", "", FALSE); + test_end(); + + test_begin("gz no concat (autodetect=yes)"); + test_gz("hello", "", TRUE); + test_end(); +} + +static void test_gz_header_int(bool autodetect) +{ + const struct compression_handler *gz; + const char *input_strings[] = { + "\x1F\x8B", + "\x1F\x8B\x01\x02"/* GZ_FLAG_FHCRC */"\xFF\xFF\x01\x01\x01\x01", + "\x1F\x8B\x01\x04"/* GZ_FLAG_FEXTRA */"\xFF\xFF\x01\x01\x01\x01", + "\x1F\x8B\x01\x08"/* GZ_FLAG_FNAME */"\x01\x01\x01\x01\x01\x01", + "\x1F\x8B\x01\x10"/* GZ_FLAG_FCOMMENT */"\x01\x01\x01\x01\x01\x01", + "\x1F\x8B\x01\x0C"/* GZ_FLAG_FEXTRA | GZ_FLAG_FNAME */"\xFF\xFF\x01\x01\x01\x01", + }; + struct istream *file_input, *input; + if (compression_lookup_handler("gz", &gz) <= 0 ) + return; /* not compiled in or unkown*/ + + test_begin(t_strdup_printf( + "gz header (autodetect=%s)", autodetect ? "yes" : "no")); + for (unsigned int i = 0; i < N_ELEMENTS(input_strings); i++) { + file_input = test_istream_create_data(input_strings[i], + strlen(input_strings[i])); + file_input->blocking = TRUE; + input = !autodetect ? gz->create_istream(file_input) : + i_stream_create_decompress(file_input, 0); + test_assert_idx(i_stream_read(input) == -1, i); + test_assert_idx(input->stream_errno == EINVAL, i); + i_stream_unref(&input); + i_stream_unref(&file_input); + } + test_end(); +} + +static void test_gz_header(void) +{ + test_gz_header_int(FALSE); + test_gz_header_int(TRUE); +} + +static void test_gz_large_header_int(bool autodetect) +{ + const struct compression_handler *gz; + static const unsigned char gz_input[] = { + 0x1f, 0x8b, 0x08, 0x08, + 'a','a','a','a','a','a','a','a','a','a','a', + 0 + }; + struct istream *file_input, *input; + size_t i; + + if (compression_lookup_handler("gz", &gz) <= 0 ) + return; /* not compiled in or unkown*/ + + test_begin(t_strdup_printf( + "gz large header (autodetect=%s)", autodetect ? "yes" : "no")); + + /* max buffer size smaller than gz header */ + for (i = 1; i < sizeof(gz_input); i++) { + file_input = test_istream_create_data(gz_input, sizeof(gz_input)); + test_istream_set_size(file_input, i); + test_istream_set_max_buffer_size(file_input, i); + + input = !autodetect ? gz->create_istream(file_input) : + i_stream_create_decompress(file_input, 0); + test_assert_idx(i_stream_read(input) == 0, i); + test_assert_idx(i_stream_read(input) == -1 && + input->stream_errno == EINVAL, i); + i_stream_unref(&input); + i_stream_unref(&file_input); + } + + /* max buffer size is exactly the gz header */ + file_input = test_istream_create_data(gz_input, sizeof(gz_input)); + input = !autodetect ? gz->create_istream(file_input) : + i_stream_create_decompress(file_input, 0); + test_istream_set_size(file_input, i); + test_istream_set_allow_eof(file_input, FALSE); + test_istream_set_max_buffer_size(file_input, i); + test_assert(i_stream_read(input) == 0); + i_stream_unref(&input); + i_stream_unref(&file_input); + + test_end(); +} + +static void test_gz_large_header(void) +{ + test_gz_large_header_int(FALSE); + test_gz_large_header_int(TRUE); +} + +static void test_lz4_small_header(void) +{ + const struct compression_handler *lz4; + struct iostream_lz4_header lz4_input; + struct istream *file_input, *input; + + if (compression_lookup_handler("lz4", &lz4) <= 0) + return; /* not compiled in or unkown */ + + test_begin("lz4 small header"); + + memcpy(lz4_input.magic, IOSTREAM_LZ4_MAGIC, IOSTREAM_LZ4_MAGIC_LEN); + lz4_input.max_uncompressed_chunk_size[0] = 0; + lz4_input.max_uncompressed_chunk_size[1] = 1; + lz4_input.max_uncompressed_chunk_size[2] = 0; + lz4_input.max_uncompressed_chunk_size[3] = 0; + + /* truncated header */ + file_input = i_stream_create_from_data(&lz4_input, sizeof(lz4_input)-1); + input = lz4->create_istream(file_input); + test_assert(i_stream_read(input) == -1 && + input->stream_errno == EINVAL); + i_stream_unref(&input); + i_stream_unref(&file_input); + + /* partial initial header read */ + file_input = test_istream_create_data(&lz4_input, sizeof(lz4_input)); + file_input->blocking = TRUE; + + test_istream_set_max_buffer_size(file_input, sizeof(lz4_input)-1); + (void)i_stream_read(file_input); + test_istream_set_max_buffer_size(file_input, sizeof(lz4_input)); + + input = lz4->create_istream(file_input); + test_assert(i_stream_read(input) == -1 && + input->stream_errno == 0); + i_stream_unref(&input); + i_stream_unref(&file_input); + + test_end(); +} + +static void test_uncompress_file(const char *path) +{ + const struct compression_handler *handler; + struct istream *input, *file_input; + const unsigned char *data; + size_t size; + int ret; + + ret = compression_lookup_handler_from_ext(path, &handler); + if (ret < 0) + i_fatal("Can't detect compression algorithm from path %s", path); + else if (ret == 0) + i_fatal("Support not compiled in for %s", handler->name); + + file_input = i_stream_create_file(path, IO_BLOCK_SIZE); + input = handler->create_istream(file_input); + while (i_stream_read_more(input, &data, &size) > 0) { + if (write(STDOUT_FILENO, data, size) < 0) + break; + i_stream_skip(input, size); + } + i_stream_destroy(&input); +} + +static void test_compress_file(const char *in_path, const char *out_path) +{ + const struct compression_handler *handler; + struct istream *input, *file_input; + struct ostream *output, *file_output; + int fd_in, fd_out; + struct sha1_ctxt sha1; + unsigned char output_sha1[SHA1_RESULTLEN], input_sha1[SHA1_RESULTLEN]; + const unsigned char *data; + size_t size; + int ret; + + ret = compression_lookup_handler_from_ext(out_path, &handler); + if (ret < 0) + i_fatal("Can't detect compression algorithm from path %s", out_path); + if (ret == 0) + i_fatal("Support not compiled in for %s", handler->name); + + /* write the compressed output file */ + fd_in = open(in_path, O_RDONLY); + if (fd_in == -1) + i_fatal("open(%s) failed: %m", in_path); + fd_out = open(out_path, O_TRUNC | O_CREAT | O_RDWR, 0600); + if (fd_out == -1) + i_fatal("creat(%s) failed: %m", out_path); + + sha1_init(&sha1); + file_output = o_stream_create_fd_file(fd_out, 0, FALSE); + output = handler->create_ostream(file_output, 1); + input = i_stream_create_fd_autoclose(&fd_in, IO_BLOCK_SIZE); + while (i_stream_read_more(input, &data, &size) > 0) { + sha1_loop(&sha1, data, size); + o_stream_nsend(output, data, size); + i_stream_skip(input, size); + } + if (o_stream_finish(output) < 0) { + i_fatal("write(%s) failed: %s", + out_path, o_stream_get_error(output)); + } + i_stream_destroy(&input); + o_stream_destroy(&output); + o_stream_destroy(&file_output); + sha1_result(&sha1, output_sha1); + + /* verify that we can read the compressed file */ + sha1_init(&sha1); + file_input = i_stream_create_fd(fd_out, IO_BLOCK_SIZE); + input = handler->create_istream(file_input); + while (i_stream_read_more(input, &data, &size) > 0) { + sha1_loop(&sha1, data, size); + i_stream_skip(input, size); + } + i_stream_destroy(&input); + i_stream_destroy(&file_input); + sha1_result(&sha1, input_sha1); + + if (memcmp(input_sha1, output_sha1, sizeof(input_sha1)) != 0) + i_fatal("Decompression couldn't get the original input"); + i_close_fd(&fd_out); +} + +static void test_compression_ext(void) +{ + const struct compression_handler *handler; + test_begin("compression handler by extension"); + + for (unsigned int i = 0; compression_handlers[i].name != NULL; i++) { + if (compression_handlers[i].ext != NULL) { + const char *path = + t_strconcat("file", compression_handlers[i].ext, NULL); + int ret = compression_lookup_handler_from_ext(path, &handler); + test_assert(ret == 0 || handler == &compression_handlers[i]); + } + } + + test_end(); +} + +int main(int argc, char *argv[]) +{ + static void (*const test_functions[])(void) = { + test_compression, + test_istream_decompression_try, + test_gz_concat, + test_gz_no_concat, + test_gz_header, + test_gz_large_header, + test_lz4_small_header, + test_compression_ext, + NULL + }; + if (argc == 2) { + test_uncompress_file(argv[1]); + return 0; + } + if (argc == 3) { + test_compress_file(argv[1], argv[2]); + return 0; + } + return test_run(test_functions); +} |