summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 18:35:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 18:35:28 +0000
commitea314d2f45c40a006c0104157013ab4b857f665f (patch)
tree3ef2971cb3675c318b8d9effd987854ad3f6d3e8 /lib
parentInitial commit. (diff)
downloaddpkg-ea314d2f45c40a006c0104157013ab4b857f665f.tar.xz
dpkg-ea314d2f45c40a006c0104157013ab4b857f665f.zip
Adding upstream version 1.22.4.upstream/1.22.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--lib/Makefile.am6
-rw-r--r--lib/Makefile.in712
-rw-r--r--lib/compat/Makefile.am96
-rw-r--r--lib/compat/Makefile.in948
-rw-r--r--lib/compat/alphasort.c32
-rw-r--r--lib/compat/asprintf.c38
-rw-r--r--lib/compat/compat-zlib.h41
-rw-r--r--lib/compat/compat.h218
-rw-r--r--lib/compat/empty.c3
-rw-r--r--lib/compat/getopt.c854
-rw-r--r--lib/compat/getopt.h129
-rw-r--r--lib/compat/getopt1.c170
-rw-r--r--lib/compat/getopt_int.h131
-rw-r--r--lib/compat/gettext.h307
-rw-r--r--lib/compat/obstack.c416
-rw-r--r--lib/compat/obstack.h509
-rw-r--r--lib/compat/scandir.c99
-rw-r--r--lib/compat/snprintf.c39
-rw-r--r--lib/compat/strchrnul.c37
-rw-r--r--lib/compat/strerror.c48
-rw-r--r--lib/compat/strndup.c42
-rw-r--r--lib/compat/strnlen.c33
-rw-r--r--lib/compat/strsignal.c76
-rw-r--r--lib/compat/unsetenv.c39
-rw-r--r--lib/compat/vasprintf.c59
-rw-r--r--lib/compat/vsnprintf.c81
-rw-r--r--lib/dpkg/Makefile.am296
-rw-r--r--lib/dpkg/Makefile.in1991
-rw-r--r--lib/dpkg/ar.c234
-rw-r--r--lib/dpkg/ar.h102
-rw-r--r--lib/dpkg/arch.c342
-rw-r--r--lib/dpkg/arch.h73
-rw-r--r--lib/dpkg/atomic-file.c132
-rw-r--r--lib/dpkg/atomic-file.h62
-rw-r--r--lib/dpkg/buffer.c293
-rw-r--r--lib/dpkg/buffer.h104
-rw-r--r--lib/dpkg/c-ctype.c186
-rw-r--r--lib/dpkg/c-ctype.h131
-rw-r--r--lib/dpkg/cleanup.c69
-rw-r--r--lib/dpkg/color.c74
-rw-r--r--lib/dpkg/color.h87
-rw-r--r--lib/dpkg/command.c260
-rw-r--r--lib/dpkg/command.h67
-rw-r--r--lib/dpkg/compress.c1447
-rw-r--r--lib/dpkg/compress.h89
-rw-r--r--lib/dpkg/db-ctrl-access.c119
-rw-r--r--lib/dpkg/db-ctrl-format.c157
-rw-r--r--lib/dpkg/db-ctrl-upgrade.c250
-rw-r--r--lib/dpkg/db-ctrl.h56
-rw-r--r--lib/dpkg/db-fsys-digest.c151
-rw-r--r--lib/dpkg/db-fsys-divert.c132
-rw-r--r--lib/dpkg/db-fsys-files.c329
-rw-r--r--lib/dpkg/db-fsys-override.c268
-rw-r--r--lib/dpkg/db-fsys.h80
-rw-r--r--lib/dpkg/dbdir.c117
-rw-r--r--lib/dpkg/dbmodify.c535
-rw-r--r--lib/dpkg/deb-version.c87
-rw-r--r--lib/dpkg/deb-version.h60
-rw-r--r--lib/dpkg/debug.c133
-rw-r--r--lib/dpkg/debug.h68
-rw-r--r--lib/dpkg/depcon.c125
-rw-r--r--lib/dpkg/dir.c213
-rw-r--r--lib/dpkg/dir.h51
-rw-r--r--lib/dpkg/dlist.h45
-rw-r--r--lib/dpkg/dpkg-db.h505
-rw-r--r--lib/dpkg/dpkg.h158
-rw-r--r--lib/dpkg/dump.c561
-rw-r--r--lib/dpkg/ehandle.c511
-rw-r--r--lib/dpkg/ehandle.h84
-rw-r--r--lib/dpkg/error.c127
-rw-r--r--lib/dpkg/error.h72
-rw-r--r--lib/dpkg/fdio.c163
-rw-r--r--lib/dpkg/fdio.h46
-rw-r--r--lib/dpkg/fields.c739
-rw-r--r--lib/dpkg/file.c257
-rw-r--r--lib/dpkg/file.h73
-rw-r--r--lib/dpkg/fsys-dir.c119
-rw-r--r--lib/dpkg/fsys-hash.c196
-rw-r--r--lib/dpkg/fsys-iter.c126
-rw-r--r--lib/dpkg/fsys.h214
-rw-r--r--lib/dpkg/glob.c50
-rw-r--r--lib/dpkg/glob.h46
-rw-r--r--lib/dpkg/i18n.c91
-rw-r--r--lib/dpkg/i18n.h59
-rw-r--r--lib/dpkg/libdpkg.map498
-rw-r--r--lib/dpkg/libdpkg.pc.in11
-rw-r--r--lib/dpkg/log.c129
-rw-r--r--lib/dpkg/macros.h243
-rw-r--r--lib/dpkg/meminfo.c168
-rw-r--r--lib/dpkg/meminfo.h55
-rw-r--r--lib/dpkg/mustlib.c148
-rw-r--r--lib/dpkg/namevalue.c40
-rw-r--r--lib/dpkg/namevalue.h51
-rw-r--r--lib/dpkg/nfmalloc.c81
-rw-r--r--lib/dpkg/options-dirs.c61
-rw-r--r--lib/dpkg/options-parsers.c53
-rw-r--r--lib/dpkg/options.c326
-rw-r--r--lib/dpkg/options.h98
-rw-r--r--lib/dpkg/pager.c145
-rw-r--r--lib/dpkg/pager.h54
-rw-r--r--lib/dpkg/parse.c926
-rw-r--r--lib/dpkg/parsedump.h179
-rw-r--r--lib/dpkg/parsehelp.c343
-rw-r--r--lib/dpkg/path-remove.c158
-rw-r--r--lib/dpkg/path.c170
-rw-r--r--lib/dpkg/path.h55
-rw-r--r--lib/dpkg/perf.h77
-rw-r--r--lib/dpkg/pkg-array.c129
-rw-r--r--lib/dpkg/pkg-array.h59
-rw-r--r--lib/dpkg/pkg-files.c95
-rw-r--r--lib/dpkg/pkg-files.h46
-rw-r--r--lib/dpkg/pkg-format.c475
-rw-r--r--lib/dpkg/pkg-format.h53
-rw-r--r--lib/dpkg/pkg-hash.c409
-rw-r--r--lib/dpkg/pkg-list.c78
-rw-r--r--lib/dpkg/pkg-list.h47
-rw-r--r--lib/dpkg/pkg-namevalue.c77
-rw-r--r--lib/dpkg/pkg-queue.c126
-rw-r--r--lib/dpkg/pkg-queue.h64
-rw-r--r--lib/dpkg/pkg-show.c445
-rw-r--r--lib/dpkg/pkg-show.h49
-rw-r--r--lib/dpkg/pkg-spec.c303
-rw-r--r--lib/dpkg/pkg-spec.h91
-rw-r--r--lib/dpkg/pkg.c217
-rw-r--r--lib/dpkg/pkg.h50
-rw-r--r--lib/dpkg/progname.c89
-rw-r--r--lib/dpkg/progname.h41
-rw-r--r--lib/dpkg/program.c59
-rw-r--r--lib/dpkg/program.h41
-rw-r--r--lib/dpkg/progress.c77
-rw-r--r--lib/dpkg/progress.h54
-rw-r--r--lib/dpkg/report.c132
-rw-r--r--lib/dpkg/report.h58
-rw-r--r--lib/dpkg/strhash.c50
-rw-r--r--lib/dpkg/string.c196
-rw-r--r--lib/dpkg/string.h78
-rw-r--r--lib/dpkg/strwide.c141
-rw-r--r--lib/dpkg/subproc.c195
-rw-r--r--lib/dpkg/subproc.h62
-rw-r--r--lib/dpkg/t/b-fsys-hash.c81
-rw-r--r--lib/dpkg/t/b-pkg-hash.c65
-rw-r--r--lib/dpkg/t/c-tarextract.c154
-rw-r--r--lib/dpkg/t/c-treewalk.c132
-rw-r--r--lib/dpkg/t/c-trigdeferred.c96
-rwxr-xr-xlib/dpkg/t/data/command/path-a/cmd1
-rwxr-xr-xlib/dpkg/t/data/command/path-a/cmd-a1
-rwxr-xr-xlib/dpkg/t/data/command/path-b/cmd1
-rwxr-xr-xlib/dpkg/t/data/command/path-b/cmd-b1
-rw-r--r--lib/dpkg/t/data/command/path-noexec/cmd1
-rw-r--r--lib/dpkg/t/data/command/path-noexec/cmd-noexec1
-rw-r--r--lib/dpkg/t/data/meminfo-no-data0
-rw-r--r--lib/dpkg/t/data/meminfo-no-info52
-rw-r--r--lib/dpkg/t/data/meminfo-no-unit53
-rw-r--r--lib/dpkg/t/data/meminfo-ok53
-rw-r--r--lib/dpkg/t/t-ar.c61
-rw-r--r--lib/dpkg/t/t-arch.c227
-rw-r--r--lib/dpkg/t/t-buffer.c82
-rw-r--r--lib/dpkg/t/t-c-ctype.c106
-rw-r--r--lib/dpkg/t/t-command.c311
-rw-r--r--lib/dpkg/t/t-deb-version.c90
-rw-r--r--lib/dpkg/t/t-ehandle.c128
-rw-r--r--lib/dpkg/t/t-error.c87
-rw-r--r--lib/dpkg/t/t-file.c106
-rw-r--r--lib/dpkg/t/t-fsys-dir.c91
-rw-r--r--lib/dpkg/t/t-fsys-hash.c108
-rw-r--r--lib/dpkg/t/t-headers-cpp.cc82
-rw-r--r--lib/dpkg/t/t-macros.c45
-rw-r--r--lib/dpkg/t/t-meminfo.c75
-rw-r--r--lib/dpkg/t/t-mod-db.c57
-rw-r--r--lib/dpkg/t/t-namevalue.c48
-rw-r--r--lib/dpkg/t/t-pager.c77
-rw-r--r--lib/dpkg/t/t-path.c181
-rw-r--r--lib/dpkg/t/t-pkg-format.c142
-rw-r--r--lib/dpkg/t/t-pkg-hash.c179
-rw-r--r--lib/dpkg/t/t-pkg-list.c90
-rw-r--r--lib/dpkg/t/t-pkg-queue.c116
-rw-r--r--lib/dpkg/t/t-pkg-show.c70
-rw-r--r--lib/dpkg/t/t-pkginfo.c155
-rw-r--r--lib/dpkg/t/t-progname.c53
-rw-r--r--lib/dpkg/t/t-string.c281
-rw-r--r--lib/dpkg/t/t-subproc.c100
-rw-r--r--lib/dpkg/t/t-tar.c148
-rwxr-xr-xlib/dpkg/t/t-tarextract.t159
-rw-r--r--lib/dpkg/t/t-test-skip.c31
-rw-r--r--lib/dpkg/t/t-test.c66
-rwxr-xr-xlib/dpkg/t/t-treewalk.t154
-rwxr-xr-xlib/dpkg/t/t-trigdeferred.t116
-rw-r--r--lib/dpkg/t/t-trigger.c49
-rw-r--r--lib/dpkg/t/t-varbuf.c560
-rw-r--r--lib/dpkg/t/t-version.c308
-rw-r--r--lib/dpkg/tarfn.c605
-rw-r--r--lib/dpkg/tarfn.h134
-rw-r--r--lib/dpkg/test.h190
-rw-r--r--lib/dpkg/treewalk.c542
-rw-r--r--lib/dpkg/treewalk.h94
-rw-r--r--lib/dpkg/trigdeferred.c285
-rw-r--r--lib/dpkg/trigdeferred.h70
-rw-r--r--lib/dpkg/triglib.c863
-rw-r--r--lib/dpkg/triglib.h129
-rw-r--r--lib/dpkg/trigname.c42
-rw-r--r--lib/dpkg/trignote.c133
-rw-r--r--lib/dpkg/utils.c58
-rw-r--r--lib/dpkg/varbuf.c276
-rw-r--r--lib/dpkg/varbuf.h189
-rw-r--r--lib/dpkg/version.c198
-rw-r--r--lib/dpkg/version.h91
206 files changed, 35860 insertions, 0 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am
new file mode 100644
index 0000000..c09fc7a
--- /dev/null
+++ b/lib/Makefile.am
@@ -0,0 +1,6 @@
+## Process this file with automake to produce Makefile.in
+
+SUBDIRS = \
+ compat \
+ dpkg \
+ # EOL
diff --git a/lib/Makefile.in b/lib/Makefile.in
new file mode 100644
index 0000000..e0644ac
--- /dev/null
+++ b/lib/Makefile.in
@@ -0,0 +1,712 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 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@
+subdir = lib
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dpkg-arch.m4 \
+ $(top_srcdir)/m4/dpkg-build.m4 \
+ $(top_srcdir)/m4/dpkg-compiler.m4 \
+ $(top_srcdir)/m4/dpkg-coverage.m4 \
+ $(top_srcdir)/m4/dpkg-funcs.m4 \
+ $(top_srcdir)/m4/dpkg-headers.m4 $(top_srcdir)/m4/dpkg-libs.m4 \
+ $(top_srcdir)/m4/dpkg-linker.m4 $(top_srcdir)/m4/dpkg-progs.m4 \
+ $(top_srcdir)/m4/dpkg-types.m4 \
+ $(top_srcdir)/m4/dpkg-unicode.m4 $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/host-cpu-c-abi.m4 $(top_srcdir)/m4/iconv.m4 \
+ $(top_srcdir)/m4/intlmacosx.m4 $(top_srcdir)/m4/lib-ld.m4 \
+ $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \
+ $(top_srcdir)/m4/libtool.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/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+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 =
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+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)`
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOM4TE = @AUTOM4TE@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BUILD_DEVEL_DOCS = @BUILD_DEVEL_DOCS@
+BZ2_LIBS = @BZ2_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CURSES_LIBS = @CURSES_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEB_DEFAULT_COMPRESSOR = @DEB_DEFAULT_COMPRESSOR@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOXYGEN = @DOXYGEN@
+DPKG_DEFAULT_PAGER = @DPKG_DEFAULT_PAGER@
+DPKG_DEFAULT_SHELL = @DPKG_DEFAULT_SHELL@
+DPKG_PAGER = @DPKG_PAGER@
+DPKG_SHELL = @DPKG_SHELL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GCOV = @GCOV@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_DOT = @HAVE_DOT@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+KVM_LIBS = @KVM_LIBS@
+LCOV = @LCOV@
+LCOV_GENHTML = @LCOV_GENHTML@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LZMA_LIBS = @LZMA_LIBS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MD_LIBS = @MD_LIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGMERGE = @MSGMERGE@
+MSGMERGE_FOR_MSGFMT_OPTION = @MSGMERGE_FOR_MSGFMT_OPTION@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_BUG_WEB = @PACKAGE_BUG_WEB@
+PACKAGE_COPYRIGHT_HOLDER = @PACKAGE_COPYRIGHT_HOLDER@
+PACKAGE_CPAN_NAME = @PACKAGE_CPAN_NAME@
+PACKAGE_DIST_IS_RELEASE = @PACKAGE_DIST_IS_RELEASE@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_RELEASE_DATE = @PACKAGE_RELEASE_DATE@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VCS_TYPE = @PACKAGE_VCS_TYPE@
+PACKAGE_VCS_URL = @PACKAGE_VCS_URL@
+PACKAGE_VCS_WEB = @PACKAGE_VCS_WEB@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATCH = @PATCH@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PERL = @PERL@
+PERL_COVER = @PERL_COVER@
+PERL_COVERAGE = @PERL_COVERAGE@
+PERL_LIBDIR = @PERL_LIBDIR@
+PERL_MIN_VERSION = @PERL_MIN_VERSION@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PO4A = @PO4A@
+POD2MAN = @POD2MAN@
+POSUB = @POSUB@
+PS_LIBS = @PS_LIBS@
+RANLIB = @RANLIB@
+RT_LIBS = @RT_LIBS@
+SED = @SED@
+SELINUX_CFLAGS = @SELINUX_CFLAGS@
+SELINUX_LIBS = @SELINUX_LIBS@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SOCKET_LIBS = @SOCKET_LIBS@
+STRIP = @STRIP@
+TAR = @TAR@
+USE_NLS = @USE_NLS@
+USE_PO4A = @USE_PO4A@
+USE_UNICODE = @USE_UNICODE@
+VERSION = @VERSION@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+ZSTD_LIBS = @ZSTD_LIBS@
+Z_LIBS = @Z_LIBS@
+Z_NG_LIBS = @Z_NG_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@
+admindir = @admindir@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+backupsdir = @backupsdir@
+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@
+devlibdir = @devlibdir@
+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@
+logdir = @logdir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgconfdir = @pkgconfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+zshcompletionsdir = @zshcompletionsdir@
+SUBDIRS = \
+ compat \
+ dpkg \
+ # EOL
+
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(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 lib/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign lib/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: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(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-recursive
+
+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-recursive
+
+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
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+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-recursive
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-am clean clean-generic clean-libtool cscopelist-am ctags \
+ ctags-am distclean 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-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \
+ ps ps-am tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# 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/lib/compat/Makefile.am b/lib/compat/Makefile.am
new file mode 100644
index 0000000..58a2524
--- /dev/null
+++ b/lib/compat/Makefile.am
@@ -0,0 +1,96 @@
+## Process this file with automake to produce Makefile.in
+
+AM_CPPFLAGS = \
+ -idirafter $(top_srcdir)/lib/compat \
+ -I$(top_builddir) \
+ # EOL
+
+
+noinst_LTLIBRARIES = \
+ libcompat-test.la \
+ libcompat.la \
+ # EOL
+
+libcompat_test_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -DTEST_LIBCOMPAT=1 \
+ # EOL
+libcompat_test_la_SOURCES = \
+ compat.h \
+ strchrnul.c \
+ strnlen.c \
+ strndup.c \
+ strsignal.c \
+ snprintf.c vsnprintf.c \
+ asprintf.c vasprintf.c \
+ alphasort.c \
+ scandir.c \
+ unsetenv.c \
+ # EOL
+
+if HAVE_SYS_ERRLIST
+libcompat_test_la_SOURCES += strerror.c
+endif
+
+libcompat_la_SOURCES = \
+ empty.c \
+ compat.h \
+ compat-zlib.h \
+ gettext.h \
+ # EOL
+
+if !HAVE_GETOPT
+libcompat_la_SOURCES += getopt.c getopt.h getopt_int.h
+else
+if !HAVE_GETOPT_LONG
+libcompat_la_SOURCES += getopt.c getopt.h getopt_int.h
+endif
+endif
+
+if !HAVE_GETOPT_LONG
+libcompat_la_SOURCES += getopt1.c
+endif
+
+if !HAVE_OBSTACK_FREE
+libcompat_la_SOURCES += obstack.c obstack.h
+endif
+
+if !HAVE_STRNLEN
+libcompat_la_SOURCES += strnlen.c
+endif
+
+if !HAVE_STRCHRNUL
+libcompat_la_SOURCES += strchrnul.c
+endif
+
+if !HAVE_STRNDUP
+libcompat_la_SOURCES += strndup.c
+endif
+
+if !HAVE_STRERROR
+libcompat_la_SOURCES += strerror.c
+endif
+
+if !HAVE_STRSIGNAL
+libcompat_la_SOURCES += strsignal.c
+endif
+
+if !HAVE_C99_SNPRINTF
+libcompat_la_SOURCES += snprintf.c vsnprintf.c
+endif
+
+if !HAVE_ASPRINTF
+libcompat_la_SOURCES += asprintf.c vasprintf.c
+endif
+
+if !HAVE_ALPHASORT
+libcompat_la_SOURCES += alphasort.c
+endif
+
+if !HAVE_SCANDIR
+libcompat_la_SOURCES += scandir.c
+endif
+
+if !HAVE_UNSETENV
+libcompat_la_SOURCES += unsetenv.c
+endif
diff --git a/lib/compat/Makefile.in b/lib/compat/Makefile.in
new file mode 100644
index 0000000..05e090b
--- /dev/null
+++ b/lib/compat/Makefile.in
@@ -0,0 +1,948 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 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@
+@HAVE_SYS_ERRLIST_TRUE@am__append_1 = strerror.c
+@HAVE_GETOPT_FALSE@am__append_2 = getopt.c getopt.h getopt_int.h
+@HAVE_GETOPT_LONG_FALSE@@HAVE_GETOPT_TRUE@am__append_3 = getopt.c getopt.h getopt_int.h
+@HAVE_GETOPT_LONG_FALSE@am__append_4 = getopt1.c
+@HAVE_OBSTACK_FREE_FALSE@am__append_5 = obstack.c obstack.h
+@HAVE_STRNLEN_FALSE@am__append_6 = strnlen.c
+@HAVE_STRCHRNUL_FALSE@am__append_7 = strchrnul.c
+@HAVE_STRNDUP_FALSE@am__append_8 = strndup.c
+@HAVE_STRERROR_FALSE@am__append_9 = strerror.c
+@HAVE_STRSIGNAL_FALSE@am__append_10 = strsignal.c
+@HAVE_C99_SNPRINTF_FALSE@am__append_11 = snprintf.c vsnprintf.c
+@HAVE_ASPRINTF_FALSE@am__append_12 = asprintf.c vasprintf.c
+@HAVE_ALPHASORT_FALSE@am__append_13 = alphasort.c
+@HAVE_SCANDIR_FALSE@am__append_14 = scandir.c
+@HAVE_UNSETENV_FALSE@am__append_15 = unsetenv.c
+subdir = lib/compat
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dpkg-arch.m4 \
+ $(top_srcdir)/m4/dpkg-build.m4 \
+ $(top_srcdir)/m4/dpkg-compiler.m4 \
+ $(top_srcdir)/m4/dpkg-coverage.m4 \
+ $(top_srcdir)/m4/dpkg-funcs.m4 \
+ $(top_srcdir)/m4/dpkg-headers.m4 $(top_srcdir)/m4/dpkg-libs.m4 \
+ $(top_srcdir)/m4/dpkg-linker.m4 $(top_srcdir)/m4/dpkg-progs.m4 \
+ $(top_srcdir)/m4/dpkg-types.m4 \
+ $(top_srcdir)/m4/dpkg-unicode.m4 $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/host-cpu-c-abi.m4 $(top_srcdir)/m4/iconv.m4 \
+ $(top_srcdir)/m4/intlmacosx.m4 $(top_srcdir)/m4/lib-ld.m4 \
+ $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \
+ $(top_srcdir)/m4/libtool.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/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libcompat_test_la_LIBADD =
+am__libcompat_test_la_SOURCES_DIST = compat.h strchrnul.c strnlen.c \
+ strndup.c strsignal.c snprintf.c vsnprintf.c asprintf.c \
+ vasprintf.c alphasort.c scandir.c unsetenv.c strerror.c
+@HAVE_SYS_ERRLIST_TRUE@am__objects_1 = libcompat_test_la-strerror.lo
+am_libcompat_test_la_OBJECTS = libcompat_test_la-strchrnul.lo \
+ libcompat_test_la-strnlen.lo libcompat_test_la-strndup.lo \
+ libcompat_test_la-strsignal.lo libcompat_test_la-snprintf.lo \
+ libcompat_test_la-vsnprintf.lo libcompat_test_la-asprintf.lo \
+ libcompat_test_la-vasprintf.lo libcompat_test_la-alphasort.lo \
+ libcompat_test_la-scandir.lo libcompat_test_la-unsetenv.lo \
+ $(am__objects_1)
+libcompat_test_la_OBJECTS = $(am_libcompat_test_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 =
+libcompat_la_LIBADD =
+am__libcompat_la_SOURCES_DIST = empty.c compat.h compat-zlib.h \
+ gettext.h getopt.c getopt.h getopt_int.h getopt1.c obstack.c \
+ obstack.h strnlen.c strchrnul.c strndup.c strerror.c \
+ strsignal.c snprintf.c vsnprintf.c asprintf.c vasprintf.c \
+ alphasort.c scandir.c unsetenv.c
+@HAVE_GETOPT_FALSE@am__objects_2 = getopt.lo
+@HAVE_GETOPT_LONG_FALSE@@HAVE_GETOPT_TRUE@am__objects_3 = getopt.lo
+@HAVE_GETOPT_LONG_FALSE@am__objects_4 = getopt1.lo
+@HAVE_OBSTACK_FREE_FALSE@am__objects_5 = obstack.lo
+@HAVE_STRNLEN_FALSE@am__objects_6 = strnlen.lo
+@HAVE_STRCHRNUL_FALSE@am__objects_7 = strchrnul.lo
+@HAVE_STRNDUP_FALSE@am__objects_8 = strndup.lo
+@HAVE_STRERROR_FALSE@am__objects_9 = strerror.lo
+@HAVE_STRSIGNAL_FALSE@am__objects_10 = strsignal.lo
+@HAVE_C99_SNPRINTF_FALSE@am__objects_11 = snprintf.lo vsnprintf.lo
+@HAVE_ASPRINTF_FALSE@am__objects_12 = asprintf.lo vasprintf.lo
+@HAVE_ALPHASORT_FALSE@am__objects_13 = alphasort.lo
+@HAVE_SCANDIR_FALSE@am__objects_14 = scandir.lo
+@HAVE_UNSETENV_FALSE@am__objects_15 = unsetenv.lo
+am_libcompat_la_OBJECTS = empty.lo $(am__objects_2) $(am__objects_3) \
+ $(am__objects_4) $(am__objects_5) $(am__objects_6) \
+ $(am__objects_7) $(am__objects_8) $(am__objects_9) \
+ $(am__objects_10) $(am__objects_11) $(am__objects_12) \
+ $(am__objects_13) $(am__objects_14) $(am__objects_15)
+libcompat_la_OBJECTS = $(am_libcompat_la_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 =
+depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/alphasort.Plo \
+ ./$(DEPDIR)/asprintf.Plo ./$(DEPDIR)/empty.Plo \
+ ./$(DEPDIR)/getopt.Plo ./$(DEPDIR)/getopt1.Plo \
+ ./$(DEPDIR)/libcompat_test_la-alphasort.Plo \
+ ./$(DEPDIR)/libcompat_test_la-asprintf.Plo \
+ ./$(DEPDIR)/libcompat_test_la-scandir.Plo \
+ ./$(DEPDIR)/libcompat_test_la-snprintf.Plo \
+ ./$(DEPDIR)/libcompat_test_la-strchrnul.Plo \
+ ./$(DEPDIR)/libcompat_test_la-strerror.Plo \
+ ./$(DEPDIR)/libcompat_test_la-strndup.Plo \
+ ./$(DEPDIR)/libcompat_test_la-strnlen.Plo \
+ ./$(DEPDIR)/libcompat_test_la-strsignal.Plo \
+ ./$(DEPDIR)/libcompat_test_la-unsetenv.Plo \
+ ./$(DEPDIR)/libcompat_test_la-vasprintf.Plo \
+ ./$(DEPDIR)/libcompat_test_la-vsnprintf.Plo \
+ ./$(DEPDIR)/obstack.Plo ./$(DEPDIR)/scandir.Plo \
+ ./$(DEPDIR)/snprintf.Plo ./$(DEPDIR)/strchrnul.Plo \
+ ./$(DEPDIR)/strerror.Plo ./$(DEPDIR)/strndup.Plo \
+ ./$(DEPDIR)/strnlen.Plo ./$(DEPDIR)/strsignal.Plo \
+ ./$(DEPDIR)/unsetenv.Plo ./$(DEPDIR)/vasprintf.Plo \
+ ./$(DEPDIR)/vsnprintf.Plo
+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 = $(libcompat_test_la_SOURCES) $(libcompat_la_SOURCES)
+DIST_SOURCES = $(am__libcompat_test_la_SOURCES_DIST) \
+ $(am__libcompat_la_SOURCES_DIST)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+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)`
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+ $(top_srcdir)/build-aux/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOM4TE = @AUTOM4TE@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BUILD_DEVEL_DOCS = @BUILD_DEVEL_DOCS@
+BZ2_LIBS = @BZ2_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CURSES_LIBS = @CURSES_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEB_DEFAULT_COMPRESSOR = @DEB_DEFAULT_COMPRESSOR@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOXYGEN = @DOXYGEN@
+DPKG_DEFAULT_PAGER = @DPKG_DEFAULT_PAGER@
+DPKG_DEFAULT_SHELL = @DPKG_DEFAULT_SHELL@
+DPKG_PAGER = @DPKG_PAGER@
+DPKG_SHELL = @DPKG_SHELL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GCOV = @GCOV@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_DOT = @HAVE_DOT@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+KVM_LIBS = @KVM_LIBS@
+LCOV = @LCOV@
+LCOV_GENHTML = @LCOV_GENHTML@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LZMA_LIBS = @LZMA_LIBS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MD_LIBS = @MD_LIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGMERGE = @MSGMERGE@
+MSGMERGE_FOR_MSGFMT_OPTION = @MSGMERGE_FOR_MSGFMT_OPTION@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_BUG_WEB = @PACKAGE_BUG_WEB@
+PACKAGE_COPYRIGHT_HOLDER = @PACKAGE_COPYRIGHT_HOLDER@
+PACKAGE_CPAN_NAME = @PACKAGE_CPAN_NAME@
+PACKAGE_DIST_IS_RELEASE = @PACKAGE_DIST_IS_RELEASE@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_RELEASE_DATE = @PACKAGE_RELEASE_DATE@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VCS_TYPE = @PACKAGE_VCS_TYPE@
+PACKAGE_VCS_URL = @PACKAGE_VCS_URL@
+PACKAGE_VCS_WEB = @PACKAGE_VCS_WEB@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATCH = @PATCH@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PERL = @PERL@
+PERL_COVER = @PERL_COVER@
+PERL_COVERAGE = @PERL_COVERAGE@
+PERL_LIBDIR = @PERL_LIBDIR@
+PERL_MIN_VERSION = @PERL_MIN_VERSION@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PO4A = @PO4A@
+POD2MAN = @POD2MAN@
+POSUB = @POSUB@
+PS_LIBS = @PS_LIBS@
+RANLIB = @RANLIB@
+RT_LIBS = @RT_LIBS@
+SED = @SED@
+SELINUX_CFLAGS = @SELINUX_CFLAGS@
+SELINUX_LIBS = @SELINUX_LIBS@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SOCKET_LIBS = @SOCKET_LIBS@
+STRIP = @STRIP@
+TAR = @TAR@
+USE_NLS = @USE_NLS@
+USE_PO4A = @USE_PO4A@
+USE_UNICODE = @USE_UNICODE@
+VERSION = @VERSION@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+ZSTD_LIBS = @ZSTD_LIBS@
+Z_LIBS = @Z_LIBS@
+Z_NG_LIBS = @Z_NG_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@
+admindir = @admindir@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+backupsdir = @backupsdir@
+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@
+devlibdir = @devlibdir@
+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@
+logdir = @logdir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgconfdir = @pkgconfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+zshcompletionsdir = @zshcompletionsdir@
+AM_CPPFLAGS = \
+ -idirafter $(top_srcdir)/lib/compat \
+ -I$(top_builddir) \
+ # EOL
+
+noinst_LTLIBRARIES = \
+ libcompat-test.la \
+ libcompat.la \
+ # EOL
+
+libcompat_test_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -DTEST_LIBCOMPAT=1 \
+ # EOL
+
+libcompat_test_la_SOURCES = compat.h strchrnul.c strnlen.c strndup.c \
+ strsignal.c snprintf.c vsnprintf.c asprintf.c vasprintf.c \
+ alphasort.c scandir.c unsetenv.c $(am__append_1)
+libcompat_la_SOURCES = empty.c compat.h compat-zlib.h gettext.h \
+ $(am__append_2) $(am__append_3) $(am__append_4) \
+ $(am__append_5) $(am__append_6) $(am__append_7) \
+ $(am__append_8) $(am__append_9) $(am__append_10) \
+ $(am__append_11) $(am__append_12) $(am__append_13) \
+ $(am__append_14) $(am__append_15)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: $(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 lib/compat/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign lib/compat/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: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+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}; \
+ }
+
+libcompat-test.la: $(libcompat_test_la_OBJECTS) $(libcompat_test_la_DEPENDENCIES) $(EXTRA_libcompat_test_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libcompat_test_la_OBJECTS) $(libcompat_test_la_LIBADD) $(LIBS)
+
+libcompat.la: $(libcompat_la_OBJECTS) $(libcompat_la_DEPENDENCIES) $(EXTRA_libcompat_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libcompat_la_OBJECTS) $(libcompat_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/alphasort.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/asprintf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/empty.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getopt.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getopt1.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcompat_test_la-alphasort.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcompat_test_la-asprintf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcompat_test_la-scandir.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcompat_test_la-snprintf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcompat_test_la-strchrnul.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcompat_test_la-strerror.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcompat_test_la-strndup.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcompat_test_la-strnlen.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcompat_test_la-strsignal.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcompat_test_la-unsetenv.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcompat_test_la-vasprintf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcompat_test_la-vsnprintf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/obstack.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/scandir.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/snprintf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/strchrnul.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/strerror.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/strndup.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/strnlen.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/strsignal.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unsetenv.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/vasprintf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/vsnprintf.Plo@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)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.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)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.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)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.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 $@ $<
+
+libcompat_test_la-strchrnul.lo: strchrnul.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libcompat_test_la-strchrnul.lo -MD -MP -MF $(DEPDIR)/libcompat_test_la-strchrnul.Tpo -c -o libcompat_test_la-strchrnul.lo `test -f 'strchrnul.c' || echo '$(srcdir)/'`strchrnul.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcompat_test_la-strchrnul.Tpo $(DEPDIR)/libcompat_test_la-strchrnul.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='strchrnul.c' object='libcompat_test_la-strchrnul.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libcompat_test_la-strchrnul.lo `test -f 'strchrnul.c' || echo '$(srcdir)/'`strchrnul.c
+
+libcompat_test_la-strnlen.lo: strnlen.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libcompat_test_la-strnlen.lo -MD -MP -MF $(DEPDIR)/libcompat_test_la-strnlen.Tpo -c -o libcompat_test_la-strnlen.lo `test -f 'strnlen.c' || echo '$(srcdir)/'`strnlen.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcompat_test_la-strnlen.Tpo $(DEPDIR)/libcompat_test_la-strnlen.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='strnlen.c' object='libcompat_test_la-strnlen.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libcompat_test_la-strnlen.lo `test -f 'strnlen.c' || echo '$(srcdir)/'`strnlen.c
+
+libcompat_test_la-strndup.lo: strndup.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libcompat_test_la-strndup.lo -MD -MP -MF $(DEPDIR)/libcompat_test_la-strndup.Tpo -c -o libcompat_test_la-strndup.lo `test -f 'strndup.c' || echo '$(srcdir)/'`strndup.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcompat_test_la-strndup.Tpo $(DEPDIR)/libcompat_test_la-strndup.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='strndup.c' object='libcompat_test_la-strndup.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libcompat_test_la-strndup.lo `test -f 'strndup.c' || echo '$(srcdir)/'`strndup.c
+
+libcompat_test_la-strsignal.lo: strsignal.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libcompat_test_la-strsignal.lo -MD -MP -MF $(DEPDIR)/libcompat_test_la-strsignal.Tpo -c -o libcompat_test_la-strsignal.lo `test -f 'strsignal.c' || echo '$(srcdir)/'`strsignal.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcompat_test_la-strsignal.Tpo $(DEPDIR)/libcompat_test_la-strsignal.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='strsignal.c' object='libcompat_test_la-strsignal.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libcompat_test_la-strsignal.lo `test -f 'strsignal.c' || echo '$(srcdir)/'`strsignal.c
+
+libcompat_test_la-snprintf.lo: snprintf.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libcompat_test_la-snprintf.lo -MD -MP -MF $(DEPDIR)/libcompat_test_la-snprintf.Tpo -c -o libcompat_test_la-snprintf.lo `test -f 'snprintf.c' || echo '$(srcdir)/'`snprintf.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcompat_test_la-snprintf.Tpo $(DEPDIR)/libcompat_test_la-snprintf.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='snprintf.c' object='libcompat_test_la-snprintf.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libcompat_test_la-snprintf.lo `test -f 'snprintf.c' || echo '$(srcdir)/'`snprintf.c
+
+libcompat_test_la-vsnprintf.lo: vsnprintf.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libcompat_test_la-vsnprintf.lo -MD -MP -MF $(DEPDIR)/libcompat_test_la-vsnprintf.Tpo -c -o libcompat_test_la-vsnprintf.lo `test -f 'vsnprintf.c' || echo '$(srcdir)/'`vsnprintf.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcompat_test_la-vsnprintf.Tpo $(DEPDIR)/libcompat_test_la-vsnprintf.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='vsnprintf.c' object='libcompat_test_la-vsnprintf.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libcompat_test_la-vsnprintf.lo `test -f 'vsnprintf.c' || echo '$(srcdir)/'`vsnprintf.c
+
+libcompat_test_la-asprintf.lo: asprintf.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libcompat_test_la-asprintf.lo -MD -MP -MF $(DEPDIR)/libcompat_test_la-asprintf.Tpo -c -o libcompat_test_la-asprintf.lo `test -f 'asprintf.c' || echo '$(srcdir)/'`asprintf.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcompat_test_la-asprintf.Tpo $(DEPDIR)/libcompat_test_la-asprintf.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='asprintf.c' object='libcompat_test_la-asprintf.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libcompat_test_la-asprintf.lo `test -f 'asprintf.c' || echo '$(srcdir)/'`asprintf.c
+
+libcompat_test_la-vasprintf.lo: vasprintf.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libcompat_test_la-vasprintf.lo -MD -MP -MF $(DEPDIR)/libcompat_test_la-vasprintf.Tpo -c -o libcompat_test_la-vasprintf.lo `test -f 'vasprintf.c' || echo '$(srcdir)/'`vasprintf.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcompat_test_la-vasprintf.Tpo $(DEPDIR)/libcompat_test_la-vasprintf.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='vasprintf.c' object='libcompat_test_la-vasprintf.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libcompat_test_la-vasprintf.lo `test -f 'vasprintf.c' || echo '$(srcdir)/'`vasprintf.c
+
+libcompat_test_la-alphasort.lo: alphasort.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libcompat_test_la-alphasort.lo -MD -MP -MF $(DEPDIR)/libcompat_test_la-alphasort.Tpo -c -o libcompat_test_la-alphasort.lo `test -f 'alphasort.c' || echo '$(srcdir)/'`alphasort.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcompat_test_la-alphasort.Tpo $(DEPDIR)/libcompat_test_la-alphasort.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='alphasort.c' object='libcompat_test_la-alphasort.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libcompat_test_la-alphasort.lo `test -f 'alphasort.c' || echo '$(srcdir)/'`alphasort.c
+
+libcompat_test_la-scandir.lo: scandir.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libcompat_test_la-scandir.lo -MD -MP -MF $(DEPDIR)/libcompat_test_la-scandir.Tpo -c -o libcompat_test_la-scandir.lo `test -f 'scandir.c' || echo '$(srcdir)/'`scandir.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcompat_test_la-scandir.Tpo $(DEPDIR)/libcompat_test_la-scandir.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='scandir.c' object='libcompat_test_la-scandir.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libcompat_test_la-scandir.lo `test -f 'scandir.c' || echo '$(srcdir)/'`scandir.c
+
+libcompat_test_la-unsetenv.lo: unsetenv.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libcompat_test_la-unsetenv.lo -MD -MP -MF $(DEPDIR)/libcompat_test_la-unsetenv.Tpo -c -o libcompat_test_la-unsetenv.lo `test -f 'unsetenv.c' || echo '$(srcdir)/'`unsetenv.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcompat_test_la-unsetenv.Tpo $(DEPDIR)/libcompat_test_la-unsetenv.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='unsetenv.c' object='libcompat_test_la-unsetenv.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libcompat_test_la-unsetenv.lo `test -f 'unsetenv.c' || echo '$(srcdir)/'`unsetenv.c
+
+libcompat_test_la-strerror.lo: strerror.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libcompat_test_la-strerror.lo -MD -MP -MF $(DEPDIR)/libcompat_test_la-strerror.Tpo -c -o libcompat_test_la-strerror.lo `test -f 'strerror.c' || echo '$(srcdir)/'`strerror.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcompat_test_la-strerror.Tpo $(DEPDIR)/libcompat_test_la-strerror.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='strerror.c' object='libcompat_test_la-strerror.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcompat_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libcompat_test_la-strerror.lo `test -f 'strerror.c' || echo '$(srcdir)/'`strerror.c
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+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
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+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 \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/alphasort.Plo
+ -rm -f ./$(DEPDIR)/asprintf.Plo
+ -rm -f ./$(DEPDIR)/empty.Plo
+ -rm -f ./$(DEPDIR)/getopt.Plo
+ -rm -f ./$(DEPDIR)/getopt1.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-alphasort.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-asprintf.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-scandir.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-snprintf.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-strchrnul.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-strerror.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-strndup.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-strnlen.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-strsignal.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-unsetenv.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-vasprintf.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-vsnprintf.Plo
+ -rm -f ./$(DEPDIR)/obstack.Plo
+ -rm -f ./$(DEPDIR)/scandir.Plo
+ -rm -f ./$(DEPDIR)/snprintf.Plo
+ -rm -f ./$(DEPDIR)/strchrnul.Plo
+ -rm -f ./$(DEPDIR)/strerror.Plo
+ -rm -f ./$(DEPDIR)/strndup.Plo
+ -rm -f ./$(DEPDIR)/strnlen.Plo
+ -rm -f ./$(DEPDIR)/strsignal.Plo
+ -rm -f ./$(DEPDIR)/unsetenv.Plo
+ -rm -f ./$(DEPDIR)/vasprintf.Plo
+ -rm -f ./$(DEPDIR)/vsnprintf.Plo
+ -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-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+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)/alphasort.Plo
+ -rm -f ./$(DEPDIR)/asprintf.Plo
+ -rm -f ./$(DEPDIR)/empty.Plo
+ -rm -f ./$(DEPDIR)/getopt.Plo
+ -rm -f ./$(DEPDIR)/getopt1.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-alphasort.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-asprintf.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-scandir.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-snprintf.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-strchrnul.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-strerror.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-strndup.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-strnlen.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-strsignal.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-unsetenv.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-vasprintf.Plo
+ -rm -f ./$(DEPDIR)/libcompat_test_la-vsnprintf.Plo
+ -rm -f ./$(DEPDIR)/obstack.Plo
+ -rm -f ./$(DEPDIR)/scandir.Plo
+ -rm -f ./$(DEPDIR)/snprintf.Plo
+ -rm -f ./$(DEPDIR)/strchrnul.Plo
+ -rm -f ./$(DEPDIR)/strerror.Plo
+ -rm -f ./$(DEPDIR)/strndup.Plo
+ -rm -f ./$(DEPDIR)/strnlen.Plo
+ -rm -f ./$(DEPDIR)/strsignal.Plo
+ -rm -f ./$(DEPDIR)/unsetenv.Plo
+ -rm -f ./$(DEPDIR)/vasprintf.Plo
+ -rm -f ./$(DEPDIR)/vsnprintf.Plo
+ -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:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ 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-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
+
+.PRECIOUS: Makefile
+
+
+# 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/lib/compat/alphasort.c b/lib/compat/alphasort.c
new file mode 100644
index 0000000..b393b87
--- /dev/null
+++ b/lib/compat/alphasort.c
@@ -0,0 +1,32 @@
+/*
+ * libcompat - system compatibility library
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <dirent.h>
+
+#include "compat.h"
+
+int
+alphasort(const void *a, const void *b)
+{
+ return strcmp((*(const struct dirent **)a)->d_name,
+ (*(const struct dirent **)b)->d_name);
+}
diff --git a/lib/compat/asprintf.c b/lib/compat/asprintf.c
new file mode 100644
index 0000000..9605faf
--- /dev/null
+++ b/lib/compat/asprintf.c
@@ -0,0 +1,38 @@
+/*
+ * libcompat - system compatibility library
+ *
+ * Copyright © 2010 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "compat.h"
+
+int
+asprintf(char **strp, char const *fmt, ...)
+{
+ va_list args;
+ int n;
+
+ va_start(args, fmt);
+ n = vasprintf(strp, fmt, args);
+ va_end(args);
+
+ return n;
+}
diff --git a/lib/compat/compat-zlib.h b/lib/compat/compat-zlib.h
new file mode 100644
index 0000000..aa15e4a
--- /dev/null
+++ b/lib/compat/compat-zlib.h
@@ -0,0 +1,41 @@
+/*
+ * libcompat - system compatibility library
+ * compat-zlib.h - zlib compatibility declarations
+ *
+ * Copyright © 2021 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPAT_ZLIB_H
+#define COMPAT_ZLIB_H
+
+#if USE_LIBZ_IMPL == USE_LIBZ_IMPL_ZLIB_NG
+#include <zlib-ng.h>
+#elif USE_LIBZ_IMPL == USE_LIBZ_IMPL_ZLIB
+#include <zlib.h>
+#endif
+
+#if USE_LIBZ_IMPL == USE_LIBZ_IMPL_ZLIB_NG
+/* Compatibility symbols for zlib-ng. */
+#define gzdopen zng_gzdopen
+#define gzopen zng_gzopen
+#define gzread zng_gzread
+#define gzwrite zng_gzwrite
+#define gzerror zng_gzerror
+#define gzclose zng_gzclose
+#define zError zng_zError
+#endif
+
+#endif /* COMPAT_ZLIB_H */
diff --git a/lib/compat/compat.h b/lib/compat/compat.h
new file mode 100644
index 0000000..861299c
--- /dev/null
+++ b/lib/compat/compat.h
@@ -0,0 +1,218 @@
+/*
+ * libcompat - system compatibility library
+ * compat.h - system compatibility declarations
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008, 2009 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPAT_H
+#define COMPAT_H
+
+#ifndef TEST_LIBCOMPAT
+#define TEST_LIBCOMPAT 0
+#endif
+
+#if TEST_LIBCOMPAT || !defined(HAVE_STRNLEN) || !defined(HAVE_STRNDUP) || \
+ !defined(HAVE_C99_SNPRINTF)
+#include <stddef.h>
+#endif
+
+#if TEST_LIBCOMPAT || !defined(HAVE_ASPRINTF) || !defined(HAVE_C99_SNPRINTF)
+#include <stdarg.h>
+#endif
+
+#if TEST_LIBCOMPAT || !defined(HAVE_VA_COPY)
+#include <string.h>
+#endif
+
+/* Language definitions. */
+
+/* Supported since gcc 5.1.0 and clang 2.9.0. For attributes that appeared
+ * before these versions, in addition we need to do version checks. */
+#ifndef __has_attribute
+#define __has_attribute(x) 0
+#endif
+
+#ifndef __has_include
+#define __has_include(x) 0
+#endif
+
+#ifdef __GNUC__
+#define LIBCOMPAT_GCC_VERSION (__GNUC__ << 8 | __GNUC_MINOR__)
+#else
+#define LIBCOMPAT_GCC_VERSION 0
+#endif
+
+#if LIBCOMPAT_GCC_VERSION >= 0x0300 || __has_attribute(__format__)
+#define LIBCOMPAT_ATTR_FMT(t, f, a) __attribute__((__format__(t, f, a)))
+#define LIBCOMPAT_ATTR_PRINTF(n) LIBCOMPAT_ATTR_FMT(__printf__, n, n + 1)
+#define LIBCOMPAT_ATTR_VPRINTF(n) LIBCOMPAT_ATTR_FMT(__printf__, n, 0)
+#else
+#define LIBCOMPAT_ATTR_FMT(t, f, a)
+#define LIBCOMPAT_ATTR_PRINTF(n)
+#define LIBCOMPAT_ATTR_VPRINTF(n)
+#endif
+
+#if LIBCOMPAT_GCC_VERSION >= 0x0300 || __has_attribute(__noreturn__)
+#define LIBCOMPAT_ATTR_NORET __attribute__((__noreturn__))
+#else
+#define LIBCOMPAT_ATTR_NORET
+#endif
+
+#if LIBCOMPAT_GCC_VERSION >= 0x0400 || __has_attribute(__sentinel__)
+#define LIBCOMPAT_ATTR_SENTINEL __attribute__((__sentinel__))
+#else
+#define LIBCOMPAT_ATTR_SENTINEL
+#endif
+
+#if __has_attribute(__enum_extensibility__)
+#define LIBCOMPAT_ATTR_ENUM_FLAGS \
+ __attribute__((__enum_extensibility__(closed),__flag_enum__))
+#else
+#define LIBCOMPAT_ATTR_ENUM_FLAGS
+#endif
+
+/* For C++, define a __func__ fallback in case it's not natively supported. */
+#if defined(__cplusplus) && __cplusplus < 201103L
+# if LIBCOMPAT_GCC_VERSION >= 0x0200
+# define __func__ __PRETTY_FUNCTION__
+# else
+# define __func__ __FUNCTION__
+# endif
+#endif
+
+#if defined(__cplusplus) && __cplusplus < 201103L
+#define nullptr 0
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef HAVE_OFFSETOF
+#define offsetof(st, m) ((size_t)&((st *)NULL)->m)
+#endif
+
+#ifndef HAVE_MAKEDEV
+#define makedev(maj, min) ((((maj) & 0xff) << 8) | ((min) & 0xff))
+#endif
+
+#ifndef HAVE_O_NOFOLLOW
+#define O_NOFOLLOW 0
+#endif
+
+#ifndef HAVE_P_TMPDIR
+#define P_tmpdir "/tmp"
+#endif
+
+/*
+ * Define WCOREDUMP if we don't have it already, coredumps won't be
+ * detected, though.
+ */
+#ifndef HAVE_WCOREDUMP
+#define WCOREDUMP(x) 0
+#endif
+
+#ifndef HAVE_VA_COPY
+#define va_copy(dest, src) memcpy(&(dest), &(src), sizeof(va_list))
+#endif
+
+#if TEST_LIBCOMPAT
+#undef snprintf
+#define snprintf test_snprintf
+#undef vsnprintf
+#define vsnprintf test_vsnprintf
+#undef asprintf
+#define asprintf test_asprintf
+#undef vasprintf
+#define vasprintf test_vasprintf
+#undef strchrnul
+#define strchrnul test_strchrnul
+#undef strndup
+#define strndup test_strndup
+#undef strnlen
+#define strnlen test_strnlen
+#undef strerror
+#define strerror test_strerror
+#undef strsignal
+#define strsignal test_strsignal
+#undef scandir
+#define scandir test_scandir
+#undef alphasort
+#define alphasort test_alphasort
+#undef unsetenv
+#define unsetenv test_unsetenv
+#endif
+
+#if !HAVE_DECL_SYS_SIGLIST
+extern const char *const sys_siglist[];
+#endif
+
+#if TEST_LIBCOMPAT || !defined(HAVE_C99_SNPRINTF)
+int snprintf(char *str, size_t n, char const *fmt, ...)
+ LIBCOMPAT_ATTR_PRINTF(3);
+int vsnprintf(char *buf, size_t maxsize, const char *fmt, va_list args)
+ LIBCOMPAT_ATTR_VPRINTF(3);
+#endif
+
+#if TEST_LIBCOMPAT || !defined(HAVE_ASPRINTF)
+int asprintf(char **str, char const *fmt, ...)
+ LIBCOMPAT_ATTR_PRINTF(2);
+int vasprintf(char **str, const char *fmt, va_list args)
+ LIBCOMPAT_ATTR_VPRINTF(2);
+#endif
+
+#if TEST_LIBCOMPAT || !defined(HAVE_STRCHRNUL)
+char *strchrnul(const char *s, int c);
+#endif
+
+#if TEST_LIBCOMPAT || !defined(HAVE_STRNLEN)
+size_t strnlen(const char *s, size_t n);
+#endif
+
+#if TEST_LIBCOMPAT || !defined(HAVE_STRNDUP)
+char *strndup(const char *s, size_t n);
+#endif
+
+#if TEST_LIBCOMPAT || !defined(HAVE_STRERROR)
+const char *strerror(int);
+#endif
+
+#if TEST_LIBCOMPAT || !defined(HAVE_STRSIGNAL)
+const char *strsignal(int);
+#endif
+
+#if TEST_LIBCOMPAT || !defined(HAVE_SCANDIR)
+struct dirent;
+int scandir(const char *dir, struct dirent ***namelist,
+ int (*filter)(const struct dirent *),
+ int (*cmp)(const void *, const void *));
+#endif
+
+#if TEST_LIBCOMPAT || !defined(HAVE_ALPHASORT)
+int alphasort(const void *a, const void *b);
+#endif
+
+#if TEST_LIBCOMPAT || !defined(HAVE_UNSETENV)
+int unsetenv(const char *x);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* COMPAT_H */
diff --git a/lib/compat/empty.c b/lib/compat/empty.c
new file mode 100644
index 0000000..544a484
--- /dev/null
+++ b/lib/compat/empty.c
@@ -0,0 +1,3 @@
+/* Some implementations of ar cannot create an empty archive. */
+extern int libdpkg_empty_dummy_symbol;
+int libdpkg_empty_dummy_symbol;
diff --git a/lib/compat/getopt.c b/lib/compat/getopt.c
new file mode 100644
index 0000000..73f05b4
--- /dev/null
+++ b/lib/compat/getopt.c
@@ -0,0 +1,854 @@
+/* Getopt for GNU.
+ NOTE: getopt is now part of the C library, so if you don't know what
+ "Keep this file name-space clean" means, talk to drepper@gnu.org
+ before changing it!
+ Copyright (C) 1987,88,89,90,91,92,93,94,95,96,98,99,2000,2001,2002,2003,2004,2006
+ Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "getopt.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef __VMS
+# include <unixlib.h>
+#endif
+
+/* XXX: Disable intl support, because we do not carry the translations anyway
+ * and this pulls indirectly libintl, which we do not want to impose. */
+#ifndef _
+#define _(msgid) (msgid)
+#endif
+
+#ifndef attribute_hidden
+# define attribute_hidden
+#endif
+
+/* Unlike standard Unix `getopt', functions like `getopt_long'
+ let the user intersperse the options with the other arguments.
+
+ As `getopt_long' works, it permutes the elements of ARGV so that,
+ when it is done, all the options precede everything else. Thus
+ all application programs are extended to handle flexible argument order.
+
+ Using `getopt' or setting the environment variable POSIXLY_CORRECT
+ disables permutation.
+ Then the application's behavior is completely standard.
+
+ GNU application programs can use a third alternative mode in which
+ they can distinguish the relative order of options and other arguments. */
+
+#include "getopt_int.h"
+
+/* For communication from `getopt' to the caller.
+ When `getopt' finds an option that takes an argument,
+ the argument value is returned here.
+ Also, when `ordering' is RETURN_IN_ORDER,
+ each non-option ARGV-element is returned here. */
+
+char *optarg;
+
+/* Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to `getopt'.
+
+ On entry to `getopt', zero means this is the first call; initialize.
+
+ When `getopt' returns -1, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, `optind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+/* 1003.2 says this must be 1 before any call. */
+int optind = 1;
+
+/* Callers store zero here to inhibit the error message
+ for unrecognized options. */
+
+int opterr = 1;
+
+/* Set to an option character which was unrecognized.
+ This must be initialized on some systems to avoid linking in the
+ system's own getopt implementation. */
+
+int optopt = '?';
+
+/* Keep a global copy of all internal members of getopt_data. */
+
+static struct _getopt_data getopt_data;
+
+
+#if defined HAVE_DECL_GETENV && !HAVE_DECL_GETENV
+extern char *getenv ();
+#endif
+
+# define SWAP_FLAGS(ch1, ch2)
+
+/* Exchange two adjacent subsequences of ARGV.
+ One subsequence is elements [first_nonopt,last_nonopt)
+ which contains all the non-options that have been skipped so far.
+ The other is elements [last_nonopt,optind), which contains all
+ the options processed since those non-options were skipped.
+
+ `first_nonopt' and `last_nonopt' are relocated so that they describe
+ the new indices of the non-options in ARGV after they are moved. */
+
+static void
+exchange (char **argv, struct _getopt_data *d)
+{
+ int bottom = d->__first_nonopt;
+ int middle = d->__last_nonopt;
+ int top = d->optind;
+ char *tem;
+
+ /* Exchange the shorter segment with the far end of the longer segment.
+ That puts the shorter segment into the right place.
+ It leaves the longer segment in the right place overall,
+ but it consists of two parts that need to be swapped next. */
+
+ while (top > middle && middle > bottom)
+ {
+ if (top - middle > middle - bottom)
+ {
+ /* Bottom segment is the short one. */
+ int len = middle - bottom;
+ int i;
+
+ /* Swap it with the top part of the top segment. */
+ for (i = 0; i < len; i++)
+ {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[top - (middle - bottom) + i];
+ argv[top - (middle - bottom) + i] = tem;
+ SWAP_FLAGS (bottom + i, top - (middle - bottom) + i);
+ }
+ /* Exclude the moved bottom segment from further swapping. */
+ top -= len;
+ }
+ else
+ {
+ /* Top segment is the short one. */
+ int len = top - middle;
+ int i;
+
+ /* Swap it with the bottom part of the bottom segment. */
+ for (i = 0; i < len; i++)
+ {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[middle + i];
+ argv[middle + i] = tem;
+ SWAP_FLAGS (bottom + i, middle + i);
+ }
+ /* Exclude the moved top segment from further swapping. */
+ bottom += len;
+ }
+ }
+
+ /* Update records for the slots the non-options now occupy. */
+
+ d->__first_nonopt += (d->optind - d->__last_nonopt);
+ d->__last_nonopt = d->optind;
+}
+
+/* Initialize the internal data when the first call is made. */
+
+static const char *
+_getopt_initialize (int argc, char **argv, const char *optstring,
+ int posixly_correct, struct _getopt_data *d)
+{
+ /* Start processing options with ARGV-element 1 (since ARGV-element 0
+ is the program name); the sequence of previously skipped
+ non-option ARGV-elements is empty. */
+
+ d->__first_nonopt = d->__last_nonopt = d->optind;
+
+ d->__nextchar = NULL;
+
+ d->__posixly_correct = posixly_correct || !!getenv ("POSIXLY_CORRECT");
+
+ /* Determine how to handle the ordering of options and nonoptions. */
+
+ if (optstring[0] == '-')
+ {
+ d->__ordering = RETURN_IN_ORDER;
+ ++optstring;
+ }
+ else if (optstring[0] == '+')
+ {
+ d->__ordering = REQUIRE_ORDER;
+ ++optstring;
+ }
+ else if (d->__posixly_correct)
+ d->__ordering = REQUIRE_ORDER;
+ else
+ d->__ordering = PERMUTE;
+
+ return optstring;
+}
+
+/* Scan elements of ARGV (whose length is ARGC) for option characters
+ given in OPTSTRING.
+
+ If an element of ARGV starts with '-', and is not exactly "-" or "--",
+ then it is an option element. The characters of this element
+ (aside from the initial '-') are option characters. If `getopt'
+ is called repeatedly, it returns successively each of the option characters
+ from each of the option elements.
+
+ If `getopt' finds another option character, it returns that character,
+ updating `optind' and `nextchar' so that the next call to `getopt' can
+ resume the scan with the following option character or ARGV-element.
+
+ If there are no more option characters, `getopt' returns -1.
+ Then `optind' is the index in ARGV of the first ARGV-element
+ that is not an option. (The ARGV-elements have been permuted
+ so that those that are not options now come last.)
+
+ OPTSTRING is a string containing the legitimate option characters.
+ If an option character is seen that is not listed in OPTSTRING,
+ return '?' after printing an error message. If you set `opterr' to
+ zero, the error message is suppressed but we still return '?'.
+
+ If a char in OPTSTRING is followed by a colon, that means it wants an arg,
+ so the following text in the same ARGV-element, or the text of the following
+ ARGV-element, is returned in `optarg'. Two colons mean an option that
+ wants an optional arg; if there is text in the current ARGV-element,
+ it is returned in `optarg', otherwise `optarg' is set to zero.
+
+ If OPTSTRING starts with `-' or `+', it requests different methods of
+ handling the non-option ARGV-elements.
+ See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above.
+
+ Long-named options begin with `--' instead of `-'.
+ Their names may be abbreviated as long as the abbreviation is unique
+ or is an exact match for some defined option. If they have an
+ argument, it follows the option name in the same ARGV-element, separated
+ from the option name by a `=', or else the in next ARGV-element.
+ When `getopt' finds a long-named option, it returns 0 if that option's
+ `flag' field is nonzero, the value of the option's `val' field
+ if the `flag' field is zero.
+
+ LONGOPTS is a vector of `struct option' terminated by an
+ element containing a name which is zero.
+
+ LONGIND returns the index in LONGOPT of the long-named option found.
+ It is only valid when a long-named option has been found by the most
+ recent call.
+
+ If LONG_ONLY is nonzero, '-' as well as '--' can introduce
+ long-named options.
+
+ If POSIXLY_CORRECT is nonzero, behave as if the POSIXLY_CORRECT
+ environment variable were set. */
+
+int
+_getopt_internal_r (int argc, char **argv, const char *optstring,
+ const struct option *longopts, int *longind,
+ int long_only, int posixly_correct, struct _getopt_data *d)
+{
+ int print_errors = d->opterr;
+ if (optstring[0] == ':')
+ print_errors = 0;
+
+ if (argc < 1)
+ return -1;
+
+ d->optarg = NULL;
+
+ if (d->optind == 0 || !d->__initialized)
+ {
+ if (d->optind == 0)
+ d->optind = 1; /* Don't scan ARGV[0], the program name. */
+ optstring = _getopt_initialize (argc, argv, optstring,
+ posixly_correct, d);
+ d->__initialized = 1;
+ }
+
+ /* Test whether ARGV[optind] points to a non-option argument.
+ Either it does not have option syntax, or there is an environment flag
+ from the shell indicating it is not an option. The later information
+ is only used when the used in the GNU libc. */
+# define NONOPTION_P (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0')
+
+ if (d->__nextchar == NULL || *d->__nextchar == '\0')
+ {
+ /* Advance to the next ARGV-element. */
+
+ /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been
+ moved back by the user (who may also have changed the arguments). */
+ if (d->__last_nonopt > d->optind)
+ d->__last_nonopt = d->optind;
+ if (d->__first_nonopt > d->optind)
+ d->__first_nonopt = d->optind;
+
+ if (d->__ordering == PERMUTE)
+ {
+ /* If we have just processed some options following some non-options,
+ exchange them so that the options come first. */
+
+ if (d->__first_nonopt != d->__last_nonopt
+ && d->__last_nonopt != d->optind)
+ exchange ((char **) argv, d);
+ else if (d->__last_nonopt != d->optind)
+ d->__first_nonopt = d->optind;
+
+ /* Skip any additional non-options
+ and extend the range of non-options previously skipped. */
+
+ while (d->optind < argc && NONOPTION_P)
+ d->optind++;
+ d->__last_nonopt = d->optind;
+ }
+
+ /* The special ARGV-element `--' means premature end of options.
+ Skip it like a null option,
+ then exchange with previous non-options as if it were an option,
+ then skip everything else like a non-option. */
+
+ if (d->optind != argc && !strcmp (argv[d->optind], "--"))
+ {
+ d->optind++;
+
+ if (d->__first_nonopt != d->__last_nonopt
+ && d->__last_nonopt != d->optind)
+ exchange ((char **) argv, d);
+ else if (d->__first_nonopt == d->__last_nonopt)
+ d->__first_nonopt = d->optind;
+ d->__last_nonopt = argc;
+
+ d->optind = argc;
+ }
+
+ /* If we have done all the ARGV-elements, stop the scan
+ and back over any non-options that we skipped and permuted. */
+
+ if (d->optind == argc)
+ {
+ /* Set the next-arg-index to point at the non-options
+ that we previously skipped, so the caller will digest them. */
+ if (d->__first_nonopt != d->__last_nonopt)
+ d->optind = d->__first_nonopt;
+ return -1;
+ }
+
+ /* If we have come to a non-option and did not permute it,
+ either stop the scan or describe it to the caller and pass it by. */
+
+ if (NONOPTION_P)
+ {
+ if (d->__ordering == REQUIRE_ORDER)
+ return -1;
+ d->optarg = argv[d->optind++];
+ return 1;
+ }
+
+ /* We have found another option-ARGV-element.
+ Skip the initial punctuation. */
+
+ d->__nextchar = (argv[d->optind] + 1
+ + (longopts != NULL && argv[d->optind][1] == '-'));
+ }
+
+ /* Decode the current option-ARGV-element. */
+
+ /* Check whether the ARGV-element is a long option.
+
+ If long_only and the ARGV-element has the form "-f", where f is
+ a valid short option, don't consider it an abbreviated form of
+ a long option that starts with f. Otherwise there would be no
+ way to give the -f short option.
+
+ On the other hand, if there's a long option "fubar" and
+ the ARGV-element is "-fu", do consider that an abbreviation of
+ the long option, just like "--fu", and not "-f" with arg "u".
+
+ This distinction seems to be the most useful approach. */
+
+ if (longopts != NULL
+ && (argv[d->optind][1] == '-'
+ || (long_only && (argv[d->optind][2]
+ || !strchr (optstring, argv[d->optind][1])))))
+ {
+ char *nameend;
+ const struct option *p;
+ const struct option *pfound = NULL;
+ int exact = 0;
+ int ambig = 0;
+ int indfound = -1;
+ int option_index;
+
+ for (nameend = d->__nextchar; *nameend && *nameend != '='; nameend++)
+ /* Do nothing. */ ;
+
+ /* Test all long options for either exact match
+ or abbreviated matches. */
+ for (p = longopts, option_index = 0; p->name; p++, option_index++)
+ if (!strncmp (p->name, d->__nextchar, nameend - d->__nextchar))
+ {
+ if ((unsigned int) (nameend - d->__nextchar)
+ == (unsigned int) strlen (p->name))
+ {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ }
+ else if (pfound == NULL)
+ {
+ /* First nonexact match found. */
+ pfound = p;
+ indfound = option_index;
+ }
+ else if (long_only
+ || pfound->has_arg != p->has_arg
+ || pfound->flag != p->flag
+ || pfound->val != p->val)
+ /* Second or later nonexact match found. */
+ ambig = 1;
+ }
+
+ if (ambig && !exact)
+ {
+ if (print_errors)
+ {
+ fprintf (stderr, _("%s: option `%s' is ambiguous\n"),
+ argv[0], argv[d->optind]);
+ }
+ d->__nextchar += strlen (d->__nextchar);
+ d->optind++;
+ d->optopt = 0;
+ return '?';
+ }
+
+ if (pfound != NULL)
+ {
+ option_index = indfound;
+ d->optind++;
+ if (*nameend)
+ {
+ /* Don't test has_arg with >, because some C compilers don't
+ allow it to be used on enums. */
+ if (pfound->has_arg)
+ d->optarg = nameend + 1;
+ else
+ {
+ if (print_errors)
+ {
+ if (argv[d->optind - 1][1] == '-')
+ {
+ /* --option */
+ fprintf (stderr, _("\
+%s: option `--%s' doesn't allow an argument\n"),
+ argv[0], pfound->name);
+ }
+ else
+ {
+ /* +option or -option */
+ fprintf (stderr, _("\
+%s: option `%c%s' doesn't allow an argument\n"),
+ argv[0], argv[d->optind - 1][0],
+ pfound->name);
+ }
+
+ }
+
+ d->__nextchar += strlen (d->__nextchar);
+
+ d->optopt = pfound->val;
+ return '?';
+ }
+ }
+ else if (pfound->has_arg == 1)
+ {
+ if (d->optind < argc)
+ d->optarg = argv[d->optind++];
+ else
+ {
+ if (print_errors)
+ {
+ fprintf (stderr,
+ _("%s: option `%s' requires an argument\n"),
+ argv[0], argv[d->optind - 1]);
+ }
+ d->__nextchar += strlen (d->__nextchar);
+ d->optopt = pfound->val;
+ return optstring[0] == ':' ? ':' : '?';
+ }
+ }
+ d->__nextchar += strlen (d->__nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ if (pfound->flag)
+ {
+ *(pfound->flag) = pfound->val;
+ return 0;
+ }
+ return pfound->val;
+ }
+
+ /* Can't find it as a long option. If this is not getopt_long_only,
+ or the option starts with '--' or is not a valid short
+ option, then it's an error.
+ Otherwise interpret it as a short option. */
+ if (!long_only || argv[d->optind][1] == '-'
+ || strchr (optstring, *d->__nextchar) == NULL)
+ {
+ if (print_errors)
+ {
+ if (argv[d->optind][1] == '-')
+ {
+ /* --option */
+ fprintf (stderr, _("%s: unrecognized option `--%s'\n"),
+ argv[0], d->__nextchar);
+ }
+ else
+ {
+ /* +option or -option */
+ fprintf (stderr, _("%s: unrecognized option `%c%s'\n"),
+ argv[0], argv[d->optind][0], d->__nextchar);
+ }
+
+ }
+ d->__nextchar = (char *) "";
+ d->optind++;
+ d->optopt = 0;
+ return '?';
+ }
+ }
+
+ /* Look at and handle the next short option-character. */
+
+ {
+ char c = *d->__nextchar++;
+ const char *temp = strchr (optstring, c);
+
+ /* Increment `optind' when we start to process its last character. */
+ if (*d->__nextchar == '\0')
+ ++d->optind;
+
+ if (temp == NULL || c == ':')
+ {
+ if (print_errors)
+ {
+ if (d->__posixly_correct)
+ {
+ /* 1003.2 specifies the format of this message. */
+ fprintf (stderr, _("%s: illegal option -- %c\n"), argv[0], c);
+ }
+ else
+ {
+ fprintf (stderr, _("%s: invalid option -- %c\n"), argv[0], c);
+ }
+ }
+ d->optopt = c;
+ return '?';
+ }
+ /* Convenience. Treat POSIX -W foo same as long option --foo */
+ if (temp[0] == 'W' && temp[1] == ';')
+ {
+ char *nameend;
+ const struct option *p;
+ const struct option *pfound = NULL;
+ int exact = 0;
+ int ambig = 0;
+ int indfound = 0;
+ int option_index;
+
+ /* This is an option that requires an argument. */
+ if (*d->__nextchar != '\0')
+ {
+ d->optarg = d->__nextchar;
+ /* If we end this ARGV-element by taking the rest as an arg,
+ we must advance to the next element now. */
+ d->optind++;
+ }
+ else if (d->optind == argc)
+ {
+ if (print_errors)
+ {
+ /* 1003.2 specifies the format of this message. */
+ fprintf (stderr, _("%s: option requires an argument -- %c\n"),
+ argv[0], c);
+ }
+ d->optopt = c;
+ if (optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ return c;
+ }
+ else
+ /* We already incremented `d->optind' once;
+ increment it again when taking next ARGV-elt as argument. */
+ d->optarg = argv[d->optind++];
+
+ /* optarg is now the argument, see if it's in the
+ table of longopts. */
+
+ for (d->__nextchar = nameend = d->optarg; *nameend && *nameend != '=';
+ nameend++)
+ /* Do nothing. */ ;
+
+ /* Test all long options for either exact match
+ or abbreviated matches. */
+ for (p = longopts, option_index = 0; p->name; p++, option_index++)
+ if (!strncmp (p->name, d->__nextchar, nameend - d->__nextchar))
+ {
+ if ((unsigned int) (nameend - d->__nextchar) == strlen (p->name))
+ {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ }
+ else if (pfound == NULL)
+ {
+ /* First nonexact match found. */
+ pfound = p;
+ indfound = option_index;
+ }
+ else
+ /* Second or later nonexact match found. */
+ ambig = 1;
+ }
+ if (ambig && !exact)
+ {
+ if (print_errors)
+ {
+ fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"),
+ argv[0], argv[d->optind]);
+ }
+ d->__nextchar += strlen (d->__nextchar);
+ d->optind++;
+ return '?';
+ }
+ if (pfound != NULL)
+ {
+ option_index = indfound;
+ if (*nameend)
+ {
+ /* Don't test has_arg with >, because some C compilers don't
+ allow it to be used on enums. */
+ if (pfound->has_arg)
+ d->optarg = nameend + 1;
+ else
+ {
+ if (print_errors)
+ {
+ fprintf (stderr, _("\
+%s: option `-W %s' doesn't allow an argument\n"),
+ argv[0], pfound->name);
+ }
+
+ d->__nextchar += strlen (d->__nextchar);
+ return '?';
+ }
+ }
+ else if (pfound->has_arg == 1)
+ {
+ if (d->optind < argc)
+ d->optarg = argv[d->optind++];
+ else
+ {
+ if (print_errors)
+ {
+ fprintf (stderr,
+ _("%s: option `%s' requires an argument\n"),
+ argv[0], argv[d->optind - 1]);
+ }
+ d->__nextchar += strlen (d->__nextchar);
+ return optstring[0] == ':' ? ':' : '?';
+ }
+ }
+ d->__nextchar += strlen (d->__nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ if (pfound->flag)
+ {
+ *(pfound->flag) = pfound->val;
+ return 0;
+ }
+ return pfound->val;
+ }
+ d->__nextchar = NULL;
+ return 'W'; /* Let the application handle it. */
+ }
+ if (temp[1] == ':')
+ {
+ if (temp[2] == ':')
+ {
+ /* This is an option that accepts an argument optionally. */
+ if (*d->__nextchar != '\0')
+ {
+ d->optarg = d->__nextchar;
+ d->optind++;
+ }
+ else
+ d->optarg = NULL;
+ d->__nextchar = NULL;
+ }
+ else
+ {
+ /* This is an option that requires an argument. */
+ if (*d->__nextchar != '\0')
+ {
+ d->optarg = d->__nextchar;
+ /* If we end this ARGV-element by taking the rest as an arg,
+ we must advance to the next element now. */
+ d->optind++;
+ }
+ else if (d->optind == argc)
+ {
+ if (print_errors)
+ {
+ /* 1003.2 specifies the format of this message. */
+ fprintf (stderr,
+ _("%s: option requires an argument -- %c\n"),
+ argv[0], c);
+ }
+ d->optopt = c;
+ if (optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ }
+ else
+ /* We already incremented `optind' once;
+ increment it again when taking next ARGV-elt as argument. */
+ d->optarg = argv[d->optind++];
+ d->__nextchar = NULL;
+ }
+ }
+ return c;
+ }
+}
+
+int
+_getopt_internal (int argc, char **argv, const char *optstring,
+ const struct option *longopts, int *longind,
+ int long_only, int posixly_correct)
+{
+ int result;
+
+ getopt_data.optind = optind;
+ getopt_data.opterr = opterr;
+
+ result = _getopt_internal_r (argc, argv, optstring, longopts, longind,
+ long_only, posixly_correct, &getopt_data);
+
+ optind = getopt_data.optind;
+ optarg = getopt_data.optarg;
+ optopt = getopt_data.optopt;
+
+ return result;
+}
+
+/* glibc gets a LSB-compliant getopt.
+ Standalone applications get a POSIX-compliant getopt. */
+#if _LIBC
+enum { POSIXLY_CORRECT = 0 };
+#else
+enum { POSIXLY_CORRECT = 1 };
+#endif
+
+int
+getopt (int argc, char *const *argv, const char *optstring)
+{
+ return _getopt_internal (argc, (char **) argv, optstring, NULL, NULL, 0,
+ POSIXLY_CORRECT);
+}
+
+
+#ifdef TEST
+
+/* Compile with -DTEST to make an executable for use in testing
+ the above definition of `getopt'. */
+
+int
+main (int argc, char **argv)
+{
+ int digit_optind = 0;
+
+ while (1)
+ {
+ int this_option_optind = optind ? optind : 1;
+ int c;
+
+ c = getopt (argc, argv, "abc:d:0123456789");
+ if (c == -1)
+ break;
+
+ switch (c)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (digit_optind != 0 && digit_optind != this_option_optind)
+ printf ("digits occur in two different argv-elements.\n");
+ digit_optind = this_option_optind;
+ printf ("option %c\n", c);
+ break;
+
+ case 'a':
+ printf ("option a\n");
+ break;
+
+ case 'b':
+ printf ("option b\n");
+ break;
+
+ case 'c':
+ printf ("option c with value `%s'\n", optarg);
+ break;
+
+ case '?':
+ break;
+
+ default:
+ printf ("?? getopt returned character code 0%o ??\n", c);
+ }
+ }
+
+ if (optind < argc)
+ {
+ printf ("non-option ARGV-elements: ");
+ while (optind < argc)
+ printf ("%s ", argv[optind++]);
+ printf ("\n");
+ }
+
+ exit (0);
+}
+
+#endif /* TEST */
diff --git a/lib/compat/getopt.h b/lib/compat/getopt.h
new file mode 100644
index 0000000..d9c1494
--- /dev/null
+++ b/lib/compat/getopt.h
@@ -0,0 +1,129 @@
+/* Declarations for getopt.
+ Copyright (C) 1989,90,91,92,93,94,96,97 Free Software Foundation, Inc.
+
+ the C library, however. The master source lives in /gd/gnu/lib.
+
+NOTE: The canonical source of this file is maintained with the GNU C Library.
+Bugs can be reported to bug-glibc@prep.ai.mit.edu.
+
+This program is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2, or (at your option) any
+later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+USA. */
+
+#ifndef _GETOPT_H
+#define _GETOPT_H 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* For communication from `getopt' to the caller.
+ When `getopt' finds an option that takes an argument,
+ the argument value is returned here.
+ Also, when `ordering' is RETURN_IN_ORDER,
+ each non-option ARGV-element is returned here. */
+
+extern char *optarg;
+
+/* Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to `getopt'.
+
+ On entry to `getopt', zero means this is the first call; initialize.
+
+ When `getopt' returns -1, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, `optind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+extern int optind;
+
+/* Callers store zero here to inhibit the error message `getopt' prints
+ for unrecognized options. */
+
+extern int opterr;
+
+/* Set to an option character which was unrecognized. */
+
+extern int optopt;
+
+/* Describe the long-named options requested by the application.
+ The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector
+ of `struct option' terminated by an element containing a name which is
+ zero.
+
+ The field `has_arg' is:
+ no_argument (or 0) if the option does not take an argument,
+ required_argument (or 1) if the option requires an argument,
+ optional_argument (or 2) if the option takes an optional argument.
+
+ If the field `flag' is not NULL, it points to a variable that is set
+ to the value given in the field `val' when the option is found, but
+ left unchanged if the option is not found.
+
+ To have a long-named option do something other than set an `int' to
+ a compiled-in constant, such as set a value from `optarg', set the
+ option's `flag' field to zero and its `val' field to a nonzero
+ value (the equivalent single-letter option character, if there is
+ one). For long options that have a zero `flag' field, `getopt'
+ returns the contents of the `val' field. */
+
+struct option
+{
+#if defined (__STDC__) && __STDC__
+ const char *name;
+#else
+ char *name;
+#endif
+ /* has_arg can't be an enum because some compilers complain about
+ type mismatches in all the code that assumes it is an int. */
+ int has_arg;
+ int *flag;
+ int val;
+};
+
+/* Names for the values of the `has_arg' field of `struct option'. */
+
+#define no_argument 0
+#define required_argument 1
+#define optional_argument 2
+
+#if defined (__STDC__) && __STDC__
+#ifdef __GNU_LIBRARY__
+/* Many other libraries have conflicting prototypes for getopt, with
+ differences in the consts, in stdlib.h. To avoid compilation
+ errors, only prototype getopt for the GNU C library. */
+extern int getopt (int argc, char *const *argv, const char *shortopts);
+#else /* not __GNU_LIBRARY__ */
+
+#endif /* __GNU_LIBRARY__ */
+extern int getopt_long (int argc, char *const *argv, const char *shortopts,
+ const struct option *longopts, int *longind);
+extern int getopt_long_only (int argc, char *const *argv,
+ const char *shortopts,
+ const struct option *longopts, int *longind);
+#else /* not __STDC__ */
+extern int getopt ();
+extern int getopt_long ();
+extern int getopt_long_only ();
+
+extern int _getopt_internal ();
+#endif /* __STDC__ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _GETOPT_H */
diff --git a/lib/compat/getopt1.c b/lib/compat/getopt1.c
new file mode 100644
index 0000000..0ee87b0
--- /dev/null
+++ b/lib/compat/getopt1.c
@@ -0,0 +1,170 @@
+/* getopt_long and getopt_long_only entry points for GNU getopt.
+ Copyright (C) 1987,88,89,90,91,92,93,94,96,97,98,2004,2006
+ Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "getopt.h"
+#include "getopt_int.h"
+
+#include <stdio.h>
+
+/* This needs to come after some library #include
+ to get __GNU_LIBRARY__ defined. */
+#ifdef __GNU_LIBRARY__
+#include <stdlib.h>
+#endif
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+int
+getopt_long (int argc, char *const *argv, const char *options,
+ const struct option *long_options, int *opt_index)
+{
+ return _getopt_internal (argc, (char **) argv, options, long_options,
+ opt_index, 0, 0);
+}
+
+int
+_getopt_long_r (int argc, char **argv, const char *options,
+ const struct option *long_options, int *opt_index,
+ struct _getopt_data *d)
+{
+ return _getopt_internal_r (argc, argv, options, long_options, opt_index,
+ 0, 0, d);
+}
+
+/* Like getopt_long, but '-' as well as '--' can indicate a long option.
+ If an option that starts with '-' (not '--') doesn't match a long option,
+ but does match a short option, it is parsed as a short option
+ instead. */
+
+int
+getopt_long_only (int argc, char *const *argv,
+ const char *options,
+ const struct option *long_options, int *opt_index)
+{
+ return _getopt_internal (argc, (char **) argv, options, long_options,
+ opt_index, 1, 0);
+}
+
+int
+_getopt_long_only_r (int argc, char **argv, const char *options,
+ const struct option *long_options, int *opt_index,
+ struct _getopt_data *d)
+{
+ return _getopt_internal_r (argc, argv, options, long_options, opt_index,
+ 1, 0, d);
+}
+
+
+#ifdef TEST
+
+#include <stdio.h>
+
+int
+main (int argc, char **argv)
+{
+ int digit_optind = 0;
+
+ while (1)
+ {
+ int this_option_optind = optind ? optind : 1;
+ int option_index = 0;
+ static const struct option long_options[] =
+ {
+ {"add", 1, 0, 0},
+ {"append", 0, 0, 0},
+ {"delete", 1, 0, 0},
+ {"verbose", 0, 0, 0},
+ {"create", 0, 0, 0},
+ {"file", 1, 0, 0},
+ {0, 0, 0, 0}
+ };
+ int c;
+
+ c = getopt_long (argc, argv, "abc:d:0123456789",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c)
+ {
+ case 0:
+ printf ("option %s", long_options[option_index].name);
+ if (optarg)
+ printf (" with arg %s", optarg);
+ printf ("\n");
+ break;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (digit_optind != 0 && digit_optind != this_option_optind)
+ printf ("digits occur in two different argv-elements.\n");
+ digit_optind = this_option_optind;
+ printf ("option %c\n", c);
+ break;
+
+ case 'a':
+ printf ("option a\n");
+ break;
+
+ case 'b':
+ printf ("option b\n");
+ break;
+
+ case 'c':
+ printf ("option c with value `%s'\n", optarg);
+ break;
+
+ case 'd':
+ printf ("option d with value `%s'\n", optarg);
+ break;
+
+ case '?':
+ break;
+
+ default:
+ printf ("?? getopt returned character code 0%o ??\n", c);
+ }
+ }
+
+ if (optind < argc)
+ {
+ printf ("non-option ARGV-elements: ");
+ while (optind < argc)
+ printf ("%s ", argv[optind++]);
+ printf ("\n");
+ }
+
+ exit (0);
+}
+
+#endif /* TEST */
diff --git a/lib/compat/getopt_int.h b/lib/compat/getopt_int.h
new file mode 100644
index 0000000..401579f
--- /dev/null
+++ b/lib/compat/getopt_int.h
@@ -0,0 +1,131 @@
+/* Internal declarations for getopt.
+ Copyright (C) 1989-1994,1996-1999,2001,2003,2004
+ Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#ifndef _GETOPT_INT_H
+#define _GETOPT_INT_H 1
+
+extern int _getopt_internal (int ___argc, char **___argv,
+ const char *__shortopts,
+ const struct option *__longopts, int *__longind,
+ int __long_only, int __posixly_correct);
+
+
+/* Reentrant versions which can handle parsing multiple argument
+ vectors at the same time. */
+
+/* Data type for reentrant functions. */
+struct _getopt_data
+{
+ /* These have exactly the same meaning as the corresponding global
+ variables, except that they are used for the reentrant
+ versions of getopt. */
+ int optind;
+ int opterr;
+ int optopt;
+ char *optarg;
+
+ /* Internal members. */
+
+ /* True if the internal members have been initialized. */
+ int __initialized;
+
+ /* The next char to be scanned in the option-element
+ in which the last option character we returned was found.
+ This allows us to pick up the scan where we left off.
+
+ If this is zero, or a null string, it means resume the scan
+ by advancing to the next ARGV-element. */
+ char *__nextchar;
+
+ /* Describe how to deal with options that follow non-option ARGV-elements.
+
+ If the caller did not specify anything,
+ the default is REQUIRE_ORDER if the environment variable
+ POSIXLY_CORRECT is defined, PERMUTE otherwise.
+
+ REQUIRE_ORDER means don't recognize them as options;
+ stop option processing when the first non-option is seen.
+ This is what Unix does.
+ This mode of operation is selected by either setting the environment
+ variable POSIXLY_CORRECT, or using `+' as the first character
+ of the list of option characters, or by calling getopt.
+
+ PERMUTE is the default. We permute the contents of ARGV as we
+ scan, so that eventually all the non-options are at the end.
+ This allows options to be given in any order, even with programs
+ that were not written to expect this.
+
+ RETURN_IN_ORDER is an option available to programs that were
+ written to expect options and other ARGV-elements in any order
+ and that care about the ordering of the two. We describe each
+ non-option ARGV-element as if it were the argument of an option
+ with character code 1. Using `-' as the first character of the
+ list of option characters selects this mode of operation.
+
+ The special argument `--' forces an end of option-scanning regardless
+ of the value of `ordering'. In the case of RETURN_IN_ORDER, only
+ `--' can cause `getopt' to return -1 with `optind' != ARGC. */
+
+ enum
+ {
+ REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER
+ } __ordering;
+
+ /* If the POSIXLY_CORRECT environment variable is set
+ or getopt was called. */
+ int __posixly_correct;
+
+
+ /* Handle permutation of arguments. */
+
+ /* Describe the part of ARGV that contains non-options that have
+ been skipped. `first_nonopt' is the index in ARGV of the first
+ of them; `last_nonopt' is the index after the last of them. */
+
+ int __first_nonopt;
+ int __last_nonopt;
+
+#if defined _LIBC && defined USE_NONOPTION_FLAGS
+ int __nonoption_flags_max_len;
+ int __nonoption_flags_len;
+# endif
+};
+
+/* The initializer is necessary to set OPTIND and OPTERR to their
+ default values and to clear the initialization flag. */
+#define _GETOPT_DATA_INITIALIZER { 1, 1 }
+
+extern int _getopt_internal_r (int ___argc, char **___argv,
+ const char *__shortopts,
+ const struct option *__longopts, int *__longind,
+ int __long_only, int __posixly_correct,
+ struct _getopt_data *__data);
+
+extern int _getopt_long_r (int ___argc, char **___argv,
+ const char *__shortopts,
+ const struct option *__longopts, int *__longind,
+ struct _getopt_data *__data);
+
+extern int _getopt_long_only_r (int ___argc, char **___argv,
+ const char *__shortopts,
+ const struct option *__longopts,
+ int *__longind,
+ struct _getopt_data *__data);
+
+#endif /* getopt_int.h */
diff --git a/lib/compat/gettext.h b/lib/compat/gettext.h
new file mode 100644
index 0000000..69cb448
--- /dev/null
+++ b/lib/compat/gettext.h
@@ -0,0 +1,307 @@
+/* Convenience header for conditional use of GNU <libintl.h>.
+ Copyright (C) 1995-1998, 2000-2002, 2004-2006 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ USA. */
+
+#ifndef _LIBGETTEXT_H
+#define _LIBGETTEXT_H 1
+
+/* NLS can be disabled through the configure --disable-nls option. */
+#if ENABLE_NLS
+
+/* Get declarations of GNU message catalog functions. */
+# include <libintl.h>
+
+/* You can set the DEFAULT_TEXT_DOMAIN macro to specify the domain used by
+ the gettext() and ngettext() macros. This is an alternative to calling
+ textdomain(), and is useful for libraries. */
+# ifdef DEFAULT_TEXT_DOMAIN
+# undef gettext
+# define gettext(Msgid) \
+ dgettext (DEFAULT_TEXT_DOMAIN, Msgid)
+# undef ngettext
+# define ngettext(Msgid1, Msgid2, N) \
+ dngettext (DEFAULT_TEXT_DOMAIN, Msgid1, Msgid2, N)
+# endif
+
+#else
+
+/* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which
+ chokes if dcgettext is defined as a macro. So include it now, to make
+ later inclusions of <locale.h> a NOP. We don't include <libintl.h>
+ as well because people using "gettext.h" will not include <libintl.h>,
+ and also including <libintl.h> would fail on SunOS 4, whereas <locale.h>
+ is OK. */
+#if defined(__sun)
+# include <locale.h>
+#endif
+
+/* Many header files from the libstdc++ coming with g++ 3.3 or newer include
+ <libintl.h>, which chokes if dcgettext is defined as a macro. So include
+ it now, to make later inclusions of <libintl.h> a NOP. */
+#if defined(__cplusplus) && defined(__GNUG__) && (__GNUC__ >= 3)
+# include <cstdlib>
+# if (__GLIBC__ >= 2 && !defined __UCLIBC__) || _GLIBCXX_HAVE_LIBINTL_H
+# include <libintl.h>
+# endif
+#endif
+
+/* Disabled NLS.
+ The casts to 'const char *' serve the purpose of producing warnings
+ for invalid uses of the value returned from these functions.
+ On pre-ANSI systems without 'const', the config.h file is supposed to
+ contain "#define const". */
+# define gettext(Msgid) ((const char *) (Msgid))
+# define dgettext(Domainname, Msgid) ((void) (Domainname), gettext (Msgid))
+# define dcgettext(Domainname, Msgid, Category) \
+ ((void) (Category), dgettext (Domainname, Msgid))
+# define ngettext(Msgid1, Msgid2, N) \
+ ((N) == 1 \
+ ? (const char *)(Msgid1) \
+ : (const char *)(Msgid2))
+# define dngettext(Domainname, Msgid1, Msgid2, N) \
+ ((void) (Domainname), ngettext (Msgid1, Msgid2, N))
+# define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \
+ ((void) (Category), dngettext(Domainname, Msgid1, Msgid2, N))
+# define textdomain(Domainname) ((const char *) (Domainname))
+# define bindtextdomain(Domainname, Dirname) \
+ ((void) (Domainname), (const char *) (Dirname))
+# define bind_textdomain_codeset(Domainname, Codeset) \
+ ((void) (Domainname), (const char *) (Codeset))
+
+#endif
+
+/* A pseudo function call that serves as a marker for the automated
+ extraction of messages, but does not call gettext(). The run-time
+ translation is done at a different place in the code.
+ The argument, String, should be a literal string. Concatenated strings
+ and other string expressions won't work.
+ The macro's expansion is not parenthesized, so that it is suitable as
+ initializer for static 'char[]' or 'const char[]' variables. */
+#define gettext_noop(String) String
+
+/* The separator between msgctxt and msgid in a .mo file. */
+#define GETTEXT_CONTEXT_GLUE "\004"
+
+#if ENABLE_NLS
+
+/* Pseudo function calls, taking a MSGCTXT and a MSGID instead of just a
+ MSGID. MSGCTXT and MSGID must be string literals. MSGCTXT should be
+ short and rarely need to change.
+ The letter 'p' stands for 'particular' or 'special'. */
+#ifdef DEFAULT_TEXT_DOMAIN
+# define pgettext(Msgctxt, Msgid) \
+ pgettext_aux (DEFAULT_TEXT_DOMAIN, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES)
+#else
+# define pgettext(Msgctxt, Msgid) \
+ pgettext_aux (NULL, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES)
+#endif
+#define dpgettext(Domainname, Msgctxt, Msgid) \
+ pgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES)
+#define dcpgettext(Domainname, Msgctxt, Msgid, Category) \
+ pgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, Category)
+#ifdef DEFAULT_TEXT_DOMAIN
+# define npgettext(Msgctxt, Msgid, MsgidPlural, N) \
+ npgettext_aux (DEFAULT_TEXT_DOMAIN, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES)
+#else
+# define npgettext(Msgctxt, Msgid, MsgidPlural, N) \
+ npgettext_aux (NULL, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES)
+#endif
+#define dnpgettext(Domainname, Msgctxt, Msgid, MsgidPlural, N) \
+ npgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES)
+#define dcnpgettext(Domainname, Msgctxt, Msgid, MsgidPlural, N, Category) \
+ npgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, Category)
+
+#ifdef __GNUC__
+__inline
+#else
+#ifdef __cplusplus
+inline
+#endif
+#endif
+static const char *
+pgettext_aux (const char *domain,
+ const char *msg_ctxt_id, const char *msgid,
+ int category)
+{
+ const char *translation = dcgettext (domain, msg_ctxt_id, category);
+ if (translation == msg_ctxt_id)
+ return msgid;
+ else
+ return translation;
+}
+
+#ifdef __GNUC__
+__inline
+#else
+#ifdef __cplusplus
+inline
+#endif
+#endif
+static const char *
+npgettext_aux (const char *domain,
+ const char *msg_ctxt_id, const char *msgid,
+ const char *msgid_plural, unsigned long int n,
+ int category)
+{
+ const char *translation =
+ dcngettext (domain, msg_ctxt_id, msgid_plural, n, category);
+ if (translation == msg_ctxt_id || translation == msgid_plural)
+ return (n == 1 ? msgid : msgid_plural);
+ else
+ return translation;
+}
+
+/* The same thing extended for non-constant arguments. Here MSGCTXT and MSGID
+ can be arbitrary expressions. But for string literals these macros are
+ less efficient than those above. */
+
+#include <string.h>
+
+/* We do not want VLAs, which have a terrible failure mode on stack
+ * exhaustion. */
+#if 0
+#define _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS \
+ (((__GNUC__ >= 3 || __GNUG__ >= 2) && !__STRICT_ANSI__) \
+ /* || __STDC_VERSION__ >= 199901L */ )
+#endif
+
+#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
+#include <stdlib.h>
+#endif
+
+#define pgettext_expr(Msgctxt, Msgid) \
+ dcpgettext_expr (NULL, Msgctxt, Msgid, LC_MESSAGES)
+#define dpgettext_expr(Domainname, Msgctxt, Msgid) \
+ dcpgettext_expr (Domainname, Msgctxt, Msgid, LC_MESSAGES)
+
+#ifdef __GNUC__
+__inline
+#else
+#ifdef __cplusplus
+inline
+#endif
+#endif
+static const char *
+dcpgettext_expr (const char *domain,
+ const char *msgctxt, const char *msgid,
+ int category)
+{
+ size_t msgctxt_len = strlen (msgctxt) + 1;
+ size_t msgid_len = strlen (msgid) + 1;
+ const char *translation;
+#if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
+ char msg_ctxt_id[msgctxt_len + msgid_len];
+#else
+ char buf[1024];
+ char *msg_ctxt_id =
+ (msgctxt_len + msgid_len <= sizeof (buf)
+ ? buf
+ : (char *) malloc (msgctxt_len + msgid_len));
+ if (msg_ctxt_id != NULL)
+#endif
+ {
+ memcpy (msg_ctxt_id, msgctxt, msgctxt_len - 1);
+ msg_ctxt_id[msgctxt_len - 1] = '\004';
+ memcpy (msg_ctxt_id + msgctxt_len, msgid, msgid_len);
+ translation = dcgettext (domain, msg_ctxt_id, category);
+#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
+ if (msg_ctxt_id != buf)
+ free (msg_ctxt_id);
+#endif
+ if (translation != msg_ctxt_id)
+ return translation;
+ }
+ return msgid;
+}
+
+#define npgettext_expr(Msgctxt, Msgid, MsgidPlural, N) \
+ dcnpgettext_expr (NULL, Msgctxt, Msgid, MsgidPlural, N, LC_MESSAGES)
+#define dnpgettext_expr(Domainname, Msgctxt, Msgid, MsgidPlural, N) \
+ dcnpgettext_expr (Domainname, Msgctxt, Msgid, MsgidPlural, N, LC_MESSAGES)
+
+#ifdef __GNUC__
+__inline
+#else
+#ifdef __cplusplus
+inline
+#endif
+#endif
+static const char *
+dcnpgettext_expr (const char *domain,
+ const char *msgctxt, const char *msgid,
+ const char *msgid_plural, unsigned long int n,
+ int category)
+{
+ size_t msgctxt_len = strlen (msgctxt) + 1;
+ size_t msgid_len = strlen (msgid) + 1;
+ const char *translation;
+#if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
+ char msg_ctxt_id[msgctxt_len + msgid_len];
+#else
+ char buf[1024];
+ char *msg_ctxt_id =
+ (msgctxt_len + msgid_len <= sizeof (buf)
+ ? buf
+ : (char *) malloc (msgctxt_len + msgid_len));
+ if (msg_ctxt_id != NULL)
+#endif
+ {
+ memcpy (msg_ctxt_id, msgctxt, msgctxt_len - 1);
+ msg_ctxt_id[msgctxt_len - 1] = '\004';
+ memcpy (msg_ctxt_id + msgctxt_len, msgid, msgid_len);
+ translation = dcngettext (domain, msg_ctxt_id, msgid_plural, n, category);
+#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
+ if (msg_ctxt_id != buf)
+ free (msg_ctxt_id);
+#endif
+ if (!(translation == msg_ctxt_id || translation == msgid_plural))
+ return translation;
+ }
+ return (n == 1 ? msgid : msgid_plural);
+}
+
+#else /* ENABLE_NLS */
+
+#define pgettext(Msgctxt, Msgid) ((void) (Msgctxt), gettext (Msgid))
+#define dpgettext(Domainname, Msgctxt, Msgid) \
+ ((void) (Domainname), pgettext (Msgctxt, Msgid))
+#define dcpgettext(Domainname, Msgctxt, Msgid, Category) \
+ ((void) (Category), dpgettext (Domainname, Msgctxt, Msgid))
+
+#define npgettext(Msgctxt, Msgid, MsgidPlural, N) \
+ ((void) (Msgctxt), ngettext (Msgid, MsgidPlural, N))
+#define dnpgettext(Domainname, Msgctxt, Msgid, MsgidPlural, N) \
+ ((void) (Domainname), npgettext (Msgctxt, Msgid, MsgidPlural, N)
+#define dcnpgettext(Domainname, Msgctxt, Msgid, MsgidPlural, N, Category) \
+ ((void) (Category), dnpgettext (Domainname, Msgctxt, Msgid, MsgidPlural, N)
+
+#define pgettext_expr(Msgctxt, Msgid) pgettext (Msgctxt, Msgid)
+#define dpgettext_expr(Domainname, Msgctxt, Msgid) \
+ dpgettext (Domainname, Msgctxt, Msgid)
+#define dcpgettext_expr(Domainname, Msgctxt, Msgid, Category) \
+ dcpgettext (Domainname, Msgctxt, Msgid, Category)
+
+#define npgettext_expr(Msgctxt, Msgid, MsgidPlural, N) \
+ npgettext (Msgctxt, Msgid, MsgidPlural, N)
+#define dnpgettext_expr(Domainname, Msgctxt, Msgid, MsgidPlural, N) \
+ dnpgettext (Domainname, Msgctxt, Msgid, MsgidPlural, N)
+#define dcnpgettext_expr(Domainname, Msgctxt, Msgid, MsgidPlural, N, Category) \
+ dcnpgettext (Domainname, Msgctxt, Msgid, MsgidPlural, N, Category)
+
+#endif /* ENABLE_NLS */
+
+#endif /* _LIBGETTEXT_H */
diff --git a/lib/compat/obstack.c b/lib/compat/obstack.c
new file mode 100644
index 0000000..ca9aa11
--- /dev/null
+++ b/lib/compat/obstack.c
@@ -0,0 +1,416 @@
+/* obstack.c - subroutines used implicitly by object stack macros
+
+ Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1996, 1997,
+ 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 Free Software
+ Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#ifdef _LIBC
+# include <obstack.h>
+# include <shlib-compat.h>
+#else
+# include <config.h>
+# include "obstack.h"
+#endif
+
+/* NOTE BEFORE MODIFYING THIS FILE: This version number must be
+ incremented whenever callers compiled using an old obstack.h can no
+ longer properly call the functions in this obstack.c. */
+#define OBSTACK_INTERFACE_VERSION 1
+
+/* Comment out all this code if we are using the GNU C Library, and are not
+ actually compiling the library itself, and the installed library
+ supports the same library interface we do. This code is part of the GNU
+ C Library, but also included in many other GNU distributions. Compiling
+ and linking in this code is a waste when using the GNU C library
+ (especially if it is a shared library). Rather than having every GNU
+ program understand `configure --with-gnu-libc' and omit the object
+ files, it is simpler to just do this in the source for each such file. */
+
+#include <stdio.h> /* Random thing to get __GNU_LIBRARY__. */
+#if !defined _LIBC && defined __GNU_LIBRARY__ && __GNU_LIBRARY__ > 1
+# include <gnu-versions.h>
+# if _GNU_OBSTACK_INTERFACE_VERSION == OBSTACK_INTERFACE_VERSION
+# define ELIDE_CODE
+# endif
+#endif
+
+#include <stddef.h>
+
+#ifndef ELIDE_CODE
+
+# include <stdint.h>
+
+/* Determine default alignment. */
+union fooround
+{
+ uintmax_t i;
+ long double d;
+ void *p;
+};
+struct fooalign
+{
+ char c;
+ union fooround u;
+};
+/* If malloc were really smart, it would round addresses to DEFAULT_ALIGNMENT.
+ But in fact it might be less smart and round addresses to as much as
+ DEFAULT_ROUNDING. So we prepare for it to do that. */
+enum
+ {
+ DEFAULT_ALIGNMENT = offsetof (struct fooalign, u),
+ DEFAULT_ROUNDING = sizeof (union fooround)
+ };
+
+/* When we copy a long block of data, this is the unit to do it with.
+ On some machines, copying successive ints does not work;
+ in such a case, redefine COPYING_UNIT to `long' (if that works)
+ or `char' as a last resort. */
+# ifndef COPYING_UNIT
+# define COPYING_UNIT int
+# endif
+
+
+/* The functions allocating more room by calling `obstack_chunk_alloc'
+ jump to the handler pointed to by `obstack_alloc_failed_handler'.
+ This can be set to a user defined function which should either
+ abort gracefully or use longjmp - but shouldn't return. This
+ variable by default points to the internal function
+ `print_and_abort'. */
+static void print_and_abort (void);
+void (*obstack_alloc_failed_handler) (void) = print_and_abort;
+
+/* Exit value used when `print_and_abort' is used. */
+# include <stdlib.h>
+# ifdef _LIBC
+int obstack_exit_failure = EXIT_FAILURE;
+# else
+#ifndef EXIT_FAILURE
+#define EXIT_FAILURE 1
+#endif
+extern int volatile exit_failure;
+int volatile exit_failure = EXIT_FAILURE;
+# define obstack_exit_failure exit_failure
+# endif
+
+# ifdef _LIBC
+# if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_3_4)
+/* A looong time ago (before 1994, anyway; we're not sure) this global variable
+ was used by non-GNU-C macros to avoid multiple evaluation. The GNU C
+ library still exports it because somebody might use it. */
+struct obstack *_obstack_compat;
+compat_symbol (libc, _obstack_compat, _obstack, GLIBC_2_0);
+# endif
+# endif
+
+/* Define a macro that either calls functions with the traditional malloc/free
+ calling interface, or calls functions with the mmalloc/mfree interface
+ (that adds an extra first argument), based on the state of use_extra_arg.
+ For free, do not use ?:, since some compilers, like the MIPS compilers,
+ do not allow (expr) ? void : void. */
+
+# define CALL_CHUNKFUN(h, size) \
+ (((h) -> use_extra_arg) \
+ ? (*(h)->chunkfun.arg2) ((h)->extra_arg, (size)) \
+ : (*(h)->chunkfun.arg1) ((size)))
+
+# define CALL_FREEFUN(h, old_chunk) \
+ do { \
+ if ((h) -> use_extra_arg) \
+ (*(h)->freefun.arg2) ((h)->extra_arg, (old_chunk)); \
+ else \
+ (*(h)->freefun.arg1) ((old_chunk)); \
+ } while (0)
+
+
+/* Initialize an obstack H for use. Specify chunk size SIZE (0 means default).
+ Objects start on multiples of ALIGNMENT (0 means use default).
+ CHUNKFUN is the function to use to allocate chunks,
+ and FREEFUN the function to free them.
+
+ Return nonzero if successful, calls obstack_alloc_failed_handler if
+ allocation fails. */
+
+int
+_obstack_begin (struct obstack *h,
+ size_t size, size_t alignment,
+ void *(*chunkfun) (size_t),
+ void (*freefun) (void *))
+{
+ struct _obstack_chunk *chunk; /* points to new chunk */
+
+ if (alignment == 0)
+ alignment = DEFAULT_ALIGNMENT;
+ if (size == 0)
+ /* Default size is what GNU malloc can fit in a 4096-byte block. */
+ {
+ /* 12 is sizeof (mhead) and 4 is EXTRA from GNU malloc.
+ Use the values for range checking, because if range checking is off,
+ the extra bytes won't be missed terribly, but if range checking is on
+ and we used a larger request, a whole extra 4096 bytes would be
+ allocated.
+
+ These number are irrelevant to the new GNU malloc. I suspect it is
+ less sensitive to the size of the request. */
+ int extra = ((((12 + DEFAULT_ROUNDING - 1) & ~(DEFAULT_ROUNDING - 1))
+ + 4 + DEFAULT_ROUNDING - 1)
+ & ~(DEFAULT_ROUNDING - 1));
+ size = 4096 - extra;
+ }
+
+ h->chunkfun.arg1 = chunkfun;
+ h->freefun.arg1 = freefun;
+ h->chunk_size = size;
+ h->alignment_mask = alignment - 1;
+ h->use_extra_arg = 0;
+
+ chunk = h->chunk = CALL_CHUNKFUN (h, h -> chunk_size);
+ if (!chunk)
+ (*obstack_alloc_failed_handler) ();
+ h->next_free = h->object_base = __PTR_ALIGN ((char *) chunk, chunk->contents,
+ alignment - 1);
+ h->chunk_limit = chunk->limit
+ = (char *) chunk + h->chunk_size;
+ chunk->prev = 0;
+ /* The initial chunk now contains no empty object. */
+ h->maybe_empty_object = 0;
+ h->alloc_failed = 0;
+ return 1;
+}
+
+int
+_obstack_begin_1 (struct obstack *h, size_t size, size_t alignment,
+ void *(*chunkfun) (void *, size_t),
+ void (*freefun) (void *, void *),
+ void *arg)
+{
+ struct _obstack_chunk *chunk; /* points to new chunk */
+
+ if (alignment == 0)
+ alignment = DEFAULT_ALIGNMENT;
+ if (size == 0)
+ /* Default size is what GNU malloc can fit in a 4096-byte block. */
+ {
+ /* 12 is sizeof (mhead) and 4 is EXTRA from GNU malloc.
+ Use the values for range checking, because if range checking is off,
+ the extra bytes won't be missed terribly, but if range checking is on
+ and we used a larger request, a whole extra 4096 bytes would be
+ allocated.
+
+ These number are irrelevant to the new GNU malloc. I suspect it is
+ less sensitive to the size of the request. */
+ int extra = ((((12 + DEFAULT_ROUNDING - 1) & ~(DEFAULT_ROUNDING - 1))
+ + 4 + DEFAULT_ROUNDING - 1)
+ & ~(DEFAULT_ROUNDING - 1));
+ size = 4096 - extra;
+ }
+
+ h->chunkfun.arg2 = chunkfun;
+ h->freefun.arg2 = freefun;
+ h->chunk_size = size;
+ h->alignment_mask = alignment - 1;
+ h->extra_arg = arg;
+ h->use_extra_arg = 1;
+
+ chunk = h->chunk = CALL_CHUNKFUN (h, h -> chunk_size);
+ if (!chunk)
+ (*obstack_alloc_failed_handler) ();
+ h->next_free = h->object_base = __PTR_ALIGN ((char *) chunk, chunk->contents,
+ alignment - 1);
+ h->chunk_limit = chunk->limit
+ = (char *) chunk + h->chunk_size;
+ chunk->prev = 0;
+ /* The initial chunk now contains no empty object. */
+ h->maybe_empty_object = 0;
+ h->alloc_failed = 0;
+ return 1;
+}
+
+/* Allocate a new current chunk for the obstack *H
+ on the assumption that LENGTH bytes need to be added
+ to the current object, or a new object of length LENGTH allocated.
+ Copies any partial object from the end of the old chunk
+ to the beginning of the new one. */
+
+void
+_obstack_newchunk (struct obstack *h, size_t length)
+{
+ struct _obstack_chunk *old_chunk = h->chunk;
+ struct _obstack_chunk *new_chunk;
+ size_t new_size;
+ size_t obj_size = h->next_free - h->object_base;
+ char *object_base;
+
+ /* Compute size for new chunk. */
+ new_size = (obj_size + length) + (obj_size >> 3) + h->alignment_mask + 100;
+ if (new_size < h->chunk_size)
+ new_size = h->chunk_size;
+
+ /* Allocate and initialize the new chunk. */
+ new_chunk = CALL_CHUNKFUN (h, new_size);
+ if (!new_chunk)
+ (*obstack_alloc_failed_handler) ();
+ h->chunk = new_chunk;
+ new_chunk->prev = old_chunk;
+ new_chunk->limit = h->chunk_limit = (char *) new_chunk + new_size;
+
+ /* Compute an aligned object_base in the new chunk */
+ object_base =
+ __PTR_ALIGN ((char *) new_chunk, new_chunk->contents, h->alignment_mask);
+
+ /* Move the existing object to the new chunk. */
+ memcpy(object_base, h->object_base, obj_size);
+
+ /* If the object just copied was the only data in OLD_CHUNK,
+ free that chunk and remove it from the chain.
+ But not if that chunk might contain an empty object. */
+ if (! h->maybe_empty_object
+ && (h->object_base
+ == __PTR_ALIGN ((char *) old_chunk, old_chunk->contents,
+ h->alignment_mask)))
+ {
+ new_chunk->prev = old_chunk->prev;
+ CALL_FREEFUN (h, old_chunk);
+ }
+
+ h->object_base = object_base;
+ h->next_free = h->object_base + obj_size;
+ /* The new chunk certainly contains no empty object yet. */
+ h->maybe_empty_object = 0;
+}
+# ifdef _LIBC
+libc_hidden_def (_obstack_newchunk)
+# endif
+
+/* Return nonzero if object OBJ has been allocated from obstack H.
+ This is here for debugging.
+ If you use it in a program, you are probably losing. */
+
+/* Suppress -Wmissing-prototypes warning. We don't want to declare this in
+ obstack.h because it is just for debugging. */
+int _obstack_allocated_p (struct obstack *h, void *obj);
+
+int
+_obstack_allocated_p (struct obstack *h, void *obj)
+{
+ struct _obstack_chunk *lp; /* below addr of any objects in this chunk */
+ struct _obstack_chunk *plp; /* point to previous chunk if any */
+
+ lp = (h)->chunk;
+ /* We use >= rather than > since the object cannot be exactly at
+ the beginning of the chunk but might be an empty object exactly
+ at the end of an adjacent chunk. */
+ while (lp != 0 && ((void *) lp >= obj || (void *) (lp)->limit < obj))
+ {
+ plp = lp->prev;
+ lp = plp;
+ }
+ return lp != 0;
+}
+
+/* Free objects in obstack H, including OBJ and everything allocate
+ more recently than OBJ. If OBJ is zero, free everything in H. */
+
+# undef obstack_free
+
+void
+__obstack_free (struct obstack *h, void *obj)
+{
+ struct _obstack_chunk *lp; /* below addr of any objects in this chunk */
+ struct _obstack_chunk *plp; /* point to previous chunk if any */
+
+ lp = h->chunk;
+ /* We use >= because there cannot be an object at the beginning of a chunk.
+ But there can be an empty object at that address
+ at the end of another chunk. */
+ while (lp != 0 && ((void *) lp >= obj || (void *) (lp)->limit < obj))
+ {
+ plp = lp->prev;
+ CALL_FREEFUN (h, lp);
+ lp = plp;
+ /* If we switch chunks, we can't tell whether the new current
+ chunk contains an empty object, so assume that it may. */
+ h->maybe_empty_object = 1;
+ }
+ if (lp)
+ {
+ h->object_base = h->next_free = (char *) (obj);
+ h->chunk_limit = lp->limit;
+ h->chunk = lp;
+ }
+ else if (obj != 0)
+ /* obj is not in any of the chunks! */
+ abort ();
+}
+
+# ifdef _LIBC
+/* Older versions of libc used a function _obstack_free intended to be
+ called by non-GCC compilers. */
+strong_alias (obstack_free, _obstack_free)
+# endif
+
+size_t
+_obstack_memory_used (struct obstack *h)
+{
+ struct _obstack_chunk* lp;
+ size_t nbytes = 0;
+
+ for (lp = h->chunk; lp != 0; lp = lp->prev)
+ {
+ nbytes += lp->limit - (char *) lp;
+ }
+ return nbytes;
+}
+
+/* Define the error handler. */
+# ifdef _LIBC
+# include <libintl.h>
+# else
+# include "gettext.h"
+# endif
+# ifndef _
+# define _(msgid) gettext (msgid)
+# endif
+
+# ifdef _LIBC
+# include <libio/iolibio.h>
+# endif
+
+# ifndef __attribute__
+/* This feature is available in gcc versions 2.5 and later. */
+# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5)
+# define __attribute__(Spec) /* empty */
+# endif
+# endif
+
+static void
+__attribute__ ((noreturn))
+print_and_abort (void)
+{
+ /* Don't change any of these strings. Yes, it would be possible to add
+ the newline to the string and use fputs or so. But this must not
+ happen because the "memory exhausted" message appears in other places
+ like this and the translation should be reused instead of creating
+ a very similar string which requires a separate translation. */
+# ifdef _LIBC
+ (void) __fxprintf (NULL, "%s\n", _("memory exhausted"));
+# else
+ fprintf (stderr, "%s\n", _("memory exhausted"));
+# endif
+ exit (obstack_exit_failure);
+}
+
+#endif /* !ELIDE_CODE */
diff --git a/lib/compat/obstack.h b/lib/compat/obstack.h
new file mode 100644
index 0000000..6236cf3
--- /dev/null
+++ b/lib/compat/obstack.h
@@ -0,0 +1,509 @@
+/* obstack.h - object stack macros
+ Copyright (C) 1988-1994,1996-1999,2003,2004,2005,2006
+ Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+/* Summary:
+
+All the apparent functions defined here are macros. The idea
+is that you would use these pre-tested macros to solve a
+very specific set of problems, and they would run fast.
+Caution: no side-effects in arguments please!! They may be
+evaluated MANY times!!
+
+These macros operate a stack of objects. Each object starts life
+small, and may grow to maturity. (Consider building a word syllable
+by syllable.) An object can move while it is growing. Once it has
+been "finished" it never changes address again. So the "top of the
+stack" is typically an immature growing object, while the rest of the
+stack is of mature, fixed size and fixed address objects.
+
+These routines grab large chunks of memory, using a function you
+supply, called `obstack_chunk_alloc'. On occasion, they free chunks,
+by calling `obstack_chunk_free'. You must define them and declare
+them before using any obstack macros.
+
+Each independent stack is represented by a `struct obstack'.
+Each of the obstack macros expects a pointer to such a structure
+as the first argument.
+
+One motivation for this package is the problem of growing char strings
+in symbol tables. Unless you are "fascist pig with a read-only mind"
+--Gosper's immortal quote from HAKMEM item 154, out of context--you
+would not like to put any arbitrary upper limit on the length of your
+symbols.
+
+In practice this often means you will build many short symbols and a
+few long symbols. At the time you are reading a symbol you don't know
+how long it is. One traditional method is to read a symbol into a
+buffer, realloc()ating the buffer every time you try to read a symbol
+that is longer than the buffer. This is beaut, but you still will
+want to copy the symbol from the buffer to a more permanent
+symbol-table entry say about half the time.
+
+With obstacks, you can work differently. Use one obstack for all symbol
+names. As you read a symbol, grow the name in the obstack gradually.
+When the name is complete, finalize it. Then, if the symbol exists already,
+free the newly read name.
+
+The way we do this is to take a large chunk, allocating memory from
+low addresses. When you want to build a symbol in the chunk you just
+add chars above the current "high water mark" in the chunk. When you
+have finished adding chars, because you got to the end of the symbol,
+you know how long the chars are, and you can create a new object.
+Mostly the chars will not burst over the highest address of the chunk,
+because you would typically expect a chunk to be (say) 100 times as
+long as an average object.
+
+In case that isn't clear, when we have enough chars to make up
+the object, THEY ARE ALREADY CONTIGUOUS IN THE CHUNK (guaranteed)
+so we just point to it where it lies. No moving of chars is
+needed and this is the second win: potentially long strings need
+never be explicitly shuffled. Once an object is formed, it does not
+change its address during its lifetime.
+
+When the chars burst over a chunk boundary, we allocate a larger
+chunk, and then copy the partly formed object from the end of the old
+chunk to the beginning of the new larger chunk. We then carry on
+accreting characters to the end of the object as we normally would.
+
+A special macro is provided to add a single char at a time to a
+growing object. This allows the use of register variables, which
+break the ordinary 'growth' macro.
+
+Summary:
+ We allocate large chunks.
+ We carve out one object at a time from the current chunk.
+ Once carved, an object never moves.
+ We are free to append data of any size to the currently
+ growing object.
+ Exactly one object is growing in an obstack at any one time.
+ You can run one obstack per control block.
+ You may have as many control blocks as you dare.
+ Because of the way we do it, you can `unwind' an obstack
+ back to a previous state. (You may remove objects much
+ as you would with a stack.)
+*/
+
+
+/* Don't do the contents of this file more than once. */
+
+#ifndef _OBSTACK_H
+#define _OBSTACK_H 1
+
+# include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* If B is the base of an object addressed by P, return the result of
+ aligning P to the next multiple of A + 1. B and P must be of type
+ char *. A + 1 must be a power of 2. */
+
+#define __BPTR_ALIGN(B, P, A) ((B) + (((P) - (B) + (A)) & ~(A)))
+
+/* Similar to _BPTR_ALIGN (B, P, A), except optimize the common case
+ where pointers can be converted to integers, aligned as integers,
+ and converted back again. If ptrdiff_t is narrower than a
+ pointer (e.g., the AS/400), play it safe and compute the alignment
+ relative to B. Otherwise, use the faster strategy of computing the
+ alignment relative to 0. */
+
+#define __PTR_ALIGN(B, P, A) \
+ __BPTR_ALIGN (sizeof (ptrdiff_t) < sizeof (void *) ? (B) : (char *) 0, \
+ P, A)
+
+#include <string.h>
+
+struct _obstack_chunk /* Lives at front of each chunk. */
+{
+ char *limit; /* 1 past end of this chunk */
+ struct _obstack_chunk *prev; /* address of prior chunk or NULL */
+ char contents[4]; /* objects begin here */
+};
+
+struct obstack /* control current object in current chunk */
+{
+ size_t chunk_size; /* preferred size to allocate chunks in */
+ struct _obstack_chunk *chunk; /* address of current struct obstack_chunk */
+ char *object_base; /* address of object we are building */
+ char *next_free; /* where to add next char to current object */
+ char *chunk_limit; /* address of char after current chunk */
+ union
+ {
+ ptrdiff_t tempint;
+ void *tempptr;
+ } temp; /* Temporary for some macros. */
+ size_t alignment_mask; /* Mask of alignment for each object. */
+ /* These prototypes vary based on `use_extra_arg', and we use
+ casts to the prototypeless function type in all assignments,
+ but having prototypes here quiets -Wstrict-prototypes. */
+ union {
+ void *(*arg1) (size_t);
+ void *(*arg2) (void *, size_t);
+ } chunkfun;
+ union {
+ void (*arg1) (void *);
+ void (*arg2) (void *, void *);
+ } freefun;
+ void *extra_arg; /* first arg for chunk alloc/dealloc funcs */
+ unsigned use_extra_arg:1; /* chunk alloc/dealloc funcs take extra arg */
+ unsigned maybe_empty_object:1;/* There is a possibility that the current
+ chunk contains a zero-length object. This
+ prevents freeing the chunk if we allocate
+ a bigger chunk to replace it. */
+ unsigned alloc_failed:1; /* No longer used, as we now call the failed
+ handler on error, but retained for binary
+ compatibility. */
+};
+
+/* Declare the external functions we use; they are in obstack.c. */
+
+extern void _obstack_newchunk (struct obstack *, size_t);
+extern int _obstack_begin (struct obstack *, size_t, size_t,
+ void *(*) (size_t), void (*) (void *));
+extern int _obstack_begin_1 (struct obstack *, size_t, size_t,
+ void *(*) (void *, size_t),
+ void (*) (void *, void *), void *);
+extern size_t _obstack_memory_used (struct obstack *);
+
+/* The default name of the function for freeing a chunk is 'obstack_free',
+ but gnulib users can override this by defining '__obstack_free'. */
+#ifndef __obstack_free
+# define __obstack_free obstack_free
+#endif
+extern void __obstack_free (struct obstack *obstack, void *block);
+
+
+/* Error handler called when `obstack_chunk_alloc' failed to allocate
+ more memory. This can be set to a user defined function which
+ should either abort gracefully or use longjmp - but shouldn't
+ return. The default action is to print a message and abort. */
+extern void (*obstack_alloc_failed_handler) (void);
+
+/* Exit value used when `print_and_abort' is used. */
+extern int obstack_exit_failure;
+
+/* Pointer to beginning of object being allocated or to be allocated next.
+ Note that this might not be the final address of the object
+ because a new chunk might be needed to hold the final size. */
+
+#define obstack_base(h) ((void *) (h)->object_base)
+
+/* Size for allocating ordinary chunks. */
+
+#define obstack_chunk_size(h) ((h)->chunk_size)
+
+/* Pointer to next byte not yet allocated in current chunk. */
+
+#define obstack_next_free(h) ((h)->next_free)
+
+/* Mask specifying low bits that should be clear in address of an object. */
+
+#define obstack_alignment_mask(h) ((h)->alignment_mask)
+
+/* To prevent prototype warnings provide complete argument list. */
+#define obstack_init(h) \
+ _obstack_begin ((h), 0, 0, \
+ (void *(*) (size_t)) obstack_chunk_alloc, \
+ (void (*) (void *)) obstack_chunk_free)
+
+#define obstack_begin(h, size) \
+ _obstack_begin ((h), (size), 0, \
+ (void *(*) (size_t)) obstack_chunk_alloc, \
+ (void (*) (void *)) obstack_chunk_free)
+
+#define obstack_specify_allocation(h, size, alignment, chunkfun, freefun) \
+ _obstack_begin ((h), (size), (alignment), \
+ (void *(*) (size_t)) (chunkfun), \
+ (void (*) (void *)) (freefun))
+
+#define obstack_specify_allocation_with_arg(h, size, alignment, chunkfun, freefun, arg) \
+ _obstack_begin_1 ((h), (size), (alignment), \
+ (void *(*) (void *, size_t)) (chunkfun), \
+ (void (*) (void *, void *)) (freefun), (arg))
+
+#define obstack_chunkfun(h, newchunkfun) \
+ ((h) -> chunkfun.arg2 = (struct _obstack_chunk *(*)(void *, size_t)) (newchunkfun))
+
+#define obstack_freefun(h, newfreefun) \
+ ((h) -> freefun.arg2 = (void (*)(void *, struct _obstack_chunk *)) (newfreefun))
+
+#define obstack_1grow_fast(h,achar) (*((h)->next_free)++ = (achar))
+
+#define obstack_blank_fast(h,n) ((h)->next_free += (n))
+
+#define obstack_memory_used(h) _obstack_memory_used (h)
+
+#if defined __GNUC__ && defined __STDC__ && __STDC__
+/* NextStep 2.0 cc is really gcc 1.93 but it defines __GNUC__ = 2 and
+ does not implement __extension__. But that compiler doesn't define
+ __GNUC_MINOR__. */
+# if __GNUC__ < 2 || (__NeXT__ && !__GNUC_MINOR__)
+# define __extension__
+# endif
+
+/* For GNU C, if not -traditional,
+ we can define these macros to compute all args only once
+ without using a global variable.
+ Also, we can avoid using the `temp' slot, to make faster code. */
+
+# define obstack_object_size(OBSTACK) \
+ __extension__ \
+ ({ struct obstack const *__o = (OBSTACK); \
+ (size_t) (__o->next_free - __o->object_base); })
+
+# define obstack_room(OBSTACK) \
+ __extension__ \
+ ({ struct obstack const *__o = (OBSTACK); \
+ (size_t) (__o->chunk_limit - __o->next_free); })
+
+# define obstack_make_room(OBSTACK,length) \
+__extension__ \
+({ struct obstack *__o = (OBSTACK); \
+ size_t __len = (length); \
+ if ((size_t) (__o->chunk_limit - __o->next_free) < __len) \
+ _obstack_newchunk (__o, __len); \
+ (void) 0; })
+
+# define obstack_empty_p(OBSTACK) \
+ __extension__ \
+ ({ struct obstack const *__o = (OBSTACK); \
+ (__o->chunk->prev == 0 \
+ && __o->next_free == __PTR_ALIGN ((char *) __o->chunk, \
+ __o->chunk->contents, \
+ __o->alignment_mask)); })
+
+# define obstack_grow(OBSTACK,where,length) \
+__extension__ \
+({ struct obstack *__o = (OBSTACK); \
+ size_t __len = (length); \
+ if (__o->next_free + __len > __o->chunk_limit) \
+ _obstack_newchunk (__o, __len); \
+ memcpy (__o->next_free, where, __len); \
+ __o->next_free += __len; \
+ (void) 0; })
+
+# define obstack_grow0(OBSTACK,where,length) \
+__extension__ \
+({ struct obstack *__o = (OBSTACK); \
+ size_t __len = (length); \
+ if (__o->next_free + __len + 1 > __o->chunk_limit) \
+ _obstack_newchunk (__o, __len + 1); \
+ memcpy (__o->next_free, where, __len); \
+ __o->next_free += __len; \
+ *(__o->next_free)++ = 0; \
+ (void) 0; })
+
+# define obstack_1grow(OBSTACK,datum) \
+__extension__ \
+({ struct obstack *__o = (OBSTACK); \
+ if (__o->next_free + 1 > __o->chunk_limit) \
+ _obstack_newchunk (__o, 1); \
+ obstack_1grow_fast (__o, datum); \
+ (void) 0; })
+
+/* These assume that the obstack alignment is good enough for pointers
+ or ints, and that the data added so far to the current object
+ shares that much alignment. */
+
+# define obstack_ptr_grow(OBSTACK,datum) \
+__extension__ \
+({ struct obstack *__o = (OBSTACK); \
+ if (__o->next_free + sizeof (void *) > __o->chunk_limit) \
+ _obstack_newchunk (__o, sizeof (void *)); \
+ obstack_ptr_grow_fast (__o, datum); }) \
+
+# define obstack_int_grow(OBSTACK,datum) \
+__extension__ \
+({ struct obstack *__o = (OBSTACK); \
+ if (__o->next_free + sizeof (int) > __o->chunk_limit) \
+ _obstack_newchunk (__o, sizeof (int)); \
+ obstack_int_grow_fast (__o, datum); })
+
+# define obstack_ptr_grow_fast(OBSTACK,aptr) \
+__extension__ \
+({ struct obstack *__o1 = (OBSTACK); \
+ *(const void **) __o1->next_free = (aptr); \
+ __o1->next_free += sizeof (const void *); \
+ (void) 0; })
+
+# define obstack_int_grow_fast(OBSTACK,aint) \
+__extension__ \
+({ struct obstack *__o1 = (OBSTACK); \
+ *(int *) __o1->next_free = (aint); \
+ __o1->next_free += sizeof (int); \
+ (void) 0; })
+
+# define obstack_blank(OBSTACK,length) \
+__extension__ \
+({ struct obstack *__o = (OBSTACK); \
+ size_t __len = (length); \
+ if ((size_t) (__o->chunk_limit - __o->next_free) < __len) \
+ _obstack_newchunk (__o, __len); \
+ obstack_blank_fast (__o, __len); \
+ (void) 0; })
+
+# define obstack_alloc(OBSTACK,length) \
+__extension__ \
+({ struct obstack *__h = (OBSTACK); \
+ obstack_blank (__h, (length)); \
+ obstack_finish (__h); })
+
+# define obstack_copy(OBSTACK,where,length) \
+__extension__ \
+({ struct obstack *__h = (OBSTACK); \
+ obstack_grow (__h, (where), (length)); \
+ obstack_finish (__h); })
+
+# define obstack_copy0(OBSTACK,where,length) \
+__extension__ \
+({ struct obstack *__h = (OBSTACK); \
+ obstack_grow0 (__h, (where), (length)); \
+ obstack_finish (__h); })
+
+/* The local variable is named __o1 to avoid a name conflict
+ when obstack_blank is called. */
+# define obstack_finish(OBSTACK) \
+__extension__ \
+({ struct obstack *__o1 = (OBSTACK); \
+ void *__value = (void *) __o1->object_base; \
+ if (__o1->next_free == __value) \
+ __o1->maybe_empty_object = 1; \
+ __o1->next_free \
+ = __PTR_ALIGN (__o1->object_base, __o1->next_free, \
+ __o1->alignment_mask); \
+ if ((size_t) (__o1->next_free - (char *)__o1->chunk) \
+ > (size_t) (__o1->chunk_limit - (char *)__o1->chunk)) \
+ __o1->next_free = __o1->chunk_limit; \
+ __o1->object_base = __o1->next_free; \
+ __value; })
+
+# define obstack_free(OBSTACK, OBJ) \
+__extension__ \
+({ struct obstack *__o = (OBSTACK); \
+ void *__obj = (OBJ); \
+ if (__obj > (void *)__o->chunk && __obj < (void *)__o->chunk_limit) \
+ __o->next_free = __o->object_base = (char *)__obj; \
+ else (__obstack_free) (__o, __obj); })
+
+#else /* not __GNUC__ or not __STDC__ */
+
+# define obstack_object_size(h) \
+ (size_t) ((h)->next_free - (h)->object_base)
+
+# define obstack_room(h) \
+ (size_t) ((h)->chunk_limit - (h)->next_free)
+
+# define obstack_empty_p(h) \
+ ((h)->chunk->prev == 0 \
+ && (h)->next_free == __PTR_ALIGN ((char *) (h)->chunk, \
+ (h)->chunk->contents, \
+ (h)->alignment_mask))
+
+/* Note that the call to _obstack_newchunk is enclosed in (..., 0)
+ so that we can avoid having void expressions
+ in the arms of the conditional expression.
+ Casting the third operand to void was tried before,
+ but some compilers won't accept it. */
+
+# define obstack_make_room(h,length) \
+( (h)->temp.tempint = (length), \
+ (((h)->next_free + (h)->temp.tempint > (h)->chunk_limit) \
+ ? (_obstack_newchunk ((h), (h)->temp.tempint), 0) : 0))
+
+# define obstack_grow(h,where,length) \
+( (h)->temp.tempint = (length), \
+ (((h)->next_free + (h)->temp.tempint > (h)->chunk_limit) \
+ ? (_obstack_newchunk ((h), (h)->temp.tempint), 0) : 0), \
+ memcpy ((h)->next_free, where, (h)->temp.tempint), \
+ (h)->next_free += (h)->temp.tempint)
+
+# define obstack_grow0(h,where,length) \
+( (h)->temp.tempint = (length), \
+ (((h)->next_free + (h)->temp.tempint + 1 > (h)->chunk_limit) \
+ ? (_obstack_newchunk ((h), (h)->temp.tempint + 1), 0) : 0), \
+ memcpy ((h)->next_free, where, (h)->temp.tempint), \
+ (h)->next_free += (h)->temp.tempint, \
+ *((h)->next_free)++ = 0)
+
+# define obstack_1grow(h,datum) \
+( (((h)->next_free + 1 > (h)->chunk_limit) \
+ ? (_obstack_newchunk ((h), 1), 0) : 0), \
+ obstack_1grow_fast (h, datum))
+
+# define obstack_ptr_grow(h,datum) \
+( (((h)->next_free + sizeof (char *) > (h)->chunk_limit) \
+ ? (_obstack_newchunk ((h), sizeof (char *)), 0) : 0), \
+ obstack_ptr_grow_fast (h, datum))
+
+# define obstack_int_grow(h,datum) \
+( (((h)->next_free + sizeof (int) > (h)->chunk_limit) \
+ ? (_obstack_newchunk ((h), sizeof (int)), 0) : 0), \
+ obstack_int_grow_fast (h, datum))
+
+# define obstack_ptr_grow_fast(h,aptr) \
+ (((const void **) ((h)->next_free += sizeof (void *)))[-1] = (aptr))
+
+# define obstack_int_grow_fast(h,aint) \
+ (((int *) ((h)->next_free += sizeof (int)))[-1] = (aint))
+
+# define obstack_blank(h,length) \
+( (h)->temp.tempint = (length), \
+ (((size_t) ((h)->chunk_limit - (h)->next_free) < (h)->temp.tempint) \
+ ? (_obstack_newchunk ((h), (h)->temp.tempint), 0) : 0), \
+ obstack_blank_fast (h, (h)->temp.tempint))
+
+# define obstack_alloc(h,length) \
+ (obstack_blank ((h), (length)), obstack_finish ((h)))
+
+# define obstack_copy(h,where,length) \
+ (obstack_grow ((h), (where), (length)), obstack_finish ((h)))
+
+# define obstack_copy0(h,where,length) \
+ (obstack_grow0 ((h), (where), (length)), obstack_finish ((h)))
+
+# define obstack_finish(h) \
+( ((h)->next_free == (h)->object_base \
+ ? (((h)->maybe_empty_object = 1), 0) \
+ : 0), \
+ (h)->temp.tempptr = (h)->object_base, \
+ (h)->next_free \
+ = __PTR_ALIGN ((h)->object_base, (h)->next_free, \
+ (h)->alignment_mask), \
+ (((size_t) ((h)->next_free - (char *) (h)->chunk) \
+ > (size_t) ((h)->chunk_limit - (char *) (h)->chunk)) \
+ ? ((h)->next_free = (h)->chunk_limit) : 0), \
+ (h)->object_base = (h)->next_free, \
+ (h)->temp.tempptr)
+
+# define obstack_free(h,obj) \
+( (h)->temp.tempint = (char *) (obj) - (char *) (h)->chunk, \
+ ((((h)->temp.tempint > 0 \
+ && (h)->temp.tempint < (size_t) ((h)->chunk_limit - (char *) (h)->chunk))) \
+ ? (int) ((h)->next_free = (h)->object_base \
+ = (h)->temp.tempint + (char *) (h)->chunk) \
+ : (((__obstack_free) ((h), (h)->temp.tempint + (char *) (h)->chunk), 0), 0)))
+
+#endif /* not __GNUC__ or not __STDC__ */
+
+#ifdef __cplusplus
+} /* C++ */
+#endif
+
+#endif /* obstack.h */
diff --git a/lib/compat/scandir.c b/lib/compat/scandir.c
new file mode 100644
index 0000000..8771de0
--- /dev/null
+++ b/lib/compat/scandir.c
@@ -0,0 +1,99 @@
+/*
+ * libcompat - system compatibility library
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008, 2009 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+
+#include <string.h>
+#include <dirent.h>
+#include <stdlib.h>
+
+#include "compat.h"
+
+static int
+cleanup(DIR *dir, struct dirent **dirlist, int used)
+{
+ if (dir)
+ closedir(dir);
+
+ if (dirlist) {
+ int i;
+
+ for (i = 0; i < used; i++)
+ free(dirlist[i]);
+ free(dirlist);
+ }
+
+ return -1;
+}
+
+int
+scandir(const char *dir, struct dirent ***namelist,
+ int (*filter)(const struct dirent *),
+ int (*cmp)(const void *, const void *))
+{
+ DIR *d;
+ struct dirent *e, *m, **list;
+ int used, avail;
+
+ d = opendir(dir);
+ if (!d)
+ return -1;
+
+ list = NULL;
+ used = avail = 0;
+
+ while ((e = readdir(d)) != NULL) {
+ if (filter != NULL && !filter(e))
+ continue;
+
+ if (used >= avail - 1) {
+ struct dirent **newlist;
+
+ if (avail)
+ avail *= 2;
+ else
+ avail = 20;
+ newlist = realloc(list, avail * sizeof(*newlist));
+ if (!newlist)
+ return cleanup(d, list, used);
+ list = newlist;
+ }
+
+ m = malloc(sizeof(*m) + strlen(e->d_name));
+ if (!m)
+ return cleanup(d, list, used);
+ *m = *e;
+ strcpy(m->d_name, e->d_name);
+
+ list[used] = m;
+ used++;
+ }
+
+ closedir(d);
+
+ if (list != NULL && cmp != NULL)
+ qsort(list, used, sizeof(list[0]), cmp);
+
+ *namelist = list;
+
+ return used;
+}
diff --git a/lib/compat/snprintf.c b/lib/compat/snprintf.c
new file mode 100644
index 0000000..cf15442
--- /dev/null
+++ b/lib/compat/snprintf.c
@@ -0,0 +1,39 @@
+/*
+ * libcompat - system compatibility library
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stddef.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "compat.h"
+
+int
+snprintf(char *str, size_t n, char const *fmt, ...)
+{
+ va_list args;
+ int i;
+
+ va_start(args, fmt);
+ i = vsnprintf(str, n, fmt, args);
+ va_end(args);
+
+ return i;
+}
diff --git a/lib/compat/strchrnul.c b/lib/compat/strchrnul.c
new file mode 100644
index 0000000..b072e1a
--- /dev/null
+++ b/lib/compat/strchrnul.c
@@ -0,0 +1,37 @@
+/*
+ * libcompat - system compatibility library
+ *
+ * Copyright © 2018 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "compat.h"
+
+char *
+strchrnul(const char *s, int c)
+{
+ char *match;
+
+ match = strchr(s, c);
+ if (match)
+ return match;
+
+ return (char *)s + strlen(s);
+}
diff --git a/lib/compat/strerror.c b/lib/compat/strerror.c
new file mode 100644
index 0000000..e35ffc1
--- /dev/null
+++ b/lib/compat/strerror.c
@@ -0,0 +1,48 @@
+/*
+ * libcompat - system compatibility library
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <gettext.h>
+
+#include "compat.h"
+
+#define _(str) gettext(str)
+
+#if !HAVE_DECL_SYS_ERRLIST
+extern const char *const sys_errlist[];
+#endif
+#if !HAVE_DECL_SYS_NERR
+extern const int sys_nerr;
+#endif
+
+const char *
+strerror(int e)
+{
+ static char buf[100];
+
+ if (e >= 0 && e < sys_nerr)
+ return sys_errlist[e];
+
+ sprintf(buf, _("Unknown error %d"), e);
+
+ return buf;
+}
diff --git a/lib/compat/strndup.c b/lib/compat/strndup.c
new file mode 100644
index 0000000..788c013
--- /dev/null
+++ b/lib/compat/strndup.c
@@ -0,0 +1,42 @@
+/*
+ * libcompat - system compatibility library
+ *
+ * Copyright © 2011 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "compat.h"
+
+char *
+strndup(const char *s, size_t n)
+{
+ size_t len;
+ char *str;
+
+ len = strnlen(s, n);
+ str = malloc(len + 1);
+ if (str == NULL)
+ return NULL;
+
+ memcpy(str, s, len);
+ str[len] = '\0';
+
+ return str;
+}
diff --git a/lib/compat/strnlen.c b/lib/compat/strnlen.c
new file mode 100644
index 0000000..d02bb4b
--- /dev/null
+++ b/lib/compat/strnlen.c
@@ -0,0 +1,33 @@
+/* Find the length of STRING, but scan at most MAXLEN characters.
+ Copyright (C) 2005, 2006 Free Software Foundation, Inc.
+ Written by Simon Josefsson.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "compat.h"
+
+/* Find the length of STRING, but scan at most MAXLEN characters.
+ If no '\0' terminator is found in that many characters, return MAXLEN. */
+
+size_t
+strnlen (const char *string, size_t maxlen)
+{
+ const char *end = memchr (string, '\0', maxlen);
+ return end ? (size_t) (end - string) : maxlen;
+}
diff --git a/lib/compat/strsignal.c b/lib/compat/strsignal.c
new file mode 100644
index 0000000..66ed0c3
--- /dev/null
+++ b/lib/compat/strsignal.c
@@ -0,0 +1,76 @@
+/*
+ * libcompat - system compatibility library
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <signal.h>
+#include <string.h>
+#include <stdio.h>
+#include <gettext.h>
+
+#include "compat.h"
+
+#define _(str) gettext(str)
+
+#if !HAVE_DECL_SYS_SIGLIST
+const char *const sys_siglist[] = {
+ NULL, /* 0 */
+ "SIGHUP", /* 1 */
+ "SIGINT", /* 2 */
+ "SIGQUIT", /* 3 */
+ "SIGILL", /* 4 */
+ "SIGTRAP", /* 5 */
+ "SIGABRT", /* 6 */
+ "SIGEMT", /* 7 */
+ "SIGFPE", /* 8 */
+ "SIGKILL", /* 9 */
+ "SIGUSR1", /* 10 */
+ "SIGSEGV", /* 11 */
+ "SIGUSR2", /* 12 */
+ "SIGPIPE", /* 13 */
+ "SIGALRM", /* 14 */
+ "SIGTERM", /* 15 */
+ "SIGSTKFLT", /* 16 */
+ "SIGCHLD", /* 17 */
+ "SIGCONT", /* 18 */
+ "SIGSTOP", /* 19 */
+ "SIGTSTP", /* 20 */
+ "SIGTTIN", /* 21 */
+ "SIGTTOU", /* 22 */
+};
+# define COMPAT_NSIGLIST (int)(sizeof(sys_siglist) / sizeof(sys_siglist[0]))
+#else
+# ifndef NSIG
+# define NSIG 32
+# endif
+# define COMPAT_NSIGLIST NSIG
+#endif
+
+const char *
+strsignal(int s)
+{
+ static char buf[100];
+
+ if (s > 0 && s < COMPAT_NSIGLIST)
+ return sys_siglist[s];
+
+ sprintf(buf, _("Unknown signal %d"), s);
+
+ return buf;
+}
diff --git a/lib/compat/unsetenv.c b/lib/compat/unsetenv.c
new file mode 100644
index 0000000..4094ae3
--- /dev/null
+++ b/lib/compat/unsetenv.c
@@ -0,0 +1,39 @@
+/*
+ * libcompat - system compatibility library
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "compat.h"
+
+int
+unsetenv(const char *p)
+{
+ char *q;
+
+ q = malloc(strlen(p) + 3);
+ if (!q)
+ return -1;
+
+ strcpy(q, p);
+ strcat(q, "=");
+ return putenv(q);
+}
diff --git a/lib/compat/vasprintf.c b/lib/compat/vasprintf.c
new file mode 100644
index 0000000..9d53a32
--- /dev/null
+++ b/lib/compat/vasprintf.c
@@ -0,0 +1,59 @@
+/*
+ * libcompat - system compatibility library
+ *
+ * Copyright © 2010 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "compat.h"
+
+int
+vasprintf(char **strp, char const *fmt, va_list args)
+{
+ va_list args_copy;
+ int needed, n;
+ char *str;
+
+ va_copy(args_copy, args);
+ needed = vsnprintf(NULL, 0, fmt, args_copy);
+ va_end(args_copy);
+
+ if (needed < 0) {
+ *strp = NULL;
+ return -1;
+ }
+
+ str = malloc(needed + 1);
+ if (str == NULL) {
+ *strp = NULL;
+ return -1;
+ }
+
+ n = vsnprintf(str, needed + 1, fmt, args);
+ if (n < 0) {
+ free(str);
+ str = NULL;
+ }
+
+ *strp = str;
+
+ return n;
+}
diff --git a/lib/compat/vsnprintf.c b/lib/compat/vsnprintf.c
new file mode 100644
index 0000000..5f709d9
--- /dev/null
+++ b/lib/compat/vsnprintf.c
@@ -0,0 +1,81 @@
+/*
+ * libcompat - system compatibility library
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008-2012 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+
+#include <unistd.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "compat.h"
+
+int
+vsnprintf(char *buf, size_t maxsize, const char *fmt, va_list args)
+{
+ static FILE *file = NULL;
+ static pid_t file_pid;
+
+ size_t want, nr;
+ int total;
+
+ if (maxsize != 0 && buf == NULL)
+ return -1;
+
+ /* Avoid race conditions from children after a fork(2). */
+ if (file_pid > 0 && file_pid != getpid()) {
+ fclose(file);
+ file = NULL;
+ }
+
+ if (!file) {
+ file = tmpfile();
+ if (!file)
+ return -1;
+ file_pid = getpid();
+ } else {
+ if (fseek(file, 0, 0))
+ return -1;
+ if (ftruncate(fileno(file), 0))
+ return -1;
+ }
+
+ total = vfprintf(file, fmt, args);
+ if (total < 0)
+ return -1;
+ if (maxsize == 0)
+ return total;
+ if (total >= (int)maxsize)
+ want = maxsize - 1;
+ else
+ want = total;
+ if (fflush(file))
+ return -1;
+ if (fseek(file, 0, SEEK_SET))
+ return -1;
+
+ nr = fread(buf, 1, want, file);
+ if (nr != want)
+ return -1;
+ buf[want] = '\0';
+
+ return total;
+}
diff --git a/lib/dpkg/Makefile.am b/lib/dpkg/Makefile.am
new file mode 100644
index 0000000..9482e32
--- /dev/null
+++ b/lib/dpkg/Makefile.am
@@ -0,0 +1,296 @@
+## Process this file with automake to produce Makefile.in
+
+AM_CPPFLAGS = \
+ -DLOCALEDIR=\"$(localedir)\" \
+ -DCONFIGDIR=\"$(pkgconfdir)\" \
+ -DADMINDIR=\"$(admindir)\" \
+ -DDEFAULT_TEXT_DOMAIN=\"@PACKAGE@\" \
+ -idirafter $(top_srcdir)/lib/compat \
+ -I$(top_builddir) \
+ -I$(top_srcdir)/lib \
+ # EOL
+
+# Needed for the test suite
+LDADD = \
+ libdpkg.la \
+ $(LIBINTL) \
+ ../compat/libcompat.la \
+ # EOL
+
+DISTCLEANFILES =
+
+EXTRA_DIST = \
+ $(test_scripts) \
+ $(test_data) \
+ libdpkg.map \
+ libdpkg.pc.in \
+ # EOL
+
+pkgconfigdir = $(devlibdir)/pkgconfig
+pkgconfig_DATA = libdpkg.pc
+
+devlib_LTLIBRARIES = libdpkg.la
+
+EXTRA_libdpkg_la_DEPENDENCIES = \
+ libdpkg.map \
+ # EOL
+
+libdpkg_la_LDFLAGS = \
+ -no-undefined \
+ # EOL
+if HAVE_LINKER_VERSION_SCRIPT
+libdpkg_la_LDFLAGS += \
+ -Wl,--version-script=$(srcdir)/libdpkg.map \
+ # EOL
+else
+libdpkg_la_LDFLAGS += \
+ -export-symbols libdpkg.sym \
+ # EOL
+EXTRA_libdpkg_la_DEPENDENCIES += \
+ libdpkg.sym \
+ # EOL
+endif
+libdpkg_la_LDFLAGS += $(MD_LIBS)
+libdpkg_la_LIBADD = \
+ ../compat/libcompat.la \
+ # EOL
+if BUILD_SHARED
+libdpkg_la_LIBADD += \
+ $(LIBINTL) \
+ $(Z_LIBS) \
+ $(LZMA_LIBS) \
+ $(ZSTD_LIBS) \
+ $(BZ2_LIBS) \
+ # EOL
+endif
+libdpkg_la_SOURCES = \
+ dlist.h \
+ ar.c \
+ arch.c \
+ atomic-file.c \
+ buffer.c \
+ c-ctype.c \
+ cleanup.c \
+ color.c \
+ command.c \
+ compress.c \
+ dbdir.c \
+ dbmodify.c \
+ db-ctrl-access.c \
+ db-ctrl-format.c \
+ db-ctrl-upgrade.c \
+ db-fsys-digest.c \
+ db-fsys-divert.c \
+ db-fsys-files.c \
+ db-fsys-override.c \
+ deb-version.c \
+ debug.c \
+ depcon.c \
+ dir.c \
+ dump.c \
+ ehandle.c \
+ error.c \
+ fdio.c \
+ file.c \
+ fields.c \
+ fsys-dir.c\
+ fsys-iter.c \
+ fsys-hash.c \
+ glob.c \
+ i18n.c i18n.h \
+ log.c \
+ meminfo.c \
+ mustlib.c \
+ namevalue.c \
+ nfmalloc.c \
+ options.c \
+ options-dirs.c \
+ options-parsers.c \
+ pager.c \
+ parse.c \
+ parsehelp.c \
+ path.c \
+ path-remove.c \
+ perf.h \
+ pkg.c \
+ pkg-array.c \
+ pkg-files.c \
+ pkg-format.c \
+ pkg-hash.c \
+ pkg-list.c \
+ pkg-namevalue.c \
+ pkg-queue.c \
+ pkg-show.c \
+ pkg-spec.c \
+ progname.c \
+ program.c \
+ progress.c \
+ report.c \
+ string.c \
+ strhash.c \
+ strwide.c \
+ subproc.c \
+ tarfn.c \
+ test.h \
+ treewalk.c \
+ trigname.c \
+ trignote.c \
+ triglib.c \
+ trigdeferred.c \
+ utils.c \
+ varbuf.c \
+ version.c \
+ # EOL
+
+pkginclude_HEADERS = \
+ ar.h \
+ arch.h \
+ atomic-file.h \
+ buffer.h \
+ c-ctype.h \
+ color.h \
+ command.h \
+ compress.h \
+ db-ctrl.h \
+ db-fsys.h \
+ deb-version.h \
+ debug.h \
+ dir.h \
+ dpkg.h \
+ dpkg-db.h \
+ ehandle.h \
+ error.h \
+ fdio.h \
+ file.h \
+ fsys.h \
+ glob.h \
+ macros.h \
+ meminfo.h \
+ namevalue.h \
+ options.h \
+ pager.h \
+ parsedump.h \
+ path.h \
+ pkg.h \
+ pkg-array.h \
+ pkg-files.h \
+ pkg-format.h \
+ pkg-list.h \
+ pkg-queue.h \
+ pkg-show.h \
+ pkg-spec.h \
+ progname.h \
+ program.h \
+ progress.h \
+ report.h \
+ string.h \
+ subproc.h \
+ tarfn.h \
+ treewalk.h \
+ trigdeferred.h \
+ triglib.h \
+ varbuf.h \
+ version.h \
+ # EOL
+
+DISTCLEANFILES += \
+ libdpkg.sym \
+ # EOL
+
+# Generate a simple libtool symbol export list to be used as a fallback if
+# there is no version script support.
+libdpkg.sym: libdpkg.map
+ $(AM_V_GEN) $(SED) -n \
+ -e 's/#.*$$//g' \
+ -e 's/^[[:space:]]\{1,\}\([A-Za-z0-9_]\{1,\}\);/\1/p' \
+ $(srcdir)/libdpkg.map \
+ >$@
+
+t_t_headers_cpp_SOURCES = \
+ t/t-headers-cpp.cc \
+ # EOL
+
+# The tests are sorted in order of increasing complexity.
+test_programs = \
+ t/t-test \
+ t/t-test-skip \
+ t/t-macros \
+ t/t-headers-cpp \
+ t/t-c-ctype \
+ t/t-namevalue \
+ t/t-ehandle \
+ t/t-error \
+ t/t-string \
+ t/t-file \
+ t/t-buffer \
+ t/t-meminfo \
+ t/t-path \
+ t/t-progname \
+ t/t-subproc \
+ t/t-command \
+ t/t-pager \
+ t/t-varbuf \
+ t/t-ar \
+ t/t-tar \
+ t/t-deb-version \
+ t/t-arch \
+ t/t-version \
+ t/t-pkginfo \
+ t/t-pkg-list \
+ t/t-pkg-queue \
+ t/t-pkg-hash \
+ t/t-pkg-show \
+ t/t-pkg-format \
+ t/t-fsys-dir \
+ t/t-fsys-hash \
+ t/t-trigger \
+ t/t-mod-db \
+ # EOL
+
+test_scripts = \
+ t/t-tarextract.t \
+ t/t-treewalk.t \
+ t/t-trigdeferred.t \
+ # EOL
+
+test_data = \
+ t/data/command/path-a/cmd \
+ t/data/command/path-a/cmd-a \
+ t/data/command/path-b/cmd \
+ t/data/command/path-b/cmd-b \
+ t/data/command/path-noexec/cmd \
+ t/data/command/path-noexec/cmd-noexec \
+ t/data/meminfo-no-data \
+ t/data/meminfo-no-info \
+ t/data/meminfo-no-unit \
+ t/data/meminfo-ok \
+ # EOL
+
+BENCHMARK_LDADD_FLAGS = \
+ $(RT_LIBS) \
+ $(LDADD) \
+ # EOL
+
+t_b_fsys_hash_LDADD = $(BENCHMARK_LDADD_FLAGS)
+t_b_pkg_hash_LDADD = $(BENCHMARK_LDADD_FLAGS)
+
+check_PROGRAMS = \
+ $(test_programs) \
+ t/b-fsys-hash \
+ t/b-pkg-hash \
+ t/c-tarextract \
+ t/c-treewalk \
+ t/c-trigdeferred \
+ # EOL
+
+TEST_ENV_VARS = \
+ DPKG_PROGTAR=$(TAR) \
+ # EOL
+
+test_tmpdir = t.tmp
+
+include $(top_srcdir)/build-aux/tap.am
+
+check-local: tap-check
+
+clean-local: tap-clean
diff --git a/lib/dpkg/Makefile.in b/lib/dpkg/Makefile.in
new file mode 100644
index 0000000..a03e3d3
--- /dev/null
+++ b/lib/dpkg/Makefile.in
@@ -0,0 +1,1991 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 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@
+
+# Variables to be defined:
+#
+# TEST_VERBOSE - set to 0 (default) or 1 to control test suite verbosity
+# TEST_PARALLEL - set to 1 (default) or N to control the parallel jobs
+# TEST_ENV_VARS - environment variables to be set for the test suite
+# TEST_COVERAGE - set to the perl module in charge of getting test coverage
+# test_tmpdir - test suite temporary directory
+# test_scripts - list of test case scripts
+# test_programs - list of test case programs
+# test_data - list of test data files
+
+
+
+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@
+@HAVE_LINKER_VERSION_SCRIPT_TRUE@am__append_1 = \
+@HAVE_LINKER_VERSION_SCRIPT_TRUE@ -Wl,--version-script=$(srcdir)/libdpkg.map \
+@HAVE_LINKER_VERSION_SCRIPT_TRUE@ # EOL
+
+@HAVE_LINKER_VERSION_SCRIPT_FALSE@am__append_2 = \
+@HAVE_LINKER_VERSION_SCRIPT_FALSE@ -export-symbols libdpkg.sym \
+@HAVE_LINKER_VERSION_SCRIPT_FALSE@ # EOL
+
+@HAVE_LINKER_VERSION_SCRIPT_FALSE@am__append_3 = \
+@HAVE_LINKER_VERSION_SCRIPT_FALSE@ libdpkg.sym \
+@HAVE_LINKER_VERSION_SCRIPT_FALSE@ # EOL
+
+@BUILD_SHARED_TRUE@am__append_4 = \
+@BUILD_SHARED_TRUE@ $(LIBINTL) \
+@BUILD_SHARED_TRUE@ $(Z_LIBS) \
+@BUILD_SHARED_TRUE@ $(LZMA_LIBS) \
+@BUILD_SHARED_TRUE@ $(ZSTD_LIBS) \
+@BUILD_SHARED_TRUE@ $(BZ2_LIBS) \
+@BUILD_SHARED_TRUE@ # EOL
+
+check_PROGRAMS = $(am__EXEEXT_1) t/b-fsys-hash$(EXEEXT) \
+ t/b-pkg-hash$(EXEEXT) t/c-tarextract$(EXEEXT) \
+ t/c-treewalk$(EXEEXT) t/c-trigdeferred$(EXEEXT)
+subdir = lib/dpkg
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dpkg-arch.m4 \
+ $(top_srcdir)/m4/dpkg-build.m4 \
+ $(top_srcdir)/m4/dpkg-compiler.m4 \
+ $(top_srcdir)/m4/dpkg-coverage.m4 \
+ $(top_srcdir)/m4/dpkg-funcs.m4 \
+ $(top_srcdir)/m4/dpkg-headers.m4 $(top_srcdir)/m4/dpkg-libs.m4 \
+ $(top_srcdir)/m4/dpkg-linker.m4 $(top_srcdir)/m4/dpkg-progs.m4 \
+ $(top_srcdir)/m4/dpkg-types.m4 \
+ $(top_srcdir)/m4/dpkg-unicode.m4 $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/host-cpu-c-abi.m4 $(top_srcdir)/m4/iconv.m4 \
+ $(top_srcdir)/m4/intlmacosx.m4 $(top_srcdir)/m4/lib-ld.m4 \
+ $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \
+ $(top_srcdir)/m4/libtool.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/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(pkginclude_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES = libdpkg.pc
+CONFIG_CLEAN_VPATH_FILES =
+am__EXEEXT_1 = t/t-test$(EXEEXT) t/t-test-skip$(EXEEXT) \
+ t/t-macros$(EXEEXT) t/t-headers-cpp$(EXEEXT) \
+ t/t-c-ctype$(EXEEXT) t/t-namevalue$(EXEEXT) \
+ t/t-ehandle$(EXEEXT) t/t-error$(EXEEXT) t/t-string$(EXEEXT) \
+ t/t-file$(EXEEXT) t/t-buffer$(EXEEXT) t/t-meminfo$(EXEEXT) \
+ t/t-path$(EXEEXT) t/t-progname$(EXEEXT) t/t-subproc$(EXEEXT) \
+ t/t-command$(EXEEXT) t/t-pager$(EXEEXT) t/t-varbuf$(EXEEXT) \
+ t/t-ar$(EXEEXT) t/t-tar$(EXEEXT) t/t-deb-version$(EXEEXT) \
+ t/t-arch$(EXEEXT) t/t-version$(EXEEXT) t/t-pkginfo$(EXEEXT) \
+ t/t-pkg-list$(EXEEXT) t/t-pkg-queue$(EXEEXT) \
+ t/t-pkg-hash$(EXEEXT) t/t-pkg-show$(EXEEXT) \
+ t/t-pkg-format$(EXEEXT) t/t-fsys-dir$(EXEEXT) \
+ t/t-fsys-hash$(EXEEXT) t/t-trigger$(EXEEXT) \
+ t/t-mod-db$(EXEEXT)
+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)$(devlibdir)" "$(DESTDIR)$(pkgconfigdir)" \
+ "$(DESTDIR)$(pkgincludedir)"
+LTLIBRARIES = $(devlib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+@BUILD_SHARED_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) \
+@BUILD_SHARED_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+@BUILD_SHARED_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+libdpkg_la_DEPENDENCIES = ../compat/libcompat.la $(am__DEPENDENCIES_2)
+am_libdpkg_la_OBJECTS = ar.lo arch.lo atomic-file.lo buffer.lo \
+ c-ctype.lo cleanup.lo color.lo command.lo compress.lo dbdir.lo \
+ dbmodify.lo db-ctrl-access.lo db-ctrl-format.lo \
+ db-ctrl-upgrade.lo db-fsys-digest.lo db-fsys-divert.lo \
+ db-fsys-files.lo db-fsys-override.lo deb-version.lo debug.lo \
+ depcon.lo dir.lo dump.lo ehandle.lo error.lo fdio.lo file.lo \
+ fields.lo fsys-dir.lo fsys-iter.lo fsys-hash.lo glob.lo \
+ i18n.lo log.lo meminfo.lo mustlib.lo namevalue.lo nfmalloc.lo \
+ options.lo options-dirs.lo options-parsers.lo pager.lo \
+ parse.lo parsehelp.lo path.lo path-remove.lo pkg.lo \
+ pkg-array.lo pkg-files.lo pkg-format.lo pkg-hash.lo \
+ pkg-list.lo pkg-namevalue.lo pkg-queue.lo pkg-show.lo \
+ pkg-spec.lo progname.lo program.lo progress.lo report.lo \
+ string.lo strhash.lo strwide.lo subproc.lo tarfn.lo \
+ treewalk.lo trigname.lo trignote.lo triglib.lo trigdeferred.lo \
+ utils.lo varbuf.lo version.lo
+libdpkg_la_OBJECTS = $(am_libdpkg_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 =
+libdpkg_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(libdpkg_la_LDFLAGS) $(LDFLAGS) -o $@
+t_b_fsys_hash_SOURCES = t/b-fsys-hash.c
+am__dirstamp = $(am__leading_dot)dirstamp
+t_b_fsys_hash_OBJECTS = t/b-fsys-hash.$(OBJEXT)
+am__DEPENDENCIES_3 = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+am__DEPENDENCIES_4 = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_3)
+t_b_fsys_hash_DEPENDENCIES = $(am__DEPENDENCIES_4)
+t_b_pkg_hash_SOURCES = t/b-pkg-hash.c
+t_b_pkg_hash_OBJECTS = t/b-pkg-hash.$(OBJEXT)
+t_b_pkg_hash_DEPENDENCIES = $(am__DEPENDENCIES_4)
+t_c_tarextract_SOURCES = t/c-tarextract.c
+t_c_tarextract_OBJECTS = t/c-tarextract.$(OBJEXT)
+t_c_tarextract_LDADD = $(LDADD)
+t_c_tarextract_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_c_treewalk_SOURCES = t/c-treewalk.c
+t_c_treewalk_OBJECTS = t/c-treewalk.$(OBJEXT)
+t_c_treewalk_LDADD = $(LDADD)
+t_c_treewalk_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_c_trigdeferred_SOURCES = t/c-trigdeferred.c
+t_c_trigdeferred_OBJECTS = t/c-trigdeferred.$(OBJEXT)
+t_c_trigdeferred_LDADD = $(LDADD)
+t_c_trigdeferred_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_ar_SOURCES = t/t-ar.c
+t_t_ar_OBJECTS = t/t-ar.$(OBJEXT)
+t_t_ar_LDADD = $(LDADD)
+t_t_ar_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_arch_SOURCES = t/t-arch.c
+t_t_arch_OBJECTS = t/t-arch.$(OBJEXT)
+t_t_arch_LDADD = $(LDADD)
+t_t_arch_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_buffer_SOURCES = t/t-buffer.c
+t_t_buffer_OBJECTS = t/t-buffer.$(OBJEXT)
+t_t_buffer_LDADD = $(LDADD)
+t_t_buffer_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_c_ctype_SOURCES = t/t-c-ctype.c
+t_t_c_ctype_OBJECTS = t/t-c-ctype.$(OBJEXT)
+t_t_c_ctype_LDADD = $(LDADD)
+t_t_c_ctype_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_command_SOURCES = t/t-command.c
+t_t_command_OBJECTS = t/t-command.$(OBJEXT)
+t_t_command_LDADD = $(LDADD)
+t_t_command_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_deb_version_SOURCES = t/t-deb-version.c
+t_t_deb_version_OBJECTS = t/t-deb-version.$(OBJEXT)
+t_t_deb_version_LDADD = $(LDADD)
+t_t_deb_version_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_ehandle_SOURCES = t/t-ehandle.c
+t_t_ehandle_OBJECTS = t/t-ehandle.$(OBJEXT)
+t_t_ehandle_LDADD = $(LDADD)
+t_t_ehandle_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_error_SOURCES = t/t-error.c
+t_t_error_OBJECTS = t/t-error.$(OBJEXT)
+t_t_error_LDADD = $(LDADD)
+t_t_error_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_file_SOURCES = t/t-file.c
+t_t_file_OBJECTS = t/t-file.$(OBJEXT)
+t_t_file_LDADD = $(LDADD)
+t_t_file_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_fsys_dir_SOURCES = t/t-fsys-dir.c
+t_t_fsys_dir_OBJECTS = t/t-fsys-dir.$(OBJEXT)
+t_t_fsys_dir_LDADD = $(LDADD)
+t_t_fsys_dir_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_fsys_hash_SOURCES = t/t-fsys-hash.c
+t_t_fsys_hash_OBJECTS = t/t-fsys-hash.$(OBJEXT)
+t_t_fsys_hash_LDADD = $(LDADD)
+t_t_fsys_hash_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+am_t_t_headers_cpp_OBJECTS = t/t-headers-cpp.$(OBJEXT)
+t_t_headers_cpp_OBJECTS = $(am_t_t_headers_cpp_OBJECTS)
+t_t_headers_cpp_LDADD = $(LDADD)
+t_t_headers_cpp_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_macros_SOURCES = t/t-macros.c
+t_t_macros_OBJECTS = t/t-macros.$(OBJEXT)
+t_t_macros_LDADD = $(LDADD)
+t_t_macros_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_meminfo_SOURCES = t/t-meminfo.c
+t_t_meminfo_OBJECTS = t/t-meminfo.$(OBJEXT)
+t_t_meminfo_LDADD = $(LDADD)
+t_t_meminfo_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_mod_db_SOURCES = t/t-mod-db.c
+t_t_mod_db_OBJECTS = t/t-mod-db.$(OBJEXT)
+t_t_mod_db_LDADD = $(LDADD)
+t_t_mod_db_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_namevalue_SOURCES = t/t-namevalue.c
+t_t_namevalue_OBJECTS = t/t-namevalue.$(OBJEXT)
+t_t_namevalue_LDADD = $(LDADD)
+t_t_namevalue_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_pager_SOURCES = t/t-pager.c
+t_t_pager_OBJECTS = t/t-pager.$(OBJEXT)
+t_t_pager_LDADD = $(LDADD)
+t_t_pager_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_path_SOURCES = t/t-path.c
+t_t_path_OBJECTS = t/t-path.$(OBJEXT)
+t_t_path_LDADD = $(LDADD)
+t_t_path_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_pkg_format_SOURCES = t/t-pkg-format.c
+t_t_pkg_format_OBJECTS = t/t-pkg-format.$(OBJEXT)
+t_t_pkg_format_LDADD = $(LDADD)
+t_t_pkg_format_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_pkg_hash_SOURCES = t/t-pkg-hash.c
+t_t_pkg_hash_OBJECTS = t/t-pkg-hash.$(OBJEXT)
+t_t_pkg_hash_LDADD = $(LDADD)
+t_t_pkg_hash_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_pkg_list_SOURCES = t/t-pkg-list.c
+t_t_pkg_list_OBJECTS = t/t-pkg-list.$(OBJEXT)
+t_t_pkg_list_LDADD = $(LDADD)
+t_t_pkg_list_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_pkg_queue_SOURCES = t/t-pkg-queue.c
+t_t_pkg_queue_OBJECTS = t/t-pkg-queue.$(OBJEXT)
+t_t_pkg_queue_LDADD = $(LDADD)
+t_t_pkg_queue_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_pkg_show_SOURCES = t/t-pkg-show.c
+t_t_pkg_show_OBJECTS = t/t-pkg-show.$(OBJEXT)
+t_t_pkg_show_LDADD = $(LDADD)
+t_t_pkg_show_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_pkginfo_SOURCES = t/t-pkginfo.c
+t_t_pkginfo_OBJECTS = t/t-pkginfo.$(OBJEXT)
+t_t_pkginfo_LDADD = $(LDADD)
+t_t_pkginfo_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_progname_SOURCES = t/t-progname.c
+t_t_progname_OBJECTS = t/t-progname.$(OBJEXT)
+t_t_progname_LDADD = $(LDADD)
+t_t_progname_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_string_SOURCES = t/t-string.c
+t_t_string_OBJECTS = t/t-string.$(OBJEXT)
+t_t_string_LDADD = $(LDADD)
+t_t_string_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_subproc_SOURCES = t/t-subproc.c
+t_t_subproc_OBJECTS = t/t-subproc.$(OBJEXT)
+t_t_subproc_LDADD = $(LDADD)
+t_t_subproc_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_tar_SOURCES = t/t-tar.c
+t_t_tar_OBJECTS = t/t-tar.$(OBJEXT)
+t_t_tar_LDADD = $(LDADD)
+t_t_tar_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_test_SOURCES = t/t-test.c
+t_t_test_OBJECTS = t/t-test.$(OBJEXT)
+t_t_test_LDADD = $(LDADD)
+t_t_test_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_test_skip_SOURCES = t/t-test-skip.c
+t_t_test_skip_OBJECTS = t/t-test-skip.$(OBJEXT)
+t_t_test_skip_LDADD = $(LDADD)
+t_t_test_skip_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_trigger_SOURCES = t/t-trigger.c
+t_t_trigger_OBJECTS = t/t-trigger.$(OBJEXT)
+t_t_trigger_LDADD = $(LDADD)
+t_t_trigger_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_varbuf_SOURCES = t/t-varbuf.c
+t_t_varbuf_OBJECTS = t/t-varbuf.$(OBJEXT)
+t_t_varbuf_LDADD = $(LDADD)
+t_t_varbuf_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+t_t_version_SOURCES = t/t-version.c
+t_t_version_OBJECTS = t/t-version.$(OBJEXT)
+t_t_version_LDADD = $(LDADD)
+t_t_version_DEPENDENCIES = libdpkg.la $(am__DEPENDENCIES_1) \
+ ../compat/libcompat.la
+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 =
+depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/ar.Plo ./$(DEPDIR)/arch.Plo \
+ ./$(DEPDIR)/atomic-file.Plo ./$(DEPDIR)/buffer.Plo \
+ ./$(DEPDIR)/c-ctype.Plo ./$(DEPDIR)/cleanup.Plo \
+ ./$(DEPDIR)/color.Plo ./$(DEPDIR)/command.Plo \
+ ./$(DEPDIR)/compress.Plo ./$(DEPDIR)/db-ctrl-access.Plo \
+ ./$(DEPDIR)/db-ctrl-format.Plo ./$(DEPDIR)/db-ctrl-upgrade.Plo \
+ ./$(DEPDIR)/db-fsys-digest.Plo ./$(DEPDIR)/db-fsys-divert.Plo \
+ ./$(DEPDIR)/db-fsys-files.Plo ./$(DEPDIR)/db-fsys-override.Plo \
+ ./$(DEPDIR)/dbdir.Plo ./$(DEPDIR)/dbmodify.Plo \
+ ./$(DEPDIR)/deb-version.Plo ./$(DEPDIR)/debug.Plo \
+ ./$(DEPDIR)/depcon.Plo ./$(DEPDIR)/dir.Plo \
+ ./$(DEPDIR)/dump.Plo ./$(DEPDIR)/ehandle.Plo \
+ ./$(DEPDIR)/error.Plo ./$(DEPDIR)/fdio.Plo \
+ ./$(DEPDIR)/fields.Plo ./$(DEPDIR)/file.Plo \
+ ./$(DEPDIR)/fsys-dir.Plo ./$(DEPDIR)/fsys-hash.Plo \
+ ./$(DEPDIR)/fsys-iter.Plo ./$(DEPDIR)/glob.Plo \
+ ./$(DEPDIR)/i18n.Plo ./$(DEPDIR)/log.Plo \
+ ./$(DEPDIR)/meminfo.Plo ./$(DEPDIR)/mustlib.Plo \
+ ./$(DEPDIR)/namevalue.Plo ./$(DEPDIR)/nfmalloc.Plo \
+ ./$(DEPDIR)/options-dirs.Plo ./$(DEPDIR)/options-parsers.Plo \
+ ./$(DEPDIR)/options.Plo ./$(DEPDIR)/pager.Plo \
+ ./$(DEPDIR)/parse.Plo ./$(DEPDIR)/parsehelp.Plo \
+ ./$(DEPDIR)/path-remove.Plo ./$(DEPDIR)/path.Plo \
+ ./$(DEPDIR)/pkg-array.Plo ./$(DEPDIR)/pkg-files.Plo \
+ ./$(DEPDIR)/pkg-format.Plo ./$(DEPDIR)/pkg-hash.Plo \
+ ./$(DEPDIR)/pkg-list.Plo ./$(DEPDIR)/pkg-namevalue.Plo \
+ ./$(DEPDIR)/pkg-queue.Plo ./$(DEPDIR)/pkg-show.Plo \
+ ./$(DEPDIR)/pkg-spec.Plo ./$(DEPDIR)/pkg.Plo \
+ ./$(DEPDIR)/progname.Plo ./$(DEPDIR)/program.Plo \
+ ./$(DEPDIR)/progress.Plo ./$(DEPDIR)/report.Plo \
+ ./$(DEPDIR)/strhash.Plo ./$(DEPDIR)/string.Plo \
+ ./$(DEPDIR)/strwide.Plo ./$(DEPDIR)/subproc.Plo \
+ ./$(DEPDIR)/tarfn.Plo ./$(DEPDIR)/treewalk.Plo \
+ ./$(DEPDIR)/trigdeferred.Plo ./$(DEPDIR)/triglib.Plo \
+ ./$(DEPDIR)/trigname.Plo ./$(DEPDIR)/trignote.Plo \
+ ./$(DEPDIR)/utils.Plo ./$(DEPDIR)/varbuf.Plo \
+ ./$(DEPDIR)/version.Plo t/$(DEPDIR)/b-fsys-hash.Po \
+ t/$(DEPDIR)/b-pkg-hash.Po t/$(DEPDIR)/c-tarextract.Po \
+ t/$(DEPDIR)/c-treewalk.Po t/$(DEPDIR)/c-trigdeferred.Po \
+ t/$(DEPDIR)/t-ar.Po t/$(DEPDIR)/t-arch.Po \
+ t/$(DEPDIR)/t-buffer.Po t/$(DEPDIR)/t-c-ctype.Po \
+ t/$(DEPDIR)/t-command.Po t/$(DEPDIR)/t-deb-version.Po \
+ t/$(DEPDIR)/t-ehandle.Po t/$(DEPDIR)/t-error.Po \
+ t/$(DEPDIR)/t-file.Po t/$(DEPDIR)/t-fsys-dir.Po \
+ t/$(DEPDIR)/t-fsys-hash.Po t/$(DEPDIR)/t-headers-cpp.Po \
+ t/$(DEPDIR)/t-macros.Po t/$(DEPDIR)/t-meminfo.Po \
+ t/$(DEPDIR)/t-mod-db.Po t/$(DEPDIR)/t-namevalue.Po \
+ t/$(DEPDIR)/t-pager.Po t/$(DEPDIR)/t-path.Po \
+ t/$(DEPDIR)/t-pkg-format.Po t/$(DEPDIR)/t-pkg-hash.Po \
+ t/$(DEPDIR)/t-pkg-list.Po t/$(DEPDIR)/t-pkg-queue.Po \
+ t/$(DEPDIR)/t-pkg-show.Po t/$(DEPDIR)/t-pkginfo.Po \
+ t/$(DEPDIR)/t-progname.Po t/$(DEPDIR)/t-string.Po \
+ t/$(DEPDIR)/t-subproc.Po t/$(DEPDIR)/t-tar.Po \
+ t/$(DEPDIR)/t-test-skip.Po t/$(DEPDIR)/t-test.Po \
+ t/$(DEPDIR)/t-trigger.Po t/$(DEPDIR)/t-varbuf.Po \
+ t/$(DEPDIR)/t-version.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 =
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+SOURCES = $(libdpkg_la_SOURCES) t/b-fsys-hash.c t/b-pkg-hash.c \
+ t/c-tarextract.c t/c-treewalk.c t/c-trigdeferred.c t/t-ar.c \
+ t/t-arch.c t/t-buffer.c t/t-c-ctype.c t/t-command.c \
+ t/t-deb-version.c t/t-ehandle.c t/t-error.c t/t-file.c \
+ t/t-fsys-dir.c t/t-fsys-hash.c $(t_t_headers_cpp_SOURCES) \
+ t/t-macros.c t/t-meminfo.c t/t-mod-db.c t/t-namevalue.c \
+ t/t-pager.c t/t-path.c t/t-pkg-format.c t/t-pkg-hash.c \
+ t/t-pkg-list.c t/t-pkg-queue.c t/t-pkg-show.c t/t-pkginfo.c \
+ t/t-progname.c t/t-string.c t/t-subproc.c t/t-tar.c t/t-test.c \
+ t/t-test-skip.c t/t-trigger.c t/t-varbuf.c t/t-version.c
+DIST_SOURCES = $(libdpkg_la_SOURCES) t/b-fsys-hash.c t/b-pkg-hash.c \
+ t/c-tarextract.c t/c-treewalk.c t/c-trigdeferred.c t/t-ar.c \
+ t/t-arch.c t/t-buffer.c t/t-c-ctype.c t/t-command.c \
+ t/t-deb-version.c t/t-ehandle.c t/t-error.c t/t-file.c \
+ t/t-fsys-dir.c t/t-fsys-hash.c $(t_t_headers_cpp_SOURCES) \
+ t/t-macros.c t/t-meminfo.c t/t-mod-db.c t/t-namevalue.c \
+ t/t-pager.c t/t-path.c t/t-pkg-format.c t/t-pkg-hash.c \
+ t/t-pkg-list.c t/t-pkg-queue.c t/t-pkg-show.c t/t-pkginfo.c \
+ t/t-progname.c t/t-string.c t/t-subproc.c t/t-tar.c t/t-test.c \
+ t/t-test-skip.c t/t-trigger.c t/t-varbuf.c t/t-version.c
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+DATA = $(pkgconfig_DATA)
+HEADERS = $(pkginclude_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)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/libdpkg.pc.in \
+ $(top_srcdir)/build-aux/depcomp $(top_srcdir)/build-aux/tap.am
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOM4TE = @AUTOM4TE@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BUILD_DEVEL_DOCS = @BUILD_DEVEL_DOCS@
+BZ2_LIBS = @BZ2_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CURSES_LIBS = @CURSES_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEB_DEFAULT_COMPRESSOR = @DEB_DEFAULT_COMPRESSOR@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOXYGEN = @DOXYGEN@
+DPKG_DEFAULT_PAGER = @DPKG_DEFAULT_PAGER@
+DPKG_DEFAULT_SHELL = @DPKG_DEFAULT_SHELL@
+DPKG_PAGER = @DPKG_PAGER@
+DPKG_SHELL = @DPKG_SHELL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GCOV = @GCOV@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_DOT = @HAVE_DOT@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+KVM_LIBS = @KVM_LIBS@
+LCOV = @LCOV@
+LCOV_GENHTML = @LCOV_GENHTML@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LZMA_LIBS = @LZMA_LIBS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MD_LIBS = @MD_LIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGMERGE = @MSGMERGE@
+MSGMERGE_FOR_MSGFMT_OPTION = @MSGMERGE_FOR_MSGFMT_OPTION@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_BUG_WEB = @PACKAGE_BUG_WEB@
+PACKAGE_COPYRIGHT_HOLDER = @PACKAGE_COPYRIGHT_HOLDER@
+PACKAGE_CPAN_NAME = @PACKAGE_CPAN_NAME@
+PACKAGE_DIST_IS_RELEASE = @PACKAGE_DIST_IS_RELEASE@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_RELEASE_DATE = @PACKAGE_RELEASE_DATE@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VCS_TYPE = @PACKAGE_VCS_TYPE@
+PACKAGE_VCS_URL = @PACKAGE_VCS_URL@
+PACKAGE_VCS_WEB = @PACKAGE_VCS_WEB@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATCH = @PATCH@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PERL = @PERL@
+PERL_COVER = @PERL_COVER@
+PERL_COVERAGE = @PERL_COVERAGE@
+PERL_LIBDIR = @PERL_LIBDIR@
+PERL_MIN_VERSION = @PERL_MIN_VERSION@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PO4A = @PO4A@
+POD2MAN = @POD2MAN@
+POSUB = @POSUB@
+PS_LIBS = @PS_LIBS@
+RANLIB = @RANLIB@
+RT_LIBS = @RT_LIBS@
+SED = @SED@
+SELINUX_CFLAGS = @SELINUX_CFLAGS@
+SELINUX_LIBS = @SELINUX_LIBS@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SOCKET_LIBS = @SOCKET_LIBS@
+STRIP = @STRIP@
+TAR = @TAR@
+USE_NLS = @USE_NLS@
+USE_PO4A = @USE_PO4A@
+USE_UNICODE = @USE_UNICODE@
+VERSION = @VERSION@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+ZSTD_LIBS = @ZSTD_LIBS@
+Z_LIBS = @Z_LIBS@
+Z_NG_LIBS = @Z_NG_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@
+admindir = @admindir@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+backupsdir = @backupsdir@
+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@
+devlibdir = @devlibdir@
+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@
+logdir = @logdir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgconfdir = @pkgconfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+zshcompletionsdir = @zshcompletionsdir@
+AM_CPPFLAGS = \
+ -DLOCALEDIR=\"$(localedir)\" \
+ -DCONFIGDIR=\"$(pkgconfdir)\" \
+ -DADMINDIR=\"$(admindir)\" \
+ -DDEFAULT_TEXT_DOMAIN=\"@PACKAGE@\" \
+ -idirafter $(top_srcdir)/lib/compat \
+ -I$(top_builddir) \
+ -I$(top_srcdir)/lib \
+ # EOL
+
+
+# Needed for the test suite
+LDADD = \
+ libdpkg.la \
+ $(LIBINTL) \
+ ../compat/libcompat.la \
+ # EOL
+
+DISTCLEANFILES = libdpkg.sym # EOL
+EXTRA_DIST = \
+ $(test_scripts) \
+ $(test_data) \
+ libdpkg.map \
+ libdpkg.pc.in \
+ # EOL
+
+pkgconfigdir = $(devlibdir)/pkgconfig
+pkgconfig_DATA = libdpkg.pc
+devlib_LTLIBRARIES = libdpkg.la
+EXTRA_libdpkg_la_DEPENDENCIES = libdpkg.map $(am__append_3)
+libdpkg_la_LDFLAGS = -no-undefined $(am__append_1) $(am__append_2) \
+ $(MD_LIBS)
+libdpkg_la_LIBADD = ../compat/libcompat.la $(am__append_4)
+libdpkg_la_SOURCES = \
+ dlist.h \
+ ar.c \
+ arch.c \
+ atomic-file.c \
+ buffer.c \
+ c-ctype.c \
+ cleanup.c \
+ color.c \
+ command.c \
+ compress.c \
+ dbdir.c \
+ dbmodify.c \
+ db-ctrl-access.c \
+ db-ctrl-format.c \
+ db-ctrl-upgrade.c \
+ db-fsys-digest.c \
+ db-fsys-divert.c \
+ db-fsys-files.c \
+ db-fsys-override.c \
+ deb-version.c \
+ debug.c \
+ depcon.c \
+ dir.c \
+ dump.c \
+ ehandle.c \
+ error.c \
+ fdio.c \
+ file.c \
+ fields.c \
+ fsys-dir.c\
+ fsys-iter.c \
+ fsys-hash.c \
+ glob.c \
+ i18n.c i18n.h \
+ log.c \
+ meminfo.c \
+ mustlib.c \
+ namevalue.c \
+ nfmalloc.c \
+ options.c \
+ options-dirs.c \
+ options-parsers.c \
+ pager.c \
+ parse.c \
+ parsehelp.c \
+ path.c \
+ path-remove.c \
+ perf.h \
+ pkg.c \
+ pkg-array.c \
+ pkg-files.c \
+ pkg-format.c \
+ pkg-hash.c \
+ pkg-list.c \
+ pkg-namevalue.c \
+ pkg-queue.c \
+ pkg-show.c \
+ pkg-spec.c \
+ progname.c \
+ program.c \
+ progress.c \
+ report.c \
+ string.c \
+ strhash.c \
+ strwide.c \
+ subproc.c \
+ tarfn.c \
+ test.h \
+ treewalk.c \
+ trigname.c \
+ trignote.c \
+ triglib.c \
+ trigdeferred.c \
+ utils.c \
+ varbuf.c \
+ version.c \
+ # EOL
+
+pkginclude_HEADERS = \
+ ar.h \
+ arch.h \
+ atomic-file.h \
+ buffer.h \
+ c-ctype.h \
+ color.h \
+ command.h \
+ compress.h \
+ db-ctrl.h \
+ db-fsys.h \
+ deb-version.h \
+ debug.h \
+ dir.h \
+ dpkg.h \
+ dpkg-db.h \
+ ehandle.h \
+ error.h \
+ fdio.h \
+ file.h \
+ fsys.h \
+ glob.h \
+ macros.h \
+ meminfo.h \
+ namevalue.h \
+ options.h \
+ pager.h \
+ parsedump.h \
+ path.h \
+ pkg.h \
+ pkg-array.h \
+ pkg-files.h \
+ pkg-format.h \
+ pkg-list.h \
+ pkg-queue.h \
+ pkg-show.h \
+ pkg-spec.h \
+ progname.h \
+ program.h \
+ progress.h \
+ report.h \
+ string.h \
+ subproc.h \
+ tarfn.h \
+ treewalk.h \
+ trigdeferred.h \
+ triglib.h \
+ varbuf.h \
+ version.h \
+ # EOL
+
+t_t_headers_cpp_SOURCES = \
+ t/t-headers-cpp.cc \
+ # EOL
+
+
+# The tests are sorted in order of increasing complexity.
+test_programs = \
+ t/t-test \
+ t/t-test-skip \
+ t/t-macros \
+ t/t-headers-cpp \
+ t/t-c-ctype \
+ t/t-namevalue \
+ t/t-ehandle \
+ t/t-error \
+ t/t-string \
+ t/t-file \
+ t/t-buffer \
+ t/t-meminfo \
+ t/t-path \
+ t/t-progname \
+ t/t-subproc \
+ t/t-command \
+ t/t-pager \
+ t/t-varbuf \
+ t/t-ar \
+ t/t-tar \
+ t/t-deb-version \
+ t/t-arch \
+ t/t-version \
+ t/t-pkginfo \
+ t/t-pkg-list \
+ t/t-pkg-queue \
+ t/t-pkg-hash \
+ t/t-pkg-show \
+ t/t-pkg-format \
+ t/t-fsys-dir \
+ t/t-fsys-hash \
+ t/t-trigger \
+ t/t-mod-db \
+ # EOL
+
+test_scripts = \
+ t/t-tarextract.t \
+ t/t-treewalk.t \
+ t/t-trigdeferred.t \
+ # EOL
+
+test_data = \
+ t/data/command/path-a/cmd \
+ t/data/command/path-a/cmd-a \
+ t/data/command/path-b/cmd \
+ t/data/command/path-b/cmd-b \
+ t/data/command/path-noexec/cmd \
+ t/data/command/path-noexec/cmd-noexec \
+ t/data/meminfo-no-data \
+ t/data/meminfo-no-info \
+ t/data/meminfo-no-unit \
+ t/data/meminfo-ok \
+ # EOL
+
+BENCHMARK_LDADD_FLAGS = \
+ $(RT_LIBS) \
+ $(LDADD) \
+ # EOL
+
+t_b_fsys_hash_LDADD = $(BENCHMARK_LDADD_FLAGS)
+t_b_pkg_hash_LDADD = $(BENCHMARK_LDADD_FLAGS)
+TEST_ENV_VARS = \
+ DPKG_PROGTAR=$(TAR) \
+ # EOL
+
+test_tmpdir = t.tmp
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .cc .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(top_srcdir)/build-aux/tap.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 lib/dpkg/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign lib/dpkg/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_srcdir)/build-aux/tap.am $(am__empty):
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+libdpkg.pc: $(top_builddir)/config.status $(srcdir)/libdpkg.pc.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+clean-checkPROGRAMS:
+ @list='$(check_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
+
+install-devlibLTLIBRARIES: $(devlib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(devlib_LTLIBRARIES)'; test -n "$(devlibdir)" || 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)$(devlibdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(devlibdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(devlibdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(devlibdir)"; \
+ }
+
+uninstall-devlibLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(devlib_LTLIBRARIES)'; test -n "$(devlibdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(devlibdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(devlibdir)/$$f"; \
+ done
+
+clean-devlibLTLIBRARIES:
+ -test -z "$(devlib_LTLIBRARIES)" || rm -f $(devlib_LTLIBRARIES)
+ @list='$(devlib_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}; \
+ }
+
+libdpkg.la: $(libdpkg_la_OBJECTS) $(libdpkg_la_DEPENDENCIES) $(EXTRA_libdpkg_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libdpkg_la_LINK) -rpath $(devlibdir) $(libdpkg_la_OBJECTS) $(libdpkg_la_LIBADD) $(LIBS)
+t/$(am__dirstamp):
+ @$(MKDIR_P) t
+ @: > t/$(am__dirstamp)
+t/$(DEPDIR)/$(am__dirstamp):
+ @$(MKDIR_P) t/$(DEPDIR)
+ @: > t/$(DEPDIR)/$(am__dirstamp)
+t/b-fsys-hash.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/b-fsys-hash$(EXEEXT): $(t_b_fsys_hash_OBJECTS) $(t_b_fsys_hash_DEPENDENCIES) $(EXTRA_t_b_fsys_hash_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/b-fsys-hash$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_b_fsys_hash_OBJECTS) $(t_b_fsys_hash_LDADD) $(LIBS)
+t/b-pkg-hash.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/b-pkg-hash$(EXEEXT): $(t_b_pkg_hash_OBJECTS) $(t_b_pkg_hash_DEPENDENCIES) $(EXTRA_t_b_pkg_hash_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/b-pkg-hash$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_b_pkg_hash_OBJECTS) $(t_b_pkg_hash_LDADD) $(LIBS)
+t/c-tarextract.$(OBJEXT): t/$(am__dirstamp) \
+ t/$(DEPDIR)/$(am__dirstamp)
+
+t/c-tarextract$(EXEEXT): $(t_c_tarextract_OBJECTS) $(t_c_tarextract_DEPENDENCIES) $(EXTRA_t_c_tarextract_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/c-tarextract$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_c_tarextract_OBJECTS) $(t_c_tarextract_LDADD) $(LIBS)
+t/c-treewalk.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/c-treewalk$(EXEEXT): $(t_c_treewalk_OBJECTS) $(t_c_treewalk_DEPENDENCIES) $(EXTRA_t_c_treewalk_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/c-treewalk$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_c_treewalk_OBJECTS) $(t_c_treewalk_LDADD) $(LIBS)
+t/c-trigdeferred.$(OBJEXT): t/$(am__dirstamp) \
+ t/$(DEPDIR)/$(am__dirstamp)
+
+t/c-trigdeferred$(EXEEXT): $(t_c_trigdeferred_OBJECTS) $(t_c_trigdeferred_DEPENDENCIES) $(EXTRA_t_c_trigdeferred_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/c-trigdeferred$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_c_trigdeferred_OBJECTS) $(t_c_trigdeferred_LDADD) $(LIBS)
+t/t-ar.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-ar$(EXEEXT): $(t_t_ar_OBJECTS) $(t_t_ar_DEPENDENCIES) $(EXTRA_t_t_ar_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-ar$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_ar_OBJECTS) $(t_t_ar_LDADD) $(LIBS)
+t/t-arch.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-arch$(EXEEXT): $(t_t_arch_OBJECTS) $(t_t_arch_DEPENDENCIES) $(EXTRA_t_t_arch_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-arch$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_arch_OBJECTS) $(t_t_arch_LDADD) $(LIBS)
+t/t-buffer.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-buffer$(EXEEXT): $(t_t_buffer_OBJECTS) $(t_t_buffer_DEPENDENCIES) $(EXTRA_t_t_buffer_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-buffer$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_buffer_OBJECTS) $(t_t_buffer_LDADD) $(LIBS)
+t/t-c-ctype.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-c-ctype$(EXEEXT): $(t_t_c_ctype_OBJECTS) $(t_t_c_ctype_DEPENDENCIES) $(EXTRA_t_t_c_ctype_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-c-ctype$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_c_ctype_OBJECTS) $(t_t_c_ctype_LDADD) $(LIBS)
+t/t-command.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-command$(EXEEXT): $(t_t_command_OBJECTS) $(t_t_command_DEPENDENCIES) $(EXTRA_t_t_command_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-command$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_command_OBJECTS) $(t_t_command_LDADD) $(LIBS)
+t/t-deb-version.$(OBJEXT): t/$(am__dirstamp) \
+ t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-deb-version$(EXEEXT): $(t_t_deb_version_OBJECTS) $(t_t_deb_version_DEPENDENCIES) $(EXTRA_t_t_deb_version_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-deb-version$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_deb_version_OBJECTS) $(t_t_deb_version_LDADD) $(LIBS)
+t/t-ehandle.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-ehandle$(EXEEXT): $(t_t_ehandle_OBJECTS) $(t_t_ehandle_DEPENDENCIES) $(EXTRA_t_t_ehandle_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-ehandle$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_ehandle_OBJECTS) $(t_t_ehandle_LDADD) $(LIBS)
+t/t-error.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-error$(EXEEXT): $(t_t_error_OBJECTS) $(t_t_error_DEPENDENCIES) $(EXTRA_t_t_error_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-error$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_error_OBJECTS) $(t_t_error_LDADD) $(LIBS)
+t/t-file.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-file$(EXEEXT): $(t_t_file_OBJECTS) $(t_t_file_DEPENDENCIES) $(EXTRA_t_t_file_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-file$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_file_OBJECTS) $(t_t_file_LDADD) $(LIBS)
+t/t-fsys-dir.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-fsys-dir$(EXEEXT): $(t_t_fsys_dir_OBJECTS) $(t_t_fsys_dir_DEPENDENCIES) $(EXTRA_t_t_fsys_dir_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-fsys-dir$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_fsys_dir_OBJECTS) $(t_t_fsys_dir_LDADD) $(LIBS)
+t/t-fsys-hash.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-fsys-hash$(EXEEXT): $(t_t_fsys_hash_OBJECTS) $(t_t_fsys_hash_DEPENDENCIES) $(EXTRA_t_t_fsys_hash_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-fsys-hash$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_fsys_hash_OBJECTS) $(t_t_fsys_hash_LDADD) $(LIBS)
+t/t-headers-cpp.$(OBJEXT): t/$(am__dirstamp) \
+ t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-headers-cpp$(EXEEXT): $(t_t_headers_cpp_OBJECTS) $(t_t_headers_cpp_DEPENDENCIES) $(EXTRA_t_t_headers_cpp_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-headers-cpp$(EXEEXT)
+ $(AM_V_CXXLD)$(CXXLINK) $(t_t_headers_cpp_OBJECTS) $(t_t_headers_cpp_LDADD) $(LIBS)
+t/t-macros.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-macros$(EXEEXT): $(t_t_macros_OBJECTS) $(t_t_macros_DEPENDENCIES) $(EXTRA_t_t_macros_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-macros$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_macros_OBJECTS) $(t_t_macros_LDADD) $(LIBS)
+t/t-meminfo.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-meminfo$(EXEEXT): $(t_t_meminfo_OBJECTS) $(t_t_meminfo_DEPENDENCIES) $(EXTRA_t_t_meminfo_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-meminfo$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_meminfo_OBJECTS) $(t_t_meminfo_LDADD) $(LIBS)
+t/t-mod-db.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-mod-db$(EXEEXT): $(t_t_mod_db_OBJECTS) $(t_t_mod_db_DEPENDENCIES) $(EXTRA_t_t_mod_db_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-mod-db$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_mod_db_OBJECTS) $(t_t_mod_db_LDADD) $(LIBS)
+t/t-namevalue.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-namevalue$(EXEEXT): $(t_t_namevalue_OBJECTS) $(t_t_namevalue_DEPENDENCIES) $(EXTRA_t_t_namevalue_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-namevalue$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_namevalue_OBJECTS) $(t_t_namevalue_LDADD) $(LIBS)
+t/t-pager.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-pager$(EXEEXT): $(t_t_pager_OBJECTS) $(t_t_pager_DEPENDENCIES) $(EXTRA_t_t_pager_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-pager$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_pager_OBJECTS) $(t_t_pager_LDADD) $(LIBS)
+t/t-path.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-path$(EXEEXT): $(t_t_path_OBJECTS) $(t_t_path_DEPENDENCIES) $(EXTRA_t_t_path_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-path$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_path_OBJECTS) $(t_t_path_LDADD) $(LIBS)
+t/t-pkg-format.$(OBJEXT): t/$(am__dirstamp) \
+ t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-pkg-format$(EXEEXT): $(t_t_pkg_format_OBJECTS) $(t_t_pkg_format_DEPENDENCIES) $(EXTRA_t_t_pkg_format_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-pkg-format$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_pkg_format_OBJECTS) $(t_t_pkg_format_LDADD) $(LIBS)
+t/t-pkg-hash.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-pkg-hash$(EXEEXT): $(t_t_pkg_hash_OBJECTS) $(t_t_pkg_hash_DEPENDENCIES) $(EXTRA_t_t_pkg_hash_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-pkg-hash$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_pkg_hash_OBJECTS) $(t_t_pkg_hash_LDADD) $(LIBS)
+t/t-pkg-list.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-pkg-list$(EXEEXT): $(t_t_pkg_list_OBJECTS) $(t_t_pkg_list_DEPENDENCIES) $(EXTRA_t_t_pkg_list_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-pkg-list$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_pkg_list_OBJECTS) $(t_t_pkg_list_LDADD) $(LIBS)
+t/t-pkg-queue.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-pkg-queue$(EXEEXT): $(t_t_pkg_queue_OBJECTS) $(t_t_pkg_queue_DEPENDENCIES) $(EXTRA_t_t_pkg_queue_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-pkg-queue$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_pkg_queue_OBJECTS) $(t_t_pkg_queue_LDADD) $(LIBS)
+t/t-pkg-show.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-pkg-show$(EXEEXT): $(t_t_pkg_show_OBJECTS) $(t_t_pkg_show_DEPENDENCIES) $(EXTRA_t_t_pkg_show_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-pkg-show$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_pkg_show_OBJECTS) $(t_t_pkg_show_LDADD) $(LIBS)
+t/t-pkginfo.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-pkginfo$(EXEEXT): $(t_t_pkginfo_OBJECTS) $(t_t_pkginfo_DEPENDENCIES) $(EXTRA_t_t_pkginfo_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-pkginfo$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_pkginfo_OBJECTS) $(t_t_pkginfo_LDADD) $(LIBS)
+t/t-progname.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-progname$(EXEEXT): $(t_t_progname_OBJECTS) $(t_t_progname_DEPENDENCIES) $(EXTRA_t_t_progname_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-progname$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_progname_OBJECTS) $(t_t_progname_LDADD) $(LIBS)
+t/t-string.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-string$(EXEEXT): $(t_t_string_OBJECTS) $(t_t_string_DEPENDENCIES) $(EXTRA_t_t_string_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-string$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_string_OBJECTS) $(t_t_string_LDADD) $(LIBS)
+t/t-subproc.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-subproc$(EXEEXT): $(t_t_subproc_OBJECTS) $(t_t_subproc_DEPENDENCIES) $(EXTRA_t_t_subproc_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-subproc$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_subproc_OBJECTS) $(t_t_subproc_LDADD) $(LIBS)
+t/t-tar.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-tar$(EXEEXT): $(t_t_tar_OBJECTS) $(t_t_tar_DEPENDENCIES) $(EXTRA_t_t_tar_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-tar$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_tar_OBJECTS) $(t_t_tar_LDADD) $(LIBS)
+t/t-test.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-test$(EXEEXT): $(t_t_test_OBJECTS) $(t_t_test_DEPENDENCIES) $(EXTRA_t_t_test_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-test$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_test_OBJECTS) $(t_t_test_LDADD) $(LIBS)
+t/t-test-skip.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-test-skip$(EXEEXT): $(t_t_test_skip_OBJECTS) $(t_t_test_skip_DEPENDENCIES) $(EXTRA_t_t_test_skip_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-test-skip$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_test_skip_OBJECTS) $(t_t_test_skip_LDADD) $(LIBS)
+t/t-trigger.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-trigger$(EXEEXT): $(t_t_trigger_OBJECTS) $(t_t_trigger_DEPENDENCIES) $(EXTRA_t_t_trigger_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-trigger$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_trigger_OBJECTS) $(t_t_trigger_LDADD) $(LIBS)
+t/t-varbuf.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-varbuf$(EXEEXT): $(t_t_varbuf_OBJECTS) $(t_t_varbuf_DEPENDENCIES) $(EXTRA_t_t_varbuf_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-varbuf$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_varbuf_OBJECTS) $(t_t_varbuf_LDADD) $(LIBS)
+t/t-version.$(OBJEXT): t/$(am__dirstamp) t/$(DEPDIR)/$(am__dirstamp)
+
+t/t-version$(EXEEXT): $(t_t_version_OBJECTS) $(t_t_version_DEPENDENCIES) $(EXTRA_t_t_version_DEPENDENCIES) t/$(am__dirstamp)
+ @rm -f t/t-version$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_t_version_OBJECTS) $(t_t_version_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+ -rm -f t/*.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ar.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/arch.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atomic-file.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/buffer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/c-ctype.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cleanup.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/color.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/command.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/compress.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db-ctrl-access.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db-ctrl-format.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db-ctrl-upgrade.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db-fsys-digest.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db-fsys-divert.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db-fsys-files.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db-fsys-override.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dbdir.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dbmodify.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/deb-version.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/debug.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/depcon.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dir.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dump.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ehandle.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/error.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdio.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fields.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fsys-dir.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fsys-hash.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fsys-iter.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/glob.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/i18n.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/meminfo.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mustlib.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/namevalue.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nfmalloc.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/options-dirs.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/options-parsers.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/options.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pager.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parse.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parsehelp.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/path-remove.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/path.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pkg-array.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pkg-files.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pkg-format.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pkg-hash.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pkg-list.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pkg-namevalue.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pkg-queue.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pkg-show.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pkg-spec.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pkg.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/progname.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/program.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/progress.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/report.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/strhash.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/string.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/strwide.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/subproc.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tarfn.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/treewalk.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trigdeferred.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/triglib.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trigname.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trignote.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utils.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/varbuf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/version.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/b-fsys-hash.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/b-pkg-hash.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/c-tarextract.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/c-treewalk.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/c-trigdeferred.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-ar.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-arch.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-buffer.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-c-ctype.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-command.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-deb-version.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-ehandle.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-error.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-file.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-fsys-dir.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-fsys-hash.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-headers-cpp.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-macros.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-meminfo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-mod-db.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-namevalue.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-pager.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-path.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-pkg-format.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-pkg-hash.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-pkg-list.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-pkg-queue.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-pkg-show.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-pkginfo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-progname.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-string.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-subproc.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-tar.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-test-skip.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-test.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-trigger.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-varbuf.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@t/$(DEPDIR)/t-version.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)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.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)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.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)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.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 $@ $<
+
+.cc.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cc.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cc.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCXX_TRUE@ $(LTCXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+ -rm -rf t/.libs t/_libs
+install-pkgconfigDATA: $(pkgconfig_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkgconfigdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkgconfigdir)" || 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_DATA) $$files '$(DESTDIR)$(pkgconfigdir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(pkgconfigdir)" || exit $$?; \
+ done
+
+uninstall-pkgconfigDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkgconfigdir)'; $(am__uninstall_files_from_dir)
+install-pkgincludeHEADERS: $(pkginclude_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginclude_HEADERS)'; test -n "$(pkgincludedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkgincludedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkgincludedir)" || 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)$(pkgincludedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkgincludedir)" || exit $$?; \
+ done
+
+uninstall-pkgincludeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginclude_HEADERS)'; test -n "$(pkgincludedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkgincludedir)'; $(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_PROGRAMS)
+ $(MAKE) $(AM_MAKEFLAGS) check-local
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(DATA) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(devlibdir)" "$(DESTDIR)$(pkgconfigdir)" "$(DESTDIR)$(pkgincludedir)"; 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)
+ -rm -f t/$(DEPDIR)/$(am__dirstamp)
+ -rm -f t/$(am__dirstamp)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+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-checkPROGRAMS clean-devlibLTLIBRARIES clean-generic \
+ clean-libtool clean-local mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/ar.Plo
+ -rm -f ./$(DEPDIR)/arch.Plo
+ -rm -f ./$(DEPDIR)/atomic-file.Plo
+ -rm -f ./$(DEPDIR)/buffer.Plo
+ -rm -f ./$(DEPDIR)/c-ctype.Plo
+ -rm -f ./$(DEPDIR)/cleanup.Plo
+ -rm -f ./$(DEPDIR)/color.Plo
+ -rm -f ./$(DEPDIR)/command.Plo
+ -rm -f ./$(DEPDIR)/compress.Plo
+ -rm -f ./$(DEPDIR)/db-ctrl-access.Plo
+ -rm -f ./$(DEPDIR)/db-ctrl-format.Plo
+ -rm -f ./$(DEPDIR)/db-ctrl-upgrade.Plo
+ -rm -f ./$(DEPDIR)/db-fsys-digest.Plo
+ -rm -f ./$(DEPDIR)/db-fsys-divert.Plo
+ -rm -f ./$(DEPDIR)/db-fsys-files.Plo
+ -rm -f ./$(DEPDIR)/db-fsys-override.Plo
+ -rm -f ./$(DEPDIR)/dbdir.Plo
+ -rm -f ./$(DEPDIR)/dbmodify.Plo
+ -rm -f ./$(DEPDIR)/deb-version.Plo
+ -rm -f ./$(DEPDIR)/debug.Plo
+ -rm -f ./$(DEPDIR)/depcon.Plo
+ -rm -f ./$(DEPDIR)/dir.Plo
+ -rm -f ./$(DEPDIR)/dump.Plo
+ -rm -f ./$(DEPDIR)/ehandle.Plo
+ -rm -f ./$(DEPDIR)/error.Plo
+ -rm -f ./$(DEPDIR)/fdio.Plo
+ -rm -f ./$(DEPDIR)/fields.Plo
+ -rm -f ./$(DEPDIR)/file.Plo
+ -rm -f ./$(DEPDIR)/fsys-dir.Plo
+ -rm -f ./$(DEPDIR)/fsys-hash.Plo
+ -rm -f ./$(DEPDIR)/fsys-iter.Plo
+ -rm -f ./$(DEPDIR)/glob.Plo
+ -rm -f ./$(DEPDIR)/i18n.Plo
+ -rm -f ./$(DEPDIR)/log.Plo
+ -rm -f ./$(DEPDIR)/meminfo.Plo
+ -rm -f ./$(DEPDIR)/mustlib.Plo
+ -rm -f ./$(DEPDIR)/namevalue.Plo
+ -rm -f ./$(DEPDIR)/nfmalloc.Plo
+ -rm -f ./$(DEPDIR)/options-dirs.Plo
+ -rm -f ./$(DEPDIR)/options-parsers.Plo
+ -rm -f ./$(DEPDIR)/options.Plo
+ -rm -f ./$(DEPDIR)/pager.Plo
+ -rm -f ./$(DEPDIR)/parse.Plo
+ -rm -f ./$(DEPDIR)/parsehelp.Plo
+ -rm -f ./$(DEPDIR)/path-remove.Plo
+ -rm -f ./$(DEPDIR)/path.Plo
+ -rm -f ./$(DEPDIR)/pkg-array.Plo
+ -rm -f ./$(DEPDIR)/pkg-files.Plo
+ -rm -f ./$(DEPDIR)/pkg-format.Plo
+ -rm -f ./$(DEPDIR)/pkg-hash.Plo
+ -rm -f ./$(DEPDIR)/pkg-list.Plo
+ -rm -f ./$(DEPDIR)/pkg-namevalue.Plo
+ -rm -f ./$(DEPDIR)/pkg-queue.Plo
+ -rm -f ./$(DEPDIR)/pkg-show.Plo
+ -rm -f ./$(DEPDIR)/pkg-spec.Plo
+ -rm -f ./$(DEPDIR)/pkg.Plo
+ -rm -f ./$(DEPDIR)/progname.Plo
+ -rm -f ./$(DEPDIR)/program.Plo
+ -rm -f ./$(DEPDIR)/progress.Plo
+ -rm -f ./$(DEPDIR)/report.Plo
+ -rm -f ./$(DEPDIR)/strhash.Plo
+ -rm -f ./$(DEPDIR)/string.Plo
+ -rm -f ./$(DEPDIR)/strwide.Plo
+ -rm -f ./$(DEPDIR)/subproc.Plo
+ -rm -f ./$(DEPDIR)/tarfn.Plo
+ -rm -f ./$(DEPDIR)/treewalk.Plo
+ -rm -f ./$(DEPDIR)/trigdeferred.Plo
+ -rm -f ./$(DEPDIR)/triglib.Plo
+ -rm -f ./$(DEPDIR)/trigname.Plo
+ -rm -f ./$(DEPDIR)/trignote.Plo
+ -rm -f ./$(DEPDIR)/utils.Plo
+ -rm -f ./$(DEPDIR)/varbuf.Plo
+ -rm -f ./$(DEPDIR)/version.Plo
+ -rm -f t/$(DEPDIR)/b-fsys-hash.Po
+ -rm -f t/$(DEPDIR)/b-pkg-hash.Po
+ -rm -f t/$(DEPDIR)/c-tarextract.Po
+ -rm -f t/$(DEPDIR)/c-treewalk.Po
+ -rm -f t/$(DEPDIR)/c-trigdeferred.Po
+ -rm -f t/$(DEPDIR)/t-ar.Po
+ -rm -f t/$(DEPDIR)/t-arch.Po
+ -rm -f t/$(DEPDIR)/t-buffer.Po
+ -rm -f t/$(DEPDIR)/t-c-ctype.Po
+ -rm -f t/$(DEPDIR)/t-command.Po
+ -rm -f t/$(DEPDIR)/t-deb-version.Po
+ -rm -f t/$(DEPDIR)/t-ehandle.Po
+ -rm -f t/$(DEPDIR)/t-error.Po
+ -rm -f t/$(DEPDIR)/t-file.Po
+ -rm -f t/$(DEPDIR)/t-fsys-dir.Po
+ -rm -f t/$(DEPDIR)/t-fsys-hash.Po
+ -rm -f t/$(DEPDIR)/t-headers-cpp.Po
+ -rm -f t/$(DEPDIR)/t-macros.Po
+ -rm -f t/$(DEPDIR)/t-meminfo.Po
+ -rm -f t/$(DEPDIR)/t-mod-db.Po
+ -rm -f t/$(DEPDIR)/t-namevalue.Po
+ -rm -f t/$(DEPDIR)/t-pager.Po
+ -rm -f t/$(DEPDIR)/t-path.Po
+ -rm -f t/$(DEPDIR)/t-pkg-format.Po
+ -rm -f t/$(DEPDIR)/t-pkg-hash.Po
+ -rm -f t/$(DEPDIR)/t-pkg-list.Po
+ -rm -f t/$(DEPDIR)/t-pkg-queue.Po
+ -rm -f t/$(DEPDIR)/t-pkg-show.Po
+ -rm -f t/$(DEPDIR)/t-pkginfo.Po
+ -rm -f t/$(DEPDIR)/t-progname.Po
+ -rm -f t/$(DEPDIR)/t-string.Po
+ -rm -f t/$(DEPDIR)/t-subproc.Po
+ -rm -f t/$(DEPDIR)/t-tar.Po
+ -rm -f t/$(DEPDIR)/t-test-skip.Po
+ -rm -f t/$(DEPDIR)/t-test.Po
+ -rm -f t/$(DEPDIR)/t-trigger.Po
+ -rm -f t/$(DEPDIR)/t-varbuf.Po
+ -rm -f t/$(DEPDIR)/t-version.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-devlibLTLIBRARIES install-pkgconfigDATA \
+ install-pkgincludeHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+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)/ar.Plo
+ -rm -f ./$(DEPDIR)/arch.Plo
+ -rm -f ./$(DEPDIR)/atomic-file.Plo
+ -rm -f ./$(DEPDIR)/buffer.Plo
+ -rm -f ./$(DEPDIR)/c-ctype.Plo
+ -rm -f ./$(DEPDIR)/cleanup.Plo
+ -rm -f ./$(DEPDIR)/color.Plo
+ -rm -f ./$(DEPDIR)/command.Plo
+ -rm -f ./$(DEPDIR)/compress.Plo
+ -rm -f ./$(DEPDIR)/db-ctrl-access.Plo
+ -rm -f ./$(DEPDIR)/db-ctrl-format.Plo
+ -rm -f ./$(DEPDIR)/db-ctrl-upgrade.Plo
+ -rm -f ./$(DEPDIR)/db-fsys-digest.Plo
+ -rm -f ./$(DEPDIR)/db-fsys-divert.Plo
+ -rm -f ./$(DEPDIR)/db-fsys-files.Plo
+ -rm -f ./$(DEPDIR)/db-fsys-override.Plo
+ -rm -f ./$(DEPDIR)/dbdir.Plo
+ -rm -f ./$(DEPDIR)/dbmodify.Plo
+ -rm -f ./$(DEPDIR)/deb-version.Plo
+ -rm -f ./$(DEPDIR)/debug.Plo
+ -rm -f ./$(DEPDIR)/depcon.Plo
+ -rm -f ./$(DEPDIR)/dir.Plo
+ -rm -f ./$(DEPDIR)/dump.Plo
+ -rm -f ./$(DEPDIR)/ehandle.Plo
+ -rm -f ./$(DEPDIR)/error.Plo
+ -rm -f ./$(DEPDIR)/fdio.Plo
+ -rm -f ./$(DEPDIR)/fields.Plo
+ -rm -f ./$(DEPDIR)/file.Plo
+ -rm -f ./$(DEPDIR)/fsys-dir.Plo
+ -rm -f ./$(DEPDIR)/fsys-hash.Plo
+ -rm -f ./$(DEPDIR)/fsys-iter.Plo
+ -rm -f ./$(DEPDIR)/glob.Plo
+ -rm -f ./$(DEPDIR)/i18n.Plo
+ -rm -f ./$(DEPDIR)/log.Plo
+ -rm -f ./$(DEPDIR)/meminfo.Plo
+ -rm -f ./$(DEPDIR)/mustlib.Plo
+ -rm -f ./$(DEPDIR)/namevalue.Plo
+ -rm -f ./$(DEPDIR)/nfmalloc.Plo
+ -rm -f ./$(DEPDIR)/options-dirs.Plo
+ -rm -f ./$(DEPDIR)/options-parsers.Plo
+ -rm -f ./$(DEPDIR)/options.Plo
+ -rm -f ./$(DEPDIR)/pager.Plo
+ -rm -f ./$(DEPDIR)/parse.Plo
+ -rm -f ./$(DEPDIR)/parsehelp.Plo
+ -rm -f ./$(DEPDIR)/path-remove.Plo
+ -rm -f ./$(DEPDIR)/path.Plo
+ -rm -f ./$(DEPDIR)/pkg-array.Plo
+ -rm -f ./$(DEPDIR)/pkg-files.Plo
+ -rm -f ./$(DEPDIR)/pkg-format.Plo
+ -rm -f ./$(DEPDIR)/pkg-hash.Plo
+ -rm -f ./$(DEPDIR)/pkg-list.Plo
+ -rm -f ./$(DEPDIR)/pkg-namevalue.Plo
+ -rm -f ./$(DEPDIR)/pkg-queue.Plo
+ -rm -f ./$(DEPDIR)/pkg-show.Plo
+ -rm -f ./$(DEPDIR)/pkg-spec.Plo
+ -rm -f ./$(DEPDIR)/pkg.Plo
+ -rm -f ./$(DEPDIR)/progname.Plo
+ -rm -f ./$(DEPDIR)/program.Plo
+ -rm -f ./$(DEPDIR)/progress.Plo
+ -rm -f ./$(DEPDIR)/report.Plo
+ -rm -f ./$(DEPDIR)/strhash.Plo
+ -rm -f ./$(DEPDIR)/string.Plo
+ -rm -f ./$(DEPDIR)/strwide.Plo
+ -rm -f ./$(DEPDIR)/subproc.Plo
+ -rm -f ./$(DEPDIR)/tarfn.Plo
+ -rm -f ./$(DEPDIR)/treewalk.Plo
+ -rm -f ./$(DEPDIR)/trigdeferred.Plo
+ -rm -f ./$(DEPDIR)/triglib.Plo
+ -rm -f ./$(DEPDIR)/trigname.Plo
+ -rm -f ./$(DEPDIR)/trignote.Plo
+ -rm -f ./$(DEPDIR)/utils.Plo
+ -rm -f ./$(DEPDIR)/varbuf.Plo
+ -rm -f ./$(DEPDIR)/version.Plo
+ -rm -f t/$(DEPDIR)/b-fsys-hash.Po
+ -rm -f t/$(DEPDIR)/b-pkg-hash.Po
+ -rm -f t/$(DEPDIR)/c-tarextract.Po
+ -rm -f t/$(DEPDIR)/c-treewalk.Po
+ -rm -f t/$(DEPDIR)/c-trigdeferred.Po
+ -rm -f t/$(DEPDIR)/t-ar.Po
+ -rm -f t/$(DEPDIR)/t-arch.Po
+ -rm -f t/$(DEPDIR)/t-buffer.Po
+ -rm -f t/$(DEPDIR)/t-c-ctype.Po
+ -rm -f t/$(DEPDIR)/t-command.Po
+ -rm -f t/$(DEPDIR)/t-deb-version.Po
+ -rm -f t/$(DEPDIR)/t-ehandle.Po
+ -rm -f t/$(DEPDIR)/t-error.Po
+ -rm -f t/$(DEPDIR)/t-file.Po
+ -rm -f t/$(DEPDIR)/t-fsys-dir.Po
+ -rm -f t/$(DEPDIR)/t-fsys-hash.Po
+ -rm -f t/$(DEPDIR)/t-headers-cpp.Po
+ -rm -f t/$(DEPDIR)/t-macros.Po
+ -rm -f t/$(DEPDIR)/t-meminfo.Po
+ -rm -f t/$(DEPDIR)/t-mod-db.Po
+ -rm -f t/$(DEPDIR)/t-namevalue.Po
+ -rm -f t/$(DEPDIR)/t-pager.Po
+ -rm -f t/$(DEPDIR)/t-path.Po
+ -rm -f t/$(DEPDIR)/t-pkg-format.Po
+ -rm -f t/$(DEPDIR)/t-pkg-hash.Po
+ -rm -f t/$(DEPDIR)/t-pkg-list.Po
+ -rm -f t/$(DEPDIR)/t-pkg-queue.Po
+ -rm -f t/$(DEPDIR)/t-pkg-show.Po
+ -rm -f t/$(DEPDIR)/t-pkginfo.Po
+ -rm -f t/$(DEPDIR)/t-progname.Po
+ -rm -f t/$(DEPDIR)/t-string.Po
+ -rm -f t/$(DEPDIR)/t-subproc.Po
+ -rm -f t/$(DEPDIR)/t-tar.Po
+ -rm -f t/$(DEPDIR)/t-test-skip.Po
+ -rm -f t/$(DEPDIR)/t-test.Po
+ -rm -f t/$(DEPDIR)/t-trigger.Po
+ -rm -f t/$(DEPDIR)/t-varbuf.Po
+ -rm -f t/$(DEPDIR)/t-version.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-devlibLTLIBRARIES uninstall-pkgconfigDATA \
+ uninstall-pkgincludeHEADERS
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \
+ check-local clean clean-checkPROGRAMS clean-devlibLTLIBRARIES \
+ clean-generic clean-libtool clean-local 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-devlibLTLIBRARIES 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-pkgconfigDATA \
+ install-pkgincludeHEADERS 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-devlibLTLIBRARIES uninstall-pkgconfigDATA \
+ uninstall-pkgincludeHEADERS
+
+.PRECIOUS: Makefile
+
+
+# Generate a simple libtool symbol export list to be used as a fallback if
+# there is no version script support.
+libdpkg.sym: libdpkg.map
+ $(AM_V_GEN) $(SED) -n \
+ -e 's/#.*$$//g' \
+ -e 's/^[[:space:]]\{1,\}\([A-Za-z0-9_]\{1,\}\);/\1/p' \
+ $(srcdir)/libdpkg.map \
+ >$@
+
+TEST_VERBOSE ?= 0
+TEST_PARALLEL ?= 1
+
+tap-clean:
+ [ -z "$(test_tmpdir)" ] || rm -fr $(test_tmpdir)
+
+tap-check: $(test_data) $(test_programs) $(test_scripts)
+ [ -z "$(test_tmpdir)" ] || $(MKDIR_P) $(test_tmpdir)
+ $(TEST_ENV_VARS) \
+ abs_top_srcdir=$(abs_top_srcdir) \
+ abs_top_builddir=$(abs_top_builddir) \
+ srcdir=$(srcdir) builddir=$(builddir) \
+ CC=$(CC) \
+ SHELL=$(SHELL) \
+ PERL=$(PERL) \
+ PERL_DL_NONLAZY=1 \
+ PERL5OPT=$(TEST_COVERAGE) \
+ $(PERL) $(top_srcdir)/build-aux/test-runner \
+ $(addprefix $(builddir)/,$(test_programs)) \
+ $(addprefix $(srcdir)/,$(test_scripts))
+
+authorcheck:
+ AUTHOR_TESTING=1 $(MAKE) check
+
+check-local: tap-check
+
+clean-local: tap-clean
+
+# 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/lib/dpkg/ar.c b/lib/dpkg/ar.c
new file mode 100644
index 0000000..6eea5ad
--- /dev/null
+++ b/lib/dpkg/ar.c
@@ -0,0 +1,234 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * ar.c - primitives for ar handling
+ *
+ * Copyright © 2010 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <time.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/fdio.h>
+#include <dpkg/buffer.h>
+#include <dpkg/ar.h>
+
+struct dpkg_ar *
+dpkg_ar_fdopen(const char *filename, int fd)
+{
+ struct dpkg_ar *ar;
+ struct stat st;
+
+ if (fstat(fd, &st) != 0)
+ ohshite(_("failed to fstat archive"));
+
+ ar = m_malloc(sizeof(*ar));
+ ar->name = filename;
+ ar->mode = st.st_mode;
+ ar->size = st.st_size;
+ ar->time = st.st_mtime;
+ ar->fd = fd;
+
+ return ar;
+}
+
+struct dpkg_ar *
+dpkg_ar_open(const char *filename)
+{
+ int fd;
+
+ if (strcmp(filename, "-") == 0)
+ fd = STDIN_FILENO;
+ else
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ ohshite(_("failed to read archive '%.255s'"), filename);
+
+ return dpkg_ar_fdopen(filename, fd);
+}
+
+struct dpkg_ar *
+dpkg_ar_create(const char *filename, mode_t mode)
+{
+ int fd;
+
+ fd = creat(filename, mode);
+ if (fd < 0)
+ ohshite(_("unable to create '%.255s'"), filename);
+
+ return dpkg_ar_fdopen(filename, fd);
+}
+
+void
+dpkg_ar_set_mtime(struct dpkg_ar *ar, intmax_t mtime)
+{
+ ar->time = mtime;
+}
+
+void
+dpkg_ar_close(struct dpkg_ar *ar)
+{
+ if (close(ar->fd))
+ ohshite(_("unable to close file '%s'"), ar->name);
+ free(ar);
+}
+
+static void
+dpkg_ar_member_init(struct dpkg_ar *ar, struct dpkg_ar_member *member,
+ const char *name, off_t size)
+{
+ member->name = name;
+ member->size = size;
+ member->time = ar->time;
+ member->mode = 0100644;
+ member->uid = 0;
+ member->gid = 0;
+}
+
+void
+dpkg_ar_normalize_name(struct dpkg_ar_hdr *arh)
+{
+ char *name = arh->ar_name;
+ int i;
+
+ /* Remove trailing spaces from the member name. */
+ for (i = sizeof(arh->ar_name) - 1; i >= 0 && name[i] == ' '; i--)
+ name[i] = '\0';
+
+ /* Remove optional slash terminator (on GNU-style archives). */
+ if (i >= 0 && name[i] == '/')
+ name[i] = '\0';
+}
+
+off_t
+dpkg_ar_member_get_size(struct dpkg_ar *ar, struct dpkg_ar_hdr *arh)
+{
+ const char *str = arh->ar_size;
+ int len = sizeof(arh->ar_size);
+ off_t size = 0;
+
+ while (len && *str == ' ')
+ str++, len--;
+
+ while (len--) {
+ if (*str == ' ')
+ break;
+ if (*str < '0' || *str > '9')
+ ohshit(_("invalid character '%c' in archive '%.250s' "
+ "member '%.16s' size"),
+ *str, ar->name, arh->ar_name);
+
+ size *= 10;
+ size += *str++ - '0';
+ }
+
+ return size;
+}
+
+bool
+dpkg_ar_member_is_illegal(struct dpkg_ar_hdr *arh)
+{
+ return memcmp(arh->ar_fmag, DPKG_AR_FMAG, sizeof(arh->ar_fmag)) != 0;
+}
+
+void
+dpkg_ar_put_magic(struct dpkg_ar *ar)
+{
+ if (fd_write(ar->fd, DPKG_AR_MAGIC, strlen(DPKG_AR_MAGIC)) < 0)
+ ohshite(_("unable to write file '%s'"), ar->name);
+}
+
+void
+dpkg_ar_member_put_header(struct dpkg_ar *ar, struct dpkg_ar_member *member)
+{
+ char header[sizeof(struct dpkg_ar_hdr) + 1];
+ int n;
+
+ if (strlen(member->name) > 15)
+ ohshit(_("ar member name '%s' length too long"), member->name);
+ if (member->size > 9999999999L)
+ ohshit(_("ar member size %jd too large"), (intmax_t)member->size);
+ if (member->time > 999999999999L)
+ ohshit(_("ar member time %jd too large"), (intmax_t)member->time);
+
+ n = snprintf(header, sizeof(struct dpkg_ar_hdr) + 1,
+ "%-16s%-12jd%-6lu%-6lu%-8lo%-10jd`\n",
+ member->name, (intmax_t)member->time,
+ (unsigned long)member->uid, (unsigned long)member->gid,
+ (unsigned long)member->mode, (intmax_t)member->size);
+ if (n != sizeof(struct dpkg_ar_hdr))
+ ohshit(_("generated corrupt ar header for '%s'"), ar->name);
+
+ if (fd_write(ar->fd, header, n) < 0)
+ ohshite(_("unable to write file '%s'"), ar->name);
+}
+
+void
+dpkg_ar_member_put_mem(struct dpkg_ar *ar,
+ const char *name, const void *data, size_t size)
+{
+ struct dpkg_ar_member member;
+
+ dpkg_ar_member_init(ar, &member, name, size);
+ dpkg_ar_member_put_header(ar, &member);
+
+ /* Copy data contents. */
+ if (fd_write(ar->fd, data, size) < 0)
+ ohshite(_("unable to write file '%s'"), ar->name);
+
+ if (size & 1)
+ if (fd_write(ar->fd, "\n", 1) < 0)
+ ohshite(_("unable to write file '%s'"), ar->name);
+}
+
+void
+dpkg_ar_member_put_file(struct dpkg_ar *ar,
+ const char *name, int fd, off_t size)
+{
+ struct dpkg_error err;
+ struct dpkg_ar_member member;
+
+ if (size <= 0) {
+ struct stat st;
+
+ if (fstat(fd, &st))
+ ohshite(_("failed to fstat ar member file (%s)"), name);
+ size = st.st_size;
+ }
+
+ dpkg_ar_member_init(ar, &member, name, size);
+ dpkg_ar_member_put_header(ar, &member);
+
+ /* Copy data contents. */
+ if (fd_fd_copy(fd, ar->fd, size, &err) < 0)
+ ohshit(_("cannot append ar member file (%s) to '%s': %s"),
+ name, ar->name, err.str);
+
+ if (size & 1)
+ if (fd_write(ar->fd, "\n", 1) < 0)
+ ohshite(_("unable to write file '%s'"), ar->name);
+}
diff --git a/lib/dpkg/ar.h b/lib/dpkg/ar.h
new file mode 100644
index 0000000..8a7aff9
--- /dev/null
+++ b/lib/dpkg/ar.h
@@ -0,0 +1,102 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * ar.h - primitives for ar handling
+ *
+ * Copyright © 2010 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_AR_H
+#define LIBDPKG_AR_H
+
+#include <sys/types.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <dpkg/macros.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup ar Ar archive handling
+ * @ingroup dpkg-public
+ * @{
+ */
+
+#define DPKG_AR_MAGIC "!<arch>\n"
+#define DPKG_AR_FMAG "`\n"
+
+/**
+ * An on-disk archive header.
+ */
+struct dpkg_ar_hdr {
+ char ar_name[16]; /* Member file name, sometimes / terminated. */
+ char ar_date[12]; /* File date, decimal seconds since Epoch. */
+ char ar_uid[6], ar_gid[6]; /* User and group IDs, in ASCII decimal. */
+ char ar_mode[8]; /* File mode, in ASCII octal. */
+ char ar_size[10]; /* File size, in ASCII decimal. */
+ char ar_fmag[2];
+};
+
+/**
+ * An archive (Unix ar) file.
+ */
+struct dpkg_ar {
+ const char *name;
+ mode_t mode;
+ intmax_t time;
+ off_t size;
+ int fd;
+};
+
+/**
+ * In-memory archive member information.
+ */
+struct dpkg_ar_member {
+ struct dpkg_ar_member *next;
+ const char *name;
+ off_t offset;
+ off_t size;
+ intmax_t time;
+ mode_t mode;
+ uid_t uid;
+ gid_t gid;
+};
+
+struct dpkg_ar *
+dpkg_ar_fdopen(const char *filename, int fd);
+struct dpkg_ar *dpkg_ar_open(const char *filename);
+struct dpkg_ar *dpkg_ar_create(const char *filename, mode_t mode);
+void dpkg_ar_set_mtime(struct dpkg_ar *ar, intmax_t mtime);
+void dpkg_ar_close(struct dpkg_ar *ar);
+
+void dpkg_ar_normalize_name(struct dpkg_ar_hdr *arh);
+bool dpkg_ar_member_is_illegal(struct dpkg_ar_hdr *arh);
+
+void dpkg_ar_put_magic(struct dpkg_ar *ar);
+void dpkg_ar_member_put_header(struct dpkg_ar *ar,
+ struct dpkg_ar_member *member);
+void dpkg_ar_member_put_file(struct dpkg_ar *ar, const char *name,
+ int fd, off_t size);
+void dpkg_ar_member_put_mem(struct dpkg_ar *ar, const char *name,
+ const void *data, size_t size);
+off_t dpkg_ar_member_get_size(struct dpkg_ar *ar, struct dpkg_ar_hdr *arh);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_AR_H */
diff --git a/lib/dpkg/arch.c b/lib/dpkg/arch.c
new file mode 100644
index 0000000..9409f5a
--- /dev/null
+++ b/lib/dpkg/arch.c
@@ -0,0 +1,342 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * arch.c - architecture database functions
+ *
+ * Copyright © 2011 Linaro Limited
+ * Copyright © 2011 Raphaël Hertzog <hertzog@debian.org>
+ * Copyright © 2011-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <limits.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/c-ctype.h>
+#include <dpkg/ehandle.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/dir.h>
+#include <dpkg/varbuf.h>
+#include <dpkg/arch.h>
+
+#define DPKG_DB_ARCH_FILE "arch"
+
+/**
+ * Verify if the architecture name is valid.
+ *
+ * Returns NULL if the architecture name is valid. Otherwise it returns a
+ * string describing why it's not valid. Currently it ensures the name
+ * starts with an alphanumeric and is then composed of a combinations of
+ * hyphens and alphanumerics.
+ *
+ * The function will abort if you pass it a NULL pointer.
+ *
+ * @param name The architecture name to verify.
+ */
+const char *
+dpkg_arch_name_is_illegal(const char *name)
+{
+ static char buf[150];
+ const char *p = name;
+
+ if (p == NULL)
+ internerr("arch name argument is NULL");
+ if (!*p)
+ return _("may not be empty string");
+ if (!c_isalnum(*p))
+ return _("must start with an alphanumeric");
+ while (*++p != '\0')
+ if (!c_isalnum(*p) && *p != '-')
+ break;
+ if (*p == '\0')
+ return NULL;
+
+ snprintf(buf, sizeof(buf), _("character '%c' not allowed (only "
+ "letters, digits and characters '%s')"),
+ *p, "-");
+ return buf;
+}
+
+/* This is a special architecture used to guarantee we always have a valid
+ * structure to handle. */
+static struct dpkg_arch arch_item_none = {
+ .name = "",
+ .type = DPKG_ARCH_NONE,
+ .next = NULL,
+};
+static struct dpkg_arch arch_item_empty = {
+ .name = "",
+ .type = DPKG_ARCH_EMPTY,
+ .next = NULL,
+};
+
+static struct dpkg_arch arch_item_any = {
+ .name = "any",
+ .type = DPKG_ARCH_WILDCARD,
+ .next = NULL,
+};
+static struct dpkg_arch arch_item_all = {
+ .name = "all",
+ .type = DPKG_ARCH_ALL,
+ .next = &arch_item_any,
+};
+static struct dpkg_arch arch_item_native = {
+ .name = ARCHITECTURE,
+ .type = DPKG_ARCH_NATIVE,
+ .next = &arch_item_all,
+};
+static struct dpkg_arch *arch_head = &arch_item_native;
+static struct dpkg_arch *arch_builtin_tail = &arch_item_any;
+static bool arch_list_dirty;
+
+static struct dpkg_arch *
+dpkg_arch_new(const char *name, enum dpkg_arch_type type)
+{
+ struct dpkg_arch *new;
+
+ new = nfmalloc(sizeof(*new));
+ new->next = NULL;
+ new->name = nfstrsave(name);
+ new->type = type;
+
+ return new;
+}
+
+/**
+ * Retrieve the struct dpkg_arch for the given architecture.
+ *
+ * Create a new structure for the architecture if it is not yet known from
+ * the system, in that case it will have type == DPKG_ARCH_UNKNOWN, if the
+ * architecture is illegal it will have type == DPKG_ARCH_ILLEGAL, if name
+ * is an empty string it will have type == DPKG_ARCH_EMPTY, and if it is
+ * NULL then it will have type == DPKG_ARCH_NONE.
+ *
+ * @param name The architecture name.
+ */
+struct dpkg_arch *
+dpkg_arch_find(const char *name)
+{
+ struct dpkg_arch *arch, *last_arch = NULL;
+ enum dpkg_arch_type type;
+
+ if (name == NULL)
+ return &arch_item_none;
+ if (name[0] == '\0')
+ return &arch_item_empty;
+
+ for (arch = arch_head; arch; arch = arch->next) {
+ if (strcmp(arch->name, name) == 0)
+ return arch;
+ last_arch = arch;
+ }
+
+ if (dpkg_arch_name_is_illegal(name))
+ type = DPKG_ARCH_ILLEGAL;
+ else
+ type = DPKG_ARCH_UNKNOWN;
+
+ arch = dpkg_arch_new(name, type);
+ last_arch->next = arch;
+
+ return arch;
+}
+
+/**
+ * Return the struct dpkg_arch corresponding to the architecture type.
+ *
+ * The function only returns instances for types which are unique. For
+ * forward-compatibility any unknown type will return NULL.
+ */
+struct dpkg_arch *
+dpkg_arch_get(enum dpkg_arch_type type)
+{
+ switch (type) {
+ case DPKG_ARCH_NONE:
+ return &arch_item_none;
+ case DPKG_ARCH_EMPTY:
+ return &arch_item_empty;
+ case DPKG_ARCH_WILDCARD:
+ return &arch_item_any;
+ case DPKG_ARCH_ALL:
+ return &arch_item_all;
+ case DPKG_ARCH_NATIVE:
+ return &arch_item_native;
+ case DPKG_ARCH_ILLEGAL:
+ case DPKG_ARCH_FOREIGN:
+ case DPKG_ARCH_UNKNOWN:
+ internerr("architecture type %d is not unique", type);
+ default:
+ /* Ignore unknown types for forward-compatibility. */
+ return NULL;
+ }
+}
+
+/**
+ * Return the complete list of architectures.
+ *
+ * In fact it returns the first item of the linked list and you can
+ * traverse the list by following arch->next until it's NULL.
+ */
+struct dpkg_arch *
+dpkg_arch_get_list(void)
+{
+ return arch_head;
+}
+
+/**
+ * Reset the list of architectures.
+ *
+ * Must be called before nffreeall() to ensure we don't point to
+ * unallocated memory.
+ */
+void
+dpkg_arch_reset_list(void)
+{
+ arch_builtin_tail->next = NULL;
+ arch_list_dirty = false;
+}
+
+void
+varbuf_add_archqual(struct varbuf *vb, const struct dpkg_arch *arch)
+{
+ if (arch->type == DPKG_ARCH_NONE)
+ return;
+ if (arch->type == DPKG_ARCH_EMPTY)
+ return;
+
+ varbuf_add_char(vb, ':');
+ varbuf_add_str(vb, arch->name);
+}
+
+/**
+ * Return a descriptive architecture name.
+ */
+const char *
+dpkg_arch_describe(const struct dpkg_arch *arch)
+{
+ if (arch->type == DPKG_ARCH_NONE)
+ return C_("architecture", "<none>");
+ if (arch->type == DPKG_ARCH_EMPTY)
+ return C_("architecture", "<empty>");
+
+ return arch->name;
+}
+
+/**
+ * Add a new foreign dpkg_arch architecture.
+ */
+struct dpkg_arch *
+dpkg_arch_add(const char *name)
+{
+ struct dpkg_arch *arch;
+
+ arch = dpkg_arch_find(name);
+ if (arch->type == DPKG_ARCH_UNKNOWN) {
+ arch->type = DPKG_ARCH_FOREIGN;
+ arch_list_dirty = true;
+ }
+
+ return arch;
+}
+
+/**
+ * Unmark a foreign dpkg_arch architecture.
+ */
+void
+dpkg_arch_unmark(const struct dpkg_arch *arch_remove)
+{
+ struct dpkg_arch *arch;
+
+ for (arch = arch_builtin_tail->next; arch; arch = arch->next) {
+ if (arch->type != DPKG_ARCH_FOREIGN)
+ continue;
+
+ if (arch == arch_remove) {
+ arch->type = DPKG_ARCH_UNKNOWN;
+ arch_list_dirty = true;
+ return;
+ }
+ }
+}
+
+/**
+ * Load the architecture database.
+ */
+void
+dpkg_arch_load_list(void)
+{
+ FILE *fp;
+ char *archfile;
+ char archname[_POSIX2_LINE_MAX];
+
+ archfile = dpkg_db_get_path(DPKG_DB_ARCH_FILE);
+ fp = fopen(archfile, "r");
+ if (fp == NULL) {
+ arch_list_dirty = true;
+ free(archfile);
+ return;
+ }
+
+ while (fgets_checked(archname, sizeof(archname), fp, archfile) >= 0)
+ dpkg_arch_add(archname);
+
+ free(archfile);
+ fclose(fp);
+}
+
+/**
+ * Save the architecture database.
+ */
+void
+dpkg_arch_save_list(void)
+{
+ struct atomic_file *file;
+ struct dpkg_arch *arch;
+ char *archfile;
+
+ if (!arch_list_dirty)
+ return;
+
+ archfile = dpkg_db_get_path(DPKG_DB_ARCH_FILE);
+ file = atomic_file_new(archfile, ATOMIC_FILE_MKPATH);
+ atomic_file_open(file);
+
+ for (arch = arch_head; arch; arch = arch->next) {
+ if (arch->type != DPKG_ARCH_FOREIGN &&
+ arch->type != DPKG_ARCH_NATIVE)
+ continue;
+
+ if (fprintf(file->fp, "%s\n", arch->name) < 0)
+ ohshite(_("error writing to architecture list"));
+ }
+
+ atomic_file_sync(file);
+ atomic_file_close(file);
+ atomic_file_commit(file);
+ atomic_file_free(file);
+
+ dir_sync_path(dpkg_db_get_dir());
+
+ arch_list_dirty = false;
+
+ free(archfile);
+}
diff --git a/lib/dpkg/arch.h b/lib/dpkg/arch.h
new file mode 100644
index 0000000..02ef06c
--- /dev/null
+++ b/lib/dpkg/arch.h
@@ -0,0 +1,73 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * arch.h - architecture database functions
+ *
+ * Copyright © 2011 Linaro Limited
+ * Copyright © 2011 Raphaël Hertzog <hertzog@debian.org>
+ * Copyright © 2011-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_ARCH_H
+#define LIBDPKG_ARCH_H
+
+#include <dpkg/macros.h>
+#include <dpkg/varbuf.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup arch Architecture database
+ * @ingroup dpkg-public
+ * @{
+ */
+
+enum dpkg_arch_type {
+ DPKG_ARCH_NONE,
+ DPKG_ARCH_EMPTY,
+ DPKG_ARCH_ILLEGAL,
+ DPKG_ARCH_WILDCARD,
+ DPKG_ARCH_ALL,
+ DPKG_ARCH_NATIVE,
+ DPKG_ARCH_FOREIGN,
+ DPKG_ARCH_UNKNOWN,
+};
+
+struct dpkg_arch {
+ struct dpkg_arch *next;
+ const char *name;
+ enum dpkg_arch_type type;
+};
+
+const char *dpkg_arch_name_is_illegal(const char *name) DPKG_ATTR_NONNULL(1);
+struct dpkg_arch *dpkg_arch_find(const char *name);
+struct dpkg_arch *dpkg_arch_get(enum dpkg_arch_type type);
+struct dpkg_arch *dpkg_arch_get_list(void);
+void dpkg_arch_reset_list(void);
+
+const char *dpkg_arch_describe(const struct dpkg_arch *arch);
+
+struct dpkg_arch *dpkg_arch_add(const char *name);
+void dpkg_arch_unmark(const struct dpkg_arch *arch);
+void dpkg_arch_load_list(void);
+void dpkg_arch_save_list(void);
+
+void varbuf_add_archqual(struct varbuf *vb, const struct dpkg_arch *arch);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_ARCH_H */
diff --git a/lib/dpkg/atomic-file.c b/lib/dpkg/atomic-file.c
new file mode 100644
index 0000000..be7a33e
--- /dev/null
+++ b/lib/dpkg/atomic-file.c
@@ -0,0 +1,132 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * atomic-file.c - atomic file helper functions
+ *
+ * Copyright © 2011-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dir.h>
+#include <dpkg/atomic-file.h>
+
+#define ATOMIC_FILE_NEW_EXT "-new"
+#define ATOMIC_FILE_OLD_EXT "-old"
+
+struct atomic_file *
+atomic_file_new(const char *filename, enum atomic_file_flags flags)
+{
+ struct atomic_file *file;
+
+ file = m_malloc(sizeof(*file));
+ file->flags = flags;
+ file->fp = NULL;
+ file->name = m_strdup(filename);
+ file->name_new = str_fmt("%s%s", filename, ATOMIC_FILE_NEW_EXT);
+
+ return file;
+}
+
+void
+atomic_file_open(struct atomic_file *file)
+{
+ file->fp = fopen(file->name_new, "w");
+ if (file->fp == NULL && file->flags & ATOMIC_FILE_MKPATH) {
+ if (dir_make_path_parent(file->name, 0755) < 0)
+ ohshite(_("cannot create base directory for %s"),
+ file->name);
+
+ file->fp = fopen(file->name_new, "w");
+ }
+ if (file->fp == NULL)
+ ohshite(_("unable to create new file '%.250s'"),
+ file->name_new);
+ fchmod(fileno(file->fp), 0644);
+
+ push_cleanup(cu_closestream, ~ehflag_normaltidy, 1, file->fp);
+}
+
+void
+atomic_file_sync(struct atomic_file *file)
+{
+ if (ferror(file->fp))
+ ohshite(_("unable to write new file '%.250s'"), file->name_new);
+ if (fflush(file->fp))
+ ohshite(_("unable to flush new file '%.250s'"), file->name_new);
+ if (fsync(fileno(file->fp)))
+ ohshite(_("unable to sync new file '%.250s'"), file->name_new);
+}
+
+void
+atomic_file_close(struct atomic_file *file)
+{
+ pop_cleanup(ehflag_normaltidy); /* fopen */
+
+ if (fclose(file->fp))
+ ohshite(_("unable to close new file '%.250s'"), file->name_new);
+}
+
+static void
+atomic_file_backup(struct atomic_file *file)
+{
+ char *name_old;
+
+ name_old = str_fmt("%s%s", file->name, ATOMIC_FILE_OLD_EXT);
+
+ if (unlink(name_old) && errno != ENOENT)
+ ohshite(_("error removing old backup file '%s'"), name_old);
+ if (link(file->name, name_old) && errno != ENOENT)
+ ohshite(_("error creating new backup file '%s'"), name_old);
+
+ free(name_old);
+}
+
+void
+atomic_file_remove(struct atomic_file *file)
+{
+ if (unlink(file->name_new))
+ ohshite(_("cannot remove '%.250s'"), file->name_new);
+ if (unlink(file->name) && errno != ENOENT)
+ ohshite(_("cannot remove '%.250s'"), file->name);
+}
+
+void
+atomic_file_commit(struct atomic_file *file)
+{
+ if (file->flags & ATOMIC_FILE_BACKUP)
+ atomic_file_backup(file);
+
+ if (rename(file->name_new, file->name))
+ ohshite(_("error installing new file '%s'"), file->name);
+}
+
+void
+atomic_file_free(struct atomic_file *file)
+{
+ free(file->name_new);
+ free(file->name);
+ free(file);
+}
diff --git a/lib/dpkg/atomic-file.h b/lib/dpkg/atomic-file.h
new file mode 100644
index 0000000..17467ae
--- /dev/null
+++ b/lib/dpkg/atomic-file.h
@@ -0,0 +1,62 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * atomic-file.h - atomic file helper functions
+ *
+ * Copyright © 2011-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_ATOMIC_FILE_H
+#define LIBDPKG_ATOMIC_FILE_H
+
+#include <stdio.h>
+
+#include <dpkg/macros.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup atomic-file Atomic file operations
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+enum DPKG_ATTR_ENUM_FLAGS atomic_file_flags {
+ ATOMIC_FILE_NORMAL = 0,
+ ATOMIC_FILE_BACKUP = DPKG_BIT(0),
+ ATOMIC_FILE_MKPATH = DPKG_BIT(1),
+};
+
+struct atomic_file {
+ enum atomic_file_flags flags;
+ char *name;
+ char *name_new;
+ FILE *fp;
+};
+
+struct atomic_file *
+atomic_file_new(const char *filename, enum atomic_file_flags flags);
+void atomic_file_open(struct atomic_file *file);
+void atomic_file_sync(struct atomic_file *file);
+void atomic_file_close(struct atomic_file *file);
+void atomic_file_commit(struct atomic_file *file);
+void atomic_file_remove(struct atomic_file *file);
+void atomic_file_free(struct atomic_file *file);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_ATOMIC_FILE_H */
diff --git a/lib/dpkg/buffer.c b/lib/dpkg/buffer.c
new file mode 100644
index 0000000..ed05f4b
--- /dev/null
+++ b/lib/dpkg/buffer.c
@@ -0,0 +1,293 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * buffer.c - buffer I/O handling routines
+ *
+ * Copyright © 1999, 2000 Wichert Akkerman <wakkerma@debian.org>
+ * Copyright © 2000-2003 Adam Heath <doogie@debian.org>
+ * Copyright © 2008-2012 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+
+#include <errno.h>
+#include <md5.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/varbuf.h>
+#include <dpkg/fdio.h>
+#include <dpkg/buffer.h>
+
+struct buffer_md5_ctx {
+ MD5_CTX ctx;
+ char *hash;
+};
+
+static void
+buffer_md5_init(struct buffer_data *data)
+{
+ struct buffer_md5_ctx *ctx;
+
+ ctx = m_malloc(sizeof(*ctx));
+ ctx->hash = data->arg.ptr;
+ data->arg.ptr = ctx;
+ MD5Init(&ctx->ctx);
+}
+
+static off_t
+buffer_digest_init(struct buffer_data *data)
+{
+ switch (data->type) {
+ case BUFFER_DIGEST_NULL:
+ break;
+ case BUFFER_DIGEST_MD5:
+ buffer_md5_init(data);
+ break;
+ }
+ return 0;
+}
+
+static off_t
+buffer_digest_update(struct buffer_data *digest, const void *buf, off_t length)
+{
+ off_t ret = length;
+
+ switch (digest->type) {
+ case BUFFER_DIGEST_NULL:
+ break;
+ case BUFFER_DIGEST_MD5:
+ MD5Update(&(((struct buffer_md5_ctx *)digest->arg.ptr)->ctx),
+ buf, length);
+ break;
+ default:
+ internerr("unknown data type %i", digest->type);
+ }
+
+ return ret;
+}
+
+static void
+buffer_md5_done(struct buffer_data *data)
+{
+ struct buffer_md5_ctx *ctx;
+ unsigned char digest[16], *p = digest;
+ char *hash;
+ int i;
+
+ ctx = (struct buffer_md5_ctx *)data->arg.ptr;
+ hash = ctx->hash;
+ MD5Final(digest, &ctx->ctx);
+ for (i = 0; i < 16; ++i) {
+ sprintf(hash, "%02x", *p++);
+ hash += 2;
+ }
+ *hash = '\0';
+ free(ctx);
+}
+
+static off_t
+buffer_digest_done(struct buffer_data *data)
+{
+ switch (data->type) {
+ case BUFFER_DIGEST_NULL:
+ break;
+ case BUFFER_DIGEST_MD5:
+ buffer_md5_done(data);
+ break;
+ }
+ return 0;
+}
+
+static off_t
+buffer_write(struct buffer_data *data, const void *buf, off_t length,
+ struct dpkg_error *err)
+{
+ off_t ret = length;
+
+ switch (data->type) {
+ case BUFFER_WRITE_VBUF:
+ varbuf_add_buf((struct varbuf *)data->arg.ptr, buf, length);
+ break;
+ case BUFFER_WRITE_FD:
+ ret = fd_write(data->arg.i, buf, length);
+ if (ret < 0)
+ dpkg_put_errno(err, _("failed to write"));
+ break;
+ case BUFFER_WRITE_NULL:
+ break;
+ default:
+ internerr("unknown data type %i", data->type);
+ }
+
+ return ret;
+}
+
+static off_t
+buffer_read(struct buffer_data *data, void *buf, off_t length,
+ struct dpkg_error *err)
+{
+ off_t ret;
+
+ switch (data->type) {
+ case BUFFER_READ_FD:
+ ret = fd_read(data->arg.i, buf, length);
+ if (ret < 0)
+ dpkg_put_errno(err, _("failed to read"));
+ break;
+ default:
+ internerr("unknown data type %i", data->type);
+ }
+
+ return ret;
+}
+
+off_t
+buffer_digest(const void *input, void *output, int type, off_t limit)
+{
+ struct buffer_data data = { .arg.ptr = output, .type = type };
+ off_t ret;
+
+ buffer_digest_init(&data);
+ ret = buffer_digest_update(&data, input, limit);
+ buffer_digest_done(&data);
+
+ return ret;
+}
+
+static off_t
+buffer_copy(struct buffer_data *read_data,
+ struct buffer_data *digest,
+ struct buffer_data *write_data,
+ off_t limit, struct dpkg_error *err)
+{
+ char *buf;
+ int bufsize = DPKG_BUFFER_SIZE;
+ off_t bytesread = 0, byteswritten = 0;
+ off_t totalread = 0, totalwritten = 0;
+
+ if ((limit != -1) && (limit < bufsize))
+ bufsize = limit;
+ if (bufsize == 0)
+ buf = NULL;
+ else
+ buf = m_malloc(bufsize);
+
+ buffer_digest_init(digest);
+
+ while (bufsize > 0) {
+ bytesread = buffer_read(read_data, buf, bufsize, err);
+ if (bytesread < 0)
+ break;
+ if (bytesread == 0)
+ break;
+
+ totalread += bytesread;
+
+ if (limit != -1) {
+ limit -= bytesread;
+ if (limit < bufsize)
+ bufsize = limit;
+ }
+
+ buffer_digest_update(digest, buf, bytesread);
+
+ byteswritten = buffer_write(write_data, buf, bytesread, err);
+ if (byteswritten < 0)
+ break;
+ if (byteswritten == 0)
+ break;
+
+ totalwritten += byteswritten;
+ }
+
+ buffer_digest_done(digest);
+
+ free(buf);
+
+ if (bytesread < 0 || byteswritten < 0)
+ return -1;
+ if (totalread != totalwritten)
+ return -1;
+ if (limit > 0)
+ return dpkg_put_error(err, _("unexpected end of file or stream"));
+
+ return totalread;
+}
+
+off_t
+buffer_copy_IntInt(int Iin, int Tin,
+ void *Pdigest, int Tdigest,
+ int Iout, int Tout,
+ off_t limit, struct dpkg_error *err)
+{
+ struct buffer_data read_data = { .type = Tin, .arg.i = Iin };
+ struct buffer_data digest = { .type = Tdigest, .arg.ptr = Pdigest };
+ struct buffer_data write_data = { .type = Tout, .arg.i = Iout };
+
+ return buffer_copy(&read_data, &digest, &write_data, limit, err);
+}
+
+off_t
+buffer_copy_IntPtr(int Iin, int Tin,
+ void *Pdigest, int Tdigest,
+ void *Pout, int Tout,
+ off_t limit, struct dpkg_error *err)
+{
+ struct buffer_data read_data = { .type = Tin, .arg.i = Iin };
+ struct buffer_data digest = { .type = Tdigest, .arg.ptr = Pdigest };
+ struct buffer_data write_data = { .type = Tout, .arg.ptr = Pout };
+
+ return buffer_copy(&read_data, &digest, &write_data, limit, err);
+}
+
+static off_t
+buffer_skip(struct buffer_data *input, off_t limit, struct dpkg_error *err)
+{
+ struct buffer_data output;
+ struct buffer_data digest;
+
+ switch (input->type) {
+ case BUFFER_READ_FD:
+ if (lseek(input->arg.i, limit, SEEK_CUR) != -1)
+ return limit;
+ if (errno != ESPIPE)
+ return dpkg_put_errno(err, _("failed to seek"));
+ break;
+ default:
+ internerr("unknown data type %i", input->type);
+ }
+
+ output.type = BUFFER_WRITE_NULL;
+ output.arg.ptr = NULL;
+ digest.type = BUFFER_DIGEST_NULL;
+ digest.arg.ptr = NULL;
+
+ return buffer_copy(input, &digest, &output, limit, err);
+}
+
+off_t
+buffer_skip_Int(int I, int T, off_t limit, struct dpkg_error *err)
+{
+ struct buffer_data input = { .type = T, .arg.i = I };
+
+ return buffer_skip(&input, limit, err);
+}
diff --git a/lib/dpkg/buffer.h b/lib/dpkg/buffer.h
new file mode 100644
index 0000000..e297660
--- /dev/null
+++ b/lib/dpkg/buffer.h
@@ -0,0 +1,104 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * buffer.h - buffer I/O handling routines
+ *
+ * Copyright © 1999, 2000 Wichert Akkerman <wakkerma@debian.org>
+ * Copyright © 2000-2003 Adam Heath <doogie@debian.org>
+ * Copyright © 2005 Scott James Remnant
+ * Copyright © 2008-2011 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_BUFFER_H
+#define LIBDPKG_BUFFER_H
+
+#include <sys/types.h>
+
+#include <dpkg/macros.h>
+#include <dpkg/error.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup buffer Buffer I/O
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+#define DPKG_BUFFER_SIZE (32 * 1024)
+
+#define BUFFER_WRITE_VBUF 1
+#define BUFFER_WRITE_FD 2
+#define BUFFER_WRITE_NULL 3
+
+#define BUFFER_DIGEST_NULL 4
+#define BUFFER_DIGEST_MD5 5
+
+#define BUFFER_READ_FD 0
+
+struct buffer_data {
+ union {
+ void *ptr;
+ int i;
+ } arg;
+ int type;
+};
+
+# define buffer_md5(buf, hash, limit) \
+ buffer_digest(buf, hash, BUFFER_DIGEST_MD5, limit)
+
+# define fd_md5(fd, hash, limit, err) \
+ buffer_copy_IntPtr(fd, BUFFER_READ_FD, \
+ hash, BUFFER_DIGEST_MD5, \
+ NULL, BUFFER_WRITE_NULL, \
+ limit, err)
+# define fd_fd_copy(fd1, fd2, limit, err) \
+ buffer_copy_IntInt(fd1, BUFFER_READ_FD, \
+ NULL, BUFFER_DIGEST_NULL, \
+ fd2, BUFFER_WRITE_FD, \
+ limit, err)
+# define fd_fd_copy_and_md5(fd1, fd2, hash, limit, err) \
+ buffer_copy_IntInt(fd1, BUFFER_READ_FD, \
+ hash, BUFFER_DIGEST_MD5, \
+ fd2, BUFFER_WRITE_FD, \
+ limit, err)
+# define fd_vbuf_copy(fd, buf, limit, err) \
+ buffer_copy_IntPtr(fd, BUFFER_READ_FD, \
+ NULL, BUFFER_DIGEST_NULL, \
+ buf, BUFFER_WRITE_VBUF, \
+ limit, err)
+# define fd_skip(fd, limit, err) \
+ buffer_skip_Int(fd, BUFFER_READ_FD, limit, err)
+
+
+off_t buffer_copy_IntPtr(int i, int typeIn,
+ void *f, int typeDigest,
+ void *p, int typeOut,
+ off_t limit, struct dpkg_error *err)
+ DPKG_ATTR_REQRET;
+off_t buffer_copy_IntInt(int i1, int typeIn,
+ void *f, int typeDigest,
+ int i2, int typeOut,
+ off_t limit, struct dpkg_error *err)
+ DPKG_ATTR_REQRET;
+off_t buffer_skip_Int(int I, int T, off_t limit, struct dpkg_error *err)
+ DPKG_ATTR_REQRET;
+off_t buffer_digest(const void *buf, void *hash, int typeDigest, off_t length);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_BUFFER_H */
diff --git a/lib/dpkg/c-ctype.c b/lib/dpkg/c-ctype.c
new file mode 100644
index 0000000..b7afd39
--- /dev/null
+++ b/lib/dpkg/c-ctype.c
@@ -0,0 +1,186 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * c-ctype.c - ASCII C locale-only functions
+ *
+ * Copyright © 2009-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/c-ctype.h>
+
+#undef S
+#define S C_CTYPE_SPACE
+#undef W
+#define W C_CTYPE_WHITE
+#undef B
+#define B C_CTYPE_BLANK
+#undef U
+#define U C_CTYPE_UPPER
+#undef L
+#define L C_CTYPE_LOWER
+#undef D
+#define D C_CTYPE_DIGIT
+
+static unsigned short int c_ctype[256] = {
+/** 0 **/
+ /* \0 */ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* \a */ 0,
+ /* \b */ 0,
+ /* \t */ S | W | B,
+ /* \n */ S | W,
+ /* \v */ S,
+ /* \f */ S,
+ /* \r */ S,
+ 0,
+ 0,
+/** 16 **/
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+/** 32 **/
+ /* */ S | W | B,
+ /* ! */ 0,
+ /* " */ 0,
+ /* # */ 0,
+ /* $ */ 0,
+ /* % */ 0,
+ /* & */ 0,
+ /* ' */ 0,
+ /* ( */ 0,
+ /* ) */ 0,
+ /* * */ 0,
+ /* + */ 0,
+ /* , */ 0,
+ /* - */ 0,
+ /* . */ 0,
+ /* / */ 0,
+/** 48 **/
+ /* 0 */ D,
+ /* 1 */ D,
+ /* 2 */ D,
+ /* 3 */ D,
+ /* 4 */ D,
+ /* 5 */ D,
+ /* 6 */ D,
+ /* 7 */ D,
+ /* 8 */ D,
+ /* 9 */ D,
+ /* : */ 0,
+ /* ; */ 0,
+ /* < */ 0,
+ /* = */ 0,
+ /* > */ 0,
+ /* ? */ 0,
+/* 64 */
+ /* @ */ 0,
+ /* A */ U,
+ /* B */ U,
+ /* C */ U,
+ /* D */ U,
+ /* E */ U,
+ /* F */ U,
+ /* G */ U,
+ /* H */ U,
+ /* I */ U,
+ /* J */ U,
+ /* K */ U,
+ /* L */ U,
+ /* M */ U,
+ /* N */ U,
+ /* O */ U,
+/* 80 */
+ /* P */ U,
+ /* Q */ U,
+ /* R */ U,
+ /* S */ U,
+ /* T */ U,
+ /* U */ U,
+ /* V */ U,
+ /* W */ U,
+ /* X */ U,
+ /* Y */ U,
+ /* Z */ U,
+ /* [ */ 0,
+ /* \ */ 0,
+ /* ] */ 0,
+ /* ^ */ 0,
+ /* _ */ 0,
+/* 96 */
+ /* ` */ 0,
+ /* a */ L,
+ /* b */ L,
+ /* c */ L,
+ /* d */ L,
+ /* e */ L,
+ /* f */ L,
+ /* g */ L,
+ /* h */ L,
+ /* i */ L,
+ /* j */ L,
+ /* k */ L,
+ /* l */ L,
+ /* m */ L,
+ /* n */ L,
+ /* o */ L,
+/* 112 */
+ /* p */ L,
+ /* q */ L,
+ /* r */ L,
+ /* s */ L,
+ /* t */ L,
+ /* u */ L,
+ /* v */ L,
+ /* w */ L,
+ /* x */ L,
+ /* y */ L,
+ /* z */ L,
+ /* { */ 0,
+ /* | */ 0,
+ /* } */ 0,
+ /* ~ */ 0,
+ 0,
+/* 128 */
+};
+
+/**
+ * Check if the character is bits ctype.
+ */
+bool
+c_isbits(int c, enum c_ctype_bit bits)
+{
+ return ((c_ctype[(unsigned char)c] & bits) != 0);
+}
diff --git a/lib/dpkg/c-ctype.h b/lib/dpkg/c-ctype.h
new file mode 100644
index 0000000..a08d852
--- /dev/null
+++ b/lib/dpkg/c-ctype.h
@@ -0,0 +1,131 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * c-ctype.h - ASCII C locale-only functions
+ *
+ * Copyright © 2009-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_C_CTYPE_H
+#define LIBDPKG_C_CTYPE_H
+
+#include <stdbool.h>
+
+#include <dpkg/macros.h>
+
+DPKG_BEGIN_DECLS
+
+#define C_CTYPE_BIT(bit) (1 << (bit))
+
+enum DPKG_ATTR_ENUM_FLAGS c_ctype_bit {
+ C_CTYPE_BLANK = C_CTYPE_BIT(0),
+ C_CTYPE_WHITE = C_CTYPE_BIT(1),
+ C_CTYPE_SPACE = C_CTYPE_BIT(2),
+ C_CTYPE_UPPER = C_CTYPE_BIT(3),
+ C_CTYPE_LOWER = C_CTYPE_BIT(4),
+ C_CTYPE_DIGIT = C_CTYPE_BIT(5),
+
+ C_CTYPE_ALPHA = C_CTYPE_UPPER | C_CTYPE_LOWER,
+ C_CTYPE_ALNUM = C_CTYPE_ALPHA | C_CTYPE_DIGIT,
+};
+
+bool
+c_isbits(int c, enum c_ctype_bit bits);
+
+/**
+ * Check if the character is [ \t].
+ */
+static inline bool
+c_isblank(int c)
+{
+ return c_isbits(c, C_CTYPE_BLANK);
+}
+
+/**
+ * Check if the character is [ \t\n].
+ */
+static inline bool
+c_iswhite(int c)
+{
+ return c_isbits(c, C_CTYPE_WHITE);
+}
+
+/**
+ * Check if the character is [ \v\t\f\r\n].
+ */
+static inline bool
+c_isspace(int c)
+{
+ return c_isbits(c, C_CTYPE_SPACE);
+}
+
+/**
+ * Check if the character is [0-9].
+ */
+static inline bool
+c_isdigit(int c)
+{
+ return c_isbits(c, C_CTYPE_DIGIT);
+}
+
+/**
+ * Check if the character is [A-Z].
+ */
+static inline bool
+c_isupper(int c)
+{
+ return c_isbits(c, C_CTYPE_UPPER);
+}
+
+/**
+ * Check if the character is [a-z].
+ */
+static inline bool
+c_islower(int c)
+{
+ return c_isbits(c, C_CTYPE_LOWER);
+}
+
+/**
+ * Check if the character is [a-zA-Z].
+ */
+static inline bool
+c_isalpha(int c)
+{
+ return c_isbits(c, C_CTYPE_ALPHA);
+}
+
+/**
+ * Check if the character is [a-zA-Z0-9].
+ */
+static inline bool
+c_isalnum(int c)
+{
+ return c_isbits(c, C_CTYPE_ALNUM);
+}
+
+/**
+ * Maps the character to its lower-case form.
+ */
+static inline int
+c_tolower(int c)
+{
+ return (c_isupper(c) ?
+ (DPKG_STATIC_CAST(unsigned char, c) & ~0x20) | 0x20 : c);
+}
+
+DPKG_END_DECLS
+
+#endif
diff --git a/lib/dpkg/cleanup.c b/lib/dpkg/cleanup.c
new file mode 100644
index 0000000..ff9d6fa
--- /dev/null
+++ b/lib/dpkg/cleanup.c
@@ -0,0 +1,69 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * cleanup.c - cleanup functions, used when we need to unwind
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dirent.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include <dpkg/dpkg.h>
+
+void
+cu_closepipe(int argc, void **argv)
+{
+ int *p1 = (int *)argv[0];
+
+ close(p1[0]);
+ close(p1[1]);
+}
+
+void
+cu_closestream(int argc, void **argv)
+{
+ FILE *f = (FILE *)(argv[0]);
+
+ fclose(f);
+}
+
+void
+cu_closedir(int argc, void **argv)
+{
+ DIR *d = (DIR *)(argv[0]);
+
+ closedir(d);
+}
+
+void
+cu_closefd(int argc, void **argv)
+{
+ int ip = *(int *)argv[0];
+
+ close(ip);
+}
+
+void
+cu_filename(int argc, void **argv)
+{
+ const char *filename = argv[0];
+
+ (void)unlink(filename);
+}
diff --git a/lib/dpkg/color.c b/lib/dpkg/color.c
new file mode 100644
index 0000000..9a33f12
--- /dev/null
+++ b/lib/dpkg/color.c
@@ -0,0 +1,74 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * color.c - color support
+ *
+ * Copyright © 2015-2016 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <dpkg/macros.h>
+#include <dpkg/color.h>
+
+static enum color_mode color_mode = COLOR_MODE_UNKNOWN;
+static bool use_color = false;
+
+bool
+color_set_mode(const char *mode)
+{
+ if (strcmp(mode, "auto") == 0) {
+ color_mode = COLOR_MODE_AUTO;
+ use_color = isatty(STDOUT_FILENO);
+ } else if (strcmp(mode, "always") == 0) {
+ color_mode = COLOR_MODE_ALWAYS;
+ use_color = true;
+ } else {
+ color_mode = COLOR_MODE_NEVER;
+ use_color = false;
+ }
+
+ return use_color;
+}
+
+static bool
+color_enabled(void)
+{
+ const char *mode;
+
+ if (color_mode != COLOR_MODE_UNKNOWN)
+ return use_color;
+
+ mode = getenv("DPKG_COLORS");
+ if (mode == NULL)
+ mode = "auto";
+
+ return color_set_mode(mode);
+}
+
+const char *
+color_get(const char *color)
+{
+ if (!color_enabled())
+ return "";
+
+ return color;
+}
diff --git a/lib/dpkg/color.h b/lib/dpkg/color.h
new file mode 100644
index 0000000..29f5359
--- /dev/null
+++ b/lib/dpkg/color.h
@@ -0,0 +1,87 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * color.h - color support
+ *
+ * Copyright © 2015-2016 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_COLOR_H
+#define LIBDPKG_COLOR_H
+
+#include <stdbool.h>
+
+#include <dpkg/macros.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup color Color support
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+/* Standard ANSI colors and attributes. */
+#define COLOR_NORMAL ""
+#define COLOR_RESET "\e[0m"
+#define COLOR_BOLD "\e[1m"
+#define COLOR_BLACK "\e[30m"
+#define COLOR_RED "\e[31m"
+#define COLOR_GREEN "\e[32m"
+#define COLOR_YELLOW "\e[33m"
+#define COLOR_BLUE "\e[34m"
+#define COLOR_MAGENTA "\e[35m"
+#define COLOR_CYAN "\e[36m"
+#define COLOR_WHITE "\e[37m"
+#define COLOR_BOLD_BLACK "\e[1;30m"
+#define COLOR_BOLD_RED "\e[1;31m"
+#define COLOR_BOLD_GREEN "\e[1;32m"
+#define COLOR_BOLD_YELLOW "\e[1;33m"
+#define COLOR_BOLD_BLUE "\e[1;34m"
+#define COLOR_BOLD_MAGENTA "\e[1;35m"
+#define COLOR_BOLD_CYAN "\e[1;36m"
+#define COLOR_BOLD_WHITE "\e[1;37m"
+
+/* Current defaults. These might become configurable in the future. */
+#define COLOR_PROG COLOR_BOLD
+#define COLOR_INFO COLOR_GREEN
+#define COLOR_NOTICE COLOR_YELLOW
+#define COLOR_WARN COLOR_BOLD_YELLOW
+#define COLOR_ERROR COLOR_BOLD_RED
+
+enum color_mode {
+ COLOR_MODE_UNKNOWN = -1,
+ COLOR_MODE_NEVER,
+ COLOR_MODE_ALWAYS,
+ COLOR_MODE_AUTO,
+};
+
+bool
+color_set_mode(const char *mode);
+
+const char *
+color_get(const char *color);
+
+static inline const char *
+color_reset(void)
+{
+ return color_get(COLOR_RESET);
+}
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_COLOR_H */
diff --git a/lib/dpkg/command.c b/lib/dpkg/command.c
new file mode 100644
index 0000000..18c917b
--- /dev/null
+++ b/lib/dpkg/command.c
@@ -0,0 +1,260 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * command.c - command execution support
+ *
+ * Copyright © 2010-2012 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <dpkg/dpkg.h>
+#include <dpkg/i18n.h>
+#include <dpkg/string.h>
+#include <dpkg/varbuf.h>
+#include <dpkg/file.h>
+#include <dpkg/path.h>
+#include <dpkg/command.h>
+
+/**
+ * Initialize a command structure.
+ *
+ * If name is NULL, then the last component of the filename path will be
+ * used to initialize the name member.
+ *
+ * @param cmd The command structure to initialize.
+ * @param filename The filename of the command to execute.
+ * @param name The description of the command to execute.
+ */
+void
+command_init(struct command *cmd, const char *filename, const char *name)
+{
+ cmd->filename = filename;
+ if (name == NULL)
+ cmd->name = path_basename(filename);
+ else
+ cmd->name = name;
+ cmd->argc = 0;
+ cmd->argv_size = 10;
+ cmd->argv = m_malloc(cmd->argv_size * sizeof(cmd->argv[0]));
+ cmd->argv[0] = NULL;
+}
+
+/**
+ * Destroy a command structure.
+ *
+ * Free the members managed by the command functions (i.e. the argv pointer
+ * array), and zero all members of a command structure.
+ *
+ * @param cmd The command structure to free.
+ */
+void
+command_destroy(struct command *cmd)
+{
+ cmd->filename = NULL;
+ cmd->name = NULL;
+ cmd->argc = 0;
+ cmd->argv_size = 0;
+ free(cmd->argv);
+ cmd->argv = NULL;
+}
+
+static void
+command_grow_argv(struct command *cmd, int need)
+{
+ /* We need a ghost byte for the NUL character. */
+ need++;
+
+ /* Check if we already have enough room. */
+ if ((cmd->argv_size - cmd->argc) >= need)
+ return;
+
+ cmd->argv_size = (cmd->argv_size + need) * 2;
+ cmd->argv = m_realloc(cmd->argv, cmd->argv_size * sizeof(cmd->argv[0]));
+}
+
+/**
+ * Append an argument to the command's argv.
+ *
+ * @param cmd The command structure to act on.
+ * @param arg The argument to append to argv.
+ */
+void
+command_add_arg(struct command *cmd, const char *arg)
+{
+ command_grow_argv(cmd, 1);
+
+ cmd->argv[cmd->argc++] = arg;
+ cmd->argv[cmd->argc] = NULL;
+}
+
+/**
+ * Append an argument array to the command's argv.
+ *
+ * @param cmd The command structure to act on.
+ * @param argv The NULL terminated argument array to append to argv.
+ */
+void
+command_add_argl(struct command *cmd, const char **argv)
+{
+ int i, add_argc = 0;
+
+ while (argv[add_argc] != NULL)
+ add_argc++;
+
+ command_grow_argv(cmd, add_argc);
+
+ for (i = 0; i < add_argc; i++)
+ cmd->argv[cmd->argc++] = argv[i];
+
+ cmd->argv[cmd->argc] = NULL;
+}
+
+/**
+ * Append a va_list of argument to the command's argv.
+ *
+ * @param cmd The command structure to act on.
+ * @param args The NULL terminated va_list of argument array to append to argv.
+ */
+void
+command_add_argv(struct command *cmd, va_list args)
+{
+ va_list args_copy;
+ int i, add_argc = 0;
+
+ va_copy(args_copy, args);
+ while (va_arg(args_copy, const char *) != NULL)
+ add_argc++;
+ va_end(args_copy);
+
+ command_grow_argv(cmd, add_argc);
+
+ for (i = 0; i < add_argc; i++)
+ cmd->argv[cmd->argc++] = va_arg(args, const char *);
+
+ cmd->argv[cmd->argc] = NULL;
+}
+
+/**
+ * Append a variable list of argument to the command's argv.
+ *
+ * @param cmd The command structure to act on.
+ * @param ... The NULL terminated variable list of argument to append to argv.
+ */
+void
+command_add_args(struct command *cmd, ...)
+{
+ va_list args;
+
+ va_start(args, cmd);
+ command_add_argv(cmd, args);
+ va_end(args);
+}
+
+/**
+ * Execute the command specified.
+ *
+ * The command is executed searching the PATH if the filename does not
+ * contain any slashes, or using the full path if it's either a relative or
+ * absolute pathname. This functions does not return.
+ *
+ * @param cmd The command structure to act on.
+ */
+void
+command_exec(struct command *cmd)
+{
+ execvp(cmd->filename, (char * const *)cmd->argv);
+ ohshite(_("unable to execute %s (%s)"), cmd->name, cmd->filename);
+}
+
+/**
+ * Execute a shell with a possible command.
+ *
+ * @param cmd The command string to execute, if it's NULL an interactive
+ * shell will be executed instead.
+ * @param name The description of the command to execute.
+ */
+void
+command_shell(const char *cmd, const char *name)
+{
+ const char *shell;
+ const char *mode;
+
+ if (cmd == NULL) {
+ mode = "-i";
+ shell = getenv("SHELL");
+ } else {
+ mode = "-c";
+ shell = NULL;
+ }
+
+ if (str_is_unset(shell))
+ shell = DPKG_DEFAULT_SHELL;
+
+#if HAVE_DPKG_SHELL_WITH_DASH_DASH
+ execlp(shell, shell, mode, "--", cmd, NULL);
+#else
+ execlp(shell, shell, mode, cmd, NULL);
+#endif
+ ohshite(_("unable to execute %s (%s)"), name, cmd);
+}
+
+/**
+ * Check whether a command can be found in PATH.
+ *
+ * @param cmd The command name to check. This is a relative pathname.
+ *
+ * @return A boolean denoting whether the command has been found.
+ */
+bool
+command_in_path(const char *cmd)
+{
+ struct varbuf filename = VARBUF_INIT;
+ const char *path_list;
+ const char *path, *path_end;
+
+ if (cmd[0] == '/')
+ return file_is_exec(cmd);
+
+ path_list = getenv("PATH");
+ if (!path_list)
+ ohshit(_("PATH is not set"));
+
+ for (path = path_list; path; path = *path_end ? path_end + 1 : NULL) {
+ size_t path_len;
+
+ path_end = strchrnul(path, ':');
+ path_len = (size_t)(path_end - path);
+
+ varbuf_set_buf(&filename, path, path_len);
+ if (path_len)
+ varbuf_add_char(&filename, '/');
+ varbuf_add_str(&filename, cmd);
+ varbuf_end_str(&filename);
+
+ if (file_is_exec(filename.buf)) {
+ varbuf_destroy(&filename);
+ return true;
+ }
+ }
+
+ varbuf_destroy(&filename);
+ return false;
+}
diff --git a/lib/dpkg/command.h b/lib/dpkg/command.h
new file mode 100644
index 0000000..768aba4
--- /dev/null
+++ b/lib/dpkg/command.h
@@ -0,0 +1,67 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * command.h - command execution support
+ *
+ * Copyright © 2010, 2012, 2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_COMMAND_H
+#define LIBDPKG_COMMAND_H
+
+#include <dpkg/macros.h>
+
+#include <stdarg.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup command Command execution
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+/**
+ * Describe a command to execute.
+ */
+struct command {
+ /** Descriptive name of the command, used when printing. */
+ const char *name;
+ /** Filename to execute; either a path or the progname. */
+ const char *filename;
+ int argc;
+ int argv_size;
+ const char **argv;
+};
+
+void command_init(struct command *cmd, const char *filename, const char *name);
+void command_destroy(struct command *cmd);
+
+void command_add_arg(struct command *cmd, const char *arg);
+void command_add_argl(struct command *cmd, const char **argv);
+void command_add_argv(struct command *cmd, va_list args);
+void command_add_args(struct command *cmd, ...) DPKG_ATTR_SENTINEL;
+
+void command_exec(struct command *cmd) DPKG_ATTR_NORET;
+
+void command_shell(const char *cmd, const char *name) DPKG_ATTR_NORET;
+
+bool command_in_path(const char *cmd);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_COMMAND_H */
diff --git a/lib/dpkg/compress.c b/lib/dpkg/compress.c
new file mode 100644
index 0000000..adf26ea
--- /dev/null
+++ b/lib/dpkg/compress.c
@@ -0,0 +1,1447 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * compress.c - compression support functions
+ *
+ * Copyright © 2000 Wichert Akkerman <wakkerma@debian.org>
+ * Copyright © 2004 Scott James Remnant <scott@netsplit.com>
+ * Copyright © 2006-2023 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#if USE_LIBZ_IMPL != USE_LIBZ_IMPL_NONE
+#include <compat-zlib.h>
+#endif
+#ifdef WITH_LIBLZMA
+#include <lzma.h>
+#endif
+#ifdef WITH_LIBZSTD
+#include <zstd.h>
+#define DPKG_ZSTD_MAX_LEVEL ZSTD_maxCLevel()
+#else
+#define DPKG_ZSTD_MAX_LEVEL 22
+#define ZSTD_CLEVEL_DEFAULT 3
+#endif
+#ifdef WITH_LIBBZ2
+#include <bzlib.h>
+#endif
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/error.h>
+#include <dpkg/varbuf.h>
+#include <dpkg/fdio.h>
+#include <dpkg/buffer.h>
+#include <dpkg/meminfo.h>
+#include <dpkg/command.h>
+#include <dpkg/compress.h>
+#if USE_LIBZ_IMPL == USE_LIBZ_IMPL_NONE || \
+ !defined(WITH_LIBLZMA) || \
+ !defined(WITH_LIBZSTD) || \
+ !defined(WITH_LIBBZ2)
+#include <dpkg/subproc.h>
+
+static void
+fd_fd_filter(struct command *cmd, int fd_in, int fd_out, const char *delenv[])
+{
+ pid_t pid;
+
+ pid = subproc_fork();
+ if (pid == 0) {
+ int i;
+
+ if (fd_in != 0) {
+ m_dup2(fd_in, 0);
+ close(fd_in);
+ }
+ if (fd_out != 1) {
+ m_dup2(fd_out, 1);
+ close(fd_out);
+ }
+
+ for (i = 0; delenv[i]; i++)
+ unsetenv(delenv[i]);
+
+ command_exec(cmd);
+ }
+ subproc_reap(pid, cmd->name, 0);
+}
+
+static void
+command_compress_init(struct command *cmd, const char *name, const char *desc,
+ int level)
+{
+ static char combuf[6];
+
+ command_init(cmd, name, desc);
+ command_add_arg(cmd, name);
+
+ snprintf(combuf, sizeof(combuf), "-c%d", level);
+ command_add_arg(cmd, combuf);
+}
+
+static void
+command_decompress_init(struct command *cmd, const char *name, const char *desc)
+{
+ command_init(cmd, name, desc);
+ command_add_arg(cmd, name);
+ command_add_arg(cmd, "-dc");
+}
+#endif
+
+#if defined(WITH_LIBLZMA) || defined(WITH_LIBZSTD)
+enum dpkg_stream_filter {
+ DPKG_STREAM_COMPRESS = 1,
+ DPKG_STREAM_DECOMPRESS = 2,
+};
+
+enum dpkg_stream_action {
+ DPKG_STREAM_INIT = 0,
+ DPKG_STREAM_RUN = 1,
+ DPKG_STREAM_FINISH = 2,
+};
+
+enum dpkg_stream_status {
+ DPKG_STREAM_OK,
+ DPKG_STREAM_END,
+ DPKG_STREAM_ERROR,
+};
+#endif
+
+struct compressor {
+ const char *name;
+ const char *extension;
+ int default_level;
+ void (*fixup_params)(struct compress_params *params);
+ void (*compress)(struct compress_params *params,
+ int fd_in, int fd_out, const char *desc);
+ void (*decompress)(struct compress_params *params,
+ int fd_in, int fd_out, const char *desc);
+};
+
+/*
+ * No compressor (pass-through).
+ */
+
+static void
+fixup_none_params(struct compress_params *params)
+{
+}
+
+static void
+decompress_none(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct dpkg_error err;
+
+ if (fd_fd_copy(fd_in, fd_out, -1, &err) < 0)
+ ohshit(_("%s: pass-through copy error: %s"), desc, err.str);
+}
+
+static void
+compress_none(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct dpkg_error err;
+
+ if (fd_fd_copy(fd_in, fd_out, -1, &err) < 0)
+ ohshit(_("%s: pass-through copy error: %s"), desc, err.str);
+}
+
+static const struct compressor compressor_none = {
+ .name = "none",
+ .extension = "",
+ .default_level = 0,
+ .fixup_params = fixup_none_params,
+ .compress = compress_none,
+ .decompress = decompress_none,
+};
+
+/*
+ * Gzip compressor.
+ */
+
+#define GZIP "gzip"
+
+static void
+fixup_gzip_params(struct compress_params *params)
+{
+ /* Normalize compression level. */
+ if (params->level == 0)
+ params->type = COMPRESSOR_TYPE_NONE;
+}
+
+#if USE_LIBZ_IMPL != USE_LIBZ_IMPL_NONE
+static void
+decompress_gzip(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ char *buffer;
+ size_t bufsize = DPKG_BUFFER_SIZE;
+ int z_errnum;
+ gzFile gzfile = gzdopen(fd_in, "r");
+
+ if (gzfile == NULL)
+ ohshit(_("%s: error binding input to gzip stream"), desc);
+
+ buffer = m_malloc(bufsize);
+
+ for (;;) {
+ int actualread, actualwrite;
+
+ actualread = gzread(gzfile, buffer, bufsize);
+ if (actualread < 0) {
+ const char *errmsg = gzerror(gzfile, &z_errnum);
+
+ if (z_errnum == Z_ERRNO)
+ errmsg = strerror(errno);
+ ohshit(_("%s: internal gzip read error: '%s'"), desc,
+ errmsg);
+ }
+ if (actualread == 0) /* EOF. */
+ break;
+
+ actualwrite = fd_write(fd_out, buffer, actualread);
+ if (actualwrite != actualread)
+ ohshite(_("%s: internal gzip write error"), desc);
+ }
+
+ free(buffer);
+
+ z_errnum = gzclose(gzfile);
+ if (z_errnum) {
+ const char *errmsg;
+
+ if (z_errnum == Z_ERRNO)
+ errmsg = strerror(errno);
+ else
+ errmsg = zError(z_errnum);
+ ohshit(_("%s: internal gzip read error: %s"), desc, errmsg);
+ }
+
+ if (close(fd_out))
+ ohshite(_("%s: internal gzip write error"), desc);
+}
+
+static void
+compress_gzip(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ char *buffer;
+ char combuf[6];
+ size_t bufsize = DPKG_BUFFER_SIZE;
+ int strategy;
+ int z_errnum;
+ gzFile gzfile;
+
+ if (params->strategy == COMPRESSOR_STRATEGY_FILTERED)
+ strategy = 'f';
+ else if (params->strategy == COMPRESSOR_STRATEGY_HUFFMAN)
+ strategy = 'h';
+ else if (params->strategy == COMPRESSOR_STRATEGY_RLE)
+ strategy = 'R';
+ else if (params->strategy == COMPRESSOR_STRATEGY_FIXED)
+ strategy = 'F';
+ else
+ strategy = ' ';
+
+ snprintf(combuf, sizeof(combuf), "w%d%c", params->level, strategy);
+ gzfile = gzdopen(fd_out, combuf);
+ if (gzfile == NULL)
+ ohshit(_("%s: error binding output to gzip stream"), desc);
+
+ buffer = m_malloc(bufsize);
+
+ for (;;) {
+ int actualread, actualwrite;
+
+ actualread = fd_read(fd_in, buffer, bufsize);
+ if (actualread < 0)
+ ohshite(_("%s: internal gzip read error"), desc);
+ if (actualread == 0) /* EOF. */
+ break;
+
+ actualwrite = gzwrite(gzfile, buffer, actualread);
+ if (actualwrite != actualread) {
+ const char *errmsg = gzerror(gzfile, &z_errnum);
+
+ if (z_errnum == Z_ERRNO)
+ errmsg = strerror(errno);
+ ohshit(_("%s: internal gzip write error: '%s'"), desc,
+ errmsg);
+ }
+ }
+
+ free(buffer);
+
+ z_errnum = gzclose(gzfile);
+ if (z_errnum) {
+ const char *errmsg;
+
+ if (z_errnum == Z_ERRNO)
+ errmsg = strerror(errno);
+ else
+ errmsg = zError(z_errnum);
+ ohshit(_("%s: internal gzip write error: %s"), desc, errmsg);
+ }
+}
+#else
+static const char *env_gzip[] = { "GZIP", NULL };
+
+static void
+decompress_gzip(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct command cmd;
+
+ command_decompress_init(&cmd, GZIP, desc);
+
+ fd_fd_filter(&cmd, fd_in, fd_out, env_gzip);
+
+ command_destroy(&cmd);
+}
+
+static void
+compress_gzip(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct command cmd;
+
+ command_compress_init(&cmd, GZIP, desc, params->level);
+ command_add_arg(&cmd, "-n");
+
+ fd_fd_filter(&cmd, fd_in, fd_out, env_gzip);
+
+ command_destroy(&cmd);
+}
+#endif
+
+static const struct compressor compressor_gzip = {
+ .name = "gzip",
+ .extension = ".gz",
+ .default_level = 9,
+ .fixup_params = fixup_gzip_params,
+ .compress = compress_gzip,
+ .decompress = decompress_gzip,
+};
+
+/*
+ * Bzip2 compressor.
+ */
+
+#define BZIP2 "bzip2"
+
+static void
+fixup_bzip2_params(struct compress_params *params)
+{
+ /* Normalize compression level. */
+ if (params->level == 0)
+ params->level = 1;
+}
+
+#ifdef WITH_LIBBZ2
+static void
+decompress_bzip2(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ char *buffer;
+ size_t bufsize = DPKG_BUFFER_SIZE;
+ BZFILE *bzfile = BZ2_bzdopen(fd_in, "r");
+
+ if (bzfile == NULL)
+ ohshit(_("%s: error binding input to bzip2 stream"), desc);
+
+ buffer = m_malloc(bufsize);
+
+ for (;;) {
+ int actualread, actualwrite;
+
+ actualread = BZ2_bzread(bzfile, buffer, bufsize);
+ if (actualread < 0) {
+ int bz_errnum = 0;
+ const char *errmsg = BZ2_bzerror(bzfile, &bz_errnum);
+
+ if (bz_errnum == BZ_IO_ERROR)
+ errmsg = strerror(errno);
+ ohshit(_("%s: internal bzip2 read error: '%s'"), desc,
+ errmsg);
+ }
+ if (actualread == 0) /* EOF. */
+ break;
+
+ actualwrite = fd_write(fd_out, buffer, actualread);
+ if (actualwrite != actualread)
+ ohshite(_("%s: internal bzip2 write error"), desc);
+ }
+
+ free(buffer);
+
+ BZ2_bzclose(bzfile);
+
+ if (close(fd_out))
+ ohshite(_("%s: internal bzip2 write error"), desc);
+}
+
+static void
+compress_bzip2(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ char *buffer;
+ char combuf[6];
+ size_t bufsize = DPKG_BUFFER_SIZE;
+ int bz_errnum;
+ BZFILE *bzfile;
+
+ snprintf(combuf, sizeof(combuf), "w%d", params->level);
+ bzfile = BZ2_bzdopen(fd_out, combuf);
+ if (bzfile == NULL)
+ ohshit(_("%s: error binding output to bzip2 stream"), desc);
+
+ buffer = m_malloc(bufsize);
+
+ for (;;) {
+ int actualread, actualwrite;
+
+ actualread = fd_read(fd_in, buffer, bufsize);
+ if (actualread < 0)
+ ohshite(_("%s: internal bzip2 read error"), desc);
+ if (actualread == 0) /* EOF. */
+ break;
+
+ actualwrite = BZ2_bzwrite(bzfile, buffer, actualread);
+ if (actualwrite != actualread) {
+ const char *errmsg = BZ2_bzerror(bzfile, &bz_errnum);
+
+ if (bz_errnum == BZ_IO_ERROR)
+ errmsg = strerror(errno);
+ ohshit(_("%s: internal bzip2 write error: '%s'"), desc,
+ errmsg);
+ }
+ }
+
+ free(buffer);
+
+ BZ2_bzWriteClose(&bz_errnum, bzfile, 0, NULL, NULL);
+ if (bz_errnum != BZ_OK) {
+ const char *errmsg = _("unexpected bzip2 error");
+
+ if (bz_errnum == BZ_IO_ERROR)
+ errmsg = strerror(errno);
+ ohshit(_("%s: internal bzip2 write error: '%s'"), desc,
+ errmsg);
+ }
+
+ /* Because BZ2_bzWriteClose has done a fflush on the file handle,
+ * doing a close on the file descriptor associated with it should
+ * be safe™. */
+ if (close(fd_out))
+ ohshite(_("%s: internal bzip2 write error"), desc);
+}
+#else
+static const char *env_bzip2[] = { "BZIP", "BZIP2", NULL };
+
+static void
+decompress_bzip2(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct command cmd;
+
+ command_decompress_init(&cmd, BZIP2, desc);
+
+ fd_fd_filter(&cmd, fd_in, fd_out, env_bzip2);
+
+ command_destroy(&cmd);
+}
+
+static void
+compress_bzip2(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct command cmd;
+
+ command_compress_init(&cmd, BZIP2, desc, params->level);
+
+ fd_fd_filter(&cmd, fd_in, fd_out, env_bzip2);
+
+ command_destroy(&cmd);
+}
+#endif
+
+static const struct compressor compressor_bzip2 = {
+ .name = "bzip2",
+ .extension = ".bz2",
+ .default_level = 9,
+ .fixup_params = fixup_bzip2_params,
+ .compress = compress_bzip2,
+ .decompress = decompress_bzip2,
+};
+
+/*
+ * Xz compressor.
+ */
+
+#define XZ "xz"
+
+#ifdef WITH_LIBLZMA
+struct io_lzma {
+ const char *desc;
+
+ struct compress_params *params;
+
+ enum dpkg_stream_filter filter;
+ enum dpkg_stream_action action;
+ enum dpkg_stream_status status;
+
+ void (*init)(struct io_lzma *io, lzma_stream *s);
+ void (*code)(struct io_lzma *io, lzma_stream *s);
+ void (*done)(struct io_lzma *io, lzma_stream *s);
+};
+
+/* XXX: liblzma does not expose error messages. */
+static const char *
+dpkg_lzma_strerror(struct io_lzma *io, lzma_ret code)
+{
+ const char *const impossible = _("internal error (bug)");
+
+ switch (code) {
+ case LZMA_MEM_ERROR:
+ return strerror(ENOMEM);
+ case LZMA_MEMLIMIT_ERROR:
+ if (io->action == DPKG_STREAM_RUN)
+ return _("memory usage limit reached");
+ return impossible;
+ case LZMA_OPTIONS_ERROR:
+ if (io->filter == DPKG_STREAM_COMPRESS &&
+ io->action == DPKG_STREAM_INIT)
+ return _("unsupported compression preset");
+ if (io->filter == DPKG_STREAM_DECOMPRESS &&
+ io->action == DPKG_STREAM_RUN)
+ return _("unsupported options in file header");
+ return impossible;
+ case LZMA_DATA_ERROR:
+ if (io->action == DPKG_STREAM_RUN)
+ return _("compressed data is corrupt");
+ return impossible;
+ case LZMA_BUF_ERROR:
+ if (io->action == DPKG_STREAM_RUN)
+ return _("unexpected end of input");
+ return impossible;
+ case LZMA_FORMAT_ERROR:
+ if (io->filter == DPKG_STREAM_DECOMPRESS &&
+ io->action == DPKG_STREAM_RUN)
+ return _("file format not recognized");
+ return impossible;
+ case LZMA_UNSUPPORTED_CHECK:
+ if (io->filter == DPKG_STREAM_COMPRESS &&
+ io->action == DPKG_STREAM_INIT)
+ return _("unsupported type of integrity check");
+ return impossible;
+ default:
+ return impossible;
+ }
+}
+
+static void
+filter_lzma(struct io_lzma *io, int fd_in, int fd_out)
+{
+ uint8_t *buf_in;
+ uint8_t *buf_out;
+ size_t buf_size = DPKG_BUFFER_SIZE;
+ lzma_stream s = LZMA_STREAM_INIT;
+
+ buf_in = m_malloc(buf_size);
+ buf_out = m_malloc(buf_size);
+
+ s.next_out = buf_out;
+ s.avail_out = buf_size;
+
+ io->status = DPKG_STREAM_OK;
+ io->action = DPKG_STREAM_INIT;
+ io->init(io, &s);
+ io->action = DPKG_STREAM_RUN;
+
+ do {
+ ssize_t len;
+
+ if (s.avail_in == 0 && io->action != DPKG_STREAM_FINISH) {
+ len = fd_read(fd_in, buf_in, buf_size);
+ if (len < 0)
+ ohshite(_("%s: lzma read error"), io->desc);
+ if (len == 0)
+ io->action = DPKG_STREAM_FINISH;
+ s.next_in = buf_in;
+ s.avail_in = len;
+ }
+
+ io->code(io, &s);
+
+ if (s.avail_out == 0 || io->status == DPKG_STREAM_END) {
+ len = fd_write(fd_out, buf_out, s.next_out - buf_out);
+ if (len < 0)
+ ohshite(_("%s: lzma write error"), io->desc);
+ s.next_out = buf_out;
+ s.avail_out = buf_size;
+ }
+ } while (io->status != DPKG_STREAM_END);
+
+ io->done(io, &s);
+
+ free(buf_in);
+ free(buf_out);
+
+ if (close(fd_out))
+ ohshite(_("%s: lzma close error"), io->desc);
+}
+
+static void DPKG_ATTR_NORET
+filter_lzma_error(struct io_lzma *io, lzma_ret ret)
+{
+ ohshit(_("%s: lzma error: %s"), io->desc,
+ dpkg_lzma_strerror(io, ret));
+}
+
+#ifdef HAVE_LZMA_MT_ENCODER
+static uint64_t
+filter_xz_get_memlimit(void)
+{
+ uint64_t mt_memlimit;
+
+ /* Ask the kernel what is currently available for us. If this fails
+ * initialize the memory limit to half the physical RAM, or to 128 MiB
+ * if we cannot infer the number. */
+ if (meminfo_get_available(&mt_memlimit) < 0) {
+ mt_memlimit = lzma_physmem() / 2;
+ if (mt_memlimit == 0)
+ mt_memlimit = 128 * 1024 * 1024;
+ }
+ /* Clamp the multi-threaded memory limit to half the addressable
+ * memory on this architecture. */
+ if (mt_memlimit > INTPTR_MAX)
+ mt_memlimit = INTPTR_MAX;
+
+ return mt_memlimit;
+}
+
+static uint32_t
+filter_xz_get_cputhreads(struct compress_params *params)
+{
+ long threads_max;
+
+ threads_max = lzma_cputhreads();
+ if (threads_max == 0)
+ threads_max = 1;
+
+ if (params->threads_max >= 0)
+ return clamp(params->threads_max, 1, threads_max);
+
+ return threads_max;
+}
+#endif
+
+static void
+filter_unxz_init(struct io_lzma *io, lzma_stream *s)
+{
+#ifdef HAVE_LZMA_MT_DECODER
+ lzma_mt mt_options = {
+ .flags = 0,
+ .block_size = 0,
+ .timeout = 0,
+ .filters = NULL,
+ };
+#else
+ uint64_t memlimit = UINT64_MAX;
+#endif
+ lzma_ret ret;
+
+ io->filter = DPKG_STREAM_DECOMPRESS;
+
+#ifdef HAVE_LZMA_MT_DECODER
+ mt_options.memlimit_stop = UINT64_MAX;
+ mt_options.memlimit_threading = filter_xz_get_memlimit();
+ mt_options.threads = filter_xz_get_cputhreads(io->params);
+
+ ret = lzma_stream_decoder_mt(s, &mt_options);
+#else
+ ret = lzma_stream_decoder(s, memlimit, 0);
+#endif
+ if (ret != LZMA_OK)
+ filter_lzma_error(io, ret);
+}
+
+static void
+filter_xz_init(struct io_lzma *io, lzma_stream *s)
+{
+ uint32_t preset;
+ lzma_check check = LZMA_CHECK_CRC64;
+#ifdef HAVE_LZMA_MT_ENCODER
+ uint64_t mt_memlimit;
+ lzma_mt mt_options = {
+ .flags = 0,
+ .block_size = 0,
+ .timeout = 0,
+ .filters = NULL,
+ .check = check,
+ };
+#endif
+ lzma_ret ret;
+
+ io->filter = DPKG_STREAM_COMPRESS;
+
+ preset = io->params->level;
+ if (io->params->strategy == COMPRESSOR_STRATEGY_EXTREME)
+ preset |= LZMA_PRESET_EXTREME;
+
+#ifdef HAVE_LZMA_MT_ENCODER
+ mt_options.preset = preset;
+ mt_memlimit = filter_xz_get_memlimit();
+ mt_options.threads = filter_xz_get_cputhreads(io->params);
+
+ /* Guess whether we have enough RAM to use the multi-threaded encoder,
+ * and decrease them up to single-threaded to reduce memory usage. */
+ for (; mt_options.threads > 1; mt_options.threads--) {
+ uint64_t mt_memusage;
+
+ mt_memusage = lzma_stream_encoder_mt_memusage(&mt_options);
+ if (mt_memusage < mt_memlimit)
+ break;
+ }
+
+ ret = lzma_stream_encoder_mt(s, &mt_options);
+#else
+ ret = lzma_easy_encoder(s, preset, check);
+#endif
+
+ if (ret != LZMA_OK)
+ filter_lzma_error(io, ret);
+}
+
+static void
+filter_lzma_code(struct io_lzma *io, lzma_stream *s)
+{
+ lzma_ret ret;
+ lzma_action action;
+
+ if (io->action == DPKG_STREAM_RUN)
+ action = LZMA_RUN;
+ else if (io->action == DPKG_STREAM_FINISH)
+ action = LZMA_FINISH;
+ else
+ internerr("unknown stream filter action %d\n", io->action);
+
+ ret = lzma_code(s, action);
+
+ if (ret == LZMA_STREAM_END)
+ io->status = DPKG_STREAM_END;
+ else if (ret != LZMA_OK)
+ filter_lzma_error(io, ret);
+}
+
+static void
+filter_lzma_done(struct io_lzma *io, lzma_stream *s)
+{
+ lzma_end(s);
+}
+
+static void
+decompress_xz(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct io_lzma io;
+
+ io.init = filter_unxz_init;
+ io.code = filter_lzma_code;
+ io.done = filter_lzma_done;
+ io.desc = desc;
+ io.params = params;
+
+ filter_lzma(&io, fd_in, fd_out);
+}
+
+static void
+compress_xz(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct io_lzma io;
+
+ io.init = filter_xz_init;
+ io.code = filter_lzma_code;
+ io.done = filter_lzma_done;
+ io.desc = desc;
+ io.params = params;
+
+ filter_lzma(&io, fd_in, fd_out);
+}
+#else
+static const char *env_xz[] = { "XZ_DEFAULTS", "XZ_OPT", NULL };
+
+static void
+decompress_xz(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct command cmd;
+ char *threads_opt = NULL;
+
+ command_decompress_init(&cmd, XZ, desc);
+
+ if (params->threads_max > 0) {
+ threads_opt = str_fmt("-T%d", params->threads_max);
+ command_add_arg(&cmd, threads_opt);
+ }
+
+ fd_fd_filter(&cmd, fd_in, fd_out, env_xz);
+
+ command_destroy(&cmd);
+ free(threads_opt);
+}
+
+static void
+compress_xz(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct command cmd;
+ char *threads_opt = NULL;
+
+ command_compress_init(&cmd, XZ, desc, params->level);
+
+ if (params->strategy == COMPRESSOR_STRATEGY_EXTREME)
+ command_add_arg(&cmd, "-e");
+
+ if (params->threads_max > 0) {
+ /* Do not generate warnings when adjusting memory usage, nor
+ * exit with non-zero due to those not emitted warnings. */
+ command_add_arg(&cmd, "--quiet");
+ command_add_arg(&cmd, "--no-warn");
+
+ /* Do not let xz fallback to single-threaded mode, to avoid
+ * non-reproducible output. */
+ command_add_arg(&cmd, "--no-adjust");
+
+ /* The xz -T1 option selects a single-threaded mode which
+ * generates different output than in multi-threaded mode.
+ * To avoid the non-reproducible output we pass -T+1
+ * (supported with xz >= 5.4.0) to request multi-threaded
+ * mode with a single thread. */
+ if (params->threads_max == 1)
+ threads_opt = m_strdup("-T+1");
+ else
+ threads_opt = str_fmt("-T%d", params->threads_max);
+ command_add_arg(&cmd, threads_opt);
+ }
+
+ fd_fd_filter(&cmd, fd_in, fd_out, env_xz);
+
+ command_destroy(&cmd);
+ free(threads_opt);
+}
+#endif
+
+static const struct compressor compressor_xz = {
+ .name = "xz",
+ .extension = ".xz",
+ .default_level = 6,
+ .fixup_params = fixup_none_params,
+ .compress = compress_xz,
+ .decompress = decompress_xz,
+};
+
+/*
+ * Lzma compressor.
+ */
+
+#ifdef WITH_LIBLZMA
+static void
+filter_unlzma_init(struct io_lzma *io, lzma_stream *s)
+{
+ uint64_t memlimit = UINT64_MAX;
+ lzma_ret ret;
+
+ io->filter = DPKG_STREAM_DECOMPRESS;
+
+ ret = lzma_alone_decoder(s, memlimit);
+ if (ret != LZMA_OK)
+ filter_lzma_error(io, ret);
+}
+
+static void
+filter_lzma_init(struct io_lzma *io, lzma_stream *s)
+{
+ uint32_t preset;
+ lzma_options_lzma options;
+ lzma_ret ret;
+
+ io->filter = DPKG_STREAM_COMPRESS;
+
+ preset = io->params->level;
+ if (io->params->strategy == COMPRESSOR_STRATEGY_EXTREME)
+ preset |= LZMA_PRESET_EXTREME;
+ if (lzma_lzma_preset(&options, preset))
+ filter_lzma_error(io, LZMA_OPTIONS_ERROR);
+
+ ret = lzma_alone_encoder(s, &options);
+ if (ret != LZMA_OK)
+ filter_lzma_error(io, ret);
+}
+
+static void
+decompress_lzma(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct io_lzma io;
+
+ io.init = filter_unlzma_init;
+ io.code = filter_lzma_code;
+ io.done = filter_lzma_done;
+ io.desc = desc;
+ io.params = params;
+
+ filter_lzma(&io, fd_in, fd_out);
+}
+
+static void
+compress_lzma(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct io_lzma io;
+
+ io.init = filter_lzma_init;
+ io.code = filter_lzma_code;
+ io.done = filter_lzma_done;
+ io.desc = desc;
+ io.params = params;
+
+ filter_lzma(&io, fd_in, fd_out);
+}
+#else
+static void
+decompress_lzma(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct command cmd;
+
+ command_decompress_init(&cmd, XZ, desc);
+ command_add_arg(&cmd, "--format=lzma");
+
+ fd_fd_filter(&cmd, fd_in, fd_out, env_xz);
+
+ command_destroy(&cmd);
+}
+
+static void
+compress_lzma(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct command cmd;
+
+ command_compress_init(&cmd, XZ, desc, params->level);
+ command_add_arg(&cmd, "--format=lzma");
+
+ fd_fd_filter(&cmd, fd_in, fd_out, env_xz);
+
+ command_destroy(&cmd);
+}
+#endif
+
+static const struct compressor compressor_lzma = {
+ .name = "lzma",
+ .extension = ".lzma",
+ .default_level = 6,
+ .fixup_params = fixup_none_params,
+ .compress = compress_lzma,
+ .decompress = decompress_lzma,
+};
+
+/*
+ * ZStandard compressor.
+ */
+
+#define ZSTD "zstd"
+
+#ifdef WITH_LIBZSTD
+struct io_zstd_stream {
+ enum dpkg_stream_filter filter;
+ enum dpkg_stream_action action;
+ enum dpkg_stream_status status;
+
+ union {
+ ZSTD_CCtx *c;
+ ZSTD_DCtx *d;
+ } ctx;
+
+ const uint8_t *next_in;
+ size_t avail_in;
+ uint8_t *next_out;
+ size_t avail_out;
+};
+
+struct io_zstd {
+ const char *desc;
+
+ struct compress_params *params;
+
+ void (*init)(struct io_zstd *io, struct io_zstd_stream *s);
+ void (*code)(struct io_zstd *io, struct io_zstd_stream *s);
+ void (*done)(struct io_zstd *io, struct io_zstd_stream *s);
+};
+
+static void DPKG_ATTR_NORET
+filter_zstd_error(struct io_zstd *io, size_t ret)
+{
+ ohshit(_("%s: zstd error: %s"), io->desc, ZSTD_getErrorName(ret));
+}
+
+static uint32_t
+filter_zstd_get_cputhreads(struct compress_params *params)
+{
+ ZSTD_bounds workers;
+ long threads_max = 1;
+
+ /* The shared library has not been built with multi-threading. */
+ workers = ZSTD_cParam_getBounds(ZSTD_c_nbWorkers);
+ if (workers.upperBound == 0)
+ return 1;
+
+#ifdef _SC_NPROCESSORS_ONLN
+ threads_max = sysconf(_SC_NPROCESSORS_ONLN);
+ if (threads_max < 0)
+ return 1;
+#endif
+
+ if (params->threads_max >= 0)
+ return clamp(params->threads_max, 1, threads_max);
+
+ return threads_max;
+}
+
+static size_t
+filter_zstd_get_buf_in_size(struct io_zstd_stream *s)
+{
+ if (s->filter == DPKG_STREAM_DECOMPRESS)
+ return ZSTD_DStreamInSize();
+ else
+ return ZSTD_CStreamInSize();
+}
+
+static size_t
+filter_zstd_get_buf_out_size(struct io_zstd_stream *s)
+{
+ if (s->filter == DPKG_STREAM_DECOMPRESS)
+ return ZSTD_DStreamOutSize();
+ else
+ return ZSTD_CStreamOutSize();
+}
+
+static void
+filter_unzstd_init(struct io_zstd *io, struct io_zstd_stream *s)
+{
+ s->filter = DPKG_STREAM_DECOMPRESS;
+ s->action = DPKG_STREAM_RUN;
+ s->status = DPKG_STREAM_OK;
+
+ s->ctx.d = ZSTD_createDCtx();
+ if (s->ctx.d == NULL)
+ ohshit(_("%s: cannot create zstd decompression context"),
+ io->desc);
+}
+
+static void
+filter_unzstd_code(struct io_zstd *io, struct io_zstd_stream *s)
+{
+ ZSTD_inBuffer buf_in = { s->next_in, s->avail_in, 0 };
+ ZSTD_outBuffer buf_out = { s->next_out, s->avail_out, 0 };
+ size_t ret;
+
+ ret = ZSTD_decompressStream(s->ctx.d, &buf_out, &buf_in);
+ if (ZSTD_isError(ret))
+ filter_zstd_error(io, ret);
+
+ s->next_in += buf_in.pos;
+ s->avail_in -= buf_in.pos;
+ s->next_out += buf_out.pos;
+ s->avail_out -= buf_out.pos;
+
+ if (ret == 0)
+ s->status = DPKG_STREAM_END;
+}
+
+static void
+filter_unzstd_done(struct io_zstd *io, struct io_zstd_stream *s)
+{
+ ZSTD_freeDCtx(s->ctx.d);
+}
+
+static void
+filter_zstd_init(struct io_zstd *io, struct io_zstd_stream *s)
+{
+ int clevel = io->params->level;
+ uint32_t nthreads;
+ size_t ret;
+
+ s->filter = DPKG_STREAM_COMPRESS;
+ s->action = DPKG_STREAM_RUN;
+ s->status = DPKG_STREAM_OK;
+
+ s->ctx.c = ZSTD_createCCtx();
+ if (s->ctx.c == NULL)
+ ohshit(_("%s: cannot create zstd compression context"),
+ io->desc);
+
+ ret = ZSTD_CCtx_setParameter(s->ctx.c, ZSTD_c_compressionLevel, clevel);
+ if (ZSTD_isError(ret))
+ filter_zstd_error(io, ret);
+ ret = ZSTD_CCtx_setParameter(s->ctx.c, ZSTD_c_checksumFlag, 1);
+ if (ZSTD_isError(ret))
+ filter_zstd_error(io, ret);
+
+ nthreads = filter_zstd_get_cputhreads(io->params);
+ if (nthreads > 1)
+ ZSTD_CCtx_setParameter(s->ctx.c, ZSTD_c_nbWorkers, nthreads);
+}
+
+static void
+filter_zstd_code(struct io_zstd *io, struct io_zstd_stream *s)
+{
+ ZSTD_inBuffer buf_in = { s->next_in, s->avail_in, 0 };
+ ZSTD_outBuffer buf_out = { s->next_out, s->avail_out, 0 };
+ ZSTD_EndDirective action;
+ size_t ret;
+
+ if (s->action == DPKG_STREAM_FINISH)
+ action = ZSTD_e_end;
+ else
+ action = ZSTD_e_continue;
+
+ ret = ZSTD_compressStream2(s->ctx.c, &buf_out, &buf_in, action);
+ if (ZSTD_isError(ret))
+ filter_zstd_error(io, ret);
+
+ s->next_in += buf_in.pos;
+ s->avail_in -= buf_in.pos;
+ s->next_out += buf_out.pos;
+ s->avail_out -= buf_out.pos;
+
+ if (s->action == DPKG_STREAM_FINISH && ret == 0)
+ s->status = DPKG_STREAM_END;
+}
+
+static void
+filter_zstd_done(struct io_zstd *io, struct io_zstd_stream *s)
+{
+ ZSTD_freeCCtx(s->ctx.c);
+}
+
+static void
+filter_zstd(struct io_zstd *io, int fd_in, int fd_out)
+{
+ ssize_t buf_in_size;
+ ssize_t buf_out_size;
+ uint8_t *buf_in;
+ uint8_t *buf_out;
+ struct io_zstd_stream s = {
+ .action = DPKG_STREAM_INIT,
+ };
+
+ io->init(io, &s);
+
+ buf_in_size = filter_zstd_get_buf_in_size(&s);
+ buf_in = m_malloc(buf_in_size);
+ buf_out_size = filter_zstd_get_buf_out_size(&s);
+ buf_out = m_malloc(buf_out_size);
+
+ s.next_out = buf_out;
+ s.avail_out = buf_out_size;
+
+ do {
+ ssize_t len;
+
+ if (s.avail_in == 0 && s.action != DPKG_STREAM_FINISH) {
+ len = fd_read(fd_in, buf_in, buf_in_size);
+ if (len < 0)
+ ohshite(_("%s: zstd read error"), io->desc);
+ if (len < buf_in_size)
+ s.action = DPKG_STREAM_FINISH;
+ s.next_in = buf_in;
+ s.avail_in = len;
+ }
+
+ io->code(io, &s);
+
+ if (s.avail_out == 0 || s.status == DPKG_STREAM_END) {
+ len = fd_write(fd_out, buf_out, s.next_out - buf_out);
+ if (len < 0)
+ ohshite(_("%s: zstd write error"), io->desc);
+ s.next_out = buf_out;
+ s.avail_out = buf_out_size;
+ }
+ } while (s.status != DPKG_STREAM_END);
+
+ io->done(io, &s);
+
+ free(buf_in);
+ free(buf_out);
+
+ if (close(fd_out))
+ ohshite(_("%s: zstd close error"), io->desc);
+}
+
+static void
+decompress_zstd(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct io_zstd io;
+
+ io.init = filter_unzstd_init;
+ io.code = filter_unzstd_code;
+ io.done = filter_unzstd_done;
+ io.desc = desc;
+ io.params = params;
+
+ filter_zstd(&io, fd_in, fd_out);
+}
+
+static void
+compress_zstd(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct io_zstd io;
+
+ io.init = filter_zstd_init;
+ io.code = filter_zstd_code;
+ io.done = filter_zstd_done;
+ io.desc = desc;
+ io.params = params;
+
+ filter_zstd(&io, fd_in, fd_out);
+}
+#else
+static const char *env_zstd[] = {
+ "ZSTD_CLEVEL",
+ "ZSTD_NBTHREADS",
+ NULL,
+};
+
+static void
+decompress_zstd(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct command cmd;
+ char *threads_opt = NULL;
+
+ command_decompress_init(&cmd, ZSTD, desc);
+ command_add_arg(&cmd, "-q");
+
+ if (params->threads_max > 0) {
+ threads_opt = str_fmt("-T%d", params->threads_max);
+ command_add_arg(&cmd, threads_opt);
+ }
+
+ fd_fd_filter(&cmd, fd_in, fd_out, env_zstd);
+
+ command_destroy(&cmd);
+ free(threads_opt);
+}
+
+static void
+compress_zstd(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct command cmd;
+ char *threads_opt = NULL;
+
+ command_compress_init(&cmd, ZSTD, desc, params->level);
+ command_add_arg(&cmd, "-q");
+
+ if (params->level > 19)
+ command_add_arg(&cmd, "--ultra");
+
+ if (params->threads_max > 0) {
+ threads_opt = str_fmt("-T%d", params->threads_max);
+ command_add_arg(&cmd, threads_opt);
+ }
+
+ fd_fd_filter(&cmd, fd_in, fd_out, env_zstd);
+
+ command_destroy(&cmd);
+ free(threads_opt);
+}
+#endif
+
+static const struct compressor compressor_zstd = {
+ .name = "zstd",
+ .extension = ".zst",
+ .default_level = ZSTD_CLEVEL_DEFAULT,
+ .fixup_params = fixup_none_params,
+ .compress = compress_zstd,
+ .decompress = decompress_zstd,
+};
+
+/*
+ * Generic compressor filter.
+ */
+
+static const struct compressor *compressor_array[] = {
+ [COMPRESSOR_TYPE_NONE] = &compressor_none,
+ [COMPRESSOR_TYPE_GZIP] = &compressor_gzip,
+ [COMPRESSOR_TYPE_XZ] = &compressor_xz,
+ [COMPRESSOR_TYPE_ZSTD] = &compressor_zstd,
+ [COMPRESSOR_TYPE_BZIP2] = &compressor_bzip2,
+ [COMPRESSOR_TYPE_LZMA] = &compressor_lzma,
+};
+
+static const struct compressor *
+compressor(enum compressor_type type)
+{
+ const enum compressor_type max_type = array_count(compressor_array);
+
+ if (type < 0 || type >= max_type)
+ internerr("compressor_type %d is out of range", type);
+
+ return compressor_array[type];
+}
+
+const char *
+compressor_get_name(enum compressor_type type)
+{
+ return compressor(type)->name;
+}
+
+const char *
+compressor_get_extension(enum compressor_type type)
+{
+ return compressor(type)->extension;
+}
+
+enum compressor_type
+compressor_find_by_name(const char *name)
+{
+ size_t i;
+
+ for (i = 0; i < array_count(compressor_array); i++)
+ if (strcmp(compressor_array[i]->name, name) == 0)
+ return i;
+
+ return COMPRESSOR_TYPE_UNKNOWN;
+}
+
+enum compressor_type
+compressor_find_by_extension(const char *extension)
+{
+ size_t i;
+
+ for (i = 0; i < array_count(compressor_array); i++)
+ if (strcmp(compressor_array[i]->extension, extension) == 0)
+ return i;
+
+ return COMPRESSOR_TYPE_UNKNOWN;
+}
+
+enum compressor_strategy
+compressor_get_strategy(const char *name)
+{
+ if (strcmp(name, "none") == 0)
+ return COMPRESSOR_STRATEGY_NONE;
+ if (strcmp(name, "filtered") == 0)
+ return COMPRESSOR_STRATEGY_FILTERED;
+ if (strcmp(name, "huffman") == 0)
+ return COMPRESSOR_STRATEGY_HUFFMAN;
+ if (strcmp(name, "rle") == 0)
+ return COMPRESSOR_STRATEGY_RLE;
+ if (strcmp(name, "fixed") == 0)
+ return COMPRESSOR_STRATEGY_FIXED;
+ if (strcmp(name, "extreme") == 0)
+ return COMPRESSOR_STRATEGY_EXTREME;
+
+ return COMPRESSOR_STRATEGY_UNKNOWN;
+}
+
+static void
+compressor_fixup_params(struct compress_params *params)
+{
+ compressor(params->type)->fixup_params(params);
+
+ if (params->level < 0)
+ params->level = compressor(params->type)->default_level;
+}
+
+bool
+compressor_check_params(struct compress_params *params, struct dpkg_error *err)
+{
+ compressor_fixup_params(params);
+
+ if ((params->type == COMPRESSOR_TYPE_ZSTD &&
+ params->level > DPKG_ZSTD_MAX_LEVEL) ||
+ (params->type != COMPRESSOR_TYPE_ZSTD &&
+ params->level > 9)) {
+ dpkg_put_error(err, _("invalid compression level %d"),
+ params->level);
+ return false;
+ }
+
+ if (params->strategy == COMPRESSOR_STRATEGY_NONE)
+ return true;
+
+ if (params->type == COMPRESSOR_TYPE_GZIP &&
+ (params->strategy == COMPRESSOR_STRATEGY_FILTERED ||
+ params->strategy == COMPRESSOR_STRATEGY_HUFFMAN ||
+ params->strategy == COMPRESSOR_STRATEGY_RLE ||
+ params->strategy == COMPRESSOR_STRATEGY_FIXED))
+ return true;
+
+ if (params->type == COMPRESSOR_TYPE_XZ &&
+ params->strategy == COMPRESSOR_STRATEGY_EXTREME)
+ return true;
+
+ dpkg_put_error(err, _("unknown compression strategy"));
+ return false;
+}
+
+void
+decompress_filter(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc_fmt, ...)
+{
+ va_list args;
+ struct varbuf desc = VARBUF_INIT;
+
+ va_start(args, desc_fmt);
+ varbuf_vprintf(&desc, desc_fmt, args);
+ va_end(args);
+
+ compressor(params->type)->decompress(params, fd_in, fd_out, desc.buf);
+
+ varbuf_destroy(&desc);
+}
+
+void
+compress_filter(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc_fmt, ...)
+{
+ va_list args;
+ struct varbuf desc = VARBUF_INIT;
+
+ va_start(args, desc_fmt);
+ varbuf_vprintf(&desc, desc_fmt, args);
+ va_end(args);
+
+ compressor(params->type)->compress(params, fd_in, fd_out, desc.buf);
+
+ varbuf_destroy(&desc);
+}
diff --git a/lib/dpkg/compress.h b/lib/dpkg/compress.h
new file mode 100644
index 0000000..b05bccf
--- /dev/null
+++ b/lib/dpkg/compress.h
@@ -0,0 +1,89 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * compress.h - compression support functions
+ *
+ * Copyright © 2004 Scott James Remnant <scott@netsplit.com>
+ * Copyright © 2006-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_COMPRESS_H
+#define LIBDPKG_COMPRESS_H
+
+#include <dpkg/macros.h>
+#include <dpkg/error.h>
+
+#include <stdbool.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup compress Compression
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+enum compressor_type {
+ COMPRESSOR_TYPE_UNKNOWN = -1,
+ COMPRESSOR_TYPE_NONE,
+ COMPRESSOR_TYPE_GZIP,
+ COMPRESSOR_TYPE_XZ,
+ COMPRESSOR_TYPE_ZSTD,
+ COMPRESSOR_TYPE_BZIP2,
+ COMPRESSOR_TYPE_LZMA,
+
+ COMPRESSOR_TYPE_MAX,
+};
+
+enum compressor_strategy {
+ COMPRESSOR_STRATEGY_UNKNOWN = -1,
+ COMPRESSOR_STRATEGY_NONE,
+ COMPRESSOR_STRATEGY_FILTERED,
+ COMPRESSOR_STRATEGY_HUFFMAN,
+ COMPRESSOR_STRATEGY_RLE,
+ COMPRESSOR_STRATEGY_FIXED,
+ COMPRESSOR_STRATEGY_EXTREME,
+};
+
+struct compress_params {
+ enum compressor_type type;
+ enum compressor_strategy strategy;
+ int level;
+ int threads_max;
+};
+
+enum compressor_type compressor_find_by_name(const char *name);
+enum compressor_type compressor_find_by_extension(const char *name);
+
+const char *compressor_get_name(enum compressor_type type);
+const char *compressor_get_extension(enum compressor_type type);
+
+enum compressor_strategy compressor_get_strategy(const char *name);
+
+bool compressor_check_params(struct compress_params *params,
+ struct dpkg_error *err);
+
+void decompress_filter(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc, ...)
+ DPKG_ATTR_PRINTF(4);
+void compress_filter(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc, ...)
+ DPKG_ATTR_PRINTF(4);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_COMPRESS_H */
diff --git a/lib/dpkg/db-ctrl-access.c b/lib/dpkg/db-ctrl-access.c
new file mode 100644
index 0000000..c252336
--- /dev/null
+++ b/lib/dpkg/db-ctrl-access.c
@@ -0,0 +1,119 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * db-ctrl-access.c - package control information database
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2011-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <dirent.h>
+#include <unistd.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/fsys.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/debug.h>
+
+bool
+pkg_infodb_has_file(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ const char *name)
+{
+ const char *filename;
+ struct stat stab;
+
+ filename = pkg_infodb_get_file(pkg, pkgbin, name);
+ if (lstat(filename, &stab) == 0)
+ return true;
+ else if (errno == ENOENT)
+ return false;
+ else
+ ohshite(_("unable to check existence of '%.250s'"), filename);
+}
+
+void
+pkg_infodb_foreach(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ pkg_infodb_file_func *func)
+{
+ DIR *db_dir;
+ struct dirent *db_de;
+ struct varbuf_state db_path_state;
+ struct varbuf db_path = VARBUF_INIT;
+ const char *pkgname;
+ enum pkg_infodb_format db_format;
+
+ /* Make sure to always read and verify the format version. */
+ db_format = pkg_infodb_get_format();
+
+ if (pkgbin->multiarch == PKG_MULTIARCH_SAME &&
+ db_format == PKG_INFODB_FORMAT_MULTIARCH)
+ pkgname = pkgbin_name(pkg, pkgbin, pnaw_always);
+ else
+ pkgname = pkgbin_name(pkg, pkgbin, pnaw_never);
+
+ varbuf_add_dir(&db_path, pkg_infodb_get_dir());
+ varbuf_end_str(&db_path);
+ varbuf_snapshot(&db_path, &db_path_state);
+
+ db_dir = opendir(db_path.buf);
+ if (!db_dir)
+ ohshite(_("cannot read info directory"));
+
+ push_cleanup(cu_closedir, ~0, 1, (void *)db_dir);
+ while ((db_de = readdir(db_dir)) != NULL) {
+ const char *filename, *filetype, *dot;
+
+ debug(dbg_veryverbose, "infodb foreach info file '%s'",
+ db_de->d_name);
+
+ /* Ignore dotfiles, including ‘.’ and ‘..’. */
+ if (db_de->d_name[0] == '.')
+ continue;
+
+ /* Ignore anything odd. */
+ dot = strrchr(db_de->d_name, '.');
+ if (dot == NULL)
+ continue;
+
+ /* Ignore files from other packages. */
+ if (strlen(pkgname) != (size_t)(dot - db_de->d_name) ||
+ strncmp(db_de->d_name, pkgname, dot - db_de->d_name))
+ continue;
+
+ debug(dbg_stupidlyverbose, "infodb foreach file this pkg");
+
+ /* Skip past the full stop. */
+ filetype = dot + 1;
+
+ varbuf_rollback(&db_path_state);
+ varbuf_add_str(&db_path, db_de->d_name);
+ varbuf_end_str(&db_path);
+ filename = db_path.buf;
+
+ func(filename, filetype);
+ }
+ pop_cleanup(ehflag_normaltidy); /* closedir */
+
+ varbuf_destroy(&db_path);
+}
diff --git a/lib/dpkg/db-ctrl-format.c b/lib/dpkg/db-ctrl-format.c
new file mode 100644
index 0000000..34b5ff5
--- /dev/null
+++ b/lib/dpkg/db-ctrl-format.c
@@ -0,0 +1,157 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * db-ctrl-format.c - package control information database format
+ *
+ * Copyright © 2011-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/varbuf.h>
+#include <dpkg/db-ctrl.h>
+
+static enum pkg_infodb_format db_format = PKG_INFODB_FORMAT_UNKNOWN;
+static bool db_upgrading;
+static char *db_infodir;
+
+static enum pkg_infodb_format
+pkg_infodb_parse_format(const char *file)
+{
+ FILE *fp;
+ unsigned int format;
+
+ fp = fopen(file, "r");
+ if (fp == NULL) {
+ /* A missing format file means legacy format (0). */
+ if (errno == ENOENT)
+ return PKG_INFODB_FORMAT_LEGACY;
+ ohshite(_("error trying to open %.250s"), file);
+ }
+
+ if (fscanf(fp, "%u", &format) != 1)
+ ohshit(_("corrupt info database format file '%s'"), file);
+
+ fclose(fp);
+
+ return format;
+}
+
+static enum pkg_infodb_format
+pkg_infodb_read_format(void)
+{
+ struct atomic_file *file;
+ struct stat st;
+ char *filename;
+
+ filename = dpkg_db_get_path(INFODIR "/format");
+ file = atomic_file_new(filename, 0);
+
+ db_format = pkg_infodb_parse_format(file->name);
+
+ /* Check if a previous upgrade got interrupted. Because we are only
+ * supposed to upgrade the db layout one format at a time, if the
+ * new file exists that means the new format is just one ahead,
+ * we don't try to read it because it contains unreliable data. */
+ if (stat(file->name_new, &st) == 0) {
+ db_format++;
+ db_upgrading = true;
+ }
+
+ atomic_file_free(file);
+ free(filename);
+
+ if (db_format < 0 || db_format >= PKG_INFODB_FORMAT_LAST)
+ ohshit(_("info database format (%d) is bogus or too new; "
+ "try getting a newer dpkg"), db_format);
+
+ return db_format;
+}
+
+enum pkg_infodb_format
+pkg_infodb_get_format(void)
+{
+ if (db_format > PKG_INFODB_FORMAT_UNKNOWN)
+ return db_format;
+ else
+ return pkg_infodb_read_format();
+}
+
+void
+pkg_infodb_set_format(enum pkg_infodb_format version)
+{
+ db_format = version;
+}
+
+bool
+pkg_infodb_is_upgrading(void)
+{
+ if (db_format < 0)
+ pkg_infodb_read_format();
+
+ return db_upgrading;
+}
+
+const char *
+pkg_infodb_get_dir(void)
+{
+ if (db_infodir == NULL)
+ db_infodir = dpkg_db_get_path(INFODIR);
+
+ return db_infodir;
+}
+
+const char *
+pkg_infodb_get_file(const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ const char *filetype)
+{
+ static struct varbuf vb;
+ enum pkg_infodb_format format;
+
+ /* Make sure to always read and verify the format version. */
+ format = pkg_infodb_get_format();
+
+ varbuf_reset(&vb);
+ varbuf_add_dir(&vb, pkg_infodb_get_dir());
+ varbuf_add_str(&vb, pkg->set->name);
+ if (pkgbin->multiarch == PKG_MULTIARCH_SAME &&
+ format == PKG_INFODB_FORMAT_MULTIARCH)
+ varbuf_add_archqual(&vb, pkgbin->arch);
+ varbuf_add_char(&vb, '.');
+ varbuf_add_str(&vb, filetype);
+ varbuf_end_str(&vb);
+
+ return vb.buf;
+}
+
+const char *
+pkg_infodb_reset_dir(void)
+{
+ free(db_infodir);
+ db_infodir = NULL;
+
+ return pkg_infodb_get_dir();
+}
diff --git a/lib/dpkg/db-ctrl-upgrade.c b/lib/dpkg/db-ctrl-upgrade.c
new file mode 100644
index 0000000..86f584b
--- /dev/null
+++ b/lib/dpkg/db-ctrl-upgrade.c
@@ -0,0 +1,250 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * db-ctrl-upgrade.c - package control information database format upgrade
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2011-2014 Guillem Jover <guillem@debian.org>
+ * Copyright © 2011 Linaro Limited
+ * Copyright © 2011 Raphaël Hertzog <hertzog@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/fsys.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/path.h>
+#include <dpkg/dir.h>
+
+struct rename_node {
+ struct rename_node *next;
+ char *old;
+ char *new;
+};
+
+/* Global variables. */
+static struct rename_node *rename_head = NULL;
+
+static struct rename_node *
+rename_node_new(const char *old, const char *new, struct rename_node *next)
+{
+ struct rename_node *node;
+
+ node = m_malloc(sizeof(*node));
+ node->next = next;
+ node->old = m_strdup(old);
+ node->new = m_strdup(new);
+
+ return node;
+}
+
+static void
+rename_node_free(struct rename_node *node)
+{
+ free(node->old);
+ free(node->new);
+ free(node);
+}
+
+static void
+pkg_infodb_link_multiarch_files(void)
+{
+ DIR *db_dir;
+ struct dirent *db_de;
+ struct varbuf pkgname = VARBUF_INIT;
+ struct varbuf oldname = VARBUF_INIT;
+ struct varbuf newname = VARBUF_INIT;
+ struct varbuf_state oldname_state;
+ struct varbuf_state newname_state;
+
+ varbuf_add_dir(&oldname, pkg_infodb_get_dir());
+ varbuf_end_str(&oldname);
+ varbuf_snapshot(&oldname, &oldname_state);
+
+ varbuf_set_varbuf(&newname, &oldname);
+ varbuf_snapshot(&newname, &newname_state);
+
+ db_dir = opendir(pkg_infodb_get_dir());
+ if (!db_dir)
+ ohshite(_("cannot read info directory"));
+
+ push_cleanup(cu_closedir, ~0, 1, (void *)db_dir);
+ while ((db_de = readdir(db_dir)) != NULL) {
+ const char *filetype, *dot;
+ struct pkginfo *pkg;
+ struct pkgset *set;
+
+ /* Ignore dotfiles, including ‘.’ and ‘..’. */
+ if (db_de->d_name[0] == '.')
+ continue;
+
+ /* Ignore anything odd. */
+ dot = strrchr(db_de->d_name, '.');
+ if (dot == NULL)
+ continue;
+
+ varbuf_set_buf(&pkgname, db_de->d_name, dot - db_de->d_name);
+
+ /* Skip files already converted. */
+ if (strchr(pkgname.buf, ':'))
+ continue;
+
+ set = pkg_hash_find_set(pkgname.buf);
+ for (pkg = &set->pkg; pkg; pkg = pkg->arch_next)
+ if (pkg->status != PKG_STAT_NOTINSTALLED)
+ break;
+ if (!pkg) {
+ warning(_("info file %s/%s not associated to any package"),
+ pkg_infodb_get_dir(), db_de->d_name);
+ continue;
+ }
+
+ /* Does it need to be upgraded? */
+ if (pkg->installed.multiarch != PKG_MULTIARCH_SAME)
+ continue;
+
+ /* Skip past the full stop. */
+ filetype = dot + 1;
+
+ varbuf_rollback(&oldname_state);
+ varbuf_add_str(&oldname, db_de->d_name);
+ varbuf_end_str(&oldname);
+
+ varbuf_rollback(&newname_state);
+ varbuf_add_pkgbin_name(&newname, pkg, &pkg->installed, pnaw_always);
+ varbuf_add_char(&newname, '.');
+ varbuf_add_str(&newname, filetype);
+ varbuf_end_str(&newname);
+
+ if (link(oldname.buf, newname.buf) && errno != EEXIST)
+ ohshite(_("error creating hard link '%.255s'"),
+ newname.buf);
+ rename_head = rename_node_new(oldname.buf, newname.buf, rename_head);
+ }
+ pop_cleanup(ehflag_normaltidy); /* closedir */
+
+ varbuf_destroy(&pkgname);
+ varbuf_destroy(&newname);
+ varbuf_destroy(&oldname);
+}
+
+static void
+cu_abort_db_upgrade(int argc, void **argv)
+{
+ struct atomic_file *file = argv[0];
+ struct rename_node *next;
+
+ /* Restore the old files if needed and drop the newly created files. */
+ while (rename_head) {
+ next = rename_head->next;
+ if (link(rename_head->new, rename_head->old) && errno != EEXIST)
+ ohshite(_("error creating hard link '%.255s'"),
+ rename_head->old);
+ if (unlink(rename_head->new))
+ ohshite(_("cannot remove '%.250s'"), rename_head->new);
+ rename_node_free(rename_head);
+ rename_head = next;
+ }
+ if (unlink(file->name_new) && errno != ENOENT)
+ ohshite(_("cannot remove '%.250s'"), file->name_new);
+
+ atomic_file_free(file);
+}
+
+static void
+pkg_infodb_write_format(struct atomic_file *file, int version)
+{
+ if (fprintf(file->fp, "%d\n", version) < 0)
+ ohshite(_("error while writing '%s'"), file->name_new);
+
+ atomic_file_sync(file);
+ atomic_file_close(file);
+ dir_sync_path_parent(file->name);
+
+ pkg_infodb_set_format(version);
+}
+
+static void
+pkg_infodb_unlink_monoarch_files(void)
+{
+ struct rename_node *next;
+
+ while (rename_head) {
+ next = rename_head->next;
+ if (unlink(rename_head->old))
+ ohshite(_("cannot remove '%.250s'"), rename_head->old);
+ rename_node_free(rename_head);
+ rename_head = next;
+ }
+}
+
+static void
+pkg_infodb_upgrade_to_multiarch(void)
+{
+ struct atomic_file *db_file;
+ char *db_format_file;
+
+ db_format_file = dpkg_db_get_path(INFODIR "/format");
+ db_file = atomic_file_new(db_format_file, ATOMIC_FILE_MKPATH);
+ atomic_file_open(db_file);
+
+ push_cleanup(cu_abort_db_upgrade, ehflag_bombout, 1, db_file);
+
+ pkg_infodb_link_multiarch_files();
+ pkg_infodb_write_format(db_file, 1);
+
+ pkg_infodb_unlink_monoarch_files();
+ atomic_file_commit(db_file);
+ dir_sync_path(pkg_infodb_get_dir());
+
+ pop_cleanup(ehflag_normaltidy);
+
+ atomic_file_free(db_file);
+ free(db_format_file);
+}
+
+/**
+ * Upgrade the infodb if there's the need and possibility.
+ *
+ * Currently this implies, that the modstatdb was opened for writing and:
+ * - previous upgrade has not been completed; or
+ * - current format is not the latest one.
+ */
+void
+pkg_infodb_upgrade(void)
+{
+ enum pkg_infodb_format db_format;
+
+ /* Make sure to always read and verify the format version. */
+ db_format = pkg_infodb_get_format();
+
+ if (modstatdb_get_status() < msdbrw_write)
+ return;
+
+ if (db_format < PKG_INFODB_FORMAT_MULTIARCH ||
+ pkg_infodb_is_upgrading())
+ pkg_infodb_upgrade_to_multiarch();
+}
diff --git a/lib/dpkg/db-ctrl.h b/lib/dpkg/db-ctrl.h
new file mode 100644
index 0000000..d21bb7c
--- /dev/null
+++ b/lib/dpkg/db-ctrl.h
@@ -0,0 +1,56 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * db-ctrl.h - package control information database
+ *
+ * Copyright © 2011-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_DB_CTRL_H
+#define LIBDPKG_DB_CTRL_H
+
+#include <stdbool.h>
+
+#include <dpkg/dpkg-db.h>
+
+DPKG_BEGIN_DECLS
+
+enum pkg_infodb_format {
+ PKG_INFODB_FORMAT_UNKNOWN = -1,
+ PKG_INFODB_FORMAT_LEGACY = 0,
+ PKG_INFODB_FORMAT_MULTIARCH = 1,
+ PKG_INFODB_FORMAT_LAST,
+};
+
+enum pkg_infodb_format pkg_infodb_get_format(void);
+void pkg_infodb_set_format(enum pkg_infodb_format format);
+bool pkg_infodb_is_upgrading(void);
+void pkg_infodb_upgrade(void);
+
+const char *pkg_infodb_get_dir(void);
+const char *pkg_infodb_get_file(const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ const char *filetype);
+const char *pkg_infodb_reset_dir(void);
+bool pkg_infodb_has_file(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ const char *name);
+
+typedef void pkg_infodb_file_func(const char *filename, const char *filetype);
+
+void pkg_infodb_foreach(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ pkg_infodb_file_func *func);
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_DB_CTRL_H */
diff --git a/lib/dpkg/db-fsys-digest.c b/lib/dpkg/db-fsys-digest.c
new file mode 100644
index 0000000..5b4252b
--- /dev/null
+++ b/lib/dpkg/db-fsys-digest.c
@@ -0,0 +1,151 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * db-fsys-digest.c - management of filesystem digests database
+ *
+ * Copyright © 2012-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/debug.h>
+#include <dpkg/fdio.h>
+#include <dpkg/dir.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+
+/*
+ * If mask is nonzero, will not write any file whose fsys_namenode
+ * has any flag bits set in mask.
+ */
+void
+write_filehash_except(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ struct fsys_namenode_list *list, enum fsys_namenode_flags mask)
+{
+ struct atomic_file *file;
+ const char *hashfile;
+
+ debug(dbg_general, "generating infodb hashfile");
+
+ if (pkg_infodb_has_file(pkg, &pkg->available, HASHFILE))
+ return;
+
+ hashfile = pkg_infodb_get_file(pkg, pkgbin, HASHFILE);
+
+ file = atomic_file_new(hashfile, 0);
+ atomic_file_open(file);
+
+ for (; list; list = list->next) {
+ const struct fsys_namenode *namenode = list->namenode;
+
+ if (mask && (namenode->flags & mask))
+ continue;
+ if (namenode->newhash == NULL)
+ continue;
+
+ fprintf(file->fp, "%s %s\n",
+ namenode->newhash, namenode->name + 1);
+ }
+
+ atomic_file_sync(file);
+ atomic_file_close(file);
+ atomic_file_commit(file);
+ atomic_file_free(file);
+
+ dir_sync_path(pkg_infodb_get_dir());
+}
+
+static void
+parse_filehash_buffer(struct varbuf *buf,
+ struct pkginfo *pkg, struct pkgbin *pkgbin)
+{
+ char *thisline, *nextline;
+ const char *pkgname = pkg_name(pkg, pnaw_nonambig);
+ const char *buf_end = buf->buf + buf->used;
+
+ for (thisline = buf->buf; thisline < buf_end; thisline = nextline) {
+ struct fsys_namenode *namenode;
+ char *endline, *hash_end, *filename;
+
+ endline = memchr(thisline, '\n', buf_end - thisline);
+ if (endline == NULL)
+ ohshit(_("control file '%s' for package '%s' is "
+ "missing final newline"), HASHFILE, pkgname);
+
+ /* The md5sum hash has a constant length. */
+ hash_end = thisline + MD5HASHLEN;
+
+ filename = hash_end + 2;
+ if (filename + 1 > endline)
+ ohshit(_("control file '%s' for package '%s' is "
+ "missing value"), HASHFILE, pkgname);
+
+ if (hash_end[0] != ' ' || hash_end[1] != ' ')
+ ohshit(_("control file '%s' for package '%s' is "
+ "missing value separator"), HASHFILE, pkgname);
+ hash_end[0] = '\0';
+
+ /* Where to start next time around. */
+ nextline = endline + 1;
+
+ /* Strip trailing ‘/’. */
+ if (endline > thisline && endline[-1] == '/')
+ endline--;
+ *endline = '\0';
+
+ if (endline == thisline)
+ ohshit(_("control file '%s' for package '%s' "
+ "contains empty filename"), HASHFILE, pkgname);
+
+ debug(dbg_eachfiledetail, "load digest '%s' for filename '%s'",
+ thisline, filename);
+
+ /* Add the file to the list. */
+ namenode = fsys_hash_find_node(filename, FHFF_NONE);
+ namenode->newhash = nfstrsave(thisline);
+ }
+}
+
+void
+parse_filehash(struct pkginfo *pkg, struct pkgbin *pkgbin)
+{
+ const char *hashfile;
+ struct varbuf buf = VARBUF_INIT;
+ struct dpkg_error err = DPKG_ERROR_INIT;
+
+ hashfile = pkg_infodb_get_file(pkg, pkgbin, HASHFILE);
+
+ if (file_slurp(hashfile, &buf, &err) < 0 && err.syserrno != ENOENT)
+ dpkg_error_print(&err,
+ _("loading control file '%s' for package '%s'"),
+ HASHFILE, pkg_name(pkg, pnaw_nonambig));
+
+ if (buf.used > 0)
+ parse_filehash_buffer(&buf, pkg, pkgbin);
+
+ varbuf_destroy(&buf);
+}
diff --git a/lib/dpkg/db-fsys-divert.c b/lib/dpkg/db-fsys-divert.c
new file mode 100644
index 0000000..e0054bb
--- /dev/null
+++ b/lib/dpkg/db-fsys-divert.c
@@ -0,0 +1,132 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * db-fsys-divert.c - management of filesystem diverted files database
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2000, 2001 Wichert Akkerman <wakkerma@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/debug.h>
+#include <dpkg/db-fsys.h>
+
+static struct fsys_diversion *diversions = NULL;
+static char *diversionsname;
+
+void
+ensure_diversions(void)
+{
+ static struct stat sb_prev;
+ struct stat sb_next;
+ char linebuf[MAXDIVERTFILENAME];
+ static FILE *file_prev;
+ FILE *file;
+ struct fsys_diversion *ov, *oicontest, *oialtname;
+
+ if (diversionsname == NULL)
+ diversionsname = dpkg_db_get_path(DIVERSIONSFILE);
+
+ onerr_abort++;
+
+ file = fopen(diversionsname, "r");
+ if (!file) {
+ if (errno != ENOENT)
+ ohshite(_("failed to open diversions file"));
+ } else {
+ setcloexec(fileno(file), diversionsname);
+
+ if (fstat(fileno(file), &sb_next))
+ ohshite(_("failed to fstat diversions file"));
+
+ /*
+ * We need to keep the database file open so that the
+ * filesystem cannot reuse the inode number (f.ex. during
+ * multiple dpkg-divert invocations in a maintainer script),
+ * otherwise the following check might turn true, and we
+ * would skip reloading a modified database.
+ */
+ if (file_prev &&
+ sb_prev.st_dev == sb_next.st_dev &&
+ sb_prev.st_ino == sb_next.st_ino) {
+ fclose(file);
+ onerr_abort--;
+ debug(dbg_general, "%s: same, skipping", __func__);
+ return;
+ }
+ sb_prev = sb_next;
+ }
+ if (file_prev)
+ fclose(file_prev);
+ file_prev = file;
+
+ for (ov = diversions; ov; ov = ov->next) {
+ ov->useinstead->divert->camefrom->divert = NULL;
+ ov->useinstead->divert = NULL;
+ }
+ diversions = NULL;
+ if (!file) {
+ onerr_abort--;
+ debug(dbg_general, "%s: none, resetting", __func__);
+ return;
+ }
+ debug(dbg_general, "%s: new, (re)loading", __func__);
+
+ while (fgets_checked(linebuf, sizeof(linebuf), file, diversionsname) >= 0) {
+ oicontest = nfmalloc(sizeof(*oicontest));
+ oialtname = nfmalloc(sizeof(*oialtname));
+
+ oialtname->camefrom = fsys_hash_find_node(linebuf, FHFF_NONE);
+ oialtname->useinstead = NULL;
+
+ fgets_must(linebuf, sizeof(linebuf), file, diversionsname);
+ oicontest->useinstead = fsys_hash_find_node(linebuf, FHFF_NONE);
+ oicontest->camefrom = NULL;
+
+ fgets_must(linebuf, sizeof(linebuf), file, diversionsname);
+ oicontest->pkgset = strcmp(linebuf, ":") ?
+ pkg_hash_find_set(linebuf) : NULL;
+ oialtname->pkgset = oicontest->pkgset;
+
+ if (oialtname->camefrom->divert ||
+ oicontest->useinstead->divert)
+ ohshit(_("conflicting diversions involving '%.250s' or '%.250s'"),
+ oialtname->camefrom->name, oicontest->useinstead->name);
+
+ oialtname->camefrom->divert = oicontest;
+ oicontest->useinstead->divert = oialtname;
+
+ oicontest->next = diversions;
+ diversions = oicontest;
+ }
+
+ onerr_abort--;
+}
diff --git a/lib/dpkg/db-fsys-files.c b/lib/dpkg/db-fsys-files.c
new file mode 100644
index 0000000..099cad3
--- /dev/null
+++ b/lib/dpkg/db-fsys-files.c
@@ -0,0 +1,329 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * db-fsys-files.c - management of filesystem files database
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2000,2001 Wichert Akkerman <wakkerma@debian.org>
+ * Copyright © 2008-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#ifdef HAVE_LINUX_FIEMAP_H
+#include <linux/fiemap.h>
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+#include <sys/vfs.h>
+#endif
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/string.h>
+#include <dpkg/path.h>
+#include <dpkg/dir.h>
+#include <dpkg/fdio.h>
+#include <dpkg/pkg-array.h>
+#include <dpkg/pkg-files.h>
+#include <dpkg/progress.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+
+/*** Generic data structures and routines. ***/
+
+static bool allpackagesdone = false;
+
+void note_must_reread_files_inpackage(struct pkginfo *pkg) {
+ allpackagesdone = false;
+ pkg->files_list_valid = false;
+}
+
+enum pkg_filesdb_load_status {
+ PKG_FILESDB_LOAD_NONE = 0,
+ PKG_FILESDB_LOAD_INPROGRESS = 1,
+ PKG_FILESDB_LOAD_DONE = 2,
+};
+
+static enum pkg_filesdb_load_status saidread = PKG_FILESDB_LOAD_NONE;
+
+/**
+ * Load the list of files in this package into memory, or update the
+ * list if it is there but stale.
+ */
+void
+ensure_packagefiles_available(struct pkginfo *pkg)
+{
+ const char *filelistfile;
+ struct varbuf buf = VARBUF_INIT;
+ struct dpkg_error err = DPKG_ERROR_INIT;
+
+ if (pkg->files_list_valid)
+ return;
+
+ /* Throw away any stale data, if there was any. */
+ pkg_files_blank(pkg);
+
+ /* Packages which aren't installed don't have a files list. */
+ if (pkg->status == PKG_STAT_NOTINSTALLED) {
+ pkg->files_list_valid = true;
+ return;
+ }
+
+ filelistfile = pkg_infodb_get_file(pkg, &pkg->installed, LISTFILE);
+
+ onerr_abort++;
+
+ if (file_slurp(filelistfile, &buf, &err) < 0) {
+ if (err.syserrno != ENOENT)
+ dpkg_error_print(&err, _("loading files list file for package '%s'"),
+ pkg_name(pkg, pnaw_nonambig));
+
+ onerr_abort--;
+ if (pkg->status != PKG_STAT_CONFIGFILES &&
+ dpkg_version_is_informative(&pkg->configversion)) {
+ warning(_("files list file for package '%.250s' missing; assuming "
+ "package has no files currently installed"),
+ pkg_name(pkg, pnaw_nonambig));
+ }
+ pkg->files = NULL;
+ pkg->files_list_valid = true;
+ return;
+ }
+
+ if (buf.used) {
+ struct fsys_namenode_list **lendp;
+ char *loaded_list_end, *thisline;
+
+ loaded_list_end = buf.buf + buf.used;
+
+ lendp = &pkg->files;
+ thisline = buf.buf;
+ while (thisline < loaded_list_end) {
+ struct fsys_namenode *namenode;
+ char *nextline, *ptr;
+
+ ptr = memchr(thisline, '\n', loaded_list_end - thisline);
+ if (ptr == NULL)
+ ohshit(_("files list file for package '%.250s' is missing final newline"),
+ pkg_name(pkg, pnaw_nonambig));
+ /* Where to start next time around. */
+ nextline = ptr + 1;
+ /* Strip trailing ‘/’. */
+ if (ptr > thisline && ptr[-1] == '/') ptr--;
+ /* Add the file to the list. */
+ if (ptr == thisline)
+ ohshit(_("files list file for package '%.250s' contains empty filename"),
+ pkg_name(pkg, pnaw_nonambig));
+ *ptr = '\0';
+
+ namenode = fsys_hash_find_node(thisline, FHFF_NONE);
+ lendp = pkg_files_add_file(pkg, namenode, lendp);
+ thisline = nextline;
+ }
+ }
+
+ varbuf_destroy(&buf);
+
+ onerr_abort--;
+
+ pkg->files_list_valid = true;
+}
+
+#if defined(HAVE_LINUX_FIEMAP_H)
+static int
+pkg_sorter_by_files_list_phys_offs(const void *a, const void *b)
+{
+ const struct pkginfo *pa = *(const struct pkginfo **)a;
+ const struct pkginfo *pb = *(const struct pkginfo **)b;
+
+ /* We can't simply subtract, because the difference may be greater than
+ * INT_MAX. */
+ if (pa->files_list_phys_offs < pb->files_list_phys_offs)
+ return -1;
+ else if (pa->files_list_phys_offs > pb->files_list_phys_offs)
+ return 1;
+ else
+ return 0;
+}
+
+#define DPKG_FIEMAP_EXTENTS 1
+#define DPKG_FIEMAP_BUF_SIZE \
+ (sizeof(struct fiemap) + \
+ sizeof(struct fiemap_extent) * DPKG_FIEMAP_EXTENTS) / sizeof(__u64)
+
+static void
+pkg_files_optimize_load(struct pkg_array *array)
+{
+ __u64 fiemap_buf[DPKG_FIEMAP_BUF_SIZE];
+ struct fiemap *fm = (struct fiemap *)fiemap_buf;
+ struct statfs fs;
+ int i;
+
+ /* Get the filesystem block size. */
+ if (statfs(pkg_infodb_get_dir(), &fs) < 0)
+ return;
+
+ /* Sort packages by the physical location of their list files, so that
+ * scanning them later will minimize disk drive head movements. */
+ for (i = 0; i < array->n_pkgs; i++) {
+ struct pkginfo *pkg = array->pkgs[i];
+ const char *listfile;
+ int fd;
+
+ if (pkg->status == PKG_STAT_NOTINSTALLED ||
+ pkg->files_list_phys_offs != 0)
+ continue;
+
+ pkg->files_list_phys_offs = -1;
+
+ listfile = pkg_infodb_get_file(pkg, &pkg->installed, LISTFILE);
+
+ fd = open(listfile, O_RDONLY);
+ if (fd < 0)
+ continue;
+
+ memset(fiemap_buf, 0, sizeof(fiemap_buf));
+ fm->fm_start = 0;
+ fm->fm_length = fs.f_bsize;
+ fm->fm_flags = 0;
+ fm->fm_extent_count = DPKG_FIEMAP_EXTENTS;
+
+ if (ioctl(fd, FS_IOC_FIEMAP, (unsigned long)fm) == 0)
+ pkg->files_list_phys_offs = fm->fm_extents[0].fe_physical;
+
+ close(fd);
+ }
+
+ pkg_array_sort(array, pkg_sorter_by_files_list_phys_offs);
+}
+#elif defined(HAVE_POSIX_FADVISE)
+static void
+pkg_files_optimize_load(struct pkg_array *array)
+{
+ int i;
+
+ /* Ask the kernel to start preloading the list files, so as to get a
+ * boost when later we actually load them. */
+ for (i = 0; i < array->n_pkgs; i++) {
+ struct pkginfo *pkg = array->pkgs[i];
+ const char *listfile;
+ int fd;
+
+ listfile = pkg_infodb_get_file(pkg, &pkg->installed, LISTFILE);
+
+ fd = open(listfile, O_RDONLY | O_NONBLOCK);
+ if (fd != -1) {
+ posix_fadvise(fd, 0, 0, POSIX_FADV_WILLNEED);
+ close(fd);
+ }
+ }
+}
+#else
+static void
+pkg_files_optimize_load(struct pkg_array *array)
+{
+}
+#endif
+
+void ensure_allinstfiles_available(void) {
+ struct pkg_array array;
+ struct progress progress;
+ int i;
+
+ if (allpackagesdone) return;
+ if (saidread < PKG_FILESDB_LOAD_DONE) {
+ int max = pkg_hash_count_pkg();
+
+ saidread = PKG_FILESDB_LOAD_INPROGRESS;
+ progress_init(&progress, _("(Reading database ... "), max);
+ }
+
+ pkg_array_init_from_hash(&array);
+
+ pkg_files_optimize_load(&array);
+
+ for (i = 0; i < array.n_pkgs; i++) {
+ struct pkginfo *pkg = array.pkgs[i];
+
+ ensure_packagefiles_available(pkg);
+
+ if (saidread == PKG_FILESDB_LOAD_INPROGRESS)
+ progress_step(&progress);
+ }
+
+ pkg_array_destroy(&array);
+
+ allpackagesdone = true;
+
+ if (saidread == PKG_FILESDB_LOAD_INPROGRESS) {
+ progress_done(&progress);
+ printf(P_("%d file or directory currently installed.)\n",
+ "%d files and directories currently installed.)\n",
+ fsys_hash_entries()),
+ fsys_hash_entries());
+ saidread = PKG_FILESDB_LOAD_DONE;
+ }
+}
+
+void ensure_allinstfiles_available_quiet(void) {
+ saidread = PKG_FILESDB_LOAD_DONE;
+ ensure_allinstfiles_available();
+}
+
+/*
+ * If mask is nonzero, will not write any file whose fsys_namenode
+ * has any flag bits set in mask.
+ */
+void
+write_filelist_except(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ struct fsys_namenode_list *list, enum fsys_namenode_flags mask)
+{
+ struct atomic_file *file;
+ struct fsys_namenode_list *node;
+ const char *listfile;
+
+ listfile = pkg_infodb_get_file(pkg, pkgbin, LISTFILE);
+
+ file = atomic_file_new(listfile, 0);
+ atomic_file_open(file);
+
+ for (node = list; node; node = node->next) {
+ if (!(mask && (node->namenode->flags & mask))) {
+ fputs(node->namenode->name, file->fp);
+ putc('\n', file->fp);
+ }
+ }
+
+ atomic_file_sync(file);
+ atomic_file_close(file);
+ atomic_file_commit(file);
+ atomic_file_free(file);
+
+ dir_sync_path(pkg_infodb_get_dir());
+
+ note_must_reread_files_inpackage(pkg);
+}
diff --git a/lib/dpkg/db-fsys-override.c b/lib/dpkg/db-fsys-override.c
new file mode 100644
index 0000000..b74f6cb
--- /dev/null
+++ b/lib/dpkg/db-fsys-override.c
@@ -0,0 +1,268 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * db-fsys-override.c - management of filesystem stat overrides database
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2000, 2001 Wichert Akkerman <wakkerma@debian.org>
+ * Copyright © 2008-2012 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/fdio.h>
+#include <dpkg/debug.h>
+#include <dpkg/db-fsys.h>
+
+static char *statoverridename;
+
+uid_t
+statdb_parse_uid(const char *str)
+{
+ char *endptr;
+ uid_t uid;
+
+ if (str[0] == '#') {
+ long int value;
+
+ errno = 0;
+ value = strtol(str + 1, &endptr, 10);
+ if (str + 1 == endptr || *endptr || value < 0 || errno != 0)
+ ohshit(_("invalid statoverride uid %s"), str);
+ uid = (uid_t)value;
+ } else {
+ const struct passwd *pw = getpwnam(str);
+
+ if (pw == NULL)
+ uid = (uid_t)-1;
+ else
+ uid = pw->pw_uid;
+ }
+
+ return uid;
+}
+
+gid_t
+statdb_parse_gid(const char *str)
+{
+ char *endptr;
+ gid_t gid;
+
+ if (str[0] == '#') {
+ long int value;
+
+ errno = 0;
+ value = strtol(str + 1, &endptr, 10);
+ if (str + 1 == endptr || *endptr || value < 0 || errno != 0)
+ ohshit(_("invalid statoverride gid %s"), str);
+ gid = (gid_t)value;
+ } else {
+ const struct group *gr = getgrnam(str);
+
+ if (gr == NULL)
+ gid = (gid_t)-1;
+ else
+ gid = gr->gr_gid;
+ }
+
+ return gid;
+}
+
+mode_t
+statdb_parse_mode(const char *str)
+{
+ char *endptr;
+ long int mode;
+
+ mode = strtol(str, &endptr, 8);
+ if (str == endptr || *endptr || mode < 0 || mode > 07777)
+ ohshit(_("invalid statoverride mode %s"), str);
+
+ return (mode_t)mode;
+}
+
+void
+ensure_statoverrides(enum statdb_parse_flags flags)
+{
+ static struct stat sb_prev;
+ struct stat sb_next;
+ static FILE *file_prev;
+ FILE *file;
+ char *loaded_list, *loaded_list_end, *thisline, *nextline, *ptr;
+ struct file_stat *fso;
+ struct fsys_namenode *fnn;
+ struct fsys_hash_iter *iter;
+
+ if (statoverridename == NULL)
+ statoverridename = dpkg_db_get_path(STATOVERRIDEFILE);
+
+ onerr_abort++;
+
+ file = fopen(statoverridename, "r");
+ if (!file) {
+ if (errno != ENOENT)
+ ohshite(_("failed to open statoverride file"));
+ } else {
+ setcloexec(fileno(file), statoverridename);
+
+ if (fstat(fileno(file), &sb_next))
+ ohshite(_("failed to fstat statoverride file"));
+
+ /*
+ * We need to keep the database file open so that the
+ * filesystem cannot reuse the inode number (f.ex. during
+ * multiple dpkg-statoverride invocations in a maintainer
+ * script), otherwise the following check might turn true,
+ * and we would skip reloading a modified database.
+ */
+ if (file_prev &&
+ sb_prev.st_dev == sb_next.st_dev &&
+ sb_prev.st_ino == sb_next.st_ino) {
+ fclose(file);
+ onerr_abort--;
+ debug(dbg_general, "%s: same, skipping", __func__);
+ return;
+ }
+ sb_prev = sb_next;
+ }
+ if (file_prev)
+ fclose(file_prev);
+ file_prev = file;
+
+ /* Reset statoverride information. */
+ iter = fsys_hash_iter_new();
+ while ((fnn = fsys_hash_iter_next(iter)))
+ fnn->statoverride = NULL;
+ fsys_hash_iter_free(iter);
+
+ if (!file) {
+ onerr_abort--;
+ debug(dbg_general, "%s: none, resetting", __func__);
+ return;
+ }
+ debug(dbg_general, "%s: new, (re)loading", __func__);
+
+ /* If the statoverride list is empty we don't need to bother
+ * reading it. */
+ if (!sb_next.st_size) {
+ onerr_abort--;
+ return;
+ }
+
+ loaded_list = m_malloc(sb_next.st_size);
+ loaded_list_end = loaded_list + sb_next.st_size;
+
+ if (fd_read(fileno(file), loaded_list, sb_next.st_size) < 0)
+ ohshite(_("reading statoverride file '%.250s'"), statoverridename);
+
+ thisline = loaded_list;
+ while (thisline < loaded_list_end) {
+ fso = nfmalloc(sizeof(*fso));
+
+ ptr = memchr(thisline, '\n', loaded_list_end - thisline);
+ if (ptr == NULL)
+ ohshit(_("statoverride file is missing final newline"));
+ /* Where to start next time around. */
+ nextline = ptr + 1;
+ if (ptr == thisline)
+ ohshit(_("statoverride file contains empty line"));
+ *ptr = '\0';
+
+ /* Extract the uid. */
+ ptr = memchr(thisline, ' ', nextline - thisline);
+ if (ptr == NULL)
+ ohshit(_("syntax error in statoverride file"));
+ *ptr = '\0';
+
+ fso->uid = statdb_parse_uid(thisline);
+ if (fso->uid == (uid_t)-1)
+ fso->uname = nfstrsave(thisline);
+ else
+ fso->uname = NULL;
+
+ if (fso->uid == (uid_t)-1 && !(flags & STATDB_PARSE_LAX))
+ ohshit(_("unknown system user '%s' in statoverride file; the system user got removed\n"
+ "before the override, which is most probably a packaging bug, to recover you\n"
+ "can remove the override manually with %s"), thisline, DPKGSTAT);
+
+ /* Move to the next bit */
+ thisline = ptr + 1;
+ if (thisline >= loaded_list_end)
+ ohshit(_("unexpected end of line in statoverride file"));
+
+ /* Extract the gid */
+ ptr = memchr(thisline, ' ', nextline - thisline);
+ if (ptr == NULL)
+ ohshit(_("syntax error in statoverride file"));
+ *ptr = '\0';
+
+ fso->gid = statdb_parse_gid(thisline);
+ if (fso->gid == (gid_t)-1)
+ fso->gname = nfstrsave(thisline);
+ else
+ fso->gname = NULL;
+
+ if (fso->gid == (gid_t)-1 && !(flags & STATDB_PARSE_LAX))
+ ohshit(_("unknown system group '%s' in statoverride file; the system group got removed\n"
+ "before the override, which is most probably a packaging bug, to recover you\n"
+ "can remove the override manually with %s"), thisline, DPKGSTAT);
+
+ /* Move to the next bit */
+ thisline = ptr + 1;
+ if (thisline >= loaded_list_end)
+ ohshit(_("unexpected end of line in statoverride file"));
+
+ /* Extract the mode */
+ ptr = memchr(thisline, ' ', nextline - thisline);
+ if (ptr == NULL)
+ ohshit(_("syntax error in statoverride file"));
+ *ptr = '\0';
+
+ fso->mode = statdb_parse_mode(thisline);
+
+ /* Move to the next bit */
+ thisline = ptr + 1;
+ if (thisline >= loaded_list_end)
+ ohshit(_("unexpected end of line in statoverride file"));
+
+ fnn = fsys_hash_find_node(thisline, FHFF_NONE);
+ if (fnn->statoverride)
+ ohshit(_("multiple statoverrides present for file '%.250s'"),
+ thisline);
+ fnn->statoverride = fso;
+
+ /* Moving on... */
+ thisline = nextline;
+ }
+
+ free(loaded_list);
+
+ onerr_abort--;
+}
diff --git a/lib/dpkg/db-fsys.h b/lib/dpkg/db-fsys.h
new file mode 100644
index 0000000..a95b29d
--- /dev/null
+++ b/lib/dpkg/db-fsys.h
@@ -0,0 +1,80 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * db-fsys.h - management of database of files installed on system
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_DB_FSYS_H
+#define LIBDPKG_DB_FSYS_H
+
+#include <dpkg/file.h>
+#include <dpkg/fsys.h>
+
+DPKG_BEGIN_DECLS
+
+/*
+ * Data structure here is as follows:
+ *
+ * For each package we have a ‘struct fsys_namenode_list *’, the head of a list of
+ * files in that package. They are in ‘forwards’ order. Each entry has a
+ * pointer to the ‘struct fsys_namenode’.
+ *
+ * The struct fsys_namenodes are in a hash table, indexed by name.
+ * (This hash table is not visible to callers.)
+ *
+ * Each fsys_namenode has a (possibly empty) list of ‘struct filepackage’,
+ * giving a list of the packages listing that filename.
+ *
+ * When we read files contained info about a particular package we set the
+ * ‘files’ member of the clientdata struct to the appropriate thing. When
+ * not yet set the files pointer is made to point to ‘fileslist_uninited’
+ * (this is available only internally, within filesdb.c - the published
+ * interface is ensure_*_available).
+ */
+
+struct pkginfo;
+struct pkgbin;
+
+void ensure_diversions(void);
+
+enum statdb_parse_flags {
+ STATDB_PARSE_NORMAL = 0,
+ STATDB_PARSE_LAX = 1,
+};
+
+uid_t statdb_parse_uid(const char *str);
+gid_t statdb_parse_gid(const char *str);
+mode_t statdb_parse_mode(const char *str);
+void ensure_statoverrides(enum statdb_parse_flags flags);
+
+#define LISTFILE "list"
+#define HASHFILE "md5sums"
+
+void ensure_packagefiles_available(struct pkginfo *pkg);
+void ensure_allinstfiles_available(void);
+void ensure_allinstfiles_available_quiet(void);
+void note_must_reread_files_inpackage(struct pkginfo *pkg);
+void parse_filehash(struct pkginfo *pkg, struct pkgbin *pkgbin);
+void write_filelist_except(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ struct fsys_namenode_list *list, enum fsys_namenode_flags mask);
+void write_filehash_except(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ struct fsys_namenode_list *list, enum fsys_namenode_flags mask);
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_DB_FSYS_H */
diff --git a/lib/dpkg/dbdir.c b/lib/dpkg/dbdir.c
new file mode 100644
index 0000000..e86affa
--- /dev/null
+++ b/lib/dpkg/dbdir.c
@@ -0,0 +1,117 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * dbdir.c - on-disk database directory functions
+ *
+ * Copyright © 2011 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+
+#include <stdlib.h>
+
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/fsys.h>
+
+static char *db_dir;
+
+/**
+ * Allocate the default on-disk database directory.
+ *
+ * The directory defaults to the value from environment variable
+ * DPKG_ADMINDIR, and if not set the built-in default ADMINDIR.
+ *
+ * @return The database directory.
+ */
+static char *
+dpkg_db_new_dir(void)
+{
+ const char *env;
+ char *dir;
+
+ /* Make sure the filesystem root directory is initialized. */
+ dpkg_fsys_get_dir();
+
+ env = getenv("DPKG_ADMINDIR");
+ if (env)
+ dir = m_strdup(env);
+ else
+ dir = dpkg_fsys_get_path(ADMINDIR);
+
+ return dir;
+}
+
+/**
+ * Set current on-disk database directory.
+ *
+ * This function can be used to set the directory to a new value, or to
+ * reset it to a default value if dir is NULL.
+ *
+ * @param dir The new database directory, or NULL to set to default.
+ *
+ * @return The new database directory.
+ */
+const char *
+dpkg_db_set_dir(const char *dir)
+{
+ char *dir_new;
+
+ if (dir == NULL)
+ dir_new = dpkg_db_new_dir();
+ else
+ dir_new = m_strdup(dir);
+
+ free(db_dir);
+ db_dir = dir_new;
+
+ return db_dir;
+}
+
+/**
+ * Get current on-disk database directory.
+ *
+ * This function will take care of initializing the directory if it has not
+ * been initialized before.
+ *
+ * @return The current database directory.
+ */
+const char *
+dpkg_db_get_dir(void)
+{
+ if (db_dir == NULL)
+ db_dir = dpkg_db_new_dir();
+
+ return db_dir;
+}
+
+/**
+ * Get a pathname to the current on-disk database directory.
+ *
+ * This function returns an allocated string, which should be freed with
+ * free(2).
+ *
+ * @param pathpart The pathpart to append to the new pathname.
+ *
+ * @return The newly allocated pathname.
+ */
+char *
+dpkg_db_get_path(const char *pathpart)
+{
+ return str_fmt("%s/%s", dpkg_db_get_dir(), pathpart);
+}
diff --git a/lib/dpkg/dbmodify.c b/lib/dpkg/dbmodify.c
new file mode 100644
index 0000000..f38bece
--- /dev/null
+++ b/lib/dpkg/dbmodify.c
@@ -0,0 +1,535 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * dbmodify.c - routines for managing dpkg database updates
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2001 Wichert Akkerman <wichert@debian.org>
+ * Copyright © 2006-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/c-ctype.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/file.h>
+#include <dpkg/dir.h>
+#include <dpkg/triglib.h>
+
+static bool db_initialized;
+
+static enum modstatdb_rw cstatus=-1, cflags=0;
+static char *lockfile;
+static char *frontendlockfile;
+static char *statusfile, *availablefile;
+static char *importanttmpfile=NULL;
+static FILE *importanttmp;
+static int nextupdate;
+static char *updatesdir;
+static int updateslength;
+static struct varbuf updatefn;
+static struct varbuf_state updatefn_state;
+static struct varbuf uvb;
+
+static int ulist_select(const struct dirent *de) {
+ const char *p;
+ int l;
+ for (p= de->d_name, l=0; *p; p++, l++)
+ if (!c_isdigit(*p))
+ return 0;
+ if (l > IMPORTANTMAXLEN)
+ ohshit(_("updates directory contains file '%.250s' whose name is too long "
+ "(length=%d, max=%d)"), de->d_name, l, IMPORTANTMAXLEN);
+ if (updateslength == -1) updateslength= l;
+ else if (l != updateslength)
+ ohshit(_("updates directory contains files with different length names "
+ "(both %d and %d)"), l, updateslength);
+ return 1;
+}
+
+static void cleanupdates(void) {
+ struct dirent **cdlist;
+ int cdn;
+
+ parsedb(statusfile, pdb_parse_status, NULL);
+
+ updateslength= -1;
+ cdn = scandir(updatesdir, &cdlist, &ulist_select, alphasort);
+ if (cdn == -1) {
+ if (errno == ENOENT) {
+ if (cstatus >= msdbrw_write &&
+ dir_make_path(updatesdir, 0755) < 0)
+ ohshite(_("cannot create the dpkg updates directory %s"),
+ updatesdir);
+ return;
+ }
+ ohshite(_("cannot scan updates directory '%.255s'"), updatesdir);
+ }
+
+ if (cdn) {
+ int i;
+
+ for (i=0; i<cdn; i++) {
+ varbuf_rollback(&updatefn_state);
+ varbuf_add_str(&updatefn, cdlist[i]->d_name);
+ varbuf_end_str(&updatefn);
+ parsedb(updatefn.buf, pdb_parse_update, NULL);
+ }
+
+ if (cstatus >= msdbrw_write) {
+ writedb(statusfile, wdb_must_sync);
+
+ for (i=0; i<cdn; i++) {
+ varbuf_rollback(&updatefn_state);
+ varbuf_add_str(&updatefn, cdlist[i]->d_name);
+ varbuf_end_str(&updatefn);
+ if (unlink(updatefn.buf))
+ ohshite(_("failed to remove incorporated update file %.255s"),
+ updatefn.buf);
+ }
+
+ dir_sync_path(updatesdir);
+ }
+
+ for (i = 0; i < cdn; i++)
+ free(cdlist[i]);
+ }
+ free(cdlist);
+
+ nextupdate= 0;
+}
+
+static void createimptmp(void) {
+ int i;
+
+ onerr_abort++;
+
+ importanttmp= fopen(importanttmpfile,"w");
+ if (!importanttmp)
+ ohshite(_("unable to create '%.255s'"), importanttmpfile);
+ setcloexec(fileno(importanttmp),importanttmpfile);
+ for (i=0; i<512; i++) fputs("#padding\n",importanttmp);
+ if (ferror(importanttmp))
+ ohshite(_("unable to fill %.250s with padding"),importanttmpfile);
+ if (fflush(importanttmp))
+ ohshite(_("unable to flush %.250s after padding"), importanttmpfile);
+ if (fseek(importanttmp,0,SEEK_SET))
+ ohshite(_("unable to seek to start of %.250s after padding"),
+ importanttmpfile);
+
+ onerr_abort--;
+}
+
+static const struct fni {
+ const char *suffix;
+ char **store;
+} fnis[] = {
+ {
+ .suffix = LOCKFILE,
+ .store = &lockfile,
+ }, {
+ .suffix = FRONTENDLOCKFILE,
+ .store = &frontendlockfile,
+ }, {
+ .suffix = STATUSFILE,
+ .store = &statusfile,
+ }, {
+ .suffix = AVAILFILE,
+ .store = &availablefile,
+ }, {
+ .suffix = UPDATESDIR,
+ .store = &updatesdir,
+ }, {
+ .suffix = UPDATESDIR "/" IMPORTANTTMP,
+ .store = &importanttmpfile,
+ }, {
+ .suffix = NULL,
+ .store = NULL,
+ }
+};
+
+void
+modstatdb_init(void)
+{
+ const struct fni *fnip;
+
+ if (db_initialized)
+ return;
+
+ for (fnip = fnis; fnip->suffix; fnip++) {
+ free(*fnip->store);
+ *fnip->store = dpkg_db_get_path(fnip->suffix);
+ }
+
+ varbuf_init(&updatefn, strlen(updatesdir) + 1 + IMPORTANTMAXLEN);
+ varbuf_add_dir(&updatefn, updatesdir);
+ varbuf_end_str(&updatefn);
+ varbuf_snapshot(&updatefn, &updatefn_state);
+
+ db_initialized = true;
+}
+
+void
+modstatdb_done(void)
+{
+ const struct fni *fnip;
+
+ if (!db_initialized)
+ return;
+
+ for (fnip = fnis; fnip->suffix; fnip++) {
+ free(*fnip->store);
+ *fnip->store = NULL;
+ }
+ varbuf_destroy(&updatefn);
+
+ db_initialized = false;
+}
+
+static int dblockfd = -1;
+static int frontendlockfd = -1;
+
+bool
+modstatdb_is_locked(void)
+{
+ int lockfd;
+ bool locked;
+
+ if (dblockfd == -1) {
+ lockfd = open(lockfile, O_RDONLY);
+ if (lockfd == -1) {
+ if (errno == ENOENT)
+ return false;
+ ohshite(_("unable to check lock file for dpkg database directory %s"),
+ dpkg_db_get_dir());
+ }
+ } else {
+ lockfd = dblockfd;
+ }
+
+ locked = file_is_locked(lockfd, lockfile);
+
+ /* We only close the file if there was no lock open, otherwise we would
+ * release the existing lock on close. */
+ if (dblockfd == -1)
+ close(lockfd);
+
+ return locked;
+}
+
+bool
+modstatdb_can_lock(void)
+{
+ if (dblockfd >= 0)
+ return true;
+
+ if (getenv("DPKG_FRONTEND_LOCKED") == NULL) {
+ frontendlockfd = open(frontendlockfile, O_RDWR | O_CREAT | O_TRUNC, 0660);
+ if (frontendlockfd == -1) {
+ if (errno == EACCES || errno == EPERM)
+ return false;
+ else
+ ohshite(_("unable to open/create dpkg frontend lock for directory %s"),
+ dpkg_db_get_dir());
+ }
+ } else {
+ frontendlockfd = -1;
+ }
+
+ dblockfd = open(lockfile, O_RDWR | O_CREAT | O_TRUNC, 0660);
+ if (dblockfd == -1) {
+ if (errno == EACCES || errno == EPERM)
+ return false;
+ else
+ ohshite(_("unable to open/create dpkg database lock file for directory %s"),
+ dpkg_db_get_dir());
+ }
+
+ return true;
+}
+
+void
+modstatdb_lock(void)
+{
+ if (!modstatdb_can_lock())
+ ohshit(_("you do not have permission to lock the dpkg database directory %s"),
+ dpkg_db_get_dir());
+
+ if (frontendlockfd != -1)
+ file_lock(&frontendlockfd, FILE_LOCK_NOWAIT, frontendlockfile,
+ _("dpkg frontend lock"));
+ file_lock(&dblockfd, FILE_LOCK_NOWAIT, lockfile,
+ _("dpkg database lock"));
+}
+
+void
+modstatdb_unlock(void)
+{
+ /* Unlock. */
+ pop_cleanup(ehflag_normaltidy);
+ if (frontendlockfd != -1)
+ pop_cleanup(ehflag_normaltidy);
+
+ dblockfd = -1;
+ frontendlockfd = -1;
+}
+
+enum modstatdb_rw
+modstatdb_open(enum modstatdb_rw readwritereq)
+{
+ bool db_can_access = false;
+
+ modstatdb_init();
+
+ cflags = readwritereq & msdbrw_available_mask;
+ readwritereq &= ~msdbrw_available_mask;
+
+ switch (readwritereq) {
+ case msdbrw_needsuperuser:
+ case msdbrw_needsuperuserlockonly:
+ if (getuid() || geteuid())
+ ohshit(_("requested operation requires superuser privilege"));
+ /* Fall through. */
+ case msdbrw_write: case msdbrw_writeifposs:
+ db_can_access = access(dpkg_db_get_dir(), W_OK) == 0;
+ if (!db_can_access && errno == ENOENT) {
+ if (dir_make_path(dpkg_db_get_dir(), 0755) == 0)
+ db_can_access = true;
+ else if (readwritereq >= msdbrw_write)
+ ohshite(_("cannot create the dpkg database directory %s"),
+ dpkg_db_get_dir());
+ else if (errno == EROFS)
+ /* If we cannot create the directory on read-only modes on read-only
+ * filesystems, make it look like an access error to be skipped. */
+ errno = EACCES;
+ }
+
+ if (!db_can_access) {
+ if (errno != EACCES)
+ ohshite(_("unable to access the dpkg database directory %s"),
+ dpkg_db_get_dir());
+ else if (readwritereq >= msdbrw_write)
+ ohshit(_("required read/write access to the dpkg database directory %s"),
+ dpkg_db_get_dir());
+ cstatus= msdbrw_readonly;
+ } else {
+ modstatdb_lock();
+ cstatus= (readwritereq == msdbrw_needsuperuserlockonly ?
+ msdbrw_needsuperuserlockonly :
+ msdbrw_write);
+ }
+ break;
+ case msdbrw_readonly:
+ cstatus= msdbrw_readonly; break;
+ default:
+ internerr("unknown modstatdb_rw '%d'", readwritereq);
+ }
+
+ dpkg_arch_load_list();
+
+ if (cstatus != msdbrw_needsuperuserlockonly) {
+ cleanupdates();
+ if (cflags >= msdbrw_available_readonly)
+ parsedb(availablefile, pdb_parse_available, NULL);
+ }
+
+ if (cstatus >= msdbrw_write) {
+ createimptmp();
+ varbuf_init(&uvb, 10240);
+ }
+
+ trig_fixup_awaiters(cstatus);
+ trig_incorporate(cstatus);
+
+ return cstatus;
+}
+
+enum modstatdb_rw
+modstatdb_get_status(void)
+{
+ return cstatus;
+}
+
+void modstatdb_checkpoint(void) {
+ int i;
+
+ if (cstatus < msdbrw_write)
+ internerr("modstatdb status '%d' is not writable", cstatus);
+
+ writedb(statusfile, wdb_must_sync);
+
+ for (i=0; i<nextupdate; i++) {
+ varbuf_rollback(&updatefn_state);
+ varbuf_printf(&updatefn, IMPORTANTFMT, i);
+
+ /* Have we made a real mess? */
+ if (varbuf_rollback_len(&updatefn_state) > IMPORTANTMAXLEN)
+ internerr("modstatdb update entry name '%s' longer than %d",
+ varbuf_rollback_start(&updatefn_state), IMPORTANTMAXLEN);
+
+ if (unlink(updatefn.buf))
+ ohshite(_("failed to remove my own update file %.255s"), updatefn.buf);
+ }
+
+ dir_sync_path(updatesdir);
+
+ nextupdate= 0;
+}
+
+void modstatdb_shutdown(void) {
+ if (cflags >= msdbrw_available_write)
+ writedb(availablefile, wdb_dump_available);
+
+ switch (cstatus) {
+ case msdbrw_write:
+ modstatdb_checkpoint();
+ /* Tidy up a bit, but don't worry too much about failure. */
+ fclose(importanttmp);
+ (void)unlink(importanttmpfile);
+ varbuf_destroy(&uvb);
+ /* Fall through. */
+ case msdbrw_needsuperuserlockonly:
+ modstatdb_unlock();
+ default:
+ break;
+ }
+
+ pkg_hash_reset();
+
+ modstatdb_done();
+}
+
+static void
+modstatdb_note_core(struct pkginfo *pkg)
+{
+ if (cstatus < msdbrw_write)
+ internerr("modstatdb status '%d' is not writable", cstatus);
+
+ varbuf_reset(&uvb);
+ varbuf_stanza(&uvb, pkg, &pkg->installed);
+
+ if (fwrite(uvb.buf, 1, uvb.used, importanttmp) != uvb.used)
+ ohshite(_("unable to write updated status of '%.250s'"),
+ pkg_name(pkg, pnaw_nonambig));
+ if (fflush(importanttmp))
+ ohshite(_("unable to flush updated status of '%.250s'"),
+ pkg_name(pkg, pnaw_nonambig));
+ if (ftruncate(fileno(importanttmp), uvb.used))
+ ohshite(_("unable to truncate for updated status of '%.250s'"),
+ pkg_name(pkg, pnaw_nonambig));
+ if (fsync(fileno(importanttmp)))
+ ohshite(_("unable to fsync updated status of '%.250s'"),
+ pkg_name(pkg, pnaw_nonambig));
+ if (fclose(importanttmp))
+ ohshite(_("unable to close updated status of '%.250s'"),
+ pkg_name(pkg, pnaw_nonambig));
+ varbuf_rollback(&updatefn_state);
+ varbuf_printf(&updatefn, IMPORTANTFMT, nextupdate);
+ if (rename(importanttmpfile, updatefn.buf))
+ ohshite(_("unable to install updated status of '%.250s'"),
+ pkg_name(pkg, pnaw_nonambig));
+
+ dir_sync_path(updatesdir);
+
+ /* Have we made a real mess? */
+ if (varbuf_rollback_len(&updatefn_state) > IMPORTANTMAXLEN)
+ internerr("modstatdb update entry name '%s' longer than %d",
+ varbuf_rollback_start(&updatefn_state), IMPORTANTMAXLEN);
+
+ nextupdate++;
+
+ if (nextupdate > MAXUPDATES) {
+ modstatdb_checkpoint();
+ nextupdate = 0;
+ }
+
+ createimptmp();
+}
+
+/*
+ * Note: If anyone wants to set some triggers-pending, they must also
+ * set status appropriately, or we will undo it. That is, it is legal
+ * to call this when pkg->status and pkg->trigpend_head disagree and
+ * in that case pkg->status takes precedence and pkg->trigpend_head
+ * will be adjusted.
+ */
+void modstatdb_note(struct pkginfo *pkg) {
+ struct trigaw *ta;
+
+ onerr_abort++;
+
+ /* Clear pending triggers here so that only code that sets the status
+ * to interesting (for triggers) values has to care about triggers. */
+ if (pkg->status != PKG_STAT_TRIGGERSPENDING &&
+ pkg->status != PKG_STAT_TRIGGERSAWAITED)
+ pkg->trigpend_head = NULL;
+
+ if (pkg->status <= PKG_STAT_CONFIGFILES) {
+ for (ta = pkg->trigaw.head; ta; ta = ta->sameaw.next)
+ ta->aw = NULL;
+ pkg->trigaw.head = pkg->trigaw.tail = NULL;
+ }
+
+ if (pkg->status_dirty) {
+ log_message("status %s %s %s", pkg_status_name(pkg),
+ pkg_name(pkg, pnaw_always),
+ versiondescribe_c(&pkg->installed.version, vdew_nonambig));
+ statusfd_send("status: %s: %s", pkg_name(pkg, pnaw_nonambig),
+ pkg_status_name(pkg));
+
+ pkg->status_dirty = false;
+ }
+
+ if (cstatus >= msdbrw_write)
+ modstatdb_note_core(pkg);
+
+ if (!pkg->trigpend_head && pkg->othertrigaw_head) {
+ /* Automatically remove us from other packages' Triggers-Awaited.
+ * We do this last because we want to maximize our chances of
+ * successfully recording the status of the package we were
+ * pointed at by our caller, although there is some risk of
+ * leaving us in a slightly odd situation which is cleared up
+ * by the trigger handling logic in deppossi_ok_found. */
+ trig_clear_awaiters(pkg);
+ }
+
+ onerr_abort--;
+}
+
+void
+modstatdb_note_ifwrite(struct pkginfo *pkg)
+{
+ if (cstatus >= msdbrw_write)
+ modstatdb_note(pkg);
+}
+
diff --git a/lib/dpkg/deb-version.c b/lib/dpkg/deb-version.c
new file mode 100644
index 0000000..cee5ddd
--- /dev/null
+++ b/lib/dpkg/deb-version.c
@@ -0,0 +1,87 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * deb-version.c - deb format version handling routines
+ *
+ * Copyright © 2012-2013 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/c-ctype.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/deb-version.h>
+
+/**
+ * Parse a .deb format version.
+ *
+ * It takes a string and parses a .deb archive format version in the form
+ * of "X.Y", without any leading whitespace, and ending in either a newline
+ * or a NUL. If there is any syntax error a descriptive error string is
+ * returned.
+ *
+ * @param version The version to return.
+ * @param str The string to parse.
+ *
+ * @return An error string, or NULL if there was no error.
+ */
+const char *
+deb_version_parse(struct deb_version *version, const char *str)
+{
+ const char *str_minor, *end;
+ unsigned int major = 0;
+ unsigned int minor = 0;
+ unsigned int divlimit = INT_MAX / 10;
+ int modlimit = INT_MAX % 10;
+
+ for (end = str; *end && c_isdigit(*end); end++) {
+ int ord = *end - '0';
+
+ if (major > divlimit || (major == divlimit && ord > modlimit))
+ return _("format version with too big major component");
+
+ major = major * 10 + ord;
+ }
+
+ if (end == str)
+ return _("format version with empty major component");
+ if (*end != '.')
+ return _("format version has no dot");
+
+ for (end = str_minor = end + 1; *end && c_isdigit(*end); end++) {
+ int ord = *end - '0';
+
+ if (minor > divlimit || (minor == divlimit && ord > modlimit))
+ return _("format version with too big minor component");
+
+ minor = minor * 10 + ord;
+ }
+
+ if (end == str_minor)
+ return _("format version with empty minor component");
+ if (*end != '\n' && *end != '\0')
+ return _("format version followed by junk");
+
+ version->major = major;
+ version->minor = minor;
+
+ return NULL;
+}
diff --git a/lib/dpkg/deb-version.h b/lib/dpkg/deb-version.h
new file mode 100644
index 0000000..620d23d
--- /dev/null
+++ b/lib/dpkg/deb-version.h
@@ -0,0 +1,60 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * deb-version.h - deb format version handling routines
+ *
+ * Copyright © 2012-2013 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_DEB_VERSION_H
+#define LIBDPKG_DEB_VERSION_H
+
+#include <dpkg/macros.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup deb-version .deb format version handling
+ * @ingroup dpkg-public
+ * @{
+ */
+
+/**
+ * Data structure representing a .deb format version.
+ */
+struct deb_version {
+ int major;
+ int minor;
+};
+
+/**
+ * Constant initializer for a deb_version.
+ */
+#define DEB_VERSION(X, Y) \
+ { .major = (X), .minor = (Y) }
+
+/**
+ * Compound literal for a deb_version.
+ */
+#define DEB_VERSION_OBJECT(X, Y) \
+ (struct deb_version)DEB_VERSION(X, Y)
+
+const char *deb_version_parse(struct deb_version *version, const char *str);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_DEB_VERSION_H */
diff --git a/lib/dpkg/debug.c b/lib/dpkg/debug.c
new file mode 100644
index 0000000..115d455
--- /dev/null
+++ b/lib/dpkg/debug.c
@@ -0,0 +1,133 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * debug.c - debugging support
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2011 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/dpkg.h>
+#include <dpkg/i18n.h>
+#include <dpkg/report.h>
+#include <dpkg/debug.h>
+
+static int debug_mask = 0;
+static FILE *debug_output = NULL;
+
+/**
+ * Set the debugging output file.
+ *
+ * Marks the file descriptor as close-on-exec.
+ */
+void
+debug_set_output(FILE *output, const char *filename)
+{
+ setcloexec(fileno(output), filename);
+ dpkg_set_report_buffer(output);
+ debug_output = output;
+}
+
+/**
+ * Set the debugging mask.
+ *
+ * The mask determines what debugging flags are going to take effect at
+ * run-time. The output will be set to stderr if it has not been set before.
+ */
+void
+debug_set_mask(int mask)
+{
+ debug_mask = mask;
+ if (!debug_output)
+ debug_output = stderr;
+}
+
+/**
+ * Parse the debugging mask.
+ *
+ * The mask is parsed from the specified string and sets the global debugging
+ * mask. If there is any error while parsing a negative number is returned.
+ */
+int
+debug_parse_mask(const char *str)
+{
+ char *endp;
+ long mask;
+
+ errno = 0;
+ mask = strtol(str, &endp, 8);
+ if (str == endp || *endp || mask < 0 || errno == ERANGE)
+ return -1;
+
+ debug_set_mask(mask);
+
+ return mask;
+}
+
+/**
+ * Check if a debugging flag is currently set on the debugging mask.
+ */
+bool
+debug_has_flag(int flag)
+{
+ return debug_mask & flag;
+}
+
+/**
+ * Output a debugging message.
+ *
+ * The message will be printed to the previously specified output if the
+ * specified flag is present in the current debugging mask.
+ */
+void
+debug(int flag, const char *fmt, ...)
+{
+ va_list args;
+
+ if (!debug_has_flag(flag))
+ return;
+
+ fprintf(debug_output, "D0%05o: ", flag);
+ va_start(args, fmt);
+ vfprintf(debug_output, fmt, args);
+ va_end(args);
+ putc('\n', debug_output);
+}
+
+/**
+ * Initialize the debugging support.
+ */
+void
+dpkg_debug_init(void)
+{
+ const char envvar[] = "DPKG_DEBUG";
+ const char *env;
+
+ env = getenv(envvar);
+ if (env == NULL)
+ return;
+
+ if (debug_parse_mask(env) < 0)
+ warning(_("cannot parse debug mask from environment variable %s"),
+ envvar);
+}
diff --git a/lib/dpkg/debug.h b/lib/dpkg/debug.h
new file mode 100644
index 0000000..be4efac
--- /dev/null
+++ b/lib/dpkg/debug.h
@@ -0,0 +1,68 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * debug.h - debugging support
+ *
+ * Copyright © 2011 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_DEBUG_H
+#define LIBDPKG_DEBUG_H
+
+#include <dpkg/macros.h>
+
+#include <stdbool.h>
+#include <stdio.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup debug Debugging
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+/*
+ * XXX: We do not use DPKG_BIT() here, because the octal values are part
+ * of the current output interface.
+ */
+enum DPKG_ATTR_ENUM_FLAGS debugflags {
+ dbg_general = 01,
+ dbg_scripts = 02,
+ dbg_eachfile = 010,
+ dbg_eachfiledetail = 0100,
+ dbg_conff = 020,
+ dbg_conffdetail = 0200,
+ dbg_depcon = 040,
+ dbg_depcondetail = 0400,
+ dbg_veryverbose = 01000,
+ dbg_stupidlyverbose = 02000,
+ dbg_triggers = 010000,
+ dbg_triggersdetail = 020000,
+ dbg_triggersstupid = 040000,
+};
+
+void dpkg_debug_init(void);
+void debug_set_output(FILE *output, const char *filename);
+void debug_set_mask(int mask);
+int debug_parse_mask(const char *str);
+bool debug_has_flag(int flag);
+void debug(int flag, const char *fmt, ...) DPKG_ATTR_PRINTF(2);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_DEBUG_H */
diff --git a/lib/dpkg/depcon.c b/lib/dpkg/depcon.c
new file mode 100644
index 0000000..488cd6b
--- /dev/null
+++ b/lib/dpkg/depcon.c
@@ -0,0 +1,125 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * depcon.c - dependency and conflict checking
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008-2014 Guillem Jover <guillem@debian.org>
+ * Copyright © 2009 Canonical Ltd.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/arch.h>
+
+bool
+versionsatisfied(struct pkgbin *it, struct deppossi *against)
+{
+ return dpkg_version_relate(&it->version,
+ against->verrel,
+ &against->version);
+}
+
+/**
+ * Check if the architecture qualifier in the dependency is satisfied.
+ *
+ * The rules are supposed to be:
+ * - unqualified Depends/Pre-Depends/Recommends/Suggests are only
+ * satisfied by a package of a different architecture if the target
+ * package is Multi-Arch: foreign.
+ * - Depends/Pre-Depends/Recommends/Suggests on pkg:any are satisfied by
+ * a package of a different architecture if the target package is
+ * Multi-Arch: allowed.
+ * - all other Depends/Pre-Depends/Recommends/Suggests are only
+ * satisfied by packages of the same architecture.
+ * - Architecture: all packages are treated the same as packages of the
+ * native architecture.
+ * - Conflicts/Replaces/Breaks are assumed to apply to packages of any arch.
+ */
+bool
+deparchsatisfied(struct pkgbin *it, const struct dpkg_arch *it_arch,
+ struct deppossi *against)
+{
+ const struct dpkg_arch *dep_arch, *pkg_arch;
+
+ if (against->arch_is_implicit &&
+ it->multiarch == PKG_MULTIARCH_FOREIGN)
+ return true;
+
+ dep_arch = against->arch;
+ if (dep_arch->type == DPKG_ARCH_WILDCARD &&
+ (it->multiarch == PKG_MULTIARCH_ALLOWED ||
+ against->up->type == dep_conflicts ||
+ against->up->type == dep_replaces ||
+ against->up->type == dep_breaks))
+ return true;
+
+ pkg_arch = it_arch;
+ if (dep_arch->type == DPKG_ARCH_NONE || dep_arch->type == DPKG_ARCH_ALL)
+ dep_arch = dpkg_arch_get(DPKG_ARCH_NATIVE);
+ if (pkg_arch->type == DPKG_ARCH_NONE || pkg_arch->type == DPKG_ARCH_ALL)
+ pkg_arch = dpkg_arch_get(DPKG_ARCH_NATIVE);
+
+ return (dep_arch == pkg_arch);
+}
+
+bool
+archsatisfied(struct pkgbin *it, struct deppossi *against)
+{
+ return deparchsatisfied(it, it->arch, against);
+}
+
+/**
+ * Check if the dependency is satisfied by a virtual package.
+ *
+ * For versioned depends, we only check providers with #DPKG_RELATION_EQ. It
+ * does not make sense to check ones without a version since we have nothing
+ * to verify against. Also, it is way too complex to allow anything but an
+ * equal in a provided version. A few examples below to deter you from trying:
+ *
+ * - pkg1 depends on virt (>= 0.6), pkg2 provides virt (<= 1.0).
+ * Should pass (easy enough).
+ *
+ * - pkg1 depends on virt (>= 0.7) and (<= 1.1), pkg2 provides virt (>= 1.2).
+ * Should fail (little harder).
+ *
+ * - pkg1 depends on virt (>= 0.4), pkg2 provides virt (<= 1.0) and (>= 0.5),
+ * IOW, inclusive of only those versions. This would require backchecking
+ * the other provided versions in the possi, which would make things sickly
+ * complex and overly time consuming. Should fail (very hard to implement).
+ *
+ * This could be handled by switching to a SAT solver, but that would imply
+ * lots of work for very little gain. Packages can easily get around most of
+ * these by providing multiple #DPKG_RELATION_EQ versions.
+ */
+bool
+pkg_virtual_deppossi_satisfied(struct deppossi *dependee,
+ struct deppossi *provider)
+{
+ if (provider->verrel != DPKG_RELATION_NONE &&
+ provider->verrel != DPKG_RELATION_EQ)
+ return false;
+
+ if (provider->verrel == DPKG_RELATION_NONE &&
+ dependee->verrel != DPKG_RELATION_NONE)
+ return false;
+
+ return dpkg_version_relate(&provider->version,
+ dependee->verrel,
+ &dependee->version);
+}
diff --git a/lib/dpkg/dir.c b/lib/dpkg/dir.c
new file mode 100644
index 0000000..f03aabc
--- /dev/null
+++ b/lib/dpkg/dir.c
@@ -0,0 +1,213 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * dir.c - directory handling functions
+ *
+ * Copyright © 2010 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <dpkg/dpkg.h>
+#include <dpkg/i18n.h>
+#include <dpkg/dir.h>
+
+static int
+dir_make_path_noalloc(char *dirname, mode_t mode)
+{
+ char *slash;
+
+ /* Find the first slash, and ignore it, as it will be either the
+ * slash for the root directory, for the current directory in a
+ * relative pathname or its parent. */
+ slash = strchr(dirname, '/');
+
+ while (slash != NULL) {
+ slash = strchr(slash + 1, '/');
+ if (slash)
+ *slash = '\0';
+
+ if (mkdir(dirname, mode) < 0 && errno != EEXIST)
+ return -1;
+ if (slash)
+ *slash = '/';
+ }
+
+ return 0;
+}
+
+/**
+ * Create the directory and all its parents if necessary.
+ *
+ * @param path The pathname to create directories for.
+ * @param mode The pathname mode.
+ */
+int
+dir_make_path(const char *path, mode_t mode)
+{
+ char *dirname;
+ int rc;
+
+ dirname = m_strdup(path);
+ rc = dir_make_path_noalloc(dirname, mode);
+ free(dirname);
+
+ return rc;
+}
+
+int
+dir_make_path_parent(const char *path, mode_t mode)
+{
+ char *dirname, *slash;
+ int rc;
+
+ dirname = m_strdup(path);
+ slash = strrchr(dirname, '/');
+ if (slash != NULL) {
+ *slash = '\0';
+ rc = dir_make_path_noalloc(dirname, mode);
+ } else {
+ rc = -1;
+ }
+ free(dirname);
+
+ return rc;
+}
+
+/**
+ * Sync a directory to disk from a DIR structure.
+ *
+ * @param dir The directory to sync.
+ * @param path The name of the directory to sync (for error messages).
+ */
+static void
+dir_sync(DIR *dir, const char *path)
+{
+#ifdef HAVE_FSYNC_DIR
+ int fd;
+
+ fd = dirfd(dir);
+ if (fd < 0)
+ ohshite(_("unable to get file descriptor for directory '%s'"),
+ path);
+
+ if (fsync(fd))
+ ohshite(_("unable to sync directory '%s'"), path);
+#endif
+}
+
+/**
+ * Sync a directory to disk from a pathname.
+ *
+ * @param path The name of the directory to sync.
+ */
+void
+dir_sync_path(const char *path)
+{
+#ifdef HAVE_FSYNC_DIR
+ DIR *dir;
+
+ dir = opendir(path);
+ if (!dir)
+ ohshite(_("unable to open directory '%s'"), path);
+
+ dir_sync(dir, path);
+
+ closedir(dir);
+#endif
+}
+
+/**
+ * Sync to disk the parent directory of a pathname.
+ *
+ * @param path The child pathname of the directory to sync.
+ */
+void
+dir_sync_path_parent(const char *path)
+{
+#ifdef HAVE_FSYNC_DIR
+ char *dirname, *slash;
+
+ dirname = m_strdup(path);
+
+ slash = strrchr(dirname, '/');
+ if (slash != NULL) {
+ *slash = '\0';
+ dir_sync_path(dirname);
+ }
+
+ free(dirname);
+#endif
+}
+
+/* TODO: Ideally we'd use openat() here, to avoid the path mangling, but
+ * it's not widely available yet, so this will do for now. */
+static void
+dir_file_sync(const char *dir, const char *filename)
+{
+ char *path;
+ int fd;
+
+ path = str_fmt("%s/%s", dir, filename);
+
+ fd = open(path, O_WRONLY);
+ if (fd < 0)
+ ohshite(_("unable to open file '%s'"), path);
+ if (fsync(fd))
+ ohshite(_("unable to sync file '%s'"), path);
+ if (close(fd))
+ ohshite(_("unable to close file '%s'"), path);
+
+ free(path);
+}
+
+/**
+ * Sync to disk the contents and the directory specified.
+ *
+ * @param path The pathname to sync.
+ */
+void
+dir_sync_contents(const char *path)
+{
+ DIR *dir;
+ struct dirent *dent;
+
+ dir = opendir(path);
+ if (!dir)
+ ohshite(_("unable to open directory '%s'"), path);
+
+ while ((dent = readdir(dir)) != NULL) {
+ if (strcmp(dent->d_name, ".") == 0 ||
+ strcmp(dent->d_name, "..") == 0)
+ continue;
+
+ dir_file_sync(path, dent->d_name);
+ }
+
+ dir_sync(dir, path);
+
+ closedir(dir);
+}
diff --git a/lib/dpkg/dir.h b/lib/dpkg/dir.h
new file mode 100644
index 0000000..65e2f46
--- /dev/null
+++ b/lib/dpkg/dir.h
@@ -0,0 +1,51 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * dir.h - directory handling routines
+ *
+ * Copyright © 2010 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_DIR_H
+#define LIBDPKG_DIR_H
+
+#include <dpkg/macros.h>
+
+#include <sys/types.h>
+
+#include <dirent.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup dir Directory handling
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+int
+dir_make_path(const char *path, mode_t mode);
+int
+dir_make_path_parent(const char *path, mode_t mode);
+
+void dir_sync_path(const char *path);
+void dir_sync_path_parent(const char *path);
+void dir_sync_contents(const char *path);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_DIR_H */
diff --git a/lib/dpkg/dlist.h b/lib/dpkg/dlist.h
new file mode 100644
index 0000000..1d3a91e
--- /dev/null
+++ b/lib/dpkg/dlist.h
@@ -0,0 +1,45 @@
+/*
+ * dlist.h - macros for handling doubly linked lists
+ *
+ * Copyright © 1997-1999 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef DPKG_DLIST_H
+#define DPKG_DLIST_H
+
+#define LIST_UNLINK_PART(list, node, part) \
+ do { \
+ if ((node)->part.prev) \
+ (node)->part.prev->part.next = (node)->part.next; \
+ else \
+ (list).head = (node)->part.next; \
+ if ((node)->part.next) \
+ (node)->part.next->part.prev = (node)->part.prev; \
+ else \
+ (list).tail = (node)->part.prev; \
+ } while (0)
+
+#define LIST_LINK_TAIL_PART(list, node, part) \
+ do { \
+ (node)->part.next = NULL; \
+ (node)->part.prev = (list).tail; \
+ if ((list).tail) \
+ (list).tail->part.next = (node); \
+ else (list).head = (node); \
+ (list).tail = (node); \
+ } while (0)
+
+#endif
diff --git a/lib/dpkg/dpkg-db.h b/lib/dpkg/dpkg-db.h
new file mode 100644
index 0000000..61f2205
--- /dev/null
+++ b/lib/dpkg/dpkg-db.h
@@ -0,0 +1,505 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * dpkg-db.h - declarations for in-core package database management
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2000,2001 Wichert Akkerman
+ * Copyright © 2006-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_DPKG_DB_H
+#define LIBDPKG_DPKG_DB_H
+
+#include <sys/types.h>
+
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <dpkg/macros.h>
+#include <dpkg/varbuf.h>
+#include <dpkg/version.h>
+#include <dpkg/arch.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup dpkg-db In-core package database management
+ * @ingroup dpkg-public
+ * @{
+ */
+
+enum deptype {
+ dep_suggests,
+ dep_recommends,
+ dep_depends,
+ dep_predepends,
+ dep_breaks,
+ dep_conflicts,
+ dep_provides,
+ dep_replaces,
+ dep_enhances
+};
+
+struct dependency {
+ struct pkginfo *up;
+ struct dependency *next;
+ struct deppossi *list;
+ enum deptype type;
+};
+
+struct deppossi {
+ struct dependency *up;
+ struct pkgset *ed;
+ struct deppossi *next, *rev_next, *rev_prev;
+ const struct dpkg_arch *arch;
+ struct dpkg_version version;
+ enum dpkg_relation verrel;
+ bool arch_is_implicit;
+ bool cyclebreak;
+};
+
+struct arbitraryfield {
+ struct arbitraryfield *next;
+ const char *name;
+ const char *value;
+};
+
+struct conffile {
+ struct conffile *next;
+ const char *name;
+ const char *hash;
+ bool obsolete;
+ bool remove_on_upgrade;
+};
+
+struct archivedetails {
+ struct archivedetails *next;
+ const char *name;
+ const char *msdosname;
+ const char *size;
+ const char *md5sum;
+};
+
+enum pkgmultiarch {
+ PKG_MULTIARCH_NO,
+ PKG_MULTIARCH_SAME,
+ PKG_MULTIARCH_ALLOWED,
+ PKG_MULTIARCH_FOREIGN,
+};
+
+/**
+ * Node describing a binary package file.
+ *
+ * This structure holds information contained on each binary package.
+ */
+struct pkgbin {
+ struct dependency *depends;
+ /** The ‘essential’ flag, true = yes, false = no (absent). */
+ bool essential;
+ /** The ‘protected’ flag, true = yes, false = no (absent). */
+ bool is_protected;
+ enum pkgmultiarch multiarch;
+ const struct dpkg_arch *arch;
+ /** The following is the "pkgname:archqual" cached string, if this was a
+ * C++ class this member would be mutable. */
+ const char *pkgname_archqual;
+ const char *description;
+ const char *maintainer;
+ const char *source;
+ const char *installedsize;
+ const char *origin;
+ const char *bugs;
+ struct dpkg_version version;
+ struct conffile *conffiles;
+ struct arbitraryfield *arbs;
+};
+
+/**
+ * Node indicates that parent's Triggers-Pending mentions name.
+ *
+ * Note: These nodes do double duty: after they're removed from a package's
+ * trigpend list, references may be preserved by the trigger cycle checker
+ * (see trigproc.c).
+ */
+struct trigpend {
+ struct trigpend *next;
+ const char *name;
+};
+
+/**
+ * Node indicates that aw's Triggers-Awaited mentions pend.
+ */
+struct trigaw {
+ struct pkginfo *aw, *pend;
+ struct trigaw *samepend_next;
+ struct {
+ struct trigaw *next, *prev;
+ } sameaw;
+};
+
+/* Note: dselect and dpkg have different versions of this. */
+struct perpackagestate;
+
+enum pkgwant {
+ PKG_WANT_UNKNOWN,
+ PKG_WANT_INSTALL,
+ PKG_WANT_HOLD,
+ PKG_WANT_DEINSTALL,
+ PKG_WANT_PURGE,
+ /** Not allowed except as special sentinel value in some places. */
+ PKG_WANT_SENTINEL,
+};
+
+enum pkgeflag {
+ PKG_EFLAG_OK = 0,
+ PKG_EFLAG_REINSTREQ = 1,
+};
+
+enum pkgstatus {
+ PKG_STAT_NOTINSTALLED,
+ PKG_STAT_CONFIGFILES,
+ PKG_STAT_HALFINSTALLED,
+ PKG_STAT_UNPACKED,
+ PKG_STAT_HALFCONFIGURED,
+ PKG_STAT_TRIGGERSAWAITED,
+ PKG_STAT_TRIGGERSPENDING,
+ PKG_STAT_INSTALLED,
+};
+
+enum pkgpriority {
+ PKG_PRIO_REQUIRED,
+ PKG_PRIO_IMPORTANT,
+ PKG_PRIO_STANDARD,
+ PKG_PRIO_OPTIONAL,
+ PKG_PRIO_EXTRA,
+ PKG_PRIO_OTHER,
+ PKG_PRIO_UNKNOWN,
+ PKG_PRIO_UNSET = -1,
+};
+
+/**
+ * Node describing an architecture package instance.
+ *
+ * This structure holds state information.
+ */
+struct pkginfo {
+ struct pkgset *set;
+ struct pkginfo *arch_next;
+
+ enum pkgwant want;
+ /** The error flag bitmask. */
+ enum pkgeflag eflag;
+ enum pkgstatus status;
+ enum pkgpriority priority;
+ const char *otherpriority;
+ const char *section;
+ struct dpkg_version configversion;
+ struct pkgbin installed;
+ struct pkgbin available;
+ struct perpackagestate *clientdata;
+
+ struct archivedetails *archives;
+
+ struct {
+ /* ->aw == this */
+ struct trigaw *head, *tail;
+ } trigaw;
+
+ /* ->pend == this, non-NULL for us when Triggers-Pending. */
+ struct trigaw *othertrigaw_head;
+ struct trigpend *trigpend_head;
+
+ /**
+ * files_list_valid files Meaning
+ * ---------------- ----- -------
+ * false NULL Not read yet, must do so if want them.
+ * false !NULL Read, but rewritten and now out of date. If want
+ * info must throw away old and reread file.
+ * true !NULL Read, all is OK.
+ * true NULL Read OK, but, there were no files.
+ */
+ struct fsys_namenode_list *files;
+ off_t files_list_phys_offs;
+ bool files_list_valid;
+
+ /* The status has changed, it needs to be logged. */
+ bool status_dirty;
+};
+
+/**
+ * Node describing a package set sharing the same package name.
+ */
+struct pkgset {
+ struct pkgset *next;
+ const char *name;
+ struct pkginfo pkg;
+ struct {
+ struct deppossi *available;
+ struct deppossi *installed;
+ } depended;
+ int installed_instances;
+};
+
+/*** from dbdir.c ***/
+
+const char *dpkg_db_set_dir(const char *dir);
+const char *dpkg_db_get_dir(void);
+char *dpkg_db_get_path(const char *pathpart);
+
+#include <dpkg/atomic-file.h>
+
+/*** from dbmodify.c ***/
+
+enum DPKG_ATTR_ENUM_FLAGS modstatdb_rw {
+ msdbrw_unset = -1,
+
+ /* Those marked with «return» are possible returns from modstatdb_open(). */
+ msdbrw_readonly, /* «return» */
+ msdbrw_needsuperuserlockonly, /* «return» */
+ msdbrw_writeifposs,
+ msdbrw_write, /* «return» */
+ msdbrw_needsuperuser,
+
+ /* Now some optional flags (starting at bit 8): */
+ msdbrw_available_readonly = DPKG_BIT(8),
+ msdbrw_available_write = DPKG_BIT(9),
+ msdbrw_available_mask = 0x0300,
+};
+
+void modstatdb_init(void);
+void modstatdb_done(void);
+bool modstatdb_is_locked(void);
+bool modstatdb_can_lock(void);
+void modstatdb_lock(void);
+void modstatdb_unlock(void);
+enum modstatdb_rw modstatdb_open(enum modstatdb_rw reqrwflags);
+enum modstatdb_rw modstatdb_get_status(void);
+void modstatdb_note(struct pkginfo *pkg);
+void modstatdb_note_ifwrite(struct pkginfo *pkg);
+void modstatdb_checkpoint(void);
+void modstatdb_shutdown(void);
+
+/*** from database.c ***/
+
+void pkgset_blank(struct pkgset *set);
+int pkgset_installed_instances(struct pkgset *set);
+
+void pkg_blank(struct pkginfo *pp);
+void pkgbin_blank(struct pkgbin *pkgbin);
+bool pkg_is_informative(struct pkginfo *pkg, struct pkgbin *info);
+
+struct pkgset *
+pkg_hash_find_set(const char *name);
+struct pkginfo *
+pkg_hash_get_singleton(struct pkgset *set);
+struct pkginfo *
+pkg_hash_find_singleton(const char *name);
+struct pkginfo *
+pkg_hash_get_pkg(struct pkgset *set, const struct dpkg_arch *arch);
+struct pkginfo *
+pkg_hash_find_pkg(const char *name, const struct dpkg_arch *arch);
+int
+pkg_hash_count_set(void);
+int
+pkg_hash_count_pkg(void);
+void
+pkg_hash_reset(void);
+
+struct pkg_hash_iter *
+pkg_hash_iter_new(void);
+struct pkgset *
+pkg_hash_iter_next_set(struct pkg_hash_iter *iter);
+struct pkginfo *
+pkg_hash_iter_next_pkg(struct pkg_hash_iter *iter);
+void
+pkg_hash_iter_free(struct pkg_hash_iter *iter);
+
+void
+pkg_hash_report(FILE *);
+
+/*** from parse.c ***/
+
+enum DPKG_ATTR_ENUM_FLAGS parsedbflags {
+ /** Parse a single control stanza. */
+ pdb_single_stanza = DPKG_BIT(0),
+ /** Store in ‘available’ in-core structures, not ‘status’. */
+ pdb_recordavailable = DPKG_BIT(1),
+ /** Throw up an error if ‘Status’ encountered. */
+ pdb_rejectstatus = DPKG_BIT(2),
+ /** Ignore priority/section info if we already have any. */
+ pdb_weakclassification = DPKG_BIT(3),
+ /** Ignore archives info if we already have them. */
+ pdb_ignore_archives = DPKG_BIT(4),
+ /** Ignore packages with older versions already read. */
+ pdb_ignoreolder = DPKG_BIT(5),
+ /** Perform laxer version parsing. */
+ pdb_lax_version_parser = DPKG_BIT(6),
+ /** Perform laxer control stanza parsing. */
+ pdb_lax_stanza_parser = DPKG_BIT(7),
+ /** Perform laxer parsing, used to transition to stricter parsing. */
+ pdb_lax_parser = pdb_lax_stanza_parser | pdb_lax_version_parser,
+ /** Close file descriptor on context destruction. */
+ pdb_close_fd = DPKG_BIT(8),
+ /** Interpret filename ‘-’ as stdin. */
+ pdb_dash_is_stdin = DPKG_BIT(9),
+ /** Allow empty/missing files. */
+ pdb_allow_empty = DPKG_BIT(10),
+
+ /* Standard operations. */
+
+ pdb_parse_status = pdb_lax_parser | pdb_weakclassification |
+ pdb_allow_empty,
+ pdb_parse_update = pdb_parse_status | pdb_single_stanza,
+ pdb_parse_available = pdb_recordavailable | pdb_rejectstatus |
+ pdb_lax_parser | pdb_allow_empty,
+ pdb_parse_binary = pdb_recordavailable | pdb_rejectstatus |
+ pdb_single_stanza,
+};
+
+const char *pkg_name_is_illegal(const char *p);
+
+const struct fieldinfo *
+find_field_info(const struct fieldinfo *fields, const char *fieldname);
+const struct arbitraryfield *
+find_arbfield_info(const struct arbitraryfield *arbs, const char *fieldname);
+
+int parsedb(const char *filename, enum parsedbflags, struct pkginfo **donep);
+void copy_dependency_links(struct pkginfo *pkg,
+ struct dependency **updateme,
+ struct dependency *newdepends,
+ bool available);
+
+/*** from parsehelp.c ***/
+
+#include <dpkg/namevalue.h>
+
+extern const struct namevalue booleaninfos[];
+extern const struct namevalue multiarchinfos[];
+extern const struct namevalue priorityinfos[];
+extern const struct namevalue statusinfos[];
+extern const struct namevalue eflaginfos[];
+extern const struct namevalue wantinfos[];
+
+#include <dpkg/error.h>
+
+enum versiondisplayepochwhen {
+ vdew_never,
+ vdew_nonambig,
+ vdew_always,
+};
+void varbufversion(struct varbuf *, const struct dpkg_version *,
+ enum versiondisplayepochwhen);
+int parseversion(struct dpkg_version *version, const char *,
+ struct dpkg_error *err);
+const char *versiondescribe(const struct dpkg_version *,
+ enum versiondisplayepochwhen);
+const char *versiondescribe_c(const struct dpkg_version *version,
+ enum versiondisplayepochwhen vdew);
+
+enum pkg_name_arch_when {
+ /** Never display arch. */
+ pnaw_never,
+ /** Display arch only when it's non-ambiguous. */
+ pnaw_nonambig,
+ /** Display arch only when it's a Multi-Arch same package. */
+ pnaw_same,
+ /** Display arch only when it's a foreign one. */
+ pnaw_foreign,
+ /** Always display arch. */
+ pnaw_always,
+};
+
+void varbuf_add_pkgbin_name(struct varbuf *vb, const struct pkginfo *pkg,
+ const struct pkgbin *pkgbin,
+ enum pkg_name_arch_when pnaw);
+
+const char *
+pkgbin_name_archqual(const struct pkginfo *pkg, const struct pkgbin *pkgbin);
+
+const char *
+pkgbin_name(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ enum pkg_name_arch_when pnaw);
+const char *
+pkg_name(struct pkginfo *pkg, enum pkg_name_arch_when pnaw);
+
+const char *
+pkgbin_name_const(const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum pkg_name_arch_when pnaw);
+const char *
+pkg_name_const(const struct pkginfo *pkg, enum pkg_name_arch_when pnaw);
+
+void
+pkg_source_version(struct dpkg_version *version,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin);
+
+void
+varbuf_add_source_version(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin);
+
+const char *pkg_want_name(const struct pkginfo *pkg);
+const char *pkg_status_name(const struct pkginfo *pkg);
+const char *pkg_eflag_name(const struct pkginfo *pkg);
+
+const char *pkg_priority_name(const struct pkginfo *pkg);
+
+/*** from dump.c ***/
+
+void
+write_stanza(FILE *, const char *,
+ const struct pkginfo *, const struct pkgbin *);
+
+enum DPKG_ATTR_ENUM_FLAGS writedb_flags {
+ /** No flags. */
+ wdb_none = 0,
+ /** Dump ‘available’ in-core structures, not ‘status’. */
+ wdb_dump_available = DPKG_BIT(0),
+ /** Must sync the written file. */
+ wdb_must_sync = DPKG_BIT(1),
+};
+
+void
+writedb_stanzas(FILE *fp, const char *filename, enum writedb_flags flags);
+void writedb(const char *filename, enum writedb_flags flags);
+
+/* Note: The varbufs must have been initialized and will not be
+ * NUL-terminated. */
+void
+varbuf_stanza(struct varbuf *, const struct pkginfo *,
+ const struct pkgbin *);
+void varbufdependency(struct varbuf *vb, struct dependency *dep);
+
+/*** from depcon.c ***/
+
+bool versionsatisfied(struct pkgbin *it, struct deppossi *against);
+bool deparchsatisfied(struct pkgbin *it, const struct dpkg_arch *arch,
+ struct deppossi *against);
+bool archsatisfied(struct pkgbin *it, struct deppossi *against);
+
+bool
+pkg_virtual_deppossi_satisfied(struct deppossi *dependee,
+ struct deppossi *provider);
+
+/*** from nfmalloc.c ***/
+void *nfmalloc(size_t);
+char *nfstrsave(const char*);
+char *nfstrnsave(const char*, size_t);
+void nffreeall(void);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_DPKG_DB_H */
diff --git a/lib/dpkg/dpkg.h b/lib/dpkg/dpkg.h
new file mode 100644
index 0000000..ee6fd3b
--- /dev/null
+++ b/lib/dpkg/dpkg.h
@@ -0,0 +1,158 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * dpkg.h - general header for Debian package handling
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2000,2001 Wichert Akkerman <wichert@debian.org>
+ * Copyright © 2006-2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_DPKG_H
+#define LIBDPKG_DPKG_H
+
+#include <sys/types.h>
+
+#include <stddef.h>
+#include <stdio.h>
+
+#include <dpkg/macros.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @mainpage libdpkg C API
+ *
+ * This is the documentation for the libdpkg C API. It is divided in an
+ * @ref dpkg-internal "internal API" and a @ref dpkg-public "public API".
+ * Applications closely tied to dpkg can make use of the internal API, the
+ * rest should only assume the availability of the public API.
+ *
+ * Applications need to define the LIBDPKG_VOLATILE_API macro to acknowledge
+ * that the API is to be considered volatile, please read doc/README.api for
+ * more information.
+ *
+ * @defgroup dpkg-internal Internal libdpkg C API
+ *
+ * @defgroup dpkg-public Public libdpkg C API
+ */
+
+#define MAXCONFFILENAME 1000
+#define MAXDIVERTFILENAME 1024
+#define MAXCONTROLFILENAME 100
+#define DEBEXT ".deb"
+#define REMOVECONFFEXTS "~", ".bak", "%", \
+ DPKGTEMPEXT, DPKGNEWEXT, DPKGOLDEXT, DPKGDISTEXT
+
+#define NEWCONFFILEFLAG "newconffile"
+#define NONEXISTENTFLAG "nonexistent"
+#define EMPTYHASHFLAG "-"
+
+#define DPKGTEMPEXT ".dpkg-tmp"
+#define DPKGNEWEXT ".dpkg-new"
+#define DPKGOLDEXT ".dpkg-old"
+#define DPKGDISTEXT ".dpkg-dist"
+
+#define CONTROLFILE "control"
+#define CONFFILESFILE "conffiles"
+#define PREINSTFILE "preinst"
+#define POSTINSTFILE "postinst"
+#define PRERMFILE "prerm"
+#define POSTRMFILE "postrm"
+/* Debconf config maintainer script. */
+#define MAINTSCRIPT_FILE_CONFIG "config"
+#define TRIGGERSCIFILE "triggers"
+
+#define STATUSFILE "status"
+#define AVAILFILE "available"
+#define LOCKFILE "lock"
+#define FRONTENDLOCKFILE "lock-frontend"
+#define DIVERSIONSFILE "diversions"
+#define STATOVERRIDEFILE "statoverride"
+#define UPDATESDIR "updates"
+#define INFODIR "info"
+#define TRIGGERSDIR "triggers"
+#define TRIGGERSFILEFILE "File"
+#define TRIGGERSDEFERREDFILE "Unincorp"
+#define TRIGGERSLOCKFILE "Lock"
+#define CONTROLDIRTMP "tmp.ci"
+#define IMPORTANTTMP "tmp.i"
+#define REASSEMBLETMP "reassemble" DEBEXT
+#define IMPORTANTMAXLEN 10
+#define IMPORTANTFMT "%04d"
+#define MAXUPDATES 250
+
+#define MD5HASHLEN 32
+#define MAXTRIGDIRECTIVE 256
+
+#define BACKEND "dpkg-deb"
+#define SPLITTER "dpkg-split"
+#define DPKGQUERY "dpkg-query"
+#define DPKGDIVERT "dpkg-divert"
+#define DPKGSTAT "dpkg-statoverride"
+#define DPKGTRIGGER "dpkg-trigger"
+#define DPKG "dpkg"
+#define DEBSIGVERIFY "debsig-verify"
+
+#define RM "rm"
+#define CAT "cat"
+#define DIFF "diff"
+
+#include <dpkg/progname.h>
+#include <dpkg/ehandle.h>
+#include <dpkg/report.h>
+#include <dpkg/string.h>
+#include <dpkg/program.h>
+
+/*** log.c ***/
+
+extern const char *log_file;
+void log_message(const char *fmt, ...) DPKG_ATTR_PRINTF(1);
+
+void statusfd_add(int fd);
+void statusfd_send(const char *fmt, ...) DPKG_ATTR_PRINTF(1);
+
+/*** cleanup.c ***/
+
+void cu_closestream(int argc, void **argv);
+void cu_closepipe(int argc, void **argv);
+void cu_closedir(int argc, void **argv);
+void cu_closefd(int argc, void **argv);
+void cu_filename(int argc, void **argv);
+
+/*** from mustlib.c ***/
+
+void setcloexec(int fd, const char *fn);
+void *m_malloc(size_t);
+void *m_calloc(size_t nmemb, size_t size);
+void *m_realloc(void *, size_t);
+char *m_strdup(const char *str);
+char *m_strndup(const char *str, size_t n);
+int m_asprintf(char **strp, const char *fmt, ...) DPKG_ATTR_PRINTF(2);
+int m_vasprintf(char **strp, const char *fmt, va_list args)
+ DPKG_ATTR_VPRINTF(2);
+int m_dup(int oldfd);
+void m_dup2(int oldfd, int newfd);
+void m_pipe(int fds[2]);
+void m_output(FILE *f, const char *name);
+
+/*** from utils.c ***/
+
+int fgets_checked(char *buf, size_t bufsz, FILE *f, const char *fn);
+int fgets_must(char *buf, size_t bufsz, FILE *f, const char *fn);
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_DPKG_H */
diff --git a/lib/dpkg/dump.c b/lib/dpkg/dump.c
new file mode 100644
index 0000000..b27e5db
--- /dev/null
+++ b/lib/dpkg/dump.c
@@ -0,0 +1,561 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * dump.c - code to write in-core database to a file
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2001 Wichert Akkerman
+ * Copyright © 2006,2008-2014 Guillem Jover <guillem@debian.org>
+ * Copyright © 2011 Linaro Limited
+ * Copyright © 2011 Raphaël Hertzog <hertzog@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg-array.h>
+#include <dpkg/pkg-show.h>
+#include <dpkg/string.h>
+#include <dpkg/dir.h>
+#include <dpkg/parsedump.h>
+
+static inline void
+varbuf_add_fieldname(struct varbuf *vb, const struct fieldinfo *fip)
+{
+ varbuf_add_str(vb, fip->name);
+ varbuf_add_str(vb, ": ");
+}
+
+void
+w_name(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ if (pkg->set->name == NULL)
+ internerr("pkgset has no name");
+
+ if (flags&fw_printheader)
+ varbuf_add_str(vb, "Package: ");
+ varbuf_add_str(vb, pkg->set->name);
+ if (flags&fw_printheader)
+ varbuf_add_char(vb, '\n');
+}
+
+void
+w_version(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ if (!dpkg_version_is_informative(&pkgbin->version))
+ return;
+ if (flags&fw_printheader)
+ varbuf_add_str(vb, "Version: ");
+ varbufversion(vb, &pkgbin->version, vdew_nonambig);
+ if (flags&fw_printheader)
+ varbuf_add_char(vb, '\n');
+}
+
+void
+w_configversion(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ if (pkgbin != &pkg->installed)
+ return;
+ if (!dpkg_version_is_informative(&pkg->configversion))
+ return;
+ if (pkg->status == PKG_STAT_INSTALLED ||
+ pkg->status == PKG_STAT_NOTINSTALLED ||
+ pkg->status == PKG_STAT_TRIGGERSPENDING)
+ return;
+ if (flags&fw_printheader)
+ varbuf_add_str(vb, "Config-Version: ");
+ varbufversion(vb, &pkg->configversion, vdew_nonambig);
+ if (flags&fw_printheader)
+ varbuf_add_char(vb, '\n');
+}
+
+void
+w_null(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+}
+
+void
+w_section(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ const char *value = pkg->section;
+
+ if (str_is_unset(value))
+ return;
+ if (flags&fw_printheader)
+ varbuf_add_str(vb, "Section: ");
+ varbuf_add_str(vb, value);
+ if (flags&fw_printheader)
+ varbuf_add_char(vb, '\n');
+}
+
+void
+w_charfield(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ const char *value = STRUCTFIELD(pkgbin, fip->integer, const char *);
+
+ if (str_is_unset(value))
+ return;
+ if (flags & fw_printheader)
+ varbuf_add_fieldname(vb, fip);
+ varbuf_add_str(vb, value);
+ if (flags&fw_printheader)
+ varbuf_add_char(vb, '\n');
+}
+
+void
+w_archives(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ struct archivedetails *archive;
+
+ if (pkgbin != &pkg->available)
+ return;
+ archive = pkg->archives;
+ if (!archive || !STRUCTFIELD(archive, fip->integer, const char *))
+ return;
+
+ if (flags&fw_printheader) {
+ varbuf_add_str(vb, fip->name);
+ varbuf_add_char(vb, ':');
+ }
+
+ while (archive) {
+ varbuf_add_char(vb, ' ');
+ varbuf_add_str(vb, STRUCTFIELD(archive, fip->integer, const char *));
+ archive = archive->next;
+ }
+
+ if (flags&fw_printheader)
+ varbuf_add_char(vb, '\n');
+}
+
+void
+w_booleandefno(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ bool value = STRUCTFIELD(pkgbin, fip->integer, bool);
+
+ if ((flags & fw_printheader) && !value)
+ return;
+
+ if (flags & fw_printheader)
+ varbuf_add_fieldname(vb, fip);
+
+ varbuf_add_str(vb, value ? "yes" : "no");
+
+ if (flags & fw_printheader)
+ varbuf_add_char(vb, '\n');
+}
+
+void
+w_multiarch(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ int value = STRUCTFIELD(pkgbin, fip->integer, int);
+
+ if ((flags & fw_printheader) && !value)
+ return;
+
+ if (flags & fw_printheader)
+ varbuf_add_fieldname(vb, fip);
+
+ varbuf_add_str(vb, multiarchinfos[value].name);
+
+ if (flags & fw_printheader)
+ varbuf_add_char(vb, '\n');
+}
+
+void
+w_architecture(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ if (!pkgbin->arch)
+ return;
+ if (pkgbin->arch->type == DPKG_ARCH_NONE)
+ return;
+ if (pkgbin->arch->type == DPKG_ARCH_EMPTY)
+ return;
+
+ if (flags & fw_printheader)
+ varbuf_add_fieldname(vb, fip);
+ varbuf_add_str(vb, pkgbin->arch->name);
+ if (flags & fw_printheader)
+ varbuf_add_char(vb, '\n');
+}
+
+void
+w_priority(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ if (pkg->priority == PKG_PRIO_UNKNOWN)
+ return;
+
+ if (pkg->priority > PKG_PRIO_UNKNOWN)
+ internerr("package %s has out-of-range priority %d",
+ pkgbin_name_const(pkg, pkgbin, pnaw_always), pkg->priority);
+
+ if (flags&fw_printheader)
+ varbuf_add_str(vb, "Priority: ");
+ varbuf_add_str(vb, pkg_priority_name(pkg));
+ if (flags&fw_printheader)
+ varbuf_add_char(vb, '\n');
+}
+
+void
+w_status(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ if (pkgbin != &pkg->installed)
+ return;
+
+ if (pkg->want > PKG_WANT_PURGE)
+ internerr("package %s has unknown want state %d",
+ pkgbin_name_const(pkg, pkgbin, pnaw_always), pkg->want);
+ if (pkg->eflag > PKG_EFLAG_REINSTREQ)
+ internerr("package %s has unknown error state %d",
+ pkgbin_name_const(pkg, pkgbin, pnaw_always), pkg->eflag);
+
+ switch (pkg->status) {
+ case PKG_STAT_NOTINSTALLED:
+ case PKG_STAT_CONFIGFILES:
+ if (pkg->trigpend_head || pkg->trigaw.head)
+ internerr("package %s in state %s, has awaited or pending triggers",
+ pkgbin_name_const(pkg, pkgbin, pnaw_always), pkg_status_name(pkg));
+ break;
+ case PKG_STAT_HALFINSTALLED:
+ case PKG_STAT_UNPACKED:
+ case PKG_STAT_HALFCONFIGURED:
+ if (pkg->trigpend_head)
+ internerr("package %s in state %s, has pending triggers",
+ pkgbin_name_const(pkg, pkgbin, pnaw_always), pkg_status_name(pkg));
+ break;
+ case PKG_STAT_TRIGGERSAWAITED:
+ if (pkg->trigaw.head == NULL)
+ internerr("package %s in state %s, has no awaited triggers",
+ pkgbin_name_const(pkg, pkgbin, pnaw_always), pkg_status_name(pkg));
+ break;
+ case PKG_STAT_TRIGGERSPENDING:
+ if (pkg->trigpend_head == NULL || pkg->trigaw.head)
+ internerr("package %s in stata %s, has awaited or no pending triggers",
+ pkgbin_name_const(pkg, pkgbin, pnaw_always), pkg_status_name(pkg));
+ break;
+ case PKG_STAT_INSTALLED:
+ if (pkg->trigpend_head || pkg->trigaw.head)
+ internerr("package %s in state %s, has awaited or pending triggers",
+ pkgbin_name_const(pkg, pkgbin, pnaw_always), pkg_status_name(pkg));
+ break;
+ default:
+ internerr("unknown package status '%d'", pkg->status);
+ }
+
+ if (flags&fw_printheader)
+ varbuf_add_str(vb, "Status: ");
+ varbuf_add_str(vb, pkg_want_name(pkg));
+ varbuf_add_char(vb, ' ');
+ varbuf_add_str(vb, pkg_eflag_name(pkg));
+ varbuf_add_char(vb, ' ');
+ varbuf_add_str(vb, pkg_status_name(pkg));
+ if (flags&fw_printheader)
+ varbuf_add_char(vb, '\n');
+}
+
+void varbufdependency(struct varbuf *vb, struct dependency *dep) {
+ struct deppossi *dop;
+ const char *possdel;
+
+ possdel= "";
+ for (dop= dep->list; dop; dop= dop->next) {
+ if (dop->up != dep)
+ internerr("dependency and deppossi not linked properly");
+
+ varbuf_add_str(vb, possdel);
+ possdel = " | ";
+ varbuf_add_str(vb, dop->ed->name);
+ if (!dop->arch_is_implicit)
+ varbuf_add_archqual(vb, dop->arch);
+ if (dop->verrel != DPKG_RELATION_NONE) {
+ varbuf_add_str(vb, " (");
+ switch (dop->verrel) {
+ case DPKG_RELATION_EQ:
+ varbuf_add_char(vb, '=');
+ break;
+ case DPKG_RELATION_GE:
+ varbuf_add_str(vb, ">=");
+ break;
+ case DPKG_RELATION_LE:
+ varbuf_add_str(vb, "<=");
+ break;
+ case DPKG_RELATION_GT:
+ varbuf_add_str(vb, ">>");
+ break;
+ case DPKG_RELATION_LT:
+ varbuf_add_str(vb, "<<");
+ break;
+ default:
+ internerr("unknown dpkg_relation %d", dop->verrel);
+ }
+ varbuf_add_char(vb, ' ');
+ varbufversion(vb,&dop->version,vdew_nonambig);
+ varbuf_add_char(vb, ')');
+ }
+ }
+}
+
+void
+w_dependency(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ struct dependency *dyp;
+ bool dep_found = false;
+
+ for (dyp = pkgbin->depends; dyp; dyp = dyp->next) {
+ if (dyp->type != fip->integer) continue;
+
+ if (dyp->up != pkg)
+ internerr("dependency and package %s not linked properly",
+ pkgbin_name_const(pkg, pkgbin, pnaw_always));
+
+ if (dep_found) {
+ varbuf_add_str(vb, ", ");
+ } else {
+ if (flags & fw_printheader)
+ varbuf_add_fieldname(vb, fip);
+ dep_found = true;
+ }
+ varbufdependency(vb,dyp);
+ }
+ if ((flags & fw_printheader) && dep_found)
+ varbuf_add_char(vb, '\n');
+}
+
+void
+w_conffiles(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ struct conffile *i;
+
+ if (!pkgbin->conffiles || pkgbin == &pkg->available)
+ return;
+ if (flags&fw_printheader)
+ varbuf_add_str(vb, "Conffiles:\n");
+ for (i = pkgbin->conffiles; i; i = i->next) {
+ if (i != pkgbin->conffiles)
+ varbuf_add_char(vb, '\n');
+ varbuf_add_char(vb, ' ');
+ varbuf_add_str(vb, i->name);
+ varbuf_add_char(vb, ' ');
+ varbuf_add_str(vb, i->hash);
+ if (i->obsolete)
+ varbuf_add_str(vb, " obsolete");
+ if (i->remove_on_upgrade)
+ varbuf_add_str(vb, " remove-on-upgrade");
+ }
+ if (flags&fw_printheader)
+ varbuf_add_char(vb, '\n');
+}
+
+void
+w_trigpend(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ struct trigpend *tp;
+
+ if (pkgbin == &pkg->available || !pkg->trigpend_head)
+ return;
+
+ if (pkg->status < PKG_STAT_TRIGGERSAWAITED ||
+ pkg->status > PKG_STAT_TRIGGERSPENDING)
+ internerr("package %s in non-trigger state %s, has pending triggers",
+ pkgbin_name_const(pkg, pkgbin, pnaw_always), pkg_status_name(pkg));
+
+ if (flags & fw_printheader)
+ varbuf_add_str(vb, "Triggers-Pending:");
+ for (tp = pkg->trigpend_head; tp; tp = tp->next) {
+ varbuf_add_char(vb, ' ');
+ varbuf_add_str(vb, tp->name);
+ }
+ if (flags & fw_printheader)
+ varbuf_add_char(vb, '\n');
+}
+
+void
+w_trigaw(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ struct trigaw *ta;
+
+ if (pkgbin == &pkg->available || !pkg->trigaw.head)
+ return;
+
+ if (pkg->status <= PKG_STAT_CONFIGFILES ||
+ pkg->status > PKG_STAT_TRIGGERSAWAITED)
+ internerr("package %s in state %s, has awaited triggers",
+ pkgbin_name_const(pkg, pkgbin, pnaw_always), pkg_status_name(pkg));
+
+ if (flags & fw_printheader)
+ varbuf_add_str(vb, "Triggers-Awaited:");
+ for (ta = pkg->trigaw.head; ta; ta = ta->sameaw.next) {
+ varbuf_add_char(vb, ' ');
+ varbuf_add_pkgbin_name(vb, ta->pend, &ta->pend->installed, pnaw_nonambig);
+ }
+ if (flags & fw_printheader)
+ varbuf_add_char(vb, '\n');
+}
+
+void
+varbuf_add_arbfield(struct varbuf *vb, const struct arbitraryfield *arbfield,
+ enum fwriteflags flags)
+{
+ if (flags & fw_printheader) {
+ varbuf_add_str(vb, arbfield->name);
+ varbuf_add_str(vb, ": ");
+ }
+ varbuf_add_str(vb, arbfield->value);
+ if (flags & fw_printheader)
+ varbuf_add_char(vb, '\n');
+}
+
+void
+varbuf_stanza(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin)
+{
+ const struct fieldinfo *fip;
+ const struct arbitraryfield *afp;
+
+ for (fip= fieldinfos; fip->name; fip++) {
+ fip->wcall(vb, pkg, pkgbin, fw_printheader, fip);
+ }
+ for (afp = pkgbin->arbs; afp; afp = afp->next) {
+ varbuf_add_arbfield(vb, afp, fw_printheader);
+ }
+}
+
+void
+write_stanza(FILE *file, const char *filename,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin)
+{
+ struct varbuf vb = VARBUF_INIT;
+
+ varbuf_stanza(&vb, pkg, pkgbin);
+ varbuf_end_str(&vb);
+
+ if (fputs(vb.buf, file) < 0)
+ ohshite(_("failed to write details of '%.50s' to '%.250s'"),
+ pkgbin_name_const(pkg, pkgbin, pnaw_nonambig), filename);
+
+ varbuf_destroy(&vb);
+}
+
+void
+writedb_stanzas(FILE *fp, const char *filename, enum writedb_flags flags)
+{
+ static char writebuf[8192];
+
+ struct pkg_array array;
+ const char *which;
+ struct varbuf vb = VARBUF_INIT;
+ int i;
+
+ which = (flags & wdb_dump_available) ? "available" : "status";
+
+ if (setvbuf(fp, writebuf, _IOFBF, sizeof(writebuf)))
+ ohshite(_("unable to set buffering on %s database file"), which);
+
+ pkg_array_init_from_hash(&array);
+ pkg_array_sort(&array, pkg_sorter_by_nonambig_name_arch);
+
+ for (i = 0; i < array.n_pkgs; i++) {
+ struct pkginfo *pkg;
+ struct pkgbin *pkgbin;
+
+ pkg = array.pkgs[i];
+ pkgbin = (flags & wdb_dump_available) ? &pkg->available : &pkg->installed;
+
+ /* Don't dump stanzas which have no useful content. */
+ if (!pkg_is_informative(pkg, pkgbin))
+ continue;
+
+ varbuf_stanza(&vb, pkg, pkgbin);
+ varbuf_add_char(&vb, '\n');
+ varbuf_end_str(&vb);
+ if (fputs(vb.buf, fp) < 0)
+ ohshite(_("failed to write %s database stanza about '%s' to '%s'"),
+ which, pkgbin_name(pkg, pkgbin, pnaw_nonambig), filename);
+ varbuf_reset(&vb);
+ }
+
+ pkg_array_destroy(&array);
+ varbuf_destroy(&vb);
+}
+
+void
+writedb(const char *filename, enum writedb_flags flags)
+{
+ struct atomic_file *file;
+ enum atomic_file_flags atomic_flags = ATOMIC_FILE_BACKUP;
+
+ if (flags & wdb_dump_available)
+ atomic_flags = 0;
+
+ file = atomic_file_new(filename, atomic_flags);
+ atomic_file_open(file);
+
+ writedb_stanzas(file->fp, filename, flags);
+
+ if (flags & wdb_must_sync)
+ atomic_file_sync(file);
+
+ atomic_file_close(file);
+ atomic_file_commit(file);
+ atomic_file_free(file);
+
+ if (flags & wdb_must_sync)
+ dir_sync_path_parent(filename);
+}
diff --git a/lib/dpkg/ehandle.c b/lib/dpkg/ehandle.c
new file mode 100644
index 0000000..be95c36
--- /dev/null
+++ b/lib/dpkg/ehandle.c
@@ -0,0 +1,511 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * ehandle.c - error handling
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/macros.h>
+#include <dpkg/i18n.h>
+#include <dpkg/progname.h>
+#include <dpkg/color.h>
+#include <dpkg/ehandle.h>
+
+/* Incremented when we do some kind of generally necessary operation,
+ * so that loops &c know to quit if we take an error exit. Decremented
+ * again afterwards. */
+volatile int onerr_abort = 0;
+
+#define NCALLS 2
+
+struct cleanup_entry {
+ struct cleanup_entry *next;
+ struct {
+ int mask;
+ void (*call)(int argc, void **argv);
+ } calls[NCALLS];
+ int cpmask, cpvalue;
+ int argc;
+ void *argv[1];
+};
+
+struct error_context {
+ struct error_context *next;
+
+ enum {
+ HANDLER_TYPE_FUNC,
+ HANDLER_TYPE_JUMP,
+ } handler_type;
+
+ union {
+ error_handler_func *func;
+ jmp_buf *jump;
+ } handler;
+
+ struct {
+ error_printer_func *func;
+ const void *data;
+ } printer;
+
+ struct cleanup_entry *cleanups;
+
+ char *errmsg;
+};
+
+static struct error_context *volatile econtext = NULL;
+
+/**
+ * Emergency variables.
+ *
+ * These are used when the system is out of resources, and we want to be able
+ * to proceed anyway at least to the point of a controlled shutdown.
+ */
+static struct {
+ struct cleanup_entry ce;
+ void *args[20];
+
+ /**
+ * Emergency error message buffer.
+ *
+ * The size is estimated using the following heuristic:
+ * - 6x255 For inserted strings (%.255s &c in fmt; and %s with limited
+ * length arg).
+ * - 1x255 For constant text.
+ * - 1x255 For strerror().
+ * - And the total doubled just in case.
+ */
+ char errmsg[4096];
+} emergency;
+
+/**
+ * Default fatal error handler.
+ *
+ * This handler performs all error unwinding for the current context, and
+ * terminates the program with an error exit code.
+ */
+void
+catch_fatal_error(void)
+{
+ pop_error_context(ehflag_bombout);
+ exit(2);
+}
+
+void
+print_fatal_error(const char *emsg, const void *data)
+{
+ fprintf(stderr, "%s%s:%s %s%s:%s %s\n",
+ color_get(COLOR_PROG), dpkg_get_progname(), color_reset(),
+ color_get(COLOR_ERROR), _("error"), color_reset(), emsg);
+}
+
+static void
+print_abort_error(const char *etype, const char *emsg)
+{
+ fprintf(stderr, _("%s%s%s: %s%s:%s\n %s\n"),
+ color_get(COLOR_PROG), dpkg_get_progname(), color_reset(),
+ color_get(COLOR_ERROR), etype, color_reset(), emsg);
+}
+
+static struct error_context *
+error_context_new(void)
+{
+ struct error_context *necp;
+
+ necp = malloc(sizeof(*necp));
+ if (!necp)
+ ohshite(_("out of memory for new error context"));
+ necp->next= econtext;
+ necp->cleanups= NULL;
+ necp->errmsg = NULL;
+ econtext= necp;
+
+ return necp;
+}
+
+static void
+set_error_printer(struct error_context *ec, error_printer_func *func,
+ const void *data)
+{
+ ec->printer.func = func;
+ ec->printer.data = data;
+}
+
+static void
+set_func_handler(struct error_context *ec, error_handler_func *func)
+{
+ ec->handler_type = HANDLER_TYPE_FUNC;
+ ec->handler.func = func;
+}
+
+static void
+set_jump_handler(struct error_context *ec, jmp_buf *jump)
+{
+ ec->handler_type = HANDLER_TYPE_JUMP;
+ ec->handler.jump = jump;
+}
+
+static void
+error_context_errmsg_free(struct error_context *ec)
+{
+ if (ec->errmsg != emergency.errmsg)
+ free(ec->errmsg);
+ ec->errmsg = NULL;
+}
+
+static void
+error_context_errmsg_set(struct error_context *ec, char *errmsg)
+{
+ error_context_errmsg_free(ec);
+ ec->errmsg = errmsg;
+}
+
+static int DPKG_ATTR_VPRINTF(1)
+error_context_errmsg_format(const char *fmt, va_list args)
+{
+ va_list args_copy;
+ char *errmsg = NULL;
+ int rc;
+
+ va_copy(args_copy, args);
+ rc = vasprintf(&errmsg, fmt, args_copy);
+ va_end(args_copy);
+
+ /* If the message was constructed successfully, at least we have some
+ * error message, which is better than nothing. */
+ if (rc >= 0)
+ error_context_errmsg_set(econtext, errmsg);
+
+ if (rc < 0) {
+ /* If there was any error, just use the emergency error message buffer,
+ * even if it ends up being truncated, at least we will have a big part
+ * of the problem. */
+ rc = vsnprintf(emergency.errmsg, sizeof(emergency.errmsg), fmt, args);
+
+ /* Return failure only if we get truncated. */
+ if (rc >= (int)sizeof(emergency.errmsg))
+ rc = -1;
+
+ error_context_errmsg_set(econtext, emergency.errmsg);
+ }
+
+ return rc;
+}
+
+void
+push_error_context_func(error_handler_func *handler,
+ error_printer_func *printer,
+ const void *printer_data)
+{
+ struct error_context *ec;
+
+ ec = error_context_new();
+ set_error_printer(ec, printer, printer_data);
+ set_func_handler(ec, handler);
+ onerr_abort = 0;
+}
+
+void
+push_error_context_jump(jmp_buf *jumper,
+ error_printer_func *printer,
+ const void *printer_data)
+{
+ struct error_context *ec;
+
+ ec = error_context_new();
+ set_error_printer(ec, printer, printer_data);
+ set_jump_handler(ec, jumper);
+ onerr_abort = 0;
+}
+
+void
+push_error_context(void)
+{
+ push_error_context_func(catch_fatal_error, print_fatal_error, NULL);
+}
+
+static void
+print_cleanup_error(const char *emsg, const void *data)
+{
+ print_abort_error(_("error while cleaning up"), emsg);
+}
+
+static void
+run_cleanups(struct error_context *econ, int flagsetin)
+{
+ static volatile int preventrecurse= 0;
+ struct cleanup_entry *volatile cep;
+ struct cleanup_entry *ncep;
+ struct error_context recurserr, *oldecontext;
+ jmp_buf recurse_jump;
+ volatile int i, flagset;
+
+ if (econ->printer.func)
+ econ->printer.func(econ->errmsg, econ->printer.data);
+
+ if (++preventrecurse > 3) {
+ onerr_abort++;
+ print_cleanup_error(_("too many nested errors during error recovery"), NULL);
+ flagset= 0;
+ } else {
+ flagset= flagsetin;
+ }
+ cep= econ->cleanups;
+ oldecontext= econtext;
+ while (cep) {
+ for (i=0; i<NCALLS; i++) {
+ if (cep->calls[i].call && cep->calls[i].mask & flagset) {
+ if (setjmp(recurse_jump)) {
+ run_cleanups(&recurserr, ehflag_bombout | ehflag_recursiveerror);
+ error_context_errmsg_free(&recurserr);
+ } else {
+ memset(&recurserr, 0, sizeof(recurserr));
+ set_error_printer(&recurserr, print_cleanup_error, NULL);
+ set_jump_handler(&recurserr, &recurse_jump);
+ econtext= &recurserr;
+ cep->calls[i].call(cep->argc,cep->argv);
+ }
+ econtext= oldecontext;
+ }
+ }
+ flagset &= cep->cpmask;
+ flagset |= cep->cpvalue;
+ ncep= cep->next;
+ if (cep != &emergency.ce) free(cep);
+ cep= ncep;
+ }
+ preventrecurse--;
+}
+
+/**
+ * Push an error cleanup checkpoint.
+ *
+ * This will arrange that when pop_error_context() is called, all previous
+ * cleanups will be executed with
+ * flagset = (original_flagset & mask) | value
+ * where original_flagset is the argument to pop_error_context() (as
+ * modified by any checkpoint which was pushed later).
+ */
+void push_checkpoint(int mask, int value) {
+ struct cleanup_entry *cep;
+ int i;
+
+ cep = malloc(sizeof(*cep) + sizeof(void *));
+ if (cep == NULL) {
+ onerr_abort++;
+ ohshite(_("out of memory for new cleanup entry"));
+ }
+
+ for (i=0; i<NCALLS; i++) { cep->calls[i].call=NULL; cep->calls[i].mask=0; }
+ cep->cpmask= mask; cep->cpvalue= value;
+ cep->argc= 0; cep->argv[0]= NULL;
+ cep->next= econtext->cleanups;
+ econtext->cleanups= cep;
+}
+
+static void
+cleanup_entry_new(void (*call1)(int argc, void **argv), int mask1,
+ void (*call2)(int argc, void **argv), int mask2,
+ unsigned int nargs, va_list vargs)
+{
+ struct cleanup_entry *cep;
+ void **argv;
+ int e = 0;
+ va_list args;
+
+ onerr_abort++;
+
+ cep = malloc(sizeof(*cep) + sizeof(void *) * (nargs + 1));
+ if (!cep) {
+ if (nargs > array_count(emergency.args))
+ ohshite(_("out of memory for new cleanup entry with many arguments"));
+ e= errno; cep= &emergency.ce;
+ }
+ cep->calls[0].call= call1; cep->calls[0].mask= mask1;
+ cep->calls[1].call= call2; cep->calls[1].mask= mask2;
+ cep->cpmask=~0; cep->cpvalue=0; cep->argc= nargs;
+
+ va_copy(args, vargs);
+ argv = cep->argv;
+ while (nargs-- > 0)
+ *argv++ = va_arg(args, void *);
+ *argv++ = NULL;
+ va_end(args);
+ cep->next= econtext->cleanups;
+ econtext->cleanups= cep;
+ if (cep == &emergency.ce) {
+ errno = e;
+ ohshite(_("out of memory for new cleanup entry"));
+ }
+
+ onerr_abort--;
+}
+
+void
+push_cleanup(void (*call)(int argc, void **argv), int mask,
+ unsigned int nargs, ...)
+{
+ va_list args;
+
+ va_start(args, nargs);
+ cleanup_entry_new(call, mask, NULL, 0, nargs, args);
+ va_end(args);
+}
+
+void
+push_cleanup_fallback(void (*call1)(int argc, void **argv), int mask1,
+ void (*call2)(int argc, void **argv), int mask2,
+ unsigned int nargs, ...)
+{
+ va_list args;
+
+ va_start(args, nargs);
+ cleanup_entry_new(call1, mask1, call2, mask2, nargs, args);
+ va_end(args);
+}
+
+void pop_cleanup(int flagset) {
+ struct cleanup_entry *cep;
+ int i;
+
+ cep= econtext->cleanups;
+ econtext->cleanups= cep->next;
+ for (i=0; i<NCALLS; i++) {
+ if (cep->calls[i].call && cep->calls[i].mask & flagset)
+ cep->calls[i].call(cep->argc,cep->argv);
+ }
+ if (cep != &emergency.ce) free(cep);
+}
+
+/**
+ * Unwind the current error context by running its registered cleanups.
+ */
+void
+pop_error_context(int flagset)
+{
+ struct error_context *tecp;
+
+ tecp = econtext;
+ econtext = tecp->next;
+
+ /* If we are cleaning up normally, do not print anything. */
+ if (flagset & ehflag_normaltidy)
+ set_error_printer(tecp, NULL, NULL);
+ run_cleanups(tecp, flagset);
+
+ error_context_errmsg_free(tecp);
+ free(tecp);
+}
+
+static void DPKG_ATTR_NORET
+run_error_handler(void)
+{
+ if (onerr_abort) {
+ /* We arrived here due to a fatal error from which we cannot recover,
+ * and trying to do so would most probably get us here again. That's
+ * why we will not try to do any error unwinding either. We'll just
+ * abort. Hopefully the user can fix the situation (out of disk, out
+ * of memory, etc). */
+ print_abort_error(_("unrecoverable fatal error, aborting"), econtext->errmsg);
+ error_context_errmsg_free(econtext);
+ exit(2);
+ }
+
+ if (econtext == NULL) {
+ print_abort_error(_("outside error context, aborting"),
+ _("an error occurred with no error handling in place"));
+ exit(2);
+ } else if (econtext->handler_type == HANDLER_TYPE_FUNC) {
+ econtext->handler.func();
+ internerr("error handler returned unexpectedly!");
+ } else if (econtext->handler_type == HANDLER_TYPE_JUMP) {
+ longjmp(*econtext->handler.jump, 1);
+ } else {
+ internerr("unknown error handler type %d!", econtext->handler_type);
+ }
+}
+
+void ohshit(const char *fmt, ...) {
+ va_list args;
+
+ va_start(args, fmt);
+ error_context_errmsg_format(fmt, args);
+ va_end(args);
+
+ run_error_handler();
+}
+
+void
+ohshitv(const char *fmt, va_list args)
+{
+ error_context_errmsg_format(fmt, args);
+
+ run_error_handler();
+}
+
+void ohshite(const char *fmt, ...) {
+ int e, rc;
+ va_list args;
+
+ e=errno;
+
+ /* First we construct the formatted message. */
+ va_start(args, fmt);
+ rc = error_context_errmsg_format(fmt, args);
+ va_end(args);
+
+ /* Then if there was no error we append the string for errno. Otherwise
+ * we just use the emergency error message buffer, and ignore the errno
+ * value, as we will probably have no space left anyway. */
+ if (rc > 0) {
+ char *errmsg = NULL;
+
+ rc = asprintf(&errmsg, "%s: %s", econtext->errmsg, strerror(e));
+ if (rc > 0)
+ error_context_errmsg_set(econtext, errmsg);
+ }
+
+ run_error_handler();
+}
+
+void
+do_internerr(const char *file, int line, const char *func, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ error_context_errmsg_format(fmt, args);
+ va_end(args);
+
+ fprintf(stderr, "%s%s:%s:%d:%s:%s %s%s:%s %s\n", color_get(COLOR_PROG),
+ dpkg_get_progname(), file, line, func, color_reset(),
+ color_get(COLOR_ERROR), _("internal error"), color_reset(),
+ econtext->errmsg);
+
+ error_context_errmsg_free(econtext);
+
+ abort();
+}
diff --git a/lib/dpkg/ehandle.h b/lib/dpkg/ehandle.h
new file mode 100644
index 0000000..3605904
--- /dev/null
+++ b/lib/dpkg/ehandle.h
@@ -0,0 +1,84 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * ehandle.h - error handling
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2000,2001 Wichert Akkerman <wichert@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_EHANDLE_H
+#define LIBDPKG_EHANDLE_H
+
+#include <setjmp.h>
+#include <stddef.h>
+#include <stdarg.h>
+
+#include <dpkg/macros.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup ehandle Error context handling
+ * @ingroup dpkg-public
+ * @{
+ */
+
+extern volatile int onerr_abort;
+
+enum {
+ ehflag_normaltidy = DPKG_BIT(0),
+ ehflag_bombout = DPKG_BIT(1),
+ ehflag_recursiveerror = DPKG_BIT(2),
+};
+
+typedef void error_handler_func(void);
+typedef void error_printer_func(const char *emsg, const void *data);
+
+void print_fatal_error(const char *emsg, const void *data);
+void catch_fatal_error(void);
+
+void push_error_context_jump(jmp_buf *jumper,
+ error_printer_func *printer,
+ const void *printer_data);
+void push_error_context_func(error_handler_func *handler,
+ error_printer_func *printer,
+ const void *printer_data);
+void push_error_context(void);
+void pop_error_context(int flagset);
+
+void push_cleanup_fallback(void (*f1)(int argc, void **argv), int flagmask1,
+ void (*f2)(int argc, void **argv), int flagmask2,
+ unsigned int nargs, ...);
+void push_cleanup(void (*call)(int argc, void **argv), int flagmask,
+ unsigned int nargs, ...);
+void push_checkpoint(int mask, int value);
+void pop_cleanup(int flagset);
+
+void ohshitv(const char *fmt, va_list args)
+ DPKG_ATTR_NORET DPKG_ATTR_VPRINTF(1);
+void ohshit(const char *fmt, ...) DPKG_ATTR_NORET DPKG_ATTR_PRINTF(1);
+void ohshite(const char *fmt, ...) DPKG_ATTR_NORET DPKG_ATTR_PRINTF(1);
+
+void do_internerr(const char *file, int line, const char *func,
+ const char *fmt, ...)
+ DPKG_ATTR_NORET DPKG_ATTR_PRINTF(4);
+#define internerr(...) do_internerr(__FILE__, __LINE__, __func__, __VA_ARGS__)
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_EHANDLE_H */
diff --git a/lib/dpkg/error.c b/lib/dpkg/error.c
new file mode 100644
index 0000000..42bdb43
--- /dev/null
+++ b/lib/dpkg/error.c
@@ -0,0 +1,127 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * error.c - error message reporting
+ *
+ * Copyright © 2011-2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <dpkg/dpkg.h>
+#include <dpkg/varbuf.h>
+#include <dpkg/error.h>
+
+static void DPKG_ATTR_VPRINTF(4)
+dpkg_error_set(struct dpkg_error *err, enum dpkg_msg_type type, int syserrno,
+ const char *fmt, va_list args)
+{
+ struct varbuf str = VARBUF_INIT;
+
+ if (err == NULL)
+ return;
+
+ err->type = type;
+ err->syserrno = syserrno;
+
+ varbuf_vprintf(&str, fmt, args);
+ if (syserrno)
+ varbuf_printf(&str, " (%s)", strerror(syserrno));
+
+ err->str = str.buf;
+}
+
+bool
+dpkg_has_error(struct dpkg_error *err)
+{
+ return err != NULL && err->type != DPKG_MSG_NONE;
+}
+
+int
+dpkg_put_warn(struct dpkg_error *err, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ dpkg_error_set(err, DPKG_MSG_WARN, 0, fmt, args);
+ va_end(args);
+
+ return -1;
+}
+
+int
+dpkg_put_error(struct dpkg_error *err, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ dpkg_error_set(err, DPKG_MSG_ERROR, 0, fmt, args);
+ va_end(args);
+
+ return -1;
+}
+
+int
+dpkg_put_errno(struct dpkg_error *err, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ dpkg_error_set(err, DPKG_MSG_ERROR, errno, fmt, args);
+ va_end(args);
+
+ return -1;
+}
+
+void
+dpkg_error_print(struct dpkg_error *err, const char *fmt, ...)
+{
+ va_list args;
+ char *str;
+
+ va_start(args, fmt);
+ m_vasprintf(&str, fmt, args);
+ va_end(args);
+
+ if (err->type == DPKG_MSG_WARN)
+ warning("%s: %s", str, err->str);
+ else
+ ohshit("%s: %s", str, err->str);
+
+ free(str);
+}
+
+void
+dpkg_error_move(struct dpkg_error *dst, struct dpkg_error *src)
+{
+ dst->type = src->type;
+ src->type = DPKG_MSG_NONE;
+ dst->str = src->str;
+ src->str = NULL;
+}
+
+void
+dpkg_error_destroy(struct dpkg_error *err)
+{
+ err->type = DPKG_MSG_NONE;
+ err->syserrno = 0;
+ free(err->str);
+ err->str = NULL;
+}
diff --git a/lib/dpkg/error.h b/lib/dpkg/error.h
new file mode 100644
index 0000000..9fd7ead
--- /dev/null
+++ b/lib/dpkg/error.h
@@ -0,0 +1,72 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * error.h - error message reporting
+ *
+ * Copyright © 2011-2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_ERROR_H
+#define LIBDPKG_ERROR_H
+
+#include <stdbool.h>
+
+#include <dpkg/macros.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup dpkg_error Error message reporting
+ * @ingroup dpkg-public
+ * @{
+ */
+
+enum dpkg_msg_type {
+ DPKG_MSG_NONE,
+ DPKG_MSG_WARN,
+ DPKG_MSG_ERROR,
+};
+
+struct dpkg_error {
+ enum dpkg_msg_type type;
+
+ int syserrno;
+ char *str;
+};
+
+#define DPKG_ERROR_INIT { DPKG_MSG_NONE, 0, NULL }
+
+#define DPKG_ERROR_OBJECT (struct dpkg_error)DPKG_ERROR_INIT
+
+bool dpkg_has_error(struct dpkg_error *err);
+
+int dpkg_put_warn(struct dpkg_error *err, const char *fmt, ...)
+ DPKG_ATTR_PRINTF(2);
+int dpkg_put_error(struct dpkg_error *err, const char *fmt, ...)
+ DPKG_ATTR_PRINTF(2);
+int dpkg_put_errno(struct dpkg_error *err, const char *fmt, ...)
+ DPKG_ATTR_PRINTF(2);
+
+void dpkg_error_print(struct dpkg_error *err, const char *fmt, ...)
+ DPKG_ATTR_PRINTF(2);
+
+void dpkg_error_move(struct dpkg_error *dst, struct dpkg_error *src);
+void dpkg_error_destroy(struct dpkg_error *err);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_ERROR_H */
diff --git a/lib/dpkg/fdio.c b/lib/dpkg/fdio.c
new file mode 100644
index 0000000..b50322b
--- /dev/null
+++ b/lib/dpkg/fdio.c
@@ -0,0 +1,163 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * fdio.c - safe file descriptor based input/output
+ *
+ * Copyright © 2009-2010 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <dpkg/fdio.h>
+
+ssize_t
+fd_read(int fd, void *buf, size_t len)
+{
+ ssize_t total = 0;
+ char *ptr = buf;
+
+ while (len > 0) {
+ ssize_t n;
+
+ n = read(fd, ptr + total, len);
+ if (n == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ return total ? -total : n;
+ }
+ if (n == 0)
+ break;
+
+ total += n;
+ len -= n;
+ }
+
+ return total;
+}
+
+ssize_t
+fd_write(int fd, const void *buf, size_t len)
+{
+ ssize_t total = 0;
+ const char *ptr = buf;
+
+ while (len > 0) {
+ ssize_t n;
+
+ n = write(fd, ptr + total, len);
+ if (n == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ return total ? -total : n;
+ }
+ if (n == 0)
+ break;
+
+ total += n;
+ len -= n;
+ }
+
+ return total;
+}
+
+#ifdef USE_DISK_PREALLOCATE
+#ifdef HAVE_F_PREALLOCATE
+static void
+fd_preallocate_setup(fstore_t *fs, int flags, off_t offset, off_t len)
+{
+ fs->fst_flags = flags;
+ fs->fst_posmode = F_PEOFPOSMODE;
+ fs->fst_offset = offset;
+ fs->fst_length = len;
+ fs->fst_bytesalloc = 0;
+}
+#endif
+
+/**
+ * Request the kernel to allocate the specified size for a file descriptor.
+ *
+ * We only want to send a hint that we will be using the requested size. But
+ * we do not want to unnecessarily write the file contents. That is why we
+ * are not using posix_fallocate(3) directly if possible, and not at all
+ * on glibc based systems (except on GNU/kFreeBSD).
+ */
+int
+fd_allocate_size(int fd, off_t offset, off_t len)
+{
+ int rc;
+
+ /* Do not preallocate on very small files as that degrades performance
+ * on some filesystems. */
+ if (len < (4 * 4096) - 1)
+ return 0;
+
+#if defined(HAVE_F_PREALLOCATE)
+ /* On macOS. */
+ fstore_t fs;
+
+ fd_preallocate_setup(&fs, F_ALLOCATECONTIG, offset, len);
+ rc = fcntl(fd, F_PREALLOCATE, &fs);
+ if (rc < 0 && errno == ENOSPC) {
+ /* If we cannot get a contiguous allocation, then try
+ * non-contiguous. */
+ fd_preallocate_setup(&fs, F_ALLOCATEALL, offset, len);
+ rc = fcntl(fd, F_PREALLOCATE, &fs);
+ }
+#elif defined(HAVE_F_ALLOCSP64)
+ /* On Solaris. */
+ struct flock64 fl;
+
+ fl.l_whence = SEEK_SET;
+ fl.l_start = offset;
+ fl.l_len = len;
+
+ rc = fcntl(fd, F_ALLOCSP64, &fl);
+#elif defined(HAVE_FALLOCATE)
+ /* On Linux. */
+ do {
+ rc = fallocate(fd, 0, offset, len);
+ } while (rc < 0 && errno == EINTR);
+#elif defined(HAVE_POSIX_FALLOCATE) && \
+ ((defined(__GLIBC__) && defined(__FreeBSD_kernel__)) || \
+ !defined(__GLIBC__))
+ /*
+ * On BSDs, newer GNU/kFreeBSD and other non-glibc based systems
+ * we can use posix_fallocate(2) which should be a simple syscall
+ * wrapper. But not on other glibc systems, as there the function
+ * will try to allocate the size by writing a '\0' to each block
+ * if the syscall is not implemented or not supported by the
+ * kernel or the filesystem, which we do not want.
+ */
+ rc = posix_fallocate(fd, offset, len);
+#else
+ errno = ENOSYS;
+ rc = -1;
+#endif
+
+ return rc;
+}
+#else
+int
+fd_allocate_size(int fd, off_t offset, off_t len)
+{
+ errno = ENOSYS;
+ return -1;
+}
+#endif
diff --git a/lib/dpkg/fdio.h b/lib/dpkg/fdio.h
new file mode 100644
index 0000000..d714dbd
--- /dev/null
+++ b/lib/dpkg/fdio.h
@@ -0,0 +1,46 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * fdio.h - safe file descriptor based input/output
+ *
+ * Copyright © 2009-2010 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_FDIO_H
+#define LIBDPKG_FDIO_H
+
+#include <sys/types.h>
+
+#include <dpkg/macros.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup fdio File descriptor I/O
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+ssize_t fd_read(int fd, void *buf, size_t len);
+ssize_t fd_write(int fd, const void *buf, size_t len);
+
+int
+fd_allocate_size(int fd, off_t offset, off_t len);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_FDIO_H */
diff --git a/lib/dpkg/fields.c b/lib/dpkg/fields.c
new file mode 100644
index 0000000..e62b7b5
--- /dev/null
+++ b/lib/dpkg/fields.c
@@ -0,0 +1,739 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * fields.c - parsing of all the different fields, when reading in
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2001 Wichert Akkerman
+ * Copyright © 2006-2015 Guillem Jover <guillem@debian.org>
+ * Copyright © 2009 Canonical Ltd.
+ * Copyright © 2011 Linaro Limited
+ * Copyright © 2011 Raphaël Hertzog <hertzog@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <string.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/c-ctype.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/arch.h>
+#include <dpkg/string.h>
+#include <dpkg/path.h>
+#include <dpkg/parsedump.h>
+#include <dpkg/pkg-spec.h>
+#include <dpkg/triglib.h>
+
+/**
+ * Flags to parse a name associated to a value.
+ */
+enum parse_nv_mode {
+ /** Expect no more words (default). */
+ PARSE_NV_LAST = 0,
+ /** Expect another word after the parsed name. */
+ PARSE_NV_NEXT = 1,
+ /** Do not fail if there is no name with an associated value found. */
+ PARSE_NV_FALLBACK = 2,
+};
+
+/**
+ * Parses a name and returns its associated value.
+ *
+ * Gets a pointer to the string to parse in @a strp, and modifies the pointer
+ * to the string to point to the end of the parsed text. If no value is found
+ * for the name and @a flags is set to #PARSE_NV_FALLBACK then @a strp is set
+ * to NULL and returns -1, otherwise a parse error is emitted.
+ */
+static int
+parse_nv(struct parsedb_state *ps, enum parse_nv_mode parse_mode,
+ const char **strp, const struct namevalue *nv_head)
+{
+ const char *str_start = *strp, *str_end;
+ const struct namevalue *nv;
+ int value;
+
+ dpkg_error_destroy(&ps->err);
+
+ if (str_start[0] == '\0')
+ return dpkg_put_error(&ps->err, _("is missing a value"));
+
+ nv = namevalue_find_by_name(nv_head, str_start);
+ if (nv == NULL) {
+ /* We got no match, skip further string validation. */
+ if (parse_mode != PARSE_NV_FALLBACK)
+ return dpkg_put_error(&ps->err, _("has invalid value '%.50s'"), str_start);
+
+ str_end = NULL;
+ value = -1;
+ } else {
+ str_end = str_start + nv->length;
+ while (c_isspace(str_end[0]))
+ str_end++;
+ value = nv->value;
+
+ if (parse_mode != PARSE_NV_NEXT && str_is_set(str_end))
+ return dpkg_put_error(&ps->err, _("has trailing junk"));
+ }
+
+ *strp = str_end;
+
+ return value;
+}
+
+void
+f_name(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ struct parsedb_state *ps,
+ const char *value, const struct fieldinfo *fip)
+{
+ const char *e;
+
+ e = pkg_name_is_illegal(value);
+ if (e != NULL)
+ parse_error(ps, _("invalid package name in '%s' field: %s"), fip->name, e);
+ /* We use the new name, as pkg_hash_find_set() may have done a tolower for us. */
+ pkg->set->name = pkg_hash_find_set(value)->name;
+}
+
+void
+f_archives(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ struct parsedb_state *ps,
+ const char *value, const struct fieldinfo *fip)
+{
+ struct archivedetails *fdp, **fdpp;
+ char *cpos, *space;
+ int allowextend;
+
+ if (!*value)
+ parse_error(ps, _("empty archive details '%s' field"), fip->name);
+ if (!(ps->flags & pdb_recordavailable))
+ parse_error(ps,
+ _("archive details '%s' field not allowed in status file"),
+ fip->name);
+ allowextend = !pkg->archives;
+ fdpp = &pkg->archives;
+ cpos= nfstrsave(value);
+ while (*cpos) {
+ space = cpos;
+ while (*space && !c_isspace(*space))
+ space++;
+ if (*space)
+ *space++ = '\0';
+ fdp= *fdpp;
+ if (!fdp) {
+ if (!allowextend)
+ parse_error(ps,
+ _("too many values in archive details '%s' field "
+ "(compared to others)"), fip->name);
+ fdp = nfmalloc(sizeof(*fdp));
+ fdp->next= NULL;
+ fdp->name= fdp->msdosname= fdp->size= fdp->md5sum= NULL;
+ *fdpp= fdp;
+ }
+ STRUCTFIELD(fdp, fip->integer, const char *) = cpos;
+ fdpp= &fdp->next;
+ while (*space && c_isspace(*space))
+ space++;
+ cpos= space;
+ }
+ if (*fdpp)
+ parse_error(ps,
+ _("too few values in archive details '%s' field "
+ "(compared to others)"), fip->name);
+}
+
+void
+f_charfield(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ struct parsedb_state *ps,
+ const char *value, const struct fieldinfo *fip)
+{
+ if (*value)
+ STRUCTFIELD(pkgbin, fip->integer, char *) = nfstrsave(value);
+}
+
+void
+f_boolean(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ struct parsedb_state *ps,
+ const char *value, const struct fieldinfo *fip)
+{
+ bool boolean;
+
+ if (!*value)
+ return;
+
+ boolean = parse_nv(ps, PARSE_NV_LAST, &value, booleaninfos);
+ if (dpkg_has_error(&ps->err))
+ parse_error(ps, _("boolean (yes/no) '%s' field: %s"),
+ fip->name, ps->err.str);
+
+ STRUCTFIELD(pkgbin, fip->integer, bool) = boolean;
+}
+
+void
+f_multiarch(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ struct parsedb_state *ps,
+ const char *value, const struct fieldinfo *fip)
+{
+ int multiarch;
+
+ if (!*value)
+ return;
+
+ multiarch = parse_nv(ps, PARSE_NV_LAST, &value, multiarchinfos);
+ if (dpkg_has_error(&ps->err))
+ parse_error(ps, _("quadstate (foreign/allowed/same/no) '%s' field: %s"),
+ fip->name, ps->err.str);
+ STRUCTFIELD(pkgbin, fip->integer, int) = multiarch;
+}
+
+void
+f_architecture(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ struct parsedb_state *ps,
+ const char *value, const struct fieldinfo *fip)
+{
+ pkgbin->arch = dpkg_arch_find(value);
+ if (pkgbin->arch->type == DPKG_ARCH_ILLEGAL)
+ parse_warn(ps, _("'%s' is not a valid architecture name in '%s' field: %s"),
+ value, fip->name, dpkg_arch_name_is_illegal(value));
+}
+
+void
+f_section(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ struct parsedb_state *ps,
+ const char *value, const struct fieldinfo *fip)
+{
+ if (!*value) return;
+ pkg->section = nfstrsave(value);
+}
+
+void
+f_priority(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ struct parsedb_state *ps,
+ const char *value, const struct fieldinfo *fip)
+{
+ const char *str = value;
+ int priority;
+
+ if (!*value) return;
+
+ priority = parse_nv(ps, PARSE_NV_FALLBACK, &str, priorityinfos);
+ if (dpkg_has_error(&ps->err))
+ parse_error(ps, _("word in '%s' field: %s"), fip->name, ps->err.str);
+
+ if (str == NULL) {
+ pkg->priority = PKG_PRIO_OTHER;
+ pkg->otherpriority = nfstrsave(value);
+ } else {
+ pkg->priority = priority;
+ }
+}
+
+void
+f_obs_class(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ struct parsedb_state *ps,
+ const char *value, const struct fieldinfo *fip)
+{
+ parse_warn(ps, _("obsolete '%s' field used"), fip->name);
+ f_priority(pkg, pkgbin, ps, value, fip);
+}
+
+void
+f_status(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ struct parsedb_state *ps,
+ const char *value, const struct fieldinfo *fip)
+{
+ if (ps->flags & pdb_rejectstatus)
+ parse_error(ps,
+ _("value for '%s' field not allowed in this context"),
+ fip->name);
+ if (ps->flags & pdb_recordavailable)
+ return;
+
+ pkg->want = parse_nv(ps, PARSE_NV_NEXT, &value, wantinfos);
+ if (dpkg_has_error(&ps->err))
+ parse_error(ps, _("first (want) word in '%s' field: %s"),
+ fip->name, ps->err.str);
+ pkg->eflag = parse_nv(ps, PARSE_NV_NEXT, &value, eflaginfos);
+ if (dpkg_has_error(&ps->err))
+ parse_error(ps, _("second (error) word in '%s' field: %s"),
+ fip->name, ps->err.str);
+ pkg->status = parse_nv(ps, PARSE_NV_LAST, &value, statusinfos);
+ if (dpkg_has_error(&ps->err))
+ parse_error(ps, _("third (status) word in '%s' field: %s"),
+ fip->name, ps->err.str);
+}
+
+void
+f_version(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ struct parsedb_state *ps,
+ const char *value, const struct fieldinfo *fip)
+{
+ if (parse_db_version(ps, &pkgbin->version, value) < 0)
+ parse_problem(ps, _("'%s' field value '%.250s'"), fip->name, value);
+}
+
+void
+f_obs_revision(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ struct parsedb_state *ps,
+ const char *value, const struct fieldinfo *fip)
+{
+ char *newversion;
+
+ parse_warn(ps, _("obsolete '%s' field used"), fip->name);
+ if (!*value) return;
+ if (str_is_set(pkgbin->version.revision)) {
+ newversion = nfmalloc(strlen(pkgbin->version.version) +
+ strlen(pkgbin->version.revision) + 2);
+ sprintf(newversion, "%s-%s", pkgbin->version.version,
+ pkgbin->version.revision);
+ pkgbin->version.version = newversion;
+ }
+ pkgbin->version.revision = nfstrsave(value);
+}
+
+void
+f_configversion(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ struct parsedb_state *ps,
+ const char *value, const struct fieldinfo *fip)
+{
+ if (ps->flags & pdb_rejectstatus)
+ parse_error(ps,
+ _("value for '%s' field not allowed in this context"),
+ fip->name);
+ if (ps->flags & pdb_recordavailable)
+ return;
+
+ if (parse_db_version(ps, &pkg->configversion, value) < 0)
+ parse_problem(ps, _("'%s' field value '%.250s'"), fip->name, value);
+}
+
+/*
+ * The code in f_conffiles ensures that value[-1] == ' ', which is helpful.
+ */
+static void conffvalue_lastword(const char *value, const char *from,
+ const char *endent,
+ const char **word_start_r, int *word_len_r,
+ const char **new_from_r,
+ struct parsedb_state *ps)
+{
+ const char *lastspc;
+
+ if (from <= value+1) goto malformed;
+ for (lastspc= from-1; *lastspc != ' '; lastspc--);
+ if (lastspc <= value+1 || lastspc >= endent-1) goto malformed;
+
+ *new_from_r= lastspc;
+ *word_start_r= lastspc + 1;
+ *word_len_r= (int)(from - *word_start_r);
+ return;
+
+malformed:
+ parse_error(ps,
+ _("value for '%s' field has malformed line '%.*s'"),
+ "Conffiles", (int)min(endent - value, 250), value);
+}
+
+void
+f_conffiles(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ struct parsedb_state *ps,
+ const char *value, const struct fieldinfo *fip)
+{
+ static const char obsolete_str[]= "obsolete";
+ static const char remove_on_upgrade_str[] = "remove-on-upgrade";
+ struct conffile **lastp;
+
+ lastp = &pkgbin->conffiles;
+ while (*value) {
+ struct conffile *newlink;
+ const char *endent, *endfn, *hashstart;
+ char *newptr;
+ int c, namelen, hashlen;
+ bool obsolete, remove_on_upgrade;
+
+ c= *value++;
+ if (c == '\n') continue;
+ if (c != ' ')
+ parse_error(ps,
+ _("value for '%s' field has line starting with non-space '%c'"),
+ fip->name, c);
+ for (endent = value; (c = *endent) != '\0' && c != '\n'; endent++) ;
+ conffvalue_lastword(value, endent, endent,
+ &hashstart, &hashlen, &endfn,
+ ps);
+ remove_on_upgrade = (hashlen == sizeof(remove_on_upgrade_str) - 1 &&
+ memcmp(hashstart, remove_on_upgrade_str, hashlen) == 0);
+ if (remove_on_upgrade)
+ conffvalue_lastword(value, endfn, endent, &hashstart, &hashlen, &endfn,
+ ps);
+
+ obsolete= (hashlen == sizeof(obsolete_str)-1 &&
+ memcmp(hashstart, obsolete_str, hashlen) == 0);
+ if (obsolete)
+ conffvalue_lastword(value, endfn, endent,
+ &hashstart, &hashlen, &endfn,
+ ps);
+ newlink = nfmalloc(sizeof(*newlink));
+ value = path_skip_slash_dotslash(value);
+ namelen= (int)(endfn-value);
+ if (namelen <= 0)
+ parse_error(ps,
+ _("root or empty directory listed as a conffile in '%s' field"),
+ fip->name);
+ newptr = nfmalloc(namelen+2);
+ newptr[0]= '/';
+ memcpy(newptr+1,value,namelen);
+ newptr[namelen+1] = '\0';
+ newlink->name= newptr;
+ newptr= nfmalloc(hashlen+1);
+ memcpy(newptr, hashstart, hashlen);
+ newptr[hashlen] = '\0';
+ newlink->hash= newptr;
+ newlink->obsolete= obsolete;
+ newlink->remove_on_upgrade = remove_on_upgrade;
+ newlink->next =NULL;
+ *lastp= newlink;
+ lastp= &newlink->next;
+ value= endent;
+ }
+}
+
+void
+f_dependency(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ struct parsedb_state *ps,
+ const char *value, const struct fieldinfo *fip)
+{
+ const char *p, *emsg;
+ static struct varbuf depname, version;
+
+ struct dependency **ldypp;
+
+ /* Empty fields are ignored. */
+ if (!*value)
+ return;
+ p= value;
+
+ ldypp = &pkgbin->depends;
+ while (*ldypp)
+ ldypp = &(*ldypp)->next;
+
+ /* Loop creating new struct dependency's. */
+ for (;;) {
+ struct deppossi **ldopp;
+ struct dependency *dyp;
+
+ dyp = nfmalloc(sizeof(*dyp));
+ /* Set this to NULL for now, as we don't know what our real
+ * struct pkginfo address (in the database) is going to be yet. */
+ dyp->up = NULL;
+ dyp->next= NULL; *ldypp= dyp; ldypp= &dyp->next;
+ dyp->list= NULL; ldopp= &dyp->list;
+ dyp->type= fip->integer;
+
+ /* Loop creating new struct deppossi's. */
+ for (;;) {
+ const char *depnamestart;
+ int depnamelength;
+ struct deppossi *dop;
+
+ depnamestart= p;
+ /* Skip over package name characters. */
+ while (*p && !c_isspace(*p) && *p != ':' && *p != '(' && *p != ',' &&
+ *p != '|')
+ p++;
+ depnamelength= p - depnamestart ;
+ if (depnamelength == 0)
+ parse_error(ps,
+ _("'%s' field, missing package name, or garbage where "
+ "package name expected"), fip->name);
+
+ varbuf_set_buf(&depname, depnamestart, depnamelength);
+
+ emsg = pkg_name_is_illegal(depname.buf);
+ if (emsg)
+ parse_error(ps,
+ _("'%s' field, invalid package name '%.255s': %s"),
+ fip->name, depname.buf, emsg);
+ dop = nfmalloc(sizeof(*dop));
+ dop->up= dyp;
+ dop->ed = pkg_hash_find_set(depname.buf);
+ dop->next= NULL; *ldopp= dop; ldopp= &dop->next;
+
+ /* Don't link this (which is after all only ‘new_pkg’ from
+ * the main parsing loop in parsedb) into the depended on
+ * packages' lists yet. This will be done later when we
+ * install this (in parse.c). For the moment we do the
+ * ‘forward’ links in deppossi (‘ed’) only, and the ‘backward’
+ * links from the depended on packages to dop are left undone. */
+ dop->rev_next = NULL;
+ dop->rev_prev = NULL;
+
+ dop->cyclebreak = false;
+
+ /* See if we have an architecture qualifier. */
+ if (*p == ':') {
+ static struct varbuf arch;
+ const char *archstart;
+ int archlength;
+
+ archstart = ++p;
+ while (*p && !c_isspace(*p) && *p != '(' && *p != ',' && *p != '|')
+ p++;
+ archlength = p - archstart;
+ if (archlength == 0)
+ parse_error(ps, _("'%s' field, missing architecture name, or garbage "
+ "where architecture name expected"), fip->name);
+
+ varbuf_set_buf(&arch, archstart, archlength);
+
+ dop->arch_is_implicit = false;
+ dop->arch = dpkg_arch_find(arch.buf);
+
+ if (dop->arch->type == DPKG_ARCH_ILLEGAL)
+ emsg = dpkg_arch_name_is_illegal(arch.buf);
+ if (emsg)
+ parse_error(ps, _("'%s' field, reference to '%.255s': "
+ "invalid architecture name '%.255s': %s"),
+ fip->name, depname.buf, arch.buf, emsg);
+ } else if (fip->integer == dep_conflicts || fip->integer == dep_breaks ||
+ fip->integer == dep_replaces) {
+ /* Conflicts/Breaks/Replaces get an implicit "any" arch qualifier. */
+ dop->arch_is_implicit = true;
+ dop->arch = dpkg_arch_get(DPKG_ARCH_WILDCARD);
+ } else {
+ /* Otherwise use the pkgbin architecture, which will be assigned to
+ * later on by parse.c, once we can guarantee we have parsed it from
+ * the control stanza. */
+ dop->arch_is_implicit = true;
+ dop->arch = NULL;
+ }
+
+ /* Skip whitespace after package name. */
+ while (c_isspace(*p))
+ p++;
+
+ /* See if we have a versioned relation. */
+ if (*p == '(') {
+ char c1;
+ const char *versionstart;
+ int versionlength;
+
+ p++;
+ while (c_isspace(*p))
+ p++;
+ c1= *p;
+ if (c1 == '<' || c1 == '>') {
+ char c2;
+
+ c2= *++p;
+ dop->verrel = (c1 == '<') ? DPKG_RELATION_LT : DPKG_RELATION_GT;
+ if (c2 == '=') {
+ dop->verrel |= DPKG_RELATION_EQ;
+ p++;
+ } else if (c2 == c1) {
+ /* Either ‘<<’ or ‘>>’. */
+ p++;
+ } else if (c2 == '<' || c2 == '>') {
+ parse_error(ps,
+ _("'%s' field, reference to '%.255s':\n"
+ " bad version relationship %c%c"),
+ fip->name, depname.buf, c1, c2);
+ } else {
+ parse_warn(ps,
+ _("'%s' field, reference to '%.255s':\n"
+ " '%c' is obsolete, use '%c=' or '%c%c' instead"),
+ fip->name, depname.buf, c1, c1, c1, c1);
+ dop->verrel |= DPKG_RELATION_EQ;
+ }
+ } else if (c1 == '=') {
+ dop->verrel = DPKG_RELATION_EQ;
+ p++;
+ } else {
+ parse_warn(ps,
+ _("'%s' field, reference to '%.255s':\n"
+ " implicit exact match on version number, "
+ "suggest using '=' instead"),
+ fip->name, depname.buf);
+ dop->verrel = DPKG_RELATION_EQ;
+ }
+ if ((dop->verrel != DPKG_RELATION_EQ) && (fip->integer == dep_provides))
+ parse_warn(ps,
+ _("only exact versions may be used for '%s' field"),
+ fip->name);
+
+ if (!c_isspace(*p) && !c_isalnum(*p)) {
+ parse_warn(ps,
+ _("'%s' field, reference to '%.255s':\n"
+ " version value starts with non-alphanumeric, "
+ "suggest adding a space"),
+ fip->name, depname.buf);
+ }
+ /* Skip spaces between the relation and the version. */
+ while (c_isspace(*p))
+ p++;
+
+ versionstart= p;
+ while (*p && *p != ')' && *p != '(') {
+ if (c_isspace(*p))
+ break;
+ p++;
+ }
+ versionlength= p - versionstart;
+ while (c_isspace(*p))
+ p++;
+ if (*p == '\0')
+ parse_error(ps,
+ _("'%s' field, reference to '%.255s': "
+ "version unterminated"), fip->name, depname.buf);
+ else if (*p != ')')
+ parse_error(ps,
+ _("'%s' field, reference to '%.255s': "
+ "version contains '%c' instead of '%c'"),
+ fip->name, depname.buf, *p, ')');
+ varbuf_set_buf(&version, versionstart, versionlength);
+ if (parse_db_version(ps, &dop->version, version.buf) < 0)
+ parse_problem(ps,
+ _("'%s' field, reference to '%.255s': version '%s'"),
+ fip->name, depname.buf, version.buf);
+ p++;
+ while (c_isspace(*p))
+ p++;
+ } else {
+ dop->verrel = DPKG_RELATION_NONE;
+ dpkg_version_blank(&dop->version);
+ }
+ if (!*p || *p == ',') break;
+ if (*p != '|')
+ parse_error(ps,
+ _("'%s' field, syntax error after reference to package '%.255s'"),
+ fip->name, dop->ed->name);
+ if (fip->integer == dep_conflicts ||
+ fip->integer == dep_breaks ||
+ fip->integer == dep_provides ||
+ fip->integer == dep_replaces)
+ parse_error(ps, _("alternatives ('|') not allowed in '%s' field"),
+ fip->name);
+ p++;
+ while (c_isspace(*p))
+ p++;
+ }
+ if (!*p) break;
+ p++;
+ while (c_isspace(*p))
+ p++;
+ }
+}
+
+void
+f_obs_dependency(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ struct parsedb_state *ps,
+ const char *value, const struct fieldinfo *fip)
+{
+ parse_warn(ps, _("obsolete '%s' field used"), fip->name);
+ f_dependency(pkg, pkgbin, ps, value, fip);
+}
+
+static const char *
+scan_word(const char **valp)
+{
+ static struct varbuf word;
+ const char *p, *start, *end;
+
+ p = *valp;
+ for (;;) {
+ if (!*p) {
+ *valp = p;
+ return NULL;
+ }
+ if (c_iswhite(*p)) {
+ p++;
+ continue;
+ }
+ start = p;
+ break;
+ }
+ for (;;) {
+ if (*p && !c_iswhite(*p)) {
+ p++;
+ continue;
+ }
+ end = p;
+ break;
+ }
+
+ varbuf_set_buf(&word, start, end - start);
+
+ *valp = p;
+
+ return word.buf;
+}
+
+void
+f_trigpend(struct pkginfo *pend, struct pkgbin *pkgbin,
+ struct parsedb_state *ps,
+ const char *value, const struct fieldinfo *fip)
+{
+ const char *word;
+
+ if (ps->flags & pdb_rejectstatus)
+ parse_error(ps,
+ _("value for '%s' field not allowed in this context"),
+ fip->name);
+
+ while ((word = scan_word(&value))) {
+ const char *emsg;
+
+ emsg = trig_name_is_illegal(word);
+ if (emsg)
+ parse_error(ps,
+ _("illegal pending trigger name '%.255s': %s"), word, emsg);
+
+ if (!trig_note_pend_core(pend, nfstrsave(word)))
+ parse_error(ps,
+ _("duplicate pending trigger '%.255s'"), word);
+ }
+}
+
+void
+f_trigaw(struct pkginfo *aw, struct pkgbin *pkgbin,
+ struct parsedb_state *ps,
+ const char *value, const struct fieldinfo *fip)
+{
+ const char *word;
+
+ if (ps->flags & pdb_rejectstatus)
+ parse_error(ps,
+ _("value for '%s' field not allowed in this context"),
+ fip->name);
+
+ while ((word = scan_word(&value))) {
+ struct pkginfo *pend;
+ struct dpkg_error err;
+
+ pend = pkg_spec_parse_pkg(word, &err);
+ if (pend == NULL)
+ parse_error(ps,
+ _("illegal package name in awaited trigger '%.255s': %s"),
+ word, err.str);
+
+ if (!trig_note_aw(pend, aw))
+ parse_error(ps,
+ _("duplicate awaited trigger package '%.255s'"), word);
+
+ trig_awaited_pend_enqueue(pend);
+ }
+}
diff --git a/lib/dpkg/file.c b/lib/dpkg/file.c
new file mode 100644
index 0000000..4d02520
--- /dev/null
+++ b/lib/dpkg/file.c
@@ -0,0 +1,257 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * file.c - file handling functions
+ *
+ * Copyright © 1994, 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008-2012 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <dpkg/dpkg.h>
+#include <dpkg/i18n.h>
+#include <dpkg/pager.h>
+#include <dpkg/fdio.h>
+#include <dpkg/buffer.h>
+#include <dpkg/file.h>
+
+/**
+ * Check whether a filename is executable.
+ *
+ * @param filename The filename to check.
+ */
+bool
+file_is_exec(const char *filename)
+{
+ struct stat st;
+
+ if (stat(filename, &st) < 0)
+ return false;
+
+ if (!S_ISREG(st.st_mode))
+ return false;
+
+ return st.st_mode & 0111;
+}
+
+/**
+ * Copy file ownership and permissions from one file to another.
+ *
+ * @param src The source filename.
+ * @param dst The destination filename.
+ */
+void
+file_copy_perms(const char *src, const char *dst)
+{
+ struct stat stab;
+
+ if (stat(src, &stab) == -1) {
+ if (errno == ENOENT)
+ return;
+ ohshite(_("unable to stat source file '%.250s'"), src);
+ }
+
+ if (chown(dst, stab.st_uid, stab.st_gid) == -1)
+ ohshite(_("unable to change ownership of target file '%.250s'"),
+ dst);
+
+ if (chmod(dst, (stab.st_mode & ~S_IFMT)) == -1)
+ ohshite(_("unable to set mode of target file '%.250s'"), dst);
+}
+
+static int
+file_slurp_fd(int fd, const char *filename, struct varbuf *vb,
+ struct dpkg_error *err)
+{
+ struct stat st;
+
+ if (fstat(fd, &st) < 0)
+ return dpkg_put_errno(err, _("cannot stat %s"), filename);
+
+ if (!S_ISREG(st.st_mode))
+ return dpkg_put_error(err, _("%s is not a regular file"),
+ filename);
+
+ if (st.st_size == 0)
+ return 0;
+
+ varbuf_init(vb, st.st_size);
+ if (fd_read(fd, vb->buf, st.st_size) < 0)
+ return dpkg_put_errno(err, _("cannot read %s"), filename);
+ vb->used = st.st_size;
+
+ return 0;
+}
+
+int
+file_slurp(const char *filename, struct varbuf *vb, struct dpkg_error *err)
+{
+ int fd;
+ int rc;
+
+ varbuf_init(vb, 0);
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ return dpkg_put_errno(err, _("cannot open %s"), filename);
+
+ rc = file_slurp_fd(fd, filename, vb, err);
+
+ (void)close(fd);
+
+ return rc;
+}
+
+static void
+file_lock_setup(struct flock *fl, short type)
+{
+ fl->l_type = type;
+ fl->l_whence = SEEK_SET;
+ fl->l_start = 0;
+ fl->l_len = 0;
+ fl->l_pid = 0;
+}
+
+/**
+ * Unlock a previously locked file.
+ */
+void
+file_unlock(int lockfd, const char *lockfile, const char *lockdesc)
+{
+ struct flock fl;
+
+ if (lockfd < 0)
+ internerr("%s (%s) fd is %d < 0", lockdesc, lockfile, lockfd);
+
+ file_lock_setup(&fl, F_UNLCK);
+
+ if (fcntl(lockfd, F_SETLK, &fl) == -1)
+ ohshite(_("unable to unlock %s"), lockdesc);
+}
+
+static void
+file_unlock_cleanup(int argc, void **argv)
+{
+ int lockfd = *(int *)argv[0];
+ const char *lockfile = argv[1];
+ const char *lockdesc = argv[2];
+
+ file_unlock(lockfd, lockfile, lockdesc);
+}
+
+/**
+ * Check if a file has a lock acquired.
+ *
+ * @param lockfd The file descriptor for the lock.
+ * @param filename The file name associated to the file descriptor.
+ */
+bool
+file_is_locked(int lockfd, const char *filename)
+{
+ struct flock fl;
+
+ file_lock_setup(&fl, F_WRLCK);
+
+ if (fcntl(lockfd, F_GETLK, &fl) == -1)
+ ohshit(_("unable to check file '%s' lock status"), filename);
+
+ if (fl.l_type == F_WRLCK && fl.l_pid != getpid())
+ return true;
+ else
+ return false;
+}
+
+/**
+ * Lock a file.
+ *
+ * @param lockfd The pointer to the lock file descriptor. It must be allocated
+ * statically as its addresses is passed to a cleanup handler.
+ * @param flags The lock flags specifying what type of locking to perform.
+ * @param filename The name of the file to lock.
+ * @param desc The description of the file to lock.
+ */
+void
+file_lock(int *lockfd, enum file_lock_flags flags, const char *filename,
+ const char *desc)
+{
+ struct flock fl;
+ int lock_cmd;
+
+ setcloexec(*lockfd, filename);
+
+ file_lock_setup(&fl, F_WRLCK);
+
+ if (flags == FILE_LOCK_WAIT)
+ lock_cmd = F_SETLKW;
+ else
+ lock_cmd = F_SETLK;
+
+ if (fcntl(*lockfd, lock_cmd, &fl) == -1) {
+ const char *warnmsg;
+
+ if (errno != EACCES && errno != EAGAIN)
+ ohshite(_("unable to lock %s"), desc);
+
+ warnmsg = _("Note: removing the lock file is always wrong, "
+ "can damage the locked area\n"
+ "and the entire system. "
+ "See <https://wiki.debian.org/Teams/Dpkg/FAQ#db-lock>.");
+
+ file_lock_setup(&fl, F_WRLCK);
+ if (fcntl(*lockfd, F_GETLK, &fl) == -1)
+ ohshit(_("%s was locked by another process\n%s"),
+ desc, warnmsg);
+
+ ohshit(_("%s was locked by another process with pid %d\n%s"),
+ desc, fl.l_pid, warnmsg);
+ }
+
+ push_cleanup(file_unlock_cleanup, ~0, 3, lockfd, filename, desc);
+}
+
+void
+file_show(const char *filename)
+{
+ struct pager *pager;
+ struct dpkg_error err;
+ int fd, rc;
+
+ if (filename == NULL)
+ internerr("filename is NULL");
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ ohshite(_("cannot open file %s"), filename);
+
+ pager = pager_spawn(_("pager to show file"));
+ rc = fd_fd_copy(fd, STDOUT_FILENO, -1, &err);
+ pager_reap(pager);
+
+ close(fd);
+
+ if (rc < 0 && err.syserrno != EPIPE) {
+ errno = err.syserrno;
+ ohshite(_("cannot write file %s into the pager"), filename);
+ }
+}
diff --git a/lib/dpkg/file.h b/lib/dpkg/file.h
new file mode 100644
index 0000000..0136f69
--- /dev/null
+++ b/lib/dpkg/file.h
@@ -0,0 +1,73 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * file.h - file handling routines
+ *
+ * Copyright © 2008-2010 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_FILE_H
+#define LIBDPKG_FILE_H
+
+#include <sys/types.h>
+
+#include <stdbool.h>
+
+#include <dpkg/macros.h>
+#include <dpkg/error.h>
+#include <dpkg/varbuf.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup file File handling
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+struct file_stat {
+ uid_t uid;
+ gid_t gid;
+ mode_t mode;
+
+ /* Used by dpkg-statoverride when parsing the database to preserve the
+ * user and group names in case the system does not know about them. */
+ char *uname;
+ char *gname;
+};
+
+bool file_is_exec(const char *filename);
+
+void file_copy_perms(const char *src, const char *dst);
+
+int
+file_slurp(const char *filename, struct varbuf *vb, struct dpkg_error *err);
+
+enum file_lock_flags {
+ FILE_LOCK_NOWAIT,
+ FILE_LOCK_WAIT,
+};
+
+bool file_is_locked(int lockfd, const char *filename);
+void file_lock(int *lockfd, enum file_lock_flags flags, const char *filename,
+ const char *filedesc);
+void file_unlock(int fd, const char *filename, const char *filedesc);
+void file_show(const char *filename);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_FILE_H */
diff --git a/lib/dpkg/fsys-dir.c b/lib/dpkg/fsys-dir.c
new file mode 100644
index 0000000..76d5f39
--- /dev/null
+++ b/lib/dpkg/fsys-dir.c
@@ -0,0 +1,119 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * fsys-dir.c - filesystem root directory functions
+ *
+ * Copyright © 2011, 2018 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <stdlib.h>
+
+#include <dpkg/dpkg.h>
+#include <dpkg/string.h>
+#include <dpkg/path.h>
+#include <dpkg/fsys.h>
+
+static char *fsys_dir;
+
+/**
+ * Allocate the default on-disk filesystem root directory.
+ *
+ * The directory defaults to the value from environment variable
+ * DPKG_ROOT, and if not set the built-in default "".
+ *
+ * @return The filesystem root directory.
+ */
+static char *
+dpkg_fsys_new_dir(void)
+{
+ const char *env;
+ char *dir;
+
+ env = getenv("DPKG_ROOT");
+ if (env == NULL) {
+ dir = m_strdup("");
+ } else {
+ dir = m_strdup(env);
+ path_trim_slash_slashdot(dir);
+ }
+
+ return dir;
+}
+
+/**
+ * Set current on-disk filesystem root directory.
+ *
+ * This function can be used to set the directory to a new value, or to
+ * reset it to a default value if dir is NULL.
+ *
+ * @param dir The new filesystem root directory, or NULL to set to default.
+ *
+ * @return The new filesystem root directory.
+ */
+const char *
+dpkg_fsys_set_dir(const char *dir)
+{
+ char *dir_new;
+
+ if (dir == NULL) {
+ dir_new = dpkg_fsys_new_dir();
+ } else {
+ dir_new = m_strdup(dir);
+ path_trim_slash_slashdot(dir_new);
+ }
+
+ free(fsys_dir);
+ fsys_dir = dir_new;
+
+ return fsys_dir;
+}
+
+/**
+ * Get current on-disk filesystem root directory.
+ *
+ * This function will take care of initializing the directory if it has not
+ * been initialized before.
+ *
+ * @return The current filesystem root directory.
+ */
+const char *
+dpkg_fsys_get_dir(void)
+{
+ if (fsys_dir == NULL)
+ fsys_dir = dpkg_fsys_new_dir();
+
+ return fsys_dir;
+}
+
+/**
+ * Get a pathname to the current on-disk filesystem root directory.
+ *
+ * This function returns an allocated string, which should be freed with
+ * free(2).
+ *
+ * @param pathpart The pathpart to append to the new pathname.
+ *
+ * @return The newly allocated pathname.
+ */
+char *
+dpkg_fsys_get_path(const char *pathpart)
+{
+ pathpart = path_skip_slash_dotslash(pathpart);
+
+ return str_fmt("%s/%s", dpkg_fsys_get_dir(), pathpart);
+}
diff --git a/lib/dpkg/fsys-hash.c b/lib/dpkg/fsys-hash.c
new file mode 100644
index 0000000..42cb956
--- /dev/null
+++ b/lib/dpkg/fsys-hash.c
@@ -0,0 +1,196 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * fsys-hash.c - filesystem nodes hash table
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2000, 2001 Wichert Akkerman <wakkerma@debian.org>
+ * Copyright © 2008-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/string.h>
+#include <dpkg/path.h>
+
+#include "fsys.h"
+
+/* This must always be a prime for optimal performance.
+ * This is the closest one to 2^18 (262144). */
+#define BINS 262139
+
+static struct fsys_namenode *bins[BINS];
+static int nfiles = 0;
+
+void
+fsys_hash_init(void)
+{
+ struct fsys_namenode *fnn;
+ int i;
+
+ for (i = 0; i < BINS; i++) {
+ for (fnn = bins[i]; fnn; fnn = fnn->next) {
+ fnn->flags = 0;
+ fnn->oldhash = NULL;
+ fnn->newhash = NULL;
+ fnn->file_ondisk_id = NULL;
+ }
+ }
+}
+
+void
+fsys_hash_reset(void)
+{
+ memset(bins, 0, sizeof(bins));
+ nfiles = 0;
+}
+
+int
+fsys_hash_entries(void)
+{
+ return nfiles;
+}
+
+struct fsys_namenode *
+fsys_hash_find_node(const char *name, enum fsys_hash_find_flags flags)
+{
+ struct fsys_namenode **pointerp, *newnode;
+ const char *orig_name = name;
+
+ /* We skip initial slashes and ‘./’ pairs, and add our own single
+ * leading slash. */
+ name = path_skip_slash_dotslash(name);
+
+ pointerp = bins + (str_fnv_hash(name) % (BINS));
+ while (*pointerp) {
+ /* XXX: This should not be needed, but it has been a constant
+ * source of assertions over the years. Hopefully with the
+ * internerr() we will get better diagnostics. */
+ if ((*pointerp)->name[0] != '/')
+ internerr("filename node '%s' does not start with '/'",
+ (*pointerp)->name);
+
+ if (strcmp((*pointerp)->name + 1, name) == 0)
+ return *pointerp;
+ pointerp = &(*pointerp)->next;
+ }
+
+ if (flags & FHFF_NO_NEW)
+ return NULL;
+
+ newnode = nfmalloc(sizeof(*newnode));
+ memset(newnode, 0, sizeof(*newnode));
+ if ((flags & FHFF_NO_COPY) && name > orig_name && name[-1] == '/') {
+ newnode->name = name - 1;
+ } else {
+ char *newname = nfmalloc(strlen(name) + 2);
+
+ newname[0] = '/';
+ strcpy(newname + 1, name);
+ newnode->name = newname;
+ }
+ *pointerp = newnode;
+ nfiles++;
+
+ return newnode;
+}
+
+void
+fsys_hash_report(FILE *file)
+{
+ struct fsys_namenode *node;
+ int i, c;
+ int *freq;
+ int empty = 0, used = 0, collided = 0;
+
+ freq = m_malloc(sizeof(freq[0]) * nfiles + 1);
+ for (i = 0; i <= nfiles; i++)
+ freq[i] = 0;
+ for (i = 0; i < BINS; i++) {
+ for (c = 0, node = bins[i]; node; c++, node = node->next);
+ fprintf(file, "fsys-hash: bin %5d has %7d\n", i, c);
+ if (c == 0)
+ empty++;
+ else if (c == 1)
+ used++;
+ else {
+ used++;
+ collided++;
+ }
+ freq[c]++;
+ }
+ for (i = nfiles; i > 0 && freq[i] == 0; i--);
+ while (i >= 0) {
+ fprintf(file, "fsys-hash: size %7d occurs %5d times\n",
+ i, freq[i]);
+ i--;
+ }
+ fprintf(file, "fsys-hash: bins empty %d\n", empty);
+ fprintf(file, "fsys-hash: bins used %d (collided %d)\n", used,
+ collided);
+
+ m_output(file, "<hash report>");
+
+ free(freq);
+}
+
+/*
+ * Forward iterator.
+ */
+
+struct fsys_hash_iter {
+ struct fsys_namenode *namenode;
+ int nbinn;
+};
+
+struct fsys_hash_iter *
+fsys_hash_iter_new(void)
+{
+ struct fsys_hash_iter *iter;
+
+ iter = m_malloc(sizeof(*iter));
+ iter->namenode = NULL;
+ iter->nbinn = 0;
+
+ return iter;
+}
+
+struct fsys_namenode *
+fsys_hash_iter_next(struct fsys_hash_iter *iter)
+{
+ struct fsys_namenode *fnn = NULL;
+
+ while (!iter->namenode) {
+ if (iter->nbinn >= BINS)
+ return NULL;
+ iter->namenode = bins[iter->nbinn++];
+ }
+ fnn = iter->namenode;
+ iter->namenode = fnn->next;
+
+ return fnn;
+}
+
+void
+fsys_hash_iter_free(struct fsys_hash_iter *iter)
+{
+ free(iter);
+}
diff --git a/lib/dpkg/fsys-iter.c b/lib/dpkg/fsys-iter.c
new file mode 100644
index 0000000..807aabb
--- /dev/null
+++ b/lib/dpkg/fsys-iter.c
@@ -0,0 +1,126 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * fsys-iter.c - filesystem nodes iterators
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2000, 2001 Wichert Akkerman <wakkerma@debian.org>
+ * Copyright © 2008-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <stdlib.h>
+
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg-list.h>
+
+#include "fsys.h"
+
+/*
+ * Reverse iterator.
+ */
+
+/*
+ * Initializes an iterator that appears to go through the file list ‘files’
+ * in reverse order, returning the namenode from each. What actually happens
+ * is that we walk the list here, building up a reverse list, and then peel
+ * it apart one entry at a time.
+ */
+void
+fsys_hash_rev_iter_init(struct fsys_hash_rev_iter *iter,
+ struct fsys_namenode_list *files)
+{
+ struct fsys_namenode_list *newent;
+
+ iter->todo = NULL;
+ while (files) {
+ newent = m_malloc(sizeof(*newent));
+ newent->namenode = files->namenode;
+ newent->next = iter->todo;
+ iter->todo = newent;
+ files = files->next;
+ }
+}
+
+struct fsys_namenode *
+fsys_hash_rev_iter_next(struct fsys_hash_rev_iter *iter)
+{
+ struct fsys_namenode *next;
+ struct fsys_namenode_list *todo;
+
+ todo = iter->todo;
+ if (!todo)
+ return NULL;
+ next = todo->namenode;
+ iter->todo = todo->next;
+ free(todo);
+
+ return next;
+}
+
+/*
+ * Clients must call this function to clean up the fsys_hash_rev_iter
+ * if they wish to break out of the iteration before it is all done.
+ * Calling this function is not necessary if fsys_hash_rev_iter_next() has
+ * been called until it returned 0.
+ */
+void
+fsys_hash_rev_iter_abort(struct fsys_hash_rev_iter *iter)
+{
+ while (fsys_hash_rev_iter_next(iter))
+ ;
+}
+
+/*
+ * Iterator for packages owning a file.
+ */
+
+struct fsys_node_pkgs_iter {
+ struct pkg_list *pkg_node;
+};
+
+struct fsys_node_pkgs_iter *
+fsys_node_pkgs_iter_new(struct fsys_namenode *fnn)
+{
+ struct fsys_node_pkgs_iter *iter;
+
+ iter = m_malloc(sizeof(*iter));
+ iter->pkg_node = fnn->packages;
+
+ return iter;
+}
+
+struct pkginfo *
+fsys_node_pkgs_iter_next(struct fsys_node_pkgs_iter *iter)
+{
+ struct pkg_list *pkg_node;
+
+ if (iter->pkg_node == NULL)
+ return NULL;
+
+ pkg_node = iter->pkg_node;
+ iter->pkg_node = pkg_node->next;
+
+ return pkg_node->pkg;
+}
+
+void
+fsys_node_pkgs_iter_free(struct fsys_node_pkgs_iter *iter)
+{
+ free(iter);
+}
diff --git a/lib/dpkg/fsys.h b/lib/dpkg/fsys.h
new file mode 100644
index 0000000..ede5cdf
--- /dev/null
+++ b/lib/dpkg/fsys.h
@@ -0,0 +1,214 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * fsys.h - filesystem nodes hash table
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_FSYS_H
+#define LIBDPKG_FSYS_H
+
+#include <stdio.h>
+
+#include <dpkg/file.h>
+
+DPKG_BEGIN_DECLS
+
+/*
+ * Data structure here is as follows:
+ *
+ * For each package we have a ‘struct fsys_namenode_list *’, the head of a list of
+ * files in that package. They are in ‘forwards’ order. Each entry has a
+ * pointer to the ‘struct fsys_namenode’.
+ *
+ * The struct fsys_namenodes are in a hash table, indexed by name.
+ * (This hash table is not visible to callers.)
+ *
+ * Each fsys_namenode has a (possibly empty) list of ‘struct filepackage’,
+ * giving a list of the packages listing that filename.
+ *
+ * When we read files contained info about a particular package we set the
+ * ‘files’ member of the clientdata struct to the appropriate thing. When
+ * not yet set the files pointer is made to point to ‘fileslist_uninited’
+ * (this is available only internally, within filesdb.c - the published
+ * interface is ensure_*_available).
+ */
+
+struct pkginfo;
+
+/**
+ * Flags to fsys_hash_find_node().
+ */
+enum DPKG_ATTR_ENUM_FLAGS fsys_hash_find_flags {
+ /** No flags. */
+ FHFF_NONE = 0,
+ /** Do not need to copy filename. */
+ FHFF_NO_COPY = DPKG_BIT(0),
+ /** Do not insert the item if it is not found, and return NULL. */
+ FHFF_NO_NEW = DPKG_BIT(1),
+};
+
+enum DPKG_ATTR_ENUM_FLAGS fsys_namenode_flags {
+ /** No flags. */
+ FNNF_NONE = 0,
+ /** In the newconffiles list. */
+ FNNF_NEW_CONFF = DPKG_BIT(0),
+ /** In the new filesystem archive. */
+ FNNF_NEW_INARCHIVE = DPKG_BIT(1),
+ /** In the old package's conffiles list. */
+ FNNF_OLD_CONFF = DPKG_BIT(2),
+ /** Obsolete conffile. */
+ FNNF_OBS_CONFF = DPKG_BIT(3),
+ /** Must remove from other packages' lists. */
+ FNNF_ELIDE_OTHER_LISTS = DPKG_BIT(4),
+ /** >= 1 instance is a dir, cannot rename over. */
+ FNNF_NO_ATOMIC_OVERWRITE = DPKG_BIT(5),
+ /** New file has been placed on the disk. */
+ FNNF_PLACED_ON_DISK = DPKG_BIT(6),
+ FNNF_DEFERRED_FSYNC = DPKG_BIT(7),
+ FNNF_DEFERRED_RENAME = DPKG_BIT(8),
+ /** Path being filtered. */
+ FNNF_FILTERED = DPKG_BIT(9),
+ /** Conffile removal requested by upgrade. */
+ FNNF_RM_CONFF_ON_UPGRADE = DPKG_BIT(10),
+};
+
+/**
+ * Stores information to uniquely identify an on-disk file.
+ */
+struct file_ondisk_id {
+ dev_t id_dev;
+ ino_t id_ino;
+};
+
+struct fsys_namenode {
+ struct fsys_namenode *next;
+ const char *name;
+ struct pkg_list *packages;
+ struct fsys_diversion *divert;
+
+ /** We allow the administrator to override the owner, group and mode
+ * of a file. If such an override is present we use that instead of
+ * the stat information stored in the archive.
+ *
+ * This functionality used to be in the suidmanager package. */
+ struct file_stat *statoverride;
+
+ struct trigfileint *trig_interested;
+
+ /*
+ * Fields from here on are used by archives.c &c, and cleared by
+ * fsys_hash_init().
+ */
+
+ /** Set to zero when a new node is created. */
+ enum fsys_namenode_flags flags;
+
+ /** Valid iff this namenode is in the newconffiles list. */
+ const char *oldhash;
+
+ /** Valid iff the file was unpacked and hashed on this run. */
+ const char *newhash;
+
+ struct file_ondisk_id *file_ondisk_id;
+};
+
+struct fsys_namenode_list {
+ struct fsys_namenode_list *next;
+ struct fsys_namenode *namenode;
+};
+
+/**
+ * Queue of fsys_namenode entries.
+ */
+struct fsys_namenode_queue {
+ struct fsys_namenode_list *head, **tail;
+};
+
+/**
+ * When we deal with an ‘overridden’ file, every package except the
+ * overriding one is considered to contain the other file instead. Both
+ * files have entries in the filesdb database, and they refer to each other
+ * via these diversion structures.
+ *
+ * The contested filename's fsys_namenode has a diversion entry with
+ * useinstead set to point to the redirected filename's fsys_namenode; the
+ * redirected fsys_namenode has camefrom set to the contested fsys_namenode.
+ * Both sides' diversion entries will have pkg set to the package (if any)
+ * which is allowed to use the contended filename.
+ *
+ * Packages that contain either version of the file will all refer to the
+ * contested fsys_namenode in their per-file package lists (both in core and
+ * on disk). References are redirected to the other fsys_namenode's filename
+ * where appropriate.
+ */
+struct fsys_diversion {
+ struct fsys_namenode *useinstead;
+ struct fsys_namenode *camefrom;
+ struct pkgset *pkgset;
+
+ /** The ‘contested’ halves are in this list for easy cleanup. */
+ struct fsys_diversion *next;
+};
+
+struct fsys_node_pkgs_iter;
+struct fsys_node_pkgs_iter *
+fsys_node_pkgs_iter_new(struct fsys_namenode *fnn);
+struct pkginfo *
+fsys_node_pkgs_iter_next(struct fsys_node_pkgs_iter *iter);
+void
+fsys_node_pkgs_iter_free(struct fsys_node_pkgs_iter *iter);
+
+void
+fsys_hash_init(void);
+void
+fsys_hash_reset(void);
+void
+fsys_hash_report(FILE *file);
+int
+fsys_hash_entries(void);
+
+struct fsys_hash_iter;
+struct fsys_hash_iter *
+fsys_hash_iter_new(void);
+struct fsys_namenode *
+fsys_hash_iter_next(struct fsys_hash_iter *iter);
+void
+fsys_hash_iter_free(struct fsys_hash_iter *iter);
+
+struct fsys_namenode *
+fsys_hash_find_node(const char *filename, enum fsys_hash_find_flags flags);
+
+struct fsys_hash_rev_iter {
+ struct fsys_namenode_list *todo;
+};
+
+void
+fsys_hash_rev_iter_init(struct fsys_hash_rev_iter *iter,
+ struct fsys_namenode_list *files);
+struct fsys_namenode *
+fsys_hash_rev_iter_next(struct fsys_hash_rev_iter *iter);
+void
+fsys_hash_rev_iter_abort(struct fsys_hash_rev_iter *iter);
+
+const char *dpkg_fsys_set_dir(const char *dir);
+const char *dpkg_fsys_get_dir(void);
+char *dpkg_fsys_get_path(const char *pathpart);
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_FSYS_H */
diff --git a/lib/dpkg/glob.c b/lib/dpkg/glob.c
new file mode 100644
index 0000000..90e401c
--- /dev/null
+++ b/lib/dpkg/glob.c
@@ -0,0 +1,50 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * glob.c - file globing functions
+ *
+ * Copyright © 2009, 2010 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <stdlib.h>
+
+#include <dpkg/dpkg.h>
+#include <dpkg/glob.h>
+
+void
+glob_list_prepend(struct glob_node **list, char *pattern)
+{
+ struct glob_node *node;
+
+ node = m_malloc(sizeof(*node));
+ node->pattern = pattern;
+ node->next = *list;
+ *list = node;
+}
+
+void
+glob_list_free(struct glob_node *head)
+{
+ while (head) {
+ struct glob_node *node = head;
+
+ head = head->next;
+ free(node->pattern);
+ free(node);
+ }
+}
diff --git a/lib/dpkg/glob.h b/lib/dpkg/glob.h
new file mode 100644
index 0000000..ed1830b
--- /dev/null
+++ b/lib/dpkg/glob.h
@@ -0,0 +1,46 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * glob.h - file globing functions
+ *
+ * Copyright © 2009, 2010 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_GLOB_H
+#define LIBDPKG_GLOB_H
+
+#include <dpkg/macros.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup glob File globbing
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+struct glob_node {
+ struct glob_node *next;
+ char *pattern;
+};
+
+void glob_list_prepend(struct glob_node **list, char *pattern);
+void glob_list_free(struct glob_node *head);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_GLOB_H */
diff --git a/lib/dpkg/i18n.c b/lib/dpkg/i18n.c
new file mode 100644
index 0000000..30e28bf
--- /dev/null
+++ b/lib/dpkg/i18n.c
@@ -0,0 +1,91 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * i18n.c - i18n support
+ *
+ * Copyright © 2013 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/i18n.h>
+
+#ifdef HAVE_USELOCALE
+#ifdef HAVE_XLOCALE_H
+#include <xlocale.h>
+#endif
+static locale_t dpkg_C_locale;
+#endif
+
+void
+dpkg_locales_init(const char *package)
+{
+ setlocale(LC_ALL, "");
+ bindtextdomain(package, LOCALEDIR);
+ textdomain(package);
+
+#ifdef HAVE_USELOCALE
+ dpkg_C_locale = newlocale(LC_ALL_MASK, "C", (locale_t)0);
+#endif
+
+#if defined(__APPLE__) && defined(__MACH__)
+ /*
+ * On macOS, the libintl code needs to call into the CoreFoundation
+ * framework, which is internally threaded, to initialize some caches.
+ * This is a problem when that first call is done after a fork(3),
+ * because per POSIX, only one thread will survive, leaving the
+ * process in a very inconsistent state, leading to crashes.
+ *
+ * XXX: To workaround this, we try to force the caches initialization
+ * at program startup time, by performing a dummy gettext() call.
+ */
+ gettext("");
+#endif
+}
+
+void
+dpkg_locales_done(void)
+{
+#ifdef HAVE_USELOCALE
+ freelocale(dpkg_C_locale);
+ dpkg_C_locale = (locale_t)0;
+#endif
+}
+
+struct dpkg_locale
+dpkg_locale_switch_C(void)
+{
+ struct dpkg_locale loc;
+
+#ifdef HAVE_USELOCALE
+ loc.oldloc = uselocale(dpkg_C_locale);
+#else
+ loc.oldloc = setlocale(LC_ALL, NULL);
+ setlocale(LC_ALL, "C");
+#endif
+
+ return loc;
+}
+
+void
+dpkg_locale_switch_back(struct dpkg_locale loc)
+{
+#ifdef HAVE_USELOCALE
+ uselocale(loc.oldloc);
+#else
+ setlocale(LC_ALL, loc.oldloc);
+#endif
+}
diff --git a/lib/dpkg/i18n.h b/lib/dpkg/i18n.h
new file mode 100644
index 0000000..f574433
--- /dev/null
+++ b/lib/dpkg/i18n.h
@@ -0,0 +1,59 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * i18n.h - i18n support
+ *
+ * Copyright © 2008-2011 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_I18N_H
+#define LIBDPKG_I18N_H
+
+#include <dpkg/macros.h>
+
+#include <gettext.h>
+
+/* We need to include this because pgettext() uses LC_MESSAGES, but libintl.h
+ * which gets pulled by gettext.h only includes it if building optimized. */
+#include <locale.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup i18n Internationalization support
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+#define _(str) gettext(str)
+#define P_(str, str_plural, n) ngettext(str, str_plural, n)
+#define N_(str) gettext_noop(str)
+#define C_(ctxt, str) pgettext(ctxt, str)
+
+void dpkg_locales_init(const char *package);
+void dpkg_locales_done(void);
+
+struct dpkg_locale {
+ void *oldloc;
+};
+
+struct dpkg_locale dpkg_locale_switch_C(void);
+void dpkg_locale_switch_back(struct dpkg_locale loc);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_I18N_H */
diff --git a/lib/dpkg/libdpkg.map b/lib/dpkg/libdpkg.map
new file mode 100644
index 0000000..e0b1152
--- /dev/null
+++ b/lib/dpkg/libdpkg.map
@@ -0,0 +1,498 @@
+LIBDPKG_0 {
+global:
+ # Error reporting
+ dpkg_has_error;
+ dpkg_put_warn;
+ dpkg_put_error;
+ dpkg_put_errno;
+ dpkg_error_print;
+ dpkg_error_move;
+ dpkg_error_destroy;
+
+ # Charset and string functions
+ dpkg_locales_init;
+ dpkg_locales_done;
+
+ # Program name
+ dpkg_set_progname;
+ dpkg_get_progname;
+
+ # Program setup/teardown
+ dpkg_program_init;
+ dpkg_program_done;
+
+ # Ar support
+ dpkg_ar_fdopen;
+ dpkg_ar_create;
+ dpkg_ar_open;
+ dpkg_ar_set_mtime;
+ dpkg_ar_close;
+ dpkg_ar_normalize_name;
+ dpkg_ar_member_is_illegal;
+
+local:
+ *;
+};
+
+LIBDPKG_PRIVATE {
+ # Color handling
+ color_set_mode;
+ color_get;
+ color_reset;
+
+ # Error handling
+ push_error_context_jump;
+ push_error_context_func;
+ push_error_context;
+ pop_error_context;
+ print_fatal_error;
+ catch_fatal_error;
+ push_checkpoint;
+ push_cleanup;
+ push_cleanup_fallback;
+ pop_cleanup;
+ onerr_abort; # XXX variable, do not export
+ ohshitv;
+ ohshite;
+ ohshit;
+ do_internerr;
+ dpkg_set_report_piped_mode;
+ dpkg_set_report_buffer;
+ dpkg_warning_printer;
+ dpkg_set_warning_printer;
+ warning_get_count;
+ warningv;
+ warning;
+ notice;
+ info;
+
+ debug_set_output;
+ debug_set_mask;
+ debug_parse_mask;
+ debug_has_flag;
+ debug;
+ dpkg_debug_init;
+
+ # Generic cleanup
+ cu_closepipe;
+ cu_closestream;
+ cu_closedir;
+ cu_closefd;
+ cu_filename;
+
+ # ‘Must do’ functions
+ m_malloc;
+ m_calloc;
+ m_realloc;
+ m_strdup;
+ m_vasprintf;
+ m_asprintf;
+ m_dup;
+ m_dup2;
+ m_pipe;
+ m_output;
+ fgets_must;
+ fgets_checked;
+
+ # Charset and string functions
+ c_isbits;
+
+ str_match_end;
+ str_fnv_hash;
+ str_concat;
+ str_fmt;
+ str_escape_fmt;
+ str_strip_quotes;
+ str_rtrim_spaces;
+ str_quote_meta;
+ str_width;
+ str_gen_crop;
+
+ # Variable buffer support
+ varbuf_new;
+ varbuf_init;
+ varbuf_reset;
+ varbuf_grow;
+ varbuf_trunc;
+ varbuf_set_varbuf;
+ varbuf_set_buf;
+ varbuf_add_varbuf;
+ varbuf_add_char;
+ varbuf_dup_char;
+ varbuf_map_char;
+ varbuf_add_buf;
+ varbuf_add_dir;
+ varbuf_get_str;
+ varbuf_end_str;
+ varbuf_printf;
+ varbuf_vprintf;
+ varbuf_detach;
+ varbuf_snapshot;
+ varbuf_rollback;
+ varbuf_rollback_len;
+ varbuf_rollback_start;
+ varbuf_destroy;
+ varbuf_free;
+
+ # Path, directory and file functions
+ secure_unlink_statted;
+ secure_unlink;
+ secure_remove;
+ path_remove_tree;
+ path_skip_slash_dotslash;
+ path_trim_slash_slashdot;
+ path_basename;
+ path_make_temp_template;
+ path_quote_filename;
+
+ dir_make_path;
+ dir_make_path_parent;
+ dir_sync_path;
+ dir_sync_path_parent;
+ dir_sync_contents;
+
+ treenode_get_name;
+ treenode_get_mode;
+ treenode_get_virtname;
+ treenode_get_pathname;
+ treewalk_open;
+ treewalk_node;
+ treewalk_next;
+ treewalk_close;
+ treewalk;
+
+ file_is_exec;
+ file_copy_perms;
+ file_show;
+ file_slurp;
+
+ atomic_file_new;
+ atomic_file_open;
+ atomic_file_sync;
+ atomic_file_close;
+ atomic_file_commit;
+ atomic_file_remove;
+ atomic_file_free;
+
+ glob_list_prepend;
+ glob_list_free;
+
+ # Data structure functions
+ namevalue_find_by_name;
+
+ # Buffer I/O functions
+ fd_read;
+ fd_write;
+ fd_allocate_size;
+ buffer_digest;
+ buffer_skip_*;
+ buffer_copy_*;
+
+ # Subprocess and command handling
+ subproc_signals_ignore;
+ subproc_signals_cleanup;
+ subproc_signals_restore;
+ subproc_fork;
+ subproc_reap;
+
+ command_init;
+ command_add_arg;
+ command_add_argl;
+ command_add_argv;
+ command_add_args;
+ command_exec;
+ command_shell;
+ command_in_path;
+ command_destroy;
+
+ pager_get_exec;
+ pager_spawn;
+ pager_reap;
+ pager_enable;
+
+ setcloexec;
+
+ # Memory information
+ meminfo_get_available_from_file;
+ meminfo_get_available;
+
+ # Compression support
+ compressor_find_by_name;
+ compressor_find_by_extension;
+ compressor_get_name;
+ compressor_get_extension;
+ compressor_get_strategy;
+ compressor_check_params;
+ compress_filter;
+ decompress_filter;
+
+ # Ar support
+ dpkg_ar_put_magic;
+ dpkg_ar_member_put_header;
+ dpkg_ar_member_put_file;
+ dpkg_ar_member_put_mem;
+ dpkg_ar_member_get_size;
+
+ # deb version support
+ deb_version_parse;
+
+ # Configuration and command line handling
+ dpkg_options_load;
+ dpkg_options_parse;
+ dpkg_options_parse_arg_int;
+ dpkg_options_parse_pkgname;
+ badusage;
+ cipaction; # XXX variable, do not export
+ setaction;
+ setobsolete;
+ set_instdir;
+ set_admindir;
+ set_root;
+
+ # General logging
+ log_file; # XXX variable, do not export
+ log_message;
+
+ # Action logging
+ statusfd_add;
+ statusfd_send;
+
+ # Progress report support
+ progress_init;
+ progress_step;
+ progress_done;
+
+ # Tar support
+ tar_atoul;
+ tar_atosl;
+ tar_extractor;
+ tar_entry_update_from_system;
+
+ # Non-freeing malloc (pool/arena)
+ nfmalloc;
+ nfstrnsave;
+ nfstrsave;
+ nffreeall;
+
+ # Version struct handling
+ dpkg_version_blank;
+ dpkg_version_is_informative;
+ dpkg_version_compare;
+ dpkg_version_relate;
+ versiondescribe;
+ versiondescribe_c;
+ parseversion;
+
+ # Architecture database
+ dpkg_arch_name_is_illegal;
+ dpkg_arch_describe;
+ dpkg_arch_find;
+ dpkg_arch_get;
+ dpkg_arch_get_list;
+ dpkg_arch_reset_list;
+ dpkg_arch_add;
+ dpkg_arch_unmark;
+ dpkg_arch_load_list;
+ dpkg_arch_save_list;
+
+ # Package struct handling
+ pkgset_blank;
+ pkgset_link_pkg;
+ pkgset_installed_instances;
+ pkg_blank;
+ pkgbin_blank;
+ pkg_name_is_illegal;
+ pkg_set_status;
+ pkg_set_eflags;
+ pkg_clear_eflags;
+ pkg_reset_eflags;
+ pkg_copy_eflags;
+ pkg_set_want;
+ pkg_is_informative;
+ copy_dependency_links;
+ pkg_sorter_by_nonambig_name_arch;
+ varbuf_add_pkgbin_name;
+ varbuf_add_archqual;
+ varbuf_add_source_version;
+ pkgbin_name;
+ pkg_name;
+ pkgbin_name_const;
+ pkg_name_const;
+ pkg_source_version;
+ pkgbin_synopsis;
+ pkg_synopsis;
+ pkg_abbrev_want;
+ pkg_abbrev_status;
+ pkg_abbrev_eflag;
+ pkg_want_name;
+ pkg_eflag_name;
+ pkg_status_name;
+ pkg_priority_name;
+
+ # Package list handling
+ pkg_list_new;
+ pkg_list_free;
+ pkg_list_prepend;
+
+ # Package array handling
+ pkg_array_init_from_hash;
+ pkg_array_init_from_names;
+ pkg_array_foreach;
+ pkg_array_sort;
+ pkg_array_destroy;
+
+ # Package queue handling
+ pkg_queue_init;
+ pkg_queue_destroy;
+ pkg_queue_is_empty;
+ pkg_queue_push;
+ pkg_queue_pop;
+
+ # Package in-core database functions
+ pkg_hash_find_set;
+ pkg_hash_find_singleton;
+ pkg_hash_find_pkg;
+ pkg_hash_get_singleton;
+ pkg_hash_count_set;
+ pkg_hash_count_pkg;
+ pkg_hash_reset;
+ pkg_hash_iter_new;
+ pkg_hash_iter_next_set;
+ pkg_hash_iter_next_pkg;
+ pkg_hash_iter_free;
+ pkg_hash_report;
+
+ # Package field handling
+ booleaninfos; # XXX variable, do not export
+ fieldinfos; # XXX variable, do not export
+ find_field_info;
+ find_arbfield_info;
+
+ # Package field format handling
+ pkg_format_parse;
+ pkg_format_print;
+ pkg_format_show;
+ pkg_format_needs_db_fsys;
+ pkg_format_free;
+
+ # Package specifiers
+ pkg_spec_is_illegal;
+ pkg_spec_init;
+ pkg_spec_destroy;
+ pkg_spec_set;
+ pkg_spec_parse;
+ pkg_spec_match_pkg;
+ pkg_spec_parse_pkg;
+ pkg_spec_find_pkg;
+ pkg_spec_iter_init;
+ pkg_spec_iter_next_pkg;
+ pkg_spec_iter_destroy;
+
+ # Dependency and Conflict functions
+ pkg_virtual_deppossi_satisfied;
+ deparchsatisfied;
+ archsatisfied;
+ versionsatisfied;
+
+ # Package on-disk database functions
+ wantinfos; # XXX variable, do not export
+ varbuf_add_arbfield;
+ varbufdependency;
+ varbuf_stanza;
+ write_stanza;
+ parsedb_new;
+ parsedb_open;
+ parsedb_load;
+ parsedb_parse;
+ parsedb_close;
+ parsedb;
+ writedb_stanzas;
+ writedb;
+
+ dpkg_db_set_dir;
+ dpkg_db_get_dir;
+ dpkg_db_get_path;
+
+ # Log based package on-disk database support
+ modstatdb_init;
+ modstatdb_is_locked;
+ modstatdb_can_lock;
+ modstatdb_lock;
+ modstatdb_unlock;
+ modstatdb_open;
+ modstatdb_get_status;
+ modstatdb_note;
+ modstatdb_note_ifwrite;
+ modstatdb_checkpoint;
+ modstatdb_shutdown;
+ modstatdb_done;
+
+ # Package on-disk control database support
+ pkg_infodb_foreach;
+ pkg_infodb_get_dir;
+ pkg_infodb_get_file;
+ pkg_infodb_has_file;
+ pkg_infodb_upgrade;
+
+ # Package on-disk diversion database support
+ ensure_diversions;
+
+ # Filesystem node hash support
+ fsys_hash_init;
+ fsys_hash_reset;
+ fsys_hash_entries;
+ fsys_hash_find_node;
+ fsys_hash_report;
+
+ fsys_hash_iter_new;
+ fsys_hash_iter_next;
+ fsys_hash_iter_free;
+
+ fsys_hash_rev_iter_init;
+ fsys_hash_rev_iter_next;
+ fsys_hash_rev_iter_abort;
+
+ fsys_node_pkgs_iter_new;
+ fsys_node_pkgs_iter_next;
+ fsys_node_pkgs_iter_free;
+
+ dpkg_fsys_set_dir;
+ dpkg_fsys_get_dir;
+ dpkg_fsys_get_path;
+
+ # Package on-disk filesystem database support
+ parse_filehash;
+ write_filelist_except;
+ write_filehash_except;
+ ensure_packagefiles_available;
+ ensure_allinstfiles_available;
+ ensure_allinstfiles_available_quiet;
+
+ # Package on-disk stat override database support
+ statdb_parse_uid;
+ statdb_parse_gid;
+ statdb_parse_mode;
+ ensure_statoverrides;
+
+ # Triggers support
+ trig_name_is_illegal;
+ trigdef_set_methods;
+ trigdef_update_start;
+ trigdef_update_printf;
+ trigdef_parse;
+ trigdef_process_done;
+ trig_override_hooks;
+ trig_file_activate_byname;
+ trig_file_activate;
+ trig_path_activate;
+ trig_note_pend;
+ trig_note_aw;
+ trig_clear_awaiters;
+ trig_file_interests_ensure;
+ trig_file_interests_save;
+ trig_cicb_interest_delete;
+ trig_cicb_interest_add;
+ trig_parse_ci;
+ trig_cicb_statuschange_activate;
+ trig_incorporate;
+} LIBDPKG_0;
diff --git a/lib/dpkg/libdpkg.pc.in b/lib/dpkg/libdpkg.pc.in
new file mode 100644
index 0000000..1a91594
--- /dev/null
+++ b/lib/dpkg/libdpkg.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@devlibdir@
+includedir=@includedir@
+
+Name: libdpkg
+Description: Debian package management system library
+Version: @VERSION@
+Libs: -L${libdir} -ldpkg
+Libs.private: @MD_LIBS@ @Z_LIBS@ @LZMA_LIBS@ @ZSTD_LIBS@ @BZ2_LIBS@
+Cflags: -I${includedir}
diff --git a/lib/dpkg/log.c b/lib/dpkg/log.c
new file mode 100644
index 0000000..10a33a2
--- /dev/null
+++ b/lib/dpkg/log.c
@@ -0,0 +1,129 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * log.c - logging related functions
+ *
+ * Copyright © 2005 Scott James Remnant <scott@netsplit.com>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <time.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdarg.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/fdio.h>
+
+const char *log_file = NULL;
+
+void
+log_message(const char *fmt, ...)
+{
+ static struct varbuf log;
+ static int logfd = -1;
+ char time_str[20];
+ time_t now;
+ struct tm tm;
+ va_list args;
+
+ if (!log_file)
+ return;
+
+ if (logfd < 0) {
+ logfd = open(log_file, O_WRONLY | O_CREAT | O_APPEND, 0644);
+ if (logfd < 0) {
+ notice(_("could not open log '%s': %s"),
+ log_file, strerror(errno));
+ log_file = NULL;
+ return;
+ }
+ setcloexec(logfd, log_file);
+ }
+
+ time(&now);
+ if (localtime_r(&now, &tm) == NULL) {
+ notice(_("cannot get local time to log into '%s': %s"),
+ log_file, strerror(errno));
+ return;
+ }
+ strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", &tm);
+
+ va_start(args, fmt);
+ varbuf_set_str(&log, time_str);
+ varbuf_add_char(&log, ' ');
+ varbuf_vprintf(&log, fmt, args);
+ varbuf_add_char(&log, '\n');
+ varbuf_end_str(&log);
+ va_end(args);
+
+ if (fd_write(logfd, log.buf, log.used) < 0)
+ notice(_("cannot write to log file '%s': %s"),
+ log_file, strerror(errno));
+}
+
+struct pipef {
+ struct pipef *next;
+ int fd;
+};
+
+static struct pipef *status_pipes = NULL;
+
+void
+statusfd_add(int fd)
+{
+ struct pipef *pipe_new;
+
+ setcloexec(fd, _("<package status and progress file descriptor>"));
+
+ pipe_new = nfmalloc(sizeof(*pipe_new));
+ pipe_new->fd = fd;
+ pipe_new->next = status_pipes;
+ status_pipes = pipe_new;
+}
+
+void
+statusfd_send(const char *fmt, ...)
+{
+ static struct varbuf vb;
+ struct pipef *pipef;
+ va_list args;
+
+ if (!status_pipes)
+ return;
+
+ va_start(args, fmt);
+ varbuf_reset(&vb);
+ varbuf_vprintf(&vb, fmt, args);
+ /* Sanitize string to not include new lines, as front-ends should be
+ * doing their own word-wrapping. */
+ varbuf_map_char(&vb, '\n', ' ');
+ varbuf_add_char(&vb, '\n');
+ va_end(args);
+
+ for (pipef = status_pipes; pipef; pipef = pipef->next) {
+ if (fd_write(pipef->fd, vb.buf, vb.used) < 0)
+ ohshite(_("unable to write to status fd %d"),
+ pipef->fd);
+ }
+}
diff --git a/lib/dpkg/macros.h b/lib/dpkg/macros.h
new file mode 100644
index 0000000..277e67e
--- /dev/null
+++ b/lib/dpkg/macros.h
@@ -0,0 +1,243 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * macros.h - C language support macros
+ *
+ * Copyright © 2008-2012 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_MACROS_H
+#define LIBDPKG_MACROS_H
+
+/**
+ * @defgroup macros C language support macros
+ * @ingroup dpkg-public
+ * @{
+ */
+
+#ifndef LIBDPKG_VOLATILE_API
+#error "The libdpkg API is to be considered volatile, please read 'README.api'."
+#endif
+
+/* Language definitions. */
+
+#ifndef __has_include
+#define __has_include(x) 0
+#endif
+
+/* Supported since clang 1.0. */
+#ifndef __has_warning
+#define __has_warning(w) (0)
+#endif
+
+#define DPKG_PRAGMA(x) _Pragma(#x)
+
+#if defined(__clang__)
+#define DPKG_PRAGMA_CC(x) DPKG_PRAGMA(clang x)
+#elif defined(__GNUC__)
+#define DPKG_PRAGMA_CC(x) DPKG_PRAGMA(GCC x)
+#else
+#define DPKG_PRAGMA_CC(x)
+#endif
+
+#define DPKG_IGNORE_WARNING(w) \
+ DPKG_PRAGMA_CC(diagnostic push); \
+ DPKG_PRAGMA_CC(diagnostic ignored w)
+#define DPKG_ACCEPT_WARNING() \
+ DPKG_PRAGMA_CC(diagnostic pop)
+
+#if __has_warning("-Wassign-enum")
+#define DPKG_IGNORE_WARNING_ASSIGN_ENUM() DPKG_IGNORE_WARNING("-Wassign-enum")
+#define DPKG_ACCEPT_WARNING_ASSIGN_ENUM() DPKG_ACCEPT_WARNING()
+#else
+#define DPKG_IGNORE_WARNING_ASSIGN_ENUM()
+#define DPKG_ACCEPT_WARNING_ASSIGN_ENUM()
+#endif
+
+/* Supported since gcc 5.1.0 and clang 2.9.0. For attributes that appeared
+ * before these versions, in addition we need to do version checks. */
+#ifndef __has_attribute
+#define __has_attribute(x) 0
+#endif
+
+#ifdef __GNUC__
+#define DPKG_GCC_VERSION (__GNUC__ << 8 | __GNUC_MINOR__)
+#else
+#define DPKG_GCC_VERSION 0
+#endif
+
+#if DPKG_GCC_VERSION >= 0x0300 || __has_attribute(__unused__)
+#define DPKG_ATTR_UNUSED __attribute__((__unused__))
+#else
+#define DPKG_ATTR_UNUSED
+#endif
+
+#if DPKG_GCC_VERSION >= 0x0300 || __has_attribute(__const__)
+#define DPKG_ATTR_CONST __attribute__((__const__))
+#else
+#define DPKG_ATTR_CONST
+#endif
+
+#if DPKG_GCC_VERSION >= 0x0300 || __has_attribute(__pure__)
+#define DPKG_ATTR_PURE __attribute__((__pure__))
+#else
+#define DPKG_ATTR_PURE
+#endif
+
+#if DPKG_GCC_VERSION >= 0x0300 || __has_attribute(__malloc__)
+#define DPKG_ATTR_MALLOC __attribute__((__malloc__))
+#else
+#define DPKG_ATTR_MALLOC
+#endif
+
+#if DPKG_GCC_VERSION >= 0x0300 || __has_attribute(__noreturn__)
+#define DPKG_ATTR_NORET __attribute__((__noreturn__))
+#else
+#define DPKG_ATTR_NORET
+#endif
+
+#if DPKG_GCC_VERSION >= 0x0300 || __has_attribute(__format__)
+#define DPKG_ATTR_FMT(t, f, a) __attribute__((__format__(t, f, a)))
+#define DPKG_ATTR_PRINTF(n) DPKG_ATTR_FMT(__printf__, n, n + 1)
+#define DPKG_ATTR_VPRINTF(n) DPKG_ATTR_FMT(__printf__, n, 0)
+#else
+#define DPKG_ATTR_FMT(t, f, a)
+#define DPKG_ATTR_PRINTF(n)
+#define DPKG_ATTR_VPRINTF(n)
+#endif
+
+#if DPKG_GCC_VERSION > 0x0302 || __has_attribute(__nonnull__)
+#define DPKG_ATTR_NONNULL(...) __attribute__((__nonnull__(__VA_ARGS__)))
+#else
+#define DPKG_ATTR_NONNULL(...)
+#endif
+
+#if DPKG_GCC_VERSION > 0x0302 || __has_attribute(__warn_unused_result__)
+#define DPKG_ATTR_REQRET __attribute__((__warn_unused_result__))
+#else
+#define DPKG_ATTR_REQRET
+#endif
+
+#if DPKG_GCC_VERSION >= 0x0400 || __has_attribute(__sentinel__)
+#define DPKG_ATTR_SENTINEL __attribute__((__sentinel__))
+#else
+#define DPKG_ATTR_SENTINEL
+#endif
+
+#if DPKG_GCC_VERSION >= 0x0801 || __has_attribute(__nonstring__)
+#define DPKG_ATTR_NONSTRING __attribute__((__nonstring__))
+#else
+#define DPKG_ATTR_NONSTRING
+#endif
+
+#if __has_attribute(__enum_extensibility__)
+#define DPKG_ATTR_ENUM_FLAGS \
+ __attribute__((__enum_extensibility__(closed),__flag_enum__))
+#else
+#define DPKG_ATTR_ENUM_FLAGS
+#endif
+
+#if defined(__cplusplus) && __cplusplus >= 201103L
+#define DPKG_ATTR_THROW(exception)
+#define DPKG_ATTR_NOEXCEPT noexcept
+#elif defined(__cplusplus)
+#define DPKG_ATTR_THROW(exception) throw(exception)
+#define DPKG_ATTR_NOEXCEPT throw()
+#endif
+
+#ifdef __cplusplus
+#define DPKG_BEGIN_DECLS extern "C" {
+#define DPKG_END_DECLS }
+#else
+#define DPKG_BEGIN_DECLS
+#define DPKG_END_DECLS
+#endif
+
+/**
+ * @def DPKG_NULL
+ *
+ * A null pointer constant that works on C or C++.
+ *
+ * To be used only on header files, where having to conditionalize the code
+ * to use either NULL or nullptr would be too cumbersome. Non-header files
+ * should use the appropriate constant directly.
+ */
+#if defined(__cplusplus) || __STDC_VERSION__ > 201710L
+#define DPKG_NULL nullptr
+#else
+#define DPKG_NULL NULL
+#endif
+
+/**
+ * @def DPKG_STATIC_CAST
+ *
+ * Cast an expression to a given type that works on C or C++.
+ *
+ * To be used only on header files, where having to conditionalize the code
+ * to use either NULL or nullptr would be too cumbersome. Non-header files
+ * should use the appropriate constant directly.
+ */
+#if defined(__cplusplus)
+#define DPKG_STATIC_CAST(type, expr) static_cast<type>(expr)
+#else
+#define DPKG_STATIC_CAST(type, expr) (type)(expr)
+#endif
+
+/**
+ * @def DPKG_BIT
+ *
+ * Return the integer value of bit n.
+ */
+#define DPKG_BIT(n) (1UL << (n))
+
+/**
+ * @def array_count
+ *
+ * Returns the amount of items in an array.
+ */
+#ifndef array_count
+#define array_count(a) (sizeof(a) / sizeof((a)[0]))
+#endif
+
+/* For C++ use native implementations from STL or similar. */
+#ifndef __cplusplus
+#ifndef min
+#define min(a, b) ((a) < (b) ? (a) : (b))
+#endif
+
+#ifndef max
+#define max(a, b) ((a) > (b) ? (a) : (b))
+#endif
+#endif
+
+/**
+ * @def clamp
+ *
+ * Returns a normalized value within the low and high limits.
+ *
+ * @param v The value to clamp.
+ * @param l The low limit.
+ * @param h The high limit.
+ */
+/* For C++ use native implementations from STL or similar. */
+#ifndef __cplusplus
+#ifndef clamp
+#define clamp(v, l, h) ((v) > (h) ? (h) : ((v) < (l) ? (l) : (v)))
+#endif
+#endif
+
+/** @} */
+
+#endif /* LIBDPKG_MACROS_H */
diff --git a/lib/dpkg/meminfo.c b/lib/dpkg/meminfo.c
new file mode 100644
index 0000000..998f65c
--- /dev/null
+++ b/lib/dpkg/meminfo.c
@@ -0,0 +1,168 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * meminfo.c - system memory information functions
+ *
+ * Copyright © 2021 Sebastian Andrzej Siewior <sebastian@breakpoint.cc>
+ * Copyright © 2021-2022 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <inttypes.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <dpkg/dpkg.h>
+#include <dpkg/fdio.h>
+#include <dpkg/meminfo.h>
+
+/*
+ * An estimate of how much memory is available. Swap will not be used, the
+ * page cache may be purged, not everything will be reclaimed that might be
+ * reclaimed, watermarks are considered.
+ */
+
+struct meminfo_field {
+ const char *name;
+ ssize_t len;
+ int tag;
+ uint64_t value;
+};
+#define MEMINFO_FIELD(name, tag) name, sizeof(name) - 1, tag, 0
+
+static struct meminfo_field *
+meminfo_find_field(struct meminfo_field *fields, const size_t nfields,
+ const char *fieldname, const ssize_t fieldlen)
+{
+ size_t f;
+
+ for (f = 0; f < nfields; f++) {
+ if (fieldlen != fields[f].len)
+ continue;
+ if (strncmp(fieldname, fields[f].name, fields[f].len) != 0)
+ continue;
+
+ return &fields[f];
+ }
+
+ return NULL;
+}
+
+static uint64_t
+meminfo_sum_fields(const struct meminfo_field *fields, const size_t nfields)
+{
+ uint64_t sum = 0;
+ size_t f;
+
+ for (f = 0; f < nfields; f++)
+ sum += fields[f].value;
+
+ return sum;
+}
+
+enum meminfo_error_code
+meminfo_get_available_from_file(const char *filename, uint64_t *val)
+{
+ char buf[4096];
+ char *str;
+ ssize_t bytes;
+ int fd;
+ struct meminfo_field fields[] = {
+ { MEMINFO_FIELD("MemFree", DPKG_BIT(0)) },
+ { MEMINFO_FIELD("Buffers", DPKG_BIT(1)) },
+ { MEMINFO_FIELD("Cached", DPKG_BIT(2)) },
+ };
+ const int want_tags = DPKG_BIT(array_count(fields)) - 1;
+ int seen_tags = 0;
+
+ *val = 0;
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ return MEMINFO_NO_FILE;
+
+ bytes = fd_read(fd, buf, sizeof(buf));
+ close(fd);
+
+ if (bytes <= 0)
+ return MEMINFO_NO_DATA;
+
+ buf[bytes] = '\0';
+
+ str = buf;
+ while (1) {
+ struct meminfo_field *field;
+ char *end;
+
+ end = strchr(str, ':');
+ if (end == 0)
+ break;
+
+ field = meminfo_find_field(fields, array_count(fields),
+ str, end - str);
+ if (field) {
+ intmax_t num;
+
+ str = end + 1;
+ errno = 0;
+ num = strtoimax(str, &end, 10);
+ if (num <= 0)
+ return MEMINFO_INT_NEG;
+ if ((num == INTMAX_MAX) && errno == ERANGE)
+ return MEMINFO_INT_MAX;
+ /* It should end with ' kB\n'. */
+ if (*end != ' ' || *(end + 1) != 'k' ||
+ *(end + 2) != 'B')
+ return MEMINFO_NO_UNIT;
+
+ /* This should not overflow, but just in case. */
+ if (num < (INTMAX_MAX / 1024))
+ num *= 1024;
+
+ field->value = num;
+ seen_tags |= field->tag;
+ }
+
+ if (seen_tags == want_tags)
+ break;
+
+ end = strchr(end + 1, '\n');
+ if (end == 0)
+ break;
+ str = end + 1;
+ }
+
+ if (seen_tags != want_tags)
+ return MEMINFO_NO_INFO;
+
+ *val = meminfo_sum_fields(fields, array_count(fields));
+ return MEMINFO_OK;
+}
+
+enum meminfo_error_code
+meminfo_get_available(uint64_t *val)
+{
+#ifdef __linux__
+ return meminfo_get_available_from_file("/proc/meminfo", val);
+#else
+ return MEMINFO_NO_FILE;
+#endif
+}
diff --git a/lib/dpkg/meminfo.h b/lib/dpkg/meminfo.h
new file mode 100644
index 0000000..d7dee07
--- /dev/null
+++ b/lib/dpkg/meminfo.h
@@ -0,0 +1,55 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * meminfo.h - system memory information functions
+ *
+ * Copyright © 2022 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_MEMINFO_H
+#define LIBDPKG_MEMINFO_H
+
+#include <stdint.h>
+
+#include <dpkg/macros.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup meminfo Memory information handling
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+enum meminfo_error_code {
+ MEMINFO_OK = 0,
+ MEMINFO_NO_FILE = -1,
+ MEMINFO_NO_DATA = -2,
+ MEMINFO_INT_NEG = -3,
+ MEMINFO_INT_MAX = -4,
+ MEMINFO_NO_UNIT = -5,
+ MEMINFO_NO_INFO = -6,
+};
+
+enum meminfo_error_code
+meminfo_get_available_from_file(const char *filename, uint64_t *val);
+enum meminfo_error_code
+meminfo_get_available(uint64_t *val);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_MEMINFO_H */
diff --git a/lib/dpkg/mustlib.c b/lib/dpkg/mustlib.c
new file mode 100644
index 0000000..52d9752
--- /dev/null
+++ b/lib/dpkg/mustlib.c
@@ -0,0 +1,148 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * mustlib.c - ‘must’ library: routines will succeed or longjmp
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2006-2013, 2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+
+static inline void *
+must_alloc(void *ptr)
+{
+ if (ptr)
+ return ptr;
+
+ onerr_abort++;
+ ohshite(_("failed to allocate memory"));
+}
+
+void *m_malloc(size_t amount) {
+ return must_alloc(malloc(amount));
+}
+
+void *
+m_calloc(size_t nmemb, size_t size)
+{
+ return must_alloc(calloc(nmemb, size));
+}
+
+void *m_realloc(void *r, size_t amount) {
+ return must_alloc(realloc(r, amount));
+}
+
+char *
+m_strdup(const char *str)
+{
+ return must_alloc(strdup(str));
+}
+
+char *
+m_strndup(const char *str, size_t n)
+{
+ return must_alloc(strndup(str, n));
+}
+
+int
+m_vasprintf(char **strp, const char *fmt, va_list args)
+{
+ int n;
+
+ n = vasprintf(strp, fmt, args);
+ if (n >= 0)
+ return n;
+
+ onerr_abort++;
+ ohshite(_("failed to allocate memory"));
+}
+
+int
+m_asprintf(char **strp, const char *fmt, ...)
+{
+ va_list args;
+ int n;
+
+ va_start(args, fmt);
+ n = m_vasprintf(strp, fmt, args);
+ va_end(args);
+
+ return n;
+}
+
+int
+m_dup(int oldfd)
+{
+ int newfd;
+
+ newfd = dup(oldfd);
+ if (newfd >= 0)
+ return newfd;
+
+ onerr_abort++;
+ ohshite(_("failed to dup for fd %d"), oldfd);
+}
+
+void m_dup2(int oldfd, int newfd) {
+ const char *const stdstrings[]= { "in", "out", "err" };
+
+ if (dup2(oldfd,newfd) == newfd) return;
+
+ onerr_abort++;
+ if (newfd < 3) ohshite(_("failed to dup for std%s"),stdstrings[newfd]);
+ ohshite(_("failed to dup for fd %d"),newfd);
+}
+
+void
+m_pipe(int fds[2])
+{
+ if (!pipe(fds)) return;
+ onerr_abort++;
+ ohshite(_("failed to create pipe"));
+}
+
+void
+m_output(FILE *f, const char *name)
+{
+ fflush(f);
+ if (ferror(f) && errno != EPIPE)
+ ohshite(_("error writing to '%s'"), name);
+}
+
+void
+setcloexec(int fd, const char *fn)
+{
+ int f;
+
+ f = fcntl(fd, F_GETFD);
+ if (f == -1)
+ ohshite(_("unable to read filedescriptor flags for %.250s"),fn);
+ if (fcntl(fd, F_SETFD, (f|FD_CLOEXEC))==-1)
+ ohshite(_("unable to set close-on-exec flag for %.250s"),fn);
+}
diff --git a/lib/dpkg/namevalue.c b/lib/dpkg/namevalue.c
new file mode 100644
index 0000000..d138f87
--- /dev/null
+++ b/lib/dpkg/namevalue.c
@@ -0,0 +1,40 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * namevalue.c - name value structure handling
+ *
+ * Copyright © 2010-2011, 2014-2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <stddef.h>
+#include <string.h>
+
+#include <dpkg/namevalue.h>
+
+const struct namevalue *
+namevalue_find_by_name(const struct namevalue *head, const char *str)
+{
+ const struct namevalue *nv;
+
+ for (nv = head; nv->name; nv++) {
+ if (strncasecmp(str, nv->name, nv->length) == 0)
+ return nv;
+ }
+
+ return NULL;
+}
diff --git a/lib/dpkg/namevalue.h b/lib/dpkg/namevalue.h
new file mode 100644
index 0000000..337b491
--- /dev/null
+++ b/lib/dpkg/namevalue.h
@@ -0,0 +1,51 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * namevalue.h - name value structure handling
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2009-2012, 2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_NAMEVALUE_H
+#define LIBDPKG_NAMEVALUE_H
+
+#include <dpkg/macros.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup namevalue Name/Value data
+ * @ingroup dpkg-public
+ * @{
+ */
+
+struct namevalue {
+ const char *name;
+ int value;
+ int length;
+};
+
+#define NAMEVALUE_DEF(n, v) \
+ [v] = { .name = n, .value = v, .length = sizeof(n) - 1 }
+
+const struct namevalue *namevalue_find_by_name(const struct namevalue *head,
+ const char *str);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_NAMEVALUE_H */
diff --git a/lib/dpkg/nfmalloc.c b/lib/dpkg/nfmalloc.c
new file mode 100644
index 0000000..ebe35b8
--- /dev/null
+++ b/lib/dpkg/nfmalloc.c
@@ -0,0 +1,81 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * nfmalloc.c - non-freeing malloc, used for in-core database
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <string.h>
+#include <stdlib.h>
+#include <obstack.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+
+#define obstack_chunk_alloc m_malloc
+#define obstack_chunk_free free
+
+static struct obstack db_obs;
+static bool dbobs_init = false;
+
+/* We use lots of mem, so use a large chunk. */
+#define CHUNK_SIZE 8192
+
+#define OBSTACK_INIT do { if (!dbobs_init) { nfobstack_init(); } } while (0)
+
+static void nfobstack_init(void) {
+ obstack_init(&db_obs);
+ dbobs_init = true;
+ obstack_chunk_size(&db_obs) = CHUNK_SIZE;
+}
+
+void *
+nfmalloc(size_t size)
+{
+ OBSTACK_INIT;
+ /* cppcheck-suppress[nullPointerArithmetic]:
+ * False positive, imported module. */
+ return obstack_alloc(&db_obs, size);
+}
+
+char *nfstrsave(const char *string) {
+ OBSTACK_INIT;
+ /* cppcheck-suppress[nullPointerArithmetic]:
+ * False positive, imported module. */
+ return obstack_copy0 (&db_obs, string, strlen(string));
+}
+
+char *
+nfstrnsave(const char *string, size_t size)
+{
+ OBSTACK_INIT;
+ /* cppcheck-suppress[nullPointerArithmetic]:
+ * False positive, imported module. */
+ return obstack_copy0(&db_obs, string, size);
+}
+
+void nffreeall(void) {
+ if (dbobs_init) {
+ /* cppcheck-suppress[nullPointerArithmetic,pointerLessThanZero]:
+ * False positive, imported module. */
+ obstack_free(&db_obs, NULL);
+ dbobs_init = false;
+ }
+}
diff --git a/lib/dpkg/options-dirs.c b/lib/dpkg/options-dirs.c
new file mode 100644
index 0000000..9b7a122
--- /dev/null
+++ b/lib/dpkg/options-dirs.c
@@ -0,0 +1,61 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * options-dirs.c - CLI options parsing directories
+ *
+ * Copyright © 2022 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <stdlib.h>
+
+#include <dpkg/macros.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/fsys.h>
+#include <dpkg/options.h>
+
+void
+set_instdir(const struct cmdinfo *cip, const char *value)
+{
+ /* Make sure the database is initialized before the root directory,
+ * otherwise, on first use it would get initialized based on the
+ * newly set root directory. */
+ dpkg_db_get_dir();
+
+ dpkg_fsys_set_dir(value);
+}
+
+void
+set_admindir(const struct cmdinfo *cip, const char *value)
+{
+ dpkg_db_set_dir(value);
+}
+
+void
+set_root(const struct cmdinfo *cip, const char *value)
+{
+ char *db_dir;
+
+ /* Initialize the root directory. */
+ dpkg_fsys_set_dir(value);
+
+ /* Set the database directory based on the new root directory. */
+ db_dir = dpkg_fsys_get_path(ADMINDIR);
+ dpkg_db_set_dir(db_dir);
+ free(db_dir);
+}
diff --git a/lib/dpkg/options-parsers.c b/lib/dpkg/options-parsers.c
new file mode 100644
index 0000000..de5bf69
--- /dev/null
+++ b/lib/dpkg/options-parsers.c
@@ -0,0 +1,53 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * options-parsers.c - command-line options parser helpers
+ *
+ * Copyright © 2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/error.h>
+#include <dpkg/pkg-spec.h>
+#include <dpkg/options.h>
+
+/**
+ * Parse an argument as a package name.
+ *
+ * The string is parsed as a singleton package name, and a #pkginfo instance
+ * is returned.
+ *
+ * @param cmd The command information structure currently parsed.
+ * @param name The package name argument
+ *
+ * @return A package instance.
+ */
+struct pkginfo *
+dpkg_options_parse_pkgname(const struct cmdinfo *cmd, const char *name)
+{
+ struct pkginfo *pkg;
+ struct dpkg_error err;
+
+ pkg = pkg_spec_parse_pkg(name, &err);
+ if (pkg == NULL)
+ badusage(_("--%s needs a valid package name but '%.250s' is not: %s"),
+ cmd->olong, name, err.str);
+
+ return pkg;
+}
diff --git a/lib/dpkg/options.c b/lib/dpkg/options.c
new file mode 100644
index 0000000..4244b7d
--- /dev/null
+++ b/lib/dpkg/options.c
@@ -0,0 +1,326 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * options.c - option parsing functions
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2000,2002 Wichert Akkerman <wichert@deephackmode.org>
+ * Copyright © 2008-2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <string.h>
+#include <dirent.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/c-ctype.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/string.h>
+#include <dpkg/options.h>
+
+static const char *printforhelp;
+
+void
+badusage(const char *fmt, ...)
+{
+ char *buf = NULL;
+ va_list args;
+
+ va_start(args, fmt);
+ m_vasprintf(&buf, fmt, args);
+ va_end(args);
+
+ ohshit("%s\n\n%s", buf, gettext(printforhelp));
+}
+
+static void DPKG_ATTR_NORET DPKG_ATTR_PRINTF(3)
+config_error(const char *file_name, int line_num, const char *fmt, ...)
+{
+ char *buf = NULL;
+ va_list args;
+
+ va_start(args, fmt);
+ m_vasprintf(&buf, fmt, args);
+ va_end(args);
+
+ ohshit(_("configuration error: %s:%d: %s"), file_name, line_num, buf);
+}
+
+static void
+dpkg_options_load_file(const char *fn, const struct cmdinfo *cmdinfos)
+{
+ FILE *file;
+ int line_num = 0;
+ char linebuf[MAX_CONFIG_LINE];
+
+ file= fopen(fn, "r");
+ if (!file) {
+ if (errno==ENOENT)
+ return;
+ warning(_("failed to open configuration file '%.255s' for reading: %s"),
+ fn, strerror(errno));
+ return;
+ }
+
+ while (fgets(linebuf, sizeof(linebuf), file)) {
+ char *opt;
+ const struct cmdinfo *cip;
+
+ line_num++;
+
+ str_rtrim_spaces(linebuf, linebuf + strlen(linebuf));
+
+ if ((linebuf[0] == '#') || (linebuf[0] == '\0'))
+ continue;
+ for (opt = linebuf; c_isalnum(*opt) || *opt == '-'; opt++) ;
+ if (*opt == '\0')
+ opt=NULL;
+ else {
+ *opt++ = '\0';
+ if (*opt=='=') opt++;
+ while (c_isspace(*opt))
+ opt++;
+
+ opt = str_strip_quotes(opt);
+ if (opt == NULL)
+ config_error(fn, line_num, _("unbalanced quotes in '%s'"), linebuf);
+ }
+
+ for (cip=cmdinfos; cip->olong || cip->oshort; cip++) {
+ int l;
+
+ if (!cip->olong) continue;
+ if (strcmp(cip->olong, linebuf) == 0)
+ break;
+ l=strlen(cip->olong);
+ if ((cip->takesvalue==2) && (linebuf[l]=='-') &&
+ !opt && strncmp(linebuf, cip->olong, l) == 0) {
+ opt=linebuf+l+1;
+ break;
+ }
+ }
+
+ if (!cip->olong)
+ config_error(fn, line_num, _("unknown option '%s'"), linebuf);
+
+ if (cip->takesvalue) {
+ if (!opt)
+ config_error(fn, line_num, _("'%s' needs a value"), linebuf);
+ if (cip->call) cip->call(cip,opt);
+ else
+ *cip->sassignto = m_strdup(opt);
+ } else {
+ if (opt)
+ config_error(fn, line_num, _("'%s' does not take a value"), linebuf);
+ if (cip->call) cip->call(cip,NULL);
+ else
+ *cip->iassignto = cip->arg_int;
+ }
+ }
+ if (ferror(file))
+ ohshite(_("read error in configuration file '%.255s'"), fn);
+ if (fclose(file))
+ ohshite(_("error closing configuration file '%.255s'"), fn);
+}
+
+static int
+valid_config_filename(const struct dirent *dent)
+{
+ const char *c;
+
+ if (dent->d_name[0] == '.')
+ return 0;
+
+ for (c = dent->d_name; *c; c++)
+ if (!c_isalnum(*c) && *c != '_' && *c != '-')
+ return 0;
+
+ if (*c == '\0')
+ return 1;
+ else
+ return 0;
+}
+
+static void
+dpkg_options_load_dir(const char *prog, const struct cmdinfo *cmdinfos)
+{
+ char *dirname;
+ struct dirent **dlist;
+ int dlist_n, i;
+
+ dirname = str_fmt("%s/%s.cfg.d", CONFIGDIR, prog);
+
+ dlist_n = scandir(dirname, &dlist, valid_config_filename, alphasort);
+ if (dlist_n < 0) {
+ if (errno == ENOENT) {
+ free(dirname);
+ return;
+ } else
+ ohshite(_("error opening configuration directory '%s'"), dirname);
+ }
+
+ for (i = 0; i < dlist_n; i++) {
+ char *filename;
+
+ filename = str_fmt("%s/%s", dirname, dlist[i]->d_name);
+ dpkg_options_load_file(filename, cmdinfos);
+
+ free(dlist[i]);
+ free(filename);
+ }
+
+ free(dirname);
+ free(dlist);
+}
+
+void
+dpkg_options_load(const char *prog, const struct cmdinfo *cmdinfos)
+{
+ char *home, *file;
+
+ dpkg_options_load_dir(prog, cmdinfos);
+
+ file = str_fmt("%s/%s.cfg", CONFIGDIR, prog);
+ dpkg_options_load_file(file, cmdinfos);
+ free(file);
+
+ home = getenv("HOME");
+ if (home != NULL) {
+ file = str_fmt("%s/.%s.cfg", home, prog);
+ dpkg_options_load_file(file, cmdinfos);
+ free(file);
+ }
+}
+
+void
+dpkg_options_parse(const char *const **argvp, const struct cmdinfo *cmdinfos,
+ const char *help_str)
+{
+ const struct cmdinfo *cip;
+ const char *p, *value;
+ int l;
+
+ if (**argvp == NULL)
+ badusage(_("missing program name in argv[0]"));
+
+ printforhelp = help_str;
+
+ ++(*argvp);
+ while ((p = **argvp) && p[0] == '-' && p[1] != '\0') {
+ ++(*argvp);
+ if (strcmp(p, "--") == 0)
+ break;
+ if (*++p == '-') {
+ ++p; value=NULL;
+ for (cip= cmdinfos;
+ cip->olong || cip->oshort;
+ cip++) {
+ if (!cip->olong) continue;
+ if (strcmp(p, cip->olong) == 0)
+ break;
+ l= strlen(cip->olong);
+ if (strncmp(p, cip->olong, l) == 0 &&
+ (p[l]== ((cip->takesvalue==2) ? '-' : '='))) { value=p+l+1; break; }
+ }
+ if (!cip->olong) badusage(_("unknown option --%s"),p);
+ if (cip->takesvalue) {
+ if (!value) {
+ value= *(*argvp)++;
+ if (!value) badusage(_("--%s option takes a value"),cip->olong);
+ }
+ if (cip->call) cip->call(cip,value);
+ else *cip->sassignto= value;
+ } else {
+ if (value) badusage(_("--%s option does not take a value"),cip->olong);
+ if (cip->call) cip->call(cip,NULL);
+ else
+ *cip->iassignto = cip->arg_int;
+ }
+ } else {
+ while (*p) {
+ for (cip= cmdinfos; (cip->olong || cip->oshort) && *p != cip->oshort; cip++);
+ if (!cip->oshort) badusage(_("unknown option -%c"),*p);
+ p++;
+ if (cip->takesvalue) {
+ if (!*p) {
+ value= *(*argvp)++;
+ if (!value) badusage(_("-%c option takes a value"),cip->oshort);
+ } else {
+ value= p; p="";
+ if (*value == '=') value++;
+ }
+ if (cip->call) cip->call(cip,value);
+ else *cip->sassignto= value;
+ } else {
+ if (*p == '=') badusage(_("-%c option does not take a value"),cip->oshort);
+ if (cip->call) cip->call(cip,NULL);
+ else
+ *cip->iassignto = cip->arg_int;
+ }
+ }
+ }
+ }
+}
+
+long
+dpkg_options_parse_arg_int(const struct cmdinfo *cmd, const char *str)
+{
+ long value;
+ char *end;
+
+ errno = 0;
+ value = strtol(str, &end, 0);
+ if (str == end || *end || value < 0 || value > INT_MAX || errno != 0) {
+ if (cmd->olong)
+ badusage(_("invalid integer for --%s: '%.250s'"), cmd->olong, str);
+ else
+ badusage(_("invalid integer for -%c: '%.250s'"), cmd->oshort, str);
+ }
+
+ return value;
+}
+
+void
+setobsolete(const struct cmdinfo *cip, const char *value)
+{
+ warning(_("obsolete option '--%s'"), cip->olong);
+}
+
+const struct cmdinfo *cipaction = NULL;
+
+/* XXX: This function is a hack. */
+static inline int
+option_short(int c)
+{
+ return c ? c : '\b';
+}
+
+void
+setaction(const struct cmdinfo *cip, const char *value)
+{
+ if (cipaction && cip)
+ badusage(_("conflicting actions -%c (--%s) and -%c (--%s)"),
+ option_short(cip->oshort), cip->olong,
+ option_short(cipaction->oshort), cipaction->olong);
+ cipaction = cip;
+ if (cip && cip->takesvalue == 2 && cip->sassignto)
+ *cipaction->sassignto = value;
+}
diff --git a/lib/dpkg/options.h b/lib/dpkg/options.h
new file mode 100644
index 0000000..c08f286
--- /dev/null
+++ b/lib/dpkg/options.h
@@ -0,0 +1,98 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * options.h - option parsing functions
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_OPTIONS_H
+#define LIBDPKG_OPTIONS_H
+
+#include <dpkg/macros.h>
+#include <dpkg/dpkg-db.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup options Option parsing
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+typedef int action_func(const char *const *argv);
+
+struct cmdinfo {
+ const char *olong;
+ char oshort;
+
+ /*
+ * 0 = Normal (-o, --option)
+ * 1 = Standard value (-o=value, --option=value or
+ * -o value, --option value)
+ * 2 = Option string continued (--option-value)
+ */
+ int takesvalue;
+ int *iassignto;
+ const char **sassignto;
+ void (*call)(const struct cmdinfo*, const char *value);
+
+ int arg_int;
+ void *arg_ptr;
+
+ action_func *action;
+};
+
+void badusage(const char *fmt, ...) DPKG_ATTR_NORET DPKG_ATTR_PRINTF(1);
+
+#define MAX_CONFIG_LINE 1024
+
+void dpkg_options_load(const char *prog, const struct cmdinfo *cmdinfos);
+void dpkg_options_parse(const char *const **argvp,
+ const struct cmdinfo *cmdinfos, const char *help_str);
+
+long dpkg_options_parse_arg_int(const struct cmdinfo *cmd, const char *str);
+
+struct pkginfo *
+dpkg_options_parse_pkgname(const struct cmdinfo *cmd, const char *name);
+
+/**
+ * Current cmdinfo action.
+ */
+extern const struct cmdinfo *cipaction;
+
+void setaction(const struct cmdinfo *cip, const char *value);
+void setobsolete(const struct cmdinfo *cip, const char *value);
+
+#define ACTION(longopt, shortopt, code, func) \
+ { longopt, shortopt, 0, NULL, NULL, setaction, code, NULL, func }
+#define ACTION_MUX(longopt, shortopt, code, func, strvar) \
+ { longopt, shortopt, 2, NULL, strvar, setaction, code, NULL, func }
+#define OBSOLETE(longopt, shortopt) \
+ { longopt, shortopt, 0, NULL, NULL, setobsolete, 0, NULL, NULL }
+
+void
+set_instdir(const struct cmdinfo *cip, const char *value);
+void
+set_admindir(const struct cmdinfo *cip, const char *value);
+void
+set_root(const struct cmdinfo *cip, const char *value);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_OPTIONS_H */
diff --git a/lib/dpkg/pager.c b/lib/dpkg/pager.c
new file mode 100644
index 0000000..08eb8d0
--- /dev/null
+++ b/lib/dpkg/pager.c
@@ -0,0 +1,145 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * pager.c - pager execution support
+ *
+ * Copyright © 2018 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include <dpkg/dpkg.h>
+#include <dpkg/i18n.h>
+#include <dpkg/string.h>
+#include <dpkg/subproc.h>
+#include <dpkg/command.h>
+#include <dpkg/pager.h>
+
+static bool pager_enabled = true;
+
+void
+pager_enable(bool enable)
+{
+ pager_enabled = enable;
+}
+
+/**
+ * Get a suitable pager.
+ *
+ * @return A string representing a pager.
+ */
+const char *
+pager_get_exec(void)
+{
+ const char *pager;
+
+ if (!isatty(0) || !isatty(1))
+ return CAT;
+
+ pager = getenv("DPKG_PAGER");
+ if (str_is_unset(pager))
+ pager = getenv("PAGER");
+ if (str_is_unset(pager))
+ pager = DPKG_DEFAULT_PAGER;
+
+ return pager;
+}
+
+struct pager {
+ bool used;
+ const char *desc;
+ pid_t pid;
+ struct sigaction sigpipe;
+ int stdout_old;
+ int pipe[2];
+};
+
+struct pager *
+pager_spawn(const char *desc)
+{
+ struct sigaction sa;
+ struct pager *pager;
+ const char *exec;
+
+ pager = m_calloc(1, sizeof(*pager));
+ pager->used = isatty(0) && isatty(1);
+ pager->desc = desc;
+
+ exec = pager_get_exec();
+ if (strcmp(exec, CAT) == 0)
+ pager->used = false;
+
+ if (!pager_enabled)
+ pager->used = false;
+
+ if (!pager->used)
+ return pager;
+
+ m_pipe(pager->pipe);
+
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = SIG_IGN;
+ sa.sa_flags = 0;
+
+ sigaction(SIGPIPE, &sa, &pager->sigpipe);
+
+ pager->pid = subproc_fork();
+ if (pager->pid == 0) {
+ /* Set better defaults for less if not already set. */
+ setenv("LESS", "-FRSXMQ", 0);
+
+ m_dup2(pager->pipe[0], 0);
+ close(pager->pipe[0]);
+ close(pager->pipe[1]);
+
+ command_shell(exec, desc);
+ }
+
+ pager->stdout_old = m_dup(1);
+ m_dup2(pager->pipe[1], 1);
+ close(pager->pipe[0]);
+ close(pager->pipe[1]);
+
+ /* Force the output to fully buffered, because originally stdout was
+ * a tty, so it was set as line buffered. This way we send as much as
+ * possible to the pager, which will handle the output by itself. */
+ setvbuf(stdout, NULL, _IOFBF, 0);
+
+ return pager;
+}
+
+void
+pager_reap(struct pager *pager)
+{
+ if (!pager->used)
+ return;
+
+ m_dup2(pager->stdout_old, 1);
+ subproc_reap(pager->pid, pager->desc, SUBPROC_NOPIPE);
+
+ sigaction(SIGPIPE, &pager->sigpipe, NULL);
+
+ free(pager);
+}
diff --git a/lib/dpkg/pager.h b/lib/dpkg/pager.h
new file mode 100644
index 0000000..f9029d0
--- /dev/null
+++ b/lib/dpkg/pager.h
@@ -0,0 +1,54 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * pager.h - pager execution support
+ *
+ * Copyright © 2018 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_PAGER_H
+#define LIBDPKG_PAGER_H
+
+#include <stdbool.h>
+
+#include <dpkg/macros.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup pager Pager execution
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+struct pager;
+
+void
+pager_enable(bool enable);
+
+const char *
+pager_get_exec(void);
+
+struct pager *
+pager_spawn(const char *desc);
+
+void
+pager_reap(struct pager *pager);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_PAGER_H */
diff --git a/lib/dpkg/parse.c b/lib/dpkg/parse.c
new file mode 100644
index 0000000..a28983e
--- /dev/null
+++ b/lib/dpkg/parse.c
@@ -0,0 +1,926 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * parse.c - database file parsing, main package/field loop
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2006, 2008-2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef USE_MMAP
+#include <sys/mman.h>
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/macros.h>
+#include <dpkg/i18n.h>
+#include <dpkg/c-ctype.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/string.h>
+#include <dpkg/pkg.h>
+#include <dpkg/parsedump.h>
+#include <dpkg/fdio.h>
+#include <dpkg/buffer.h>
+
+/**
+ * Fields information.
+ */
+const struct fieldinfo fieldinfos[]= {
+ /* Note: Capitalization of field name strings is important. */
+ { FIELD("Package"), f_name, w_name },
+ { FIELD("Essential"), f_boolean, w_booleandefno, PKGIFPOFF(essential) },
+ { FIELD("Protected"), f_boolean, w_booleandefno, PKGIFPOFF(is_protected) },
+ { FIELD("Status"), f_status, w_status },
+ { FIELD("Priority"), f_priority, w_priority },
+ { FIELD("Section"), f_section, w_section },
+ { FIELD("Installed-Size"), f_charfield, w_charfield, PKGIFPOFF(installedsize) },
+ { FIELD("Origin"), f_charfield, w_charfield, PKGIFPOFF(origin) },
+ { FIELD("Maintainer"), f_charfield, w_charfield, PKGIFPOFF(maintainer) },
+ { FIELD("Bugs"), f_charfield, w_charfield, PKGIFPOFF(bugs) },
+ { FIELD("Architecture"), f_architecture, w_architecture },
+ { FIELD("Multi-Arch"), f_multiarch, w_multiarch, PKGIFPOFF(multiarch) },
+ { FIELD("Source"), f_charfield, w_charfield, PKGIFPOFF(source) },
+ { FIELD("Version"), f_version, w_version, PKGIFPOFF(version) },
+ { FIELD("Config-Version"), f_configversion, w_configversion },
+ { FIELD("Replaces"), f_dependency, w_dependency, dep_replaces },
+ { FIELD("Provides"), f_dependency, w_dependency, dep_provides },
+ { FIELD("Depends"), f_dependency, w_dependency, dep_depends },
+ { FIELD("Pre-Depends"), f_dependency, w_dependency, dep_predepends },
+ { FIELD("Recommends"), f_dependency, w_dependency, dep_recommends },
+ { FIELD("Suggests"), f_dependency, w_dependency, dep_suggests },
+ { FIELD("Breaks"), f_dependency, w_dependency, dep_breaks },
+ { FIELD("Conflicts"), f_dependency, w_dependency, dep_conflicts },
+ { FIELD("Enhances"), f_dependency, w_dependency, dep_enhances },
+ { FIELD("Conffiles"), f_conffiles, w_conffiles },
+ { FIELD("Filename"), f_archives, w_archives, ARCHIVEFOFF(name) },
+ { FIELD("Size"), f_archives, w_archives, ARCHIVEFOFF(size) },
+ { FIELD("MD5sum"), f_archives, w_archives, ARCHIVEFOFF(md5sum) },
+ { FIELD("MSDOS-Filename"), f_archives, w_archives, ARCHIVEFOFF(msdosname) },
+ { FIELD("Description"), f_charfield, w_charfield, PKGIFPOFF(description) },
+ { FIELD("Triggers-Pending"), f_trigpend, w_trigpend },
+ { FIELD("Triggers-Awaited"), f_trigaw, w_trigaw },
+
+ /* The following are the obsolete fields that get remapped to their
+ * modern forms, while emitting an obsolescence warning. */
+ { FIELD("Recommended"), f_obs_dependency, w_null, dep_recommends },
+ { FIELD("Optional"), f_obs_dependency, w_null, dep_suggests },
+ { FIELD("Class"), f_obs_class, w_null },
+ { FIELD("Revision"), f_obs_revision, w_null },
+ { FIELD("Package-Revision"), f_obs_revision, w_null },
+ { FIELD("Package_Revision"), f_obs_revision, w_null },
+ { NULL }
+};
+
+/**
+ * Package object being parsed.
+ *
+ * Structure used to hold the parsed data for the package being constructed,
+ * before it gets properly inserted into the package database.
+ */
+struct pkg_parse_object {
+ struct pkginfo *pkg;
+ struct pkgbin *pkgbin;
+};
+
+/**
+ * Parse the field and value into the package being constructed.
+ */
+static void
+pkg_parse_field(struct parsedb_state *ps, struct field_state *fs,
+ void *parse_obj)
+{
+ struct pkg_parse_object *pkg_obj = parse_obj;
+ const struct fieldinfo *fip;
+ int *ip;
+
+ for (fip = fieldinfos, ip = fs->fieldencountered; fip->name; fip++, ip++)
+ if (fip->namelen == (size_t)fs->fieldlen &&
+ strncasecmp(fip->name, fs->fieldstart, fs->fieldlen) == 0)
+ break;
+ if (fip->name) {
+ if ((*ip)++)
+ parse_error(ps,
+ _("duplicate value for '%s' field"), fip->name);
+
+ varbuf_set_buf(&fs->value, fs->valuestart, fs->valuelen);
+
+ fip->rcall(pkg_obj->pkg, pkg_obj->pkgbin, ps, fs->value.buf, fip);
+ } else {
+ struct arbitraryfield *arp, **larpp;
+
+ if (fs->fieldlen < 2)
+ parse_error(ps,
+ _("user-defined field name '%.*s' too short"),
+ fs->fieldlen, fs->fieldstart);
+ larpp = &pkg_obj->pkgbin->arbs;
+ while ((arp = *larpp) != NULL) {
+ if (strncasecmp(arp->name, fs->fieldstart, fs->fieldlen) == 0 &&
+ strlen(arp->name) == (size_t)fs->fieldlen)
+ parse_error(ps,
+ _("duplicate value for user-defined field '%.*s'"),
+ fs->fieldlen, fs->fieldstart);
+ larpp = &arp->next;
+ }
+ arp = nfmalloc(sizeof(*arp));
+ arp->name = nfstrnsave(fs->fieldstart, fs->fieldlen);
+ arp->value = nfstrnsave(fs->valuestart, fs->valuelen);
+ arp->next = NULL;
+ *larpp = arp;
+ }
+}
+
+/**
+ * Verify and fixup the package structure being constructed.
+ */
+static void
+pkg_parse_verify(struct parsedb_state *ps,
+ struct pkginfo *pkg, struct pkgbin *pkgbin)
+{
+ struct dependency *dep;
+ struct deppossi *dop;
+
+ parse_must_have_field(ps, pkg->set->name, "Package");
+
+ /* XXX: We need to check for status != PKG_STAT_HALFINSTALLED as while
+ * unpacking an unselected package, it will not have yet all data in
+ * place. But we cannot check for > PKG_STAT_HALFINSTALLED as
+ * PKG_STAT_CONFIGFILES always should have those fields. */
+ if ((ps->flags & pdb_recordavailable) ||
+ (pkg->status != PKG_STAT_NOTINSTALLED &&
+ pkg->status != PKG_STAT_HALFINSTALLED)) {
+ parse_ensure_have_field(ps, &pkgbin->description, "Description");
+ parse_ensure_have_field(ps, &pkgbin->maintainer, "Maintainer");
+ parse_must_have_field(ps, pkgbin->version.version, "Version");
+ }
+
+ /* XXX: Versions before dpkg 1.10.19 did not preserve the Architecture
+ * field in the status file. So there's still live systems with packages
+ * in PKG_STAT_CONFIGFILES, ignore those too for now. */
+ if ((ps->flags & pdb_recordavailable) ||
+ pkg->status > PKG_STAT_HALFINSTALLED) {
+ /* We always want usable architecture information (as long as the package
+ * is in such a state that it makes sense), so that it can be used safely
+ * on string comparisons and the like. */
+ if (pkgbin->arch->type == DPKG_ARCH_NONE)
+ parse_warn(ps, _("missing '%s' field"), "Architecture");
+ else if (pkgbin->arch->type == DPKG_ARCH_EMPTY)
+ parse_warn(ps, _("empty value for '%s' field"), "Architecture");
+ }
+ /* Mark missing architectures as empty, to distinguish these from
+ * unused slots in the db. */
+ if (pkgbin->arch->type == DPKG_ARCH_NONE)
+ pkgbin->arch = dpkg_arch_get(DPKG_ARCH_EMPTY);
+
+ if (pkgbin->arch->type == DPKG_ARCH_EMPTY &&
+ pkgbin->multiarch == PKG_MULTIARCH_SAME)
+ parse_error(ps, _("package has '%s' field but is missing architecture"),
+ "Multi-Arch: same");
+ if (pkgbin->arch->type == DPKG_ARCH_ALL &&
+ pkgbin->multiarch == PKG_MULTIARCH_SAME)
+ parse_error(ps, _("package has '%s' field but is architecture '%s'"),
+ "Multi-Arch: same", "all");
+
+ /* Generate the cached fully qualified package name representation. */
+ pkgbin->pkgname_archqual = pkgbin_name_archqual(pkg, pkgbin);
+
+ /* Initialize deps to be arch-specific unless stated otherwise. */
+ for (dep = pkgbin->depends; dep; dep = dep->next)
+ for (dop = dep->list; dop; dop = dop->next)
+ if (!dop->arch)
+ dop->arch = pkgbin->arch;
+
+ /*
+ * Check the Config-Version information:
+ *
+ * If there is a Config-Version it is definitely to be used, but there
+ * should not be one if the package is ‘installed’ or ‘triggers-pending’
+ * (in which case the Version will be copied) or if the package is
+ * ‘not-installed’ (in which case there is no Config-Version).
+ */
+ if (!(ps->flags & pdb_recordavailable)) {
+ if (pkg->configversion.version) {
+ if (pkg->status == PKG_STAT_INSTALLED ||
+ pkg->status == PKG_STAT_NOTINSTALLED ||
+ pkg->status == PKG_STAT_TRIGGERSPENDING)
+ parse_error(ps,
+ _("'%s' field present for package with inappropriate '%s' field"),
+ "Config-Version", "Status");
+ } else {
+ if (pkg->status == PKG_STAT_INSTALLED ||
+ pkg->status == PKG_STAT_TRIGGERSPENDING)
+ pkg->configversion = pkgbin->version;
+ }
+ }
+
+ if (pkg->trigaw.head &&
+ (pkg->status <= PKG_STAT_CONFIGFILES ||
+ pkg->status >= PKG_STAT_TRIGGERSPENDING))
+ parse_error(ps,
+ _("package has status %s but triggers are awaited"),
+ pkg_status_name(pkg));
+ else if (pkg->status == PKG_STAT_TRIGGERSAWAITED && !pkg->trigaw.head)
+ parse_error(ps,
+ _("package has status %s but no triggers awaited"),
+ pkg_status_name(pkg));
+
+ if (pkg->trigpend_head &&
+ !(pkg->status == PKG_STAT_TRIGGERSPENDING ||
+ pkg->status == PKG_STAT_TRIGGERSAWAITED))
+ parse_error(ps,
+ _("package has status %s but triggers are pending"),
+ pkg_status_name(pkg));
+ else if (pkg->status == PKG_STAT_TRIGGERSPENDING && !pkg->trigpend_head)
+ parse_error(ps,
+ _("package has status %s but no triggers pending"),
+ pkg_status_name(pkg));
+
+ /* Note: There was a bug that could make a not-installed package have
+ * conffiles, so we check for them here and remove them (rather than
+ * calling it an error, which will do at some point). */
+ if (!(ps->flags & pdb_recordavailable) &&
+ pkg->status == PKG_STAT_NOTINSTALLED &&
+ pkgbin->conffiles) {
+ parse_warn(ps,
+ _("package has status %s and has conffiles, forgetting them"),
+ pkg_status_name(pkg));
+ pkgbin->conffiles = NULL;
+ }
+
+ /* Note: Mark not-installed leftover packages for automatic removal on
+ * next database dump. */
+ if (!(ps->flags & pdb_recordavailable) &&
+ pkg->status == PKG_STAT_NOTINSTALLED &&
+ pkg->eflag == PKG_EFLAG_OK &&
+ (pkg->want == PKG_WANT_PURGE ||
+ pkg->want == PKG_WANT_DEINSTALL)) {
+ pkg_set_want(pkg, PKG_WANT_UNKNOWN);
+ }
+
+ /* Note: Mark not-installed non-arch-qualified selections for automatic
+ * removal, as they do not make sense in a multiarch enabled world, and
+ * might cause those selections to be unreferencable from command-line
+ * interfaces when there's other more specific selections. */
+ if (ps->type == pdb_file_status &&
+ pkg->status == PKG_STAT_NOTINSTALLED &&
+ pkg->eflag == PKG_EFLAG_OK &&
+ pkg->want == PKG_WANT_INSTALL &&
+ pkgbin->arch->type == DPKG_ARCH_EMPTY)
+ pkg_set_want(pkg, PKG_WANT_UNKNOWN);
+
+ /* Note: Versions before dpkg 1.13.10 did not blank the Origin and Bugs
+ * fields, so there can be packages that should be garbage collected but
+ * are lingering around. Blank them to make sure we will forget all about
+ * them on the next database dump. */
+ if (!(ps->flags & pdb_recordavailable) &&
+ pkg->status == PKG_STAT_NOTINSTALLED &&
+ pkg->eflag == PKG_EFLAG_OK &&
+ pkg->want == PKG_WANT_UNKNOWN) {
+ pkgbin_blank(pkgbin);
+ }
+}
+
+struct pkgcount {
+ int single;
+ int multi;
+ int total;
+};
+
+static void
+parse_count_pkg_instance(struct pkgcount *count,
+ struct pkginfo *pkg, struct pkgbin *pkgbin)
+{
+ if (pkg->status == PKG_STAT_NOTINSTALLED)
+ return;
+
+ if (pkgbin->multiarch == PKG_MULTIARCH_SAME)
+ count->multi++;
+ else
+ count->single++;
+
+ count->total++;
+}
+
+/**
+ * Lookup the package set slot for the parsed package.
+ *
+ * Perform various checks, to make sure the database is always in a sane
+ * state, and to not allow breaking it.
+ */
+static struct pkgset *
+parse_find_set_slot(struct parsedb_state *ps,
+ struct pkginfo *new_pkg, struct pkgbin *new_pkgbin)
+{
+ struct pkgcount count = { .single = 0, .multi = 0, .total = 0 };
+ struct pkgset *set;
+ struct pkginfo *pkg;
+
+ set = pkg_hash_find_set(new_pkg->set->name);
+
+ /* Sanity checks: verify that the db is in a consistent state. */
+
+ if (ps->type == pdb_file_status)
+ parse_count_pkg_instance(&count, new_pkg, new_pkgbin);
+
+ count.total = 0;
+
+ for (pkg = &set->pkg; pkg; pkg = pkg->arch_next)
+ parse_count_pkg_instance(&count, pkg, &pkg->installed);
+
+ if (count.single > 1)
+ parse_error(ps, _("multiple non-coinstallable package instances present; "
+ "most probably due to an upgrade from an unofficial dpkg"));
+
+ if (count.single > 0 && count.multi > 0)
+ parse_error(ps, _("mixed non-coinstallable and coinstallable package "
+ "instances present; most probably due to an upgrade "
+ "from an unofficial dpkg"));
+
+ if (pkgset_installed_instances(set) != count.total)
+ internerr("in-core pkgset '%s' with inconsistent number of instances",
+ set->name);
+
+ return set;
+}
+
+/**
+ * Lookup the package slot for the parsed package.
+ *
+ * Cross-grading (i.e. switching arch) is only possible when parsing an
+ * update entry or when installing a new package.
+ *
+ * Most of the time each pkginfo in a pkgset has the same architecture for
+ * both the installed and available pkgbin members. But when cross-grading
+ * there's going to be a temporary discrepancy, because we reuse the single
+ * instance and fill the available pkgbin with the candidate pkgbin, until
+ * that is copied over the installed pkgbin.
+ *
+ * If there's 0 or > 1 package instances, then we match against the pkginfo
+ * slot architecture, because cross-grading is just not possible.
+ *
+ * If there's 1 instance, we are cross-grading and both installed and
+ * candidate are not PKG_MULTIARCH_SAME, we have to reuse the existing single
+ * slot regardless of the arch differing between the two. If we are not
+ * cross-grading, then we use the entry with the matching arch.
+ */
+static struct pkginfo *
+parse_find_pkg_slot(struct parsedb_state *ps,
+ struct pkginfo *new_pkg, struct pkgbin *new_pkgbin)
+{
+ struct pkgset *db_set;
+
+ db_set = parse_find_set_slot(ps, new_pkg, new_pkgbin);
+
+ if (ps->type == pdb_file_available) {
+ /* If there's a single package installed and the new package is not
+ * “Multi-Arch: same”, then we preserve the previous behavior of
+ * possible architecture switch, for example from native to all. */
+ if (pkgset_installed_instances(db_set) == 1 &&
+ new_pkgbin->multiarch != PKG_MULTIARCH_SAME)
+ return pkg_hash_get_singleton(db_set);
+ else
+ return pkg_hash_get_pkg(db_set, new_pkgbin->arch);
+ } else {
+ bool selection = false;
+
+ /* If the package is part of the status file, and it's not installed
+ * then this means it's just a selection. */
+ if (ps->type == pdb_file_status && new_pkg->status == PKG_STAT_NOTINSTALLED)
+ selection = true;
+
+ /* Verify we don't allow something that will mess up the db. */
+ if (pkgset_installed_instances(db_set) > 1 &&
+ !selection && new_pkgbin->multiarch != PKG_MULTIARCH_SAME)
+ ohshit(_("package %s (%s) with field '%s: %s' is not co-installable "
+ "with %s which has multiple installed instances"),
+ pkgbin_name(new_pkg, new_pkgbin, pnaw_always),
+ versiondescribe(&new_pkgbin->version, vdew_nonambig),
+ "Multi-Arch", multiarchinfos[new_pkgbin->multiarch].name,
+ db_set->name);
+
+ /* If we are parsing the status file, use a slot per arch. */
+ if (ps->type == pdb_file_status)
+ return pkg_hash_get_pkg(db_set, new_pkgbin->arch);
+
+ /* If we are doing an update, from the log or a new package, then
+ * handle cross-grades. */
+ if (pkgset_installed_instances(db_set) == 1) {
+ struct pkginfo *db_pkg;
+
+ db_pkg = pkg_hash_get_singleton(db_set);
+
+ if (db_pkg->installed.multiarch == PKG_MULTIARCH_SAME &&
+ new_pkgbin->multiarch == PKG_MULTIARCH_SAME)
+ return pkg_hash_get_pkg(db_set, new_pkgbin->arch);
+ else
+ return db_pkg;
+ } else {
+ return pkg_hash_get_pkg(db_set, new_pkgbin->arch);
+ }
+ }
+}
+
+/**
+ * Copy into the in-core database the package being constructed.
+ */
+static void
+pkg_parse_copy(struct parsedb_state *ps,
+ struct pkginfo *dst_pkg, struct pkgbin *dst_pkgbin,
+ struct pkginfo *src_pkg, struct pkgbin *src_pkgbin)
+{
+ /* Copy the priority and section across, but don't overwrite existing
+ * values if the pdb_weakclassification flag is set. */
+ if (str_is_set(src_pkg->section) &&
+ !((ps->flags & pdb_weakclassification) &&
+ str_is_set(dst_pkg->section)))
+ dst_pkg->section = src_pkg->section;
+ if (src_pkg->priority != PKG_PRIO_UNKNOWN &&
+ !((ps->flags & pdb_weakclassification) &&
+ dst_pkg->priority != PKG_PRIO_UNKNOWN)) {
+ dst_pkg->priority = src_pkg->priority;
+ if (src_pkg->priority == PKG_PRIO_OTHER)
+ dst_pkg->otherpriority = src_pkg->otherpriority;
+ }
+
+ /* Sort out the dependency mess. */
+ copy_dependency_links(dst_pkg, &dst_pkgbin->depends, src_pkgbin->depends,
+ (ps->flags & pdb_recordavailable) ? true : false);
+
+ /* Copy across data. */
+ memcpy(dst_pkgbin, src_pkgbin, sizeof(struct pkgbin));
+ if (!(ps->flags & pdb_recordavailable)) {
+ struct trigaw *ta;
+
+ pkg_set_want(dst_pkg, src_pkg->want);
+ pkg_copy_eflags(dst_pkg, src_pkg);
+ pkg_set_status(dst_pkg, src_pkg->status);
+ dst_pkg->configversion = src_pkg->configversion;
+ dst_pkg->archives = NULL;
+
+ dst_pkg->trigpend_head = src_pkg->trigpend_head;
+ dst_pkg->trigaw = src_pkg->trigaw;
+ for (ta = dst_pkg->trigaw.head; ta; ta = ta->sameaw.next) {
+ if (ta->aw != src_pkg)
+ internerr("trigger awaited package %s and origin package %s not linked properly",
+ pkg_name(ta->aw, pnaw_always),
+ pkgbin_name(src_pkg, src_pkgbin, pnaw_always));
+ ta->aw = dst_pkg;
+ /* ->othertrigaw_head is updated by trig_note_aw in *(pkg_hash_find())
+ * rather than in dst_pkg. */
+ }
+ } else if (!(ps->flags & pdb_ignore_archives)) {
+ dst_pkg->archives = src_pkg->archives;
+ }
+}
+
+/**
+ * Return a descriptive parser type.
+ */
+static enum parsedbtype
+parse_get_type(struct parsedb_state *ps, enum parsedbflags flags)
+{
+ if (flags & pdb_recordavailable) {
+ if (flags & pdb_single_stanza)
+ return pdb_file_control;
+ else
+ return pdb_file_available;
+ } else {
+ if (flags & pdb_single_stanza)
+ return pdb_file_update;
+ else
+ return pdb_file_status;
+ }
+}
+
+/**
+ * Create a new deb822 parser context.
+ */
+struct parsedb_state *
+parsedb_new(const char *filename, int fd, enum parsedbflags flags)
+{
+ struct parsedb_state *ps;
+
+ ps = m_malloc(sizeof(*ps));
+ ps->err = DPKG_ERROR_OBJECT;
+ ps->errmsg = VARBUF_OBJECT;
+ ps->filename = filename;
+ ps->type = parse_get_type(ps, flags);
+ ps->flags = flags;
+ ps->fd = fd;
+ ps->lno = 0;
+ ps->data = NULL;
+ ps->dataptr = NULL;
+ ps->endptr = NULL;
+ ps->pkg = NULL;
+ ps->pkgbin = NULL;
+
+ return ps;
+}
+
+/**
+ * Open a file for deb822 parsing.
+ */
+struct parsedb_state *
+parsedb_open(const char *filename, enum parsedbflags flags)
+{
+ struct parsedb_state *ps;
+ int fd;
+
+ /* Special case stdin handling. */
+ if (flags & pdb_dash_is_stdin && strcmp(filename, "-") == 0)
+ return parsedb_new(filename, STDIN_FILENO, flags);
+
+ fd = open(filename, O_RDONLY);
+ if (fd == -1 && !(errno == ENOENT && (flags & pdb_allow_empty)))
+ ohshite(_("failed to open package info file '%.255s' for reading"),
+ filename);
+
+ ps = parsedb_new(filename, fd, flags | pdb_close_fd);
+
+ push_cleanup(cu_closefd, ~ehflag_normaltidy, 1, &ps->fd);
+
+ return ps;
+}
+
+/**
+ * Load data for package deb822 style parsing.
+ */
+void
+parsedb_load(struct parsedb_state *ps)
+{
+ struct stat st;
+
+ if (ps->fd < 0 && (ps->flags & pdb_allow_empty))
+ return;
+
+ if (fstat(ps->fd, &st) == -1)
+ ohshite(_("can't stat package info file '%.255s'"), ps->filename);
+
+ if (S_ISFIFO(st.st_mode)) {
+ struct varbuf buf = VARBUF_INIT;
+ struct dpkg_error err;
+ off_t size;
+
+ size = fd_vbuf_copy(ps->fd, &buf, -1, &err);
+ if (size < 0)
+ ohshit(_("reading package info file '%s': %s"), ps->filename, err.str);
+
+ varbuf_end_str(&buf);
+
+ ps->dataptr = varbuf_detach(&buf);
+ ps->endptr = ps->dataptr + size;
+ } else if (st.st_size > 0) {
+#ifdef USE_MMAP
+ ps->dataptr = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, ps->fd, 0);
+ if (ps->dataptr == MAP_FAILED)
+ ohshite(_("can't mmap package info file '%.255s'"), ps->filename);
+#else
+ ps->dataptr = m_malloc(st.st_size);
+
+ if (fd_read(ps->fd, ps->dataptr, st.st_size) < 0)
+ ohshite(_("reading package info file '%.255s'"), ps->filename);
+#endif
+ ps->endptr = ps->dataptr + st.st_size;
+ } else {
+ ps->dataptr = ps->endptr = NULL;
+ }
+ ps->data = ps->dataptr;
+}
+
+/**
+ * Parse an RFC-822 style stanza.
+ */
+bool
+parse_stanza(struct parsedb_state *ps, struct field_state *fs,
+ parse_field_func *parse_field, void *parse_obj)
+{
+ int c = '\0';
+
+ /* Skip adjacent new lines. */
+ while (!parse_at_eof(ps)) {
+ c = parse_getc(ps);
+ if (c != '\n' && c != MSDOS_EOF_CHAR)
+ break;
+ ps->lno++;
+ }
+
+ /* Nothing relevant parsed, bail out. */
+ if (parse_at_eof(ps))
+ return false;
+
+ /* Loop per field. */
+ for (;;) {
+ bool blank_line;
+
+ /* Scan field name. */
+ fs->fieldstart = ps->dataptr - 1;
+ while (!parse_at_eof(ps) && !c_isspace(c) && c != ':' && c != MSDOS_EOF_CHAR)
+ c = parse_getc(ps);
+ fs->fieldlen = ps->dataptr - fs->fieldstart - 1;
+ if (fs->fieldlen == 0)
+ parse_error(ps, _("empty field name"));
+ if (fs->fieldstart[0] == '-')
+ parse_error(ps, _("field name '%.*s' cannot start with hyphen"),
+ fs->fieldlen, fs->fieldstart);
+
+ /* Skip spaces before ‘:’. */
+ while (!parse_at_eof(ps) && c != '\n' && c_isspace(c))
+ c = parse_getc(ps);
+
+ /* Validate ‘:’. */
+ if (parse_at_eof(ps))
+ parse_error(ps, _("end of file after field name '%.*s'"),
+ fs->fieldlen, fs->fieldstart);
+ if (c == '\n')
+ parse_error(ps,
+ _("newline in field name '%.*s'"), fs->fieldlen, fs->fieldstart);
+ if (c == MSDOS_EOF_CHAR)
+ parse_error(ps, _("MSDOS end of file (^Z) in field name '%.*s'"),
+ fs->fieldlen, fs->fieldstart);
+ if (c != ':')
+ parse_error(ps,
+ _("field name '%.*s' must be followed by colon"),
+ fs->fieldlen, fs->fieldstart);
+
+ /* Skip space after ‘:’ but before value and EOL. */
+ while (!parse_at_eof(ps)) {
+ c = parse_getc(ps);
+ if (c == '\n' || !c_isspace(c))
+ break;
+ }
+ if (parse_at_eof(ps))
+ parse_error(ps, _("end of file before value of field '%.*s' (missing final newline)"),
+ fs->fieldlen, fs->fieldstart);
+ if (c == MSDOS_EOF_CHAR)
+ parse_error(ps, _("MSDOS end of file (^Z) in value of field '%.*s' (missing newline?)"),
+ fs->fieldlen, fs->fieldstart);
+
+ blank_line = false;
+
+ /* Scan field value. */
+ fs->valuestart = ps->dataptr - 1;
+ for (;;) {
+ if (c == '\n' || c == MSDOS_EOF_CHAR) {
+ if (blank_line) {
+ if (ps->flags & pdb_lax_stanza_parser)
+ parse_warn(ps, _("blank line in value of field '%.*s'"),
+ fs->fieldlen, fs->fieldstart);
+ else
+ parse_error(ps, _("blank line in value of field '%.*s'"),
+ fs->fieldlen, fs->fieldstart);
+ }
+ ps->lno++;
+
+ if (parse_at_eof(ps))
+ break;
+ c = parse_getc(ps);
+
+ /* Found double EOL, or start of new field. */
+ if (parse_at_eof(ps) || c == '\n' || !c_isspace(c))
+ break;
+
+ parse_ungetc(c, ps);
+ blank_line = true;
+ } else if (blank_line && !c_isspace(c)) {
+ blank_line = false;
+ }
+
+ if (parse_at_eof(ps))
+ parse_error(ps, _("end of file during value of field '%.*s' (missing final newline)"),
+ fs->fieldlen, fs->fieldstart);
+
+ c = parse_getc(ps);
+ }
+ fs->valuelen = ps->dataptr - fs->valuestart - 1;
+
+ /* Trim ending space on value. */
+ while (fs->valuelen && c_isspace(*(fs->valuestart + fs->valuelen - 1)))
+ fs->valuelen--;
+
+ parse_field(ps, fs, parse_obj);
+
+ if (parse_at_eof(ps) || c == '\n' || c == MSDOS_EOF_CHAR)
+ break;
+ } /* Loop per field. */
+
+ if (c == '\n')
+ ps->lno++;
+
+ return true;
+}
+
+/**
+ * Teardown a package deb822 parser context.
+ */
+void
+parsedb_close(struct parsedb_state *ps)
+{
+ if (ps->flags & pdb_close_fd) {
+ pop_cleanup(ehflag_normaltidy);
+
+ if (ps->fd >= 0 && close(ps->fd) < 0)
+ ohshite(_("failed to close after read: '%.255s'"), ps->filename);
+ }
+
+ if (ps->data != NULL) {
+#ifdef USE_MMAP
+ munmap(ps->data, ps->endptr - ps->data);
+#else
+ free(ps->data);
+#endif
+ }
+ dpkg_error_destroy(&ps->err);
+ varbuf_destroy(&ps->errmsg);
+ free(ps);
+}
+
+/**
+ * Parse deb822 style package data from a buffer.
+ *
+ * donep may be NULL.
+ * If donep is not NULL only one package's information is expected.
+ */
+int
+parsedb_parse(struct parsedb_state *ps, struct pkginfo **donep)
+{
+ struct pkgset tmp_set;
+ struct pkginfo *new_pkg, *db_pkg;
+ struct pkgbin *new_pkgbin, *db_pkgbin;
+ struct pkg_parse_object pkg_obj;
+ int fieldencountered[array_count(fieldinfos)];
+ int pdone;
+ struct field_state fs;
+
+ if (ps->data == NULL && (ps->flags & pdb_allow_empty))
+ return 0;
+
+ memset(&fs, 0, sizeof(fs));
+ fs.fieldencountered = fieldencountered;
+
+ new_pkg = &tmp_set.pkg;
+ if (ps->flags & pdb_recordavailable)
+ new_pkgbin = &new_pkg->available;
+ else
+ new_pkgbin = &new_pkg->installed;
+
+ ps->pkg = new_pkg;
+ ps->pkgbin = new_pkgbin;
+
+ pkg_obj.pkg = new_pkg;
+ pkg_obj.pkgbin = new_pkgbin;
+
+ pdone= 0;
+
+ /* Loop per package. */
+ for (;;) {
+ memset(fieldencountered, 0, sizeof(fieldencountered));
+ pkgset_blank(&tmp_set);
+
+ if (!parse_stanza(ps, &fs, pkg_parse_field, &pkg_obj))
+ break;
+
+ if (pdone && donep)
+ parse_error(ps,
+ _("several package info entries found, only one allowed"));
+
+ pkg_parse_verify(ps, new_pkg, new_pkgbin);
+
+ db_pkg = parse_find_pkg_slot(ps, new_pkg, new_pkgbin);
+ if (ps->flags & pdb_recordavailable)
+ db_pkgbin = &db_pkg->available;
+ else
+ db_pkgbin = &db_pkg->installed;
+
+ if (((ps->flags & pdb_ignoreolder) || ps->type == pdb_file_available) &&
+ dpkg_version_is_informative(&db_pkgbin->version) &&
+ dpkg_version_compare(&new_pkgbin->version, &db_pkgbin->version) < 0)
+ continue;
+
+ pkg_parse_copy(ps, db_pkg, db_pkgbin, new_pkg, new_pkgbin);
+
+ if (donep)
+ *donep = db_pkg;
+ pdone++;
+ if (parse_at_eof(ps))
+ break;
+ }
+
+ varbuf_destroy(&fs.value);
+ if (donep && !pdone)
+ ohshit(_("no package information in '%.255s'"), ps->filename);
+
+ return pdone;
+}
+
+/**
+ * Parse a deb822 style file.
+ *
+ * donep may be NULL.
+ * If donep is not NULL only one package's information is expected.
+ */
+int
+parsedb(const char *filename, enum parsedbflags flags, struct pkginfo **pkgp)
+{
+ struct parsedb_state *ps;
+ int count;
+
+ ps = parsedb_open(filename, flags);
+ parsedb_load(ps);
+ count = parsedb_parse(ps, pkgp);
+ parsedb_close(ps);
+
+ return count;
+}
+
+/**
+ * Copy dependency links structures.
+ *
+ * This routine is used to update the ‘reverse’ dependency pointers when
+ * new ‘forwards’ information has been constructed. It first removes all
+ * the links based on the old information. The old information starts in
+ * *updateme; after much brou-ha-ha the reverse structures are created
+ * and *updateme is set to the value from newdepends.
+ *
+ * @param pkg The package we're doing this for. This is used to construct
+ * correct uplinks.
+ * @param updateme The forwards dependency pointer that we are to update.
+ * This starts out containing the old forwards info, which we use to
+ * unthread the old reverse links. After we're done it is updated.
+ * @param newdepends The value that we ultimately want to have in updateme.
+ * @param available The pkgbin to modify, available or installed.
+ *
+ * It is likely that the backward pointer for the package in question
+ * (‘depended’) will be updated by this routine, but this will happen by
+ * the routine traversing the dependency data structures. It doesn't need
+ * to be told where to update that; just mentioned here as something that
+ * one should be cautious about.
+ */
+void copy_dependency_links(struct pkginfo *pkg,
+ struct dependency **updateme,
+ struct dependency *newdepends,
+ bool available)
+{
+ struct dependency *dyp;
+ struct deppossi *dop, **revdeps;
+
+ /* Delete ‘backward’ (‘depended’) links from other packages to
+ * dependencies listed in old version of this one. We do this by
+ * going through all the dependencies in the old version of this
+ * one and following them down to find which deppossi nodes to
+ * remove. */
+ for (dyp= *updateme; dyp; dyp= dyp->next) {
+ for (dop= dyp->list; dop; dop= dop->next) {
+ if (dop->rev_prev)
+ dop->rev_prev->rev_next = dop->rev_next;
+ else
+ if (available)
+ dop->ed->depended.available = dop->rev_next;
+ else
+ dop->ed->depended.installed = dop->rev_next;
+ if (dop->rev_next)
+ dop->rev_next->rev_prev = dop->rev_prev;
+ }
+ }
+
+ /* Now fill in new ‘ed’ links from other packages to dependencies
+ * listed in new version of this one, and set our uplinks correctly. */
+ for (dyp= newdepends; dyp; dyp= dyp->next) {
+ dyp->up= pkg;
+ for (dop= dyp->list; dop; dop= dop->next) {
+ revdeps = available ? &dop->ed->depended.available :
+ &dop->ed->depended.installed;
+ dop->rev_next = *revdeps;
+ dop->rev_prev = NULL;
+ if (*revdeps)
+ (*revdeps)->rev_prev = dop;
+ *revdeps = dop;
+ }
+ }
+
+ /* Finally, we fill in the new value. */
+ *updateme= newdepends;
+}
diff --git a/lib/dpkg/parsedump.h b/lib/dpkg/parsedump.h
new file mode 100644
index 0000000..c43da7f
--- /dev/null
+++ b/lib/dpkg/parsedump.h
@@ -0,0 +1,179 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * parsedump.h - declarations for in-core database reading/writing
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2001 Wichert Akkerman
+ * Copyright © 2008-2011 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_PARSEDUMP_H
+#define LIBDPKG_PARSEDUMP_H
+
+#include <stdint.h>
+
+#include <dpkg/error.h>
+#include <dpkg/dpkg-db.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup parsedump In-core package database parsing and reading
+ * @ingroup dpkg-public
+ * @{
+ */
+
+struct fieldinfo;
+
+/**
+ * Parse action.
+ */
+enum parsedbtype {
+ pdb_file_update,
+ pdb_file_status,
+ pdb_file_control,
+ pdb_file_available,
+};
+
+struct parsedb_state {
+ enum parsedbtype type;
+ enum parsedbflags flags;
+ struct dpkg_error err;
+ struct varbuf errmsg;
+ struct pkginfo *pkg;
+ struct pkgbin *pkgbin;
+ char *data;
+ char *dataptr;
+ char *endptr;
+ const char *filename;
+ int fd;
+ int lno;
+};
+
+#define parse_at_eof(ps) ((ps)->dataptr >= (ps)->endptr)
+#define parse_getc(ps) *(ps)->dataptr++
+#define parse_ungetc(c, ps) (ps)->dataptr--
+
+struct field_state {
+ const char *fieldstart;
+ const char *valuestart;
+ struct varbuf value;
+ int fieldlen;
+ int valuelen;
+ int *fieldencountered;
+};
+
+struct parsedb_state *
+parsedb_new(const char *filename, int fd, enum parsedbflags flags);
+struct parsedb_state *
+parsedb_open(const char *filename, enum parsedbflags flags);
+void
+parsedb_load(struct parsedb_state *ps);
+int
+parsedb_parse(struct parsedb_state *ps, struct pkginfo **pkgp);
+void
+parsedb_close(struct parsedb_state *ps);
+
+typedef void parse_field_func(struct parsedb_state *ps, struct field_state *fs,
+ void *parse_obj);
+
+bool parse_stanza(struct parsedb_state *ps, struct field_state *fs,
+ parse_field_func *parse_field, void *parse_obj);
+
+#define STRUCTFIELD(klass, off, type) (*(type *)((uintptr_t)(klass) + (off)))
+
+#define PKGIFPOFF(f) (offsetof(struct pkgbin, f))
+#define ARCHIVEFOFF(f) (offsetof(struct archivedetails, f))
+
+typedef void freadfunction(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ struct parsedb_state *ps,
+ const char *value, const struct fieldinfo *fip);
+freadfunction f_name;
+freadfunction f_charfield;
+freadfunction f_priority;
+freadfunction f_obs_class;
+freadfunction f_section;
+freadfunction f_status;
+freadfunction f_boolean;
+freadfunction f_dependency;
+freadfunction f_conffiles;
+freadfunction f_version;
+freadfunction f_obs_revision;
+freadfunction f_obs_dependency;
+freadfunction f_configversion;
+freadfunction f_multiarch;
+freadfunction f_architecture;
+freadfunction f_trigpend, f_trigaw;
+freadfunction f_archives;
+
+enum DPKG_ATTR_ENUM_FLAGS fwriteflags {
+ /** No flags. */
+ fw_none = 0,
+ /** Print field header and trailing newline. */
+ fw_printheader = DPKG_BIT(0),
+};
+
+typedef void fwritefunction(struct varbuf*,
+ const struct pkginfo *, const struct pkgbin *,
+ enum fwriteflags flags, const struct fieldinfo*);
+fwritefunction w_name, w_charfield, w_priority, w_section, w_status, w_configversion;
+fwritefunction w_version, w_null, w_booleandefno, w_dependency, w_conffiles;
+fwritefunction w_multiarch;
+fwritefunction w_architecture;
+fwritefunction w_trigpend, w_trigaw;
+fwritefunction w_archives;
+
+void
+varbuf_add_arbfield(struct varbuf *vb, const struct arbitraryfield *arbfield,
+ enum fwriteflags flags);
+
+#define FIELD(name) name, sizeof(name) - 1
+
+struct fieldinfo {
+ const char *name;
+ size_t namelen;
+ freadfunction *rcall;
+ fwritefunction *wcall;
+ size_t integer;
+};
+
+int
+parse_db_version(struct parsedb_state *ps,
+ struct dpkg_version *version, const char *value)
+ DPKG_ATTR_REQRET;
+
+void parse_error(struct parsedb_state *ps, const char *fmt, ...)
+ DPKG_ATTR_NORET DPKG_ATTR_PRINTF(2);
+void parse_warn(struct parsedb_state *ps, const char *fmt, ...)
+ DPKG_ATTR_PRINTF(2);
+void
+parse_problem(struct parsedb_state *ps, const char *fmt, ...)
+ DPKG_ATTR_PRINTF(2);
+
+void parse_must_have_field(struct parsedb_state *ps,
+ const char *value, const char *what);
+void parse_ensure_have_field(struct parsedb_state *ps,
+ const char **value, const char *what);
+
+#define MSDOS_EOF_CHAR '\032' /* ^Z */
+
+extern const struct fieldinfo fieldinfos[];
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_PARSEDUMP_H */
diff --git a/lib/dpkg/parsehelp.c b/lib/dpkg/parsehelp.c
new file mode 100644
index 0000000..63a36f5
--- /dev/null
+++ b/lib/dpkg/parsehelp.c
@@ -0,0 +1,343 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * parsehelp.c - helpful routines for parsing and writing
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2006-2012 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/c-ctype.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/string.h>
+#include <dpkg/error.h>
+#include <dpkg/parsedump.h>
+
+static DPKG_ATTR_VPRINTF(2) const char *
+parse_error_msg(struct parsedb_state *ps, const char *fmt, va_list args)
+{
+ struct varbuf *vb = &ps->errmsg;
+
+ varbuf_reset(vb);
+
+ if (ps->pkg && ps->pkg->set->name)
+ varbuf_printf(vb, _("parsing file '%s' near line %d package '%s':\n "),
+ ps->filename, ps->lno,
+ pkgbin_name(ps->pkg, ps->pkgbin, pnaw_nonambig));
+ else
+ varbuf_printf(vb, _("parsing file '%.255s' near line %d:\n "),
+ ps->filename, ps->lno);
+
+ varbuf_vprintf(vb, fmt, args);
+
+ return vb->buf;
+}
+
+void
+parse_error(struct parsedb_state *ps, const char *fmt, ...)
+{
+ va_list args;
+ const char *str;
+
+ va_start(args, fmt);
+ str = parse_error_msg(ps, fmt, args);
+ va_end(args);
+
+ ohshit("%s", str);
+}
+
+void
+parse_warn(struct parsedb_state *ps, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ warning("%s", parse_error_msg(ps, fmt, args));
+ va_end(args);
+}
+
+void
+parse_problem(struct parsedb_state *ps, const char *fmt, ...)
+{
+ va_list args;
+ const char *str;
+
+ va_start(args, fmt);
+ str = parse_error_msg(ps, fmt, args);
+ va_end(args);
+
+ if (ps->err.type == DPKG_MSG_WARN)
+ warning("%s: %s", str, ps->err.str);
+ else
+ ohshit("%s: %s", str, ps->err.str);
+}
+
+const struct fieldinfo *
+find_field_info(const struct fieldinfo *fields, const char *fieldname)
+{
+ const struct fieldinfo *field;
+
+ for (field = fields; field->name; field++)
+ if (strcasecmp(field->name, fieldname) == 0)
+ return field;
+
+ return NULL;
+}
+
+const struct arbitraryfield *
+find_arbfield_info(const struct arbitraryfield *arbs, const char *fieldname)
+{
+ const struct arbitraryfield *arbfield;
+
+ for (arbfield = arbs; arbfield; arbfield = arbfield->next)
+ if (strcasecmp(arbfield->name, fieldname) == 0)
+ return arbfield;
+
+ return NULL;
+}
+
+const char *
+pkg_name_is_illegal(const char *p)
+{
+ /* TODO: _ is deprecated, remove sometime. */
+ static const char alsoallowed[] = "-+._";
+ static char buf[150];
+ int c;
+
+ if (!*p) return _("may not be empty string");
+ if (!c_isalnum(*p))
+ return _("must start with an alphanumeric character");
+ while ((c = *p++) != '\0')
+ if (!c_isalnum(c) && !strchr(alsoallowed, c))
+ break;
+ if (!c) return NULL;
+
+ snprintf(buf, sizeof(buf), _(
+ "character '%c' not allowed (only letters, digits and characters '%s')"),
+ c, alsoallowed);
+ return buf;
+}
+
+void varbufversion
+(struct varbuf *vb,
+ const struct dpkg_version *version,
+ enum versiondisplayepochwhen vdew)
+{
+ switch (vdew) {
+ case vdew_never:
+ break;
+ case vdew_nonambig:
+ if (!version->epoch &&
+ (!version->version || !strchr(version->version,':')) &&
+ (!version->revision || !strchr(version->revision,':'))) break;
+ /* Fall through. */
+ case vdew_always:
+ varbuf_printf(vb, "%u:", version->epoch);
+ break;
+ default:
+ internerr("unknown versiondisplayepochwhen '%d'", vdew);
+ }
+ if (version->version)
+ varbuf_add_str(vb, version->version);
+ if (str_is_set(version->revision)) {
+ varbuf_add_char(vb, '-');
+ varbuf_add_str(vb, version->revision);
+ }
+}
+
+const char *versiondescribe
+(const struct dpkg_version *version,
+ enum versiondisplayepochwhen vdew)
+{
+ static struct varbuf bufs[10];
+ static int bufnum=0;
+
+ struct varbuf *vb;
+
+ if (!dpkg_version_is_informative(version))
+ return C_("version", "<none>");
+
+ vb= &bufs[bufnum]; bufnum++; if (bufnum == 10) bufnum= 0;
+ varbuf_reset(vb);
+ varbufversion(vb,version,vdew);
+ varbuf_end_str(vb);
+
+ return vb->buf;
+}
+
+const char *
+versiondescribe_c(const struct dpkg_version *version,
+ enum versiondisplayepochwhen vdew)
+{
+ struct dpkg_locale oldloc;
+ const char *str;
+
+ oldloc = dpkg_locale_switch_C();
+ str = versiondescribe(version, vdew);
+ dpkg_locale_switch_back(oldloc);
+
+ return str;
+}
+
+/**
+ * Parse a version string and check for invalid syntax.
+ *
+ * Distinguish between lax (warnings) and strict (error) parsing.
+ *
+ * @param rversion The parsed version.
+ * @param string The version string to parse.
+ * @param err The warning or error message if any.
+ *
+ * @retval 0 On success.
+ * @retval -1 On failure, and err is set accordingly.
+ */
+int
+parseversion(struct dpkg_version *rversion, const char *string,
+ struct dpkg_error *err)
+{
+ char *hyphen, *colon, *eepochcolon;
+ const char *end, *ptr;
+
+ /* Trim leading and trailing space. */
+ while (*string && c_isblank(*string))
+ string++;
+
+ if (!*string)
+ return dpkg_put_error(err, _("version string is empty"));
+
+ /* String now points to the first non-whitespace char. */
+ end = string;
+ /* Find either the end of the string, or a whitespace char. */
+ while (*end && !c_isblank(*end))
+ end++;
+ /* Check for extra chars after trailing space. */
+ ptr = end;
+ while (*ptr && c_isblank(*ptr))
+ ptr++;
+ if (*ptr)
+ return dpkg_put_error(err, _("version string has embedded spaces"));
+
+ colon= strchr(string,':');
+ if (colon) {
+ long epoch;
+
+ errno = 0;
+ epoch = strtol(string, &eepochcolon, 10);
+ if (string == eepochcolon)
+ return dpkg_put_error(err, _("epoch in version is empty"));
+ if (colon != eepochcolon)
+ return dpkg_put_error(err, _("epoch in version is not number"));
+ if (epoch < 0)
+ return dpkg_put_error(err, _("epoch in version is negative"));
+ if (epoch > INT_MAX || errno == ERANGE)
+ return dpkg_put_error(err, _("epoch in version is too big"));
+ if (!*++colon)
+ return dpkg_put_error(err, _("nothing after colon in version number"));
+ string= colon;
+ rversion->epoch= epoch;
+ } else {
+ rversion->epoch= 0;
+ }
+ rversion->version= nfstrnsave(string,end-string);
+ hyphen= strrchr(rversion->version,'-');
+ if (hyphen) {
+ *hyphen++ = '\0';
+
+ if (*hyphen == '\0')
+ return dpkg_put_error(err, _("revision number is empty"));
+ }
+ rversion->revision= hyphen ? hyphen : "";
+
+ /* XXX: Would be faster to use something like cisversion and cisrevision. */
+ ptr = rversion->version;
+ if (!*ptr)
+ return dpkg_put_error(err, _("version number is empty"));
+ if (!c_isdigit(*ptr++))
+ return dpkg_put_warn(err, _("version number does not start with digit"));
+ for (; *ptr; ptr++) {
+ if (!c_isdigit(*ptr) && !c_isalpha(*ptr) && strchr(".-+~:", *ptr) == NULL)
+ return dpkg_put_warn(err, _("invalid character in version number"));
+ }
+ for (ptr = rversion->revision; *ptr; ptr++) {
+ if (!c_isdigit(*ptr) && !c_isalpha(*ptr) && strchr(".+~", *ptr) == NULL)
+ return dpkg_put_warn(err, _("invalid character in revision number"));
+ }
+
+ return 0;
+}
+
+/**
+ * Parse a version string coming from a database file.
+ *
+ * It parses a version string, and prints a warning or an error depending
+ * on the parse options.
+ *
+ * @param ps The parsedb state.
+ * @param version The version to parse into.
+ * @param value The version string to parse from.
+ *
+ * @retval 0 On success, and err is reset.
+ * @retval -1 On failure, and err is set accordingly.
+ */
+int
+parse_db_version(struct parsedb_state *ps, struct dpkg_version *version,
+ const char *value)
+{
+ dpkg_error_destroy(&ps->err);
+
+ if (parseversion(version, value, &ps->err) == 0)
+ return 0;
+
+ /* If not in lax mode, turn everything into an error. */
+ if (!(ps->flags & pdb_lax_version_parser))
+ ps->err.type = DPKG_MSG_ERROR;
+
+ return -1;
+}
+
+void
+parse_must_have_field(struct parsedb_state *ps,
+ const char *value, const char *what)
+{
+ if (!value)
+ parse_error(ps, _("missing '%s' field"), what);
+ else if (!*value)
+ parse_error(ps, _("empty value for '%s' field"), what);
+}
+
+void
+parse_ensure_have_field(struct parsedb_state *ps,
+ const char **value, const char *what)
+{
+ static const char empty[] = "";
+
+ if (!*value) {
+ parse_warn(ps, _("missing '%s' field"), what);
+ *value = empty;
+ } else if (!**value) {
+ parse_warn(ps, _("empty value for '%s' field"), what);
+ }
+}
diff --git a/lib/dpkg/path-remove.c b/lib/dpkg/path-remove.c
new file mode 100644
index 0000000..6afb376
--- /dev/null
+++ b/lib/dpkg/path-remove.c
@@ -0,0 +1,158 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * path-remove.c - path removal functions
+ *
+ * Copyright © 1994-1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2007-2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/path.h>
+#include <dpkg/debug.h>
+#include <dpkg/subproc.h>
+
+int
+secure_unlink_statted(const char *pathname, const struct stat *stab)
+{
+ if (S_ISREG(stab->st_mode) ? (stab->st_mode & 07000) :
+ !(S_ISLNK(stab->st_mode) || S_ISDIR(stab->st_mode) ||
+ S_ISFIFO(stab->st_mode) || S_ISSOCK(stab->st_mode))) {
+ if (chmod(pathname, 0600))
+ return -1;
+ }
+
+ if (unlink(pathname))
+ return -1;
+
+ return 0;
+}
+
+/**
+ * Securely unlink a pathname.
+ *
+ * If the pathname to remove is:
+ *
+ * 1. a sticky or set-id file, or
+ * 2. an unknown object (i.e., not a file, link, directory, fifo or socket)
+ *
+ * we change its mode so that a malicious user cannot use it, even if it's
+ * linked to another file.
+ */
+int
+secure_unlink(const char *pathname)
+{
+ struct stat stab;
+
+ if (lstat(pathname, &stab))
+ return -1;
+
+ return secure_unlink_statted(pathname, &stab);
+}
+
+/**
+ * Securely remove a pathname.
+ *
+ * This is a secure version of remove(3) using secure_unlink() instead of
+ * unlink(2).
+ *
+ * @retval 0 On success.
+ * @retval -1 On failure, just like unlink(2) & rmdir(2).
+ */
+int
+secure_remove(const char *pathname)
+{
+ int rc, e;
+
+ if (!rmdir(pathname)) {
+ debug(dbg_eachfiledetail, "secure_remove '%s' rmdir OK",
+ pathname);
+ return 0;
+ }
+
+ if (errno != ENOTDIR) {
+ e = errno;
+ debug(dbg_eachfiledetail, "secure_remove '%s' rmdir %s",
+ pathname, strerror(e));
+ errno = e;
+ return -1;
+ }
+
+ rc = secure_unlink(pathname);
+ e = errno;
+ debug(dbg_eachfiledetail, "secure_remove '%s' unlink %s",
+ pathname, rc ? strerror(e) : "OK");
+ errno = e;
+
+ return rc;
+}
+
+/**
+ * Remove a pathname and anything below it.
+ *
+ * This function removes pathname and all its contents recursively.
+ */
+void
+path_remove_tree(const char *pathname)
+{
+ pid_t pid;
+ const char *u;
+
+ u = path_skip_slash_dotslash(pathname);
+ if (u[0] == '\0')
+ internerr("pathname '%s' reduces to nothing", pathname);
+
+ debug(dbg_eachfile, "%s '%s'", __func__, pathname);
+ if (!rmdir(pathname))
+ return; /* Deleted it OK, it was a directory. */
+ if (errno == ENOENT || errno == ELOOP)
+ return;
+ if (errno == ENOTDIR) {
+ /* Either it's a file, or one of the path components is. If
+ * one of the path components is this will fail again ... */
+ if (secure_unlink(pathname) == 0)
+ return; /* OK, it was. */
+ if (errno == ENOTDIR)
+ return;
+ }
+ /* Trying to remove a directory or a file on a read-only filesystem,
+ * even if non-existent, always returns EROFS. */
+ if (errno == EROFS) {
+ if (access(pathname, F_OK) < 0 && errno == ENOENT)
+ return;
+ errno = EROFS;
+ }
+ if (errno != ENOTEMPTY && errno != EEXIST) /* Huh? */
+ ohshite(_("unable to securely remove '%.255s'"), pathname);
+
+ pid = subproc_fork();
+ if (pid == 0) {
+ execlp(RM, "rm", "-rf", "--", pathname, NULL);
+ ohshite(_("unable to execute %s (%s)"),
+ _("rm command for cleanup"), RM);
+ }
+ debug(dbg_eachfile, "%s running rm -rf '%s'", __func__, pathname);
+ subproc_reap(pid, _("rm command for cleanup"), 0);
+}
diff --git a/lib/dpkg/path.c b/lib/dpkg/path.c
new file mode 100644
index 0000000..d7b20d6
--- /dev/null
+++ b/lib/dpkg/path.c
@@ -0,0 +1,170 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * path.c - path handling functions
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008-2012 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <dpkg/dpkg.h>
+#include <dpkg/string.h>
+#include <dpkg/path.h>
+
+/**
+ * Trim ‘/’ and ‘/.’ from the end of a pathname.
+ *
+ * The given string will get NUL-terminatd.
+ *
+ * @param path The pathname to trim.
+ *
+ * @return The size of the trimmed pathname.
+ */
+size_t
+path_trim_slash_slashdot(char *path)
+{
+ char *end;
+
+ if (str_is_unset(path))
+ return 0;
+
+ for (end = path + strlen(path) - 1; end - path >= 1; end--) {
+ if (*end == '/' || (*(end - 1) == '/' && *end == '.'))
+ *end = '\0';
+ else
+ break;
+ }
+
+ return end - path + 1;
+}
+
+/**
+ * Skip ‘/’ and ‘./’ from the beginning of a pathname.
+ *
+ * @param path The pathname to skip.
+ *
+ * @return The new beginning of the pathname.
+ */
+const char *
+path_skip_slash_dotslash(const char *path)
+{
+ while (path[0] == '/' || (path[0] == '.' && path[1] == '/'))
+ path++;
+
+ return path;
+}
+
+/**
+ * Return the last component of a pathname.
+ *
+ * @param path The pathname to get the base name from.
+ *
+ * @return A pointer to the last component inside pathname.
+ */
+const char *
+path_basename(const char *path)
+{
+ const char *last_slash;
+
+ last_slash = strrchr(path, '/');
+ if (last_slash == NULL)
+ return path;
+ else
+ return last_slash + 1;
+}
+
+/**
+ * Create a template for a temporary pathname.
+ *
+ * @param suffix The suffix to use for the template string.
+ *
+ * @return An allocated string with the created template.
+ */
+char *
+path_make_temp_template(const char *suffix)
+{
+ const char *tmpdir;
+
+ tmpdir = getenv("TMPDIR");
+ if (!tmpdir)
+ tmpdir = P_tmpdir;
+
+ return str_fmt("%s/%s.XXXXXX", tmpdir, suffix);
+}
+
+/**
+ * Escape characters in a pathname for safe locale printing.
+ *
+ * We need to quote paths so that they do not cause problems when printing
+ * them, for example with snprintf(3) which does not work if the format
+ * string contains %s and an argument has invalid characters for the
+ * current locale, it will then return -1.
+ *
+ * To simplify things, we just escape all 8 bit characters, instead of
+ * just invalid characters.
+ *
+ * @param dst The escaped destination string.
+ * @param src The source string to escape.
+ * @param n The size of the destination buffer.
+ *
+ * @return The destination string.
+ */
+char *
+path_quote_filename(char *dst, const char *src, size_t n)
+{
+ char *ret = dst;
+ ssize_t size = (ssize_t)n;
+
+ if (size == 0)
+ return ret;
+
+ while (*src) {
+ if (*src == '\\') {
+ size -= 2;
+ if (size <= 0)
+ break;
+
+ *dst++ = '\\';
+ *dst++ = '\\';
+ src++;
+ } else if (((*src) & 0x80) == '\0') {
+ size--;
+ if (size <= 0)
+ break;
+
+ *dst++ = *src++;
+ } else {
+ size -= 4;
+ if (size <= 0)
+ break;
+
+ sprintf(dst, "\\%03o",
+ *(const unsigned char *)src);
+ dst += 4;
+ src++;
+ }
+ }
+
+ *dst = '\0';
+
+ return ret;
+}
diff --git a/lib/dpkg/path.h b/lib/dpkg/path.h
new file mode 100644
index 0000000..3479a8b
--- /dev/null
+++ b/lib/dpkg/path.h
@@ -0,0 +1,55 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * path.h - path handling routines
+ *
+ * Copyright © 2008-2012, 2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_PATH_H
+#define LIBDPKG_PATH_H
+
+#include <sys/stat.h>
+
+#include <stddef.h>
+
+#include <dpkg/macros.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup path Path handling
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+size_t path_trim_slash_slashdot(char *path);
+const char *path_skip_slash_dotslash(const char *path);
+const char *path_basename(const char *path);
+char *path_quote_filename(char *dst, const char *src, size_t size);
+
+char *path_make_temp_template(const char *suffix);
+
+int secure_unlink_statted(const char *pathname, const struct stat *stab);
+int secure_unlink(const char *pathname);
+int secure_remove(const char *pathname);
+
+void path_remove_tree(const char *pathname);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_PATH_H */
diff --git a/lib/dpkg/perf.h b/lib/dpkg/perf.h
new file mode 100644
index 0000000..48e69cc
--- /dev/null
+++ b/lib/dpkg/perf.h
@@ -0,0 +1,77 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * perf.h - performance testing support
+ *
+ * Copyright © 2009-2019 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_PERF_H
+#define LIBDPKG_PERF_H
+
+#include <config.h>
+#include <compat.h>
+
+#include <time.h>
+#include <stdio.h>
+
+#define TEST_OMIT_VARIABLES
+#include <dpkg/test.h>
+
+DPKG_BEGIN_DECLS
+
+struct perf_slot {
+ struct timespec t_ini, t_end;
+};
+
+static inline void
+perf_ts_sub(struct timespec *a, struct timespec *b, struct timespec *res)
+{
+ res->tv_sec = a->tv_sec - b->tv_sec;
+ res->tv_nsec = a->tv_nsec - b->tv_nsec;
+ if (res->tv_nsec < 0) {
+ res->tv_sec--;
+ res->tv_nsec += 1000000000;
+ }
+}
+
+static inline void
+perf_ts_mark_print(const char *str)
+{
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+
+ printf("%lu.%.9lu: %s\n", ts.tv_sec, ts.tv_nsec, str);
+}
+
+static inline void
+perf_ts_slot_print(struct perf_slot *ps, const char *str)
+{
+ struct timespec t_res;
+
+ perf_ts_sub(&ps->t_end, &ps->t_ini, &t_res);
+
+ printf("%lu.%.9lu: %s (%lu.%.9lu sec)\n",
+ ps->t_end.tv_sec, ps->t_end.tv_nsec,
+ str, t_res.tv_sec, t_res.tv_nsec);
+}
+
+#define perf_ts_slot_start(ps) clock_gettime(CLOCK_MONOTONIC, &((ps)->t_ini))
+#define perf_ts_slot_stop(ps) clock_gettime(CLOCK_MONOTONIC, &((ps)->t_end))
+
+DPKG_END_DECLS
+
+#endif
diff --git a/lib/dpkg/pkg-array.c b/lib/dpkg/pkg-array.c
new file mode 100644
index 0000000..0ce285e
--- /dev/null
+++ b/lib/dpkg/pkg-array.c
@@ -0,0 +1,129 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * pkg-array.c - primitives for pkg array handling
+ *
+ * Copyright © 1995,1996 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2009-2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg-spec.h>
+#include <dpkg/pkg-array.h>
+
+/**
+ * Initialize a package array from package names.
+ *
+ * @param a The array to initialize.
+ * @param pkg_mapper A function that maps a package name to a package instance.
+ * @param pkg_names The package names list.
+ */
+void
+pkg_array_init_from_names(struct pkg_array *a, pkg_mapper_func pkg_mapper,
+ const char **pkg_names)
+{
+ int i = 0;
+
+ while (pkg_names[i])
+ i++;
+
+ a->n_pkgs = i;
+ a->pkgs = m_malloc(sizeof(a->pkgs[0]) * a->n_pkgs);
+
+ for (i = 0; pkg_names[i]; i++)
+ a->pkgs[i] = pkg_mapper(pkg_names[i]);
+}
+
+/**
+ * Initialize a package array from the package database.
+ *
+ * @param a The array to initialize.
+ */
+void
+pkg_array_init_from_hash(struct pkg_array *a)
+{
+ struct pkg_hash_iter *iter;
+ struct pkginfo *pkg;
+ int i;
+
+ a->n_pkgs = pkg_hash_count_pkg();
+ a->pkgs = m_malloc(sizeof(a->pkgs[0]) * a->n_pkgs);
+
+ iter = pkg_hash_iter_new();
+ for (i = 0; (pkg = pkg_hash_iter_next_pkg(iter)); i++)
+ a->pkgs[i] = pkg;
+ pkg_hash_iter_free(iter);
+
+ if (i != a->n_pkgs)
+ internerr("inconsistent state in pkg array: i=%d != npkgs=%d",
+ i, a->n_pkgs);
+}
+
+/**
+ * Visit each non-NULL package in a package array.
+ *
+ * @param a The array to visit.
+ * @param pkg_visitor The function to visit each item of the array.
+ * @param pkg_data Data to pass pkg_visit for each package visited.
+ */
+void
+pkg_array_foreach(struct pkg_array *a, pkg_array_visitor_func *pkg_visitor,
+ void *pkg_data)
+{
+ int i;
+
+ for (i = 0; i < a->n_pkgs; i++) {
+ struct pkginfo *pkg = a->pkgs[i];
+
+ if (pkg == NULL)
+ continue;
+
+ pkg_visitor(a, pkg, pkg_data);
+ }
+}
+
+/**
+ * Sort a package array.
+ *
+ * @param a The array to sort.
+ * @param pkg_sort The function to sort the array.
+ */
+void
+pkg_array_sort(struct pkg_array *a, pkg_sorter_func *pkg_sort)
+{
+ qsort(a->pkgs, a->n_pkgs, sizeof(a->pkgs[0]), pkg_sort);
+}
+
+/**
+ * Destroy a package array.
+ *
+ * Frees the allocated memory and resets the members.
+ *
+ * @param a The array to destroy.
+ */
+void
+pkg_array_destroy(struct pkg_array *a)
+{
+ a->n_pkgs = 0;
+ free(a->pkgs);
+ a->pkgs = NULL;
+}
diff --git a/lib/dpkg/pkg-array.h b/lib/dpkg/pkg-array.h
new file mode 100644
index 0000000..f9ea385
--- /dev/null
+++ b/lib/dpkg/pkg-array.h
@@ -0,0 +1,59 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * pkg-array.h - primitives for pkg array handling
+ *
+ * Copyright © 2009-2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_PKG_ARRAY_H
+#define LIBDPKG_PKG_ARRAY_H
+
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup pkg-array Package array primitives
+ * @ingroup dpkg-public
+ * @{
+ */
+
+/**
+ * Holds an array of pointers to package data.
+ */
+struct pkg_array {
+ int n_pkgs;
+ struct pkginfo **pkgs;
+};
+
+typedef struct pkginfo *pkg_mapper_func(const char *name);
+typedef void pkg_array_visitor_func(struct pkg_array *a, struct pkginfo *pkg,
+ void *pkg_data);
+
+void pkg_array_init_from_hash(struct pkg_array *a);
+void pkg_array_init_from_names(struct pkg_array *a, pkg_mapper_func *pkg_mapper,
+ const char **pkg_names);
+void pkg_array_foreach(struct pkg_array *a, pkg_array_visitor_func *pkg_visitor,
+ void *pkg_data);
+void pkg_array_sort(struct pkg_array *a, pkg_sorter_func *pkg_sort);
+void pkg_array_destroy(struct pkg_array *a);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_PKG_ARRAY_H */
diff --git a/lib/dpkg/pkg-files.c b/lib/dpkg/pkg-files.c
new file mode 100644
index 0000000..f022132
--- /dev/null
+++ b/lib/dpkg/pkg-files.c
@@ -0,0 +1,95 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * pkg-files.c - handle list of filesystem files per package
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2000,2001 Wichert Akkerman <wakkerma@debian.org>
+ * Copyright © 2008-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg-list.h>
+#include <dpkg/pkg-files.h>
+
+/**
+ * Erase the files saved in pkg.
+ */
+void
+pkg_files_blank(struct pkginfo *pkg)
+{
+ struct fsys_namenode_list *current;
+
+ for (current = pkg->files;
+ current;
+ current = current->next) {
+ struct pkg_list **pkg_prev = &current->namenode->packages;
+ struct pkg_list *pkg_node;
+
+ /* For each file that used to be in the package, go through
+ * looking for this package's entry in the list of packages
+ * containing this file, and blank it out. */
+ for (pkg_node = current->namenode->packages;
+ pkg_node;
+ pkg_node = pkg_node->next) {
+ if (pkg_node->pkg == pkg) {
+ *pkg_prev = pkg_node->next;
+
+ /* The actual filelist links were allocated
+ * w/ nfmalloc, so we should not free them. */
+ break;
+ }
+
+ pkg_prev = &pkg_node->next;
+ }
+ }
+ pkg->files = NULL;
+}
+
+struct fsys_namenode_list **
+pkg_files_add_file(struct pkginfo *pkg, struct fsys_namenode *namenode,
+ struct fsys_namenode_list **file_tail)
+{
+ struct fsys_namenode_list *newent;
+ struct pkg_list *pkg_node;
+
+ if (file_tail == NULL)
+ file_tail = &pkg->files;
+
+ /* Make sure we're at the end. */
+ while ((*file_tail) != NULL)
+ file_tail = &((*file_tail)->next);
+
+ /* Create a new node. */
+ newent = nfmalloc(sizeof(*newent));
+ newent->namenode = namenode;
+ newent->next = NULL;
+ *file_tail = newent;
+ file_tail = &newent->next;
+
+ /* Add pkg to newent's package list. */
+ pkg_node = nfmalloc(sizeof(*pkg_node));
+ pkg_node->pkg = pkg;
+ pkg_node->next = newent->namenode->packages;
+ newent->namenode->packages = pkg_node;
+
+ /* Return the position for the next guy. */
+ return file_tail;
+}
diff --git a/lib/dpkg/pkg-files.h b/lib/dpkg/pkg-files.h
new file mode 100644
index 0000000..bee137a
--- /dev/null
+++ b/lib/dpkg/pkg-files.h
@@ -0,0 +1,46 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * pkg-files.h - primitives for pkg files handling
+ *
+ * Copyright © 2018 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_PKG_FILES_H
+#define LIBDPKG_PKG_FILES_H
+
+#include <dpkg/dpkg-db.h>
+#include <dpkg/fsys.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup pkg-files Package files handling
+ * @ingroup dpkg-public
+ * @{
+ */
+
+void
+pkg_files_blank(struct pkginfo *pkg);
+
+struct fsys_namenode_list **
+pkg_files_add_file(struct pkginfo *pkg, struct fsys_namenode *namenode,
+ struct fsys_namenode_list **file_tail);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_PKG_FILES_H */
diff --git a/lib/dpkg/pkg-format.c b/lib/dpkg/pkg-format.c
new file mode 100644
index 0000000..b984012
--- /dev/null
+++ b/lib/dpkg/pkg-format.c
@@ -0,0 +1,475 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * pkg-format.c - customizable package formatting
+ *
+ * Copyright © 2001 Wichert Akkerman <wakkerma@debian.org>
+ * Copyright © 2008-2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/error.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+#include <dpkg/parsedump.h>
+#include <dpkg/pkg-show.h>
+#include <dpkg/pkg-format.h>
+
+enum pkg_format_type {
+ PKG_FORMAT_INVALID,
+ PKG_FORMAT_STRING,
+ PKG_FORMAT_FIELD,
+};
+
+struct pkg_format_node {
+ struct pkg_format_node *next;
+ enum pkg_format_type type;
+ int width;
+ char *data;
+};
+
+
+static struct pkg_format_node *
+pkg_format_node_new(void)
+{
+ struct pkg_format_node *buf;
+
+ buf = m_malloc(sizeof(*buf));
+ buf->type = PKG_FORMAT_INVALID;
+ buf->next = NULL;
+ buf->data = NULL;
+ buf->width = 0;
+
+ return buf;
+}
+
+static bool
+parsefield(struct pkg_format_node *node, const char *fmt, const char *fmtend,
+ struct dpkg_error *err)
+{
+ int len;
+ const char *ws;
+
+ len = fmtend - fmt + 1;
+
+ ws = memchr(fmt, ';', len);
+ if (ws) {
+ char *endptr;
+ long w;
+
+ errno = 0;
+ w = strtol(ws + 1, &endptr, 0);
+ if (endptr[0] != '}') {
+ dpkg_put_error(err,
+ _("invalid character '%c' in field width"),
+ *endptr);
+ return false;
+ }
+ if (w < INT_MIN || w > INT_MAX || errno == ERANGE) {
+ dpkg_put_error(err, _("field width is out of range"));
+ return false;
+ }
+
+ node->width = w;
+
+ len = ws - fmt;
+ }
+
+ node->type = PKG_FORMAT_FIELD;
+ node->data = m_malloc(len + 1);
+ memcpy(node->data, fmt, len);
+ node->data[len] = '\0';
+
+ return true;
+}
+
+static bool
+parsestring(struct pkg_format_node *node, const char *fmt, const char *fmtend,
+ struct dpkg_error *err)
+{
+ int len;
+ char *write;
+
+ len = fmtend - fmt + 1;
+
+ node->type = PKG_FORMAT_STRING;
+ node->data = write = m_malloc(len + 1);
+ while (fmt <= fmtend) {
+ if (*fmt == '\\') {
+ fmt++;
+ switch (*fmt) {
+ case 'n':
+ *write = '\n';
+ break;
+ case 't':
+ *write = '\t';
+ break;
+ case 'r':
+ *write = '\r';
+ break;
+ case '\\':
+ default:
+ *write = *fmt;
+ break;
+ }
+ } else
+ *write = *fmt;
+ write++;
+ fmt++;
+ }
+ *write = '\0';
+
+ return true;
+}
+
+void
+pkg_format_free(struct pkg_format_node *head)
+{
+ struct pkg_format_node *node;
+
+ while (head) {
+ node = head;
+ head = node->next;
+
+ free(node->data);
+ free(node);
+ }
+}
+
+struct pkg_format_node *
+pkg_format_parse(const char *fmt, struct dpkg_error *err)
+{
+ struct pkg_format_node *head, *node;
+ const char *fmtend;
+
+ head = node = NULL;
+
+ while (*fmt) {
+ if (node)
+ node = node->next = pkg_format_node_new();
+ else
+ head = node = pkg_format_node_new();
+
+ if (fmt[0] == '$' && fmt[1] == '{') {
+ fmtend = strchr(fmt, '}');
+ if (!fmtend) {
+ dpkg_put_error(err, _("missing closing brace"));
+ pkg_format_free(head);
+ return NULL;
+ }
+
+ if (!parsefield(node, fmt + 2, fmtend - 1, err)) {
+ pkg_format_free(head);
+ return NULL;
+ }
+ fmt = fmtend + 1;
+ } else {
+ fmtend = fmt;
+ do {
+ fmtend += 1;
+ fmtend = strchrnul(fmtend, '$');
+ } while (fmtend[0] && fmtend[1] != '{');
+
+ if (!parsestring(node, fmt, fmtend - 1, err)) {
+ pkg_format_free(head);
+ return NULL;
+ }
+ fmt = fmtend;
+ }
+ }
+
+ if (!head)
+ dpkg_put_error(err, _("may not be empty string"));
+
+ return head;
+}
+
+static void
+virt_package(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ varbuf_add_pkgbin_name(vb, pkg, pkgbin, pnaw_nonambig);
+}
+
+static void
+virt_status_abbrev(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ if (pkgbin != &pkg->installed)
+ return;
+
+ varbuf_add_char(vb, pkg_abbrev_want(pkg));
+ varbuf_add_char(vb, pkg_abbrev_status(pkg));
+ varbuf_add_char(vb, pkg_abbrev_eflag(pkg));
+}
+
+static void
+virt_status_want(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ if (pkgbin != &pkg->installed)
+ return;
+
+ varbuf_add_str(vb, pkg_want_name(pkg));
+}
+
+static void
+virt_status_status(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ if (pkgbin != &pkg->installed)
+ return;
+
+ varbuf_add_str(vb, pkg_status_name(pkg));
+}
+
+static void
+virt_status_eflag(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ if (pkgbin != &pkg->installed)
+ return;
+
+ varbuf_add_str(vb, pkg_eflag_name(pkg));
+}
+
+static void
+virt_synopsis(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ const char *desc;
+ int len;
+
+ desc = pkgbin_synopsis(pkg, pkgbin, &len);
+
+ varbuf_add_buf(vb, desc, len);
+}
+
+static void
+virt_fsys_last_modified(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ const char *listfile;
+ struct stat st;
+ intmax_t mtime;
+
+ if (pkg->status == PKG_STAT_NOTINSTALLED)
+ return;
+
+ listfile = pkg_infodb_get_file(pkg, pkgbin, LISTFILE);
+
+ if (stat(listfile, &st) < 0) {
+ if (errno == ENOENT)
+ return;
+
+ ohshite(_("cannot get package %s filesystem last modification time"),
+ pkgbin_name_const(pkg, pkgbin, pnaw_nonambig));
+ }
+
+ mtime = st.st_mtime;
+ varbuf_printf(vb, "%jd", mtime);
+}
+
+/*
+ * This function requires the caller to have loaded the package fsys metadata,
+ * otherwise it will do nothing.
+ */
+static void
+virt_fsys_files(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ struct fsys_namenode_list *node;
+
+ if (!pkg->files_list_valid)
+ return;
+
+ for (node = pkg->files; node; node = node->next) {
+ varbuf_add_char(vb, ' ');
+ varbuf_add_str(vb, node->namenode->name);
+ varbuf_add_char(vb, '\n');
+ }
+}
+
+static void
+virt_source_package(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ const char *name;
+ size_t len;
+
+ name = pkgbin->source;
+ if (name == NULL)
+ name = pkg->set->name;
+
+ len = strcspn(name, " ");
+
+ varbuf_add_buf(vb, name, len);
+}
+
+static void
+virt_source_version(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ varbuf_add_source_version(vb, pkg, pkgbin);
+}
+
+static void
+virt_source_upstream_version(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ struct dpkg_version version;
+
+ pkg_source_version(&version, pkg, pkgbin);
+
+ if (version.version)
+ varbuf_add_str(vb, version.version);
+ varbuf_end_str(vb);
+}
+
+static const struct fieldinfo virtinfos[] = {
+ { FIELD("binary:Package"), NULL, virt_package },
+ { FIELD("binary:Synopsis"), NULL, virt_synopsis },
+ { FIELD("binary:Summary"), NULL, virt_synopsis },
+ { FIELD("db:Status-Abbrev"), NULL, virt_status_abbrev },
+ { FIELD("db:Status-Want"), NULL, virt_status_want },
+ { FIELD("db:Status-Status"), NULL, virt_status_status },
+ { FIELD("db:Status-Eflag"), NULL, virt_status_eflag },
+ { FIELD("db-fsys:Files"), NULL, virt_fsys_files },
+ { FIELD("db-fsys:Last-Modified"), NULL, virt_fsys_last_modified },
+ { FIELD("source:Package"), NULL, virt_source_package },
+ { FIELD("source:Version"), NULL, virt_source_version },
+ { FIELD("source:Upstream-Version"), NULL, virt_source_upstream_version },
+ { NULL },
+};
+
+bool
+pkg_format_needs_db_fsys(const struct pkg_format_node *head)
+{
+ const struct pkg_format_node *node;
+
+ for (node = head; node; node = node->next) {
+ if (node->type != PKG_FORMAT_FIELD)
+ continue;
+ if (strcmp(node->data, "db-fsys:Files") == 0)
+ return true;
+ }
+
+ return false;
+}
+
+static void
+pkg_format_item(struct varbuf *vb,
+ const struct pkg_format_node *node, const char *str)
+{
+ if (node->width == 0)
+ varbuf_add_str(vb, str);
+ else
+ varbuf_printf(vb, "%*s", node->width, str);
+}
+
+void
+pkg_format_print(struct varbuf *vb, const struct pkg_format_node *head,
+ struct pkginfo *pkg, struct pkgbin *pkgbin)
+{
+ const struct pkg_format_node *node;
+ struct varbuf fb = VARBUF_INIT, wb = VARBUF_INIT;
+
+ for (node = head; node; node = node->next) {
+ bool ok = false;
+
+ if (node->type == PKG_FORMAT_STRING) {
+ pkg_format_item(&fb, node, node->data);
+ ok = true;
+ } else if (node->type == PKG_FORMAT_FIELD) {
+ const struct fieldinfo *fip;
+
+ fip = find_field_info(fieldinfos, node->data);
+ if (fip == NULL)
+ fip = find_field_info(virtinfos, node->data);
+
+ if (fip) {
+ fip->wcall(&wb, pkg, pkgbin, 0, fip);
+
+ varbuf_end_str(&wb);
+ pkg_format_item(&fb, node, wb.buf);
+ varbuf_reset(&wb);
+ ok = true;
+ } else {
+ const struct arbitraryfield *afp;
+
+ afp = find_arbfield_info(pkgbin->arbs, node->data);
+ if (afp) {
+ pkg_format_item(&fb, node, afp->value);
+ ok = true;
+ }
+ }
+ }
+
+ if (ok) {
+ size_t len = fb.used;
+ size_t width = abs(node->width);
+
+ if ((width != 0) && (len > width))
+ len = width;
+ varbuf_add_buf(vb, fb.buf, len);
+ varbuf_end_str(vb);
+ }
+
+ varbuf_reset(&fb);
+ }
+
+ varbuf_destroy(&wb);
+ varbuf_destroy(&fb);
+}
+
+void
+pkg_format_show(const struct pkg_format_node *head,
+ struct pkginfo *pkg, struct pkgbin *pkgbin)
+{
+ struct varbuf vb = VARBUF_INIT;
+
+ pkg_format_print(&vb, head, pkg, pkgbin);
+
+ if (vb.buf)
+ fputs(vb.buf, stdout);
+
+ varbuf_destroy(&vb);
+}
diff --git a/lib/dpkg/pkg-format.h b/lib/dpkg/pkg-format.h
new file mode 100644
index 0000000..40d41ff
--- /dev/null
+++ b/lib/dpkg/pkg-format.h
@@ -0,0 +1,53 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * pkg-format.g - customizable package formatting
+ *
+ * Copyright © 2001 Wichert Akkerman <wakkerma@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_PKG_FORMAT_H
+#define LIBDPKG_PKG_FORMAT_H
+
+#include <dpkg/macros.h>
+#include <dpkg/error.h>
+#include <dpkg/dpkg-db.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup pkg-format Package information formatting
+ * @ingroup dpkg-public
+ * @{
+ */
+
+struct pkg_format_node;
+
+bool
+pkg_format_needs_db_fsys(const struct pkg_format_node *head);
+
+struct pkg_format_node *pkg_format_parse(const char *fmt,
+ struct dpkg_error *err);
+void pkg_format_free(struct pkg_format_node *head);
+void pkg_format_print(struct varbuf *vb, const struct pkg_format_node *head,
+ struct pkginfo *pkg, struct pkgbin *pkgbin);
+void pkg_format_show(const struct pkg_format_node *head,
+ struct pkginfo *pkg, struct pkgbin *pkgbin);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_PKG_FORMAT_H */
diff --git a/lib/dpkg/pkg-hash.c b/lib/dpkg/pkg-hash.c
new file mode 100644
index 0000000..b6bc126
--- /dev/null
+++ b/lib/dpkg/pkg-hash.c
@@ -0,0 +1,409 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * pkg-hash.c - low level package database routines (hash tables, etc.)
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008-2014 Guillem Jover <guillem@debian.org>
+ * Copyright © 2011 Linaro Limited
+ * Copyright © 2011 Raphaël Hertzog <hertzog@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/c-ctype.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/string.h>
+#include <dpkg/arch.h>
+
+/*
+ * This must always be a prime for optimal performance.
+ *
+ * We use a number that is close to the amount of packages currently present
+ * in a Debian suite, so that installed and available packages do not add
+ * tons of collisions.
+ *
+ * The memory usage is «BINS * sizeof(void *)».
+ */
+#define BINS 65521
+
+static struct pkgset *bins[BINS];
+static int npkg, nset;
+
+/**
+ * Return the package set with the given name.
+ *
+ * If the package already exists in the internal database, then it returns
+ * the existing structure. Otherwise it allocates a new one and will return
+ * it. The actual name associated to the package set is a lowercase version
+ * of the name given in parameter.
+ *
+ * A package set (struct pkgset) can be composed of multiple package instances
+ * (struct pkginfo) where each instance is distinguished by its architecture
+ * (as recorded in pkg.installed.arch and pkg.available.arch).
+ *
+ * @param inname Name of the package set.
+ *
+ * @return The package set.
+ */
+struct pkgset *
+pkg_hash_find_set(const char *inname)
+{
+ struct pkgset **setp, *new_set;
+ char *name = m_strdup(inname), *p;
+
+ p= name;
+ while (*p) {
+ *p = c_tolower(*p);
+ p++;
+ }
+
+ setp = bins + (str_fnv_hash(name) % (BINS));
+ while (*setp && strcasecmp((*setp)->name, name))
+ setp = &(*setp)->next;
+ if (*setp) {
+ free(name);
+ return *setp;
+ }
+
+ new_set = nfmalloc(sizeof(*new_set));
+ pkgset_blank(new_set);
+ new_set->name = nfstrsave(name);
+ new_set->next = NULL;
+ *setp = new_set;
+ nset++;
+ npkg++;
+
+ free(name);
+
+ return new_set;
+}
+
+/**
+ * Return the singleton package instance from a package set.
+ *
+ * This means, if none are installed either an instance with native or
+ * all arch or the first if none found, the single installed instance,
+ * or NULL if more than one instance is installed.
+ *
+ * @param set The package set to use.
+ *
+ * @return The singleton package instance.
+ */
+struct pkginfo *
+pkg_hash_get_singleton(struct pkgset *set)
+{
+ struct pkginfo *pkg;
+
+ switch (pkgset_installed_instances(set)) {
+ case 0:
+ /* Pick an available candidate. */
+ for (pkg = &set->pkg; pkg; pkg = pkg->arch_next) {
+ const struct dpkg_arch *arch = pkg->available.arch;
+
+ if (arch->type == DPKG_ARCH_NATIVE || arch->type == DPKG_ARCH_ALL)
+ return pkg;
+ }
+ /* Or failing that, the first entry. */
+ return &set->pkg;
+ case 1:
+ for (pkg = &set->pkg; pkg; pkg = pkg->arch_next) {
+ if (pkg->status > PKG_STAT_NOTINSTALLED)
+ return pkg;
+ }
+ internerr("pkgset '%s' should have one installed instance", set->name);
+ default:
+ return NULL;
+ }
+}
+
+/**
+ * Return the singleton package instance with the given name.
+ *
+ * @param name The package name.
+ *
+ * @return The package instance.
+ */
+struct pkginfo *
+pkg_hash_find_singleton(const char *name)
+{
+ struct pkgset *set;
+ struct pkginfo *pkg;
+
+ set = pkg_hash_find_set(name);
+ pkg = pkg_hash_get_singleton(set);
+ if (pkg == NULL)
+ ohshit(_("ambiguous package name '%s' with more "
+ "than one installed instance"), set->name);
+
+ return pkg;
+}
+
+/**
+ * Return the package instance in a set with the given architecture.
+ *
+ * It traverse the various instances to find out whether there's one
+ * matching the given architecture. If found, it returns it. Otherwise it
+ * allocates a new instance and registers it in the package set before
+ * returning it.
+ *
+ * @param set The package set to use.
+ * @param arch The requested architecture.
+ *
+ * @return The package instance.
+ */
+struct pkginfo *
+pkg_hash_get_pkg(struct pkgset *set, const struct dpkg_arch *arch)
+{
+ struct pkginfo *pkg, **pkgp;
+
+ if (arch == NULL)
+ internerr("arch argument is NULL");
+ if (arch->type == DPKG_ARCH_NONE)
+ internerr("arch argument is none");
+
+ pkg = &set->pkg;
+
+ /* If there's a single unused slot, let's use that. */
+ if (pkg->installed.arch->type == DPKG_ARCH_NONE && pkg->arch_next == NULL) {
+ /* We can only initialize the arch pkgbin members, because those are used
+ * to find instances, anything else will be overwritten at parse time. */
+ pkg->installed.arch = arch;
+ pkg->available.arch = arch;
+ return pkg;
+ }
+
+ /* Match the slot with the most appropriate architecture. The installed
+ * architecture always has preference over the available one, as there's
+ * a small time window on cross-grades, where they might differ. */
+ /* cppcheck-suppress[knownConditionTrueFalse,knownPointerToBool]:
+ * False positive, the condition always ends up being false. */
+ for (pkgp = &pkg; *pkgp; pkgp = &(*pkgp)->arch_next) {
+ if ((*pkgp)->installed.arch == arch)
+ return *pkgp;
+ }
+
+ /* Need to create a new instance for the wanted architecture. */
+ pkg = nfmalloc(sizeof(*pkg));
+ pkg_blank(pkg);
+ pkg->set = set;
+ pkg->arch_next = NULL;
+ /* We can only initialize the arch pkgbin members, because those are used
+ * to find instances, anything else will be overwritten at parse time. */
+ pkg->installed.arch = arch;
+ pkg->available.arch = arch;
+ *pkgp = pkg;
+ npkg++;
+
+ return pkg;
+}
+
+/**
+ * Return the package instance with the given name and architecture.
+ *
+ * @param name The package name.
+ * @param arch The requested architecture.
+ *
+ * @return The package instance.
+ */
+struct pkginfo *
+pkg_hash_find_pkg(const char *name, const struct dpkg_arch *arch)
+{
+ struct pkgset *set;
+ struct pkginfo *pkg;
+
+ set = pkg_hash_find_set(name);
+ pkg = pkg_hash_get_pkg(set, arch);
+
+ return pkg;
+}
+
+/**
+ * Return the number of package sets available in the database.
+ *
+ * @return The number of package sets.
+ */
+int
+pkg_hash_count_set(void)
+{
+ return nset;
+}
+
+/**
+ * Return the number of package instances available in the database.
+ *
+ * @return The number of package instances.
+ */
+int
+pkg_hash_count_pkg(void)
+{
+ return npkg;
+}
+
+struct pkg_hash_iter {
+ struct pkginfo *pkg;
+ int nbinn;
+};
+
+/**
+ * Create a new package iterator.
+ *
+ * It can iterate either over package sets or over package instances.
+ *
+ * @return The iterator.
+ */
+struct pkg_hash_iter *
+pkg_hash_iter_new(void)
+{
+ struct pkg_hash_iter *iter;
+
+ iter = m_malloc(sizeof(*iter));
+ iter->pkg = NULL;
+ iter->nbinn = 0;
+
+ return iter;
+}
+
+/**
+ * Return the next package set in the database.
+ *
+ * If no further package set is available, it will return NULL.
+ *
+ * @name iter The iterator.
+ *
+ * @return A package set.
+ */
+struct pkgset *
+pkg_hash_iter_next_set(struct pkg_hash_iter *iter)
+{
+ struct pkgset *set;
+
+ while (!iter->pkg) {
+ if (iter->nbinn >= BINS)
+ return NULL;
+ if (bins[iter->nbinn])
+ iter->pkg = &bins[iter->nbinn]->pkg;
+ iter->nbinn++;
+ }
+
+ set = iter->pkg->set;
+ if (set->next)
+ iter->pkg = &set->next->pkg;
+ else
+ iter->pkg = NULL;
+
+ return set;
+}
+
+/**
+ * Return the next package instance in the database.
+ *
+ * If no further package instance is available, it will return NULL. Note
+ * that it will return all instances of a given package set in sequential
+ * order. The first instance for a given package set will always correspond
+ * to the native architecture even if that package is not installed or
+ * available.
+ *
+ * @name iter The iterator.
+ *
+ * @return A package instance.
+ */
+struct pkginfo *
+pkg_hash_iter_next_pkg(struct pkg_hash_iter *iter)
+{
+ struct pkginfo *pkg;
+
+ while (!iter->pkg) {
+ if (iter->nbinn >= BINS)
+ return NULL;
+ if (bins[iter->nbinn])
+ iter->pkg = &bins[iter->nbinn]->pkg;
+ iter->nbinn++;
+ }
+
+ pkg = iter->pkg;
+ if (pkg->arch_next)
+ iter->pkg = pkg->arch_next;
+ else if (pkg->set->next)
+ iter->pkg = &pkg->set->next->pkg;
+ else
+ iter->pkg = NULL;
+
+ return pkg;
+}
+
+/**
+ * Free the package database iterator.
+ *
+ * @name iter The iterator.
+ */
+void
+pkg_hash_iter_free(struct pkg_hash_iter *iter)
+{
+ free(iter);
+}
+
+void
+pkg_hash_reset(void)
+{
+ dpkg_arch_reset_list();
+ nffreeall();
+ nset = 0;
+ npkg = 0;
+ memset(bins, 0, sizeof(bins));
+}
+
+void
+pkg_hash_report(FILE *file)
+{
+ int i, c;
+ struct pkgset *pkg;
+ int *freq;
+ int empty = 0, used = 0, collided = 0;
+
+ freq = m_malloc(sizeof(int) * nset + 1);
+ for (i = 0; i <= nset; i++)
+ freq[i] = 0;
+ for (i=0; i<BINS; i++) {
+ for (c=0, pkg= bins[i]; pkg; c++, pkg= pkg->next);
+ fprintf(file, "pkg-hash: bin %5d has %7d\n", i, c);
+ if (c == 0)
+ empty++;
+ else if (c == 1)
+ used++;
+ else {
+ used++;
+ collided++;
+ }
+ freq[c]++;
+ }
+ for (i = nset; i > 0 && freq[i] == 0; i--);
+ while (i >= 0) {
+ fprintf(file, "pkg-hash: size %7d occurs %5d times\n", i, freq[i]);
+ i--;
+ }
+ fprintf(file, "pkg-hash: bins empty %d\n", empty);
+ fprintf(file, "pkg-hash: bins used %d (collided %d)\n", used, collided);
+
+ m_output(file, "<hash report>");
+
+ free(freq);
+}
diff --git a/lib/dpkg/pkg-list.c b/lib/dpkg/pkg-list.c
new file mode 100644
index 0000000..fa889b1
--- /dev/null
+++ b/lib/dpkg/pkg-list.c
@@ -0,0 +1,78 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * pkg-list.c - primitives for pkg linked list handling
+ *
+ * Copyright © 2009 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <stdlib.h>
+
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg-list.h>
+
+/**
+ * Create a new package list node.
+ *
+ * @param pkg The pkginfo to assign to the node.
+ * @param next The next package list node.
+ *
+ * @return The new package list node.
+ */
+struct pkg_list *
+pkg_list_new(struct pkginfo *pkg, struct pkg_list *next)
+{
+ struct pkg_list *node;
+
+ node = m_malloc(sizeof(*node));
+ node->pkg = pkg;
+ node->next = next;
+
+ return node;
+}
+
+/**
+ * Free all nodes of a package list.
+ *
+ * @param head The head of the list to free.
+ */
+void
+pkg_list_free(struct pkg_list *head)
+{
+ while (head) {
+ struct pkg_list *node;
+
+ node = head;
+ head = head->next;
+
+ free(node);
+ }
+}
+
+/**
+ * Prepend a package list node to a package list.
+ *
+ * @param head The head of the list to prepend to.
+ * @param pkg The pkginfo to prepend to the list.
+ */
+void
+pkg_list_prepend(struct pkg_list **head, struct pkginfo *pkg)
+{
+ *head = pkg_list_new(pkg, *head);
+}
diff --git a/lib/dpkg/pkg-list.h b/lib/dpkg/pkg-list.h
new file mode 100644
index 0000000..6a34755
--- /dev/null
+++ b/lib/dpkg/pkg-list.h
@@ -0,0 +1,47 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * pkg-list.h - primitives for pkg linked list handling
+ *
+ * Copyright © 2009 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_PKG_LIST_H
+#define LIBDPKG_PKG_LIST_H
+
+#include <dpkg/dpkg-db.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup pkg-list Package linked lists
+ * @ingroup dpkg-public
+ * @{
+ */
+
+struct pkg_list {
+ struct pkg_list *next;
+ struct pkginfo *pkg;
+};
+
+struct pkg_list *pkg_list_new(struct pkginfo *pkg, struct pkg_list *next);
+void pkg_list_free(struct pkg_list *head);
+void pkg_list_prepend(struct pkg_list **head, struct pkginfo *pkg);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_PKG_LIST_H */
diff --git a/lib/dpkg/pkg-namevalue.c b/lib/dpkg/pkg-namevalue.c
new file mode 100644
index 0000000..5f6fcb7
--- /dev/null
+++ b/lib/dpkg/pkg-namevalue.c
@@ -0,0 +1,77 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * pkg-namevalue.c - name/value package tables
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2006-2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/dpkg-db.h>
+#include <dpkg/namevalue.h>
+
+const struct namevalue booleaninfos[] = {
+ NAMEVALUE_DEF("no", false),
+ NAMEVALUE_DEF("yes", true),
+ { .name = NULL }
+};
+
+const struct namevalue multiarchinfos[] = {
+ NAMEVALUE_DEF("no", PKG_MULTIARCH_NO),
+ NAMEVALUE_DEF("same", PKG_MULTIARCH_SAME),
+ NAMEVALUE_DEF("allowed", PKG_MULTIARCH_ALLOWED),
+ NAMEVALUE_DEF("foreign", PKG_MULTIARCH_FOREIGN),
+ { .name = NULL }
+};
+
+const struct namevalue priorityinfos[] = {
+ NAMEVALUE_DEF("required", PKG_PRIO_REQUIRED),
+ NAMEVALUE_DEF("important", PKG_PRIO_IMPORTANT),
+ NAMEVALUE_DEF("standard", PKG_PRIO_STANDARD),
+ NAMEVALUE_DEF("optional", PKG_PRIO_OPTIONAL),
+ NAMEVALUE_DEF("extra", PKG_PRIO_EXTRA),
+ NAMEVALUE_DEF("unknown", PKG_PRIO_UNKNOWN),
+ { .name = NULL }
+};
+
+const struct namevalue wantinfos[] = {
+ NAMEVALUE_DEF("unknown", PKG_WANT_UNKNOWN),
+ NAMEVALUE_DEF("install", PKG_WANT_INSTALL),
+ NAMEVALUE_DEF("hold", PKG_WANT_HOLD),
+ NAMEVALUE_DEF("deinstall", PKG_WANT_DEINSTALL),
+ NAMEVALUE_DEF("purge", PKG_WANT_PURGE),
+ { .name = NULL }
+};
+
+const struct namevalue eflaginfos[] = {
+ NAMEVALUE_DEF("ok", PKG_EFLAG_OK),
+ NAMEVALUE_DEF("reinstreq", PKG_EFLAG_REINSTREQ),
+ { .name = NULL }
+};
+
+const struct namevalue statusinfos[] = {
+ NAMEVALUE_DEF("not-installed", PKG_STAT_NOTINSTALLED),
+ NAMEVALUE_DEF("config-files", PKG_STAT_CONFIGFILES),
+ NAMEVALUE_DEF("half-installed", PKG_STAT_HALFINSTALLED),
+ NAMEVALUE_DEF("unpacked", PKG_STAT_UNPACKED),
+ NAMEVALUE_DEF("half-configured", PKG_STAT_HALFCONFIGURED),
+ NAMEVALUE_DEF("triggers-awaited", PKG_STAT_TRIGGERSAWAITED),
+ NAMEVALUE_DEF("triggers-pending", PKG_STAT_TRIGGERSPENDING),
+ NAMEVALUE_DEF("installed", PKG_STAT_INSTALLED),
+ { .name = NULL }
+};
diff --git a/lib/dpkg/pkg-queue.c b/lib/dpkg/pkg-queue.c
new file mode 100644
index 0000000..a807f1e
--- /dev/null
+++ b/lib/dpkg/pkg-queue.c
@@ -0,0 +1,126 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * pkg-queue.c - primitives for pkg queue handling
+ *
+ * Copyright © 2010 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <stdlib.h>
+
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg-queue.h>
+
+/**
+ * Initialize a package queue.
+ *
+ * @param queue The queue to initialize.
+ */
+void
+pkg_queue_init(struct pkg_queue *queue)
+{
+ queue->head = NULL;
+ queue->tail = NULL;
+ queue->length = 0;
+}
+
+/**
+ * Destroy a package queue.
+ *
+ * It frees the contained package list and resets the queue members.
+ *
+ * @param queue The queue to destroy.
+ */
+void
+pkg_queue_destroy(struct pkg_queue *queue)
+{
+ pkg_list_free(queue->head);
+ pkg_queue_init(queue);
+}
+
+/**
+ * Check if a package queue is empty.
+ *
+ * @param queue The queue to check.
+ *
+ * @return A boolean value.
+ */
+int
+pkg_queue_is_empty(struct pkg_queue *queue)
+{
+ return (queue->head == NULL);
+}
+
+/**
+ * Push a new node containing pkginfo to the tail of the queue.
+ *
+ * @param queue The queue to insert to.
+ * @param pkg The package to use fo the new node.
+ *
+ * @return The newly inserted pkg_list node.
+ */
+struct pkg_list *
+pkg_queue_push(struct pkg_queue *queue, struct pkginfo *pkg)
+{
+ struct pkg_list *node;
+
+ node = pkg_list_new(pkg, NULL);
+
+ if (queue->tail == NULL)
+ queue->head = node;
+ else
+ queue->tail->next = node;
+
+ queue->tail = node;
+
+ queue->length++;
+
+ return node;
+}
+
+/**
+ * Pop a node containing pkginfo from the head of the queue.
+ *
+ * This removes and frees the node from the queue, effectively reducing its
+ * size.
+ *
+ * @param queue The queue to remove from.
+ *
+ * @return The pkginfo from the removed node, or NULL if the queue was empty.
+ */
+struct pkginfo *
+pkg_queue_pop(struct pkg_queue *queue)
+{
+ struct pkg_list *node;
+ struct pkginfo *pkg;
+
+ if (pkg_queue_is_empty(queue))
+ return NULL;
+
+ node = queue->head;
+ pkg = node->pkg;
+
+ queue->head = node->next;
+ if (queue->head == NULL)
+ queue->tail = NULL;
+
+ free(node);
+ queue->length--;
+
+ return pkg;
+}
diff --git a/lib/dpkg/pkg-queue.h b/lib/dpkg/pkg-queue.h
new file mode 100644
index 0000000..21460af
--- /dev/null
+++ b/lib/dpkg/pkg-queue.h
@@ -0,0 +1,64 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * pkg-queue.h - primitives for pkg queue handling
+ *
+ * Copyright © 2010 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef DPKG_PKG_QUEUE_H
+#define DPKG_PKG_QUEUE_H
+
+#include <dpkg/macros.h>
+#include <dpkg/pkg-list.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup pkg-queue Package queues
+ * @ingroup dpkg-public
+ * @{
+ */
+
+struct pkg_queue {
+ struct pkg_list *head, *tail;
+ int length;
+};
+
+/**
+ * Constant initializer for a package queue.
+ */
+#define PKG_QUEUE_INIT \
+ { .head = NULL, .tail = NULL, .length = 0 }
+
+/**
+ * Compound literal for a package queue.
+ */
+#define PKG_QUEUE_OBJECT \
+ (struct pkg_queue)PKG_QUEUE_INIT
+
+void pkg_queue_init(struct pkg_queue *queue);
+void pkg_queue_destroy(struct pkg_queue *queue);
+
+int pkg_queue_is_empty(struct pkg_queue *queue);
+
+struct pkg_list *pkg_queue_push(struct pkg_queue *queue, struct pkginfo *pkg);
+struct pkginfo *pkg_queue_pop(struct pkg_queue *queue);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* DPKG_PKG_QUEUE_H */
diff --git a/lib/dpkg/pkg-show.c b/lib/dpkg/pkg-show.c
new file mode 100644
index 0000000..038343c
--- /dev/null
+++ b/lib/dpkg/pkg-show.c
@@ -0,0 +1,445 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * pkg-show.c - primitives for pkg information display
+ *
+ * Copyright © 1995,1996 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <string.h>
+
+#include <dpkg/macros.h>
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg-show.h>
+
+static bool
+pkgbin_name_needs_arch(const struct pkgbin *pkgbin,
+ enum pkg_name_arch_when pnaw)
+{
+ if (pkgbin->arch->type == DPKG_ARCH_NONE ||
+ pkgbin->arch->type == DPKG_ARCH_EMPTY)
+ return false;
+
+ switch (pnaw) {
+ case pnaw_never:
+ break;
+ case pnaw_same:
+ if (pkgbin->multiarch == PKG_MULTIARCH_SAME)
+ return true;
+ return false;
+ case pnaw_nonambig:
+ if (pkgbin->multiarch == PKG_MULTIARCH_SAME)
+ return true;
+ /* Fall through. */
+ case pnaw_foreign:
+ if (pkgbin->arch->type == DPKG_ARCH_NATIVE ||
+ pkgbin->arch->type == DPKG_ARCH_ALL)
+ break;
+ /* Fall through. */
+ case pnaw_always:
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Add a string representation of the package name to a varbuf.
+ *
+ * Works exactly like pkgbin_name() but acts on the varbuf instead of
+ * returning a string. It NUL terminates the varbuf.
+ *
+ * @param vb The varbuf struct to modify.
+ * @param pkg The package to consider.
+ * @param pkgbin The binary package instance to consider.
+ * @param pnaw When to display the architecture qualifier.
+ */
+void
+varbuf_add_pkgbin_name(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum pkg_name_arch_when pnaw)
+{
+ varbuf_add_str(vb, pkg->set->name);
+ if (pkgbin_name_needs_arch(pkgbin, pnaw))
+ varbuf_add_archqual(vb, pkgbin->arch);
+ varbuf_end_str(vb);
+}
+
+const char *
+pkgbin_name_archqual(const struct pkginfo *pkg, const struct pkgbin *pkgbin)
+{
+ char *pkgname;
+
+ if (pkgbin->arch->type == DPKG_ARCH_NONE ||
+ pkgbin->arch->type == DPKG_ARCH_EMPTY)
+ return pkg->set->name;
+
+ pkgname = nfmalloc(strlen(pkg->set->name) + 1 +
+ strlen(pkgbin->arch->name) + 1);
+ str_concat(pkgname, pkg->set->name, ":",
+ pkgbin->arch->name, NULL);
+
+ return pkgname;
+}
+
+/**
+ * Return a string representation of the package name.
+ *
+ * The returned string must not be freed, and it's permanently allocated so
+ * can be used as long as the non-freeing memory pool has not been freed.
+ *
+ * Note, that this const variant will "leak" a new non-freeing string on
+ * each call if the internal cache has not been previously initialized,
+ * so it is advised to use it only in error reporting code paths.
+ *
+ * The pnaw parameter should be one of pnaw_never (never print arch),
+ * pnaw_foreign (print arch for foreign packages only), pnaw_nonambig (print
+ * arch for non ambiguous cases) or pnaw_always (always print arch),
+ *
+ * @param pkg The package to consider.
+ * @param pkgbin The binary package instance to consider.
+ * @param pnaw When to display the architecture qualifier.
+ *
+ * @return The string representation.
+ */
+const char *
+pkgbin_name_const(const struct pkginfo *pkg, const struct pkgbin *pkgbin,
+ enum pkg_name_arch_when pnaw)
+{
+ if (!pkgbin_name_needs_arch(pkgbin, pnaw))
+ return pkg->set->name;
+
+ /* Return a non-freeing package name representation, which
+ * is intended to be used in error-handling code, as we will keep
+ * "leaking" them until the next memory pool flush. */
+ if (pkgbin->pkgname_archqual == NULL)
+ return pkgbin_name_archqual(pkg, pkgbin);
+
+ return pkgbin->pkgname_archqual;
+}
+
+/**
+ * Return a string representation of the installed package name.
+ *
+ * This is equivalent to pkgbin_name_const() but just for its installed pkgbin.
+ *
+ * @param pkg The package to consider.
+ * @param pnaw When to display the architecture qualifier.
+ *
+ * @return The string representation.
+ */
+const char *
+pkg_name_const(const struct pkginfo *pkg, enum pkg_name_arch_when pnaw)
+{
+ return pkgbin_name_const(pkg, &pkg->installed, pnaw);
+}
+
+/**
+ * Return a string representation of the package name.
+ *
+ * The returned string must not be freed, and it's permanently allocated so
+ * can be used as long as the non-freeing memory pool has not been freed.
+ *
+ * The pnaw parameter should be one of pnaw_never (never print arch),
+ * pnaw_foreign (print arch for foreign packages only), pnaw_nonambig (print
+ * arch for non ambiguous cases) or pnaw_always (always print arch),
+ *
+ * @param pkg The package to consider.
+ * @param pkgbin The binary package instance to consider.
+ * @param pnaw When to display the architecture qualifier.
+ *
+ * @return The string representation.
+ */
+const char *
+pkgbin_name(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ enum pkg_name_arch_when pnaw)
+{
+ if (!pkgbin_name_needs_arch(pkgbin, pnaw))
+ return pkg->set->name;
+
+ /* Cache the package name representation, for later reuse. */
+ if (pkgbin->pkgname_archqual == NULL)
+ pkgbin->pkgname_archqual = pkgbin_name_archqual(pkg, pkgbin);
+
+ return pkgbin->pkgname_archqual;
+}
+
+/**
+ * Return a string representation of the installed package name.
+ *
+ * This is equivalent to pkgbin_name() but just for its installed pkgbin.
+ *
+ * @param pkg The package to consider.
+ * @param pnaw When to display the architecture qualifier.
+ *
+ * @return The string representation.
+ */
+const char *
+pkg_name(struct pkginfo *pkg, enum pkg_name_arch_when pnaw)
+{
+ return pkgbin_name(pkg, &pkg->installed, pnaw);
+}
+
+/**
+ * Return a string representation of the package synopsis.
+ *
+ * The returned string must not be freed, and it's permanently allocated so
+ * can be used as long as the non-freeing memory pool has not been freed.
+ *
+ * The package synopsis is the short description, but it is not NUL terminated,
+ * so the output len argument should be used to limit the string length.
+ *
+ * @param pkg The package to consider.
+ * @param pkgbin The binary package instance to consider.
+ * @param[out] len The length of the synopsis string within the description.
+ *
+ * @return The string representation.
+ */
+const char *
+pkgbin_synopsis(const struct pkginfo *pkg, const struct pkgbin *pkgbin, int *len)
+{
+ const char *pdesc;
+
+ pdesc = pkgbin->description;
+ if (!pdesc)
+ pdesc = _("(no description available)");
+
+ *len = strcspn(pdesc, "\n");
+
+ return pdesc;
+}
+
+/**
+ * Return a string representation of the package synopsis.
+ *
+ * The returned string must not be freed, and it's permanently allocated so
+ * can be used as long as the non-freeing memory pool has not been freed.
+ *
+ * It will try to use the installed version, otherwise it will fallback to
+ * use the available version.
+ *
+ * The package synopsis is the short description, but it is not NUL terminated,
+ * so the output len argument should be used to limit the string length.
+ *
+ * @param pkg The package to consider.
+ * @param[out] len The length of the synopsis string within the description.
+ *
+ * @return The string representation.
+ */
+const char *
+pkg_synopsis(const struct pkginfo *pkg, int *len)
+{
+ const char *pdesc;
+
+ pdesc = pkg->installed.description;
+ if (!pdesc)
+ pdesc = pkg->available.description;
+ if (!pdesc)
+ pdesc = _("(no description available)");
+
+ *len = strcspn(pdesc, "\n");
+
+ return pdesc;
+}
+
+/**
+ * Return a character abbreviated representation of the package want status.
+ *
+ * @param pkg The package to consider.
+ *
+ * @return The character abbreviated representation.
+ */
+int
+pkg_abbrev_want(const struct pkginfo *pkg)
+{
+ return "uihrp"[pkg->want];
+}
+
+/**
+ * Return a character abbreviated representation of the package current status.
+ *
+ * @param pkg The package to consider.
+ *
+ * @return The character abbreviated representation.
+ */
+int
+pkg_abbrev_status(const struct pkginfo *pkg)
+{
+ return "ncHUFWti"[pkg->status];
+}
+
+/**
+ * Return a character abbreviated representation of the package eflag status.
+ *
+ * @param pkg The package to consider.
+ *
+ * @return The character abbreviated representation.
+ */
+int
+pkg_abbrev_eflag(const struct pkginfo *pkg)
+{
+ return " R"[pkg->eflag];
+}
+
+/**
+ * Return a string representation of the package want status name.
+ *
+ * @param pkg The package to consider.
+ *
+ * @return The string representation.
+ */
+const char *
+pkg_want_name(const struct pkginfo *pkg)
+{
+ return wantinfos[pkg->want].name;
+}
+
+/**
+ * Return a string representation of the package eflag status name.
+ *
+ * @param pkg The package to consider.
+ *
+ * @return The string representation.
+ */
+const char *
+pkg_eflag_name(const struct pkginfo *pkg)
+{
+ return eflaginfos[pkg->eflag].name;
+}
+
+/**
+ * Return a string representation of the package current status name.
+ *
+ * @param pkg The package to consider.
+ *
+ * @return The string representation.
+ */
+const char *
+pkg_status_name(const struct pkginfo *pkg)
+{
+ return statusinfos[pkg->status].name;
+}
+
+/**
+ * Return a string representation of the package priority name.
+ *
+ * @param pkg The package to consider.
+ *
+ * @return The string representation.
+ */
+const char *
+pkg_priority_name(const struct pkginfo *pkg)
+{
+ if (pkg->priority == PKG_PRIO_OTHER)
+ return pkg->otherpriority;
+ else
+ return priorityinfos[pkg->priority].name;
+}
+
+/**
+ * Compare a package to be sorted by non-ambiguous name and architecture.
+ *
+ * @param a A pointer of a pointer to a struct pkginfo.
+ * @param b A pointer of a pointer to a struct pkginfo.
+ *
+ * @return An integer with the result of the comparison.
+ * @retval -1 a is earlier than b.
+ * @retval 0 a is equal to b.
+ * @retval 1 a is later than b.
+ */
+int
+pkg_sorter_by_nonambig_name_arch(const void *a, const void *b)
+{
+ const struct pkginfo *pa = *(const struct pkginfo **)a;
+ const struct pkginfo *pb = *(const struct pkginfo **)b;
+ const struct pkgbin *pbina = &pa->installed;
+ const struct pkgbin *pbinb = &pb->installed;
+ int res;
+
+ res = strcmp(pa->set->name, pb->set->name);
+ if (res)
+ return res;
+
+ if (pbina->arch == pbinb->arch)
+ return 0;
+
+ if (pkgbin_name_needs_arch(pbina, pnaw_nonambig)) {
+ if (pkgbin_name_needs_arch(pbinb, pnaw_nonambig))
+ return strcmp(pbina->arch->name, pbinb->arch->name);
+ else
+ return 1;
+ } else {
+ return -1;
+ }
+}
+
+/**
+ * Add a string representation of the source package version to a varbuf.
+ *
+ * It parses the Source field (if present), and extracts the optional
+ * version enclosed in parenthesis. Otherwise it fallsback to use the
+ * binary package version. It NUL terminates the varbuf.
+ *
+ * @param vb The varbuf struct to modify.
+ * @param pkg The package to consider.
+ * @param pkgbin The binary package instance to consider.
+ */
+void
+varbuf_add_source_version(struct varbuf *vb,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin)
+{
+ struct dpkg_version version = DPKG_VERSION_INIT;
+
+ pkg_source_version(&version, pkg, pkgbin);
+ varbufversion(vb, &version, vdew_nonambig);
+ varbuf_end_str(vb);
+}
+
+void
+pkg_source_version(struct dpkg_version *version,
+ const struct pkginfo *pkg, const struct pkgbin *pkgbin)
+{
+ const char *version_str;
+
+ if (pkgbin->source)
+ version_str = strchr(pkgbin->source, '(');
+ else
+ version_str = NULL;
+
+ if (version_str == NULL) {
+ *version = pkgbin->version;
+ } else {
+ struct dpkg_error err;
+ struct varbuf vb = VARBUF_INIT;
+ size_t len;
+
+ version_str++;
+ len = strcspn(version_str, ")");
+ varbuf_add_buf(&vb, version_str, len);
+ varbuf_end_str(&vb);
+
+ if (parseversion(version, vb.buf, &err) < 0)
+ ohshit(_("version '%s' has bad syntax: %s"),
+ vb.buf, err.str);
+
+ varbuf_destroy(&vb);
+ }
+}
diff --git a/lib/dpkg/pkg-show.h b/lib/dpkg/pkg-show.h
new file mode 100644
index 0000000..4ac3f5d
--- /dev/null
+++ b/lib/dpkg/pkg-show.h
@@ -0,0 +1,49 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * pkg-show.h - primitives for pkg information display
+ *
+ * Copyright © 2010-2012 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef DPKG_PKG_SHOW_H
+#define DPKG_PKG_SHOW_H
+
+#include <dpkg/macros.h>
+#include <dpkg/dpkg-db.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup pkg-show Package information display
+ * @ingroup dpkg-public
+ * @{
+ */
+
+int pkg_sorter_by_nonambig_name_arch(const void *a, const void *b);
+
+const char *pkgbin_synopsis(const struct pkginfo *pkg,
+ const struct pkgbin *pkgbin, int *len_ret);
+const char *
+pkg_synopsis(const struct pkginfo *pkg, int *len_ret);
+int pkg_abbrev_want(const struct pkginfo *pkg);
+int pkg_abbrev_status(const struct pkginfo *pkg);
+int pkg_abbrev_eflag(const struct pkginfo *pkg);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* DPKG_PKG_SHOW_H */
diff --git a/lib/dpkg/pkg-spec.c b/lib/dpkg/pkg-spec.c
new file mode 100644
index 0000000..3e3f59f
--- /dev/null
+++ b/lib/dpkg/pkg-spec.c
@@ -0,0 +1,303 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * pkg-spec.c - primitives for pkg specifier handling
+ *
+ * Copyright © 2011 Linaro Limited
+ * Copyright © 2011 Raphaël Hertzog <hertzog@debian.org>
+ * Copyright © 2011-2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <stdlib.h>
+#include <fnmatch.h>
+#include <string.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/arch.h>
+#include <dpkg/pkg-spec.h>
+
+static void
+pkg_spec_blank(struct pkg_spec *ps)
+{
+ ps->name = NULL;
+ ps->arch = NULL;
+
+ ps->name_is_pattern = false;
+ ps->arch_is_pattern = false;
+}
+
+static void
+pkg_spec_iter_blank(struct pkg_spec *ps)
+{
+ ps->pkg_iter = NULL;
+ ps->pkg_next = NULL;
+}
+
+void
+pkg_spec_init(struct pkg_spec *ps, enum pkg_spec_flags flags)
+{
+ ps->flags = flags;
+
+ pkg_spec_blank(ps);
+ pkg_spec_iter_blank(ps);
+}
+
+const char *
+pkg_spec_is_illegal(struct pkg_spec *ps)
+{
+ static char msg[1024];
+ const char *emsg;
+
+ if (!ps->name_is_pattern &&
+ (emsg = pkg_name_is_illegal(ps->name))) {
+ const char *arch_sep;
+
+ /* Only check for DPKG_ARCH_NONE, because for everything else
+ * we want to see the passed package specification, even if
+ * the architecture is empty. */
+ if (ps->arch->type == DPKG_ARCH_NONE)
+ arch_sep = "";
+ else
+ arch_sep = ":";
+
+ snprintf(msg, sizeof(msg),
+ _("illegal package name in specifier '%s%s%s': %s"),
+ ps->name, arch_sep, ps->arch->name, emsg);
+ return msg;
+ }
+
+ if ((!ps->arch_is_pattern && ps->arch->type == DPKG_ARCH_ILLEGAL) ||
+ ps->arch->type == DPKG_ARCH_EMPTY) {
+ emsg = dpkg_arch_name_is_illegal(ps->arch->name);
+ snprintf(msg, sizeof(msg),
+ _("illegal architecture name in specifier '%s:%s': %s"),
+ ps->name, ps->arch->name, emsg);
+ return msg;
+ }
+
+ /* If we have been requested a single instance, check that the
+ * package does not contain other instances. */
+ if (!ps->arch_is_pattern && ps->flags & PKG_SPEC_ARCH_SINGLE) {
+ struct pkgset *set;
+
+ set = pkg_hash_find_set(ps->name);
+
+ /* Single instancing only applies with no architecture. */
+ if (ps->arch->type == DPKG_ARCH_NONE &&
+ pkgset_installed_instances(set) > 1) {
+ snprintf(msg, sizeof(msg),
+ _("ambiguous package name '%s' with more "
+ "than one installed instance"), ps->name);
+ return msg;
+ }
+ }
+
+ return NULL;
+}
+
+static const char *
+pkg_spec_prep(struct pkg_spec *ps, char *pkgname, const char *archname)
+{
+ ps->name = pkgname;
+ ps->arch = dpkg_arch_find(archname);
+
+ ps->name_is_pattern = false;
+ ps->arch_is_pattern = false;
+
+ /* Detect if we have patterns and/or illegal names. */
+ if ((ps->flags & PKG_SPEC_PATTERNS) && strpbrk(ps->name, "*[?\\"))
+ ps->name_is_pattern = true;
+
+ if ((ps->flags & PKG_SPEC_PATTERNS) && strpbrk(ps->arch->name, "*[?\\"))
+ ps->arch_is_pattern = true;
+
+ return pkg_spec_is_illegal(ps);
+}
+
+const char *
+pkg_spec_set(struct pkg_spec *ps, const char *pkgname, const char *archname)
+{
+ return pkg_spec_prep(ps, m_strdup(pkgname), archname);
+}
+
+const char *
+pkg_spec_parse(struct pkg_spec *ps, const char *str)
+{
+ char *pkgname, *archname;
+
+ archname = strchr(str, ':');
+ if (archname == NULL) {
+ pkgname = m_strdup(str);
+ } else {
+ pkgname = m_strndup(str, archname - str);
+ archname++;
+ }
+
+ return pkg_spec_prep(ps, pkgname, archname);
+}
+
+static bool
+pkg_spec_match_name(struct pkg_spec *ps, const char *name)
+{
+ if (ps->name_is_pattern)
+ return (fnmatch(ps->name, name, 0) == 0);
+ else
+ return (strcmp(ps->name, name) == 0);
+}
+
+static bool
+pkg_spec_match_arch(struct pkg_spec *ps, struct pkginfo *pkg,
+ const struct dpkg_arch *arch)
+{
+ if (ps->arch_is_pattern)
+ return (fnmatch(ps->arch->name, arch->name, 0) == 0);
+ else if (ps->arch->type != DPKG_ARCH_NONE) /* !arch_is_pattern */
+ return (ps->arch == arch);
+
+ /* No arch specified. */
+ switch (ps->flags & PKG_SPEC_ARCH_MASK) {
+ case PKG_SPEC_ARCH_SINGLE:
+ return pkgset_installed_instances(pkg->set) <= 1;
+ case PKG_SPEC_ARCH_WILDCARD:
+ return true;
+ default:
+ internerr("unknown PKG_SPEC_ARCH_* flags %d in pkg_spec",
+ ps->flags & PKG_SPEC_ARCH_MASK);
+ }
+}
+
+bool
+pkg_spec_match_pkg(struct pkg_spec *ps, struct pkginfo *pkg,
+ struct pkgbin *pkgbin)
+{
+ return (pkg_spec_match_name(ps, pkg->set->name) &&
+ pkg_spec_match_arch(ps, pkg, pkgbin->arch));
+}
+
+static struct pkginfo *
+pkg_spec_get_pkg(struct pkg_spec *ps)
+{
+ if (ps->arch->type == DPKG_ARCH_NONE)
+ return pkg_hash_find_singleton(ps->name);
+ else
+ return pkg_hash_find_pkg(ps->name, ps->arch);
+}
+
+struct pkginfo *
+pkg_spec_parse_pkg(const char *str, struct dpkg_error *err)
+{
+ struct pkg_spec ps;
+ struct pkginfo *pkg;
+ const char *emsg;
+
+ pkg_spec_init(&ps, PKG_SPEC_ARCH_SINGLE);
+ emsg = pkg_spec_parse(&ps, str);
+ if (emsg) {
+ dpkg_put_error(err, "%s", emsg);
+ pkg = NULL;
+ } else {
+ pkg = pkg_spec_get_pkg(&ps);
+ }
+ pkg_spec_destroy(&ps);
+
+ return pkg;
+}
+
+struct pkginfo *
+pkg_spec_find_pkg(const char *pkgname, const char *archname,
+ struct dpkg_error *err)
+{
+ struct pkg_spec ps;
+ struct pkginfo *pkg;
+ const char *emsg;
+
+ pkg_spec_init(&ps, PKG_SPEC_ARCH_SINGLE);
+ emsg = pkg_spec_set(&ps, pkgname, archname);
+ if (emsg) {
+ dpkg_put_error(err, "%s", emsg);
+ pkg = NULL;
+ } else {
+ pkg = pkg_spec_get_pkg(&ps);
+ }
+ pkg_spec_destroy(&ps);
+
+ return pkg;
+}
+
+void
+pkg_spec_iter_init(struct pkg_spec *ps)
+{
+ if (ps->name_is_pattern)
+ ps->pkg_iter = pkg_hash_iter_new();
+ else
+ ps->pkg_next = &pkg_hash_find_set(ps->name)->pkg;
+}
+
+static struct pkginfo *
+pkg_spec_iter_next_pkgname(struct pkg_spec *ps)
+{
+ struct pkginfo *pkg;
+
+ while ((pkg = pkg_hash_iter_next_pkg(ps->pkg_iter))) {
+ if (pkg_spec_match_pkg(ps, pkg, &pkg->installed))
+ return pkg;
+ }
+
+ return NULL;
+}
+
+static struct pkginfo *
+pkg_spec_iter_next_pkgarch(struct pkg_spec *ps)
+{
+ struct pkginfo *pkg;
+
+ while ((pkg = ps->pkg_next)) {
+ ps->pkg_next = pkg->arch_next;
+
+ if (pkg_spec_match_arch(ps, pkg, pkg->installed.arch))
+ return pkg;
+ }
+
+ return NULL;
+}
+
+struct pkginfo *
+pkg_spec_iter_next_pkg(struct pkg_spec *ps)
+{
+ if (ps->name_is_pattern)
+ return pkg_spec_iter_next_pkgname(ps);
+ else
+ return pkg_spec_iter_next_pkgarch(ps);
+}
+
+void
+pkg_spec_iter_destroy(struct pkg_spec *ps)
+{
+ pkg_hash_iter_free(ps->pkg_iter);
+ pkg_spec_iter_blank(ps);
+}
+
+void
+pkg_spec_destroy(struct pkg_spec *ps)
+{
+ free(ps->name);
+ pkg_spec_blank(ps);
+ pkg_spec_iter_destroy(ps);
+}
diff --git a/lib/dpkg/pkg-spec.h b/lib/dpkg/pkg-spec.h
new file mode 100644
index 0000000..5415d85
--- /dev/null
+++ b/lib/dpkg/pkg-spec.h
@@ -0,0 +1,91 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * pkg-spec.h - primitives for pkg specifier handling
+ *
+ * Copyright © 2011 Linaro Limited
+ * Copyright © 2011 Raphaël Hertzog <hertzog@debian.org>
+ * Copyright © 2011-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_PKG_SPEC_H
+#define LIBDPKG_PKG_SPEC_H
+
+#include <stdbool.h>
+
+#include <dpkg/macros.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/error.h>
+#include <dpkg/arch.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup pkg-spec Package specifiers
+ * @ingroup dpkg-public
+ * @{
+ */
+
+enum DPKG_ATTR_ENUM_FLAGS pkg_spec_flags {
+ /** Recognize glob patterns. */
+ PKG_SPEC_PATTERNS = DPKG_BIT(0),
+
+ /* How to consider the lack of an arch qualifier. */
+ PKG_SPEC_ARCH_SINGLE = DPKG_BIT(8),
+ PKG_SPEC_ARCH_WILDCARD = DPKG_BIT(9),
+ PKG_SPEC_ARCH_MASK = 0x0300,
+};
+
+struct pkg_spec {
+ char *name;
+ const struct dpkg_arch *arch;
+
+ enum pkg_spec_flags flags;
+
+ /* Members below are private state. */
+
+ bool name_is_pattern;
+ bool arch_is_pattern;
+
+ /** Used for the pkg_db iterator. */
+ struct pkg_hash_iter *pkg_iter;
+ /** Used for the pkgset iterator. */
+ struct pkginfo *pkg_next;
+};
+
+void pkg_spec_init(struct pkg_spec *ps, enum pkg_spec_flags flags);
+void pkg_spec_destroy(struct pkg_spec *ps);
+
+const char *pkg_spec_is_illegal(struct pkg_spec *ps);
+
+const char *pkg_spec_set(struct pkg_spec *ps,
+ const char *pkgname, const char *archname);
+const char *pkg_spec_parse(struct pkg_spec *ps, const char *str);
+bool pkg_spec_match_pkg(struct pkg_spec *ps,
+ struct pkginfo *pkg, struct pkgbin *pkgbin);
+
+struct pkginfo *pkg_spec_parse_pkg(const char *str, struct dpkg_error *err);
+struct pkginfo *pkg_spec_find_pkg(const char *pkgname, const char *archname,
+ struct dpkg_error *err);
+
+void pkg_spec_iter_init(struct pkg_spec *ps);
+struct pkginfo *pkg_spec_iter_next_pkg(struct pkg_spec *ps);
+void pkg_spec_iter_destroy(struct pkg_spec *ps);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif
diff --git a/lib/dpkg/pkg.c b/lib/dpkg/pkg.c
new file mode 100644
index 0000000..22bab5b
--- /dev/null
+++ b/lib/dpkg/pkg.c
@@ -0,0 +1,217 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * pkg.c - primitives for pkg handling
+ *
+ * Copyright © 1995, 1996 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2009-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <string.h>
+
+#include <dpkg/ehandle.h>
+#include <dpkg/string.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg.h>
+
+/**
+ * Set the package installation status.
+ */
+void
+pkg_set_status(struct pkginfo *pkg, enum pkgstatus status)
+{
+ if (pkg->status == status)
+ return;
+ else if (pkg->status == PKG_STAT_NOTINSTALLED)
+ pkg->set->installed_instances++;
+ else if (status == PKG_STAT_NOTINSTALLED)
+ pkg->set->installed_instances--;
+
+ if (pkg->set->installed_instances < 0)
+ internerr("pkgset %s went into negative installed instances %d",
+ pkg->set->name, pkg->set->installed_instances);
+
+ pkg->status = status;
+ pkg->status_dirty = true;
+}
+
+/**
+ * Set the specified flags to 1 in the package error flags.
+ */
+void
+pkg_set_eflags(struct pkginfo *pkg, enum pkgeflag eflag)
+{
+ pkg->eflag |= eflag;
+}
+
+/**
+ * Clear the specified flags to 0 in the package error flags.
+ */
+void
+pkg_clear_eflags(struct pkginfo *pkg, enum pkgeflag eflag)
+{
+ pkg->eflag &= ~eflag;
+}
+
+/**
+ * Reset the package error flags to 0.
+ */
+void
+pkg_reset_eflags(struct pkginfo *pkg)
+{
+ pkg->eflag = PKG_EFLAG_OK;
+}
+
+/**
+ * Copy the package error flags to another package.
+ */
+void
+pkg_copy_eflags(struct pkginfo *pkg_dst, struct pkginfo *pkg_src)
+{
+ pkg_dst->eflag = pkg_src->eflag;
+}
+
+/**
+ * Set the package selection status.
+ */
+void
+pkg_set_want(struct pkginfo *pkg, enum pkgwant want)
+{
+ pkg->want = want;
+}
+
+void
+pkgbin_blank(struct pkgbin *pkgbin)
+{
+ pkgbin->essential = false;
+ pkgbin->is_protected = false;
+ pkgbin->depends = NULL;
+ pkgbin->pkgname_archqual = NULL;
+ pkgbin->description = NULL;
+ pkgbin->maintainer = NULL;
+ pkgbin->source = NULL;
+ pkgbin->installedsize = NULL;
+ pkgbin->bugs = NULL;
+ pkgbin->origin = NULL;
+ dpkg_version_blank(&pkgbin->version);
+ pkgbin->conffiles = NULL;
+ pkgbin->arbs = NULL;
+}
+
+void
+pkg_blank(struct pkginfo *pkg)
+{
+ pkg->status = PKG_STAT_NOTINSTALLED;
+ pkg->status_dirty = false;
+ pkg->eflag = PKG_EFLAG_OK;
+ pkg->want = PKG_WANT_UNKNOWN;
+ pkg->priority = PKG_PRIO_UNKNOWN;
+ pkg->otherpriority = NULL;
+ pkg->section = NULL;
+ dpkg_version_blank(&pkg->configversion);
+ pkg->files_list_valid = false;
+ pkg->files_list_phys_offs = 0;
+ pkg->files = NULL;
+ pkg->archives = NULL;
+ pkg->clientdata = NULL;
+ pkg->trigaw.head = NULL;
+ pkg->trigaw.tail = NULL;
+ pkg->othertrigaw_head = NULL;
+ pkg->trigpend_head = NULL;
+ pkgbin_blank(&pkg->installed);
+ pkgbin_blank(&pkg->available);
+
+ /* The architectures are reset here (instead of in pkgbin_blank),
+ * because they are part of the package specification, and needed
+ * for selections. */
+ pkg->installed.arch = dpkg_arch_get(DPKG_ARCH_NONE);
+ pkg->installed.multiarch = PKG_MULTIARCH_NO;
+ pkg->available.arch = dpkg_arch_get(DPKG_ARCH_NONE);
+ pkg->available.multiarch = PKG_MULTIARCH_NO;
+}
+
+void
+pkgset_blank(struct pkgset *set)
+{
+ set->name = NULL;
+ set->depended.available = NULL;
+ set->depended.installed = NULL;
+ pkg_blank(&set->pkg);
+ set->installed_instances = 0;
+ set->pkg.set = set;
+ set->pkg.arch_next = NULL;
+}
+
+/**
+ * Link a pkginfo instance into a package set.
+ *
+ * @param set The package set to use.
+ * @param pkg The package to link into the set.
+ */
+void
+pkgset_link_pkg(struct pkgset *set, struct pkginfo *pkg)
+{
+ pkg->set = set;
+ pkg->arch_next = set->pkg.arch_next;
+ set->pkg.arch_next = pkg;
+}
+
+/**
+ * Get the number of installed package instances in a package set.
+ *
+ * @param set The package set to use.
+ *
+ * @return The count of installed packages.
+ */
+int
+pkgset_installed_instances(struct pkgset *set)
+{
+ return set->installed_instances;
+}
+
+/**
+ * Check if a pkg is informative.
+ *
+ * Used by dselect and dpkg query options as an aid to decide whether to
+ * display things, and by dump to decide whether to write them out.
+ */
+bool
+pkg_is_informative(struct pkginfo *pkg, struct pkgbin *pkgbin)
+{
+ /* We ignore Section and Priority, as these tend to hang around. */
+ if (pkgbin == &pkg->installed &&
+ (pkg->want != PKG_WANT_UNKNOWN ||
+ pkg->eflag != PKG_EFLAG_OK ||
+ pkg->status != PKG_STAT_NOTINSTALLED ||
+ dpkg_version_is_informative(&pkg->configversion)))
+ return true;
+
+ if (pkgbin->depends ||
+ str_is_set(pkgbin->description) ||
+ str_is_set(pkgbin->maintainer) ||
+ str_is_set(pkgbin->origin) ||
+ str_is_set(pkgbin->bugs) ||
+ str_is_set(pkgbin->installedsize) ||
+ str_is_set(pkgbin->source) ||
+ dpkg_version_is_informative(&pkgbin->version) ||
+ pkgbin->conffiles ||
+ pkgbin->arbs)
+ return true;
+
+ return false;
+}
diff --git a/lib/dpkg/pkg.h b/lib/dpkg/pkg.h
new file mode 100644
index 0000000..766966f
--- /dev/null
+++ b/lib/dpkg/pkg.h
@@ -0,0 +1,50 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * pkg.h - primitives for pkg handling
+ *
+ * Copyright © 2009,2011-2012 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_PKG_H
+#define LIBDPKG_PKG_H
+
+#include <dpkg/macros.h>
+#include <dpkg/dpkg-db.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup pkg Package handling primitives
+ * @ingroup dpkg-public
+ * @{
+ */
+
+typedef int pkg_sorter_func(const void *a, const void *b);
+
+void pkgset_link_pkg(struct pkgset *set, struct pkginfo *pkg);
+
+void pkg_set_status(struct pkginfo *pkg, enum pkgstatus status);
+void pkg_set_eflags(struct pkginfo *pkg, enum pkgeflag eflag);
+void pkg_clear_eflags(struct pkginfo *pkg, enum pkgeflag eflag);
+void pkg_reset_eflags(struct pkginfo *pkg);
+void pkg_copy_eflags(struct pkginfo *pkg_dst, struct pkginfo *pkg_src);
+void pkg_set_want(struct pkginfo *pkg, enum pkgwant want);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_PKG_H */
diff --git a/lib/dpkg/progname.c b/lib/dpkg/progname.c
new file mode 100644
index 0000000..65592cc
--- /dev/null
+++ b/lib/dpkg/progname.c
@@ -0,0 +1,89 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * progname.c - program name handling functions
+ *
+ * Copyright © 2011 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#ifdef HAVE_GETPROCS64
+#include <sys/types.h>
+#include <string.h>
+#include <unistd.h>
+#include <procinfo.h>
+#endif
+#include <errno.h>
+#include <stdlib.h>
+
+#include <dpkg/path.h>
+#include <dpkg/progname.h>
+
+static const char *progname;
+
+/**
+ * Set the program name.
+ *
+ * This function will set the program name which will be used by error
+ * reporting functions, among others.
+ *
+ * @param name The new program name.
+ */
+void
+dpkg_set_progname(const char *name)
+{
+ progname = path_basename(name);
+}
+
+#if defined(HAVE___PROGNAME)
+extern const char *__progname;
+#endif
+
+/**
+ * Get the program name.
+ *
+ * The program name might have been set previously by dpkg_set_progname(),
+ * but if not this function will try to initialize it by system dependent
+ * means, so it's half safe to not call dpkg_set_progname() at all. At worst
+ * the function might return NULL in that case.
+ *
+ * @return A pointer to a static buffer with the program name.
+ */
+const char *
+dpkg_get_progname(void)
+{
+ if (progname == NULL) {
+#if defined(HAVE_PROGRAM_INVOCATION_SHORT_NAME)
+ progname = program_invocation_short_name;
+#elif defined(HAVE___PROGNAME)
+ progname = __progname;
+#elif defined(HAVE_GETPROGNAME)
+ progname = getprogname();
+#elif defined(HAVE_GETEXECNAME)
+ /* getexecname(3) returns an absolute path, normalize it. */
+ dpkg_set_progname(getexecname());
+#elif defined(HAVE_GETPROCS64)
+ struct procentry64 pe;
+ pid_t pid = getpid();
+
+ if (getprocs64(&pe, sizeof(pe), NULL, 0, &pid, 1) >= 0)
+ progname = strdup(pe.pi_comm);
+#endif
+ }
+
+ return progname;
+}
diff --git a/lib/dpkg/progname.h b/lib/dpkg/progname.h
new file mode 100644
index 0000000..d7c6061
--- /dev/null
+++ b/lib/dpkg/progname.h
@@ -0,0 +1,41 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * progname.h - program name handling functions
+ *
+ * Copyright © 2011 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_PROGNAME_H
+#define LIBDPKG_PROGNAME_H
+
+#include <dpkg/macros.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup progname Program name handling
+ * @ingroup dpkg-public
+ * @{
+ */
+
+void dpkg_set_progname(const char *name);
+const char *dpkg_get_progname(void);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif
diff --git a/lib/dpkg/program.c b/lib/dpkg/program.c
new file mode 100644
index 0000000..12226c0
--- /dev/null
+++ b/lib/dpkg/program.c
@@ -0,0 +1,59 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * program.c - dpkg-based program support
+ *
+ * Copyright © 2013 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/stat.h>
+
+#include <stdio.h>
+
+#include <dpkg/progname.h>
+#include <dpkg/debug.h>
+#include <dpkg/report.h>
+#include <dpkg/ehandle.h>
+#include <dpkg/program.h>
+
+/**
+ * Standard initializations when starting a dpkg-based program.
+ *
+ * @param progname The program name.
+ */
+void
+dpkg_program_init(const char *progname)
+{
+ dpkg_set_progname(progname);
+ dpkg_set_report_buffer(stdout);
+ dpkg_debug_init();
+
+ push_error_context();
+
+ /* Set sane default permissions for newly created files. */
+ umask(022);
+}
+
+/**
+ * Standard cleanups before terminating a dpkg-based program.
+ */
+void
+dpkg_program_done(void)
+{
+ pop_error_context(ehflag_normaltidy);
+}
diff --git a/lib/dpkg/program.h b/lib/dpkg/program.h
new file mode 100644
index 0000000..e4fe4f4
--- /dev/null
+++ b/lib/dpkg/program.h
@@ -0,0 +1,41 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * program.h - dpkg-based program support
+ *
+ * Copyright © 2013 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_PROGRAM_H
+#define LIBDPKG_PROGRAM_H
+
+#include <dpkg/macros.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup program Program support
+ * @ingroup dpkg-public
+ * @{
+ */
+
+void dpkg_program_init(const char *progname);
+void dpkg_program_done(void);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif
diff --git a/lib/dpkg/progress.c b/lib/dpkg/progress.c
new file mode 100644
index 0000000..9b6032f
--- /dev/null
+++ b/lib/dpkg/progress.c
@@ -0,0 +1,77 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * progress.c - generic progress reporting
+ *
+ * Copyright © 2009 Romain Francoise <rfrancoise@debian.org>
+ * Copyright © 2009 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <unistd.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+
+#include "progress.h"
+
+void
+progress_init(struct progress *progress, const char *text, int max)
+{
+ progress->text = text;
+ progress->max = max;
+ progress->cur = 0;
+ progress->last_percent = 0;
+
+ progress->on_tty = isatty(1);
+
+ fputs(text, stdout);
+ if (progress->on_tty)
+ putchar('\r');
+}
+
+void
+progress_step(struct progress *progress)
+{
+ int cur_percent;
+
+ if (!progress->on_tty)
+ return;
+
+ progress->cur++;
+
+ cur_percent = (progress->cur * 100) / progress->max;
+ if (cur_percent <= progress->last_percent)
+ return;
+ if (cur_percent % 5)
+ return;
+
+ progress->last_percent = cur_percent;
+
+ fputs(progress->text, stdout);
+ /* TRANSLATORS: This is part of the progress output, it is a decimal
+ * percentage. */
+ printf(_("%d%%"), cur_percent);
+ putchar('\r');
+}
+
+void
+progress_done(struct progress *progress)
+{
+ if (progress->on_tty)
+ fputs(progress->text, stdout);
+}
diff --git a/lib/dpkg/progress.h b/lib/dpkg/progress.h
new file mode 100644
index 0000000..c5450a5
--- /dev/null
+++ b/lib/dpkg/progress.h
@@ -0,0 +1,54 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * progress.h - generic progress reporting
+ *
+ * Copyright © 2009 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_PROGRESS_H
+#define LIBDPKG_PROGRESS_H
+
+#include <stdbool.h>
+
+#include <dpkg/macros.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup progress Progress reporting
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+struct progress {
+ const char *text;
+
+ int max;
+ int cur;
+ int last_percent;
+
+ bool on_tty;
+};
+
+void progress_init(struct progress *progress, const char *text, int max);
+void progress_step(struct progress *progress);
+void progress_done(struct progress *progress);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif
diff --git a/lib/dpkg/report.c b/lib/dpkg/report.c
new file mode 100644
index 0000000..f7763b3
--- /dev/null
+++ b/lib/dpkg/report.c
@@ -0,0 +1,132 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * report.c - message reporting
+ *
+ * Copyright © 2004-2005 Scott James Remnant <scott@netsplit.com>
+ * Copyright © 2008-2013 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <dpkg/dpkg.h>
+#include <dpkg/macros.h>
+#include <dpkg/i18n.h>
+#include <dpkg/progname.h>
+#include <dpkg/color.h>
+#include <dpkg/report.h>
+
+static int piped_mode = _IOLBF;
+
+void
+dpkg_set_report_piped_mode(int mode)
+{
+ piped_mode = mode;
+}
+
+void
+dpkg_set_report_buffer(FILE *fp)
+{
+ if (isatty(fileno(fp)))
+ setvbuf(fp, NULL, _IONBF, 0);
+ else
+ setvbuf(fp, NULL, piped_mode, 0);
+}
+
+void
+dpkg_warning_printer(const char *msg, void *data)
+{
+ fprintf(stderr, "%s%s:%s %s%s:%s %s\n",
+ color_get(COLOR_PROG), dpkg_get_progname(), color_reset(),
+ color_get(COLOR_WARN), _("warning"), color_reset(), msg);
+}
+
+static dpkg_warning_printer_func *warning_printer_func = dpkg_warning_printer;
+static void *warning_printer_data;
+
+void
+dpkg_set_warning_printer(dpkg_warning_printer_func *printer, void *data)
+{
+ warning_printer_func = printer;
+ warning_printer_data = data;
+}
+
+static int warn_count = 0;
+
+int
+warning_get_count(void)
+{
+ return warn_count;
+}
+
+void
+warningv(const char *fmt, va_list args)
+{
+ char *buf = NULL;
+
+ warn_count++;
+
+ m_vasprintf(&buf, fmt, args);
+ warning_printer_func(buf, warning_printer_data);
+ free(buf);
+}
+
+void
+warning(const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ warningv(fmt, args);
+ va_end(args);
+}
+
+void
+notice(const char *fmt, ...)
+{
+ char *buf = NULL;
+ va_list args;
+
+ va_start(args, fmt);
+ m_vasprintf(&buf, fmt, args);
+ va_end(args);
+
+ fprintf(stderr, "%s%s:%s %s\n",
+ color_get(COLOR_PROG), dpkg_get_progname(), color_reset(), buf);
+
+ free(buf);
+}
+
+void
+info(const char *fmt, ...)
+{
+ char *buf;
+ va_list args;
+
+ va_start(args, fmt);
+ m_vasprintf(&buf, fmt, args);
+ va_end(args);
+
+ printf("%s%s:%s %s\n",
+ color_get(COLOR_PROG), dpkg_get_progname(), color_reset(), buf);
+
+ free(buf);
+}
diff --git a/lib/dpkg/report.h b/lib/dpkg/report.h
new file mode 100644
index 0000000..862a842
--- /dev/null
+++ b/lib/dpkg/report.h
@@ -0,0 +1,58 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * report.h - message reporting
+ *
+ * Copyright © 2004 Scott James Remnant <scott@netsplit.com>
+ * Copyright © 2008-2012 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_REPORT_H
+#define LIBDPKG_REPORT_H
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include <dpkg/macros.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup report Message reporting
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+void dpkg_set_report_piped_mode(int mode);
+void dpkg_set_report_buffer(FILE *fp);
+
+typedef void dpkg_warning_printer_func(const char *msg, void *data);
+
+void dpkg_warning_printer(const char *msg, void *data);
+void dpkg_set_warning_printer(dpkg_warning_printer_func *printer, void *data);
+
+int warning_get_count(void);
+void warningv(const char *fmt, va_list args) DPKG_ATTR_VPRINTF(1);
+void warning(const char *fmt, ...) DPKG_ATTR_PRINTF(1);
+
+void notice(const char *fmt, ...) DPKG_ATTR_PRINTF(1);
+
+void info(const char *fmt, ...) DPKG_ATTR_PRINTF(1);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_REPORT_H */
diff --git a/lib/dpkg/strhash.c b/lib/dpkg/strhash.c
new file mode 100644
index 0000000..7fd51ee
--- /dev/null
+++ b/lib/dpkg/strhash.c
@@ -0,0 +1,50 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * strhash.c - FNV string hashing support
+ *
+ * Copyright © 2003 Daniel Silverstone <dsilvers@digital-scurf.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/string.h>
+
+#define FNV_OFFSET_BASIS 2166136261UL
+#define FNV_MIXING_PRIME 16777619UL
+
+/**
+ * Fowler/Noll/Vo -- FNV-1a simple string hash.
+ *
+ * For more info, @see <http://www.isthe.com/chongo/tech/comp/fnv/index.html>.
+ *
+ * @param str The string to hash.
+ *
+ * @return The hashed value.
+ */
+unsigned int
+str_fnv_hash(const char *str)
+{
+ unsigned int h = FNV_OFFSET_BASIS;
+ unsigned int p = FNV_MIXING_PRIME;
+
+ while (*str) {
+ h ^= *str++;
+ h *= p;
+ }
+
+ return h;
+}
diff --git a/lib/dpkg/string.c b/lib/dpkg/string.c
new file mode 100644
index 0000000..de18297
--- /dev/null
+++ b/lib/dpkg/string.c
@@ -0,0 +1,196 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * string.c - string handling routines
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008-2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <string.h>
+
+#include <dpkg/c-ctype.h>
+#include <dpkg/string.h>
+#include <dpkg/dpkg.h>
+
+char *
+str_concat(char *dst, ...)
+{
+ va_list args;
+ const char *src;
+
+ va_start(args, dst);
+ while ((src = va_arg(args, const char *))) {
+ size_t len;
+
+ len = strlen(src);
+ memcpy(dst, src, len);
+ dst += len;
+ }
+ va_end(args);
+ *dst = '\0';
+
+ return dst;
+}
+
+/**
+ * Match the end of a string.
+ *
+ * @param str The string.
+ * @param end The end to match in str.
+ *
+ * @return Whether the string was matched at the end.
+ */
+bool
+str_match_end(const char *str, const char *end)
+{
+ size_t str_len = strlen(str);
+ size_t end_len = strlen(end);
+ const char *str_end = str + str_len - end_len;
+
+ if (str_len >= end_len && strcmp(str_end, end) == 0)
+ return true;
+ else
+ return false;
+}
+
+/**
+ * Print formatted output to an allocated string.
+ *
+ * @param fmt The format string.
+ * @param ... The format arguments.
+ *
+ * @return The new allocated formatted output string (never NULL).
+ */
+char *
+str_fmt(const char *fmt, ...)
+{
+ va_list args;
+ char *str;
+
+ va_start(args, fmt);
+ m_vasprintf(&str, fmt, args);
+ va_end(args);
+
+ return str;
+}
+
+/**
+ * Escape format characters from a string.
+ *
+ * @param dst The destination string.
+ * @param src The source string.
+ * @param n The size of the destination buffer.
+ *
+ * @return The end of the destination string.
+ */
+char *
+str_escape_fmt(char *dst, const char *src, size_t n)
+{
+ char *d = dst;
+ const char *s = src;
+
+ if (n == 0)
+ return d;
+
+ while (*s) {
+ if (*s == '%') {
+ if (n-- <= 2)
+ break;
+ *d++ = '%';
+ }
+ if (n-- <= 1)
+ break;
+ *d++ = *s++;
+ }
+
+ *d = '\0';
+
+ return d;
+}
+
+/**
+ * Quote shell metacharacters in a string.
+ *
+ * This function allows passing strings to commands without splitting the
+ * arguments, like in system(3)
+ *
+ * @param src The source string to escape.
+ *
+ * @return The new allocated string (never NULL).
+ */
+char *
+str_quote_meta(const char *src)
+{
+ char *new_dst, *dst;
+
+ new_dst = dst = m_malloc(strlen(src) * 2);
+
+ while (*src) {
+ if (!c_isdigit(*src) && !c_isalpha(*src))
+ *dst++ = '\\';
+
+ *dst++ = *src++;
+ }
+
+ *dst = '\0';
+
+ return new_dst;
+}
+
+/**
+ * Check and strip possible surrounding quotes in string.
+ *
+ * @param str The string to act on.
+ *
+ * @return A pointer to str or NULL if the quotes were unbalanced.
+ */
+char *
+str_strip_quotes(char *str)
+{
+ if (str[0] == '"' || str[0] == '\'') {
+ size_t str_len = strlen(str);
+
+ if (str[0] != str[str_len - 1])
+ return NULL;
+
+ /* Remove surrounding quotes. */
+ str[str_len - 1] = '\0';
+ str++;
+ }
+
+ return str;
+}
+
+/**
+ * Trim possible ending spaces in string.
+ *
+ * @param str The string to act on.
+ * @param str_end The end of the string to act on.
+ *
+ * @return A pointer to the end of the trimmed string.
+ */
+char *
+str_rtrim_spaces(const char *str, char *str_end)
+{
+ while (str_end > str && c_isspace(str_end[-1]))
+ str_end--;
+ if (str_end >= str)
+ *str_end = '\0';
+ return str_end;
+}
diff --git a/lib/dpkg/string.h b/lib/dpkg/string.h
new file mode 100644
index 0000000..47ecd04
--- /dev/null
+++ b/lib/dpkg/string.h
@@ -0,0 +1,78 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * string.h - string handling routines
+ *
+ * Copyright © 2008-2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_STRING_H
+#define LIBDPKG_STRING_H
+
+#include <stddef.h>
+#include <stdbool.h>
+
+#include <dpkg/macros.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup string String handling
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+/**
+ * Check if a string is either null or empty.
+ */
+static inline bool
+str_is_unset(const char *str)
+{
+ return str == DPKG_NULL || str[0] == '\0';
+}
+
+/**
+ * Check if a string has content.
+ */
+static inline bool
+str_is_set(const char *str)
+{
+ return str != DPKG_NULL && str[0] != '\0';
+}
+
+bool str_match_end(const char *str, const char *end);
+
+unsigned int str_fnv_hash(const char *str);
+
+char *str_concat(char *dst, ...) DPKG_ATTR_SENTINEL;
+char *str_fmt(const char *fmt, ...) DPKG_ATTR_PRINTF(1);
+char *str_escape_fmt(char *dest, const char *src, size_t n);
+char *str_quote_meta(const char *src);
+char *str_strip_quotes(char *str);
+char *str_rtrim_spaces(const char *str, char *str_end);
+
+struct str_crop_info {
+ int str_bytes;
+ int max_bytes;
+};
+
+int str_width(const char *str);
+void str_gen_crop(const char *str, int max_width, struct str_crop_info *crop);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_STRING_H */
diff --git a/lib/dpkg/strwide.c b/lib/dpkg/strwide.c
new file mode 100644
index 0000000..0a510b7
--- /dev/null
+++ b/lib/dpkg/strwide.c
@@ -0,0 +1,141 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * strwide.c - wide character string handling routines
+ *
+ * Copyright © 2004 Changwoo Ryu <cwryu@debian.org>
+ * Copyright © 2012-2013 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <string.h>
+#include <stdlib.h>
+#ifdef ENABLE_NLS
+#include <wchar.h>
+#endif
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/string.h>
+
+/**
+ * Compute the screen width of a string.
+ *
+ * @param str The multibyte string.
+ *
+ * @return The width of the string.
+ */
+int
+str_width(const char *str)
+{
+#ifdef ENABLE_NLS
+ mbstate_t state;
+ wchar_t *wcs;
+ const char *mbs = str;
+ size_t len, res;
+ int width;
+
+ len = strlen(str) + 1;
+ wcs = m_malloc(sizeof(wcs[0]) * len);
+
+ memset(&state, 0, sizeof(state));
+
+ res = mbsrtowcs(wcs, &mbs, len, &state);
+ if (res == (size_t)-1) {
+#ifdef DPKG_UNIFORM_ENCODING
+ ohshit(_("cannot convert multibyte string '%s' "
+ "to a wide-character string"), str);
+#else
+ /* Cannot convert, fallback to ASCII method. */
+ free(wcs);
+ return strlen(str);
+#endif
+ }
+
+ width = wcswidth(wcs, res);
+
+ free(wcs);
+
+ return width;
+#else
+ return strlen(str);
+#endif
+}
+
+/**
+ * Generate the crop values for a string given a maximum screen width.
+ *
+ * This function analyzes the string passed and computes the correct point
+ * where to crop the string, returning the amount of string and maximum
+ * bytes to use for padding for example.
+ *
+ * On NLS enabled builds, in addition the string will be cropped on any
+ * newline.
+ *
+ * @param str The string to crop.
+ * @param max_width The max screen width to use.
+ * @param[out] crop The generated crop values for the string.
+ */
+void
+str_gen_crop(const char *str, int max_width, struct str_crop_info *crop)
+{
+#ifdef ENABLE_NLS
+ mbstate_t state;
+ size_t str_bytes;
+ int mbs_bytes = 0;
+ int mbs_width = 0;
+
+ str_bytes = strlen(str) + 1;
+ memset(&state, 0, sizeof(state));
+
+ for (;;) {
+ wchar_t wc;
+ int wc_width;
+ size_t mb_bytes;
+
+ mb_bytes = mbrtowc(&wc, str, str_bytes, &state);
+ if (mb_bytes == (size_t)-1 || mb_bytes == (size_t)-2) {
+#ifdef DPKG_UNIFORM_ENCODING
+ ohshit(_("cannot convert multibyte sequence '%s' "
+ "to a wide character"), str);
+#else
+ /* Cannot convert, fallback to ASCII method. */
+ crop->str_bytes = crop->max_bytes = max_width;
+ return;
+#endif
+ }
+ if (mb_bytes == 0)
+ break;
+
+ wc_width = wcwidth(wc);
+ if (wc_width < 0)
+ break;
+ if (mbs_width + wc_width > max_width)
+ break;
+
+ mbs_width += wc_width;
+ mbs_bytes += mb_bytes;
+ str_bytes -= mb_bytes;
+ str += mb_bytes;
+ }
+
+ crop->str_bytes = mbs_bytes;
+ crop->max_bytes = mbs_bytes + max_width - mbs_width;
+#else
+ crop->str_bytes = crop->max_bytes = max_width;
+#endif
+}
diff --git a/lib/dpkg/subproc.c b/lib/dpkg/subproc.c
new file mode 100644
index 0000000..7b3fb99
--- /dev/null
+++ b/lib/dpkg/subproc.c
@@ -0,0 +1,195 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * subproc.c - subprocess helper routines
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/subproc.h>
+
+static int signo_ignores[] = { SIGQUIT, SIGINT };
+static struct sigaction sa_save[array_count(signo_ignores)];
+
+static void
+subproc_reset_signal(int sig, struct sigaction *sa_old)
+{
+ if (sigaction(sig, sa_old, NULL)) {
+ fprintf(stderr, _("error un-catching signal %s: %s\n"),
+ strsignal(sig), strerror(errno));
+ onerr_abort++;
+ }
+}
+
+static void
+subproc_set_signal(int sig, struct sigaction *sa, struct sigaction *sa_old,
+ const char *name)
+{
+ if (sigaction(sig, sa, sa_old))
+ ohshite(_("unable to ignore signal %s before running %.250s"),
+ strsignal(sig), name);
+}
+
+void
+subproc_signals_ignore(const char *name)
+{
+ struct sigaction sa;
+ size_t i;
+
+ onerr_abort++;
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = SIG_IGN;
+ sa.sa_flags = 0;
+
+ for (i = 0; i < array_count(signo_ignores); i++)
+ subproc_set_signal(signo_ignores[i], &sa, &sa_save[i], name);
+
+ push_cleanup(subproc_signals_cleanup, ~0, 0);
+ onerr_abort--;
+}
+
+void
+subproc_signals_cleanup(int argc, void **argv)
+{
+ size_t i;
+
+ for (i = 0; i < array_count(signo_ignores); i++)
+ subproc_reset_signal(signo_ignores[i], &sa_save[i]);
+}
+
+void
+subproc_signals_restore(void)
+{
+ pop_cleanup(ehflag_normaltidy);
+}
+
+static void
+print_subproc_error(const char *emsg, const void *data)
+{
+ fprintf(stderr, _("%s (subprocess): %s\n"), dpkg_get_progname(), emsg);
+}
+
+pid_t
+subproc_fork(void)
+{
+ pid_t pid;
+
+ pid = fork();
+ if (pid == -1) {
+ onerr_abort++;
+ ohshite(_("fork failed"));
+ }
+ if (pid > 0)
+ return pid;
+
+ /* Push a new error context, so that we don't do the other cleanups,
+ * because they'll be done by/in the parent process. */
+ push_error_context_func(catch_fatal_error, print_subproc_error, NULL);
+
+ return pid;
+}
+
+static int
+subproc_check(int status, const char *desc, enum subproc_flags flags)
+{
+ void (*out)(const char *fmt, ...) DPKG_ATTR_PRINTF(1);
+ int n;
+
+ if (flags & SUBPROC_WARN)
+ out = warning;
+ else
+ out = ohshit;
+
+ if (WIFEXITED(status)) {
+ n = WEXITSTATUS(status);
+ if (!n)
+ return 0;
+ if (flags & SUBPROC_RETERROR)
+ return n;
+
+ out(_("%s subprocess returned error exit status %d"), desc, n);
+ } else if (WIFSIGNALED(status)) {
+ n = WTERMSIG(status);
+ if (!n)
+ return 0;
+ if ((flags & SUBPROC_NOPIPE) && n == SIGPIPE)
+ return 0;
+ if (flags & SUBPROC_RETSIGNO)
+ return n;
+
+ if (n == SIGINT)
+ out(_("%s subprocess was interrupted"), desc);
+ else
+ out(_("%s subprocess was killed by signal (%s)%s"),
+ desc, strsignal(n),
+ WCOREDUMP(status) ? _(", core dumped") : "");
+ } else {
+ if (flags & SUBPROC_RETERROR)
+ return -1;
+
+ out(_("%s subprocess failed with wait status code %d"), desc,
+ status);
+ }
+
+ return -1;
+}
+
+static int
+subproc_wait(pid_t pid, const char *desc)
+{
+ pid_t dead_pid;
+ int status;
+
+ while ((dead_pid = waitpid(pid, &status, 0)) == -1 && errno == EINTR) ;
+
+ if (dead_pid != pid) {
+ onerr_abort++;
+ ohshite(_("wait for %s subprocess failed"), desc);
+ }
+
+ return status;
+}
+
+int
+subproc_reap(pid_t pid, const char *desc, enum subproc_flags flags)
+{
+ int status, rc;
+
+ status = subproc_wait(pid, desc);
+
+ if (flags & SUBPROC_NOCHECK)
+ rc = status;
+ else
+ rc = subproc_check(status, desc, flags);
+
+ return rc;
+}
diff --git a/lib/dpkg/subproc.h b/lib/dpkg/subproc.h
new file mode 100644
index 0000000..4bdf9f2
--- /dev/null
+++ b/lib/dpkg/subproc.h
@@ -0,0 +1,62 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * subproc.h - sub-process handling routines
+ *
+ * Copyright © 2008-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_SUBPROC_H
+#define LIBDPKG_SUBPROC_H
+
+#include <sys/types.h>
+
+#include <dpkg/macros.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup subproc Sub-process handling
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+enum DPKG_ATTR_ENUM_FLAGS subproc_flags {
+ /** Default subprocess flags. */
+ SUBPROC_NORMAL = 0,
+ /** Emit a warning instead of an error. */
+ SUBPROC_WARN = DPKG_BIT(0),
+ /** Ignore SIGPIPE, and make it return 0. */
+ SUBPROC_NOPIPE = DPKG_BIT(1),
+ /** Do not check the subprocess status. */
+ SUBPROC_NOCHECK = DPKG_BIT(2),
+ /** Do not emit errors, just return the exit status. */
+ SUBPROC_RETERROR = DPKG_BIT(3),
+ /** Do not emit errors, just return the signal number. */
+ SUBPROC_RETSIGNO = DPKG_BIT(3),
+};
+
+void subproc_signals_ignore(const char *name);
+void subproc_signals_cleanup(int argc, void **argv);
+void subproc_signals_restore(void);
+
+pid_t subproc_fork(void);
+int subproc_reap(pid_t pid, const char *desc, enum subproc_flags flags);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_SUBPROC_H */
diff --git a/lib/dpkg/t/b-fsys-hash.c b/lib/dpkg/t/b-fsys-hash.c
new file mode 100644
index 0000000..7ae8f9c
--- /dev/null
+++ b/lib/dpkg/t/b-fsys-hash.c
@@ -0,0 +1,81 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * b-fsys-hash.c - test fsys database load and hash performance
+ *
+ * Copyright © 2009-2019 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+
+#include <dpkg/perf.h>
+
+#include <dpkg/db-fsys.h>
+
+static const char *admindir;
+
+int
+main(int argc, const char *const *argv)
+{
+ struct perf_slot ps;
+
+ push_error_context();
+ setvbuf(stdout, NULL, _IONBF, 0);
+
+ admindir = dpkg_db_set_dir(admindir);
+
+ perf_ts_mark_print("init");
+
+ perf_ts_slot_start(&ps);
+ fsys_hash_init();
+ perf_ts_slot_stop(&ps);
+
+ perf_ts_slot_print(&ps, "fsys_hash_init");
+
+ perf_ts_slot_start(&ps);
+ modstatdb_open(msdbrw_readonly | msdbrw_available_readonly);
+ perf_ts_slot_stop(&ps);
+
+ perf_ts_slot_print(&ps, "modstatdb_init");
+
+ perf_ts_slot_start(&ps);
+ ensure_allinstfiles_available_quiet();
+ perf_ts_slot_stop(&ps);
+
+ perf_ts_slot_print(&ps, "load .list");
+
+ if (test_is_verbose()) {
+ pkg_hash_report(stdout);
+ fsys_hash_report(stdout);
+ }
+
+ modstatdb_shutdown();
+ pop_error_context(ehflag_normaltidy);
+
+ perf_ts_mark_print("shutdown");
+
+ return 0;
+}
diff --git a/lib/dpkg/t/b-pkg-hash.c b/lib/dpkg/t/b-pkg-hash.c
new file mode 100644
index 0000000..26cdc12
--- /dev/null
+++ b/lib/dpkg/t/b-pkg-hash.c
@@ -0,0 +1,65 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * b-pkg-hash.c - test pkg database load and hash performance
+ *
+ * Copyright © 2009-2019 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+
+#include <dpkg/perf.h>
+
+static const char *admindir;
+
+int
+main(int argc, const char *const *argv)
+{
+ struct perf_slot ps;
+
+ push_error_context();
+ setvbuf(stdout, NULL, _IONBF, 0);
+
+ admindir = dpkg_db_set_dir(admindir);
+
+ perf_ts_mark_print("init");
+
+ perf_ts_slot_start(&ps);
+ modstatdb_open(msdbrw_readonly | msdbrw_available_readonly);
+ perf_ts_slot_stop(&ps);
+
+ perf_ts_slot_print(&ps, "modstatdb_init");
+
+ if (test_is_verbose())
+ pkg_hash_report(stdout);
+
+ modstatdb_shutdown();
+ pop_error_context(ehflag_normaltidy);
+
+ perf_ts_mark_print("shutdown");
+
+ return 0;
+}
diff --git a/lib/dpkg/t/c-tarextract.c b/lib/dpkg/t/c-tarextract.c
new file mode 100644
index 0000000..82a8dab
--- /dev/null
+++ b/lib/dpkg/t/c-tarextract.c
@@ -0,0 +1,154 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * c-tarextract.c - test tar extractor
+ *
+ * Copyright © 2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#if HAVE_SYS_SYSMACROS_H
+#include <sys/sysmacros.h> /* Needed on AIX for major()/minor(). */
+#endif
+#if HAVE_SYS_MKDEV_H
+#include <sys/mkdev.h> /* Needed on Solaris for major()/minor(). */
+#endif
+
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <dpkg/ehandle.h>
+#include <dpkg/fdio.h>
+#include <dpkg/buffer.h>
+#include <dpkg/tarfn.h>
+
+struct tar_context {
+ int tar_fd;
+};
+
+static int
+tar_read(struct tar_archive *tar, char *buffer, int size)
+{
+ struct tar_context *tc = tar->ctx;
+
+ return fd_read(tc->tar_fd, buffer, size);
+}
+
+static int
+tar_object_skip(struct tar_archive *tar, struct tar_entry *te)
+{
+ struct tar_context *tc = tar->ctx;
+ off_t size;
+
+ size = (te->size + TARBLKSZ - 1) / TARBLKSZ * TARBLKSZ;
+ if (size == 0)
+ return 0;
+
+ return fd_skip(tc->tar_fd, size, NULL);
+}
+
+static int
+tar_object(struct tar_archive *tar, struct tar_entry *te)
+{
+ printf("%s mode=%o time=%jd.%.9d uid=%d gid=%d", te->name,
+ te->stat.mode, te->mtime, 0, te->stat.uid, te->stat.gid);
+ if (te->stat.uname)
+ printf(" uname=%s", te->stat.uname);
+ if (te->stat.gname)
+ printf(" gname=%s", te->stat.gname);
+
+ switch (te->type) {
+ case TAR_FILETYPE_FILE0:
+ case TAR_FILETYPE_FILE:
+ tar_object_skip(tar, te);
+ printf(" type=file size=%jd", (intmax_t)te->size);
+ break;
+ case TAR_FILETYPE_HARDLINK:
+ printf(" type=hardlink linkto=%s size=%jd",
+ te->linkname, (intmax_t)te->size);
+ break;
+ case TAR_FILETYPE_SYMLINK:
+ printf(" type=symlink linkto=%s size=%jd",
+ te->linkname, (intmax_t)te->size);
+ break;
+ case TAR_FILETYPE_DIR:
+ printf(" type=dir");
+ break;
+ case TAR_FILETYPE_CHARDEV:
+ case TAR_FILETYPE_BLOCKDEV:
+ printf(" type=device id=%d.%d", major(te->dev), minor(te->dev));
+ break;
+ case TAR_FILETYPE_FIFO:
+ printf(" type=fifo");
+ break;
+ default:
+ ohshit("unexpected tar entry type '%c'", te->type);
+ }
+
+ printf("\n");
+
+ return 0;
+}
+
+static struct tar_operations tar_ops = {
+ .read = tar_read,
+ .extract_file = tar_object,
+ .link = tar_object,
+ .symlink = tar_object,
+ .mkdir = tar_object,
+ .mknod = tar_object,
+};
+
+int
+main(int argc, char **argv)
+{
+ struct tar_archive tar;
+ struct tar_context tar_ctx;
+ const char *tar_name = argv[1];
+
+ setvbuf(stdout, NULL, _IOLBF, 0);
+
+ push_error_context();
+
+ if (tar_name) {
+ tar_ctx.tar_fd = open(tar_name, O_RDONLY);
+ if (tar_ctx.tar_fd < 0)
+ ohshite("cannot open file '%s'", tar_name);
+ } else {
+ tar_ctx.tar_fd = STDIN_FILENO;
+ }
+
+ tar.err = DPKG_ERROR_OBJECT;
+ tar.ctx = &tar_ctx;
+ tar.ops = &tar_ops;
+
+ if (tar_extractor(&tar))
+ ohshite("extracting tar");
+
+ dpkg_error_destroy(&tar.err);
+
+ if (tar_name)
+ close(tar_ctx.tar_fd);
+
+ pop_error_context(ehflag_normaltidy);
+
+ return 0;
+}
diff --git a/lib/dpkg/t/c-treewalk.c b/lib/dpkg/t/c-treewalk.c
new file mode 100644
index 0000000..4cd2b8c
--- /dev/null
+++ b/lib/dpkg/t/c-treewalk.c
@@ -0,0 +1,132 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * c-treewalk.c - test tree walk implementation
+ *
+ * Copyright © 2013-2016 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/ehandle.h>
+#include <dpkg/treewalk.h>
+
+static int
+treenode_type(struct treenode *node)
+{
+ switch (treenode_get_mode(node) & S_IFMT) {
+ case S_IFREG:
+ return 'f';
+ case S_IFDIR:
+ return 'd';
+ case S_IFLNK:
+ return 'l';
+ case S_IFIFO:
+ return 'p';
+ case S_IFBLK:
+ return 'b';
+ case S_IFCHR:
+ return 'c';
+ case S_IFSOCK:
+ return 's';
+ default:
+ return '?';
+ }
+}
+
+static int
+treenode_visit_meta(struct treenode *node)
+{
+ printf("T=%c N=%s V=%s R=%s\n",
+ treenode_type(node), treenode_get_name(node),
+ treenode_get_virtname(node), treenode_get_pathname(node));
+
+ return 0;
+}
+
+static int
+treenode_visit_virt(struct treenode *node)
+{
+ printf("%s\n", treenode_get_virtname(node));
+
+ return 0;
+}
+
+static int
+treenode_visit_path(struct treenode *node)
+{
+ printf("%s\n", treenode_get_pathname(node));
+
+ return 0;
+}
+
+static const char *skipname;
+
+static bool
+treenode_skip(struct treenode *node)
+{
+ const char *absname = treenode_get_pathname(node);
+
+ if (strcmp(absname, skipname) == 0)
+ return true;
+
+ return false;
+}
+
+static void
+test_treewalk_list(const char *rootdir)
+{
+ const char *visitname = getenv("TREEWALK_VISIT");
+ struct treewalk_funcs funcs = { NULL };
+
+ if (visitname == NULL || strcmp(visitname, "meta") == 0)
+ funcs.visit = treenode_visit_meta;
+ else if (strcmp(visitname, "virt") == 0)
+ funcs.visit = treenode_visit_virt;
+ else if (strcmp(visitname, "path") == 0)
+ funcs.visit = treenode_visit_path;
+ else
+ ohshit("unknown treewalk visit name");
+
+ skipname = getenv("TREEWALK_SKIP");
+ if (skipname)
+ funcs.skip = treenode_skip;
+
+ treewalk(rootdir, 0, &funcs);
+}
+
+int
+main(int argc, char **argv)
+{
+ const char *rootdir = argv[1];
+
+ setvbuf(stdout, NULL, _IOLBF, 0);
+
+ push_error_context();
+
+ if (rootdir == NULL)
+ ohshit("missing treewalk root dir");
+
+ test_treewalk_list(rootdir);
+
+ pop_error_context(ehflag_normaltidy);
+
+ return 0;
+}
diff --git a/lib/dpkg/t/c-trigdeferred.c b/lib/dpkg/t/c-trigdeferred.c
new file mode 100644
index 0000000..5c21ea3
--- /dev/null
+++ b/lib/dpkg/t/c-trigdeferred.c
@@ -0,0 +1,96 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * c-trigdeferred.c - test triggered deferred file parser
+ *
+ * Copyright © 2016 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/dpkg-db.h>
+#include <dpkg/ehandle.h>
+#include <dpkg/trigdeferred.h>
+
+static void
+test_tdm_begin(const char *trig)
+{
+ printf("<T='%s'", trig);
+}
+
+static void
+test_tdm_package(const char *awname)
+{
+ printf(" P='%s'", awname);
+}
+
+static void
+test_tdm_end(void)
+{
+ printf(" E>\n");
+}
+
+static const struct trigdefmeths test_tdm = {
+ .trig_begin = test_tdm_begin,
+ .package = test_tdm_package,
+ .trig_end = test_tdm_end,
+};
+
+static int
+test_trigdeferred_parser(const char *admindir)
+{
+ enum trigdef_update_flags tduf;
+ enum trigdef_update_status tdus;
+
+ dpkg_db_set_dir(admindir);
+
+ trigdef_set_methods(&test_tdm);
+
+ tduf = TDUF_NO_LOCK | TDUF_WRITE_IF_EMPTY | TDUF_WRITE_IF_ENOENT;
+ tdus = trigdef_update_start(tduf);
+ if (tdus < TDUS_OK)
+ return -tdus + TDUS_OK;
+
+ trigdef_parse();
+
+ trigdef_process_done();
+
+ return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+ const char *admindir = argv[1];
+ int ret;
+
+ setvbuf(stdout, NULL, _IOLBF, 0);
+
+ push_error_context();
+
+ if (admindir == NULL)
+ ohshit("missing triggers deferred admindir");
+
+ ret = test_trigdeferred_parser(admindir);
+
+ pop_error_context(ehflag_normaltidy);
+
+ return ret;
+}
diff --git a/lib/dpkg/t/data/command/path-a/cmd b/lib/dpkg/t/data/command/path-a/cmd
new file mode 100755
index 0000000..1a24852
--- /dev/null
+++ b/lib/dpkg/t/data/command/path-a/cmd
@@ -0,0 +1 @@
+#!/bin/sh
diff --git a/lib/dpkg/t/data/command/path-a/cmd-a b/lib/dpkg/t/data/command/path-a/cmd-a
new file mode 100755
index 0000000..1a24852
--- /dev/null
+++ b/lib/dpkg/t/data/command/path-a/cmd-a
@@ -0,0 +1 @@
+#!/bin/sh
diff --git a/lib/dpkg/t/data/command/path-b/cmd b/lib/dpkg/t/data/command/path-b/cmd
new file mode 100755
index 0000000..1a24852
--- /dev/null
+++ b/lib/dpkg/t/data/command/path-b/cmd
@@ -0,0 +1 @@
+#!/bin/sh
diff --git a/lib/dpkg/t/data/command/path-b/cmd-b b/lib/dpkg/t/data/command/path-b/cmd-b
new file mode 100755
index 0000000..1a24852
--- /dev/null
+++ b/lib/dpkg/t/data/command/path-b/cmd-b
@@ -0,0 +1 @@
+#!/bin/sh
diff --git a/lib/dpkg/t/data/command/path-noexec/cmd b/lib/dpkg/t/data/command/path-noexec/cmd
new file mode 100644
index 0000000..1a24852
--- /dev/null
+++ b/lib/dpkg/t/data/command/path-noexec/cmd
@@ -0,0 +1 @@
+#!/bin/sh
diff --git a/lib/dpkg/t/data/command/path-noexec/cmd-noexec b/lib/dpkg/t/data/command/path-noexec/cmd-noexec
new file mode 100644
index 0000000..1a24852
--- /dev/null
+++ b/lib/dpkg/t/data/command/path-noexec/cmd-noexec
@@ -0,0 +1 @@
+#!/bin/sh
diff --git a/lib/dpkg/t/data/meminfo-no-data b/lib/dpkg/t/data/meminfo-no-data
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/dpkg/t/data/meminfo-no-data
diff --git a/lib/dpkg/t/data/meminfo-no-info b/lib/dpkg/t/data/meminfo-no-info
new file mode 100644
index 0000000..e54196b
--- /dev/null
+++ b/lib/dpkg/t/data/meminfo-no-info
@@ -0,0 +1,52 @@
+MemTotal: 3951508 kB
+MemFree: 130976 kB
+MemAvailable: 10584 kB
+Buffers: 2448 kB
+SwapCached: 12936 kB
+Active: 3111920 kB
+Inactive: 610376 kB
+Active(anon): 3102668 kB
+Inactive(anon): 606952 kB
+Active(file): 9252 kB
+Inactive(file): 3424 kB
+Unevictable: 0 kB
+Mlocked: 0 kB
+SwapTotal: 4194300 kB
+SwapFree: 3777400 kB
+Zswap: 0 kB
+Zswapped: 0 kB
+Dirty: 0 kB
+Writeback: 0 kB
+AnonPages: 12960 kB
+Mapped: 6700 kB
+Shmem: 3684416 kB
+KReclaimable: 27616 kB
+Slab: 54652 kB
+SReclaimable: 27616 kB
+SUnreclaim: 27036 kB
+KernelStack: 2496 kB
+PageTables: 1516 kB
+NFS_Unstable: 0 kB
+Bounce: 0 kB
+WritebackTmp: 0 kB
+CommitLimit: 6170052 kB
+Committed_AS: 4212940 kB
+VmallocTotal: 34359738367 kB
+VmallocUsed: 16116 kB
+VmallocChunk: 0 kB
+Percpu: 2288 kB
+HardwareCorrupted: 0 kB
+AnonHugePages: 0 kB
+ShmemHugePages: 0 kB
+ShmemPmdMapped: 0 kB
+FileHugePages: 0 kB
+FilePmdMapped: 0 kB
+HugePages_Total: 0
+HugePages_Free: 0
+HugePages_Rsvd: 0
+HugePages_Surp: 0
+Hugepagesize: 2048 kB
+Hugetlb: 0 kB
+DirectMap4k: 110452 kB
+DirectMap2M: 5132288 kB
+DirectMap1G: 5242880 kB
diff --git a/lib/dpkg/t/data/meminfo-no-unit b/lib/dpkg/t/data/meminfo-no-unit
new file mode 100644
index 0000000..4db2d9c
--- /dev/null
+++ b/lib/dpkg/t/data/meminfo-no-unit
@@ -0,0 +1,53 @@
+MemTotal: 3951508
+MemFree: 130976
+MemAvailable: 10584
+Buffers: 2448
+Cached: 3694676
+SwapCached: 12936
+Active: 3111920
+Inactive: 610376
+Active(anon): 3102668
+Inactive(anon): 606952
+Active(file): 9252
+Inactive(file): 3424
+Unevictable: 0
+Mlocked: 0
+SwapTotal: 4194300
+SwapFree: 3777400
+Zswap: 0
+Zswapped: 0
+Dirty: 0
+Writeback: 0
+AnonPages: 12960
+Mapped: 6700
+Shmem: 3684416
+KReclaimable: 27616
+Slab: 54652
+SReclaimable: 27616
+SUnreclaim: 27036
+KernelStack: 2496
+PageTables: 1516
+NFS_Unstable: 0
+Bounce: 0
+WritebackTmp: 0
+CommitLimit: 6170052
+Committed_AS: 4212940
+VmallocTotal: 34359738367
+VmallocUsed: 16116
+VmallocChunk: 0
+Percpu: 2288
+HardwareCorrupted: 0
+AnonHugePages: 0
+ShmemHugePages: 0
+ShmemPmdMapped: 0
+FileHugePages: 0
+FilePmdMapped: 0
+HugePages_Total: 0
+HugePages_Free: 0
+HugePages_Rsvd: 0
+HugePages_Surp: 0
+Hugepagesize: 2048
+Hugetlb: 0
+DirectMap4k: 110452
+DirectMap2M: 5132288
+DirectMap1G: 5242880
diff --git a/lib/dpkg/t/data/meminfo-ok b/lib/dpkg/t/data/meminfo-ok
new file mode 100644
index 0000000..d4fdf0b
--- /dev/null
+++ b/lib/dpkg/t/data/meminfo-ok
@@ -0,0 +1,53 @@
+MemTotal: 3951508 kB
+MemFree: 130976 kB
+MemAvailable: 10584 kB
+Buffers: 2448 kB
+Cached: 3694676 kB
+SwapCached: 12936 kB
+Active: 3111920 kB
+Inactive: 610376 kB
+Active(anon): 3102668 kB
+Inactive(anon): 606952 kB
+Active(file): 9252 kB
+Inactive(file): 3424 kB
+Unevictable: 0 kB
+Mlocked: 0 kB
+SwapTotal: 4194300 kB
+SwapFree: 3777400 kB
+Zswap: 0 kB
+Zswapped: 0 kB
+Dirty: 0 kB
+Writeback: 0 kB
+AnonPages: 12960 kB
+Mapped: 6700 kB
+Shmem: 3684416 kB
+KReclaimable: 27616 kB
+Slab: 54652 kB
+SReclaimable: 27616 kB
+SUnreclaim: 27036 kB
+KernelStack: 2496 kB
+PageTables: 1516 kB
+NFS_Unstable: 0 kB
+Bounce: 0 kB
+WritebackTmp: 0 kB
+CommitLimit: 6170052 kB
+Committed_AS: 4212940 kB
+VmallocTotal: 34359738367 kB
+VmallocUsed: 16116 kB
+VmallocChunk: 0 kB
+Percpu: 2288 kB
+HardwareCorrupted: 0 kB
+AnonHugePages: 0 kB
+ShmemHugePages: 0 kB
+ShmemPmdMapped: 0 kB
+FileHugePages: 0 kB
+FilePmdMapped: 0 kB
+HugePages_Total: 0
+HugePages_Free: 0
+HugePages_Rsvd: 0
+HugePages_Surp: 0
+Hugepagesize: 2048 kB
+Hugetlb: 0 kB
+DirectMap4k: 110452 kB
+DirectMap2M: 5132288 kB
+DirectMap1G: 5242880 kB
diff --git a/lib/dpkg/t/t-ar.c b/lib/dpkg/t/t-ar.c
new file mode 100644
index 0000000..88e9387
--- /dev/null
+++ b/lib/dpkg/t/t-ar.c
@@ -0,0 +1,61 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-ar.c - test ar implementation
+ *
+ * Copyright © 2010 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <string.h>
+
+#include <dpkg/test.h>
+#include <dpkg/ar.h>
+
+static void
+test_ar_normalize_name(void)
+{
+ struct dpkg_ar_hdr arh;
+
+ memccpy(arh.ar_name, "member-name/ ", '\0', sizeof(arh.ar_name));
+ dpkg_ar_normalize_name(&arh);
+ test_str(arh.ar_name, ==, "member-name");
+
+ memccpy(arh.ar_name, "member-name ", '\0', sizeof(arh.ar_name));
+ dpkg_ar_normalize_name(&arh);
+ test_str(arh.ar_name, ==, "member-name");
+}
+
+static void
+test_ar_member_is_illegal(void)
+{
+ struct dpkg_ar_hdr arh;
+
+ memset(&arh, ' ', sizeof(arh));
+ test_pass(dpkg_ar_member_is_illegal(&arh));
+
+ memcpy(arh.ar_fmag, DPKG_AR_FMAG, sizeof(arh.ar_fmag));
+ test_fail(dpkg_ar_member_is_illegal(&arh));
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(4);
+
+ test_ar_normalize_name();
+ test_ar_member_is_illegal();
+}
diff --git a/lib/dpkg/t/t-arch.c b/lib/dpkg/t/t-arch.c
new file mode 100644
index 0000000..d0b5735
--- /dev/null
+++ b/lib/dpkg/t/t-arch.c
@@ -0,0 +1,227 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-arch.c - test dpkg_arch implementation
+ *
+ * Copyright © 2011 Linaro Limited
+ * Copyright © 2011 Raphaël Hertzog <hertzog@debian.org>
+ * Copyright © 2011-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/test.h>
+#include <dpkg/varbuf.h>
+#include <dpkg/arch.h>
+
+static void
+test_dpkg_arch_name_is_illegal(void)
+{
+ /* Test invalid architecture names. */
+ test_fail(dpkg_arch_name_is_illegal("") == NULL);
+ test_fail(dpkg_arch_name_is_illegal("-i386") == NULL);
+ test_fail(dpkg_arch_name_is_illegal(" i386") == NULL);
+ test_fail(dpkg_arch_name_is_illegal(":any") == NULL);
+ test_fail(dpkg_arch_name_is_illegal("amd64_test") == NULL);
+ test_fail(dpkg_arch_name_is_illegal("i386:test") == NULL);
+ test_fail(dpkg_arch_name_is_illegal("i386 amd64") == NULL);
+ test_fail(dpkg_arch_name_is_illegal("i386,amd64") == NULL);
+ test_fail(dpkg_arch_name_is_illegal("i386|amd64") == NULL);
+
+ /* Test valid architecture names. */
+ test_pass(dpkg_arch_name_is_illegal("i386") == NULL);
+ test_pass(dpkg_arch_name_is_illegal("amd64") == NULL);
+ test_pass(dpkg_arch_name_is_illegal("hurd-i386") == NULL);
+ test_pass(dpkg_arch_name_is_illegal("kfreebsd-i386") == NULL);
+ test_pass(dpkg_arch_name_is_illegal("kfreebsd-amd64") == NULL);
+ test_pass(dpkg_arch_name_is_illegal("ia64") == NULL);
+ test_pass(dpkg_arch_name_is_illegal("alpha") == NULL);
+ test_pass(dpkg_arch_name_is_illegal("armel") == NULL);
+ test_pass(dpkg_arch_name_is_illegal("hppa") == NULL);
+ test_pass(dpkg_arch_name_is_illegal("mips") == NULL);
+ test_pass(dpkg_arch_name_is_illegal("mipsel") == NULL);
+ test_pass(dpkg_arch_name_is_illegal("powerpc") == NULL);
+ test_pass(dpkg_arch_name_is_illegal("s390") == NULL);
+ test_pass(dpkg_arch_name_is_illegal("sparc") == NULL);
+}
+
+static void
+test_dpkg_arch_get_list(void)
+{
+ struct dpkg_arch *arch;
+ int count = 1;
+
+ /* Must never return NULL. */
+ arch = dpkg_arch_get_list();
+ test_alloc(arch);
+
+ while ((arch = arch->next))
+ count++;
+
+ /* The default list should contain 3 architectures. */
+ test_pass(count == 3);
+}
+
+static void
+test_dpkg_arch_find(void)
+{
+ struct dpkg_arch *arch;
+
+ /* Test existence and initial values of default architectures. */
+ arch = dpkg_arch_find("all");
+ test_pass(arch->type == DPKG_ARCH_ALL);
+ test_pass(dpkg_arch_get(DPKG_ARCH_ALL) == arch);
+ arch = dpkg_arch_find(ARCHITECTURE);
+ test_pass(arch->type == DPKG_ARCH_NATIVE);
+ test_pass(dpkg_arch_get(DPKG_ARCH_NATIVE) == arch);
+ arch = dpkg_arch_find("any");
+ test_pass(arch->type == DPKG_ARCH_WILDCARD);
+ test_pass(dpkg_arch_get(DPKG_ARCH_WILDCARD) == arch);
+
+ /* Test missing architecture. */
+ arch = dpkg_arch_find(NULL);
+ test_pass(arch->type == DPKG_ARCH_NONE);
+ test_pass(dpkg_arch_get(DPKG_ARCH_NONE) == arch);
+ test_str(arch->name, ==, "");
+
+ /* Test empty architectures. */
+ arch = dpkg_arch_find("");
+ test_pass(arch->type == DPKG_ARCH_EMPTY);
+ test_pass(dpkg_arch_get(DPKG_ARCH_EMPTY) == arch);
+ test_str(arch->name, ==, "");
+
+ /* Test for an unknown type. */
+ DPKG_IGNORE_WARNING_ASSIGN_ENUM();
+ test_pass(dpkg_arch_get(1000) == NULL);
+ DPKG_ACCEPT_WARNING_ASSIGN_ENUM();
+
+ /* New valid architectures are marked unknown. */
+ arch = dpkg_arch_find("foobar");
+ test_pass(arch->type == DPKG_ARCH_UNKNOWN);
+ test_str(arch->name, ==, "foobar");
+
+ /* New illegal architectures are marked illegal. */
+ arch = dpkg_arch_find("a:b");
+ test_pass(arch->type == DPKG_ARCH_ILLEGAL);
+ test_str(arch->name, ==, "a:b");
+}
+
+static void
+test_dpkg_arch_reset_list(void)
+{
+ dpkg_arch_reset_list();
+
+ test_dpkg_arch_get_list();
+}
+
+static void
+test_dpkg_arch_modify(void)
+{
+ struct dpkg_arch *arch;
+
+ dpkg_arch_reset_list();
+
+ /* Insert a new unknown arch. */
+ arch = dpkg_arch_find("foo");
+ test_pass(arch->type == DPKG_ARCH_UNKNOWN);
+ test_str(arch->name, ==, "foo");
+
+ /* Check that existing unknown arch gets tagged. */
+ arch = dpkg_arch_add("foo");
+ test_pass(arch->type == DPKG_ARCH_FOREIGN);
+ test_str(arch->name, ==, "foo");
+
+ /* Check that new unknown arch gets tagged. */
+ arch = dpkg_arch_add("quux");
+ test_pass(arch->type == DPKG_ARCH_FOREIGN);
+ test_str(arch->name, ==, "quux");
+
+ /* Unmark foreign architectures. */
+
+ arch = dpkg_arch_find("foo");
+ dpkg_arch_unmark(arch);
+ test_pass(arch->type == DPKG_ARCH_UNKNOWN);
+
+ arch = dpkg_arch_find("bar");
+ dpkg_arch_unmark(arch);
+ test_pass(arch->type == DPKG_ARCH_UNKNOWN);
+
+ arch = dpkg_arch_find("quux");
+ dpkg_arch_unmark(arch);
+ test_pass(arch->type == DPKG_ARCH_UNKNOWN);
+}
+
+static void
+test_dpkg_arch_varbuf_archqual(void)
+{
+ struct varbuf vb = VARBUF_INIT;
+
+ varbuf_add_archqual(&vb, dpkg_arch_get(DPKG_ARCH_NONE));
+ varbuf_end_str(&vb);
+ test_str(vb.buf, ==, "");
+ varbuf_reset(&vb);
+
+ varbuf_add_archqual(&vb, dpkg_arch_get(DPKG_ARCH_EMPTY));
+ varbuf_end_str(&vb);
+ test_str(vb.buf, ==, "");
+ varbuf_reset(&vb);
+
+ varbuf_add_archqual(&vb, dpkg_arch_get(DPKG_ARCH_ALL));
+ varbuf_end_str(&vb);
+ test_str(vb.buf, ==, ":all");
+ varbuf_reset(&vb);
+
+ varbuf_add_archqual(&vb, dpkg_arch_get(DPKG_ARCH_WILDCARD));
+ varbuf_end_str(&vb);
+ test_str(vb.buf, ==, ":any");
+ varbuf_reset(&vb);
+
+ varbuf_destroy(&vb);
+}
+
+static void
+test_dpkg_arch_describe(void)
+{
+ struct dpkg_arch *arch;
+
+ arch = dpkg_arch_get(DPKG_ARCH_NONE);
+ test_str(dpkg_arch_describe(arch), ==, "<none>");
+
+ arch = dpkg_arch_get(DPKG_ARCH_EMPTY);
+ test_str(dpkg_arch_describe(arch), ==, "<empty>");
+
+ arch = dpkg_arch_get(DPKG_ARCH_ALL);
+ test_str(dpkg_arch_describe(arch), ==, "all");
+
+ arch = dpkg_arch_get(DPKG_ARCH_WILDCARD);
+ test_str(dpkg_arch_describe(arch), ==, "any");
+
+ arch = dpkg_arch_get(DPKG_ARCH_NATIVE);
+ test_str(dpkg_arch_describe(arch), ==, ARCHITECTURE);
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(60);
+
+ test_dpkg_arch_name_is_illegal();
+ test_dpkg_arch_get_list();
+ test_dpkg_arch_find();
+ test_dpkg_arch_reset_list();
+ test_dpkg_arch_modify();
+ test_dpkg_arch_varbuf_archqual();
+ test_dpkg_arch_describe();
+}
diff --git a/lib/dpkg/t/t-buffer.c b/lib/dpkg/t/t-buffer.c
new file mode 100644
index 0000000..fa939e5
--- /dev/null
+++ b/lib/dpkg/t/t-buffer.c
@@ -0,0 +1,82 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-buffer.c - test buffer handling
+ *
+ * Copyright © 2009-2011 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/test.h>
+#include <dpkg/buffer.h>
+#include <dpkg/dpkg.h>
+
+#include <sys/types.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+static const char str_empty[] = "";
+static const char ref_hash_empty[] = "d41d8cd98f00b204e9800998ecf8427e";
+static const char str_test[] = "this is a test string\n";
+static const char ref_hash_test[] = "475aae3b885d70a9130eec23ab33f2b9";
+
+static void
+test_buffer_hash(void)
+{
+ char hash[MD5HASHLEN + 1];
+
+ buffer_md5(str_empty, hash, strlen(str_empty));
+ test_str(hash, ==, ref_hash_empty);
+
+ buffer_md5(str_test, hash, strlen(str_test));
+ test_str(hash, ==, ref_hash_test);
+}
+
+static void
+test_fdio_hash(void)
+{
+ char hash[MD5HASHLEN + 1];
+ char *test_file;
+ int fd;
+
+ test_file = test_alloc(strdup("test.XXXXXX"));
+ fd = mkstemp(test_file);
+ test_pass(fd >= 0);
+
+ test_pass(fd_md5(fd, hash, -1, NULL) >= 0);
+ test_str(hash, ==, ref_hash_empty);
+
+ test_pass(write(fd, str_test, strlen(str_test)) == (ssize_t)strlen(str_test));
+ test_pass(lseek(fd, 0, SEEK_SET) == 0);
+
+ test_pass(fd_md5(fd, hash, -1, NULL) >= 0);
+ test_str(hash, ==, ref_hash_test);
+
+ test_pass(unlink(test_file) == 0);
+
+ free(test_file);
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(10);
+
+ test_buffer_hash();
+ test_fdio_hash();
+}
diff --git a/lib/dpkg/t/t-c-ctype.c b/lib/dpkg/t/t-c-ctype.c
new file mode 100644
index 0000000..2da0553
--- /dev/null
+++ b/lib/dpkg/t/t-c-ctype.c
@@ -0,0 +1,106 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-c-ctype.c - test C locale ctype functions
+ *
+ * Copyright © 2009-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/test.h>
+#include <dpkg/c-ctype.h>
+
+static void
+test_ctype(void)
+{
+ int c;
+
+ for (c = -1; c < 256; c++) {
+ /* Test blank. */
+ if (c == '\t' || c == ' ')
+ test_pass(c_isblank(c));
+ else
+ test_fail(c_isblank(c));
+
+ /* Test white. */
+ if (c == '\t' || c == ' ' || c == '\n')
+ test_pass(c_iswhite(c));
+ else
+ test_fail(c_iswhite(c));
+
+ /* Test space. */
+ if (c == '\t' || c == '\v' || c == '\f' ||
+ c == '\r' || c == '\n' || c == ' ')
+ test_pass(c_isspace(c));
+ else
+ test_fail(c_isspace(c));
+
+ /* Test digit. */
+ if (c >= '0' && c <= '9')
+ test_pass(c_isdigit(c));
+ else
+ test_fail(c_isdigit(c));
+
+ /* Test lower case. */
+ if (c >= 'a' && c <= 'z')
+ test_pass(c_islower(c));
+ else
+ test_fail(c_islower(c));
+
+ /* Test upper case. */
+ if (c >= 'A' && c <= 'Z')
+ test_pass(c_isupper(c));
+ else
+ test_fail(c_isupper(c));
+
+ /* Test alpha. */
+ if (c_islower(c) || c_isupper(c))
+ test_pass(c_isalpha(c));
+ else
+ test_fail(c_isalpha(c));
+
+ /* Test alphanumeric. */
+ if (c_isdigit(c) || c_isalpha(c))
+ test_pass(c_isalnum(c));
+ else
+ test_fail(c_isalnum(c));
+ }
+}
+
+static void
+test_casing(void)
+{
+ test_pass(c_tolower('A') == 'a');
+ test_pass(c_tolower('Z') == 'z');
+
+ test_pass(c_tolower('a') == 'a');
+ test_pass(c_tolower('z') == 'z');
+
+ test_pass(c_tolower('0') == '0');
+ test_pass(c_tolower('9') == '9');
+
+ /* Test if we can handle the value for EOF. */
+ test_pass(c_tolower(-1) == -1);
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(2063);
+
+ test_ctype();
+ test_casing();
+}
diff --git a/lib/dpkg/t/t-command.c b/lib/dpkg/t/t-command.c
new file mode 100644
index 0000000..3a2e64b
--- /dev/null
+++ b/lib/dpkg/t/t-command.c
@@ -0,0 +1,311 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-command.c - test command implementation
+ *
+ * Copyright © 2010-2012 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <dpkg/test.h>
+#include <dpkg/subproc.h>
+#include <dpkg/command.h>
+#include <dpkg/dpkg.h>
+
+static void
+test_command_init(void)
+{
+ struct command cmd;
+
+ command_init(&cmd, "/absolute/path/to/progname", NULL);
+ test_str(cmd.filename, ==, "/absolute/path/to/progname");
+ test_str(cmd.name, ==, "progname");
+ test_pass(cmd.argc == 0);
+ test_pass(cmd.argv[0] == NULL);
+
+ command_destroy(&cmd);
+ test_pass(cmd.filename == NULL);
+ test_pass(cmd.name == NULL);
+ test_pass(cmd.argc == 0);
+ test_pass(cmd.argv == NULL);
+
+ command_init(&cmd, "progname", NULL);
+ test_str(cmd.filename, ==, "progname");
+ test_str(cmd.name, ==, "progname");
+ test_pass(cmd.argc == 0);
+ test_pass(cmd.argv[0] == NULL);
+
+ command_destroy(&cmd);
+
+ command_init(&cmd, "progname", "description");
+ test_str(cmd.filename, ==, "progname");
+ test_str(cmd.name, ==, "description");
+ test_pass(cmd.argc == 0);
+ test_pass(cmd.argv[0] == NULL);
+
+ command_destroy(&cmd);
+}
+
+static void
+test_command_grow_argv(void)
+{
+ struct command cmd;
+ int argv_size, i;
+
+ command_init(&cmd, "test", NULL);
+
+ argv_size = cmd.argv_size + 4;
+ for (i = 0; i < argv_size; i++)
+ command_add_arg(&cmd, "arg");
+
+ test_pass(cmd.argc == argv_size);
+ test_pass(cmd.argv_size >= argv_size);
+ test_str(cmd.argv[0], ==, "arg");
+ test_str(cmd.argv[argv_size - 1], ==, "arg");
+ test_pass(cmd.argv[argv_size] == NULL);
+
+ command_destroy(&cmd);
+}
+
+static void
+test_command_add_arg(void)
+{
+ struct command cmd;
+
+ command_init(&cmd, "test", NULL);
+
+ command_add_arg(&cmd, "arg 0");
+ test_pass(cmd.argc == 1);
+ test_str(cmd.argv[0], ==, "arg 0");
+ test_pass(cmd.argv[1] == NULL);
+
+ command_add_arg(&cmd, "arg 1");
+ test_pass(cmd.argc == 2);
+ test_str(cmd.argv[0], ==, "arg 0");
+ test_str(cmd.argv[1], ==, "arg 1");
+ test_pass(cmd.argv[2] == NULL);
+
+ command_add_arg(&cmd, "arg 2");
+ test_pass(cmd.argc == 3);
+ test_str(cmd.argv[0], ==, "arg 0");
+ test_str(cmd.argv[1], ==, "arg 1");
+ test_str(cmd.argv[2], ==, "arg 2");
+ test_pass(cmd.argv[3] == NULL);
+
+ command_destroy(&cmd);
+}
+
+static void
+test_command_add_argl(void)
+{
+ struct command cmd;
+ const char *args[] = {
+ "arg 1",
+ "arg 2",
+ "arg 3",
+ NULL,
+ };
+
+ command_init(&cmd, "test", NULL);
+
+ command_add_arg(&cmd, "arg 0");
+
+ command_add_argl(&cmd, args);
+ test_pass(cmd.argc == 4);
+ test_str(cmd.argv[0], ==, "arg 0");
+ test_str(cmd.argv[1], ==, "arg 1");
+ test_str(cmd.argv[2], ==, "arg 2");
+ test_str(cmd.argv[3], ==, "arg 3");
+ test_pass(cmd.argv[4] == NULL);
+
+ command_destroy(&cmd);
+}
+
+static void
+test_command_add_args(void)
+{
+ struct command cmd;
+
+ command_init(&cmd, "test", NULL);
+
+ command_add_arg(&cmd, "arg 0");
+
+ command_add_args(&cmd, "arg 1", "arg 2", "arg 3", NULL);
+ test_pass(cmd.argc == 4);
+ test_str(cmd.argv[0], ==, "arg 0");
+ test_str(cmd.argv[1], ==, "arg 1");
+ test_str(cmd.argv[2], ==, "arg 2");
+ test_str(cmd.argv[3], ==, "arg 3");
+ test_pass(cmd.argv[4] == NULL);
+
+ command_destroy(&cmd);
+}
+
+static void
+test_command_exec(void)
+{
+ struct command cmd;
+ pid_t pid;
+ int ret;
+
+ command_init(&cmd, "true", "exec test");
+
+ command_add_arg(&cmd, "true");
+ command_add_arg(&cmd, "arg 0");
+ command_add_arg(&cmd, "arg 1");
+
+ pid = subproc_fork();
+
+ if (pid == 0)
+ command_exec(&cmd);
+
+ ret = subproc_reap(pid, "command exec test", 0);
+ test_pass(ret == 0);
+
+ command_destroy(&cmd);
+}
+
+static void
+test_command_in_path(void)
+{
+ char *path_a, *path_b, *path_noexec;
+ char *oldpath, *newpath = NULL;
+ bool ret;
+ int rc;
+
+ path_a = test_data_file("command/path-a");
+ path_b = test_data_file("command/path-b");
+ path_noexec = test_data_file("command/path-noexec");
+
+ oldpath = getenv("PATH");
+
+ setenv("PATH", "", 1);
+ ret = command_in_path("cmd");
+ test_pass(ret == false);
+ ret = command_in_path("cmd-a");
+ test_pass(ret == false);
+ ret = command_in_path("cmd-b");
+ test_pass(ret == false);
+ ret = command_in_path("cmd-noexec");
+ test_pass(ret == false);
+
+ setenv("PATH", "/nonexistent", 1);
+ ret = command_in_path("cmd");
+ test_pass(ret == false);
+ ret = command_in_path("cmd-a");
+ test_pass(ret == false);
+ ret = command_in_path("cmd-b");
+ test_pass(ret == false);
+ ret = command_in_path("cmd-noexec");
+ test_pass(ret == false);
+
+ setenv("PATH", path_a, 1);
+ ret = command_in_path("cmd");
+ test_pass(ret == true);
+ ret = command_in_path("cmd-a");
+ test_pass(ret == true);
+ ret = command_in_path("cmd-b");
+ test_pass(ret == false);
+ ret = command_in_path("cmd-noexec");
+ test_pass(ret == false);
+
+ setenv("PATH", path_b, 1);
+ ret = command_in_path("cmd");
+ test_pass(ret == true);
+ ret = command_in_path("cmd-a");
+ test_pass(ret == false);
+ ret = command_in_path("cmd-b");
+ test_pass(ret == true);
+ ret = command_in_path("cmd-noexec");
+ test_pass(ret == false);
+
+ setenv("PATH", path_noexec, 1);
+ ret = command_in_path("cmd");
+ test_pass(ret == false);
+ ret = command_in_path("cmd-a");
+ test_pass(ret == false);
+ ret = command_in_path("cmd-b");
+ test_pass(ret == false);
+ ret = command_in_path("cmd-noexec");
+ test_pass(ret == false);
+
+ rc = asprintf(&newpath, "/nonexistent:%s:%s:%s", path_a, path_b, path_noexec);
+ if (rc < 0)
+ test_bail("cannot allocate new PATH variable");
+ setenv("PATH", newpath, 1);
+ ret = command_in_path("cmd");
+ test_pass(ret == true);
+ ret = command_in_path("cmd-a");
+ test_pass(ret == true);
+ ret = command_in_path("cmd-b");
+ test_pass(ret == true);
+ ret = command_in_path("cmd-noexec");
+ test_pass(ret == false);
+
+ setenv("PATH", oldpath, 1);
+
+ free(path_a);
+ free(path_b);
+ free(path_noexec);
+ free(newpath);
+}
+
+static void
+test_command_shell(void)
+{
+ pid_t pid;
+ int ret;
+
+ pid = subproc_fork();
+ if (pid == 0)
+ command_shell("true", "command shell pass test");
+ ret = subproc_reap(pid, "command shell pass test", 0);
+ test_pass(ret == 0);
+
+ pid = subproc_fork();
+ if (pid == 0)
+ command_shell("false", "command shell fail test");
+ ret = subproc_reap(pid, "command shell fail test", SUBPROC_RETERROR);
+ test_fail(ret == 0);
+
+ unsetenv("SHELL");
+ pid = subproc_fork();
+ if (pid == 0)
+ command_shell("true", "command default shell test");
+ ret = subproc_reap(pid, "command default shell test", 0);
+ test_pass(ret == 0);
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(73);
+
+ test_command_init();
+ test_command_grow_argv();
+ test_command_add_arg();
+ test_command_add_argl();
+ test_command_add_args();
+ test_command_exec();
+ test_command_in_path();
+ test_command_shell();
+}
diff --git a/lib/dpkg/t/t-deb-version.c b/lib/dpkg/t/t-deb-version.c
new file mode 100644
index 0000000..38b4608
--- /dev/null
+++ b/lib/dpkg/t/t-deb-version.c
@@ -0,0 +1,90 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-deb-version.c - test deb version handling
+ *
+ * Copyright © 2012 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <limits.h>
+#include <stdio.h>
+
+#include <dpkg/test.h>
+#include <dpkg/deb-version.h>
+
+static void
+test_deb_version_parse(void)
+{
+ struct deb_version v;
+ char *vs;
+
+ /* Test valid versions. */
+ test_pass(deb_version_parse(&v, "0.0") == NULL);
+ test_pass(v.major == 0 && v.minor == 0);
+
+ test_pass(deb_version_parse(&v, "1.1") == NULL);
+ test_pass(v.major == 1 && v.minor == 1);
+
+ test_pass(deb_version_parse(&v, "1.001") == NULL);
+ test_pass(v.major == 1 && v.minor == 1);
+
+ test_pass(deb_version_parse(&v, "1.0010") == NULL);
+ test_pass(v.major == 1 && v.minor == 10);
+
+ test_pass(deb_version_parse(&v, "0.939000") == NULL);
+ test_pass(v.major == 0 && v.minor == 939000);
+
+ test_pass(deb_version_parse(&v, "1.1\n") == NULL);
+ test_pass(v.major == 1 && v.minor == 1);
+
+ /* Test invalid versions. */
+ test_fail(deb_version_parse(&v, "0") == NULL);
+ test_fail(deb_version_parse(&v, "a") == NULL);
+ test_fail(deb_version_parse(&v, "a.b") == NULL);
+ test_fail(deb_version_parse(&v, "a~b") == NULL);
+ test_fail(deb_version_parse(&v, " 1.1") == NULL);
+ test_fail(deb_version_parse(&v, "2 .2") == NULL);
+ test_fail(deb_version_parse(&v, "3. 3") == NULL);
+ test_fail(deb_version_parse(&v, "4.4 ") == NULL);
+ test_fail(deb_version_parse(&v, " 5.5 ") == NULL);
+
+ /* Test integer limits. */
+ if (asprintf(&vs, "%d.0", INT_MAX) < 0)
+ test_bail("cannot allocate memory for asprintf()");
+ test_pass(deb_version_parse(&v, vs) == NULL);
+ free(vs);
+
+ if (asprintf(&vs, "%d.0", INT_MAX - 1) < 0)
+ test_bail("cannot allocate memory for asprintf()");
+ test_pass(deb_version_parse(&v, vs) == NULL);
+ free(vs);
+
+ if (asprintf(&vs, "%u.0", 1U + (unsigned int)INT_MAX) < 0)
+ test_bail("cannot allocate memory for asprintf()");
+ test_fail(deb_version_parse(&v, vs) == NULL);
+ free(vs);
+
+ /* TODO: Complete. */
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(24);
+
+ test_deb_version_parse();
+}
diff --git a/lib/dpkg/t/t-ehandle.c b/lib/dpkg/t/t-ehandle.c
new file mode 100644
index 0000000..47ba58e
--- /dev/null
+++ b/lib/dpkg/t/t-ehandle.c
@@ -0,0 +1,128 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-ehandle.c - test error handling implementation
+ *
+ * Copyright © 2016 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <stdbool.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <dpkg/test.h>
+#include <dpkg/ehandle.h>
+
+static jmp_buf global_handler_jump;
+
+static void
+printer_empty(const char *msg, const void *data)
+{
+}
+
+static void
+handler_func(void)
+{
+ longjmp(global_handler_jump, 1);
+}
+
+static void
+test_error_handler_func(void)
+{
+ bool pass;
+
+ if (setjmp(global_handler_jump)) {
+ pass = true;
+ pop_error_context(ehflag_normaltidy);
+ } else {
+ pass = false;
+ push_error_context_func(handler_func, printer_empty, "test func");
+ ohshit("test func error");
+ test_bail("ohshit() is not supposed to return");
+ }
+ test_pass(pass);
+}
+
+static void
+test_error_handler_jump(void)
+{
+ jmp_buf handler_jump;
+ bool pass;
+
+ if (setjmp(handler_jump)) {
+ pass = true;
+ pop_error_context(ehflag_normaltidy);
+ } else {
+ pass = false;
+ push_error_context_jump(&handler_jump, printer_empty, "test jump");
+ ohshit("test jump error");
+ test_bail("ohshit() is not supposed to return");
+ }
+ test_pass(pass);
+}
+
+static void
+cleanup_error(int argc, void **argv)
+{
+ ohshit("cleanup error");
+}
+
+static void
+test_cleanup_error(void)
+{
+ jmp_buf handler_jump;
+ bool pass;
+
+ if (setjmp(handler_jump)) {
+ /* The ohshit() is not supposed to get us here, as it should
+ * be caught by the internal recursive error context. */
+ pass = false;
+
+ pop_cleanup(ehflag_normaltidy);
+ pop_error_context(ehflag_normaltidy);
+ } else {
+ push_error_context_jump(&handler_jump, printer_empty, "test cleanup");
+ push_cleanup(cleanup_error, ~ehflag_normaltidy, 0);
+ pop_error_context(ehflag_bombout);
+
+ /* We should have recovered from the cleanup handler failing,
+ * and arrived here correctly. */
+ pass = true;
+ }
+
+ test_pass(pass);
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(3);
+
+ if (!test_is_verbose()) {
+ int fd;
+
+ /* Shut up stderr, we do not want the error output. */
+ fd = open("/dev/null", O_RDWR);
+ if (fd < 0)
+ test_bail("cannot open /dev/null");
+ dup2(fd, 2);
+ }
+
+ test_error_handler_func();
+ test_error_handler_jump();
+ test_cleanup_error();
+}
diff --git a/lib/dpkg/t/t-error.c b/lib/dpkg/t/t-error.c
new file mode 100644
index 0000000..e93459e
--- /dev/null
+++ b/lib/dpkg/t/t-error.c
@@ -0,0 +1,87 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-error.c - test error message reporting
+ *
+ * Copyright © 2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <dpkg/test.h>
+#include <dpkg/error.h>
+
+static void
+test_dpkg_error_put(void)
+{
+ struct dpkg_error err = DPKG_ERROR_INIT;
+ char *errstr_ref = NULL;
+
+ test_pass(err.type == DPKG_MSG_NONE);
+ test_pass(err.str == NULL);
+
+ test_pass(dpkg_put_warn(NULL, "void error") < 0);
+ test_pass(dpkg_put_error(NULL, "void error") < 0);
+ test_pass(dpkg_put_errno(NULL, "void error") < 0);
+
+ test_pass(dpkg_put_warn(&err, "test warning %d", 10) < 0);
+ test_pass(err.syserrno == 0);
+ test_str(err.str, ==, "test warning 10");
+ test_warn(err);
+
+ test_pass(dpkg_put_error(&err, "test error %d", 20) < 0);
+ test_pass(err.syserrno == 0);
+ test_str(err.str, ==, "test error 20");
+ test_error(err);
+
+ errno = ENOENT;
+ if (asprintf(&errstr_ref, "test errno 30 (%s)", strerror(errno)) < 0)
+ test_bail("cannot allocate string");
+ test_pass(dpkg_put_errno(&err, "test errno %d", 30) < 0);
+ test_str(err.str, ==, errstr_ref);
+ test_pass(err.syserrno == ENOENT);
+ test_error(err);
+ free(errstr_ref);
+ errno = 0;
+}
+
+static void
+test_dpkg_error_destroy(void)
+{
+ struct dpkg_error err = DPKG_ERROR_INIT;
+
+ errno = ENOENT;
+ test_pass(dpkg_put_errno(&err, "test destroy") < 0);
+ test_pass(err.syserrno == ENOENT);
+ test_pass(err.type == DPKG_MSG_ERROR);
+ test_pass(err.str != NULL);
+ dpkg_error_destroy(&err);
+ test_pass(err.type == DPKG_MSG_NONE);
+ test_pass(err.syserrno == 0);
+ test_pass(err.str == NULL);
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(24);
+
+ test_dpkg_error_put();
+ test_dpkg_error_destroy();
+}
diff --git a/lib/dpkg/t/t-file.c b/lib/dpkg/t/t-file.c
new file mode 100644
index 0000000..0004df4
--- /dev/null
+++ b/lib/dpkg/t/t-file.c
@@ -0,0 +1,106 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-file.c - test file functions
+ *
+ * Copyright © 2018 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/test.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/file.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+static const char ref_data[] =
+ "this is a test string\n"
+ "within a test file\n"
+ "containing multiple lines\n"
+;
+
+static void
+test_file_slurp(void)
+{
+ struct varbuf vb = VARBUF_INIT;
+ struct dpkg_error err = DPKG_ERROR_INIT;
+ char *test_file;
+ char *test_dir;
+ int fd;
+
+ test_pass(file_slurp("/nonexistent", &vb, &err) < 0);
+ test_pass(vb.used == 0);
+ test_pass(vb.buf == NULL);
+ test_pass(err.syserrno == ENOENT);
+ test_error(err);
+ varbuf_destroy(&vb);
+
+ test_dir = test_alloc(strdup("test.XXXXXX"));
+ test_pass(mkdtemp(test_dir) != NULL);
+ test_pass(file_slurp(test_dir, &vb, &err) < 0);
+ test_pass(vb.used == 0);
+ test_pass(vb.buf == NULL);
+ test_pass(err.syserrno == 0);
+ test_error(err);
+ varbuf_destroy(&vb);
+ test_pass(rmdir(test_dir) == 0);
+
+ test_file = test_alloc(strdup("test.XXXXXX"));
+ fd = mkstemp(test_file);
+ test_pass(fd >= 0);
+
+ test_pass(file_slurp(test_file, &vb, &err) == 0);
+ test_pass(vb.used == 0);
+ test_pass(vb.buf == NULL);
+ test_pass(err.syserrno == 0);
+ test_pass(err.type == DPKG_MSG_NONE);
+ varbuf_destroy(&vb);
+
+ test_pass(write(fd, ref_data, strlen(ref_data)) == (ssize_t)strlen(ref_data));
+ test_pass(lseek(fd, 0, SEEK_SET) == 0);
+
+ test_pass(file_slurp(test_file, &vb, &err) == 0);
+ test_pass(vb.used == strlen(ref_data));
+ test_mem(vb.buf, ==, ref_data, min(vb.used, strlen(ref_data)));
+ test_pass(err.syserrno == 0);
+ test_pass(err.type == DPKG_MSG_NONE);
+ varbuf_destroy(&vb);
+
+ test_fail(file_is_exec(test_dir));
+ test_fail(file_is_exec(test_file));
+ test_pass(chmod(test_file, 0755) == 0);
+ test_pass(file_is_exec(test_file));
+ test_pass(chmod(test_file, 0750) == 0);
+ test_pass(file_is_exec(test_file));
+
+ test_pass(unlink(test_file) == 0);
+ free(test_file);
+ free(test_dir);
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(32);
+
+ test_file_slurp();
+}
diff --git a/lib/dpkg/t/t-fsys-dir.c b/lib/dpkg/t/t-fsys-dir.c
new file mode 100644
index 0000000..32f4aab
--- /dev/null
+++ b/lib/dpkg/t/t-fsys-dir.c
@@ -0,0 +1,91 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-fsys-dir.c - test filesystem handling
+ *
+ * Copyright © 2018 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <stdlib.h>
+
+#include <dpkg/test.h>
+#include <dpkg/fsys.h>
+
+static void
+test_fsys_dir(void)
+{
+ const char *newdir;
+ char *dir;
+
+ test_str(dpkg_fsys_get_dir(), ==, "");
+
+ newdir = dpkg_fsys_set_dir("/testdir//./");
+ test_str(newdir, ==, "/testdir");
+ test_str(dpkg_fsys_get_dir(), ==, "/testdir");
+
+ newdir = dpkg_fsys_set_dir("/testdir");
+ test_str(newdir, ==, "/testdir");
+ test_str(dpkg_fsys_get_dir(), ==, "/testdir");
+
+ newdir = dpkg_fsys_set_dir(newdir);
+ test_str(newdir, ==, "/testdir");
+ test_str(dpkg_fsys_get_dir(), ==, "/testdir");
+
+ dir = dpkg_fsys_get_path("testfile");
+ test_str(dir, ==, "/testdir/testfile");
+ free(dir);
+
+ dir = dpkg_fsys_get_path("/testfile");
+ test_str(dir, ==, "/testdir/testfile");
+ free(dir);
+
+ setenv("DPKG_ROOT", "/testenvdir//./", 1);
+ dpkg_fsys_set_dir(NULL);
+ test_str(dpkg_fsys_get_dir(), ==, "/testenvdir");
+
+ setenv("DPKG_ROOT", "/testenvdir", 1);
+ dpkg_fsys_set_dir(NULL);
+ test_str(dpkg_fsys_get_dir(), ==, "/testenvdir");
+
+ dir = dpkg_fsys_get_path("testfile");
+ test_str(dir, ==, "/testenvdir/testfile");
+ free(dir);
+
+ dir = dpkg_fsys_get_path("/testfile");
+ test_str(dir, ==, "/testenvdir/testfile");
+ free(dir);
+
+ unsetenv("DPKG_ROOT");
+ dpkg_fsys_set_dir(NULL);
+ test_str(dpkg_fsys_get_dir(), ==, "");
+
+ dir = dpkg_fsys_get_path("testfile");
+ test_str(dir, ==, "/testfile");
+ free(dir);
+
+ dir = dpkg_fsys_get_path("/testfile");
+ test_str(dir, ==, "/testfile");
+ free(dir);
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(16);
+
+ test_fsys_dir();
+}
diff --git a/lib/dpkg/t/t-fsys-hash.c b/lib/dpkg/t/t-fsys-hash.c
new file mode 100644
index 0000000..4318779
--- /dev/null
+++ b/lib/dpkg/t/t-fsys-hash.c
@@ -0,0 +1,108 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-fsys-hash.c - test fsys-hash implementation
+ *
+ * Copyright © 2018 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/test.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/fsys.h>
+
+static void
+test_fsys_nodes(void)
+{
+ struct fsys_namenode *fnn;
+ struct fsys_hash_iter *iter;
+ const char *name;
+
+ test_pass(fsys_hash_entries() == 0);
+
+ fsys_hash_init();
+
+ fnn = fsys_hash_find_node("/nonexistent", FHFF_NO_NEW);
+ test_pass(fnn == NULL);
+ test_pass(fsys_hash_entries() == 0);
+
+ name = "/test/path/aa";
+ fnn = fsys_hash_find_node(name, FHFF_NO_COPY);
+ test_pass(fnn != NULL);
+ test_pass(fsys_hash_entries() == 1);
+ test_pass(fnn->name == name);
+ test_str(fnn->name, ==, "/test/path/aa");
+ test_pass(fnn->flags == 0);
+ test_pass(fnn->oldhash == NULL);
+ test_pass(fnn->newhash == NULL);
+
+ fnn = fsys_hash_find_node("//./test/path/bb", FHFF_NONE);
+ test_pass(fnn != NULL);
+ test_pass(fsys_hash_entries() == 2);
+ test_str(fnn->name, ==, "/test/path/bb");
+ test_pass(fnn->flags == 0);
+ test_pass(fnn->oldhash == NULL);
+ test_pass(fnn->newhash == NULL);
+
+ fnn = fsys_hash_find_node("/test/path/cc", FHFF_NONE);
+ test_pass(fnn != NULL);
+ test_pass(fsys_hash_entries() == 3);
+ test_str(fnn->name, ==, "/test/path/cc");
+ test_pass(fnn->flags == 0);
+ test_pass(fnn->oldhash == NULL);
+ test_pass(fnn->newhash == NULL);
+
+ iter = fsys_hash_iter_new();
+ while ((fnn = fsys_hash_iter_next(iter))) {
+ if (strcmp(fnn->name, "/test/path/aa") == 0)
+ test_str(fnn->name, ==, "/test/path/aa");
+ else if (strcmp(fnn->name, "/test/path/bb") == 0)
+ test_str(fnn->name, ==, "/test/path/bb");
+ else if (strcmp(fnn->name, "/test/path/cc") == 0)
+ test_str(fnn->name, ==, "/test/path/cc");
+ else
+ test_fail("unknown fsys_namenode");
+ }
+ fsys_hash_iter_free(iter);
+
+ fsys_hash_init();
+ test_pass(fsys_hash_entries() == 3);
+ fnn = fsys_hash_find_node("/test/path/aa", FHFF_NO_NEW);
+ test_pass(fnn != NULL);
+ fnn = fsys_hash_find_node("/test/path/bb", FHFF_NO_NEW);
+ test_pass(fnn != NULL);
+ fnn = fsys_hash_find_node("/test/path/cc", FHFF_NO_NEW);
+ test_pass(fnn != NULL);
+ test_pass(fsys_hash_entries() == 3);
+
+ fsys_hash_reset();
+ test_pass(fsys_hash_entries() == 0);
+ fnn = fsys_hash_find_node("/test/path/aa", FHFF_NO_NEW);
+ test_pass(fnn == NULL);
+ fnn = fsys_hash_find_node("/test/path/bb", FHFF_NO_NEW);
+ test_pass(fnn == NULL);
+ fnn = fsys_hash_find_node("/test/path/cc", FHFF_NO_NEW);
+ test_pass(fnn == NULL);
+ test_pass(fsys_hash_entries() == 0);
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(35);
+
+ test_fsys_nodes();
+}
diff --git a/lib/dpkg/t/t-headers-cpp.cc b/lib/dpkg/t/t-headers-cpp.cc
new file mode 100644
index 0000000..b5becf9
--- /dev/null
+++ b/lib/dpkg/t/t-headers-cpp.cc
@@ -0,0 +1,82 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-headers-cpp.cc - test C++ inclusion of headers
+ *
+ * Copyright © 2018 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <cstdbool>
+
+#include <dpkg/ar.h>
+#include <dpkg/arch.h>
+#include <dpkg/atomic-file.h>
+#include <dpkg/buffer.h>
+#include <dpkg/c-ctype.h>
+#include <dpkg/color.h>
+#include <dpkg/command.h>
+#include <dpkg/compress.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+#include <dpkg/deb-version.h>
+#include <dpkg/debug.h>
+#include <dpkg/dir.h>
+#include <dpkg/dlist.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/ehandle.h>
+#include <dpkg/error.h>
+#include <dpkg/fdio.h>
+#include <dpkg/file.h>
+#include <dpkg/fsys.h>
+#include <dpkg/glob.h>
+#include <dpkg/i18n.h>
+#include <dpkg/macros.h>
+#include <dpkg/namevalue.h>
+#include <dpkg/options.h>
+#include <dpkg/pager.h>
+#include <dpkg/parsedump.h>
+#include <dpkg/path.h>
+#include <dpkg/pkg-array.h>
+#include <dpkg/pkg-files.h>
+#include <dpkg/pkg-format.h>
+#include <dpkg/pkg-list.h>
+#include <dpkg/pkg-queue.h>
+#include <dpkg/pkg-show.h>
+#include <dpkg/pkg-spec.h>
+#include <dpkg/pkg.h>
+#include <dpkg/progname.h>
+#include <dpkg/program.h>
+#include <dpkg/progress.h>
+#include <dpkg/report.h>
+#include <dpkg/string.h>
+#include <dpkg/subproc.h>
+#include <dpkg/tarfn.h>
+#include <dpkg/test.h>
+#include <dpkg/treewalk.h>
+#include <dpkg/trigdeferred.h>
+#include <dpkg/triglib.h>
+#include <dpkg/varbuf.h>
+#include <dpkg/version.h>
+
+TEST_ENTRY(test)
+{
+ test_plan(1);
+
+ test_pass(true);
+}
diff --git a/lib/dpkg/t/t-macros.c b/lib/dpkg/t/t-macros.c
new file mode 100644
index 0000000..9f4eaa1
--- /dev/null
+++ b/lib/dpkg/t/t-macros.c
@@ -0,0 +1,45 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-macros.c - test C support macros
+ *
+ * Copyright © 2009,2012 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/test.h>
+#include <dpkg/macros.h>
+
+TEST_ENTRY(test)
+{
+ test_plan(12);
+
+ test_pass(min(10, 30) == 10);
+ test_pass(min(30, 10) == 10);
+ test_pass(min(0, 10) == 0);
+ test_pass(min(-10, 0) == -10);
+
+ test_pass(max(10, 30) == 30);
+ test_pass(max(30, 10) == 30);
+ test_pass(max(0, 10) == 10);
+ test_pass(max(-10, 0) == 0);
+
+ test_pass(clamp(0, 0, 0) == 0);
+ test_pass(clamp(0, -10, 10) == 0);
+ test_pass(clamp(20, -10, 10) == 10);
+ test_pass(clamp(-20, -10, 10) == -10);
+}
diff --git a/lib/dpkg/t/t-meminfo.c b/lib/dpkg/t/t-meminfo.c
new file mode 100644
index 0000000..0c3e635
--- /dev/null
+++ b/lib/dpkg/t/t-meminfo.c
@@ -0,0 +1,75 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-meminfo.c - test memory information handling code
+ *
+ * Copyright © 2022 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/test.h>
+#include <dpkg/meminfo.h>
+
+static void
+test_meminfo(void)
+{
+ char *pathname;
+ uint64_t mem;
+ int rc;
+
+ mem = 0;
+ pathname = test_data_file("meminfo-no-file");
+ rc = meminfo_get_available_from_file(pathname, &mem);
+ test_pass(rc == MEMINFO_NO_FILE);
+ test_pass(mem == 0);
+ free(pathname);
+
+ mem = 0;
+ pathname = test_data_file("meminfo-no-data");
+ rc = meminfo_get_available_from_file(pathname, &mem);
+ test_pass(rc == MEMINFO_NO_DATA);
+ test_pass(mem == 0);
+ free(pathname);
+
+ mem = 0;
+ pathname = test_data_file("meminfo-no-unit");
+ rc = meminfo_get_available_from_file(pathname, &mem);
+ test_pass(rc == MEMINFO_NO_UNIT);
+ test_pass(mem == 0);
+ free(pathname);
+
+ mem = 0;
+ pathname = test_data_file("meminfo-no-info");
+ rc = meminfo_get_available_from_file(pathname, &mem);
+ test_pass(rc == MEMINFO_NO_INFO);
+ test_pass(mem == 0);
+ free(pathname);
+
+ mem = 0;
+ pathname = test_data_file("meminfo-ok");
+ rc = meminfo_get_available_from_file(pathname, &mem);
+ test_pass(rc == MEMINFO_OK);
+ test_pass(mem == 3919974400UL);
+ free(pathname);
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(10);
+
+ test_meminfo();
+}
diff --git a/lib/dpkg/t/t-mod-db.c b/lib/dpkg/t/t-mod-db.c
new file mode 100644
index 0000000..c40d8e8
--- /dev/null
+++ b/lib/dpkg/t/t-mod-db.c
@@ -0,0 +1,57 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-mod-db.c - test database implementation
+ *
+ * Copyright © 2011 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <stdlib.h>
+
+#include <dpkg/test.h>
+#include <dpkg/dpkg-db.h>
+
+static void
+test_db_dir(void)
+{
+ char *dir;
+
+ test_str(dpkg_db_get_dir(), ==, ADMINDIR);
+
+ dpkg_db_set_dir("testdir");
+ test_str(dpkg_db_get_dir(), ==, "testdir");
+
+ setenv("DPKG_ADMINDIR", "testenvdir", 1);
+ dpkg_db_set_dir(NULL);
+ test_str(dpkg_db_get_dir(), ==, "testenvdir");
+
+ unsetenv("DPKG_ADMINDIR");
+ dpkg_db_set_dir(NULL);
+ test_str(dpkg_db_get_dir(), ==, ADMINDIR);
+
+ dir = dpkg_db_get_path("testfile");
+ test_str(dir, ==, ADMINDIR "/testfile");
+ free(dir);
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(5);
+
+ test_db_dir();
+}
diff --git a/lib/dpkg/t/t-namevalue.c b/lib/dpkg/t/t-namevalue.c
new file mode 100644
index 0000000..c864766
--- /dev/null
+++ b/lib/dpkg/t/t-namevalue.c
@@ -0,0 +1,48 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-namevalue.c - test name/value implementation
+ *
+ * Copyright © 2018 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/test.h>
+#include <dpkg/namevalue.h>
+#include <dpkg/dpkg-db.h>
+
+static void
+test_namevalue(void)
+{
+ const struct namevalue *nv;
+
+ nv = namevalue_find_by_name(booleaninfos, "");
+ test_pass(nv == NULL);
+
+ nv = namevalue_find_by_name(booleaninfos, "no");
+ test_pass(nv != NULL);
+ test_pass(nv->value == false);
+ test_pass(nv->length == strlen("no"));
+ test_str(nv->name, ==, "no");
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(5);
+
+ test_namevalue();
+}
diff --git a/lib/dpkg/t/t-pager.c b/lib/dpkg/t/t-pager.c
new file mode 100644
index 0000000..d007dde
--- /dev/null
+++ b/lib/dpkg/t/t-pager.c
@@ -0,0 +1,77 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-pager.c - test pager implementation
+ *
+ * Copyright © 2010-2012 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <dpkg/test.h>
+#include <dpkg/pager.h>
+#include <dpkg/dpkg.h>
+
+static void
+test_dup_file(int fd, const char *filename, int flags)
+{
+ int newfd;
+
+ newfd = open(filename, flags);
+ dup2(newfd, fd);
+ close(newfd);
+}
+
+static void
+test_pager_get_exec(void)
+{
+ const char *pager;
+ int origfd = dup(STDOUT_FILENO);
+
+ /* Test stdout being a tty. */
+ test_todo_block("environment might not expose controlling terminal") {
+ const char *default_pager;
+
+ test_dup_file(STDOUT_FILENO, "/dev/tty", O_WRONLY);
+ setenv("PAGER", "test-pager", 1);
+ pager = pager_get_exec();
+ unsetenv("PAGER");
+ default_pager = pager_get_exec();
+ dup2(origfd, STDOUT_FILENO);
+ test_str(pager, ==, "test-pager");
+ test_str(default_pager, ==, DPKG_DEFAULT_PAGER);
+ }
+
+ /* Test stdout not being a tty. */
+ test_dup_file(STDOUT_FILENO, "/dev/null", O_WRONLY);
+ pager = pager_get_exec();
+ dup2(origfd, STDOUT_FILENO);
+ test_str(pager, ==, CAT);
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(3);
+
+ test_pager_get_exec();
+}
diff --git a/lib/dpkg/t/t-path.c b/lib/dpkg/t/t-path.c
new file mode 100644
index 0000000..deb1b72
--- /dev/null
+++ b/lib/dpkg/t/t-path.c
@@ -0,0 +1,181 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-path.c - test path handling code
+ *
+ * Copyright © 2009-2012 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <ctype.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <dpkg/test.h>
+#include <dpkg/path.h>
+
+/* Use the test_trim_eq_ref macro to avoid leaking the string and to get
+ * meaningful line numbers from assert. */
+#define test_trim_eq_ref(p, ref) \
+do { \
+ char *t = test_alloc(strdup((p))); \
+ path_trim_slash_slashdot(t); \
+ test_str(t, ==, (ref)); \
+ free(t); \
+} while (0)
+
+static void
+test_path_trim(void)
+{
+ test_trim_eq_ref("/a", "/a");
+ test_trim_eq_ref("./././.", ".");
+ test_trim_eq_ref("./././", ".");
+ test_trim_eq_ref("./.", ".");
+ test_trim_eq_ref("./", ".");
+ test_trim_eq_ref("/./././.", "/");
+ test_trim_eq_ref("/./", "/");
+ test_trim_eq_ref("/.", "/");
+ test_trim_eq_ref("/", "/");
+ test_trim_eq_ref("", "");
+ test_trim_eq_ref("/./../.", "/./..");
+ test_trim_eq_ref("/foo/bar/./", "/foo/bar");
+ test_trim_eq_ref("./foo/bar/./", "./foo/bar");
+ test_trim_eq_ref("/./foo/bar/./", "/./foo/bar");
+}
+
+static void
+test_path_skip(void)
+{
+ test_str(path_skip_slash_dotslash("./././."), ==, ".");
+ test_str(path_skip_slash_dotslash("./././"), ==, "");
+ test_str(path_skip_slash_dotslash("./."), ==, ".");
+ test_str(path_skip_slash_dotslash("./"), ==, "");
+ test_str(path_skip_slash_dotslash("/./././."), ==, ".");
+ test_str(path_skip_slash_dotslash("/./"), ==, "");
+ test_str(path_skip_slash_dotslash("/."), ==, ".");
+ test_str(path_skip_slash_dotslash("/"), ==, "");
+ test_str(path_skip_slash_dotslash("/./../."), ==, "../.");
+ test_str(path_skip_slash_dotslash("/foo/bar/./"), ==, "foo/bar/./");
+ test_str(path_skip_slash_dotslash("./foo/bar/./"), ==, "foo/bar/./");
+ test_str(path_skip_slash_dotslash("/./foo/bar/./"), ==, "foo/bar/./");
+}
+
+static void
+test_path_basename(void)
+{
+ test_str(path_basename("./."), ==, ".");
+ test_str(path_basename("./"), ==, "");
+ test_str(path_basename("/."), ==, ".");
+ test_str(path_basename("/"), ==, "");
+ test_str(path_basename("/foo"), ==, "foo");
+ test_str(path_basename("/foo/bar"), ==, "bar");
+ test_str(path_basename("/foo/bar/"), ==, "");
+}
+
+static void
+test_path_temp(void)
+{
+ char *template;
+
+ template = path_make_temp_template("test");
+
+ test_pass(strstr(template, "test") != NULL);
+ test_pass(strstr(template, "XXXXXX") != NULL);
+
+ free(template);
+}
+
+static bool
+string_is_ascii(const char *str)
+{
+ while (*str) {
+ if (!isascii(*str))
+ return false;
+
+ str++;
+ }
+
+ return true;
+}
+
+static void
+test_path_quote(void)
+{
+ const char src_7_bit[] = "string with 7-bit chars only";
+ const char src_7_bit_trim[] = "string with 7-bit chars";
+ const char src_8_bit[] = "text w/ 8-bit chars: \\ \370 \300 \342 end";
+ const char src_8_bit_end[] = "text \370";
+ const char src_bs_end[] = "text \\";
+ char *dst;
+ size_t len;
+
+ /* Test 0 length. */
+ dst = NULL;
+ path_quote_filename(dst, src_7_bit, 0);
+
+ /* Test no quoting. */
+ len = strlen(src_7_bit) + 1;
+ dst = test_alloc(malloc(len));
+
+ path_quote_filename(dst, src_7_bit, len);
+ test_str(dst, ==, src_7_bit);
+ free(dst);
+
+ /* Test no quoting with limit. */
+ len = strlen(src_7_bit_trim) + 1;
+ dst = test_alloc(malloc(len));
+
+ path_quote_filename(dst, src_7_bit, len);
+ test_str(dst, ==, src_7_bit_trim);
+ free(dst);
+
+ /* Test normal quoting. */
+ len = strlen(src_8_bit) * 2 + 1;
+ dst = test_alloc(malloc(len));
+
+ path_quote_filename(dst, src_8_bit, len);
+ test_pass(strstr(dst, "end") != NULL);
+ test_pass(string_is_ascii(dst));
+ free(dst);
+
+ /* Test normal quoting with limit. */
+ len = strlen(src_8_bit_end) + 1 + 2;
+ dst = test_alloc(malloc(len));
+
+ path_quote_filename(dst, src_8_bit_end, len);
+ test_str(dst, ==, "text ");
+ free(dst);
+
+ /* Test backslash quoting with limit. */
+ len = strlen(src_bs_end) + 1;
+ dst = test_alloc(malloc(len));
+
+ path_quote_filename(dst, src_bs_end, len);
+ test_str(dst, ==, "text ");
+ free(dst);
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(41);
+
+ test_path_trim();
+ test_path_skip();
+ test_path_basename();
+ test_path_temp();
+ test_path_quote();
+}
diff --git a/lib/dpkg/t/t-pkg-format.c b/lib/dpkg/t/t-pkg-format.c
new file mode 100644
index 0000000..81e7ddb
--- /dev/null
+++ b/lib/dpkg/t/t-pkg-format.c
@@ -0,0 +1,142 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-pkg-format.c - test pkg-format implementation
+ *
+ * Copyright © 2022 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/test.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg.h>
+#include <dpkg/pkg-format.h>
+
+static void
+prep_pkg(struct pkgset *set, struct pkginfo *pkg)
+{
+ pkg_blank(pkg);
+
+ pkgset_blank(set);
+ pkgset_link_pkg(set, pkg);
+
+ set->name = "test-bin";
+ pkg->installed.description = "short synopsis -- some package\n"
+ " This is the extended description for this package-\n"
+ " .\n"
+ " Potentially expanding multiple lines.\n";
+ pkg->installed.arch = dpkg_arch_get(DPKG_ARCH_ALL);
+ pkg->installed.version = DPKG_VERSION_OBJECT(0, "4.5", "2");
+}
+
+static void
+prep_virtpkg(struct pkgset *set, struct pkginfo *pkg)
+{
+ pkg_blank(pkg);
+
+ pkgset_blank(set);
+ pkgset_link_pkg(set, pkg);
+
+ set->name = "test-virt";
+}
+
+static void
+test_field(struct pkginfo *pkg, const char *fmt, const char *exp)
+{
+ struct pkg_format_node *head;
+ struct varbuf vb = VARBUF_INIT;
+
+ head = pkg_format_parse(fmt, NULL);
+ test_pass(head);
+ pkg_format_print(&vb, head, pkg, &pkg->installed);
+ test_str(vb.buf, ==, exp);
+ pkg_format_free(head);
+ varbuf_destroy(&vb);
+}
+
+static void
+test_pkg_format_real_fields(void)
+{
+ struct pkgset pkgset;
+ struct pkginfo pkg;
+
+ prep_pkg(&pkgset, &pkg);
+
+ test_field(&pkg, "${Package}_${Version}_${Architecture}",
+ "test-bin_4.5-2_all");
+}
+
+static void
+test_pkg_format_virtual_fields(void)
+{
+ struct pkgset pkgset;
+ struct pkginfo pkg;
+
+ prep_pkg(&pkgset, &pkg);
+
+ test_field(&pkg, "${source:Package}_${source:Version}",
+ "test-bin_4.5-2");
+
+ pkg.installed.source = "test-src";
+ test_field(&pkg, "${source:Package}_${source:Version}",
+ "test-src_4.5-2");
+ test_field(&pkg, "${source:Upstream-Version}",
+ "4.5");
+
+ pkg.installed.source = "test-src (1:3.4-6)";
+ test_field(&pkg, "${source:Package}_${source:Version}",
+ "test-src_1:3.4-6");
+ test_field(&pkg, "${source:Upstream-Version}",
+ "3.4");
+
+ test_field(&pkg, "${binary:Synopsis}",
+ "short synopsis -- some package");
+
+ test_field(&pkg, "${binary:Summary}",
+ "short synopsis -- some package");
+
+ prep_virtpkg(&pkgset, &pkg);
+ test_field(&pkg, "${source:Package}_${source:Version}",
+ "test-virt_");
+ test_field(&pkg, "${source:Upstream-Version}",
+ "");
+}
+
+static void
+test_pkg_format_virtual_fields_db_fsys(void)
+{
+ struct pkg_format_node *head;
+
+ head = pkg_format_parse("prefix ${unknown-variable} suffix", NULL);
+ test_pass(head);
+ test_fail(pkg_format_needs_db_fsys(head));
+ pkg_format_free(head);
+
+ head = pkg_format_parse("prefix ${db-fsys:Files} suffix", NULL);
+ test_pass(head);
+ test_pass(pkg_format_needs_db_fsys(head));
+ pkg_format_free(head);
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(24);
+
+ test_pkg_format_real_fields();
+ test_pkg_format_virtual_fields();
+ test_pkg_format_virtual_fields_db_fsys();
+}
diff --git a/lib/dpkg/t/t-pkg-hash.c b/lib/dpkg/t/t-pkg-hash.c
new file mode 100644
index 0000000..14935c2
--- /dev/null
+++ b/lib/dpkg/t/t-pkg-hash.c
@@ -0,0 +1,179 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-pkg-hash.c - test pkg-hash implementation
+ *
+ * Copyright © 2018 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/test.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg.h>
+
+static void
+test_pkg_hash(void)
+{
+ struct dpkg_arch *arch;
+ struct pkgset *set;
+ struct pkginfo *pkg;
+ struct pkg_hash_iter *iter;
+ int pkginstance;
+
+ test_pass(pkg_hash_count_set() == 0);
+ test_pass(pkg_hash_count_pkg() == 0);
+
+ set = pkg_hash_find_set("pkg-aa");
+ test_pass(set != NULL);
+ test_str(set->name, ==, "pkg-aa");
+ test_pass(pkg_hash_count_set() == 1);
+ test_pass(pkg_hash_count_pkg() == 1);
+
+ set = pkg_hash_find_set("pkg-aa");
+ test_pass(set != NULL);
+ test_str(set->name, ==, "pkg-aa");
+ test_pass(pkg_hash_count_set() == 1);
+ test_pass(pkg_hash_count_pkg() == 1);
+
+ set = pkg_hash_find_set("Pkg-AA");
+ test_pass(set != NULL);
+ test_str(set->name, ==, "pkg-aa");
+ test_pass(pkg_hash_count_set() == 1);
+ test_pass(pkg_hash_count_pkg() == 1);
+
+ set = pkg_hash_find_set("pkg-bb");
+ test_pass(set != NULL);
+ pkg_set_status(&set->pkg, PKG_STAT_INSTALLED);
+ test_str(set->name, ==, "pkg-bb");
+ test_pass(pkg_hash_count_set() == 2);
+ test_pass(pkg_hash_count_pkg() == 2);
+
+ set = pkg_hash_find_set("pkg-cc");
+ test_pass(set != NULL);
+ test_str(set->name, ==, "pkg-cc");
+ test_pass(pkg_hash_count_set() == 3);
+ test_pass(pkg_hash_count_pkg() == 3);
+
+ arch = dpkg_arch_find("arch-xx");
+ pkg = pkg_hash_find_pkg("pkg-aa", arch);
+ test_pass(pkg != NULL);
+ pkg_set_status(pkg, PKG_STAT_INSTALLED);
+ test_str(pkg->set->name, ==, "pkg-aa");
+ test_str(pkg->installed.arch->name, ==, "arch-xx");
+ test_str(pkg->available.arch->name, ==, "arch-xx");
+ test_pass(pkg_hash_count_set() == 3);
+ test_pass(pkg_hash_count_pkg() == 3);
+
+ arch = dpkg_arch_find("arch-yy");
+ pkg = pkg_hash_find_pkg("pkg-aa", arch);
+ test_pass(pkg != NULL);
+ test_str(pkg->set->name, ==, "pkg-aa");
+ test_str(pkg->installed.arch->name, ==, "arch-yy");
+ test_str(pkg->available.arch->name, ==, "arch-yy");
+ test_pass(pkg_hash_count_set() == 3);
+ test_pass(pkg_hash_count_pkg() == 4);
+
+ arch = dpkg_arch_find("arch-zz");
+ pkg = pkg_hash_find_pkg("pkg-aa", arch);
+ test_pass(pkg != NULL);
+ pkg_set_status(pkg, PKG_STAT_UNPACKED);
+ test_str(pkg->set->name, ==, "pkg-aa");
+ test_str(pkg->installed.arch->name, ==, "arch-zz");
+ test_str(pkg->available.arch->name, ==, "arch-zz");
+ test_pass(pkg_hash_count_set() == 3);
+ test_pass(pkg_hash_count_pkg() == 5);
+
+ arch = dpkg_arch_find("arch-xx");
+ pkg = pkg_hash_find_pkg("pkg-aa", arch);
+ test_pass(pkg != NULL);
+ test_str(pkg->set->name, ==, "pkg-aa");
+ test_str(pkg->installed.arch->name, ==, "arch-xx");
+ test_str(pkg->available.arch->name, ==, "arch-xx");
+ test_pass(pkg_hash_count_set() == 3);
+ test_pass(pkg_hash_count_pkg() == 5);
+
+ set = pkg_hash_find_set("pkg-aa");
+ test_str(set->name, ==, "pkg-aa");
+ pkg = pkg_hash_get_singleton(set);
+ test_pass(pkg == NULL);
+ test_pass(pkg_hash_count_set() == 3);
+ test_pass(pkg_hash_count_pkg() == 5);
+
+ pkg = pkg_hash_find_singleton("pkg-bb");
+ test_pass(pkg != NULL);
+ test_str(pkg->set->name, ==, "pkg-bb");
+ test_pass(pkg_hash_count_set() == 3);
+ test_pass(pkg_hash_count_pkg() == 5);
+
+ pkg = pkg_hash_find_singleton("pkg-cc");
+ test_pass(pkg != NULL);
+ test_str(pkg->set->name, ==, "pkg-cc");
+ test_pass(pkg_hash_count_set() == 3);
+ test_pass(pkg_hash_count_pkg() == 5);
+
+ iter = pkg_hash_iter_new();
+ while ((set = pkg_hash_iter_next_set(iter))) {
+ if (strcmp(set->name, "pkg-aa") == 0)
+ test_str(set->name, ==, "pkg-aa");
+ else if (strcmp(set->name, "pkg-bb") == 0)
+ test_str(set->name, ==, "pkg-bb");
+ else if (strcmp(set->name, "pkg-cc") == 0)
+ test_str(set->name, ==, "pkg-cc");
+ else
+ test_fail("unknown fsys_namenode");
+ }
+ pkg_hash_iter_free(iter);
+
+ pkginstance = 0;
+ iter = pkg_hash_iter_new();
+ while ((pkg = pkg_hash_iter_next_pkg(iter))) {
+ pkginstance++;
+ if (strcmp(pkg->set->name, "pkg-aa") == 0) {
+ struct pkgbin *pkgbin = &pkg->installed;
+
+ test_str(pkg->set->name, ==, "pkg-aa");
+ if (strcmp(pkgbin->arch->name, "arch-xx") == 0)
+ test_str(pkgbin->arch->name, ==, "arch-xx");
+ else if (strcmp(pkgbin->arch->name, "arch-yy") == 0)
+ test_str(pkgbin->arch->name, ==, "arch-yy");
+ else if (strcmp(pkgbin->arch->name, "arch-zz") == 0)
+ test_str(pkgbin->arch->name, ==, "arch-zz");
+ else
+ test_fail("unknown pkginfo instance");
+ } else if (strcmp(pkg->set->name, "pkg-bb") == 0) {
+ test_str(pkg->set->name, ==, "pkg-bb");
+ } else if (strcmp(pkg->set->name, "pkg-cc") == 0) {
+ test_str(pkg->set->name, ==, "pkg-cc");
+ } else {
+ test_fail("unknown fsys_namenode");
+ }
+ }
+ pkg_hash_iter_free(iter);
+ test_pass(pkginstance == 5);
+
+ pkg_hash_reset();
+ test_pass(pkg_hash_count_set() == 0);
+ test_pass(pkg_hash_count_pkg() == 0);
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(72);
+
+ test_pkg_hash();
+}
diff --git a/lib/dpkg/t/t-pkg-list.c b/lib/dpkg/t/t-pkg-list.c
new file mode 100644
index 0000000..722cf2e
--- /dev/null
+++ b/lib/dpkg/t/t-pkg-list.c
@@ -0,0 +1,90 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-pkg-list.c - test pkg-list implementation
+ *
+ * Copyright © 2010,2012 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/test.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg-list.h>
+
+static void
+test_pkg_list_new(void)
+{
+ struct pkg_list *l1, *l2, *l3;
+ struct pkginfo pkg1, pkg2, pkg3;
+
+ l1 = pkg_list_new(&pkg1, NULL);
+ test_alloc(l1);
+ test_pass(l1->next == NULL);
+ test_pass(l1->pkg == &pkg1);
+
+ l2 = pkg_list_new(&pkg2, l1);
+ test_alloc(l2);
+ test_pass(l2->next == l1);
+ test_pass(l2->pkg == &pkg2);
+
+ l3 = pkg_list_new(&pkg3, l2);
+ test_alloc(l3);
+ test_pass(l3->next == l2);
+ test_pass(l3->pkg == &pkg3);
+
+ pkg_list_free(l3);
+}
+
+static void
+test_pkg_list_prepend(void)
+{
+ struct pkg_list *head = NULL, *l1, *l2, *l3;
+ struct pkginfo pkg1, pkg2, pkg3, pkg4;
+
+ pkg_list_prepend(&head, &pkg1);
+ test_alloc(head);
+ test_pass(head->next == NULL);
+ test_pass(head->pkg == &pkg1);
+ l1 = head;
+
+ pkg_list_prepend(&head, &pkg2);
+ test_alloc(head);
+ test_pass(head->next == l1);
+ test_pass(head->pkg == &pkg2);
+ l2 = head;
+
+ pkg_list_prepend(&head, &pkg3);
+ test_alloc(head);
+ test_pass(head->next == l2);
+ test_pass(head->pkg == &pkg3);
+ l3 = head;
+
+ pkg_list_prepend(&head, &pkg4);
+ test_alloc(head);
+ test_pass(head->next == l3);
+ test_pass(head->pkg == &pkg4);
+
+ pkg_list_free(head);
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(14);
+
+ test_pkg_list_new();
+ test_pkg_list_prepend();
+}
diff --git a/lib/dpkg/t/t-pkg-queue.c b/lib/dpkg/t/t-pkg-queue.c
new file mode 100644
index 0000000..cf1e327
--- /dev/null
+++ b/lib/dpkg/t/t-pkg-queue.c
@@ -0,0 +1,116 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-pkg-queue.c - test pkg-queue implementation
+ *
+ * Copyright © 2010,2012 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/test.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg-queue.h>
+
+static void
+test_pkg_queue_init(void)
+{
+ struct pkg_queue q = PKG_QUEUE_INIT;
+ struct pkg_list l;
+
+ test_pass(q.length == 0);
+ test_pass(q.head == NULL);
+ test_pass(q.tail == NULL);
+
+ test_pass(pkg_queue_is_empty(&q));
+
+ q = (struct pkg_queue){ .length = 10, .head = &l, .tail = &l };
+
+ pkg_queue_init(&q);
+ test_pass(q.length == 0);
+ test_pass(q.head == NULL);
+ test_pass(q.tail == NULL);
+
+ test_pass(pkg_queue_is_empty(&q));
+}
+
+static void
+test_pkg_queue_push_pop(void)
+{
+ struct pkg_queue q = PKG_QUEUE_INIT;
+ struct pkg_list *l1, *l2, *l3;
+ struct pkginfo *pkgp, pkg1, pkg2, pkg3;
+
+ test_pass(pkg_queue_is_empty(&q));
+
+ /* Test push operations. */
+
+ l1 = pkg_queue_push(&q, &pkg1);
+ test_pass(l1 != NULL);
+ test_pass(q.head == l1);
+ test_pass(q.tail == l1);
+ test_pass(q.length == 1);
+
+ l2 = pkg_queue_push(&q, &pkg2);
+ test_pass(l2 != NULL);
+ test_pass(q.head == l1);
+ test_pass(q.tail == l2);
+ test_pass(q.length == 2);
+
+ l3 = pkg_queue_push(&q, &pkg3);
+ test_pass(l3 != NULL);
+ test_pass(q.head == l1);
+ test_pass(q.tail == l3);
+ test_pass(q.length == 3);
+
+ /* Test pop operations. */
+
+ pkgp = pkg_queue_pop(&q);
+ test_pass(pkgp == &pkg1);
+ test_pass(q.head == l2);
+ test_pass(q.tail == l3);
+ test_pass(q.length == 2);
+
+ pkgp = pkg_queue_pop(&q);
+ test_pass(pkgp == &pkg2);
+ test_pass(q.head == l3);
+ test_pass(q.tail == l3);
+ test_pass(q.length == 1);
+
+ pkgp = pkg_queue_pop(&q);
+ test_pass(pkgp == &pkg3);
+ test_pass(q.head == NULL);
+ test_pass(q.tail == NULL);
+ test_pass(q.length == 0);
+
+ test_pass(pkg_queue_is_empty(&q));
+
+ pkgp = pkg_queue_pop(&q);
+ test_pass(pkgp == NULL);
+ test_pass(q.head == NULL);
+ test_pass(q.tail == NULL);
+ test_pass(q.length == 0);
+
+ pkg_queue_destroy(&q);
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(38);
+
+ test_pkg_queue_init();
+ test_pkg_queue_push_pop();
+}
diff --git a/lib/dpkg/t/t-pkg-show.c b/lib/dpkg/t/t-pkg-show.c
new file mode 100644
index 0000000..6bb361d
--- /dev/null
+++ b/lib/dpkg/t/t-pkg-show.c
@@ -0,0 +1,70 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-pkg-show.c - test pkg-show implementation
+ *
+ * Copyright © 2018 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/test.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/arch.h>
+
+static void
+test_pkg_show_name(void)
+{
+ struct dpkg_arch *arch;
+ struct pkginfo *pkg;
+ const char *pkgname;
+
+ arch = dpkg_arch_find("arch");
+ test_pass(arch);
+
+ pkg = pkg_hash_find_pkg("test", arch);
+ test_pass(pkg);
+ test_str(pkg->set->name, ==, "test");
+ test_pass(pkg->installed.arch->type == DPKG_ARCH_UNKNOWN);
+
+ pkgname = pkg_name(pkg, pnaw_never);
+ test_pass(pkgname);
+ test_str(pkgname, ==, "test");
+
+ pkgname = pkg_name(pkg, pnaw_nonambig);
+ test_pass(pkgname);
+ test_str(pkgname, ==, "test:arch");
+
+ pkgname = pkg_name(pkg, pnaw_always);
+ test_pass(pkgname);
+ test_str(pkgname, ==, "test:arch");
+
+ pkgname = pkg_name(pkg, pnaw_same);
+ test_pass(pkgname);
+ test_str(pkgname, ==, "test");
+
+ pkg->installed.multiarch = PKG_MULTIARCH_SAME;
+ pkgname = pkg_name(pkg, pnaw_same);
+ test_pass(pkgname);
+ test_str(pkgname, ==, "test:arch");
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(14);
+
+ test_pkg_show_name();
+}
diff --git a/lib/dpkg/t/t-pkginfo.c b/lib/dpkg/t/t-pkginfo.c
new file mode 100644
index 0000000..b974027
--- /dev/null
+++ b/lib/dpkg/t/t-pkginfo.c
@@ -0,0 +1,155 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-pkginfo.c - test pkginfo handling
+ *
+ * Copyright © 2009-2010,2012-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/test.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg.h>
+
+static void
+test_pkginfo_informative(void)
+{
+ struct pkginfo pkg;
+
+ pkg_blank(&pkg);
+ test_fail(pkg_is_informative(&pkg, &pkg.installed));
+
+ pkg_set_want(&pkg, PKG_WANT_PURGE);
+ test_pass(pkg_is_informative(&pkg, &pkg.installed));
+
+ pkg_blank(&pkg);
+ pkg.installed.description = "test description";
+ test_pass(pkg_is_informative(&pkg, &pkg.installed));
+
+ /* TODO: Complete. */
+}
+
+static void
+test_pkginfo_eflags(void)
+{
+ struct pkginfo pkg;
+
+ pkg_blank(&pkg);
+ test_pass(pkg.eflag == PKG_EFLAG_OK);
+
+ pkg_set_eflags(&pkg, PKG_EFLAG_REINSTREQ);
+ test_pass(pkg.eflag == PKG_EFLAG_REINSTREQ);
+
+ pkg_clear_eflags(&pkg, PKG_EFLAG_REINSTREQ);
+ test_pass(pkg.eflag == PKG_EFLAG_OK);
+
+ DPKG_IGNORE_WARNING_ASSIGN_ENUM();
+ pkg_set_eflags(&pkg, 0x11);
+ test_pass(pkg.eflag == 0x11);
+ DPKG_ACCEPT_WARNING_ASSIGN_ENUM();
+ pkg_reset_eflags(&pkg);
+ test_pass(pkg.eflag == PKG_EFLAG_OK);
+}
+
+static void
+test_pkginfo_instance_tracking(void)
+{
+ struct pkgset set;
+ struct pkginfo pkg2, pkg3, pkg4;
+
+ pkgset_blank(&set);
+ pkg_blank(&pkg2);
+ pkg_blank(&pkg3);
+ pkg_blank(&pkg4);
+
+ test_pass(pkgset_installed_instances(&set) == 0);
+
+ /* Link the other instances into the pkgset. */
+ pkgset_link_pkg(&set, &pkg4);
+ pkgset_link_pkg(&set, &pkg3);
+ pkgset_link_pkg(&set, &pkg2);
+
+ /* Test installation state transitions. */
+ pkg_set_status(&pkg4, PKG_STAT_INSTALLED);
+ test_pass(pkgset_installed_instances(&set) == 1);
+
+ pkg_set_status(&pkg4, PKG_STAT_INSTALLED);
+ test_pass(pkgset_installed_instances(&set) == 1);
+
+ pkg_set_status(&pkg4, PKG_STAT_TRIGGERSPENDING);
+ test_pass(pkgset_installed_instances(&set) == 1);
+
+ pkg_set_status(&pkg4, PKG_STAT_TRIGGERSAWAITED);
+ test_pass(pkgset_installed_instances(&set) == 1);
+
+ pkg_set_status(&pkg4, PKG_STAT_HALFCONFIGURED);
+ test_pass(pkgset_installed_instances(&set) == 1);
+
+ pkg_set_status(&pkg4, PKG_STAT_UNPACKED);
+ test_pass(pkgset_installed_instances(&set) == 1);
+
+ pkg_set_status(&pkg4, PKG_STAT_HALFINSTALLED);
+ test_pass(pkgset_installed_instances(&set) == 1);
+
+ pkg_set_status(&pkg4, PKG_STAT_CONFIGFILES);
+ test_pass(pkgset_installed_instances(&set) == 1);
+
+ pkg_set_status(&pkg4, PKG_STAT_NOTINSTALLED);
+ test_pass(pkgset_installed_instances(&set) == 0);
+
+ pkg_set_status(&pkg4, PKG_STAT_NOTINSTALLED);
+ test_pass(pkgset_installed_instances(&set) == 0);
+
+ /* Toggle installation states on various packages. */
+ pkg_set_status(&pkg4, PKG_STAT_INSTALLED);
+ test_pass(pkgset_installed_instances(&set) == 1);
+
+ pkg_set_status(&pkg2, PKG_STAT_HALFINSTALLED);
+ test_pass(pkgset_installed_instances(&set) == 2);
+
+ pkg_set_status(&set.pkg, PKG_STAT_CONFIGFILES);
+ test_pass(pkgset_installed_instances(&set) == 3);
+
+ pkg_set_status(&pkg3, PKG_STAT_NOTINSTALLED);
+ test_pass(pkgset_installed_instances(&set) == 3);
+
+ pkg_set_status(&pkg3, PKG_STAT_UNPACKED);
+ test_pass(pkgset_installed_instances(&set) == 4);
+
+ pkg_set_status(&set.pkg, PKG_STAT_NOTINSTALLED);
+ test_pass(pkgset_installed_instances(&set) == 3);
+
+ pkg_set_status(&pkg2, PKG_STAT_NOTINSTALLED);
+ test_pass(pkgset_installed_instances(&set) == 2);
+
+ pkg_set_status(&pkg3, PKG_STAT_NOTINSTALLED);
+ test_pass(pkgset_installed_instances(&set) == 1);
+
+ pkg_set_status(&pkg4, PKG_STAT_NOTINSTALLED);
+ test_pass(pkgset_installed_instances(&set) == 0);
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(28);
+
+ test_pkginfo_informative();
+ test_pkginfo_eflags();
+ test_pkginfo_instance_tracking();
+
+ /* TODO: Complete. */
+}
diff --git a/lib/dpkg/t/t-progname.c b/lib/dpkg/t/t-progname.c
new file mode 100644
index 0000000..e90e923
--- /dev/null
+++ b/lib/dpkg/t/t-progname.c
@@ -0,0 +1,53 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-progname.c - test program name handling
+ *
+ * Copyright © 2011 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/test.h>
+#include <dpkg/progname.h>
+
+static void
+test_progname(void)
+{
+ const char *progname;
+
+ /* Test initially empty progname. */
+ progname = dpkg_get_progname();
+ /* Handle libtool executables. */
+ if (strncmp(progname, "lt-", 3) == 0)
+ progname += 3;
+ test_str(progname, ==, "t-progname");
+
+ /* Test setting a new progname. */
+ dpkg_set_progname("newname");
+ test_str(dpkg_get_progname(), ==, "newname");
+
+ /* Test setting a new progname with path. */
+ dpkg_set_progname("path/newprogname");
+ test_str(dpkg_get_progname(), ==, "newprogname");
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(3);
+
+ test_progname();
+}
diff --git a/lib/dpkg/t/t-string.c b/lib/dpkg/t/t-string.c
new file mode 100644
index 0000000..7b4350d
--- /dev/null
+++ b/lib/dpkg/t/t-string.c
@@ -0,0 +1,281 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-string.c - test string handling
+ *
+ * Copyright © 2009-2011, 2014-2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/test.h>
+#include <dpkg/string.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <stdio.h>
+
+static void
+test_str_is_set(void)
+{
+ /* Test if strings are unset. */
+ test_pass(str_is_unset(NULL));
+ test_pass(str_is_unset(""));
+ test_fail(str_is_unset("aaa"));
+
+ /* Test if strings are set. */
+ test_fail(str_is_set(NULL));
+ test_fail(str_is_set(""));
+ test_pass(str_is_set("ccc"));
+}
+
+static void
+test_str_match_end(void)
+{
+ test_pass(str_match_end("foo bar quux", "quux"));
+ test_pass(str_match_end("foo bar quux", "bar quux"));
+ test_pass(str_match_end("foo bar quux", "foo bar quux"));
+ test_fail(str_match_end("foo bar quux", "foo bar quux zorg"));
+ test_fail(str_match_end("foo bar quux", "foo bar"));
+ test_fail(str_match_end("foo bar quux", "foo"));
+}
+
+static void
+test_str_fnv_hash(void)
+{
+ test_pass(str_fnv_hash("") == 0x811c9dc5U);
+ test_pass(str_fnv_hash("a") == 0xe40c292cUL);
+ test_pass(str_fnv_hash("b") == 0xe70c2de5UL);
+ test_pass(str_fnv_hash("c") == 0xe60c2c52UL);
+ test_pass(str_fnv_hash("d") == 0xe10c2473UL);
+ test_pass(str_fnv_hash("e") == 0xe00c22e0UL);
+ test_pass(str_fnv_hash("f") == 0xe30c2799UL);
+ test_pass(str_fnv_hash("fo") == 0x6222e842UL);
+ test_pass(str_fnv_hash("foo") == 0xa9f37ed7UL);
+ test_pass(str_fnv_hash("foob") == 0x3f5076efUL);
+ test_pass(str_fnv_hash("fooba") == 0x39aaa18aUL);
+ test_pass(str_fnv_hash("foobar") == 0xbf9cf968UL);
+
+ test_pass(str_fnv_hash("test-string") == 0xd28f6e61UL);
+ test_pass(str_fnv_hash("Test-string") == 0x00a54b81UL);
+ test_pass(str_fnv_hash("rest-string") == 0x1cdeebffUL);
+ test_pass(str_fnv_hash("Rest-string") == 0x20464b9fUL);
+}
+
+static void
+test_str_concat(void)
+{
+ char buf[1024], *str;
+
+ memset(buf, 0, sizeof(buf));
+ str = str_concat(buf, NULL);
+ test_pass(str == buf);
+ test_str(buf, ==, "");
+
+ memset(buf, 0, sizeof(buf));
+ str = str_concat(buf, "aaa", NULL);
+ test_str(buf, ==, "aaa");
+ test_pass(str == buf + 3);
+
+ memset(buf, 0, sizeof(buf));
+ str = str_concat(buf, "zzzz", "yy", "xxxx", NULL);
+ test_str(buf, ==, "zzzzyyxxxx");
+ test_pass(str == buf + 10);
+
+ memset(buf, 0, sizeof(buf));
+ str = str_concat(buf, "1234", "", "5678", NULL);
+ test_str(buf, ==, "12345678");
+ test_pass(str == buf + 8);
+
+ memset(buf, ' ', sizeof(buf));
+ str = str_concat(buf, "eol", NULL, "bom", NULL);
+ test_str(buf, ==, "eol");
+ test_pass(str == buf + 3);
+}
+
+static void
+test_str_fmt(void)
+{
+ char *str;
+
+ str = str_fmt("%s", "abcde");
+ test_str(str, ==, "abcde");
+ free(str);
+
+ str = str_fmt("%d", 15);
+ test_str(str, ==, "15");
+ free(str);
+}
+
+static void
+test_str_escape_fmt(void)
+{
+ char buf[1024], *q;
+
+ memset(buf, 'a', sizeof(buf));
+ q = str_escape_fmt(buf, "", sizeof(buf));
+ strcpy(q, " end");
+ test_str(buf, ==, " end");
+
+ memset(buf, 'a', sizeof(buf));
+ q = str_escape_fmt(buf, "%", sizeof(buf));
+ strcpy(q, " end");
+ test_str(buf, ==, "%% end");
+
+ memset(buf, 'a', sizeof(buf));
+ q = str_escape_fmt(buf, "%%%", sizeof(buf));
+ strcpy(q, " end");
+ test_str(buf, ==, "%%%%%% end");
+
+ memset(buf, 'a', sizeof(buf));
+ q = str_escape_fmt(buf, "%b%b%c%c%%", sizeof(buf));
+ strcpy(q, " end");
+ test_str(buf, ==, "%%b%%b%%c%%c%%%% end");
+
+ /* Test delimited buffer. */
+ memset(buf, 'a', sizeof(buf));
+ q = str_escape_fmt(buf, NULL, 0);
+ test_mem(buf, ==, "aaaa", 4);
+ test_pass(buf == q);
+ test_pass(strnlen(buf, sizeof(buf)) == sizeof(buf));
+
+ memset(buf, 'a', sizeof(buf));
+ q = str_escape_fmt(buf, "b", 1);
+ test_str(q, ==, "");
+
+ memset(buf, 'a', sizeof(buf));
+ q = str_escape_fmt(buf, "%%%", 5);
+ strcpy(q, " end");
+ test_str(buf, ==, "%%%% end");
+
+ memset(buf, 'a', sizeof(buf));
+ q = str_escape_fmt(buf, "%%%", 4);
+ strcpy(q, " end");
+ test_str(buf, ==, "%% end");
+}
+
+static void
+test_str_rtrim_spaces(void)
+{
+ char buf[1024];
+ char *str_end;
+
+ strcpy(buf, "");
+ str_end = str_rtrim_spaces(buf, buf + strlen(buf));
+ test_pass(str_end == buf);
+ test_str(buf, ==, "");
+
+ strcpy(buf, " \t\t \r\n ");
+ str_end = str_rtrim_spaces(buf, buf + strlen(buf));
+ test_pass(str_end == buf);
+ test_str(buf, ==, "");
+
+ strcpy(buf, "abcd");
+ str_end = str_rtrim_spaces(buf, buf + strlen(buf));
+ test_pass(str_end == buf + 4);
+ test_str(buf, ==, "abcd");
+
+ strcpy(buf, "abcd ");
+ str_end = str_rtrim_spaces(buf, buf + strlen(buf));
+ test_pass(str_end == buf + 4);
+ test_str(buf, ==, "abcd");
+
+ strcpy(buf, "abcd\t \t ");
+ str_end = str_rtrim_spaces(buf, buf + strlen(buf));
+ test_pass(str_end == buf + 4);
+ test_str(buf, ==, "abcd");
+
+ strcpy(buf, " \t \t abcd");
+ str_end = str_rtrim_spaces(buf, buf + strlen(buf));
+ test_pass(str_end == buf + 12);
+ test_str(buf, ==, " \t \t abcd");
+}
+
+static void
+test_str_quote_meta(void)
+{
+ char *str;
+
+ str = str_quote_meta("foo1 2bar");
+ test_str(str, ==, "foo1\\ 2bar");
+ free(str);
+
+ str = str_quote_meta("foo1?2bar");
+ test_str(str, ==, "foo1\\?2bar");
+ free(str);
+
+ str = str_quote_meta("foo1*2bar");
+ test_str(str, ==, "foo1\\*2bar");
+ free(str);
+}
+
+static void
+test_str_strip_quotes(void)
+{
+ char buf[1024], *str;
+
+ strcpy(buf, "unquoted text");
+ str = str_strip_quotes(buf);
+ test_str(str, ==, "unquoted text");
+
+ strcpy(buf, "contained 'quoted text'");
+ str = str_strip_quotes(buf);
+ test_str(str, ==, "contained 'quoted text'");
+
+ strcpy(buf, "contained \"quoted text\"");
+ str = str_strip_quotes(buf);
+ test_str(str, ==, "contained \"quoted text\"");
+
+ strcpy(buf, "'unbalanced quotes");
+ str = str_strip_quotes(buf);
+ test_pass(str == NULL);
+
+ strcpy(buf, "\"unbalanced quotes");
+ str = str_strip_quotes(buf);
+ test_pass(str == NULL);
+
+ strcpy(buf, "'mismatched quotes\"");
+ str = str_strip_quotes(buf);
+ test_pass(str == NULL);
+
+ strcpy(buf, "\"mismatched quotes'");
+ str = str_strip_quotes(buf);
+ test_pass(str == NULL);
+
+ strcpy(buf, "'completely quoted text'");
+ str = str_strip_quotes(buf);
+ test_str(str, ==, "completely quoted text");
+
+ strcpy(buf, "\"completely quoted text\"");
+ str = str_strip_quotes(buf);
+ test_str(str, ==, "completely quoted text");
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(74);
+
+ test_str_is_set();
+ test_str_match_end();
+ test_str_fnv_hash();
+ test_str_concat();
+ test_str_fmt();
+ test_str_escape_fmt();
+ test_str_quote_meta();
+ test_str_strip_quotes();
+ test_str_rtrim_spaces();
+}
diff --git a/lib/dpkg/t/t-subproc.c b/lib/dpkg/t/t-subproc.c
new file mode 100644
index 0000000..7ce610b
--- /dev/null
+++ b/lib/dpkg/t/t-subproc.c
@@ -0,0 +1,100 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-subproc.c - test sub-process module
+ *
+ * Copyright © 2011 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <dpkg/test.h>
+#include <dpkg/subproc.h>
+
+static void
+test_subproc_fork(void)
+{
+ struct sigaction sa;
+ pid_t pid;
+ int ret;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = SIG_DFL;
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+ sigaction(SIGPIPE, &sa, NULL);
+
+ /* Test exit(). */
+ pid = subproc_fork();
+ if (pid == 0)
+ exit(0);
+ ret = subproc_reap(pid, "subproc exit pass", SUBPROC_RETERROR);
+ test_pass(ret == 0);
+
+ pid = subproc_fork();
+ if (pid == 0)
+ exit(128);
+ ret = subproc_reap(pid, "subproc exit fail", SUBPROC_RETERROR);
+ test_pass(ret == 128);
+
+ /* Test signals. */
+ pid = subproc_fork();
+ if (pid == 0)
+ raise(SIGINT);
+ ret = subproc_reap(pid, "subproc signal", SUBPROC_WARN);
+ test_pass(ret == -1);
+
+ pid = subproc_fork();
+ if (pid == 0)
+ raise(SIGTERM);
+ ret = subproc_reap(pid, "subproc signal", SUBPROC_WARN);
+ test_pass(ret == -1);
+
+ pid = subproc_fork();
+ if (pid == 0)
+ raise(SIGPIPE);
+ ret = subproc_reap(pid, "subproc SIGPIPE",
+ SUBPROC_WARN | SUBPROC_NOPIPE);
+ test_pass(ret == 0);
+
+ pid = subproc_fork();
+ if (pid == 0)
+ raise(SIGPIPE);
+ ret = subproc_reap(pid, "subproc SIGPIPE", SUBPROC_WARN);
+ test_pass(ret == -1);
+}
+
+TEST_ENTRY(test)
+{
+ int fd;
+
+ test_plan(6);
+
+ /* XXX: Shut up stderr, we don't want the error output. */
+ fd = open("/dev/null", O_RDWR);
+ if (fd < 0)
+ test_bail("cannot open /dev/null");
+ dup2(fd, 2);
+
+ test_subproc_fork();
+}
diff --git a/lib/dpkg/t/t-tar.c b/lib/dpkg/t/t-tar.c
new file mode 100644
index 0000000..6fa217d
--- /dev/null
+++ b/lib/dpkg/t/t-tar.c
@@ -0,0 +1,148 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-tar.c - test tar implementation
+ *
+ * Copyright © 2017 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <errno.h>
+
+#include <dpkg/test.h>
+#include <dpkg/tarfn.h>
+
+static void
+test_tar_atol8(void)
+{
+ uintmax_t u;
+
+ /* Test valid octal numbers. */
+ u = tar_atoul("000000\0\0\0\0\0\0", 12, UINTMAX_MAX);
+ test_pass(u == 0);
+ u = tar_atoul("00000000000\0", 12, UINTMAX_MAX);
+ test_pass(u == 0);
+ u = tar_atoul("00000000001\0", 12, UINTMAX_MAX);
+ test_pass(u == 1);
+ u = tar_atoul("00000000777\0", 12, UINTMAX_MAX);
+ test_pass(u == 511);
+ u = tar_atoul("77777777777\0", 12, UINTMAX_MAX);
+ test_pass(u == 8589934591);
+
+ /* Test legacy formatted octal numbers. */
+ u = tar_atoul(" 0\0", 12, UINTMAX_MAX);
+ test_pass(u == 0);
+ u = tar_atoul(" 1\0", 12, UINTMAX_MAX);
+ test_pass(u == 1);
+ u = tar_atoul(" 777\0", 12, UINTMAX_MAX);
+ test_pass(u == 511);
+
+ /* Test extended octal numbers not terminated by space or NUL,
+ * (as is required by POSIX), but accepted by several implementations
+ * to get one byte larger values. */
+ u = tar_atoul("000000000000", 12, UINTMAX_MAX);
+ test_pass(u == 0);
+ u = tar_atoul("000000000001", 12, UINTMAX_MAX);
+ test_pass(u == 1);
+ u = tar_atoul("000000000777", 12, UINTMAX_MAX);
+ test_pass(u == 511);
+ u = tar_atoul("777777777777", 12, UINTMAX_MAX);
+ test_pass(u == 68719476735);
+
+ /* Test invalid octal numbers. */
+ errno = 0;
+ u = tar_atoul(" ", 12, UINTMAX_MAX);
+ test_pass(u == 0);
+ test_pass(errno == EINVAL);
+
+ errno = 0;
+ u = tar_atoul(" 11111aaa ", 12, UINTMAX_MAX);
+ test_pass(u == 0);
+ test_pass(errno == ERANGE);
+
+ errno = 0;
+ u = tar_atoul(" 8 ", 12, UINTMAX_MAX);
+ test_pass(u == 0);
+ test_pass(errno == ERANGE);
+
+ errno = 0;
+ u = tar_atoul(" 18 ", 12, UINTMAX_MAX);
+ test_pass(u == 0);
+ test_pass(errno == ERANGE);
+
+ errno = 0;
+ u = tar_atoul(" aa ", 12, UINTMAX_MAX);
+ test_pass(u == 0);
+ test_pass(errno == ERANGE);
+}
+
+static void
+test_tar_atol256(void)
+{
+ uintmax_t u;
+ intmax_t i;
+
+ /* Test positive numbers. */
+ u = tar_atoul("\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 12, UINTMAX_MAX);
+ test_pass(u == 0);
+ u = tar_atoul("\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 12, UINTMAX_MAX);
+ test_pass(u == 1);
+ u = tar_atoul("\x80\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00", 12, UINTMAX_MAX);
+ test_pass(u == 8589934592);
+ u = tar_atoul("\x80\x00\x00\x00\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 12, UINTMAX_MAX);
+ test_pass(u == INTMAX_MAX);
+
+ /* Test overflow. */
+ errno = 0;
+ u = tar_atoul("\x80\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00", 12, UINTMAX_MAX);
+ test_pass(u == UINTMAX_MAX);
+ test_pass(errno == ERANGE);
+
+ errno = 0;
+ u = tar_atoul("\x80\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00", 12, UINTMAX_MAX);
+ test_pass(u == UINTMAX_MAX);
+ test_pass(errno == ERANGE);
+
+ /* Test negative numbers. */
+ i = tar_atosl("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 12, INTMAX_MIN, INTMAX_MAX);
+ test_pass(i == -1);
+ i = tar_atosl("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE", 12, INTMAX_MIN, INTMAX_MAX);
+ test_pass(i == -2);
+ i = tar_atosl("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00", 12, INTMAX_MIN, INTMAX_MAX);
+ test_pass(i == -8589934592);
+ i = tar_atosl("\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00", 12, INTMAX_MIN, INTMAX_MAX);
+ test_pass(i == INTMAX_MIN);
+
+ /* Test underflow. */
+ errno = 0;
+ i = tar_atosl("\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00", 12, INTMAX_MIN, INTMAX_MAX);
+ test_pass(i == INTMAX_MIN);
+ test_pass(errno == ERANGE);
+
+ errno = 0;
+ i = tar_atosl("\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 12, INTMAX_MIN, INTMAX_MAX);
+ test_pass(i == INTMAX_MIN);
+ test_pass(errno == ERANGE);
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(38);
+
+ test_tar_atol8();
+ test_tar_atol256();
+}
diff --git a/lib/dpkg/t/t-tarextract.t b/lib/dpkg/t/t-tarextract.t
new file mode 100755
index 0000000..5499cdc
--- /dev/null
+++ b/lib/dpkg/t/t-tarextract.t
@@ -0,0 +1,159 @@
+#!/usr/bin/perl
+#
+# Copyright © 2014 Guillem Jover <guillem@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use Test::More;
+use Cwd;
+use File::Path qw(make_path remove_tree);
+use File::Temp qw(tempdir);
+use File::Spec;
+use File::Find;
+use POSIX qw(mkfifo);
+
+use Dpkg ();
+use Dpkg::File;
+use Dpkg::IPC;
+
+use strict;
+use warnings;
+use version;
+
+my $srcdir = $ENV{srcdir} || '.';
+my $builddir = $ENV{builddir} || '.';
+my $tmpdir = 't.tmp/t-tarextract';
+
+# We require GNU tar >= 1.27 for --owner=NAME:ID and --group=NAME:ID.
+my $tar_version = qx($Dpkg::PROGTAR --version 2>/dev/null);
+if ($tar_version and $tar_version =~ m/^tar \(GNU tar\) (\d+\.\d+)/ and
+ qv("v$1") >= qv('v1.27'))
+{
+ plan tests => 12;
+} else {
+ plan skip_all => 'needs GNU tar >= 1.27';
+}
+
+# Set a known umask.
+umask 0022;
+
+sub tar_create_tree {
+ my $type = shift;
+
+ my $long_a = 'a' x 29;
+ my $long_b = 'b' x 29;
+ my $long_c = 'c' x 29;
+ my $long_d = 'd' x 29;
+ my $long_e = 'e' x 29;
+ my $long_f = 'f' x 22;
+
+ # Populate tar hierarchy
+ file_touch('file');
+ link 'file', 'hardlink';
+
+ make_path("$long_a/$long_b/$long_c/$long_d/$long_e/");
+ make_path("$long_a/$long_b/$long_c/$long_d/$long_e/$long_f/");
+ file_touch("$long_a/$long_b/$long_c/$long_d/$long_e/$long_f/long");
+
+ # POSIX specifies that symlinks have undefined permissions in their
+ # mode, so their handling is system dependent. Linux does not honor
+ # the umask for symlinks, other systems like GNU/Hurd or kFreeBSD do,
+ # which means we get different results due to this.
+ my $umask = umask 0;
+
+ symlink "$long_a/$long_b/$long_c/$long_d/$long_e/$long_f/long",
+ 'symlink-long';
+ symlink 'file', 'symlink-a';
+ symlink 'hardlink', 'symlink-b';
+ symlink 'dangling', 'symlink-c';
+
+ umask $umask;
+
+ mkdir 'directory';
+ mkfifo('fifo', 0770);
+
+ # TODO: Need root.
+ # system 'mknod', 'chardev', 'c', '1', '3';
+ # system 'mknod', 'blockdev', 'b', '0', '0';
+}
+
+sub test_tar_extractor {
+ my $stdout;
+ my $stderr;
+
+ my $expected_tar = <<'TAR';
+. mode=40755 time=100000000.000000000 uid=100 gid=200 uname=user gname=group type=dir
+./fifo mode=10750 time=100000000.000000000 uid=100 gid=200 uname=user gname=group type=fifo
+./file mode=100644 time=100000000.000000000 uid=100 gid=200 uname=user gname=group type=file size=0
+./hardlink mode=100644 time=100000000.000000000 uid=100 gid=200 uname=user gname=group type=hardlink linkto=./file size=0
+./aaaaaaaaaaaaaaaaaaaaaaaaaaaaa mode=40755 time=100000000.000000000 uid=100 gid=200 uname=user gname=group type=dir
+./aaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbb mode=40755 time=100000000.000000000 uid=100 gid=200 uname=user gname=group type=dir
+./aaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ccccccccccccccccccccccccccccc mode=40755 time=100000000.000000000 uid=100 gid=200 uname=user gname=group type=dir
+./aaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ccccccccccccccccccccccccccccc/ddddddddddddddddddddddddddddd mode=40755 time=100000000.000000000 uid=100 gid=200 uname=user gname=group type=dir
+./aaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ccccccccccccccccccccccccccccc/ddddddddddddddddddddddddddddd/eeeeeeeeeeeeeeeeeeeeeeeeeeeee mode=40755 time=100000000.000000000 uid=100 gid=200 uname=user gname=group type=dir
+./aaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ccccccccccccccccccccccccccccc/ddddddddddddddddddddddddddddd/eeeeeeeeeeeeeeeeeeeeeeeeeeeee/ffffffffffffffffffffff mode=40755 time=100000000.000000000 uid=100 gid=200 uname=user gname=group type=dir
+./aaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ccccccccccccccccccccccccccccc/ddddddddddddddddddddddddddddd/eeeeeeeeeeeeeeeeeeeeeeeeeeeee/ffffffffffffffffffffff/long mode=100644 time=100000000.000000000 uid=100 gid=200 uname=user gname=group type=file size=0
+./directory mode=40755 time=100000000.000000000 uid=100 gid=200 uname=user gname=group type=dir
+./symlink-a mode=120777 time=100000000.000000000 uid=100 gid=200 uname=user gname=group type=symlink linkto=file size=0
+./symlink-b mode=120777 time=100000000.000000000 uid=100 gid=200 uname=user gname=group type=symlink linkto=hardlink size=0
+./symlink-c mode=120777 time=100000000.000000000 uid=100 gid=200 uname=user gname=group type=symlink linkto=dangling size=0
+./symlink-long mode=120777 time=100000000.000000000 uid=100 gid=200 uname=user gname=group type=symlink linkto=aaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ccccccccccccccccccccccccccccc/ddddddddddddddddddddddddddddd/eeeeeeeeeeeeeeeeeeeeeeeeeeeee/ffffffffffffffffffffff/long size=0
+TAR
+
+ make_path($tmpdir);
+
+ my $cwd = getcwd();
+
+ # Check generated tarballs.
+ foreach my $type (qw(v7 ustar oldgnu gnu)) {
+ my $dirtree = "$tmpdir/$type";
+ my @paths;
+
+ mkdir $dirtree;
+ chdir $dirtree;
+ tar_create_tree($type);
+ find({ no_chdir => 1, wanted => sub {
+ return if $type eq 'v7' and length > 99;
+ return if $type eq 'v7' and -l and length readlink > 99;
+ return if $type eq 'v7' and not (-f or -l or -d);
+ return if $type eq 'ustar' and length > 256;
+ return if $type eq 'ustar' and -l and length readlink > 100;
+ push @paths, $_;
+ },
+ preprocess => sub { my (@files) = sort @_; @files } }, '.');
+ chdir $cwd;
+
+ my $paths_list = join "\0", @paths;
+ spawn(exec => [ $Dpkg::PROGTAR, '-cf', "$dirtree.tar",
+ '--format', $type,
+ '-C', $dirtree, '--mtime=@100000000',
+ '--owner=user:100', '--group=group:200',
+ '--null', '--no-unquote', '--no-recursion', '-T-' ],
+ wait_child => 1, from_string => \$paths_list);
+
+ my $expected = $expected_tar;
+ $expected =~ s/[ug]name=[^ ]+ //g if $type eq 'v7';
+ $expected =~ s/\n^.*fifo.*$//mg if $type eq 'v7';
+ $expected =~ s/\n^.*dddd.*$//mg if $type eq 'v7';
+ $expected =~ s/\n^.*symlink-long.*$//mg if $type eq 'ustar';
+
+ spawn(exec => [ "$builddir/t/c-tarextract", "$dirtree.tar" ],
+ nocheck => 1, to_string => \$stdout, to_error => \$stderr);
+ ok($? == 0, "tar extractor $type should succeed");
+ is($stderr, undef, "tar extractor $type stderr is empty");
+ is($stdout, $expected, "tar extractor $type is ok");
+ }
+}
+
+test_tar_extractor();
diff --git a/lib/dpkg/t/t-test-skip.c b/lib/dpkg/t/t-test-skip.c
new file mode 100644
index 0000000..972cdf1
--- /dev/null
+++ b/lib/dpkg/t/t-test-skip.c
@@ -0,0 +1,31 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-test-skip.c - test suite self tests, skip all
+ *
+ * Copyright © 2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/test.h>
+
+TEST_ENTRY(test)
+{
+ test_skip_all("ignore all tests");
+
+ test_fail(1);
+}
diff --git a/lib/dpkg/t/t-test.c b/lib/dpkg/t/t-test.c
new file mode 100644
index 0000000..48ce872
--- /dev/null
+++ b/lib/dpkg/t/t-test.c
@@ -0,0 +1,66 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-test.c - test suite self tests
+ *
+ * Copyright © 2009, 2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/test.h>
+
+TEST_ENTRY(test)
+{
+ test_plan(22);
+
+ test_pass(1);
+ test_fail(0);
+
+ test_skip("ignore test");
+
+ test_skip_block(1) {
+ test_pass(0);
+ test_pass(1);
+ }
+
+ test_todo(0, "unimplemented test", "failing test");
+
+ test_todo_block("unimplemented block") {
+ test_pass(0);
+ test_fail(1);
+ }
+
+ test_str("aaa", ==, "aaa");
+ test_str("aaa", <, "bbb");
+ test_str("ccc", >, "bbb");
+ test_str("ccc", !=, "bbb");
+
+ test_mem("aaa", ==, "aaa", 3);
+ test_mem("aaa", <, "bbb", 3);
+ test_mem("ccc", >, "bbb", 3);
+ test_mem("ccc", !=, "bbb", 3);
+
+ test_mem("abcd", ==, "abcd", 4);
+ test_mem("abcd", ==, "abcd", 5);
+ test_mem("ababcd", ==, "ababff", 4);
+ test_mem("ababcd", !=, "ababff", 6);
+
+ setenv("srcdir", "aaa", 1);
+ setenv("builddir", "bbb", 1);
+ test_str(test_get_srcdir(), ==, "aaa");
+ test_str(test_get_builddir(), ==, "bbb");
+}
diff --git a/lib/dpkg/t/t-treewalk.t b/lib/dpkg/t/t-treewalk.t
new file mode 100755
index 0000000..573103d
--- /dev/null
+++ b/lib/dpkg/t/t-treewalk.t
@@ -0,0 +1,154 @@
+#!/usr/bin/perl
+#
+# Copyright © 2016 Guillem Jover <guillem@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use strict;
+use warnings;
+use version;
+
+use Test::More tests => 6;
+use Cwd;
+use File::Path qw(make_path remove_tree);
+use File::Temp qw(tempdir);
+use File::Basename;
+use File::Find;
+
+use Dpkg::File;
+use Dpkg::IPC;
+
+my $srcdir = $ENV{srcdir} || '.';
+my $builddir = $ENV{builddir} || '.';
+my $tmpdir = 't.tmp/t-treewalk';
+
+# Set a known umask.
+umask 0022;
+
+# Populate the tree hierarchy.
+sub make_tree {
+ my ($dirtree) = @_;
+ my $cwd = getcwd();
+
+ make_path($dirtree);
+ chdir $dirtree;
+
+ # Deep tree.
+ make_path('aaaa/aaaa/aaaa/aaaa/');
+ file_touch('aaaa/aaaa/aaaa/aaaa/abcde');
+ file_touch('aaaa/aaaa/aaaa/aaaa/ddddd');
+ file_touch('aaaa/aaaa/aaaa/aaaa/wwwwa');
+ file_touch('aaaa/aaaa/aaaa/aaaa/wwwwz');
+ file_touch('aaaa/aaaa/aaaa/aaaa/zzzzz');
+
+ # Shallow tree.
+ make_path('bbbb/');
+ file_touch('bbbb/abcde');
+ file_touch('bbbb/ddddd');
+ file_touch('bbbb/wwwwa');
+ file_touch('bbbb/wwwwz');
+ file_touch('bbbb/zzzzz');
+
+ # Populated tree.
+ make_path('cccc/aa/aa/aa/');
+ make_path('cccc/aa/aa/bb/aa/');
+ file_touch('cccc/aa/aa/bb/aa/file-a');
+ file_touch('cccc/aa/aa/bb/aa/file-z');
+ make_path('cccc/aa/bb/');
+ make_path('cccc/bb/aa/');
+ make_path('cccc/bb/bb/aa/aa/');
+ file_touch('cccc/bb/bb/aa/aa/file-a');
+ file_touch('cccc/bb/bb/aa/aa/file-z');
+ make_path('cccc/bb/bb/bb/');
+ file_touch('cccc/bb/bb/bb/file-w');
+ make_path('cccc/cc/aa/');
+ make_path('cccc/cc/bb/aa/');
+ file_touch('cccc/cc/bb/aa/file-t');
+ make_path('cccc/cc/bb/bb/');
+ file_touch('cccc/cc/bb/bb/file-x');
+ make_path('cccc/cc/cc/');
+ make_path('cccc/dd/aa/aa/aa/');
+ file_touch('cccc/dd/aa/aa/aa/file-y');
+ make_path('cccc/dd/aa/aa/bb/');
+ file_touch('cccc/dd/aa/aa/bb/file-o');
+ make_path('cccc/dd/aa/bb/aa/');
+ file_touch('cccc/dd/aa/bb/aa/file-k');
+ make_path('cccc/dd/aa/bb/bb/');
+ file_touch('cccc/dd/aa/bb/bb/file-l');
+ make_path('cccc/dd/aa/cc/aa/');
+ file_touch('cccc/dd/aa/cc/aa/file-s');
+ make_path('cccc/dd/aa/cc/bb/');
+ file_touch('cccc/dd/aa/cc/bb/file-u');
+
+ # Tree with symlinks cycles.
+ make_path('llll/self/');
+ file_touch('llll/file');
+ symlink '..', 'llll/self/loop';
+ make_path('llll/real/');
+ file_touch('llll/real/file-r');
+ symlink '../virt', 'llll/real/loop';
+ make_path('llll/virt/');
+ file_touch('llll/virt/file-v');
+ symlink '../real', 'llll/virt/loop';
+
+ chdir $cwd;
+}
+
+sub test_treewalker {
+ my $stdout;
+ my $stderr;
+ my $dirtree = $tmpdir;
+
+ # Check generated tarballs.
+ foreach my $type (qw(full skip)) {
+ my @paths;
+
+ make_tree($dirtree);
+
+ find({ no_chdir => 1, wanted => sub {
+ return if $type eq 'skip' and m{^\Q$dirtree\E/cccc};
+ push @paths, s{\./}{}r;
+ },
+ }, $dirtree);
+
+ my $expected;
+
+ foreach my $path (sort @paths) {
+ lstat $path;
+
+ my $ptype;
+ if (-f _) {
+ $ptype = 'f';
+ } elsif (-l _) {
+ $ptype = 'l';
+ } elsif (-d _) {
+ $ptype = 'd';
+ }
+ my $pname = basename($path);
+ my $pvirt = $path =~ s{\Q$dirtree\E/?}{}r;
+
+ $expected .= "T=$ptype N=$pname V=$pvirt R=$path\n";
+ }
+
+ $ENV{TREEWALK_SKIP} = $type eq 'skip' ? "$dirtree/cccc" : undef;
+
+ spawn(exec => [ "$builddir/t/c-treewalk", $dirtree ],
+ nocheck => 1, to_string => \$stdout, to_error => \$stderr);
+ ok($? == 0, "tree walker $type should succeed");
+ is($stderr, undef, "tree walker $type stderr is empty");
+ is($stdout, $expected, "tree walker $type is ok");
+ }
+}
+
+test_treewalker();
diff --git a/lib/dpkg/t/t-trigdeferred.t b/lib/dpkg/t/t-trigdeferred.t
new file mode 100755
index 0000000..bf73541
--- /dev/null
+++ b/lib/dpkg/t/t-trigdeferred.t
@@ -0,0 +1,116 @@
+#!/usr/bin/perl
+#
+# Copyright © 2016 Guillem Jover <guillem@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use strict;
+use warnings;
+use version;
+
+use Test::More;
+use Cwd;
+use File::Path qw(make_path remove_tree);
+use File::Temp qw(tempdir);
+use File::Basename;
+use File::Find;
+
+use Dpkg::File;
+use Dpkg::IPC;
+
+my $srcdir = $ENV{srcdir} || '.';
+my $builddir = $ENV{builddir} || '.';
+my $tmpdir = 't.tmp/t-trigdeferred';
+
+my @deferred = (
+ {
+ exitcode => 0,
+ original => <<'TEXT',
+
+ # Comment
+ # Another Comment
+ /root/pathname/file-trigger pkg-a pkg-b pkg-c
+named-trigger pkg-1 pkg-2 pkg-3
+parse-trigger pkg:a pkg+b pkg.0 0-pkg
+:other-trigger -
+TEXT
+ expected => <<'TEXT',
+<T='/root/pathname/file-trigger' P='pkg-a' P='pkg-b' P='pkg-c' E>
+<T='named-trigger' P='pkg-1' P='pkg-2' P='pkg-3' E>
+<T='parse-trigger' P='pkg:a' P='pkg+b' P='pkg.0' P='0-pkg' E>
+<T=':other-trigger' P='-' E>
+TEXT
+ }, {
+ exitcode => 2,
+ original => <<"TEXT",
+\b # invalid character
+TEXT
+ }, {
+ exitcode => 2,
+ original => <<'TEXT',
+trigger -pkg
+TEXT
+ }, {
+ exitcode => 2,
+ original => <<'TEXT',
+trigger +pkg
+TEXT
+ }, {
+ exitcode => 2,
+ original => <<'TEXT',
+trigger .pkg
+TEXT
+ }, {
+ exitcode => 2,
+ original => <<'TEXT',
+trigger :pkg
+TEXT
+ }, {
+ exitcode => 2,
+ original => 'missing newline',
+ }
+);
+
+plan tests => scalar(@deferred) * 3;
+
+# Set a known umask.
+umask 0022;
+
+sub test_trigdeferred {
+ my $stdout;
+ my $stderr;
+ my $admindir = "$tmpdir";
+
+ # Check triggers deferred file parsing.
+ make_path("$admindir/triggers");
+
+ foreach my $test (@deferred) {
+ file_dump("$admindir/triggers/Unincorp", $test->{original});
+
+ spawn(exec => [ "$builddir/t/c-trigdeferred", $admindir ],
+ nocheck => 1, to_string => \$stdout, error_to_string => \$stderr);
+ my $exitcode = $? >> 8;
+
+ is($exitcode, $test->{exitcode}, 'trigger deferred expected exitcode');
+ if ($test->{exitcode} == 0) {
+ is($stderr, '', 'trigger deferred expected stderr');
+ is($stdout, $test->{expected}, 'trigger deferred expected stdout');
+ } else {
+ isnt($stderr, '', 'trigger deferred expected stderr');
+ isnt($stdout, undef, 'trigger deferred expected stdout');
+ }
+ }
+}
+
+test_trigdeferred();
diff --git a/lib/dpkg/t/t-trigger.c b/lib/dpkg/t/t-trigger.c
new file mode 100644
index 0000000..af78f23
--- /dev/null
+++ b/lib/dpkg/t/t-trigger.c
@@ -0,0 +1,49 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-trigger.c - test triggers
+ *
+ * Copyright © 2012 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/test.h>
+#include <dpkg/triglib.h>
+
+static void
+test_trig_name_is_illegal(void)
+{
+ /* Test invalid trigger names. */
+ test_fail(trig_name_is_illegal("") == NULL);
+ test_fail(trig_name_is_illegal("\a") == NULL);
+ test_fail(trig_name_is_illegal("\t") == NULL);
+ test_fail(trig_name_is_illegal("\200") == NULL);
+ test_fail(trig_name_is_illegal("trigger name") == NULL);
+
+ /* Test valid trigger names. */
+ test_pass(trig_name_is_illegal("TRIGGER") == NULL);
+ test_pass(trig_name_is_illegal("trigger") == NULL);
+ test_pass(trig_name_is_illegal("0123456789") == NULL);
+ test_pass(trig_name_is_illegal("/file/trigger") == NULL);
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(9);
+
+ test_trig_name_is_illegal();
+}
diff --git a/lib/dpkg/t/t-varbuf.c b/lib/dpkg/t/t-varbuf.c
new file mode 100644
index 0000000..08998a5
--- /dev/null
+++ b/lib/dpkg/t/t-varbuf.c
@@ -0,0 +1,560 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-varbuf.c - test varbuf implementation
+ *
+ * Copyright © 2009-2011, 2013-2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/test.h>
+#include <dpkg/varbuf.h>
+
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+static void
+test_varbuf_init(void)
+{
+ struct varbuf vb;
+
+ varbuf_init(&vb, 0);
+ test_pass(vb.used == 0);
+ test_pass(vb.size == 0);
+ test_pass(vb.buf == NULL);
+
+ varbuf_destroy(&vb);
+ test_pass(vb.used == 0);
+ test_pass(vb.size == 0);
+ test_pass(vb.buf == NULL);
+}
+
+static void
+test_varbuf_prealloc(void)
+{
+ struct varbuf vb;
+
+ varbuf_init(&vb, 10);
+ test_pass(vb.used == 0);
+ test_pass(vb.size >= 10);
+ test_pass(vb.buf != NULL);
+
+ varbuf_destroy(&vb);
+ test_pass(vb.used == 0);
+ test_pass(vb.size == 0);
+ test_pass(vb.buf == NULL);
+}
+
+static void
+test_varbuf_new(void)
+{
+ struct varbuf *vb;
+
+ vb = varbuf_new(0);
+ test_pass(vb != NULL);
+ test_pass(vb->used == 0);
+ test_pass(vb->size == 0);
+ test_pass(vb->buf == NULL);
+ varbuf_free(vb);
+
+ vb = varbuf_new(10);
+ test_pass(vb != NULL);
+ test_pass(vb->used == 0);
+ test_pass(vb->size >= 10);
+ test_pass(vb->buf != NULL);
+ varbuf_free(vb);
+}
+
+static void
+test_varbuf_grow(void)
+{
+ struct varbuf vb;
+ jmp_buf grow_jump;
+ size_t old_size;
+ bool grow_overflow;
+ int i;
+
+ varbuf_init(&vb, 10);
+
+ /* Test that we grow when needed. */
+ varbuf_grow(&vb, 100);
+ test_pass(vb.used == 0);
+ test_pass(vb.size >= 100);
+
+ old_size = vb.size;
+
+ /* Test that we are not leaking. */
+ for (i = 0; i < 10; i++) {
+ varbuf_grow(&vb, 100);
+ test_pass(vb.used == 0);
+ test_pass(vb.size >= 100);
+ test_pass(vb.size == old_size);
+ }
+
+ /* Test that we grow when needed, with used space. */
+ vb.used = 10;
+ varbuf_grow(&vb, 100);
+ test_pass(vb.used == 10);
+ test_pass(vb.size >= 110);
+
+ /* Test that we do not allow allocation overflows. */
+ grow_overflow = false;
+ old_size = vb.size;
+ test_try(grow_jump) {
+ varbuf_grow(&vb, SIZE_MAX - vb.size + 2);
+ } test_catch {
+ grow_overflow = true;
+ } test_finally;
+ test_pass(vb.size == old_size && grow_overflow);
+
+ grow_overflow = false;
+ old_size = vb.size;
+ test_try(grow_jump) {
+ varbuf_grow(&vb, (SIZE_MAX - vb.size - 2) / 2);
+ } test_catch {
+ grow_overflow = true;
+ } test_finally;
+ test_pass(vb.size == old_size && grow_overflow);
+
+ varbuf_destroy(&vb);
+}
+
+static void
+test_varbuf_trunc(void)
+{
+ struct varbuf vb;
+
+ varbuf_init(&vb, 50);
+
+ /* Test that we truncate (grow). */
+ varbuf_trunc(&vb, 20);
+ test_pass(vb.used == 20);
+ test_pass(vb.size >= 50);
+
+ /* Test that we truncate (shrink). */
+ varbuf_trunc(&vb, 10);
+ test_pass(vb.used == 10);
+ test_pass(vb.size >= 50);
+
+ varbuf_destroy(&vb);
+}
+
+static void
+test_varbuf_set(void)
+{
+ struct varbuf vb, cb;
+
+ varbuf_init(&vb, 10);
+ varbuf_init(&cb, 10);
+
+ varbuf_set_buf(&vb, "1234567890", 5);
+ test_pass(vb.used == 5);
+ test_mem(vb.buf, ==, "12345", 5);
+
+ varbuf_set_buf(&vb, "abcd", 4);
+ test_pass(vb.used == 4);
+ test_mem(vb.buf, ==, "abcd", 4);
+
+ varbuf_set_varbuf(&cb, &vb);
+ test_pass(cb.used == 4);
+ test_mem(cb.buf, ==, "abcd", 4);
+
+ varbuf_set_str(&vb, "12345");
+ test_pass(vb.used == 5);
+ test_str(vb.buf, ==, "12345");
+
+ varbuf_set_strn(&vb, "1234567890", 8);
+ test_pass(vb.used == 8);
+ test_str(vb.buf, ==, "12345678");
+
+ varbuf_destroy(&cb);
+ varbuf_destroy(&vb);
+}
+
+static void
+test_varbuf_add_varbuf(void)
+{
+ struct varbuf vb, cb;
+
+ varbuf_init(&vb, 5);
+ varbuf_init(&cb, 0);
+
+ varbuf_set_str(&vb, "1234567890");
+ varbuf_add_varbuf(&cb, &vb);
+ test_pass(cb.used == 10);
+ test_pass(cb.size >= cb.used);
+ test_mem(cb.buf, ==, "1234567890", 10);
+
+ varbuf_set_str(&vb, "abcde");
+ varbuf_add_varbuf(&cb, &vb);
+ test_pass(cb.used == 15);
+ test_pass(cb.size >= cb.used);
+ test_mem(cb.buf, ==, "1234567890abcde", 15);
+
+ varbuf_destroy(&cb);
+ varbuf_destroy(&vb);
+}
+
+static void
+test_varbuf_add_buf(void)
+{
+ struct varbuf vb;
+
+ varbuf_init(&vb, 5);
+
+ varbuf_add_buf(&vb, "1234567890", 10);
+ test_pass(vb.used == 10);
+ test_pass(vb.size >= vb.used);
+ test_mem(vb.buf, ==, "1234567890", 10);
+
+ varbuf_add_buf(&vb, "abcde", 5);
+ test_pass(vb.used == 15);
+ test_pass(vb.size >= vb.used);
+ test_mem(vb.buf, ==, "1234567890abcde", 15);
+
+ varbuf_destroy(&vb);
+}
+
+static void
+test_varbuf_add_str(void)
+{
+ struct varbuf vb;
+
+ varbuf_init(&vb, 5);
+
+ varbuf_add_str(&vb, "1234567890");
+ varbuf_end_str(&vb);
+ test_str(vb.buf, ==, "1234567890");
+
+ varbuf_add_str(&vb, "abcd");
+ varbuf_end_str(&vb);
+ test_str(vb.buf, ==, "1234567890abcd");
+
+ varbuf_add_strn(&vb, "1234567890", 5);
+ varbuf_end_str(&vb);
+ test_str(vb.buf, ==, "1234567890abcd12345");
+
+ varbuf_add_strn(&vb, "abcd", 0);
+ varbuf_end_str(&vb);
+ test_str(vb.buf, ==, "1234567890abcd12345");
+
+ varbuf_destroy(&vb);
+}
+
+static void
+test_varbuf_add_char(void)
+{
+ struct varbuf vb;
+
+ varbuf_init(&vb, 1);
+
+ varbuf_add_char(&vb, 'a');
+ test_pass(vb.used == 1);
+ test_pass(vb.size >= vb.used);
+ test_pass(vb.buf[0] == 'a');
+
+ varbuf_add_char(&vb, 'b');
+ test_pass(vb.used == 2);
+ test_pass(vb.size >= vb.used);
+ test_mem(vb.buf, ==, "ab", 2);
+
+ varbuf_add_char(&vb, 'c');
+ test_pass(vb.used == 3);
+ test_pass(vb.size >= vb.used);
+ test_mem(vb.buf, ==, "abc", 3);
+
+ varbuf_add_char(&vb, 'd');
+ test_pass(vb.used == 4);
+ test_pass(vb.size >= vb.used);
+ test_mem(vb.buf, ==, "abcd", 4);
+
+ varbuf_destroy(&vb);
+}
+
+static void
+test_varbuf_dup_char(void)
+{
+ struct varbuf vb;
+
+ varbuf_init(&vb, 5);
+
+ varbuf_dup_char(&vb, 'z', 10);
+ test_pass(vb.used == 10);
+ test_pass(vb.size >= vb.used);
+ test_mem(vb.buf, ==, "zzzzzzzzzz", 10);
+
+ varbuf_dup_char(&vb, 'y', 5);
+ test_pass(vb.used == 15);
+ test_pass(vb.size >= vb.used);
+ test_mem(vb.buf, ==, "zzzzzzzzzzyyyyy", 15);
+
+ varbuf_destroy(&vb);
+}
+
+static void
+test_varbuf_map_char(void)
+{
+ struct varbuf vb;
+
+ varbuf_init(&vb, 5);
+
+ varbuf_add_buf(&vb, "1234a5678a9012a", 15);
+
+ varbuf_map_char(&vb, 'a', 'z');
+ test_pass(vb.used == 15);
+ test_pass(vb.size >= vb.used);
+ test_mem(vb.buf, ==, "1234z5678z9012z", 15);
+
+ varbuf_destroy(&vb);
+}
+
+static void
+test_varbuf_add_dir(void)
+{
+ struct varbuf vb;
+
+ varbuf_init(&vb, 10);
+
+ varbuf_add_dir(&vb, "");
+ varbuf_end_str(&vb);
+ test_str(vb.buf, ==, "/");
+ varbuf_add_dir(&vb, "");
+ varbuf_end_str(&vb);
+ test_str(vb.buf, ==, "/");
+ varbuf_add_dir(&vb, "aa");
+ varbuf_end_str(&vb);
+ test_str(vb.buf, ==, "/aa/");
+ varbuf_add_dir(&vb, "");
+ varbuf_end_str(&vb);
+ test_str(vb.buf, ==, "/aa/");
+
+ varbuf_reset(&vb);
+
+ varbuf_add_dir(&vb, "/foo/bar");
+ varbuf_end_str(&vb);
+ test_str(vb.buf, ==, "/foo/bar/");
+
+ varbuf_reset(&vb);
+
+ varbuf_add_dir(&vb, "/foo/bar/");
+ varbuf_end_str(&vb);
+ test_str(vb.buf, ==, "/foo/bar/");
+ varbuf_add_dir(&vb, "quux");
+ varbuf_end_str(&vb);
+ test_str(vb.buf, ==, "/foo/bar/quux/");
+ varbuf_add_dir(&vb, "zoo");
+ varbuf_end_str(&vb);
+ test_str(vb.buf, ==, "/foo/bar/quux/zoo/");
+
+ varbuf_destroy(&vb);
+}
+
+static void
+test_varbuf_end_str(void)
+{
+ struct varbuf vb;
+
+ varbuf_init(&vb, 10);
+
+ varbuf_add_buf(&vb, "1234567890X", 11);
+ test_pass(vb.used == 11);
+ test_pass(vb.size >= vb.used);
+ test_mem(vb.buf, ==, "1234567890X", 11);
+
+ varbuf_trunc(&vb, 10);
+
+ varbuf_end_str(&vb);
+ test_pass(vb.used == 10);
+ test_pass(vb.size >= vb.used + 1);
+ test_pass(vb.buf[10] == '\0');
+ test_str(vb.buf, ==, "1234567890");
+
+ varbuf_destroy(&vb);
+}
+
+static void
+test_varbuf_get_str(void)
+{
+ struct varbuf vb;
+ const char *str;
+
+ varbuf_init(&vb, 10);
+
+ varbuf_add_buf(&vb, "1234567890", 10);
+ str = varbuf_get_str(&vb);
+ test_pass(vb.buf == str);
+ test_pass(vb.used == 10);
+ test_pass(vb.buf[vb.used] == '\0');
+ test_pass(str[vb.used] == '\0');
+ test_str(vb.buf, ==, "1234567890");
+ test_str(str, ==, "1234567890");
+
+ varbuf_add_buf(&vb, "abcde", 5);
+ str = varbuf_get_str(&vb);
+ test_pass(vb.buf == str);
+ test_pass(vb.used == 15);
+ test_pass(vb.buf[vb.used] == '\0');
+ test_pass(str[vb.used] == '\0');
+ test_str(vb.buf, ==, "1234567890abcde");
+ test_str(str, ==, "1234567890abcde");
+
+ varbuf_destroy(&vb);
+}
+
+static void
+test_varbuf_printf(void)
+{
+ struct varbuf vb;
+
+ varbuf_init(&vb, 5);
+
+ /* Test normal format printing. */
+ varbuf_printf(&vb, "format %s number %d", "string", 10);
+ test_pass(vb.used == strlen("format string number 10"));
+ test_pass(vb.size >= vb.used);
+ test_str(vb.buf, ==, "format string number 10");
+
+ varbuf_reset(&vb);
+
+ /* Test concatenated format printing. */
+ varbuf_printf(&vb, "format %s number %d", "string", 10);
+ varbuf_printf(&vb, " extra %s", "string");
+ test_pass(vb.used == strlen("format string number 10 extra string"));
+ test_pass(vb.size >= vb.used);
+ test_str(vb.buf, ==, "format string number 10 extra string");
+
+ varbuf_destroy(&vb);
+}
+
+static void
+test_varbuf_reset(void)
+{
+ struct varbuf vb;
+
+ varbuf_init(&vb, 10);
+
+ varbuf_add_buf(&vb, "1234567890", 10);
+
+ varbuf_reset(&vb);
+ test_pass(vb.used == 0);
+ test_pass(vb.size >= vb.used);
+
+ varbuf_add_buf(&vb, "abcdefghijklmno", 15);
+ test_pass(vb.used == 15);
+ test_pass(vb.size >= vb.used);
+ test_mem(vb.buf, ==, "abcdefghijklmno", 15);
+
+ varbuf_destroy(&vb);
+}
+
+static void
+test_varbuf_snapshot(void)
+{
+ struct varbuf vb;
+ struct varbuf_state vbs;
+
+ varbuf_init(&vb, 0);
+
+ test_pass(vb.used == 0);
+ varbuf_snapshot(&vb, &vbs);
+ test_pass(vb.used == 0);
+ test_pass(vb.used == vbs.used);
+ test_pass(varbuf_rollback_len(&vbs) == 0);
+ test_str(varbuf_rollback_start(&vbs), ==, "");
+
+ varbuf_add_buf(&vb, "1234567890", 10);
+ varbuf_end_str(&vb);
+ test_pass(vb.used == 10);
+ test_pass(varbuf_rollback_len(&vbs) == 10);
+ test_str(varbuf_rollback_start(&vbs), ==, "1234567890");
+ varbuf_rollback(&vbs);
+ varbuf_end_str(&vb);
+ test_pass(vb.used == 0);
+ test_pass(varbuf_rollback_len(&vbs) == 0);
+ test_str(varbuf_rollback_start(&vbs), ==, "");
+
+ varbuf_add_buf(&vb, "1234567890", 10);
+ varbuf_end_str(&vb);
+ test_pass(vb.used == 10);
+ test_pass(varbuf_rollback_len(&vbs) == 10);
+ test_str(varbuf_rollback_start(&vbs), ==, "1234567890");
+ varbuf_snapshot(&vb, &vbs);
+ test_pass(vb.used == 10);
+ test_pass(varbuf_rollback_len(&vbs) == 0);
+ test_str(varbuf_rollback_start(&vbs), ==, "");
+
+ varbuf_add_buf(&vb, "1234567890", 10);
+ varbuf_end_str(&vb);
+ test_pass(vb.used == 20);
+ test_pass(varbuf_rollback_len(&vbs) == 10);
+ test_str(varbuf_rollback_start(&vbs), ==, "1234567890");
+ varbuf_rollback(&vbs);
+ varbuf_end_str(&vb);
+ test_pass(vb.used == 10);
+ test_pass(varbuf_rollback_len(&vbs) == 0);
+ test_str(varbuf_rollback_start(&vbs), ==, "");
+
+ varbuf_destroy(&vb);
+}
+
+static void
+test_varbuf_detach(void)
+{
+ struct varbuf vb;
+ char *str;
+
+ varbuf_init(&vb, 0);
+
+ varbuf_add_buf(&vb, "1234567890", 10);
+
+ str = varbuf_detach(&vb);
+
+ test_mem(str, ==, "1234567890", 10);
+ test_pass(vb.used == 0);
+ test_pass(vb.size == 0);
+ test_pass(vb.buf == NULL);
+
+ free(str);
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(172);
+
+ test_varbuf_init();
+ test_varbuf_prealloc();
+ test_varbuf_new();
+ test_varbuf_grow();
+ test_varbuf_trunc();
+ test_varbuf_set();
+ test_varbuf_add_varbuf();
+ test_varbuf_add_buf();
+ test_varbuf_add_str();
+ test_varbuf_add_char();
+ test_varbuf_dup_char();
+ test_varbuf_map_char();
+ test_varbuf_add_dir();
+ test_varbuf_end_str();
+ test_varbuf_get_str();
+ test_varbuf_printf();
+ test_varbuf_reset();
+ test_varbuf_snapshot();
+ test_varbuf_detach();
+
+ /* TODO: Complete. */
+}
diff --git a/lib/dpkg/t/t-version.c b/lib/dpkg/t/t-version.c
new file mode 100644
index 0000000..6a771ca
--- /dev/null
+++ b/lib/dpkg/t/t-version.c
@@ -0,0 +1,308 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-version.c - test version handling
+ *
+ * Copyright © 2009-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <stdlib.h>
+
+#include <dpkg/test.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+
+static void
+test_version_blank(void)
+{
+ struct dpkg_version a;
+
+ dpkg_version_blank(&a);
+ test_pass(a.epoch == 0);
+ test_pass(a.version == NULL);
+ test_pass(a.revision == NULL);
+}
+
+static void
+test_version_is_informative(void)
+{
+ struct dpkg_version a;
+
+ dpkg_version_blank(&a);
+ test_fail(dpkg_version_is_informative(&a));
+
+ a.epoch = 1;
+ test_pass(dpkg_version_is_informative(&a));
+
+ dpkg_version_blank(&a);
+ a.version = "1";
+ test_pass(dpkg_version_is_informative(&a));
+
+ dpkg_version_blank(&a);
+ a.revision = "1";
+ test_pass(dpkg_version_is_informative(&a));
+}
+
+static void
+test_version_compare(void)
+{
+ struct dpkg_version a, b;
+
+ dpkg_version_blank(&a);
+ dpkg_version_blank(&b);
+ test_pass(dpkg_version_compare(&a, &b) == 0);
+
+ a.epoch = 1;
+ b.epoch = 2;
+ test_fail(dpkg_version_compare(&a, &b) == 0);
+
+ a = DPKG_VERSION_OBJECT(0, "1", "1");
+ b = DPKG_VERSION_OBJECT(0, "2", "1");
+ test_fail(dpkg_version_compare(&a, &b) == 0);
+
+ a = DPKG_VERSION_OBJECT(0, "1", "1");
+ b = DPKG_VERSION_OBJECT(0, "1", "2");
+ test_fail(dpkg_version_compare(&a, &b) == 0);
+
+ /* Test for version equality. */
+ a = b = DPKG_VERSION_OBJECT(0, "0", "0");
+ test_pass(dpkg_version_compare(&a, &b) == 0);
+
+ a = DPKG_VERSION_OBJECT(0, "0", "00");
+ b = DPKG_VERSION_OBJECT(0, "00", "0");
+ test_pass(dpkg_version_compare(&a, &b) == 0);
+
+ a = b = DPKG_VERSION_OBJECT(1, "2", "3");
+ test_pass(dpkg_version_compare(&a, &b) == 0);
+
+ /* Test for epoch difference. */
+ a = DPKG_VERSION_OBJECT(0, "0", "0");
+ b = DPKG_VERSION_OBJECT(1, "0", "0");
+ test_pass(dpkg_version_compare(&a, &b) < 0);
+ test_pass(dpkg_version_compare(&b, &a) > 0);
+
+ /* Test for version component difference. */
+ a = DPKG_VERSION_OBJECT(0, "a", "0");
+ b = DPKG_VERSION_OBJECT(0, "b", "0");
+ test_pass(dpkg_version_compare(&a, &b) < 0);
+ test_pass(dpkg_version_compare(&b, &a) > 0);
+
+ /* Test for revision component difference. */
+ a = DPKG_VERSION_OBJECT(0, "0", "a");
+ b = DPKG_VERSION_OBJECT(0, "0", "b");
+ test_pass(dpkg_version_compare(&a, &b) < 0);
+ test_pass(dpkg_version_compare(&b, &a) > 0);
+
+ /* TODO: Complete. */
+}
+
+static void
+test_version_relate(void)
+{
+ struct dpkg_version a, b;
+
+ dpkg_version_blank(&a);
+ dpkg_version_blank(&b);
+ test_pass(dpkg_version_relate(&a, DPKG_RELATION_NONE, &b));
+
+ a = DPKG_VERSION_OBJECT(0, "1", "1");
+ b = DPKG_VERSION_OBJECT(0, "1", "1");
+ test_pass(dpkg_version_relate(&a, DPKG_RELATION_EQ, &b));
+ test_fail(dpkg_version_relate(&a, DPKG_RELATION_LT, &b));
+ test_pass(dpkg_version_relate(&a, DPKG_RELATION_LE, &b));
+ test_fail(dpkg_version_relate(&a, DPKG_RELATION_GT, &b));
+ test_pass(dpkg_version_relate(&a, DPKG_RELATION_GE, &b));
+
+ a = DPKG_VERSION_OBJECT(0, "1", "1");
+ b = DPKG_VERSION_OBJECT(0, "2", "1");
+ test_fail(dpkg_version_relate(&a, DPKG_RELATION_EQ, &b));
+ test_pass(dpkg_version_relate(&a, DPKG_RELATION_LT, &b));
+ test_pass(dpkg_version_relate(&a, DPKG_RELATION_LE, &b));
+ test_fail(dpkg_version_relate(&a, DPKG_RELATION_GT, &b));
+ test_fail(dpkg_version_relate(&a, DPKG_RELATION_GE, &b));
+
+ a = DPKG_VERSION_OBJECT(0, "2", "1");
+ b = DPKG_VERSION_OBJECT(0, "1", "1");
+ test_fail(dpkg_version_relate(&a, DPKG_RELATION_EQ, &b));
+ test_fail(dpkg_version_relate(&a, DPKG_RELATION_LT, &b));
+ test_fail(dpkg_version_relate(&a, DPKG_RELATION_LE, &b));
+ test_pass(dpkg_version_relate(&a, DPKG_RELATION_GT, &b));
+ test_pass(dpkg_version_relate(&a, DPKG_RELATION_GE, &b));
+}
+
+static void
+test_version_parse(void)
+{
+ struct dpkg_error err;
+ struct dpkg_version a, b;
+ const char *p;
+ char *verstr;
+
+ /* Test 0 versions. */
+ dpkg_version_blank(&a);
+ b = DPKG_VERSION_OBJECT(0, "0", "");
+
+ test_pass(parseversion(&a, "0", NULL) == 0);
+ test_pass(dpkg_version_compare(&a, &b) == 0);
+
+ test_pass(parseversion(&a, "0:0", NULL) == 0);
+ test_pass(dpkg_version_compare(&a, &b) == 0);
+
+ b = DPKG_VERSION_OBJECT(0, "0", "0");
+ test_pass(parseversion(&a, "0:0-0", NULL) == 0);
+ test_pass(dpkg_version_compare(&a, &b) == 0);
+
+ b = DPKG_VERSION_OBJECT(0, "0.0", "0.0");
+ test_pass(parseversion(&a, "0:0.0-0.0", NULL) == 0);
+ test_pass(dpkg_version_compare(&a, &b) == 0);
+
+ /* Test epoched versions. */
+ b = DPKG_VERSION_OBJECT(1, "0", "");
+ test_pass(parseversion(&a, "1:0", NULL) == 0);
+ test_pass(dpkg_version_compare(&a, &b) == 0);
+
+ b = DPKG_VERSION_OBJECT(5, "1", "");
+ test_pass(parseversion(&a, "5:1", NULL) == 0);
+ test_pass(dpkg_version_compare(&a, &b) == 0);
+
+ /* Test multiple hyphens. */
+ b = DPKG_VERSION_OBJECT(0, "0-0", "0");
+ test_pass(parseversion(&a, "0:0-0-0", NULL) == 0);
+ test_pass(dpkg_version_compare(&a, &b) == 0);
+
+ b = DPKG_VERSION_OBJECT(0, "0-0-0", "0");
+ test_pass(parseversion(&a, "0:0-0-0-0", NULL) == 0);
+ test_pass(dpkg_version_compare(&a, &b) == 0);
+
+ /* Test multiple colons. */
+ b = DPKG_VERSION_OBJECT(0, "0:0", "0");
+ test_pass(parseversion(&a, "0:0:0-0", NULL) == 0);
+ test_pass(dpkg_version_compare(&a, &b) == 0);
+
+ b = DPKG_VERSION_OBJECT(0, "0:0:0", "0");
+ test_pass(parseversion(&a, "0:0:0:0-0", NULL) == 0);
+ test_pass(dpkg_version_compare(&a, &b) == 0);
+
+ /* Test multiple hyphens and colons. */
+ b = DPKG_VERSION_OBJECT(0, "0:0-0", "0");
+ test_pass(parseversion(&a, "0:0:0-0-0", NULL) == 0);
+ test_pass(dpkg_version_compare(&a, &b) == 0);
+
+ b = DPKG_VERSION_OBJECT(0, "0-0:0", "0");
+ test_pass(parseversion(&a, "0:0-0:0-0", NULL) == 0);
+ test_pass(dpkg_version_compare(&a, &b) == 0);
+
+ /* Test valid characters in upstream version. */
+ b = DPKG_VERSION_OBJECT(0, "09azAZ.-+~:", "0");
+ test_pass(parseversion(&a, "0:09azAZ.-+~:-0", NULL) == 0);
+ test_pass(dpkg_version_compare(&a, &b) == 0);
+
+ /* Test valid characters in revision. */
+ b = DPKG_VERSION_OBJECT(0, "0", "azAZ09.+~");
+ test_pass(parseversion(&a, "0:0-azAZ09.+~", NULL) == 0);
+ test_pass(dpkg_version_compare(&a, &b) == 0);
+
+ /* Test version with leading and trailing spaces. */
+ b = DPKG_VERSION_OBJECT(0, "0", "1");
+ test_pass(parseversion(&a, " 0:0-1", &err) == 0);
+ test_pass(dpkg_version_compare(&a, &b) == 0);
+ test_pass(parseversion(&a, "0:0-1 ", &err) == 0);
+ test_pass(dpkg_version_compare(&a, &b) == 0);
+ test_pass(parseversion(&a, " 0:0-1 ", &err) == 0);
+ test_pass(dpkg_version_compare(&a, &b) == 0);
+
+ /* Test empty version. */
+ test_pass(parseversion(&a, "", &err) != 0);
+ test_error(err);
+ test_pass(parseversion(&a, " ", &err) != 0);
+ test_error(err);
+
+ /* Test empty upstream version after epoch. */
+ test_fail(parseversion(&a, "0:", &err) == 0);
+ test_error(err);
+
+ /* Test empty epoch in version. */
+ test_fail(parseversion(&a, ":1.0", &err) == 0);
+ test_error(err);
+
+ /* Test empty revision in version. */
+ test_fail(parseversion(&a, "1.0-", &err) == 0);
+ test_error(err);
+
+ /* Test version with embedded spaces. */
+ test_fail(parseversion(&a, "0:0 0-1", &err) == 0);
+ test_error(err);
+
+ /* Test version with negative epoch. */
+ test_fail(parseversion(&a, "-1:0-1", &err) == 0);
+ test_error(err);
+
+ /* Test version with huge epoch. */
+ test_fail(parseversion(&a, "999999999999999999999999:0-1", &err) == 0);
+ test_error(err);
+
+ /* Test invalid characters in epoch. */
+ test_fail(parseversion(&a, "a:0-0", &err) == 0);
+ test_error(err);
+ test_fail(parseversion(&a, "A:0-0", &err) == 0);
+ test_error(err);
+
+ /* Test invalid empty upstream version. */
+ test_fail(parseversion(&a, "-0", &err) == 0);
+ test_error(err);
+ test_fail(parseversion(&a, "0:-0", &err) == 0);
+ test_error(err);
+
+ /* Test upstream version not starting with a digit */
+ test_fail(parseversion(&a, "0:abc3-0", &err) == 0);
+ test_warn(err);
+
+ /* Test invalid characters in upstream version. */
+ verstr = test_alloc(strdup("0:0a-0"));
+ for (p = "!#@$%&/|\\<>()[]{};,_=*^'"; *p; p++) {
+ verstr[3] = *p;
+ test_fail(parseversion(&a, verstr, &err) == 0);
+ test_warn(err);
+ }
+ free(verstr);
+
+ /* Test invalid characters in revision. */
+ test_fail(parseversion(&a, "0:0-0:0", &err) == 0);
+ test_warn(err);
+
+ verstr = test_alloc(strdup("0:0-0"));
+ for (p = "!#@$%&/|\\<>()[]{}:;,_=*^'"; *p; p++) {
+ verstr[4] = *p;
+ test_fail(parseversion(&a, verstr, &err) == 0);
+ test_warn(err);
+ }
+ free(verstr);
+
+ /* TODO: Complete. */
+}
+
+TEST_ENTRY(test)
+{
+ test_plan(196);
+
+ test_version_blank();
+ test_version_is_informative();
+ test_version_compare();
+ test_version_relate();
+ test_version_parse();
+}
diff --git a/lib/dpkg/tarfn.c b/lib/dpkg/tarfn.c
new file mode 100644
index 0000000..d999db6
--- /dev/null
+++ b/lib/dpkg/tarfn.c
@@ -0,0 +1,605 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * tarfn.c - tar archive extraction functions
+ *
+ * Copyright © 1995 Bruce Perens
+ * Copyright © 2007-2011, 2013-2017 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#if HAVE_SYS_SYSMACROS_H
+#include <sys/sysmacros.h>
+#endif
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/macros.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/i18n.h>
+#include <dpkg/error.h>
+#include <dpkg/tarfn.h>
+
+#define TAR_MAGIC_USTAR "ustar\0" "00"
+#define TAR_MAGIC_GNU "ustar " " \0"
+
+#define TAR_TYPE_SIGNED(t) (!((t)0 < (t)-1))
+
+#define TAR_TYPE_MIN(t) \
+ (TAR_TYPE_SIGNED(t) ? \
+ ~(t)TAR_TYPE_MAX(t) : \
+ (t)0)
+#define TAR_TYPE_MAX(t) \
+ (TAR_TYPE_SIGNED(t) ? \
+ ((((t)1 << (sizeof(t) * 8 - 2)) - 1) * 2 + 1) : \
+ ~(t)0)
+
+#define TAR_ATOUL(str, type) \
+ (type)tar_atoul(str, sizeof(str), TAR_TYPE_MAX(type))
+#define TAR_ATOSL(str, type) \
+ (type)tar_atosl(str, sizeof(str), TAR_TYPE_MIN(type), TAR_TYPE_MAX(type))
+
+struct tar_header {
+ char name[100] DPKG_ATTR_NONSTRING;
+ char mode[8] DPKG_ATTR_NONSTRING;
+ char uid[8] DPKG_ATTR_NONSTRING;
+ char gid[8] DPKG_ATTR_NONSTRING;
+ char size[12] DPKG_ATTR_NONSTRING;
+ char mtime[12] DPKG_ATTR_NONSTRING;
+ char checksum[8] DPKG_ATTR_NONSTRING;
+ char linkflag;
+ char linkname[100] DPKG_ATTR_NONSTRING;
+
+ /* Only valid on ustar and gnu. */
+ char magic[8] DPKG_ATTR_NONSTRING;
+ char user[32] DPKG_ATTR_NONSTRING;
+ char group[32] DPKG_ATTR_NONSTRING;
+ char devmajor[8] DPKG_ATTR_NONSTRING;
+ char devminor[8] DPKG_ATTR_NONSTRING;
+
+ /* Only valid on ustar. */
+ char prefix[155] DPKG_ATTR_NONSTRING;
+};
+
+static inline uintmax_t
+tar_ret_errno(int err, uintmax_t ret)
+{
+ errno = err;
+ return ret;
+}
+
+/**
+ * Convert an ASCII octal string to an intmax_t.
+ */
+static uintmax_t
+tar_atol8(const char *s, size_t size)
+{
+ const char *end = s + size;
+ uintmax_t n = 0;
+
+ /* Old implementations might precede the value with spaces. */
+ while (s < end && *s == ' ')
+ s++;
+
+ if (s == end)
+ return tar_ret_errno(EINVAL, 0);
+
+ while (s < end) {
+ if (*s == '\0' || *s == ' ')
+ break;
+ if (*s < '0' || *s > '7')
+ return tar_ret_errno(ERANGE, 0);
+ n = (n * 010) + (*s++ - '0');
+ }
+
+ while (s < end) {
+ if (*s != '\0' && *s != ' ')
+ return tar_ret_errno(EINVAL, 0);
+ s++;
+ }
+
+ return tar_ret_errno(0, n);
+}
+
+/**
+ * Convert a base-256 two-complement number to an intmax_t.
+ */
+static uintmax_t
+tar_atol256(const char *s, size_t size, intmax_t min, uintmax_t max)
+{
+ uintmax_t n = 0;
+ unsigned char c;
+ int sign;
+
+ /* The encoding always sets the first bit to one, so that it can be
+ * distinguished from the ASCII encoding. For positive numbers we
+ * need to reset it. For negative numbers we initialize n to -1. */
+ c = *s++;
+ if (c == 0x80)
+ c = 0;
+ else
+ n = ~(uintmax_t)0;
+ sign = c;
+
+ /* Check for overflows. */
+ while (size > sizeof(uintmax_t)) {
+ if (c != sign)
+ return tar_ret_errno(ERANGE, sign ? (uintmax_t)min : max);
+ c = *s++;
+ size--;
+ }
+
+ if ((c & 0x80) != (sign & 0x80))
+ return tar_ret_errno(ERANGE, sign ? (uintmax_t)min : max);
+
+ for (;;) {
+ n = (n << 8) | c;
+ if (--size == 0)
+ break;
+ c = *s++;
+ }
+
+ return tar_ret_errno(0, n);
+}
+
+static uintmax_t
+tar_atol(const char *s, size_t size, intmax_t min, uintmax_t max)
+{
+ const unsigned char *a = (const unsigned char *)s;
+
+ /* Check if it is a long two-complement base-256 number, positive or
+ * negative. */
+ if (*a == 0xff || *a == 0x80)
+ return tar_atol256(s, size, min, max);
+ else
+ return tar_atol8(s, size);
+}
+
+uintmax_t
+tar_atoul(const char *s, size_t size, uintmax_t max)
+{
+ uintmax_t n = tar_atol(s, size, 0, UINTMAX_MAX);
+
+ if (n > max)
+ return tar_ret_errno(ERANGE, UINTMAX_MAX);
+
+ return n;
+}
+
+intmax_t
+tar_atosl(const char *s, size_t size, intmax_t min, intmax_t max)
+{
+ intmax_t n = tar_atol(s, size, INTMAX_MIN, INTMAX_MAX);
+
+ if (n < min)
+ return tar_ret_errno(ERANGE, INTMAX_MIN);
+ if (n > max)
+ return tar_ret_errno(ERANGE, INTMAX_MAX);
+
+ return n;
+}
+
+static char *
+tar_header_get_prefix_name(struct tar_header *h)
+{
+ struct varbuf path = VARBUF_INIT;
+
+ varbuf_add_strn(&path, h->prefix, sizeof(h->prefix));
+ varbuf_add_char(&path, '/');
+ varbuf_add_strn(&path, h->name, sizeof(h->name));
+ varbuf_end_str(&path);
+
+ return path.buf;
+}
+
+static mode_t
+tar_header_get_unix_mode(struct tar_header *h)
+{
+ mode_t mode;
+ enum tar_filetype type;
+
+ type = (enum tar_filetype)h->linkflag;
+
+ switch (type) {
+ case TAR_FILETYPE_FILE0:
+ case TAR_FILETYPE_FILE:
+ case TAR_FILETYPE_HARDLINK:
+ mode = S_IFREG;
+ break;
+ case TAR_FILETYPE_SYMLINK:
+ mode = S_IFLNK;
+ break;
+ case TAR_FILETYPE_DIR:
+ mode = S_IFDIR;
+ break;
+ case TAR_FILETYPE_CHARDEV:
+ mode = S_IFCHR;
+ break;
+ case TAR_FILETYPE_BLOCKDEV:
+ mode = S_IFBLK;
+ break;
+ case TAR_FILETYPE_FIFO:
+ mode = S_IFIFO;
+ break;
+ default:
+ mode = 0;
+ break;
+ }
+
+ mode |= TAR_ATOUL(h->mode, mode_t) & 07777;
+
+ return mode;
+}
+
+static long
+tar_header_checksum(struct tar_header *h)
+{
+ unsigned char *s = (unsigned char *)h;
+ unsigned int i;
+ const size_t checksum_offset = offsetof(struct tar_header, checksum);
+ long sum;
+
+ /* Treat checksum field as all blank. */
+ sum = ' ' * sizeof(h->checksum);
+
+ for (i = checksum_offset; i > 0; i--)
+ sum += *s++;
+
+ /* Skip the real checksum field. */
+ s += sizeof(h->checksum);
+
+ for (i = TARBLKSZ - checksum_offset - sizeof(h->checksum); i > 0; i--)
+ sum += *s++;
+
+ return sum;
+}
+
+static int
+tar_header_decode(struct tar_header *h, struct tar_entry *d, struct dpkg_error *err)
+{
+ long checksum;
+
+ errno = 0;
+
+ if (memcmp(h->magic, TAR_MAGIC_GNU, 6) == 0)
+ d->format = TAR_FORMAT_GNU;
+ else if (memcmp(h->magic, TAR_MAGIC_USTAR, 6) == 0)
+ d->format = TAR_FORMAT_USTAR;
+ else
+ d->format = TAR_FORMAT_OLD;
+
+ d->type = (enum tar_filetype)h->linkflag;
+ if (d->type == TAR_FILETYPE_FILE0)
+ d->type = TAR_FILETYPE_FILE;
+
+ /* Concatenate prefix and name to support ustar style long names. */
+ if (d->format == TAR_FORMAT_USTAR && h->prefix[0] != '\0')
+ d->name = tar_header_get_prefix_name(h);
+ else
+ d->name = m_strndup(h->name, sizeof(h->name));
+ d->linkname = m_strndup(h->linkname, sizeof(h->linkname));
+ d->stat.mode = tar_header_get_unix_mode(h);
+ /* Even though off_t is signed, we use an unsigned parser here because
+ * negative offsets are not allowed. */
+ d->size = TAR_ATOUL(h->size, off_t);
+ if (errno)
+ return dpkg_put_errno(err, _("invalid tar header size field"));
+ d->mtime = TAR_ATOSL(h->mtime, time_t);
+ if (errno)
+ return dpkg_put_errno(err, _("invalid tar header mtime field"));
+
+ if (d->type == TAR_FILETYPE_CHARDEV || d->type == TAR_FILETYPE_BLOCKDEV)
+ d->dev = makedev(TAR_ATOUL(h->devmajor, dev_t),
+ TAR_ATOUL(h->devminor, dev_t));
+ else
+ d->dev = makedev(0, 0);
+
+ if (*h->user)
+ d->stat.uname = m_strndup(h->user, sizeof(h->user));
+ else
+ d->stat.uname = NULL;
+ d->stat.uid = TAR_ATOUL(h->uid, uid_t);
+ if (errno)
+ return dpkg_put_errno(err, _("invalid tar header uid field"));
+
+ if (*h->group)
+ d->stat.gname = m_strndup(h->group, sizeof(h->group));
+ else
+ d->stat.gname = NULL;
+ d->stat.gid = TAR_ATOUL(h->gid, gid_t);
+ if (errno)
+ return dpkg_put_errno(err, _("invalid tar header gid field"));
+
+ checksum = tar_atol8(h->checksum, sizeof(h->checksum));
+ if (errno)
+ return dpkg_put_errno(err, _("invalid tar header checksum field"));
+
+ if (tar_header_checksum(h) != checksum)
+ return dpkg_put_error(err, _("invalid tar header checksum"));
+
+ return 0;
+}
+
+/**
+ * Decode a GNU longlink or longname from the tar archive.
+ *
+ * The way the GNU long{link,name} stuff works is like this:
+ *
+ * - The first header is a “dummy” header that contains the size of the
+ * filename.
+ * - The next N headers contain the filename.
+ * - After the headers with the filename comes the “real” header with a
+ * bogus name or link.
+ */
+static int
+tar_gnu_long(struct tar_archive *tar, struct tar_entry *te, char **longp)
+{
+ char buf[TARBLKSZ];
+ char *bp;
+ int status = 0;
+ int long_read;
+
+ free(*longp);
+ *longp = bp = m_malloc(te->size + 1);
+
+ for (long_read = te->size; long_read > 0; long_read -= TARBLKSZ) {
+ int copysize;
+
+ status = tar->ops->read(tar, buf, TARBLKSZ);
+ if (status == TARBLKSZ)
+ status = 0;
+ else {
+ /* Read partial header record? */
+ if (status > 0) {
+ errno = 0;
+ status = dpkg_put_error(&tar->err,
+ _("partially read tar header"));
+ }
+
+ /* If we didn't get TARBLKSZ bytes read, punt. */
+ break;
+ }
+
+ copysize = min(long_read, TARBLKSZ);
+ memcpy(bp, buf, copysize);
+ bp += copysize;
+ }
+ *bp = '\0';
+
+ return status;
+}
+
+static void
+tar_entry_copy(struct tar_entry *dst, struct tar_entry *src)
+{
+ memcpy(dst, src, sizeof(struct tar_entry));
+
+ dst->name = m_strdup(src->name);
+ dst->linkname = m_strdup(src->linkname);
+
+ if (src->stat.uname)
+ dst->stat.uname = m_strdup(src->stat.uname);
+ if (src->stat.gname)
+ dst->stat.gname = m_strdup(src->stat.gname);
+}
+
+static void
+tar_entry_destroy(struct tar_entry *te)
+{
+ free(te->name);
+ free(te->linkname);
+ free(te->stat.uname);
+ free(te->stat.gname);
+
+ memset(te, 0, sizeof(*te));
+}
+
+struct tar_symlink_entry {
+ struct tar_symlink_entry *next;
+ struct tar_entry h;
+};
+
+/**
+ * Update the tar entry from system information.
+ *
+ * Normalize UID and GID relative to the current system.
+ */
+void
+tar_entry_update_from_system(struct tar_entry *te)
+{
+ struct passwd *passwd;
+ struct group *group;
+
+ if (te->stat.uname) {
+ passwd = getpwnam(te->stat.uname);
+ if (passwd)
+ te->stat.uid = passwd->pw_uid;
+ }
+ if (te->stat.gname) {
+ group = getgrnam(te->stat.gname);
+ if (group)
+ te->stat.gid = group->gr_gid;
+ }
+}
+
+int
+tar_extractor(struct tar_archive *tar)
+{
+ int status;
+ char buffer[TARBLKSZ];
+ struct tar_entry h;
+
+ char *next_long_name, *next_long_link;
+ struct tar_symlink_entry *symlink_head, *symlink_tail, *symlink_node;
+
+ next_long_name = NULL;
+ next_long_link = NULL;
+ symlink_tail = symlink_head = NULL;
+
+ h.name = NULL;
+ h.linkname = NULL;
+ h.stat.uname = NULL;
+ h.stat.gname = NULL;
+
+ while ((status = tar->ops->read(tar, buffer, TARBLKSZ)) == TARBLKSZ) {
+ int name_len;
+
+ if (tar_header_decode((struct tar_header *)buffer, &h, &tar->err) < 0) {
+ if (h.name[0] == '\0') {
+ /* The checksum failed on the terminating
+ * End Of Tape block entry of zeros. */
+ dpkg_error_destroy(&tar->err);
+
+ /* End Of Tape. */
+ status = 0;
+ } else {
+ status = -1;
+ }
+ tar_entry_destroy(&h);
+ break;
+ }
+ if (h.type != TAR_FILETYPE_GNU_LONGLINK &&
+ h.type != TAR_FILETYPE_GNU_LONGNAME) {
+ if (next_long_name) {
+ free(h.name);
+ h.name = next_long_name;
+ }
+
+ if (next_long_link) {
+ free(h.linkname);
+ h.linkname = next_long_link;
+ }
+
+ next_long_link = NULL;
+ next_long_name = NULL;
+ }
+
+ if (h.name[0] == '\0') {
+ status = dpkg_put_error(&tar->err,
+ _("invalid tar header with empty name field"));
+ errno = 0;
+ tar_entry_destroy(&h);
+ break;
+ }
+
+ name_len = strlen(h.name);
+
+ switch (h.type) {
+ case TAR_FILETYPE_FILE:
+ /* Compatibility with pre-ANSI ustar. */
+ if (h.name[name_len - 1] != '/') {
+ status = tar->ops->extract_file(tar, &h);
+ break;
+ }
+ /* Else, fall through. */
+ case TAR_FILETYPE_DIR:
+ if (h.name[name_len - 1] == '/') {
+ h.name[name_len - 1] = '\0';
+ }
+ status = tar->ops->mkdir(tar, &h);
+ break;
+ case TAR_FILETYPE_HARDLINK:
+ status = tar->ops->link(tar, &h);
+ break;
+ case TAR_FILETYPE_SYMLINK:
+ symlink_node = m_malloc(sizeof(*symlink_node));
+ symlink_node->next = NULL;
+ tar_entry_copy(&symlink_node->h, &h);
+
+ if (symlink_head)
+ symlink_tail->next = symlink_node;
+ else
+ symlink_head = symlink_node;
+ symlink_tail = symlink_node;
+ status = 0;
+ break;
+ case TAR_FILETYPE_CHARDEV:
+ case TAR_FILETYPE_BLOCKDEV:
+ case TAR_FILETYPE_FIFO:
+ status = tar->ops->mknod(tar, &h);
+ break;
+ case TAR_FILETYPE_GNU_LONGLINK:
+ status = tar_gnu_long(tar, &h, &next_long_link);
+ break;
+ case TAR_FILETYPE_GNU_LONGNAME:
+ status = tar_gnu_long(tar, &h, &next_long_name);
+ break;
+ case TAR_FILETYPE_GNU_VOLUME:
+ case TAR_FILETYPE_GNU_MULTIVOL:
+ case TAR_FILETYPE_GNU_SPARSE:
+ case TAR_FILETYPE_GNU_DUMPDIR:
+ status = dpkg_put_error(&tar->err,
+ _("unsupported GNU tar header type '%c'"),
+ h.type);
+ errno = 0;
+ break;
+ case TAR_FILETYPE_SOLARIS_EXTENDED:
+ case TAR_FILETYPE_SOLARIS_ACL:
+ status = dpkg_put_error(&tar->err,
+ _("unsupported Solaris tar header type '%c'"),
+ h.type);
+ errno = 0;
+ break;
+ case TAR_FILETYPE_PAX_GLOBAL:
+ case TAR_FILETYPE_PAX_EXTENDED:
+ status = dpkg_put_error(&tar->err,
+ _("unsupported PAX tar header type '%c'"),
+ h.type);
+ errno = 0;
+ break;
+ default:
+ status = dpkg_put_error(&tar->err,
+ _("unknown tar header type '%c'"),
+ h.type);
+ errno = 0;
+ }
+ tar_entry_destroy(&h);
+ if (status != 0)
+ /* Pass on status from coroutine. */
+ break;
+ }
+
+ while (symlink_head) {
+ symlink_node = symlink_head->next;
+ if (status == 0)
+ status = tar->ops->symlink(tar, &symlink_head->h);
+ tar_entry_destroy(&symlink_head->h);
+ free(symlink_head);
+ symlink_head = symlink_node;
+ }
+ /* Make sure we free the long names, in case of a bogus or truncated
+ * tar archive with long entries not followed by a normal entry. */
+ free(next_long_name);
+ free(next_long_link);
+
+ if (status > 0) {
+ status = dpkg_put_error(&tar->err,
+ _("partially read tar header"));
+ errno = 0;
+ }
+
+ /* Return whatever I/O function returned. */
+ return status;
+}
diff --git a/lib/dpkg/tarfn.h b/lib/dpkg/tarfn.h
new file mode 100644
index 0000000..c03a593
--- /dev/null
+++ b/lib/dpkg/tarfn.h
@@ -0,0 +1,134 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * tarfn.h - tar archive extraction functions
+ *
+ * Copyright © 1995 Bruce Perens
+ * Copyright © 2009-2014, 2017 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_TARFN_H
+#define LIBDPKG_TARFN_H
+
+#include <sys/types.h>
+
+#include <stdint.h>
+
+#include <dpkg/error.h>
+#include <dpkg/file.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup tar Tar archive handling
+ * @ingroup dpkg-public
+ * @{
+ */
+
+#define TARBLKSZ 512
+
+enum tar_format {
+ TAR_FORMAT_UNKNOWN,
+ TAR_FORMAT_OLD,
+ TAR_FORMAT_GNU,
+ TAR_FORMAT_USTAR,
+ TAR_FORMAT_PAX,
+};
+
+enum tar_filetype {
+ /** For compatibility with decades-old bug. */
+ TAR_FILETYPE_FILE0 = '\0',
+ TAR_FILETYPE_FILE = '0',
+ TAR_FILETYPE_HARDLINK = '1',
+ TAR_FILETYPE_SYMLINK = '2',
+ TAR_FILETYPE_CHARDEV = '3',
+ TAR_FILETYPE_BLOCKDEV = '4',
+ TAR_FILETYPE_DIR = '5',
+ TAR_FILETYPE_FIFO = '6',
+ TAR_FILETYPE_CONTIG = '7',
+ TAR_FILETYPE_GNU_LONGLINK = 'K',
+ TAR_FILETYPE_GNU_LONGNAME = 'L',
+ TAR_FILETYPE_GNU_VOLUME = 'V',
+ TAR_FILETYPE_GNU_MULTIVOL = 'M',
+ TAR_FILETYPE_GNU_DUMPDIR = 'D',
+ TAR_FILETYPE_GNU_SPARSE = 'S',
+ TAR_FILETYPE_PAX_GLOBAL = 'g',
+ TAR_FILETYPE_PAX_EXTENDED = 'x',
+ TAR_FILETYPE_SOLARIS_EXTENDED = 'X',
+ TAR_FILETYPE_SOLARIS_ACL = 'A',
+};
+
+struct tar_entry {
+ /** Tar entry format. */
+ enum tar_format format;
+ /** File type. */
+ enum tar_filetype type;
+ /** File name. */
+ char *name;
+ /** Symlink or hardlink name. */
+ char *linkname;
+ /** File size. */
+ off_t size;
+ /** Last-modified time. */
+ intmax_t mtime;
+ /** Special device for mknod(). */
+ dev_t dev;
+
+ struct file_stat stat;
+};
+
+struct tar_archive;
+
+typedef int tar_read_func(struct tar_archive *tar, char *buffer, int length);
+typedef int tar_make_func(struct tar_archive *tar, struct tar_entry *h);
+
+struct tar_operations {
+ tar_read_func *read;
+
+ tar_make_func *extract_file;
+ tar_make_func *link;
+ tar_make_func *symlink;
+ tar_make_func *mkdir;
+ tar_make_func *mknod;
+};
+
+struct tar_archive {
+ /* Global tar archive error. */
+ struct dpkg_error err;
+
+ /** Tar archive format. */
+ enum tar_format format;
+
+ /* Operation functions and context. */
+ const struct tar_operations *ops;
+ void *ctx;
+};
+
+uintmax_t
+tar_atoul(const char *s, size_t size, uintmax_t max);
+intmax_t
+tar_atosl(const char *s, size_t size, intmax_t min, intmax_t max);
+
+void
+tar_entry_update_from_system(struct tar_entry *te);
+
+int
+tar_extractor(struct tar_archive *tar);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif
diff --git a/lib/dpkg/test.h b/lib/dpkg/test.h
new file mode 100644
index 0000000..7bc0af1
--- /dev/null
+++ b/lib/dpkg/test.h
@@ -0,0 +1,190 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * test.h - test suite support
+ *
+ * Copyright © 2009-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_TEST_H
+#define LIBDPKG_TEST_H
+
+#include <string.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/macros.h>
+#ifndef TEST_MAIN_CTOR
+#include <dpkg/ehandle.h>
+#define TEST_MAIN_CTOR push_error_context()
+#define TEST_MAIN_DTOR pop_error_context(ehflag_normaltidy)
+#endif
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup dpkg_test Test suite support
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+#define test_bail(reason) \
+ do { \
+ printf("Bail out! %s\n", (reason)); \
+ exit(255); \
+ } while (0)
+
+#define test_xstringify(str) \
+ #str
+#define test_stringify(str) \
+ test_xstringify(str)
+
+static inline void *
+test_alloc(void *ptr, const char *reason)
+{
+ if (ptr == DPKG_NULL)
+ test_bail(reason);
+ return ptr;
+}
+
+#define test_alloc(ptr) \
+ test_alloc((ptr), "cannot allocate memory for " #ptr " in " __FILE__ ":" test_stringify(__LINE__))
+
+#define test_try(jmp) \
+ push_error_context_jump(&(jmp), DPKG_NULL, "test try"); \
+ if (!setjmp((jmp)))
+#define test_catch \
+ else
+#define test_finally \
+ pop_error_context(ehflag_normaltidy);
+
+static inline const char *
+test_get_envdir(const char *envvar)
+{
+ const char *envdir = getenv(envvar);
+ return envdir ? envdir : ".";
+}
+
+#define test_get_srcdir() \
+ test_get_envdir("srcdir")
+#define test_get_builddir() \
+ test_get_envdir("builddir")
+
+static inline char *
+test_data_file(const char *filename)
+{
+ char *pathname;
+ int rc;
+
+ rc = asprintf(&pathname, "%s/t/data/%s", test_get_srcdir(), filename);
+ if (rc < 0)
+ test_bail("cannot allocate data filename");
+
+ return pathname;
+}
+
+static inline bool
+test_is_verbose(void)
+{
+ const char *verbose = getenv("TEST_VERBOSE");
+ return verbose != DPKG_NULL && strcmp(verbose, "1") == 0;
+}
+
+#ifndef TEST_OMIT_VARIABLES
+static int test_id = 1;
+static int test_skip_code;
+static const char *test_skip_prefix;
+static const char *test_skip_reason;
+#endif
+
+#define test_plan(n) \
+ printf("1..%d\n", n);
+
+#define test_skip_all(reason) \
+ do { \
+ printf("1..0 # SKIP %s\n", (reason)); \
+ exit(0); \
+ } while (0)
+#define test_skip(reason) \
+ printf("ok %d # SKIP %s\n", test_id++, (reason))
+#define test_skip_block(cond) \
+ for (test_skip_prefix = " # SKIP ", \
+ test_skip_reason = cond ? #cond : DPKG_NULL, \
+ test_skip_code = 1; \
+ test_skip_prefix; \
+ test_skip_prefix = test_skip_reason = DPKG_NULL, \
+ test_skip_code = 0)
+
+#define test_todo(a, reason, desc) \
+ do { \
+ test_skip_prefix = " # TODO "; \
+ test_skip_reason = reason; \
+ test_case(a, "%s", desc); \
+ test_skip_prefix = test_skip_reason = DPKG_NULL; \
+ } while(0)
+#define test_todo_block(reason) \
+ for (test_skip_prefix = " # TODO ", test_skip_reason = reason; \
+ test_skip_prefix; \
+ test_skip_prefix = test_skip_reason = DPKG_NULL)
+
+#define test_case(a, fmt, ...) \
+ printf("%sok %d - " fmt "%s%s\n", \
+ test_skip_code || (a) ? "" : "not ", \
+ test_id++, __VA_ARGS__, \
+ test_skip_reason ? test_skip_prefix : "", \
+ test_skip_reason ? test_skip_reason : "")
+
+#define test_pass(a) \
+ test_case((a), "pass %s", #a)
+#define test_fail(a) \
+ test_case(!(a), "fail %s", #a)
+#define test_str(a, op, b) \
+ test_case(strcmp((a), (b)) op 0, "strcmp '%s' %s '%s'", a, #op, b)
+#define test_mem(a, op, b, size) \
+ test_case(memcmp((a), (b), (size)) op 0, "memcmp %p %s %p", a, #op, b)
+
+/* Specific test macros. */
+#define test_warn(e) \
+ do { \
+ test_pass((e).type == DPKG_MSG_WARN); \
+ dpkg_error_destroy(&(e)); \
+ } while (0)
+#define test_error(e) \
+ do { \
+ test_pass((e).type == DPKG_MSG_ERROR); \
+ dpkg_error_destroy(&(e)); \
+ } while (0)
+
+/** @} */
+
+DPKG_END_DECLS
+
+#define TEST_ENTRY(name) \
+static void name(void); \
+int \
+main(int argc, char **argv) \
+{ \
+ setvbuf(stdout, DPKG_NULL, _IOLBF, 0); \
+ \
+ TEST_MAIN_CTOR; \
+ name(); \
+ TEST_MAIN_DTOR; \
+ return 0; \
+} \
+static void \
+name(void)
+
+#endif
diff --git a/lib/dpkg/treewalk.c b/lib/dpkg/treewalk.c
new file mode 100644
index 0000000..4dc62b3
--- /dev/null
+++ b/lib/dpkg/treewalk.c
@@ -0,0 +1,542 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * treewalk.c - directory tree walk support
+ *
+ * Copyright © 2013-2016 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <dpkg/dpkg.h>
+#include <dpkg/i18n.h>
+#include <dpkg/treewalk.h>
+
+/* treenode functions. */
+
+typedef int treewalk_stat_func(const char *pathname, struct stat *st);
+
+struct treenode {
+ struct treenode *up; /* Parent dir. */
+ struct treenode *next; /* Next node in dir. */
+ struct treenode **down; /* Dir contents. */
+
+ char *pathname; /* Node pathname. */
+ char *virtname; /* Node virtname. */
+ char *name; /* Node name. */
+
+ struct stat *stat; /* Node metadata. */
+ mode_t mode;
+
+ size_t pathname_len; /* Node pathname length. */
+
+ size_t down_used; /* Number of used nodes in dir. */
+ size_t down_size; /* Number of allocated nodes in dir. */
+};
+
+static inline struct treenode *
+treenode_alloc(void)
+{
+ return m_calloc(1, sizeof(struct treenode));
+}
+
+static struct treenode *
+treenode_root_new(const char *rootdir)
+{
+ struct treenode *node;
+
+ node = treenode_alloc();
+ node->up = NULL;
+ node->pathname = m_strdup(rootdir);
+ node->pathname_len = strlen(node->pathname);
+ node->virtname = node->pathname + node->pathname_len;
+ node->name = strrchr(node->pathname, '/');
+ if (node->name == NULL)
+ node->name = node->pathname;
+ else
+ node->name++;
+
+ return node;
+}
+
+static struct treenode *
+treenode_node_new(struct treenode *root, struct treenode *dir, const char *name)
+{
+ struct treenode *node;
+
+ node = treenode_alloc();
+ node->up = dir;
+
+ node->pathname = str_fmt("%s/%s", node->up->pathname, name);
+ node->pathname_len = strlen(node->pathname);
+ node->virtname = node->pathname + root->pathname_len + 1;
+ node->name = node->pathname + node->up->pathname_len + 1;
+
+ return node;
+}
+
+static void
+treenode_stat(struct treenode *node, treewalk_stat_func *stat_func)
+{
+ if (node->stat)
+ return;
+
+ node->stat = m_malloc(sizeof(*node->stat));
+
+ if (stat_func(node->pathname, node->stat) < 0)
+ ohshite(_("cannot stat pathname '%s'"), node->pathname);
+
+ node->mode = node->stat->st_mode;
+}
+
+static mode_t
+dirent_to_mode_type(struct dirent *e)
+{
+#ifdef _DIRENT_HAVE_D_TYPE
+ switch (e->d_type) {
+ case DT_REG:
+ return S_IFREG;
+ case DT_DIR:
+ return S_IFDIR;
+ case DT_LNK:
+ return S_IFLNK;
+ case DT_CHR:
+ return S_IFCHR;
+ case DT_BLK:
+ return S_IFBLK;
+ case DT_FIFO:
+ return S_IFIFO;
+ case DT_SOCK:
+ return S_IFSOCK;
+ case DT_UNKNOWN:
+ default:
+ return 0;
+ }
+#else
+ return 0;
+#endif
+}
+
+static void
+treenode_stat_shallow(struct treenode *node, struct dirent *e,
+ treewalk_stat_func *stat_func)
+{
+ mode_t mode;
+
+ mode = dirent_to_mode_type(e);
+ if (mode == 0 || S_ISDIR(mode) || S_ISLNK(mode))
+ treenode_stat(node, stat_func);
+ else
+ node->mode |= mode;
+}
+
+const char *
+treenode_get_name(struct treenode *node)
+{
+ return node->name;
+}
+
+const char *
+treenode_get_pathname(struct treenode *node)
+{
+ return node->pathname;
+}
+
+const char *
+treenode_get_virtname(struct treenode *node)
+{
+ return node->virtname;
+}
+
+mode_t
+treenode_get_mode(struct treenode *node)
+{
+ return node->mode;
+}
+
+struct stat *
+treenode_get_stat(struct treenode *node)
+{
+ treenode_stat(node, lstat);
+ return node->stat;
+}
+
+struct treenode *
+treenode_get_parent(struct treenode *node)
+{
+ return node->up;
+}
+
+static inline bool
+treenode_is_dir(struct treenode *node)
+{
+ return node && S_ISDIR(node->mode);
+}
+
+static void
+treenode_resize_down(struct treenode *node)
+{
+ size_t new_size;
+
+ if (node->down_size)
+ node->down_size *= 2;
+ else if (node->stat->st_nlink > 4)
+ node->down_size = node->stat->st_nlink * 2;
+ else
+ node->down_size = 8;
+
+ new_size = node->down_size * sizeof(*node);
+ node->down = m_realloc(node->down, new_size);
+}
+
+static int
+treenode_cmp(const void *a, const void *b)
+{
+ return strcmp((*(const struct treenode **)a)->name,
+ (*(const struct treenode **)b)->name);
+}
+
+static void
+treenode_sort_down(struct treenode *dir)
+{
+ size_t i;
+
+ qsort(dir->down, dir->down_used, sizeof(struct treenode *), treenode_cmp);
+
+ /* Relink the nodes. */
+ for (i = 0; i < dir->down_used - 1; i++)
+ dir->down[i]->next = dir->down[i + 1];
+ dir->down[i]->next = NULL;
+}
+
+static void
+treenode_fill_down(struct treenode *root, struct treenode *dir,
+ enum treewalk_options options)
+{
+ DIR *d;
+ struct dirent *e;
+ treewalk_stat_func *stat_func;
+
+ if (options & TREEWALK_FOLLOW_LINKS)
+ stat_func = stat;
+ else
+ stat_func = lstat;
+
+ d = opendir(dir->pathname);
+ if (!d)
+ ohshite(_("cannot open directory '%s'"), dir->pathname);
+
+ while ((e = readdir(d)) != NULL) {
+ struct treenode *node;
+
+ if (strcmp(e->d_name, ".") == 0 ||
+ strcmp(e->d_name, "..") == 0)
+ continue;
+
+ if (dir->down_used >= dir->down_size)
+ treenode_resize_down(dir);
+
+ node = treenode_node_new(root, dir, e->d_name);
+ if (options & TREEWALK_FORCE_STAT)
+ treenode_stat(node, stat_func);
+ else
+ treenode_stat_shallow(node, e, stat_func);
+
+ dir->down[dir->down_used] = node;
+ dir->down_used++;
+ }
+
+ closedir(d);
+}
+
+static void
+treenode_free_node(struct treenode *node)
+{
+ free(node->pathname);
+ free(node->stat);
+ free(node);
+}
+
+static void
+treenode_free_down(struct treenode *node)
+{
+ size_t i;
+
+ if (!node->down_size)
+ return;
+
+ for (i = 0; i < node->down_used; i++)
+ treenode_free_node(node->down[i]);
+ free(node->down);
+}
+
+
+/* treeroot functions. */
+
+struct treeroot {
+ struct treenode *rootnode;
+
+ struct treenode *curdir;
+ struct treenode *curnode;
+
+ enum treewalk_options options;
+ struct treewalk_funcs func;
+};
+
+static inline void
+treeroot_set_curdir(struct treeroot *tree, struct treenode *node)
+{
+ tree->curdir = node;
+}
+
+static inline void
+treeroot_set_curnode(struct treeroot *tree, struct treenode *node)
+{
+ tree->curnode = node;
+}
+
+static bool
+treeroot_skip_node(struct treeroot *tree, struct treenode *node)
+{
+ if (tree->func.skip)
+ return tree->func.skip(node);
+
+ return false;
+}
+
+static void
+treeroot_fill_node(struct treeroot *tree, struct treenode *dir)
+{
+ treenode_fill_down(tree->rootnode, dir, tree->options);
+}
+
+static void
+treeroot_sort_node(struct treeroot *tree, struct treenode *dir)
+{
+ static struct treenode *down_empty[] = { NULL, NULL };
+
+ if (dir->down_used == 0)
+ dir->down = down_empty;
+ else if (tree->func.sort)
+ tree->func.sort(dir);
+ else
+ treenode_sort_down(dir);
+}
+
+static void
+treeroot_visit_node(struct treeroot *tree, struct treenode *node)
+{
+ if (tree->func.visit == NULL)
+ return;
+
+ if (!treeroot_skip_node(tree, node))
+ tree->func.visit(node);
+}
+
+/**
+ * Open a new tree to be walked.
+ *
+ * @param rootdir The root directory to start walking the tree.
+ * @param options The options specifying how to walk the tree.
+ * @param func The functions callbacks.
+ */
+struct treeroot *
+treewalk_open(const char *rootdir, enum treewalk_options options,
+ const struct treewalk_funcs *func)
+{
+ struct treeroot *tree;
+ struct treenode *root;
+
+ tree = m_malloc(sizeof(*tree));
+
+ tree->options = options;
+ if (func)
+ tree->func = *func;
+ else
+ tree->func = TREEWALK_OBJECT;
+
+ root = treenode_root_new(rootdir);
+ treenode_stat(root, lstat);
+
+ if (!treenode_is_dir(root))
+ ohshit(_("treewalk root %s is not a directory"), rootdir);
+
+ treeroot_set_curnode(tree, root);
+ tree->rootnode = tree->curdir = root;
+
+ return tree;
+}
+
+/**
+ * Return the current node.
+ *
+ * This function is only needed if you want to start walking the tree from
+ * the root node. With something like:
+ *
+ * @code
+ * struct treeroot *tree;
+ * struct treenode *node;
+ *
+ * tree = treewalk_open(...);
+ * for (node = treewalk_node(tree); node; node = treewalk_next(tree))
+ * visitor(node);
+ * treewalk_close(tree);
+ * @endcode
+ *
+ * @param tree The tree structure.
+ */
+struct treenode *
+treewalk_node(struct treeroot *tree)
+{
+ return tree->curnode;
+}
+
+/**
+ * Return the next node.
+ *
+ * This function works basically as an iterator. And will return NULL when
+ * the whole tree has been traversed. When starting it will skip the root
+ * node, so you might want to use treewalk_node() to get that, otherwise
+ * you could use it like this:
+ *
+ * @code
+ * struct treeroot *tree;
+ * struct treenode *node;
+ *
+ * tree = treewalk_open(...);
+ * while ((node = treewalk_next(tree))
+ * visitor(node);
+ * treewalk_close(tree);
+ * @endcode
+ *
+ * @param tree The tree structure.
+ */
+struct treenode *
+treewalk_next(struct treeroot *tree)
+{
+ struct treenode *node;
+
+ /* Handle rootless trees, such as uninitialized or fully traversed. */
+ if (tree->rootnode == NULL)
+ return NULL;
+
+ node = tree->curnode;
+
+ /* Handle end of tree. */
+ if (node == NULL)
+ return NULL;
+
+ /* Get next node, descending or sidewide. */
+ if (treenode_is_dir(node) && !treeroot_skip_node(tree, node)) {
+ struct treenode *dir;
+
+ treeroot_fill_node(tree, node);
+ treeroot_sort_node(tree, node);
+ treeroot_set_curdir(tree, node);
+
+ /* Check for directory loops. */
+ for (dir = node->up; dir; dir = dir->up) {
+ if (dir->stat->st_dev == node->stat->st_dev &&
+ dir->stat->st_ino == node->stat->st_ino)
+ break;
+ }
+
+ /* Skip directory loops. */
+ if (dir)
+ node = node->next;
+ else
+ node = node->down[0];
+ } else {
+ node = node->next;
+ }
+
+ /* Back track node, ascending. */
+ while (node == NULL) {
+ struct treenode *olddir = tree->curdir;
+
+ if (tree->curdir->next) {
+ /* Next entry in the parent directory. */
+ node = tree->curdir->next;
+ treeroot_set_curdir(tree, olddir->up);
+ treenode_free_down(olddir);
+ } else if (tree->curdir->up) {
+ /* Next entry in the grand-parent directory. */
+ node = tree->curdir->up->next;
+ treeroot_set_curdir(tree, olddir->up->up);
+ treenode_free_down(olddir);
+ treenode_free_down(olddir->up);
+ } else {
+ /* Otherwise, we're in the rootnode. */
+ treenode_free_down(olddir);
+ treenode_free_node(olddir);
+ break;
+ }
+
+ if (tree->curdir == NULL) {
+ treenode_free_node(tree->rootnode);
+ tree->rootnode = NULL;
+ break;
+ }
+ }
+
+ treeroot_set_curnode(tree, node);
+
+ return node;
+}
+
+/**
+ * Closes the tree being walked.
+ *
+ * It will free any resources previously allocated.
+ */
+void
+treewalk_close(struct treeroot *tree)
+{
+ free(tree);
+}
+
+/**
+ * Tree walker.
+ *
+ * @param rootdir The root directory to start walking the tree.
+ * @param options The options specifying how to walk the tree.
+ * @param func The function callbacks.
+ */
+int
+treewalk(const char *rootdir, enum treewalk_options options,
+ struct treewalk_funcs *func)
+{
+ struct treeroot *tree;
+ struct treenode *node;
+
+ tree = treewalk_open(rootdir, options, func);
+
+ /* Breath first visit. */
+ for (node = treewalk_node(tree); node; node = treewalk_next(tree))
+ treeroot_visit_node(tree, node);
+
+ treewalk_close(tree);
+
+ return 0;
+}
diff --git a/lib/dpkg/treewalk.h b/lib/dpkg/treewalk.h
new file mode 100644
index 0000000..8c14d80
--- /dev/null
+++ b/lib/dpkg/treewalk.h
@@ -0,0 +1,94 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * treewalk.h - directory tree walk support
+ *
+ * Copyright © 2013-2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_TREEWALK_H
+#define LIBDPKG_TREEWALK_H
+
+#include <dpkg/macros.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <stdbool.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup treewalk Directory tree walking
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+enum DPKG_ATTR_ENUM_FLAGS treewalk_options {
+ TREEWALK_NONE = 0,
+ TREEWALK_FORCE_STAT = DPKG_BIT(0),
+ TREEWALK_FOLLOW_LINKS = DPKG_BIT(1),
+};
+
+struct treenode;
+
+typedef int treenode_visit_func(struct treenode *node);
+typedef bool treenode_skip_func(struct treenode *node);
+typedef int treenode_sort_func(struct treenode *node);
+
+struct treewalk_funcs {
+ treenode_visit_func *visit;
+ treenode_sort_func *sort;
+ treenode_skip_func *skip;
+};
+
+#if __STDC_VERSION__ > 201710L
+#define TREEWALK_OBJECT (struct treewalk_funcs){ }
+#else
+#define TREEWALK_OBJECT (struct treewalk_funcs){ 0 }
+#endif
+
+struct treeroot *
+treewalk_open(const char *rootdir, enum treewalk_options options,
+ const struct treewalk_funcs *funcs);
+struct treenode *
+treewalk_node(struct treeroot *tree);
+struct treenode *
+treewalk_next(struct treeroot *tree);
+void
+treewalk_close(struct treeroot *tree);
+
+int
+treewalk(const char *rootdir, enum treewalk_options options,
+ struct treewalk_funcs *funcs);
+
+struct treenode *
+treenode_get_parent(struct treenode *node);
+const char *
+treenode_get_name(struct treenode *node);
+const char *
+treenode_get_pathname(struct treenode *node);
+const char *
+treenode_get_virtname(struct treenode *node);
+mode_t
+treenode_get_mode(struct treenode *node);
+struct stat *
+treenode_get_stat(struct treenode *node);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_TREEWALK_H */
diff --git a/lib/dpkg/trigdeferred.c b/lib/dpkg/trigdeferred.c
new file mode 100644
index 0000000..e19eca7
--- /dev/null
+++ b/lib/dpkg/trigdeferred.c
@@ -0,0 +1,285 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * trigdeferred.c - parsing of triggers/Unincorp (was …/Deferred)
+ *
+ * Copyright © 2007 Canonical Ltd
+ * written by Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008-2016 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/c-ctype.h>
+#include <dpkg/file.h>
+#include <dpkg/dir.h>
+#include <dpkg/trigdeferred.h>
+#include <dpkg/triglib.h>
+
+static struct varbuf fn, newfn;
+
+static const struct trigdefmeths *trigdef;
+
+/*---------- Deferred file handling ----------*/
+
+static char *triggersdir;
+static int lock_fd = -1;
+static FILE *old_deferred;
+static FILE *trig_new_deferred;
+
+static void
+constructfn(struct varbuf *vb, const char *dir, const char *tail)
+{
+ varbuf_reset(vb);
+ varbuf_add_dir(vb, dir);
+ varbuf_add_str(vb, tail);
+ varbuf_end_str(vb);
+}
+
+/**
+ * Start processing of the triggers deferred file.
+ *
+ * @retval -1 Lock ENOENT with O_CREAT (directory does not exist).
+ * @retval -2 Unincorp empty, TDUF_WRITE_IF_EMPTY unset.
+ * @retval -3 Unincorp ENOENT, TDUF_WRITE_IF_ENOENT unset.
+ * @retval 1 Unincorp ENOENT, TDUF_WRITE_IF_ENOENT set.
+ * @retval 2 Ok.
+ *
+ * For positive return values the caller must call trigdef_update_done!
+ */
+enum trigdef_update_status
+trigdef_update_start(enum trigdef_update_flags uf)
+{
+ free(triggersdir);
+ triggersdir = dpkg_db_get_path(TRIGGERSDIR);
+
+ if (uf & TDUF_WRITE) {
+ constructfn(&fn, triggersdir, TRIGGERSLOCKFILE);
+ if (lock_fd == -1) {
+ lock_fd = open(fn.buf, O_RDWR | O_CREAT | O_TRUNC, 0600);
+ if (lock_fd == -1) {
+ if (!(errno == ENOENT && (uf & TDUF_NO_LOCK_OK)))
+ ohshite(_("unable to open/create "
+ "triggers lock file '%.250s'"),
+ fn.buf);
+ return TDUS_ERROR_NO_DIR;
+ }
+ }
+
+ file_lock(&lock_fd, FILE_LOCK_WAIT, fn.buf,
+ _("triggers database lock"));
+ }
+
+ constructfn(&fn, triggersdir, TRIGGERSDEFERREDFILE);
+
+ if (old_deferred)
+ fclose(old_deferred);
+ old_deferred = fopen(fn.buf, "r");
+ if (!old_deferred) {
+ if (errno != ENOENT)
+ ohshite(_("unable to open triggers deferred file '%.250s'"),
+ fn.buf);
+ if (!(uf & TDUF_WRITE_IF_ENOENT)) {
+ if (uf & TDUF_WRITE)
+ pop_cleanup(ehflag_normaltidy);
+ return TDUS_ERROR_NO_DEFERRED;
+ }
+ } else {
+ struct stat stab;
+ int rc;
+
+ setcloexec(fileno(old_deferred), fn.buf);
+
+ rc = fstat(fileno(old_deferred), &stab);
+ if (rc < 0)
+ ohshite(_("unable to stat triggers deferred file '%.250s'"),
+ fn.buf);
+
+ if (stab.st_size == 0 && !(uf & TDUF_WRITE_IF_EMPTY)) {
+ if (uf & TDUF_WRITE)
+ pop_cleanup(ehflag_normaltidy);
+ return TDUS_ERROR_EMPTY_DEFERRED;
+ }
+ }
+
+ if (uf & TDUF_WRITE) {
+ constructfn(&newfn, triggersdir, TRIGGERSDEFERREDFILE ".new");
+ if (trig_new_deferred)
+ fclose(trig_new_deferred);
+ trig_new_deferred = fopen(newfn.buf, "w");
+ if (!trig_new_deferred)
+ ohshite(_("unable to open/create new triggers deferred file '%.250s'"),
+ newfn.buf);
+
+ setcloexec(fileno(trig_new_deferred), newfn.buf);
+ }
+
+ if (!old_deferred)
+ return TDUS_NO_DEFERRED;
+
+ return TDUS_OK;
+}
+
+void
+trigdef_set_methods(const struct trigdefmeths *methods)
+{
+ trigdef = methods;
+}
+
+void
+trigdef_update_printf(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vfprintf(trig_new_deferred, format, ap);
+ va_end(ap);
+}
+
+static void
+trigdef_parse_error(int line_num, const char *line, const char *ptr)
+{
+ ohshit(_("syntax error in triggers deferred file '%.250s' at "
+ "line %d character %zd '%s'"),
+ fn.buf, line_num, ptr - line + 1, ptr);
+}
+
+/* Trim leading space. */
+static char *
+trigdef_skip_whitespace(char *ptr)
+{
+ while (*ptr) {
+ if (!c_iswhite(*ptr))
+ break;
+ ptr++;
+ }
+
+ return ptr;
+}
+
+int
+trigdef_parse(void)
+{
+ char line[_POSIX2_LINE_MAX];
+ char *ptr, *ptr_ini;
+ int line_num = 0;
+
+ while (fgets_checked(line, sizeof(line), old_deferred, fn.buf) > 0) {
+ line_num++;
+
+ ptr = trigdef_skip_whitespace(line);
+
+ /* Skip comments and empty lines. */
+ if (*ptr == '\0' || *ptr == '#')
+ continue;
+
+ /* Parse the trigger directive. */
+ ptr_ini = ptr;
+ while (*ptr) {
+ if (*ptr < 0x21 || *ptr > 0x7e)
+ break;
+ ptr++;
+ }
+
+ if (*ptr == '\0' || ptr_ini == ptr)
+ trigdef_parse_error(line_num, line, ptr);
+ *ptr++ = '\0';
+
+ /* Set the trigger directive. */
+ trigdef->trig_begin(ptr_ini);
+
+ /* Parse the package names. */
+ while (*ptr) {
+ ptr = trigdef_skip_whitespace(ptr);
+
+ ptr_ini = ptr;
+ if (*ptr == '\0' ||
+ !(c_isdigit(*ptr) || c_islower(*ptr) || *ptr == '-'))
+ trigdef_parse_error(line_num, line, ptr);
+
+ while (*++ptr) {
+ if (!c_isdigit(*ptr) && !c_islower(*ptr) &&
+ *ptr != '-' && *ptr != ':' &&
+ *ptr != '+' && *ptr != '.')
+ break;
+ }
+
+ if (*ptr != '\0' && *ptr != '#' && !c_iswhite(*ptr))
+ trigdef_parse_error(line_num, line, ptr);
+ *ptr++ = '\0';
+
+ if (ptr_ini[0] == '-' && ptr_ini[1])
+ ohshit(_("invalid package name '%.250s' in "
+ "triggers deferred file '%.250s'"),
+ ptr_ini, fn.buf);
+
+ /* Set the package name. */
+ trigdef->package(ptr_ini);
+ }
+
+ trigdef->trig_end();
+ }
+
+ return 0;
+}
+
+void
+trigdef_process_done(void)
+{
+ if (old_deferred) {
+ if (ferror(old_deferred))
+ ohshite(_("error reading triggers deferred file '%.250s'"),
+ fn.buf);
+ fclose(old_deferred);
+ old_deferred = NULL;
+ }
+
+ if (trig_new_deferred) {
+ int rc;
+
+ if (ferror(trig_new_deferred))
+ ohshite(_("unable to write new triggers deferred "
+ "file '%.250s'"), newfn.buf);
+ rc = fclose(trig_new_deferred);
+ trig_new_deferred = NULL;
+ if (rc)
+ ohshite(_("unable to close new triggers deferred "
+ "file '%.250s'"), newfn.buf);
+
+ if (rename(newfn.buf, fn.buf))
+ ohshite(_("unable to install new triggers deferred "
+ "file '%.250s'"), fn.buf);
+
+ dir_sync_path(triggersdir);
+ }
+
+ free(triggersdir);
+ triggersdir = NULL;
+
+ /* Unlock. */
+ if (lock_fd >= 0)
+ pop_cleanup(ehflag_normaltidy);
+}
diff --git a/lib/dpkg/trigdeferred.h b/lib/dpkg/trigdeferred.h
new file mode 100644
index 0000000..3832334
--- /dev/null
+++ b/lib/dpkg/trigdeferred.h
@@ -0,0 +1,70 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * trigdeferred.h - parsing of triggers/Unincorp (was …/Deferred)
+ *
+ * Copyright © 2007 Canonical, Ltd.
+ * written by Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_TRIGDEFERRED_H
+#define LIBDPKG_TRIGDEFERRED_H
+
+#include <dpkg/macros.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup trigdeferred Trigger deferred file handling
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+enum DPKG_ATTR_ENUM_FLAGS trigdef_update_flags {
+ TDUF_NO_LOCK_OK = DPKG_BIT(0),
+ TDUF_WRITE = DPKG_BIT(1),
+ TDUF_NO_LOCK = TDUF_NO_LOCK_OK | TDUF_WRITE,
+ /** Should not be set unless TDUF_WRITE is. */
+ TDUF_WRITE_IF_EMPTY = DPKG_BIT(3),
+ TDUF_WRITE_IF_ENOENT = DPKG_BIT(4),
+};
+
+enum trigdef_update_status {
+ TDUS_ERROR_NO_DIR = -1,
+ TDUS_ERROR_EMPTY_DEFERRED = -2,
+ TDUS_ERROR_NO_DEFERRED = -3,
+ TDUS_NO_DEFERRED = 1,
+ TDUS_OK = 2,
+};
+
+struct trigdefmeths {
+ void (*trig_begin)(const char *trig);
+ void (*package)(const char *awname);
+ void (*trig_end)(void);
+};
+
+void trigdef_set_methods(const struct trigdefmeths *methods);
+
+enum trigdef_update_status trigdef_update_start(enum trigdef_update_flags uf);
+void trigdef_update_printf(const char *format, ...) DPKG_ATTR_PRINTF(1);
+int trigdef_parse(void);
+void trigdef_process_done(void);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_TRIGDEFERRED_H */
diff --git a/lib/dpkg/triglib.c b/lib/dpkg/triglib.c
new file mode 100644
index 0000000..82cb298
--- /dev/null
+++ b/lib/dpkg/triglib.c
@@ -0,0 +1,863 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * triglib.c - trigger handling
+ *
+ * Copyright © 2007 Canonical Ltd
+ * Written by Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008-2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/c-ctype.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg.h>
+#include <dpkg/dlist.h>
+#include <dpkg/dir.h>
+#include <dpkg/pkg-spec.h>
+#include <dpkg/trigdeferred.h>
+#include <dpkg/triglib.h>
+
+/*========== Recording triggers. ==========*/
+
+static char *triggersdir, *triggersfilefile;
+
+static char *
+trig_get_filename(const char *dir, const char *filename)
+{
+ return str_fmt("%s/%s", dir, filename);
+}
+
+static struct trig_hooks trigh;
+
+/*---------- Noting trigger activation in memory. ----------*/
+
+/*
+ * Called via trig_*activate* et al from:
+ * - trig_incorporate: reading of Unincorp (explicit trigger activations)
+ * - various places: processing start (‘activate’ in triggers ci file)
+ * - namenodetouse: file triggers during unpack / remove
+ * - deferred_configure: file triggers during config file processing
+ *
+ * Not called from trig_transitional_activation; that runs
+ * trig_note_pend directly which means that (a) awaiters are not
+ * recorded (how would we know?) and (b) we don't enqueue them for
+ * deferred processing in this run.
+ *
+ * We add the trigger to Triggers-Pending first. This makes it
+ * harder to get into the state where Triggers-Awaited for aw lists
+ * pend but Triggers-Pending for pend is empty. (See also the
+ * comment in deppossi_ok_found regarding this situation.)
+ */
+
+/*
+ * aw might be NULL.
+ * trig is not copied!
+ */
+static void
+trig_record_activation(struct pkginfo *pend, struct pkginfo *aw, const char *trig)
+{
+ if (pend->status < PKG_STAT_TRIGGERSAWAITED)
+ return; /* Not interested then. */
+
+ if (trig_note_pend(pend, trig))
+ modstatdb_note_ifwrite(pend);
+
+ if (trigh.enqueue_deferred)
+ trigh.enqueue_deferred(pend);
+
+ if (aw && pend->status > PKG_STAT_CONFIGFILES)
+ if (trig_note_aw(pend, aw)) {
+ if (aw->status > PKG_STAT_TRIGGERSAWAITED)
+ pkg_set_status(aw, PKG_STAT_TRIGGERSAWAITED);
+ modstatdb_note_ifwrite(aw);
+ }
+}
+
+void
+trig_clear_awaiters(struct pkginfo *notpend)
+{
+ struct trigaw *ta;
+
+ if (notpend->trigpend_head)
+ internerr("package %s has pending triggers",
+ pkg_name(notpend, pnaw_always));
+
+ ta = notpend->othertrigaw_head;
+ notpend->othertrigaw_head = NULL;
+ for (; ta; ta = ta->samepend_next) {
+ struct pkginfo *aw;
+
+ aw = ta->aw;
+ if (!aw)
+ continue;
+ LIST_UNLINK_PART(aw->trigaw, ta, sameaw);
+ if (!aw->trigaw.head && aw->status == PKG_STAT_TRIGGERSAWAITED) {
+ if (aw->trigpend_head)
+ pkg_set_status(aw, PKG_STAT_TRIGGERSPENDING);
+ else
+ pkg_set_status(aw, PKG_STAT_INSTALLED);
+ modstatdb_note(aw);
+ }
+ }
+}
+
+/*
+ * Fix up packages in state triggers-awaited w/o the corresponding package
+ * with pending triggers. This can happen when dpkg was interrupted
+ * while in modstatdb_note, and the package in triggers-pending had its
+ * state modified but dpkg could not finish clearing the awaiters.
+ *
+ * XXX: Possibly get rid of some of the checks done somewhere else for
+ * this condition at run-time.
+ */
+void
+trig_fixup_awaiters(enum modstatdb_rw cstatus)
+{
+ if (cstatus < msdbrw_write)
+ return;
+
+ trig_awaited_pend_foreach(trig_clear_awaiters);
+ trig_awaited_pend_free();
+}
+
+/*---------- Generalized handling of trigger kinds. ----------*/
+
+struct trigkindinfo {
+ /* Only for trig_activate_start. */
+ void (*activate_start)(void);
+
+ /* Rest are for everyone: */
+ void (*activate_awaiter)(struct pkginfo *pkg /* may be NULL */);
+ void (*activate_done)(void);
+ void (*interest_change)(const char *name, struct pkginfo *pkg,
+ struct pkgbin *pkgbin,
+ int signum, enum trig_options opts);
+};
+
+static const struct trigkindinfo tki_explicit, tki_file, tki_unknown;
+static const struct trigkindinfo *dtki;
+
+/* As passed into activate_start. */
+static char *trig_activating_name;
+
+static const struct trigkindinfo *
+trig_classify_byname(const char *name)
+{
+ if (name[0] == '/') {
+ const char *slash;
+
+ slash = name;
+ while (slash) {
+ if (slash[1] == '\0' || slash[1] == '/')
+ goto invalid;
+
+ slash = strchr(slash + 2, '/');
+ }
+ return &tki_file;
+ }
+
+ if (!pkg_name_is_illegal(name) && !strchr(name, '_'))
+ return &tki_explicit;
+
+invalid:
+ return &tki_unknown;
+}
+
+static const char *
+trig_dump_trigger_options(enum trig_options opts)
+{
+ if (opts == TRIG_NOAWAIT)
+ return "/noawait";
+ return "";
+}
+
+/*
+ * Modifies the trigger string by removing the options at the ‘/’ delimiter
+ * and truncating it with a NUL character.
+ */
+static enum trig_options
+trig_parse_trigger_options(char *trigger)
+{
+ char *slash;
+
+ slash = strchr(trigger, '/');
+ if (slash == NULL)
+ return TRIG_AWAIT;
+
+ *slash++ = '\0';
+ if (strcmp("noawait", slash) == 0)
+ return TRIG_NOAWAIT;
+ if (strcmp("await", slash) == 0)
+ return TRIG_AWAIT;
+
+ /* XXX: Should perhaps warn or error for unknown keywords. */
+
+ return TRIG_AWAIT;
+}
+
+/*
+ * Calling sequence is:
+ * trig_activate_start(triggername);
+ * dtki->activate_awaiter(awaiting_package); } zero or more times
+ * dtki->activate_awaiter(NULL); } in any order
+ * dtki->activate_done();
+ */
+static void
+trig_activate_start(const char *name)
+{
+ dtki = trig_classify_byname(name);
+ trig_activating_name = nfstrsave(name);
+ dtki->activate_start();
+}
+
+/*---------- Unknown trigger kinds. ----------*/
+
+static void
+trk_unknown_activate_start(void)
+{
+}
+
+static void
+trk_unknown_activate_awaiter(struct pkginfo *aw)
+{
+}
+
+static void
+trk_unknown_activate_done(void)
+{
+}
+
+static void DPKG_ATTR_NORET
+trk_unknown_interest_change(const char *trig, struct pkginfo *pkg,
+ struct pkgbin *pkgbin, int signum,
+ enum trig_options opts)
+{
+ ohshit(_("invalid or unknown syntax in trigger name '%.250s'"
+ " (in trigger interests for package '%.250s')"),
+ trig, pkgbin_name(pkg, pkgbin, pnaw_nonambig));
+}
+
+static const struct trigkindinfo tki_unknown = {
+ .activate_start = trk_unknown_activate_start,
+ .activate_awaiter = trk_unknown_activate_awaiter,
+ .activate_done = trk_unknown_activate_done,
+ .interest_change = trk_unknown_interest_change,
+};
+
+/*---------- Explicit triggers. ----------*/
+
+static FILE *trk_explicit_f;
+static struct varbuf trk_explicit_fn;
+static char *trk_explicit_trig;
+
+static void
+trk_explicit_activate_done(void)
+{
+ if (trk_explicit_f) {
+ fclose(trk_explicit_f);
+ trk_explicit_f = NULL;
+ }
+}
+
+static void
+trk_explicit_start(const char *trig)
+{
+ trk_explicit_activate_done();
+
+ varbuf_reset(&trk_explicit_fn);
+ varbuf_add_dir(&trk_explicit_fn, triggersdir);
+ varbuf_add_str(&trk_explicit_fn, trig);
+ varbuf_end_str(&trk_explicit_fn);
+
+ trk_explicit_f = fopen(trk_explicit_fn.buf, "r");
+ if (!trk_explicit_f) {
+ if (errno != ENOENT)
+ ohshite(_("failed to open trigger interest list file '%.250s'"),
+ trk_explicit_fn.buf);
+ }
+}
+
+static int
+trk_explicit_fgets(char *buf, size_t sz)
+{
+ return fgets_checked(buf, sz, trk_explicit_f, trk_explicit_fn.buf);
+}
+
+static void
+trk_explicit_activate_start(void)
+{
+ trk_explicit_start(trig_activating_name);
+ trk_explicit_trig = trig_activating_name;
+}
+
+static void
+trk_explicit_activate_awaiter(struct pkginfo *aw)
+{
+ char buf[1024];
+
+ if (!trk_explicit_f)
+ return;
+
+ if (fseek(trk_explicit_f, 0, SEEK_SET))
+ ohshite(_("failed to rewind trigger interest file '%.250s'"),
+ trk_explicit_fn.buf);
+
+ while (trk_explicit_fgets(buf, sizeof(buf)) >= 0) {
+ struct pkginfo *pend;
+ struct dpkg_error err;
+ enum trig_options opts;
+
+ opts = trig_parse_trigger_options(buf);
+
+ pend = pkg_spec_parse_pkg(buf, &err);
+ if (pend == NULL)
+ ohshit(_("trigger interest file '%.250s' syntax error; "
+ "illegal package name '%.250s': %.250s"),
+ trk_explicit_fn.buf, buf, err.str);
+
+ trig_record_activation(pend, opts == TRIG_NOAWAIT ? NULL : aw,
+ trk_explicit_trig);
+ }
+}
+
+static void
+trk_explicit_interest_change(const char *trig, struct pkginfo *pkg,
+ struct pkgbin *pkgbin, int signum,
+ enum trig_options opts)
+{
+ char buf[1024];
+ struct atomic_file *file;
+ bool empty = true;
+
+ trk_explicit_start(trig);
+ file = atomic_file_new(trk_explicit_fn.buf, 0);
+ atomic_file_open(file);
+
+ while (trk_explicit_f && trk_explicit_fgets(buf, sizeof(buf)) >= 0) {
+ enum trig_options trig_opts;
+ struct pkginfo *pkg_parsed;
+ struct dpkg_error err;
+
+ trig_opts = trig_parse_trigger_options(buf);
+
+ pkg_parsed = pkg_spec_parse_pkg(buf, &err);
+ if (pkg_parsed == NULL)
+ ohshit(_("trigger interest file '%.250s' syntax error; "
+ "illegal package name '%.250s': %.250s"),
+ trk_explicit_fn.buf, buf, err.str);
+
+ if (pkg == pkg_parsed &&
+ (pkgbin == &pkg_parsed->installed ||
+ pkgbin == &pkg_parsed->available))
+ continue;
+
+ fprintf(file->fp, "%s%s\n", buf,
+ trig_dump_trigger_options(trig_opts));
+ empty = false;
+ }
+ if (signum > 0) {
+ fprintf(file->fp, "%s%s\n",
+ pkgbin_name(pkg, pkgbin, pnaw_same),
+ trig_dump_trigger_options(opts));
+ empty = false;
+ }
+
+ if (!empty)
+ atomic_file_sync(file);
+
+ atomic_file_close(file);
+
+ if (empty)
+ atomic_file_remove(file);
+ else
+ atomic_file_commit(file);
+
+ atomic_file_free(file);
+
+ dir_sync_path(triggersdir);
+}
+
+static const struct trigkindinfo tki_explicit = {
+ .activate_start = trk_explicit_activate_start,
+ .activate_awaiter = trk_explicit_activate_awaiter,
+ .activate_done = trk_explicit_activate_done,
+ .interest_change = trk_explicit_interest_change,
+};
+
+/*---------- File triggers. ----------*/
+
+static struct {
+ struct trigfileint *head, *tail;
+} filetriggers;
+
+/*
+ * Values:
+ * -1: Not read.
+ * 0: Not edited.
+ * 1: Edited
+ */
+static int filetriggers_edited = -1;
+
+/*
+ * Called by various people with signum -1 and +1 to mean remove and add
+ * and also by trig_file_interests_ensure() with signum +2 meaning add
+ * but die if already present.
+ */
+static void
+trk_file_interest_change(const char *trig, struct pkginfo *pkg,
+ struct pkgbin *pkgbin, int signum,
+ enum trig_options opts)
+{
+ struct fsys_namenode *fnn;
+ struct trigfileint **search, *tfi;
+
+ fnn = trigh.namenode_find(trig, signum <= 0);
+ if (!fnn) {
+ if (signum >= 0)
+ internerr("lost filename node '%s' for package %s "
+ "triggered to add", trig,
+ pkgbin_name(pkg, pkgbin, pnaw_always));
+ return;
+ }
+
+ for (search = trigh.namenode_interested(fnn);
+ (tfi = *search);
+ search = &tfi->samefile_next)
+ if (tfi->pkg == pkg)
+ goto found;
+
+ /* Not found. */
+ if (signum < 0)
+ return;
+
+ tfi = nfmalloc(sizeof(*tfi));
+ tfi->pkg = pkg;
+ tfi->pkgbin = pkgbin;
+ tfi->fnn = fnn;
+ tfi->options = opts;
+ tfi->samefile_next = *trigh.namenode_interested(fnn);
+ *trigh.namenode_interested(fnn) = tfi;
+
+ LIST_LINK_TAIL_PART(filetriggers, tfi, inoverall);
+ goto edited;
+
+found:
+ tfi->options = opts;
+ if (signum > 1)
+ ohshit(_("duplicate file trigger interest for filename '%.250s' "
+ "and package '%.250s'"), trig,
+ pkgbin_name(pkg, pkgbin, pnaw_nonambig));
+ if (signum > 0)
+ return;
+
+ /* Remove it: */
+ *search = tfi->samefile_next;
+ LIST_UNLINK_PART(filetriggers, tfi, inoverall);
+edited:
+ filetriggers_edited = 1;
+}
+
+static void
+trig_file_interests_remove(void)
+{
+ if (unlink(triggersfilefile) && errno != ENOENT)
+ ohshite(_("cannot remove '%.250s'"), triggersfilefile);
+}
+
+static void
+trig_file_interests_update(void)
+{
+ struct trigfileint *tfi;
+ struct atomic_file *file;
+
+ file = atomic_file_new(triggersfilefile, 0);
+ atomic_file_open(file);
+
+ for (tfi = filetriggers.head; tfi; tfi = tfi->inoverall.next)
+ fprintf(file->fp, "%s %s%s\n", trigh.namenode_name(tfi->fnn),
+ pkgbin_name(tfi->pkg, tfi->pkgbin, pnaw_same),
+ trig_dump_trigger_options(tfi->options));
+
+ atomic_file_sync(file);
+ atomic_file_close(file);
+ atomic_file_commit(file);
+ atomic_file_free(file);
+}
+
+void
+trig_file_interests_save(void)
+{
+ if (filetriggers_edited <= 0)
+ return;
+
+ if (!filetriggers.head)
+ trig_file_interests_remove();
+ else
+ trig_file_interests_update();
+
+ dir_sync_path(triggersdir);
+
+ filetriggers_edited = 0;
+}
+
+void
+trig_file_interests_ensure(void)
+{
+ FILE *f;
+ char linebuf[1024], *space;
+ struct pkginfo *pkg;
+ struct pkgbin *pkgbin;
+
+ if (filetriggers_edited >= 0)
+ return;
+
+ f = fopen(triggersfilefile, "r");
+ if (!f) {
+ if (errno == ENOENT)
+ goto ok;
+ ohshite(_("unable to read file triggers file '%.250s'"),
+ triggersfilefile);
+ }
+
+ push_cleanup(cu_closestream, ~0, 1, f);
+ while (fgets_checked(linebuf, sizeof(linebuf), f, triggersfilefile) >= 0) {
+ struct dpkg_error err;
+ enum trig_options trig_opts;
+
+ space = strchr(linebuf, ' ');
+ if (!space || linebuf[0] != '/')
+ ohshit(_("syntax error in file triggers file '%.250s'"),
+ triggersfilefile);
+ *space++ = '\0';
+
+ trig_opts = trig_parse_trigger_options(space);
+
+ pkg = pkg_spec_parse_pkg(space, &err);
+ if (pkg == NULL)
+ ohshit(_("file triggers record mentions illegal "
+ "package name '%.250s' (for interest in file "
+ "'%.250s'): %.250s"), space, linebuf, err.str);
+ pkgbin = &pkg->installed;
+
+ trk_file_interest_change(linebuf, pkg, pkgbin, +2, trig_opts);
+ }
+ pop_cleanup(ehflag_normaltidy);
+ok:
+ filetriggers_edited = 0;
+}
+
+void
+trig_file_activate_byname(const char *trig, struct pkginfo *aw)
+{
+ struct fsys_namenode *fnn = trigh.namenode_find(trig, 1);
+
+ if (fnn)
+ trig_file_activate(fnn, aw);
+}
+
+void
+trig_file_activate(struct fsys_namenode *trig, struct pkginfo *aw)
+{
+ struct trigfileint *tfi;
+
+ for (tfi = *trigh.namenode_interested(trig); tfi;
+ tfi = tfi->samefile_next)
+ trig_record_activation(tfi->pkg, (tfi->options == TRIG_NOAWAIT) ?
+ NULL : aw, trigh.namenode_name(trig));
+}
+
+static void
+trig_file_activate_parents(const char *trig, struct pkginfo *aw)
+{
+ char *path, *slash;
+
+ /* Traverse the whole pathname to activate all of its components. */
+ path = m_strdup(trig);
+
+ while ((slash = strrchr(path, '/'))) {
+ *slash = '\0';
+ trig_file_activate_byname(path, aw);
+ }
+
+ free(path);
+}
+
+void
+trig_path_activate(struct fsys_namenode *trig, struct pkginfo *aw)
+{
+ trig_file_activate(trig, aw);
+ trig_file_activate_parents(trigh.namenode_name(trig), aw);
+}
+
+static void
+trig_path_activate_byname(const char *trig, struct pkginfo *aw)
+{
+ struct fsys_namenode *fnn = trigh.namenode_find(trig, 1);
+
+ if (fnn)
+ trig_file_activate(fnn, aw);
+
+ trig_file_activate_parents(trig, aw);
+}
+
+static const char *trk_file_trig;
+
+static void
+trk_file_activate_start(void)
+{
+ trk_file_trig = trig_activating_name;
+}
+
+static void
+trk_file_activate_awaiter(struct pkginfo *aw)
+{
+ trig_path_activate_byname(trk_file_trig, aw);
+}
+
+static void
+trk_file_activate_done(void)
+{
+}
+
+static const struct trigkindinfo tki_file = {
+ .activate_start = trk_file_activate_start,
+ .activate_awaiter = trk_file_activate_awaiter,
+ .activate_done = trk_file_activate_done,
+ .interest_change = trk_file_interest_change,
+};
+
+/*---------- Trigger control info file. ----------*/
+
+static void
+trig_cicb_interest_change(const char *trig, struct pkginfo *pkg,
+ struct pkgbin *pkgbin, int signum,
+ enum trig_options opts)
+{
+ const struct trigkindinfo *tki = trig_classify_byname(trig);
+
+ if (filetriggers_edited < 0)
+ internerr("trigger control file for package %s not read",
+ pkgbin_name(pkg, pkgbin, pnaw_always));
+
+ tki->interest_change(trig, pkg, pkgbin, signum, opts);
+}
+
+void
+trig_cicb_interest_delete(const char *trig, struct pkginfo *pkg,
+ struct pkgbin *pkgbin, enum trig_options opts)
+{
+ trig_cicb_interest_change(trig, pkg, pkgbin, -1, opts);
+}
+
+void
+trig_cicb_interest_add(const char *trig, struct pkginfo *pkg,
+ struct pkgbin *pkgbin, enum trig_options opts)
+{
+ trig_cicb_interest_change(trig, pkg, pkgbin, +1, opts);
+}
+
+void
+trig_cicb_statuschange_activate(const char *trig, struct pkginfo *pkg,
+ struct pkgbin *pkgbin, enum trig_options opts)
+{
+ struct pkginfo *aw = pkg;
+
+ trig_activate_start(trig);
+ dtki->activate_awaiter((opts == TRIG_NOAWAIT) ? NULL : aw);
+ dtki->activate_done();
+}
+
+static void
+parse_ci_call(const char *file, const char *cmd, trig_parse_cicb *cb,
+ const char *trig, struct pkginfo *pkg, struct pkgbin *pkgbin,
+ enum trig_options opts)
+{
+ const char *emsg;
+
+ emsg = trig_name_is_illegal(trig);
+ if (emsg)
+ ohshit(_("triggers ci file '%.250s' contains illegal trigger "
+ "syntax in trigger name '%.250s': %.250s"),
+ file, trig, emsg);
+ if (cb)
+ cb(trig, pkg, pkgbin, opts);
+}
+
+void
+trig_parse_ci(const char *file, trig_parse_cicb *interest,
+ trig_parse_cicb *activate, struct pkginfo *pkg,
+ struct pkgbin *pkgbin)
+{
+ FILE *f;
+ char linebuf[MAXTRIGDIRECTIVE], *cmd, *spc, *eol;
+ int l;
+
+ f = fopen(file, "r");
+ if (!f) {
+ if (errno == ENOENT)
+ return; /* No file is just like an empty one. */
+ ohshite(_("unable to open triggers ci file '%.250s'"), file);
+ }
+ push_cleanup(cu_closestream, ~0, 1, f);
+
+ while ((l = fgets_checked(linebuf, sizeof(linebuf), f, file)) >= 0) {
+ for (cmd = linebuf; c_iswhite(*cmd); cmd++) ;
+ if (*cmd == '#')
+ continue;
+ for (eol = linebuf + l; eol > cmd && c_iswhite(eol[-1]); eol--) ;
+ if (eol == cmd)
+ continue;
+ *eol = '\0';
+
+ for (spc = cmd; *spc && !c_iswhite(*spc); spc++) ;
+ if (!*spc)
+ ohshit(_("triggers ci file contains unknown directive syntax"));
+ *spc++ = '\0';
+ while (c_iswhite(*spc))
+ spc++;
+ if (strcmp(cmd, "interest") == 0 ||
+ strcmp(cmd, "interest-await") == 0) {
+ parse_ci_call(file, cmd, interest, spc, pkg, pkgbin, TRIG_AWAIT);
+ } else if (strcmp(cmd, "interest-noawait") == 0) {
+ parse_ci_call(file, cmd, interest, spc, pkg, pkgbin, TRIG_NOAWAIT);
+ } else if (strcmp(cmd, "activate") == 0 ||
+ strcmp(cmd, "activate-await") == 0) {
+ parse_ci_call(file, cmd, activate, spc, pkg, pkgbin, TRIG_AWAIT);
+ } else if (strcmp(cmd, "activate-noawait") == 0) {
+ parse_ci_call(file, cmd, activate, spc, pkg, pkgbin, TRIG_NOAWAIT);
+ } else {
+ ohshit(_("triggers ci file contains unknown directive '%.250s'"),
+ cmd);
+ }
+ }
+ pop_cleanup(ehflag_normaltidy); /* fclose() */
+}
+
+/*---------- Unincorp file incorporation. ----------*/
+
+static void
+tdm_incorp_trig_begin(const char *trig)
+{
+ trig_activate_start(trig);
+}
+
+static void
+tdm_incorp_package(const char *awname)
+{
+ struct pkginfo *aw;
+
+ if (strcmp(awname, "-") == 0)
+ aw = NULL;
+ else
+ aw = pkg_spec_parse_pkg(awname, NULL);
+
+ dtki->activate_awaiter(aw);
+}
+
+static void
+tdm_incorp_trig_end(void)
+{
+ dtki->activate_done();
+}
+
+static const struct trigdefmeths tdm_incorp = {
+ .trig_begin = tdm_incorp_trig_begin,
+ .package = tdm_incorp_package,
+ .trig_end = tdm_incorp_trig_end
+};
+
+void
+trig_incorporate(enum modstatdb_rw cstatus)
+{
+ enum trigdef_update_status ur;
+ enum trigdef_update_flags tduf;
+
+ free(triggersdir);
+ triggersdir = dpkg_db_get_path(TRIGGERSDIR);
+
+ free(triggersfilefile);
+ triggersfilefile = trig_get_filename(triggersdir, TRIGGERSFILEFILE);
+
+ trigdef_set_methods(&tdm_incorp);
+ trig_file_interests_ensure();
+
+ tduf = TDUF_NO_LOCK_OK;
+ if (cstatus >= msdbrw_write) {
+ tduf |= TDUF_WRITE;
+ if (trigh.transitional_activate)
+ tduf |= TDUF_WRITE_IF_ENOENT;
+ }
+
+ ur = trigdef_update_start(tduf);
+ if (ur == TDUS_ERROR_NO_DIR && cstatus >= msdbrw_write) {
+ if (mkdir(triggersdir, 0755)) {
+ if (errno != EEXIST)
+ ohshite(_("unable to create triggers state"
+ " directory '%.250s'"), triggersdir);
+ }
+ ur = trigdef_update_start(tduf);
+ }
+ switch (ur) {
+ case TDUS_ERROR_EMPTY_DEFERRED:
+ return;
+ case TDUS_ERROR_NO_DIR:
+ case TDUS_ERROR_NO_DEFERRED:
+ if (!trigh.transitional_activate)
+ return;
+ /* Fall through. */
+ case TDUS_NO_DEFERRED:
+ trigh.transitional_activate(cstatus);
+ break;
+ case TDUS_OK:
+ /* Read and incorporate triggers. */
+ trigdef_parse();
+ break;
+ default:
+ internerr("unknown trigdef_update_start return value '%d'", ur);
+ }
+
+ /* Right, that's it. New (empty) Unincorp can be installed. */
+ trigdef_process_done();
+}
+
+/*---------- Default hooks. ----------*/
+
+TRIGHOOKS_DEFINE_NAMENODE_ACCESSORS
+
+static struct trig_hooks trigh = {
+ .enqueue_deferred = NULL,
+ .transitional_activate = NULL,
+ .namenode_find = th_nn_find,
+ .namenode_interested = th_nn_interested,
+ .namenode_name = th_nn_name,
+};
+
+void
+trig_override_hooks(const struct trig_hooks *hooks)
+{
+ trigh = *hooks;
+}
diff --git a/lib/dpkg/triglib.h b/lib/dpkg/triglib.h
new file mode 100644
index 0000000..e4e101b
--- /dev/null
+++ b/lib/dpkg/triglib.h
@@ -0,0 +1,129 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * triglib.h - declarations for trigger handling
+ *
+ * Copyright © 2007 Canonical, Ltd.
+ * written by Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_TRIGLIB_H
+#define LIBDPKG_TRIGLIB_H
+
+#include <dpkg/macros.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/fsys.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup triglib Trigger handling
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+/*
+ * Hooks for more sophisticated processing in dpkg proper.
+ *
+ * We do things like this so we can get most of the trigger tracking
+ * in dpkg-query, dselect, and so on, but avoid the transitional
+ * processing and deferred trigproc queue management other than when
+ * we're actually doing real package management work.
+ */
+
+const char *trig_name_is_illegal(const char *p);
+
+enum trig_options {
+ TRIG_AWAIT,
+ TRIG_NOAWAIT,
+};
+
+struct trigfileint {
+ struct pkginfo *pkg;
+ struct pkgbin *pkgbin;
+ struct fsys_namenode *fnn;
+ enum trig_options options;
+ struct trigfileint *samefile_next;
+ struct {
+ struct trigfileint *next, *prev;
+ } inoverall;
+};
+
+/**
+ * The first two hooks are normally NULL.
+ * If non-NULL, we're dpkg proper and we might need to invent trigger
+ * activations as the first run of a triggers-supporting dpkg.
+ */
+struct trig_hooks {
+ void (*enqueue_deferred)(struct pkginfo *pend);
+ void (*transitional_activate)(enum modstatdb_rw cstatus);
+
+ struct fsys_namenode *(*namenode_find)(const char *filename, bool nonew);
+ struct trigfileint **(*namenode_interested)(struct fsys_namenode *fnn);
+
+ /** Returns a pointer from nfmalloc. */
+ const char *(*namenode_name)(struct fsys_namenode *fnn);
+};
+
+#define TRIGHOOKS_DEFINE_NAMENODE_ACCESSORS \
+ static struct fsys_namenode *th_nn_find(const char *name, bool nonew) \
+ { return fsys_hash_find_node(name, nonew ? FHFF_NO_NEW : FHFF_NONE); } \
+ static struct trigfileint **th_nn_interested(struct fsys_namenode *fnn) \
+ { return &fnn->trig_interested; } \
+ static const char *th_nn_name(struct fsys_namenode *fnn) \
+ { return fnn->name; }
+
+void trig_override_hooks(const struct trig_hooks *hooks);
+
+void trig_file_activate_byname(const char *trig, struct pkginfo *aw);
+void trig_file_activate(struct fsys_namenode *trig, struct pkginfo *aw);
+void trig_path_activate(struct fsys_namenode *trig, struct pkginfo *aw);
+
+bool trig_note_pend_core(struct pkginfo *pend, const char *trig /*not copied!*/);
+bool trig_note_pend(struct pkginfo *pend, const char *trig /*not copied!*/);
+bool trig_note_aw(struct pkginfo *pend, struct pkginfo *aw);
+void trig_clear_awaiters(struct pkginfo *notpend);
+
+typedef void trig_awaited_pend_foreach_func(struct pkginfo *pkg);
+
+void trig_awaited_pend_enqueue(struct pkginfo *pend);
+void trig_awaited_pend_foreach(trig_awaited_pend_foreach_func *func);
+void trig_awaited_pend_free(void);
+
+void trig_fixup_awaiters(enum modstatdb_rw cstatus);
+
+void trig_file_interests_ensure(void);
+void trig_file_interests_save(void);
+
+typedef void trig_parse_cicb(const char *trig, struct pkginfo *pkg,
+ struct pkgbin *pkgbin, enum trig_options to);
+void trig_cicb_interest_delete(const char *trig, struct pkginfo *pkg,
+ struct pkgbin *pkgbin, enum trig_options to);
+void trig_cicb_interest_add(const char *trig, struct pkginfo *pkg,
+ struct pkgbin *pkgbin, enum trig_options to);
+void trig_cicb_statuschange_activate(const char *trig, struct pkginfo *pkg,
+ struct pkgbin *pkgbin, enum trig_options to);
+void trig_parse_ci(const char *file, trig_parse_cicb *interest,
+ trig_parse_cicb *activate, struct pkginfo *pkg,
+ struct pkgbin *pkgbin);
+
+void trig_incorporate(enum modstatdb_rw cstatus);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_TRIGLIB_H */
diff --git a/lib/dpkg/trigname.c b/lib/dpkg/trigname.c
new file mode 100644
index 0000000..d271140
--- /dev/null
+++ b/lib/dpkg/trigname.c
@@ -0,0 +1,42 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * trigname.c - trigger name handling
+ *
+ * Copyright © 2007 Canonical Ltd
+ * Written by Ian Jackson <ijackson@chiark.greenend.org.uk>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/triglib.h>
+
+const char *
+trig_name_is_illegal(const char *p)
+{
+ int c;
+
+ if (!*p)
+ return _("empty trigger names are not permitted");
+
+ while ((c = *p++)) {
+ if (c <= ' ' || c >= 0177)
+ return _("trigger name contains invalid character");
+ }
+
+ return NULL;
+}
diff --git a/lib/dpkg/trignote.c b/lib/dpkg/trignote.c
new file mode 100644
index 0000000..957e808
--- /dev/null
+++ b/lib/dpkg/trignote.c
@@ -0,0 +1,133 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * trignote.c - trigger note handling
+ *
+ * Copyright © 2007 Canonical Ltd
+ * Written by Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <string.h>
+
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg.h>
+#include <dpkg/pkg-list.h>
+#include <dpkg/dlist.h>
+#include <dpkg/triglib.h>
+
+/*
+ * Note: This is also called from fields.c where *pend is a temporary!
+ *
+ * trig is not copied!
+ */
+bool
+trig_note_pend_core(struct pkginfo *pend, const char *trig)
+{
+ struct trigpend *tp;
+
+ for (tp = pend->trigpend_head; tp; tp = tp->next)
+ if (strcmp(tp->name, trig) == 0)
+ return false;
+
+ tp = nfmalloc(sizeof(*tp));
+ tp->name = trig;
+ tp->next = pend->trigpend_head;
+ pend->trigpend_head = tp;
+
+ return true;
+}
+
+/*
+ * trig is not copied!
+ *
+ * @retval true For done.
+ * @retval false For already noted.
+ */
+bool
+trig_note_pend(struct pkginfo *pend, const char *trig)
+{
+ if (!trig_note_pend_core(pend, trig))
+ return false;
+
+ if (pend->trigaw.head)
+ pkg_set_status(pend, PKG_STAT_TRIGGERSAWAITED);
+ else
+ pkg_set_status(pend, PKG_STAT_TRIGGERSPENDING);
+
+ return true;
+}
+
+/*
+ * Note: This is called also from fields.c where *aw is a temporary
+ * but pend is from pkg_hash_find()!
+ *
+ * @retval true For done.
+ * @retval false For already noted.
+ */
+bool
+trig_note_aw(struct pkginfo *pend, struct pkginfo *aw)
+{
+ struct trigaw *ta;
+
+ /* We search through aw's list because that's probably shorter. */
+ for (ta = aw->trigaw.head; ta; ta = ta->sameaw.next)
+ if (ta->pend == pend)
+ return false;
+
+ ta = nfmalloc(sizeof(*ta));
+ ta->aw = aw;
+ ta->pend = pend;
+ ta->samepend_next = pend->othertrigaw_head;
+ pend->othertrigaw_head = ta;
+ LIST_LINK_TAIL_PART(aw->trigaw, ta, sameaw);
+
+ return true;
+}
+
+static struct pkg_list *trig_awaited_pend_head;
+
+void
+trig_awaited_pend_enqueue(struct pkginfo *pend)
+{
+ struct pkg_list *tp;
+
+ for (tp = trig_awaited_pend_head; tp; tp = tp->next)
+ if (tp->pkg == pend)
+ return;
+
+ pkg_list_prepend(&trig_awaited_pend_head, pend);
+}
+
+void
+trig_awaited_pend_foreach(trig_awaited_pend_foreach_func *func)
+{
+ struct pkg_list *tp;
+
+ for (tp = trig_awaited_pend_head; tp; tp = tp->next)
+ if (!tp->pkg->trigpend_head)
+ func(tp->pkg);
+}
+
+void
+trig_awaited_pend_free(void)
+{
+ pkg_list_free(trig_awaited_pend_head);
+ trig_awaited_pend_head = NULL;
+}
diff --git a/lib/dpkg/utils.c b/lib/dpkg/utils.c
new file mode 100644
index 0000000..363624c
--- /dev/null
+++ b/lib/dpkg/utils.c
@@ -0,0 +1,58 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * utils.c - helper functions for dpkg
+ *
+ * Copyright © 1995, 2008 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <string.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+
+int
+fgets_checked(char *buf, size_t bufsz, FILE *f, const char *fn)
+{
+ int l;
+
+ if (!fgets(buf, bufsz, f)) {
+ if (ferror(f))
+ ohshite(_("read error in '%.250s'"), fn);
+ return -1;
+ }
+ l = strlen(buf);
+ if (l == 0)
+ ohshit(_("fgets gave an empty string from '%.250s'"), fn);
+ if (buf[--l] != '\n')
+ ohshit(_("too-long line or missing newline in '%.250s'"), fn);
+ buf[l] = '\0';
+
+ return l;
+}
+
+int
+fgets_must(char *buf, size_t bufsz, FILE *f, const char *fn)
+{
+ int l = fgets_checked(buf, bufsz, f, fn);
+
+ if (l < 0)
+ ohshit(_("unexpected end of file reading '%.250s'"), fn);
+
+ return l;
+}
diff --git a/lib/dpkg/varbuf.c b/lib/dpkg/varbuf.c
new file mode 100644
index 0000000..a5d3d31
--- /dev/null
+++ b/lib/dpkg/varbuf.c
@@ -0,0 +1,276 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * varbuf.c - variable length expandable buffer handling
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008-2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+
+struct varbuf *
+varbuf_new(size_t size)
+{
+ struct varbuf *v;
+
+ v = m_malloc(sizeof(*v));
+ varbuf_init(v, size);
+
+ return v;
+}
+
+void
+varbuf_init(struct varbuf *v, size_t size)
+{
+ v->used = 0;
+ v->size = size;
+ if (size)
+ v->buf = m_malloc(size);
+ else
+ v->buf = NULL;
+}
+
+void
+varbuf_grow(struct varbuf *v, size_t need_size)
+{
+ size_t new_size;
+
+ /* Make sure the varbuf is in a sane state. */
+ if (v->size < v->used)
+ internerr("varbuf used(%zu) > size(%zu)", v->used, v->size);
+
+ /* Check if we already have enough room. */
+ if ((v->size - v->used) >= need_size)
+ return;
+
+ /* Check if we overflow. */
+ new_size = (v->size + need_size) * 2;
+ if (new_size < v->size)
+ ohshit(_("cannot grow varbuf to size %zu; it would overflow"),
+ need_size);
+
+ v->size = new_size;
+ v->buf = m_realloc(v->buf, v->size);
+}
+
+void
+varbuf_trunc(struct varbuf *v, size_t used_size)
+{
+ /* Make sure the caller does not claim more than available. */
+ if (v->size < used_size)
+ internerr("varbuf new_used(%zu) > size(%zu)", used_size, v->size);
+
+ v->used = used_size;
+}
+
+void
+varbuf_reset(struct varbuf *v)
+{
+ v->used = 0;
+}
+
+const char *
+varbuf_get_str(struct varbuf *v)
+{
+ varbuf_end_str(v);
+
+ return v->buf;
+}
+
+void
+varbuf_set_buf(struct varbuf *v, const void *buf, size_t size)
+{
+ varbuf_reset(v);
+ varbuf_add_buf(v, buf, size);
+ varbuf_end_str(v);
+}
+
+void
+varbuf_set_varbuf(struct varbuf *v, struct varbuf *other)
+{
+ varbuf_set_buf(v, other->buf, other->used);
+}
+
+void
+varbuf_add_varbuf(struct varbuf *v, const struct varbuf *other)
+{
+ varbuf_grow(v, other->used);
+ memcpy(v->buf + v->used, other->buf, other->used);
+ v->used += other->used;
+}
+
+void
+varbuf_add_char(struct varbuf *v, int c)
+{
+ varbuf_grow(v, 1);
+ v->buf[v->used++] = c;
+}
+
+void
+varbuf_dup_char(struct varbuf *v, int c, size_t n)
+{
+ if (n == 0)
+ return;
+ varbuf_grow(v, n);
+ memset(v->buf + v->used, c, n);
+ v->used += n;
+}
+
+void
+varbuf_map_char(struct varbuf *v, int c_src, int c_dst)
+{
+ size_t i;
+
+ for (i = 0; i < v->used; i++)
+ if (v->buf[i] == c_src)
+ v->buf[i] = c_dst;
+}
+
+void
+varbuf_add_dir(struct varbuf *v, const char *dirname)
+{
+ varbuf_add_str(v, dirname);
+ if (v->used == 0 || v->buf[v->used - 1] != '/')
+ varbuf_add_char(v, '/');
+}
+
+void
+varbuf_add_buf(struct varbuf *v, const void *s, size_t size)
+{
+ if (size == 0)
+ return;
+ varbuf_grow(v, size);
+ memcpy(v->buf + v->used, s, size);
+ v->used += size;
+}
+
+void
+varbuf_end_str(struct varbuf *v)
+{
+ varbuf_grow(v, 1);
+ v->buf[v->used] = '\0';
+}
+
+int
+varbuf_vprintf(struct varbuf *v, const char *fmt, va_list args)
+{
+ va_list args_copy;
+ int needed, n;
+
+ va_copy(args_copy, args);
+ needed = vsnprintf(NULL, 0, fmt, args_copy);
+ va_end(args_copy);
+
+ if (needed < 0)
+ ohshite(_("error formatting string into varbuf variable"));
+
+ varbuf_grow(v, needed + 1);
+
+ n = vsnprintf(v->buf + v->used, needed + 1, fmt, args);
+ if (n < 0)
+ ohshite(_("error formatting string into varbuf variable"));
+
+ v->used += n;
+
+ return n;
+}
+
+int
+varbuf_printf(struct varbuf *v, const char *fmt, ...)
+{
+ va_list args;
+ int n;
+
+ va_start(args, fmt);
+ n = varbuf_vprintf(v, fmt, args);
+ va_end(args);
+
+ return n;
+}
+
+void
+varbuf_snapshot(struct varbuf *v, struct varbuf_state *vs)
+{
+ vs->v = v;
+ vs->used = v->used;
+}
+
+void
+varbuf_rollback(struct varbuf_state *vs)
+{
+ varbuf_trunc(vs->v, vs->used);
+}
+
+size_t
+varbuf_rollback_len(struct varbuf_state *vs)
+{
+ if (vs->used > vs->v->used)
+ internerr("varbuf state_used(%zu) > used(%zu)",
+ vs->used, vs->v->used);
+ return vs->v->used - vs->used;
+}
+
+const char *
+varbuf_rollback_start(struct varbuf_state *vs)
+{
+ if (vs->v->buf == NULL) {
+ if (vs->used)
+ internerr("varbuf buf(NULL) state_used(%zu) > 0",
+ vs->used);
+ /* XXX: Ideally this would be handled by varbuf always having
+ * a valid buf or switching all users to the getter, but for
+ * now this will do. */
+ return "";
+ }
+ return vs->v->buf + vs->used;
+}
+
+char *
+varbuf_detach(struct varbuf *v)
+{
+ char *buf = v->buf;
+
+ v->buf = NULL;
+ v->size = 0;
+ v->used = 0;
+
+ return buf;
+}
+
+void
+varbuf_destroy(struct varbuf *v)
+{
+ free(v->buf);
+ v->buf = NULL;
+ v->size = 0;
+ v->used = 0;
+}
+
+void
+varbuf_free(struct varbuf *v)
+{
+ free(v->buf);
+ free(v);
+}
diff --git a/lib/dpkg/varbuf.h b/lib/dpkg/varbuf.h
new file mode 100644
index 0000000..e58dac5
--- /dev/null
+++ b/lib/dpkg/varbuf.h
@@ -0,0 +1,189 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * varbuf.h - variable length expandable buffer handling
+ *
+ * Copyright © 1994, 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008-2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_VARBUF_H
+#define LIBDPKG_VARBUF_H
+
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include <dpkg/macros.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup varbuf Variable length buffers
+ * @ingroup dpkg-public
+ * @{
+ */
+
+/**
+ * varbuf_init must be called exactly once before the use of each varbuf
+ * (including before any call to varbuf_destroy), or the variable must be
+ * initialized with VARBUF_INIT.
+ *
+ * However, varbufs allocated ‘static’ are properly initialized anyway and
+ * do not need varbuf_init; multiple consecutive calls to varbuf_init before
+ * any use are allowed.
+ *
+ * varbuf_destroy must be called after a varbuf is finished with, if anything
+ * other than varbuf_init has been done. After this you are allowed but
+ * not required to call varbuf_init again if you want to start using the
+ * varbuf again.
+ *
+ * Callers using C++ need not worry about any of this.
+ */
+struct varbuf {
+ size_t used, size;
+ char *buf;
+
+#ifdef __cplusplus
+ explicit varbuf(size_t _size = 0);
+ ~varbuf();
+ void init(size_t _size = 0);
+ void reset();
+ void destroy();
+ int fmt(const char *_fmt, ...) DPKG_ATTR_PRINTF(2);
+ int vfmt(const char *_fmt, va_list va) DPKG_ATTR_VPRINTF(2);
+ void operator()(int c);
+ void operator()(const char *s);
+ const char *string();
+#endif
+};
+
+#define VARBUF_INIT { 0, 0, NULL }
+
+#define VARBUF_OBJECT (struct varbuf)VARBUF_INIT
+
+struct varbuf *varbuf_new(size_t size);
+void varbuf_init(struct varbuf *v, size_t size);
+void varbuf_grow(struct varbuf *v, size_t need_size);
+void varbuf_trunc(struct varbuf *v, size_t used_size);
+char *varbuf_detach(struct varbuf *v);
+void varbuf_reset(struct varbuf *v);
+void varbuf_destroy(struct varbuf *v);
+void varbuf_free(struct varbuf *v);
+
+void varbuf_set_varbuf(struct varbuf *v, struct varbuf *other);
+void varbuf_set_buf(struct varbuf *v, const void *buf, size_t size);
+#define varbuf_set_str(v, s) varbuf_set_buf(v, s, strlen(s))
+#define varbuf_set_strn(v, s, n) varbuf_set_buf(v, s, strnlen(s, n))
+
+void varbuf_add_varbuf(struct varbuf *v, const struct varbuf *other);
+void varbuf_add_char(struct varbuf *v, int c);
+void varbuf_dup_char(struct varbuf *v, int c, size_t n);
+void varbuf_map_char(struct varbuf *v, int c_src, int c_dst);
+#define varbuf_add_str(v, s) varbuf_add_buf(v, s, strlen(s))
+#define varbuf_add_strn(v, s, n) varbuf_add_buf(v, s, strnlen(s, n))
+void varbuf_add_dir(struct varbuf *v, const char *dirname);
+void varbuf_add_buf(struct varbuf *v, const void *s, size_t size);
+void varbuf_end_str(struct varbuf *v);
+const char *varbuf_get_str(struct varbuf *v);
+
+int varbuf_printf(struct varbuf *v, const char *fmt, ...) DPKG_ATTR_PRINTF(2);
+int varbuf_vprintf(struct varbuf *v, const char *fmt, va_list va)
+ DPKG_ATTR_VPRINTF(2);
+
+struct varbuf_state {
+ struct varbuf *v;
+ size_t used;
+};
+
+void varbuf_snapshot(struct varbuf *v, struct varbuf_state *vs);
+void varbuf_rollback(struct varbuf_state *vs);
+size_t varbuf_rollback_len(struct varbuf_state *vs);
+const char *varbuf_rollback_start(struct varbuf_state *vs);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#ifdef __cplusplus
+inline
+varbuf::varbuf(size_t _size)
+{
+ varbuf_init(this, _size);
+}
+
+inline
+varbuf::~varbuf()
+{
+ varbuf_destroy(this);
+}
+
+inline void
+varbuf::init(size_t _size)
+{
+ varbuf_init(this, _size);
+}
+
+inline void
+varbuf::reset()
+{
+ used = 0;
+}
+
+inline void
+varbuf::destroy()
+{
+ varbuf_destroy(this);
+}
+
+inline int
+varbuf::fmt(const char *_fmt, ...)
+{
+ va_list args;
+ int rc;
+
+ va_start(args, _fmt);
+ rc = varbuf_vprintf(this, _fmt, args);
+ va_end(args);
+
+ return rc;
+}
+
+inline int
+varbuf::vfmt(const char *_fmt, va_list va)
+{
+ return varbuf_vprintf(this, _fmt, va);
+}
+
+inline void
+varbuf::operator()(int c)
+{
+ varbuf_add_char(this, c);
+}
+
+inline void
+varbuf::operator()(const char *s)
+{
+ varbuf_add_str(this, s);
+}
+
+inline const char *
+varbuf::string()
+{
+ return varbuf_get_str(this);
+}
+#endif
+
+#endif /* LIBDPKG_VARBUF_H */
diff --git a/lib/dpkg/version.c b/lib/dpkg/version.c
new file mode 100644
index 0000000..e9bc4bb
--- /dev/null
+++ b/lib/dpkg/version.c
@@ -0,0 +1,198 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * version.c - version handling functions
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/c-ctype.h>
+#include <dpkg/ehandle.h>
+#include <dpkg/string.h>
+#include <dpkg/version.h>
+
+/**
+ * Turn the passed version into an empty version.
+ *
+ * This can be used to ensure the version is properly initialized.
+ *
+ * @param version The version to clear.
+ */
+void
+dpkg_version_blank(struct dpkg_version *version)
+{
+ version->epoch = 0;
+ version->version = NULL;
+ version->revision = NULL;
+}
+
+/**
+ * Test if a version is not empty.
+ *
+ * @param version The version to test.
+ *
+ * @retval true If the version is informative (i.e. not an empty version).
+ * @retval false If the version is empty.
+ */
+bool
+dpkg_version_is_informative(const struct dpkg_version *version)
+{
+ return (version->epoch ||
+ str_is_set(version->version) ||
+ str_is_set(version->revision));
+}
+
+/**
+ * Give a weight to the character to order in the version comparison.
+ *
+ * @param c An ASCII character.
+ */
+static int
+order(int c)
+{
+ if (c_isdigit(c))
+ return 0;
+ else if (c_isalpha(c))
+ return c;
+ else if (c == '~')
+ return -1;
+ else if (c)
+ return c + 256;
+ else
+ return 0;
+}
+
+static int
+verrevcmp(const char *a, const char *b)
+{
+ if (a == NULL)
+ a = "";
+ if (b == NULL)
+ b = "";
+
+ while (*a || *b) {
+ int first_diff = 0;
+
+ while ((*a && !c_isdigit(*a)) || (*b && !c_isdigit(*b))) {
+ int ac = order(*a);
+ int bc = order(*b);
+
+ if (ac != bc)
+ return ac - bc;
+
+ a++;
+ b++;
+ }
+ while (*a == '0')
+ a++;
+ while (*b == '0')
+ b++;
+ while (c_isdigit(*a) && c_isdigit(*b)) {
+ if (!first_diff)
+ first_diff = *a - *b;
+ a++;
+ b++;
+ }
+
+ if (c_isdigit(*a))
+ return 1;
+ if (c_isdigit(*b))
+ return -1;
+ if (first_diff)
+ return first_diff;
+ }
+
+ return 0;
+}
+
+/**
+ * Compares two Debian versions.
+ *
+ * This function follows the convention of the comparator functions used by
+ * qsort().
+ *
+ * @see deb-version(5)
+ *
+ * @param a The first version.
+ * @param b The second version.
+ *
+ * @retval 0 If a and b are equal.
+ * @retval <0 If a is smaller than b.
+ * @retval >0 If a is greater than b.
+ */
+int
+dpkg_version_compare(const struct dpkg_version *a,
+ const struct dpkg_version *b)
+{
+ int rc;
+
+ if (a->epoch > b->epoch)
+ return 1;
+ if (a->epoch < b->epoch)
+ return -1;
+
+ rc = verrevcmp(a->version, b->version);
+ if (rc)
+ return rc;
+
+ return verrevcmp(a->revision, b->revision);
+}
+
+/**
+ * Check if two versions have a certain relation.
+ *
+ * @param a The first version.
+ * @param rel The relation.
+ * @param b The second version.
+ *
+ * @retval true If rel is #DPKG_RELATION_NONE or the expression “a rel b” is
+ * true.
+ * @retval false Otherwise.
+ *
+ * @warning If rel is not a valid relation, this function will terminate
+ * the program.
+ */
+bool
+dpkg_version_relate(const struct dpkg_version *a,
+ enum dpkg_relation rel,
+ const struct dpkg_version *b)
+{
+ int rc;
+
+ if (rel == DPKG_RELATION_NONE)
+ return true;
+
+ rc = dpkg_version_compare(a, b);
+
+ switch (rel) {
+ case DPKG_RELATION_EQ:
+ return rc == 0;
+ case DPKG_RELATION_LT:
+ return rc < 0;
+ case DPKG_RELATION_LE:
+ return rc <= 0;
+ case DPKG_RELATION_GT:
+ return rc > 0;
+ case DPKG_RELATION_GE:
+ return rc >= 0;
+ default:
+ internerr("unknown dpkg_relation %d", rel);
+ }
+ return false;
+}
diff --git a/lib/dpkg/version.h b/lib/dpkg/version.h
new file mode 100644
index 0000000..7c3f027
--- /dev/null
+++ b/lib/dpkg/version.h
@@ -0,0 +1,91 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * version.h - version handling routines
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2011-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_VERSION_H
+#define LIBDPKG_VERSION_H
+
+#include <stdbool.h>
+
+#include <dpkg/macros.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup version Version handling
+ * @ingroup dpkg-public
+ * @{
+ */
+
+/**
+ * Data structure representing a Debian version.
+ *
+ * @see deb-version(5)
+ */
+struct dpkg_version {
+ /** The epoch. It will be zero if no epoch is present. */
+ unsigned int epoch;
+ /** The upstream part of the version. */
+ const char *version;
+ /** The Debian revision part of the version. */
+ const char *revision;
+};
+
+/**
+ * Compound literal for a dpkg_version.
+ */
+#define DPKG_VERSION_OBJECT(e, v, r) \
+ (struct dpkg_version){ .epoch = (e), .version = (v), .revision = (r) }
+
+#define DPKG_VERSION_INIT \
+ DPKG_VERSION_OBJECT(0, NULL, NULL)
+
+/**
+ * Enum constants for the supported relation operations that can be done
+ * on Debian versions.
+ */
+enum DPKG_ATTR_ENUM_FLAGS dpkg_relation {
+ /** The “none” relation, sentinel value. */
+ DPKG_RELATION_NONE = 0,
+ /** Equality relation (‘=’). */
+ DPKG_RELATION_EQ = DPKG_BIT(0),
+ /** Less than relation (‘<<’). */
+ DPKG_RELATION_LT = DPKG_BIT(1),
+ /** Less than or equal to relation (‘<=’). */
+ DPKG_RELATION_LE = DPKG_RELATION_LT | DPKG_RELATION_EQ,
+ /** Greater than relation (‘>>’). */
+ DPKG_RELATION_GT = DPKG_BIT(2),
+ /** Greater than or equal to relation (‘>=’). */
+ DPKG_RELATION_GE = DPKG_RELATION_GT | DPKG_RELATION_EQ,
+};
+
+void dpkg_version_blank(struct dpkg_version *version);
+bool dpkg_version_is_informative(const struct dpkg_version *version);
+int dpkg_version_compare(const struct dpkg_version *a,
+ const struct dpkg_version *b);
+bool dpkg_version_relate(const struct dpkg_version *a,
+ enum dpkg_relation rel,
+ const struct dpkg_version *b);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_VERSION_H */