summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 00:45:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 00:45:20 +0000
commit9a08cbfcc1ef900a04580f35afe2a4592d7d6030 (patch)
tree004cc7027bca2f2c0bcb5806527c8e0c48df2d6e /src
parentInitial commit. (diff)
downloaddpkg-9a08cbfcc1ef900a04580f35afe2a4592d7d6030.tar.xz
dpkg-9a08cbfcc1ef900a04580f35afe2a4592d7d6030.zip
Adding upstream version 1.19.8.upstream/1.19.8upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am86
-rw-r--r--src/Makefile.in966
-rw-r--r--src/archives.c1653
-rw-r--r--src/archives.h95
-rw-r--r--src/cleanup.c258
-rw-r--r--src/configure.c829
-rw-r--r--src/depcon.c699
-rw-r--r--src/divertcmd.c860
-rw-r--r--src/enquiry.c754
-rw-r--r--src/errors.c137
-rw-r--r--src/file-match.c49
-rw-r--r--src/file-match.h35
-rw-r--r--src/filters.c133
-rw-r--r--src/filters.h37
-rw-r--r--src/force.c395
-rw-r--r--src/force.h84
-rw-r--r--src/help.c356
-rw-r--r--src/main.c788
-rw-r--r--src/main.h367
-rw-r--r--src/packages.c756
-rw-r--r--src/perpkgstate.c43
-rw-r--r--src/querycmd.c867
-rw-r--r--src/remove.c686
-rw-r--r--src/script.c399
-rw-r--r--src/select.c240
-rw-r--r--src/selinux.c128
-rw-r--r--src/statcmd.c427
-rw-r--r--src/t/dpkg_divert.t644
-rw-r--r--src/trigcmd.c256
-rw-r--r--src/trigproc.c575
-rw-r--r--src/unpack.c1599
-rw-r--r--src/update.c124
-rw-r--r--src/verify.c177
33 files changed, 15502 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..b1003f4
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,86 @@
+## Process this file with automake to produce Makefile.in
+
+localedir = $(datadir)/locale
+pkgconfdir = $(sysconfdir)/@PACKAGE@
+
+AM_CPPFLAGS = \
+ -DLOCALEDIR=\"$(localedir)\" \
+ -DADMINDIR=\"$(admindir)\" \
+ -idirafter $(top_srcdir)/lib/compat \
+ -I$(top_builddir) \
+ -I$(top_srcdir)/lib
+LDADD = \
+ ../lib/dpkg/libdpkg.la \
+ $(LIBINTL)
+
+
+EXTRA_DIST = \
+ $(test_scripts) \
+ $(nil)
+
+bin_PROGRAMS = \
+ dpkg \
+ dpkg-divert \
+ dpkg-query \
+ dpkg-statoverride \
+ dpkg-trigger
+
+dpkg_SOURCES = \
+ archives.c archives.h \
+ cleanup.c \
+ configure.c \
+ depcon.c \
+ enquiry.c \
+ errors.c \
+ file-match.c file-match.h \
+ filters.c filters.h \
+ force.c force.h \
+ help.c \
+ main.c main.h \
+ packages.c \
+ perpkgstate.c \
+ remove.c \
+ script.c \
+ select.c \
+ selinux.c \
+ trigproc.c \
+ unpack.c \
+ update.c \
+ verify.c \
+ $(nil)
+
+dpkg_LDADD = \
+ $(LDADD) \
+ $(SELINUX_LIBS)
+
+dpkg_divert_SOURCES = \
+ divertcmd.c
+
+dpkg_query_SOURCES = \
+ querycmd.c
+
+dpkg_statoverride_SOURCES = \
+ force.c force.h \
+ selinux.c \
+ statcmd.c
+
+dpkg_statoverride_LDADD = \
+ $(LDADD) \
+ $(SELINUX_LIBS)
+
+dpkg_trigger_SOURCES = \
+ trigcmd.c
+
+install-data-local:
+ $(MKDIR_P) $(DESTDIR)$(pkgconfdir)/dpkg.cfg.d
+ $(MKDIR_P) $(DESTDIR)$(admindir)/info
+ $(MKDIR_P) $(DESTDIR)$(admindir)/updates
+
+test_tmpdir = t.tmp
+
+test_scripts = \
+ t/dpkg_divert.t
+
+include $(top_srcdir)/check.am
+
+clean-local: check-clean
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 0000000..d0fcad9
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,966 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+# 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@
+bin_PROGRAMS = dpkg$(EXEEXT) dpkg-divert$(EXEEXT) dpkg-query$(EXEEXT) \
+ dpkg-statoverride$(EXEEXT) dpkg-trigger$(EXEEXT)
+subdir = src
+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-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/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__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+am_dpkg_OBJECTS = archives.$(OBJEXT) cleanup.$(OBJEXT) \
+ configure.$(OBJEXT) depcon.$(OBJEXT) enquiry.$(OBJEXT) \
+ errors.$(OBJEXT) file-match.$(OBJEXT) filters.$(OBJEXT) \
+ force.$(OBJEXT) help.$(OBJEXT) main.$(OBJEXT) \
+ packages.$(OBJEXT) perpkgstate.$(OBJEXT) remove.$(OBJEXT) \
+ script.$(OBJEXT) select.$(OBJEXT) selinux.$(OBJEXT) \
+ trigproc.$(OBJEXT) unpack.$(OBJEXT) update.$(OBJEXT) \
+ verify.$(OBJEXT)
+dpkg_OBJECTS = $(am_dpkg_OBJECTS)
+am__DEPENDENCIES_1 =
+am__DEPENDENCIES_2 = ../lib/dpkg/libdpkg.la $(am__DEPENDENCIES_1)
+dpkg_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+am_dpkg_divert_OBJECTS = divertcmd.$(OBJEXT)
+dpkg_divert_OBJECTS = $(am_dpkg_divert_OBJECTS)
+dpkg_divert_LDADD = $(LDADD)
+dpkg_divert_DEPENDENCIES = ../lib/dpkg/libdpkg.la \
+ $(am__DEPENDENCIES_1)
+am_dpkg_query_OBJECTS = querycmd.$(OBJEXT)
+dpkg_query_OBJECTS = $(am_dpkg_query_OBJECTS)
+dpkg_query_LDADD = $(LDADD)
+dpkg_query_DEPENDENCIES = ../lib/dpkg/libdpkg.la $(am__DEPENDENCIES_1)
+am_dpkg_statoverride_OBJECTS = force.$(OBJEXT) selinux.$(OBJEXT) \
+ statcmd.$(OBJEXT)
+dpkg_statoverride_OBJECTS = $(am_dpkg_statoverride_OBJECTS)
+dpkg_statoverride_DEPENDENCIES = $(am__DEPENDENCIES_2) \
+ $(am__DEPENDENCIES_1)
+am_dpkg_trigger_OBJECTS = trigcmd.$(OBJEXT)
+dpkg_trigger_OBJECTS = $(am_dpkg_trigger_OBJECTS)
+dpkg_trigger_LDADD = $(LDADD)
+dpkg_trigger_DEPENDENCIES = ../lib/dpkg/libdpkg.la \
+ $(am__DEPENDENCIES_1)
+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)/archives.Po ./$(DEPDIR)/cleanup.Po \
+ ./$(DEPDIR)/configure.Po ./$(DEPDIR)/depcon.Po \
+ ./$(DEPDIR)/divertcmd.Po ./$(DEPDIR)/enquiry.Po \
+ ./$(DEPDIR)/errors.Po ./$(DEPDIR)/file-match.Po \
+ ./$(DEPDIR)/filters.Po ./$(DEPDIR)/force.Po \
+ ./$(DEPDIR)/help.Po ./$(DEPDIR)/main.Po \
+ ./$(DEPDIR)/packages.Po ./$(DEPDIR)/perpkgstate.Po \
+ ./$(DEPDIR)/querycmd.Po ./$(DEPDIR)/remove.Po \
+ ./$(DEPDIR)/script.Po ./$(DEPDIR)/select.Po \
+ ./$(DEPDIR)/selinux.Po ./$(DEPDIR)/statcmd.Po \
+ ./$(DEPDIR)/trigcmd.Po ./$(DEPDIR)/trigproc.Po \
+ ./$(DEPDIR)/unpack.Po ./$(DEPDIR)/update.Po \
+ ./$(DEPDIR)/verify.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(dpkg_SOURCES) $(dpkg_divert_SOURCES) $(dpkg_query_SOURCES) \
+ $(dpkg_statoverride_SOURCES) $(dpkg_trigger_SOURCES)
+DIST_SOURCES = $(dpkg_SOURCES) $(dpkg_divert_SOURCES) \
+ $(dpkg_query_SOURCES) $(dpkg_statoverride_SOURCES) \
+ $(dpkg_trigger_SOURCES)
+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)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+ $(top_srcdir)/build-aux/depcomp $(top_srcdir)/check.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@
+CURSES_LIBS = @CURSES_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOXYGEN = @DOXYGEN@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+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@
+SED = @SED@
+SELINUX_CFLAGS = @SELINUX_CFLAGS@
+SELINUX_LIBS = @SELINUX_LIBS@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+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@
+Z_LIBS = @Z_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@
+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 = $(datadir)/locale
+localstatedir = @localstatedir@
+logdir = @logdir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgconfdir = $(sysconfdir)/@PACKAGE@
+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@
+AM_CPPFLAGS = \
+ -DLOCALEDIR=\"$(localedir)\" \
+ -DADMINDIR=\"$(admindir)\" \
+ -idirafter $(top_srcdir)/lib/compat \
+ -I$(top_builddir) \
+ -I$(top_srcdir)/lib
+
+LDADD = \
+ ../lib/dpkg/libdpkg.la \
+ $(LIBINTL)
+
+EXTRA_DIST = \
+ $(test_scripts) \
+ $(nil)
+
+dpkg_SOURCES = \
+ archives.c archives.h \
+ cleanup.c \
+ configure.c \
+ depcon.c \
+ enquiry.c \
+ errors.c \
+ file-match.c file-match.h \
+ filters.c filters.h \
+ force.c force.h \
+ help.c \
+ main.c main.h \
+ packages.c \
+ perpkgstate.c \
+ remove.c \
+ script.c \
+ select.c \
+ selinux.c \
+ trigproc.c \
+ unpack.c \
+ update.c \
+ verify.c \
+ $(nil)
+
+dpkg_LDADD = \
+ $(LDADD) \
+ $(SELINUX_LIBS)
+
+dpkg_divert_SOURCES = \
+ divertcmd.c
+
+dpkg_query_SOURCES = \
+ querycmd.c
+
+dpkg_statoverride_SOURCES = \
+ force.c force.h \
+ selinux.c \
+ statcmd.c
+
+dpkg_statoverride_LDADD = \
+ $(LDADD) \
+ $(SELINUX_LIBS)
+
+dpkg_trigger_SOURCES = \
+ trigcmd.c
+
+test_tmpdir = t.tmp
+test_scripts = \
+ t/dpkg_divert.t
+
+TEST_RUNNER = '\
+ my $$harness = TAP::Harness->new({ \
+ exec => sub { my (undef, $$test) = @_; \
+ return [ $$test ] if $$test !~ "\.t\$$" and -x $$test; \
+ return }, \
+ lib => [ "$(top_srcdir)/scripts", "$(top_srcdir)/dselect/methods" ], \
+ color => 1, \
+ verbosity => $(TEST_VERBOSE), \
+ jobs => $(TEST_PARALLEL), \
+ failures => 1, \
+ }); \
+ my $$aggregate = $$harness->runtests(@ARGV); \
+ die "FAIL: test suite has errors\n" if $$aggregate->has_errors;'
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(top_srcdir)/check.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/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)/check.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):
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+ @list='$(bin_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
+
+dpkg$(EXEEXT): $(dpkg_OBJECTS) $(dpkg_DEPENDENCIES) $(EXTRA_dpkg_DEPENDENCIES)
+ @rm -f dpkg$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(dpkg_OBJECTS) $(dpkg_LDADD) $(LIBS)
+
+dpkg-divert$(EXEEXT): $(dpkg_divert_OBJECTS) $(dpkg_divert_DEPENDENCIES) $(EXTRA_dpkg_divert_DEPENDENCIES)
+ @rm -f dpkg-divert$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(dpkg_divert_OBJECTS) $(dpkg_divert_LDADD) $(LIBS)
+
+dpkg-query$(EXEEXT): $(dpkg_query_OBJECTS) $(dpkg_query_DEPENDENCIES) $(EXTRA_dpkg_query_DEPENDENCIES)
+ @rm -f dpkg-query$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(dpkg_query_OBJECTS) $(dpkg_query_LDADD) $(LIBS)
+
+dpkg-statoverride$(EXEEXT): $(dpkg_statoverride_OBJECTS) $(dpkg_statoverride_DEPENDENCIES) $(EXTRA_dpkg_statoverride_DEPENDENCIES)
+ @rm -f dpkg-statoverride$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(dpkg_statoverride_OBJECTS) $(dpkg_statoverride_LDADD) $(LIBS)
+
+dpkg-trigger$(EXEEXT): $(dpkg_trigger_OBJECTS) $(dpkg_trigger_DEPENDENCIES) $(EXTRA_dpkg_trigger_DEPENDENCIES)
+ @rm -f dpkg-trigger$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(dpkg_trigger_OBJECTS) $(dpkg_trigger_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/archives.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cleanup.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/configure.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/depcon.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/divertcmd.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/enquiry.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/errors.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-match.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filters.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/force.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/help.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/packages.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/perpkgstate.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/querycmd.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/remove.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/script.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/select.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/selinux.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/statcmd.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trigcmd.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trigproc.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unpack.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/update.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/verify.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-local
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+ for dir in "$(DESTDIR)$(bindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic clean-libtool clean-local \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/archives.Po
+ -rm -f ./$(DEPDIR)/cleanup.Po
+ -rm -f ./$(DEPDIR)/configure.Po
+ -rm -f ./$(DEPDIR)/depcon.Po
+ -rm -f ./$(DEPDIR)/divertcmd.Po
+ -rm -f ./$(DEPDIR)/enquiry.Po
+ -rm -f ./$(DEPDIR)/errors.Po
+ -rm -f ./$(DEPDIR)/file-match.Po
+ -rm -f ./$(DEPDIR)/filters.Po
+ -rm -f ./$(DEPDIR)/force.Po
+ -rm -f ./$(DEPDIR)/help.Po
+ -rm -f ./$(DEPDIR)/main.Po
+ -rm -f ./$(DEPDIR)/packages.Po
+ -rm -f ./$(DEPDIR)/perpkgstate.Po
+ -rm -f ./$(DEPDIR)/querycmd.Po
+ -rm -f ./$(DEPDIR)/remove.Po
+ -rm -f ./$(DEPDIR)/script.Po
+ -rm -f ./$(DEPDIR)/select.Po
+ -rm -f ./$(DEPDIR)/selinux.Po
+ -rm -f ./$(DEPDIR)/statcmd.Po
+ -rm -f ./$(DEPDIR)/trigcmd.Po
+ -rm -f ./$(DEPDIR)/trigproc.Po
+ -rm -f ./$(DEPDIR)/unpack.Po
+ -rm -f ./$(DEPDIR)/update.Po
+ -rm -f ./$(DEPDIR)/verify.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-data-local
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+
+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)/archives.Po
+ -rm -f ./$(DEPDIR)/cleanup.Po
+ -rm -f ./$(DEPDIR)/configure.Po
+ -rm -f ./$(DEPDIR)/depcon.Po
+ -rm -f ./$(DEPDIR)/divertcmd.Po
+ -rm -f ./$(DEPDIR)/enquiry.Po
+ -rm -f ./$(DEPDIR)/errors.Po
+ -rm -f ./$(DEPDIR)/file-match.Po
+ -rm -f ./$(DEPDIR)/filters.Po
+ -rm -f ./$(DEPDIR)/force.Po
+ -rm -f ./$(DEPDIR)/help.Po
+ -rm -f ./$(DEPDIR)/main.Po
+ -rm -f ./$(DEPDIR)/packages.Po
+ -rm -f ./$(DEPDIR)/perpkgstate.Po
+ -rm -f ./$(DEPDIR)/querycmd.Po
+ -rm -f ./$(DEPDIR)/remove.Po
+ -rm -f ./$(DEPDIR)/script.Po
+ -rm -f ./$(DEPDIR)/select.Po
+ -rm -f ./$(DEPDIR)/selinux.Po
+ -rm -f ./$(DEPDIR)/statcmd.Po
+ -rm -f ./$(DEPDIR)/trigcmd.Po
+ -rm -f ./$(DEPDIR)/trigproc.Po
+ -rm -f ./$(DEPDIR)/unpack.Po
+ -rm -f ./$(DEPDIR)/update.Po
+ -rm -f ./$(DEPDIR)/verify.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-binPROGRAMS
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \
+ check-local clean clean-binPROGRAMS 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-binPROGRAMS \
+ install-data install-data-am install-data-local 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 \
+ uninstall-binPROGRAMS
+
+.PRECIOUS: Makefile
+
+
+install-data-local:
+ $(MKDIR_P) $(DESTDIR)$(pkgconfdir)/dpkg.cfg.d
+ $(MKDIR_P) $(DESTDIR)$(admindir)/info
+ $(MKDIR_P) $(DESTDIR)$(admindir)/updates
+
+TEST_VERBOSE ?= 0
+TEST_PARALLEL ?= 1
+
+check-clean:
+ [ -z "$(test_tmpdir)" ] || rm -fr $(test_tmpdir)
+
+check-local: $(test_data) $(test_programs) $(test_scripts)
+ [ -z "$(test_tmpdir)" ] || $(MKDIR_P) $(test_tmpdir)
+ PATH="$(abs_top_builddir)/src:$(abs_top_builddir)/scripts:$(abs_top_builddir)/utils:$(PATH)" \
+ LC_ALL=C \
+ DPKG_COLORS=never \
+ $(TEST_ENV_VARS) \
+ srcdir=$(srcdir) builddir=$(builddir) \
+ CC=$(CC) \
+ PERL=$(PERL) \
+ PERL_DL_NONLAZY=1 \
+ PERL5LIB=$(abs_top_srcdir)/scripts:$(abs_top_srcdir)/dselect/methods \
+ PERL5OPT=$(TEST_COVERAGE) \
+ $(PERL) -MTAP::Harness -e $(TEST_RUNNER) \
+ $(addprefix $(builddir)/,$(test_programs)) \
+ $(addprefix $(srcdir)/,$(test_scripts))
+
+clean-local: check-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/src/archives.c b/src/archives.c
new file mode 100644
index 0000000..f74bcee
--- /dev/null
+++ b/src/archives.c
@@ -0,0 +1,1653 @@
+/*
+ * dpkg - main program for package management
+ * archives.c - actions that process archive files, mainly unpack
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2000 Wichert Akkerman <wakkerma@debian.org>
+ * Copyright © 2007-2015 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/time.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <obstack.h>
+#define obstack_chunk_alloc m_malloc
+#define obstack_chunk_free free
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg.h>
+#include <dpkg/path.h>
+#include <dpkg/fdio.h>
+#include <dpkg/buffer.h>
+#include <dpkg/subproc.h>
+#include <dpkg/command.h>
+#include <dpkg/file.h>
+#include <dpkg/treewalk.h>
+#include <dpkg/tarfn.h>
+#include <dpkg/options.h>
+#include <dpkg/triglib.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+
+#include "main.h"
+#include "archives.h"
+#include "filters.h"
+
+static inline void
+fd_writeback_init(int fd)
+{
+ /* Ignore the return code as it should be considered equivalent to an
+ * asynchronous hint for the kernel, we are doing an fsync() later on
+ * anyway. */
+#if defined(SYNC_FILE_RANGE_WRITE)
+ sync_file_range(fd, 0, 0, SYNC_FILE_RANGE_WRITE);
+#elif defined(HAVE_POSIX_FADVISE)
+ posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
+#endif
+}
+
+static struct obstack tar_pool;
+static bool tar_pool_init = false;
+
+/**
+ * Allocate memory from the tar memory pool.
+ */
+static void *
+tar_pool_alloc(size_t size)
+{
+ if (!tar_pool_init) {
+ obstack_init(&tar_pool);
+ tar_pool_init = true;
+ }
+
+ return obstack_alloc(&tar_pool, size);
+}
+
+/**
+ * Free memory from the tar memory pool.
+ */
+static void
+tar_pool_free(void *ptr)
+{
+ obstack_free(&tar_pool, ptr);
+}
+
+/**
+ * Release the tar memory pool.
+ */
+static void
+tar_pool_release(void)
+{
+ if (tar_pool_init) {
+ obstack_free(&tar_pool, NULL);
+ tar_pool_init = false;
+ }
+}
+
+struct fsys_namenode_list *
+tar_fsys_namenode_queue_push(struct fsys_namenode_queue *queue,
+ struct fsys_namenode *namenode)
+{
+ struct fsys_namenode_list *node;
+
+ node = tar_pool_alloc(sizeof(*node));
+ node->namenode = namenode;
+ node->next = NULL;
+
+ *queue->tail = node;
+ queue->tail = &node->next;
+
+ return node;
+}
+
+static void
+tar_fsys_namenode_queue_pop(struct fsys_namenode_queue *queue,
+ struct fsys_namenode_list **tail_prev,
+ struct fsys_namenode_list *node)
+{
+ tar_pool_free(node);
+ queue->tail = tail_prev;
+ *tail_prev = NULL;
+}
+
+/**
+ * Check if a file or directory will save a package from disappearance.
+ *
+ * A package can only be saved by a file or directory which is part
+ * only of itself - it must be neither part of the new package being
+ * installed nor part of any 3rd package (this is important so that
+ * shared directories don't stop packages from disappearing).
+ */
+bool
+filesavespackage(struct fsys_namenode_list *file,
+ struct pkginfo *pkgtobesaved,
+ struct pkginfo *pkgbeinginstalled)
+{
+ struct fsys_node_pkgs_iter *iter;
+ struct pkgset *divpkgset;
+ struct pkginfo *thirdpkg;
+
+ debug(dbg_eachfiledetail, "filesavespackage file '%s' package %s",
+ file->namenode->name, pkg_name(pkgtobesaved, pnaw_always));
+
+ /* If the file is a contended one and it's overridden by either
+ * the package we're considering disappearing or the package
+ * we're installing then they're not actually the same file, so
+ * we can't disappear the package - it is saved by this file. */
+ if (file->namenode->divert && file->namenode->divert->useinstead) {
+ divpkgset = file->namenode->divert->pkgset;
+ if (divpkgset == pkgtobesaved->set || divpkgset == pkgbeinginstalled->set) {
+ debug(dbg_eachfiledetail,"filesavespackage ... diverted -- save!");
+ return true;
+ }
+ }
+ /* Is the file in the package being installed? If so then it can't save. */
+ if (file->namenode->flags & FNNF_NEW_INARCHIVE) {
+ debug(dbg_eachfiledetail,"filesavespackage ... in new archive -- no save");
+ return false;
+ }
+ /* Look for a 3rd package which can take over the file (in case
+ * it's a directory which is shared by many packages. */
+ iter = fsys_node_pkgs_iter_new(file->namenode);
+ while ((thirdpkg = fsys_node_pkgs_iter_next(iter))) {
+ debug(dbg_eachfiledetail, "filesavespackage ... also in %s",
+ pkg_name(thirdpkg, pnaw_always));
+
+ /* Is this not the package being installed or the one being
+ * checked for disappearance? */
+ if (thirdpkg == pkgbeinginstalled || thirdpkg == pkgtobesaved)
+ continue;
+
+ /* A Multi-Arch: same package can share files and their presence in a
+ * third package of the same set is not a sign that we can get rid of
+ * it. */
+ if (pkgtobesaved->installed.multiarch == PKG_MULTIARCH_SAME &&
+ thirdpkg->set == pkgtobesaved->set)
+ continue;
+
+ /* If !fileslistvalid then we've already disappeared this one, so
+ * we shouldn't try to make it take over this shared directory. */
+ debug(dbg_eachfiledetail,"filesavespackage ... is 3rd package");
+
+ if (!thirdpkg->files_list_valid) {
+ debug(dbg_eachfiledetail, "process_archive ... already disappeared!");
+ continue;
+ }
+
+ /* We've found a package that can take this file. */
+ debug(dbg_eachfiledetail, "filesavespackage ... taken -- no save");
+ fsys_node_pkgs_iter_free(iter);
+ return false;
+ }
+ fsys_node_pkgs_iter_free(iter);
+
+ debug(dbg_eachfiledetail, "filesavespackage ... not taken -- save !");
+ return true;
+}
+
+static void
+md5hash_prev_conffile(struct pkginfo *pkg, char *oldhash, const char *oldname,
+ struct fsys_namenode *namenode)
+{
+ struct pkginfo *otherpkg;
+ struct conffile *conff;
+
+ debug(dbg_conffdetail, "tarobject looking for shared conffile %s",
+ namenode->name);
+
+ for (otherpkg = &pkg->set->pkg; otherpkg; otherpkg = otherpkg->arch_next) {
+ if (otherpkg == pkg)
+ continue;
+ /* If we are reinstalling, even if the other package is only unpacked,
+ * we can always make use of the Conffiles hash value from an initial
+ * installation, if that happened at all. */
+ if (otherpkg->status <= PKG_STAT_UNPACKED &&
+ dpkg_version_compare(&otherpkg->installed.version,
+ &otherpkg->configversion) != 0)
+ continue;
+ for (conff = otherpkg->installed.conffiles; conff; conff = conff->next) {
+ if (conff->obsolete)
+ continue;
+ if (strcmp(conff->name, namenode->name) == 0)
+ break;
+ }
+ if (conff) {
+ strcpy(oldhash, conff->hash);
+ debug(dbg_conffdetail,
+ "tarobject found shared conffile, from pkg %s (%s); hash=%s",
+ pkg_name(otherpkg, pnaw_always),
+ pkg_status_name(otherpkg), oldhash);
+ break;
+ }
+ }
+
+ /* If no package was found with a valid Conffiles field, we make the
+ * risky assumption that the hash of the current .dpkg-new file is
+ * the one of the previously unpacked package. */
+ if (otherpkg == NULL) {
+ md5hash(pkg, oldhash, oldname);
+ debug(dbg_conffdetail,
+ "tarobject found shared conffile, from disk; hash=%s", oldhash);
+ }
+}
+
+void cu_pathname(int argc, void **argv) {
+ path_remove_tree((char*)(argv[0]));
+}
+
+int
+tarfileread(struct tar_archive *tar, char *buf, int len)
+{
+ struct tarcontext *tc = (struct tarcontext *)tar->ctx;
+ int r;
+
+ r = fd_read(tc->backendpipe, buf, len);
+ if (r < 0)
+ ohshite(_("error reading from dpkg-deb pipe"));
+ return r;
+}
+
+static void
+tarobject_skip_padding(struct tarcontext *tc, struct tar_entry *te)
+{
+ struct dpkg_error err;
+ size_t r;
+
+ r = te->size % TARBLKSZ;
+ if (r == 0)
+ return;
+
+ if (fd_skip(tc->backendpipe, TARBLKSZ - r, &err) < 0)
+ ohshit(_("cannot skip padding for file '%.255s': %s"), te->name, err.str);
+}
+
+static void
+tarobject_skip_entry(struct tarcontext *tc, struct tar_entry *ti)
+{
+ /* We need to advance the tar file to the next object, so read the
+ * file data and set it to oblivion. */
+ if (ti->type == TAR_FILETYPE_FILE) {
+ struct dpkg_error err;
+ char fnamebuf[256];
+
+ if (fd_skip(tc->backendpipe, ti->size, &err) < 0)
+ ohshit(_("cannot skip file '%.255s' (replaced or excluded?) from pipe: %s"),
+ path_quote_filename(fnamebuf, ti->name, 256), err.str);
+ tarobject_skip_padding(tc, ti);
+ }
+}
+
+struct varbuf_state fname_state;
+struct varbuf fnamevb;
+struct varbuf fnametmpvb;
+struct varbuf fnamenewvb;
+struct pkg_deconf_list *deconfigure = NULL;
+
+static time_t currenttime;
+
+static int
+does_replace(struct pkginfo *new_pkg, struct pkgbin *new_pkgbin,
+ struct pkginfo *old_pkg, struct pkgbin *old_pkgbin)
+{
+ struct dependency *dep;
+
+ debug(dbg_depcon,"does_replace new=%s old=%s (%s)",
+ pkgbin_name(new_pkg, new_pkgbin, pnaw_always),
+ pkgbin_name(old_pkg, old_pkgbin, pnaw_always),
+ versiondescribe(&old_pkgbin->version, vdew_always));
+ for (dep = new_pkgbin->depends; dep; dep = dep->next) {
+ if (dep->type != dep_replaces || dep->list->ed != old_pkg->set)
+ continue;
+ debug(dbg_depcondetail,"does_replace ... found old, version %s",
+ versiondescribe(&dep->list->version,vdew_always));
+ if (!versionsatisfied(old_pkgbin, dep->list))
+ continue;
+ /* The test below can only trigger if dep_replaces start having
+ * arch qualifiers different from “any”. */
+ if (!archsatisfied(old_pkgbin, dep->list))
+ continue;
+ debug(dbg_depcon,"does_replace ... yes");
+ return true;
+ }
+ debug(dbg_depcon,"does_replace ... no");
+ return false;
+}
+
+static void
+tarobject_extract(struct tarcontext *tc, struct tar_entry *te,
+ const char *path, struct file_stat *st,
+ struct fsys_namenode *namenode)
+{
+ static struct varbuf hardlinkfn;
+ static int fd;
+
+ struct dpkg_error err;
+ struct fsys_namenode *linknode;
+ char fnamebuf[256];
+ char fnamenewbuf[256];
+ char *newhash;
+ int rc;
+
+ switch (te->type) {
+ case TAR_FILETYPE_FILE:
+ /* We create the file with mode 0 to make sure nobody can do anything with
+ * it until we apply the proper mode, which might be a statoverride. */
+ fd = open(path, O_CREAT | O_EXCL | O_WRONLY, 0);
+ if (fd < 0)
+ ohshite(_("unable to create '%.255s' (while processing '%.255s')"),
+ path, te->name);
+ push_cleanup(cu_closefd, ehflag_bombout, 1, &fd);
+ debug(dbg_eachfiledetail, "tarobject file open size=%jd",
+ (intmax_t)te->size);
+
+ /* We try to tell the filesystem how much disk space we are going to
+ * need to let it reduce fragmentation and possibly improve performance,
+ * as we do know the size beforehand. */
+ fd_allocate_size(fd, 0, te->size);
+
+ newhash = nfmalloc(MD5HASHLEN + 1);
+ if (fd_fd_copy_and_md5(tc->backendpipe, fd, newhash, te->size, &err) < 0)
+ ohshit(_("cannot copy extracted data for '%.255s' to '%.255s': %s"),
+ path_quote_filename(fnamebuf, te->name, 256),
+ path_quote_filename(fnamenewbuf, fnamenewvb.buf, 256), err.str);
+ namenode->newhash = newhash;
+ debug(dbg_eachfiledetail, "tarobject file hash=%s", namenode->newhash);
+
+ tarobject_skip_padding(tc, te);
+
+ fd_writeback_init(fd);
+
+ if (namenode->statoverride)
+ debug(dbg_eachfile, "tarobject ... stat override, uid=%d, gid=%d, mode=%04o",
+ namenode->statoverride->uid,
+ namenode->statoverride->gid,
+ namenode->statoverride->mode);
+ rc = fchown(fd, st->uid, st->gid);
+ if (forcible_nonroot_error(rc))
+ ohshite(_("error setting ownership of '%.255s'"), te->name);
+ rc = fchmod(fd, st->mode & ~S_IFMT);
+ if (forcible_nonroot_error(rc))
+ ohshite(_("error setting permissions of '%.255s'"), te->name);
+
+ /* Postpone the fsync, to try to avoid massive I/O degradation. */
+ if (!in_force(FORCE_UNSAFE_IO))
+ namenode->flags |= FNNF_DEFERRED_FSYNC;
+
+ pop_cleanup(ehflag_normaltidy); /* fd = open(path) */
+ if (close(fd))
+ ohshite(_("error closing/writing '%.255s'"), te->name);
+ break;
+ case TAR_FILETYPE_FIFO:
+ if (mkfifo(path, 0))
+ ohshite(_("error creating pipe '%.255s'"), te->name);
+ debug(dbg_eachfiledetail, "tarobject fifo");
+ break;
+ case TAR_FILETYPE_CHARDEV:
+ if (mknod(path, S_IFCHR, te->dev))
+ ohshite(_("error creating device '%.255s'"), te->name);
+ debug(dbg_eachfiledetail, "tarobject chardev");
+ break;
+ case TAR_FILETYPE_BLOCKDEV:
+ if (mknod(path, S_IFBLK, te->dev))
+ ohshite(_("error creating device '%.255s'"), te->name);
+ debug(dbg_eachfiledetail, "tarobject blockdev");
+ break;
+ case TAR_FILETYPE_HARDLINK:
+ varbuf_reset(&hardlinkfn);
+ varbuf_add_str(&hardlinkfn, instdir);
+ linknode = fsys_hash_find_node(te->linkname, 0);
+ varbuf_add_str(&hardlinkfn,
+ namenodetouse(linknode, tc->pkg, &tc->pkg->available)->name);
+ if (linknode->flags & (FNNF_DEFERRED_RENAME | FNNF_NEW_CONFF))
+ varbuf_add_str(&hardlinkfn, DPKGNEWEXT);
+ varbuf_end_str(&hardlinkfn);
+ if (link(hardlinkfn.buf, path))
+ ohshite(_("error creating hard link '%.255s'"), te->name);
+ namenode->newhash = linknode->newhash;
+ debug(dbg_eachfiledetail, "tarobject hardlink hash=%s", namenode->newhash);
+ break;
+ case TAR_FILETYPE_SYMLINK:
+ /* We've already checked for an existing directory. */
+ if (symlink(te->linkname, path))
+ ohshite(_("error creating symbolic link '%.255s'"), te->name);
+ debug(dbg_eachfiledetail, "tarobject symlink creating");
+ break;
+ case TAR_FILETYPE_DIR:
+ /* We've already checked for an existing directory. */
+ if (mkdir(path, 0))
+ ohshite(_("error creating directory '%.255s'"), te->name);
+ debug(dbg_eachfiledetail, "tarobject directory creating");
+ break;
+ default:
+ internerr("unknown tar type '%d', but already checked", te->type);
+ }
+}
+
+static void
+tarobject_hash(struct tarcontext *tc, struct tar_entry *te,
+ struct fsys_namenode *namenode)
+{
+ if (te->type == TAR_FILETYPE_FILE) {
+ struct dpkg_error err;
+ char fnamebuf[256];
+ char *newhash;
+
+ newhash = nfmalloc(MD5HASHLEN + 1);
+ if (fd_md5(tc->backendpipe, newhash, te->size, &err) < 0)
+ ohshit(_("cannot compute MD5 hash for tar file '%.255s': %s"),
+ path_quote_filename(fnamebuf, te->name, 256), err.str);
+ tarobject_skip_padding(tc, te);
+
+ namenode->newhash = newhash;
+ debug(dbg_eachfiledetail, "tarobject file hash=%s", namenode->newhash);
+ } else if (te->type == TAR_FILETYPE_HARDLINK) {
+ struct fsys_namenode *linknode;
+
+ linknode = fsys_hash_find_node(te->linkname, 0);
+ namenode->newhash = linknode->newhash;
+ debug(dbg_eachfiledetail, "tarobject hardlink hash=%s", namenode->newhash);
+ }
+}
+
+static void
+tarobject_set_mtime(struct tar_entry *te, const char *path)
+{
+ struct timeval tv[2];
+
+ tv[0].tv_sec = currenttime;
+ tv[0].tv_usec = 0;
+ tv[1].tv_sec = te->mtime;
+ tv[1].tv_usec = 0;
+
+ if (te->type == TAR_FILETYPE_SYMLINK) {
+#ifdef HAVE_LUTIMES
+ if (lutimes(path, tv) && errno != ENOSYS)
+ ohshite(_("error setting timestamps of '%.255s'"), path);
+#endif
+ } else {
+ if (utimes(path, tv))
+ ohshite(_("error setting timestamps of '%.255s'"), path);
+ }
+}
+
+static void
+tarobject_set_perms(struct tar_entry *te, const char *path, struct file_stat *st)
+{
+ int rc;
+
+ if (te->type == TAR_FILETYPE_FILE)
+ return; /* Already handled using the file descriptor. */
+
+ if (te->type == TAR_FILETYPE_SYMLINK) {
+ rc = lchown(path, st->uid, st->gid);
+ if (forcible_nonroot_error(rc))
+ ohshite(_("error setting ownership of symlink '%.255s'"), path);
+ } else {
+ rc = chown(path, st->uid, st->gid);
+ if (forcible_nonroot_error(rc))
+ ohshite(_("error setting ownership of '%.255s'"), path);
+ rc = chmod(path, st->mode & ~S_IFMT);
+ if (forcible_nonroot_error(rc))
+ ohshite(_("error setting permissions of '%.255s'"), path);
+ }
+}
+
+static void
+tarobject_set_se_context(const char *matchpath, const char *path, mode_t mode)
+{
+ dpkg_selabel_set_context(matchpath, path, mode);
+}
+
+static void
+tarobject_matches(struct tarcontext *tc,
+ const char *fn_old, struct stat *stab, char *oldhash,
+ const char *fn_new, struct tar_entry *te,
+ struct fsys_namenode *namenode)
+{
+ char *linkname;
+ ssize_t linksize;
+
+ debug(dbg_eachfiledetail, "tarobject matches on-disk object?");
+
+ switch (te->type) {
+ case TAR_FILETYPE_DIR:
+ /* Nothing to check for a new directory. */
+ return;
+ case TAR_FILETYPE_SYMLINK:
+ /* Symlinks to existing dirs have already been dealt with, only
+ * remain real symlinks where we can compare the target. */
+ if (!S_ISLNK(stab->st_mode))
+ break;
+ linkname = m_malloc(stab->st_size + 1);
+ linksize = readlink(fn_old, linkname, stab->st_size + 1);
+ if (linksize < 0)
+ ohshite(_("unable to read link '%.255s'"), fn_old);
+ else if (linksize > stab->st_size)
+ ohshit(_("symbolic link '%.250s' size has changed from %jd to %zd"),
+ fn_old, (intmax_t)stab->st_size, linksize);
+ else if (linksize < stab->st_size)
+ warning(_("symbolic link '%.250s' size has changed from %jd to %zd"),
+ fn_old, (intmax_t)stab->st_size, linksize);
+ linkname[linksize] = '\0';
+ if (strcmp(linkname, te->linkname) == 0) {
+ free(linkname);
+ return;
+ } else {
+ free(linkname);
+ }
+ break;
+ case TAR_FILETYPE_CHARDEV:
+ if (S_ISCHR(stab->st_mode) && stab->st_rdev == te->dev)
+ return;
+ break;
+ case TAR_FILETYPE_BLOCKDEV:
+ if (S_ISBLK(stab->st_mode) && stab->st_rdev == te->dev)
+ return;
+ break;
+ case TAR_FILETYPE_FIFO:
+ if (S_ISFIFO(stab->st_mode))
+ return;
+ break;
+ case TAR_FILETYPE_HARDLINK:
+ /* Fall through. */
+ case TAR_FILETYPE_FILE:
+ /* Only check metadata for non-conffiles. */
+ if (!(namenode->flags & FNNF_NEW_CONFF) &&
+ !(S_ISREG(stab->st_mode) && te->size == stab->st_size))
+ break;
+ if (strcmp(oldhash, namenode->newhash) == 0)
+ return;
+ break;
+ default:
+ internerr("unknown tar type '%d', but already checked", te->type);
+ }
+
+ forcibleerr(FORCE_OVERWRITE,
+ _("trying to overwrite shared '%.250s', which is different "
+ "from other instances of package %.250s"),
+ namenode->name, pkg_name(tc->pkg, pnaw_nonambig));
+}
+
+void setupfnamevbs(const char *filename) {
+ varbuf_rollback(&fnamevb, &fname_state);
+ varbuf_add_str(&fnamevb, filename);
+ varbuf_end_str(&fnamevb);
+
+ varbuf_rollback(&fnametmpvb, &fname_state);
+ varbuf_add_str(&fnametmpvb, filename);
+ varbuf_add_str(&fnametmpvb, DPKGTEMPEXT);
+ varbuf_end_str(&fnametmpvb);
+
+ varbuf_rollback(&fnamenewvb, &fname_state);
+ varbuf_add_str(&fnamenewvb, filename);
+ varbuf_add_str(&fnamenewvb, DPKGNEWEXT);
+ varbuf_end_str(&fnamenewvb);
+
+ debug(dbg_eachfiledetail, "setupvnamevbs main='%s' tmp='%s' new='%s'",
+ fnamevb.buf, fnametmpvb.buf, fnamenewvb.buf);
+}
+
+static bool
+linktosameexistingdir(const struct tar_entry *ti, const char *fname,
+ struct varbuf *symlinkfn)
+{
+ struct stat oldstab, newstab;
+ int statr;
+ const char *lastslash;
+
+ statr= stat(fname, &oldstab);
+ if (statr) {
+ if (!(errno == ENOENT || errno == ELOOP || errno == ENOTDIR))
+ ohshite(_("failed to stat (dereference) existing symlink '%.250s'"),
+ fname);
+ return false;
+ }
+ if (!S_ISDIR(oldstab.st_mode))
+ return false;
+
+ /* But is it to the same dir? */
+ varbuf_reset(symlinkfn);
+ if (ti->linkname[0] == '/') {
+ varbuf_add_str(symlinkfn, instdir);
+ } else {
+ lastslash= strrchr(fname, '/');
+ if (lastslash == NULL)
+ internerr("tar entry filename '%s' does not contain '/'", fname);
+ varbuf_add_buf(symlinkfn, fname, (lastslash - fname) + 1);
+ }
+ varbuf_add_str(symlinkfn, ti->linkname);
+ varbuf_end_str(symlinkfn);
+
+ statr= stat(symlinkfn->buf, &newstab);
+ if (statr) {
+ if (!(errno == ENOENT || errno == ELOOP || errno == ENOTDIR))
+ ohshite(_("failed to stat (dereference) proposed new symlink target"
+ " '%.250s' for symlink '%.250s'"), symlinkfn->buf, fname);
+ return false;
+ }
+ if (!S_ISDIR(newstab.st_mode))
+ return false;
+ if (newstab.st_dev != oldstab.st_dev ||
+ newstab.st_ino != oldstab.st_ino)
+ return false;
+ return true;
+}
+
+int
+tarobject(struct tar_archive *tar, struct tar_entry *ti)
+{
+ static struct varbuf conffderefn, symlinkfn;
+ const char *usename;
+ struct fsys_namenode *usenode;
+
+ struct conffile *conff;
+ struct tarcontext *tc = tar->ctx;
+ bool existingdir, keepexisting;
+ bool refcounting;
+ char oldhash[MD5HASHLEN + 1];
+ int statr;
+ ssize_t r;
+ struct stat stab, stabtmp;
+ struct file_stat nodestat;
+ struct fsys_namenode_list *nifd, **oldnifd;
+ struct pkgset *divpkgset;
+ struct pkginfo *otherpkg;
+
+ tar_entry_update_from_system(ti);
+
+ /* Perform some sanity checks on the tar entry. */
+ if (strchr(ti->name, '\n'))
+ ohshit(_("newline not allowed in archive object name '%.255s'"), ti->name);
+
+ /* Append to list of files.
+ * The trailing ‘/’ put on the end of names in tarfiles has already
+ * been stripped by tar_extractor(). */
+ oldnifd = tc->newfiles_queue->tail;
+ nifd = tar_fsys_namenode_queue_push(tc->newfiles_queue,
+ fsys_hash_find_node(ti->name, 0));
+ nifd->namenode->flags |= FNNF_NEW_INARCHIVE;
+
+ debug(dbg_eachfile,
+ "tarobject ti->name='%s' mode=%lo owner=%u:%u type=%d(%c)"
+ " ti->linkname='%s' namenode='%s' flags=%o instead='%s'",
+ ti->name, (long)ti->stat.mode,
+ (unsigned)ti->stat.uid, (unsigned)ti->stat.gid,
+ ti->type,
+ ti->type >= '0' && ti->type <= '6' ? "-hlcbdp"[ti->type - '0'] : '?',
+ ti->linkname,
+ nifd->namenode->name, nifd->namenode->flags,
+ nifd->namenode->divert && nifd->namenode->divert->useinstead
+ ? nifd->namenode->divert->useinstead->name : "<none>");
+
+ if (nifd->namenode->divert && nifd->namenode->divert->camefrom) {
+ divpkgset = nifd->namenode->divert->pkgset;
+
+ if (divpkgset) {
+ forcibleerr(FORCE_OVERWRITE_DIVERTED,
+ _("trying to overwrite '%.250s', which is the "
+ "diverted version of '%.250s' (package: %.100s)"),
+ nifd->namenode->name, nifd->namenode->divert->camefrom->name,
+ divpkgset->name);
+ } else {
+ forcibleerr(FORCE_OVERWRITE_DIVERTED,
+ _("trying to overwrite '%.250s', which is the "
+ "diverted version of '%.250s'"),
+ nifd->namenode->name, nifd->namenode->divert->camefrom->name);
+ }
+ }
+
+ if (nifd->namenode->statoverride) {
+ nodestat = *nifd->namenode->statoverride;
+ nodestat.mode |= ti->stat.mode & S_IFMT;
+ } else {
+ nodestat = ti->stat;
+ }
+
+ usenode = namenodetouse(nifd->namenode, tc->pkg, &tc->pkg->available);
+ usename = usenode->name;
+
+ trig_file_activate(usenode, tc->pkg);
+
+ if (nifd->namenode->flags & FNNF_NEW_CONFF) {
+ /* If it's a conffile we have to extract it next to the installed
+ * version (i.e. we do the usual link-following). */
+ if (conffderef(tc->pkg, &conffderefn, usename))
+ usename= conffderefn.buf;
+ debug(dbg_conff, "tarobject FNNF_NEW_CONFF deref='%s'", usename);
+ }
+
+ setupfnamevbs(usename);
+
+ statr= lstat(fnamevb.buf,&stab);
+ if (statr) {
+ /* The lstat failed. */
+ if (errno != ENOENT && errno != ENOTDIR)
+ ohshite(_("unable to stat '%.255s' (which I was about to install)"),
+ ti->name);
+ /* OK, so it doesn't exist.
+ * However, it's possible that we were in the middle of some other
+ * backup/restore operation and were rudely interrupted.
+ * So, we see if we have .dpkg-tmp, and if so we restore it. */
+ if (rename(fnametmpvb.buf,fnamevb.buf)) {
+ if (errno != ENOENT && errno != ENOTDIR)
+ ohshite(_("unable to clean up mess surrounding '%.255s' before "
+ "installing another version"), ti->name);
+ debug(dbg_eachfiledetail,"tarobject nonexistent");
+ } else {
+ debug(dbg_eachfiledetail,"tarobject restored tmp to main");
+ statr= lstat(fnamevb.buf,&stab);
+ if (statr)
+ ohshite(_("unable to stat restored '%.255s' before installing"
+ " another version"), ti->name);
+ }
+ } else {
+ debug(dbg_eachfiledetail,"tarobject already exists");
+ }
+
+ /* Check to see if it's a directory or link to one and we don't need to
+ * do anything. This has to be done now so that we don't die due to
+ * a file overwriting conflict. */
+ existingdir = false;
+ switch (ti->type) {
+ case TAR_FILETYPE_SYMLINK:
+ /* If it's already an existing directory, do nothing. */
+ if (!statr && S_ISDIR(stab.st_mode)) {
+ debug(dbg_eachfiledetail, "tarobject symlink exists as directory");
+ existingdir = true;
+ } else if (!statr && S_ISLNK(stab.st_mode)) {
+ if (linktosameexistingdir(ti, fnamevb.buf, &symlinkfn))
+ existingdir = true;
+ }
+ break;
+ case TAR_FILETYPE_DIR:
+ /* If it's already an existing directory, do nothing. */
+ if (!stat(fnamevb.buf,&stabtmp) && S_ISDIR(stabtmp.st_mode)) {
+ debug(dbg_eachfiledetail, "tarobject directory exists");
+ existingdir = true;
+ }
+ break;
+ case TAR_FILETYPE_FILE:
+ case TAR_FILETYPE_CHARDEV:
+ case TAR_FILETYPE_BLOCKDEV:
+ case TAR_FILETYPE_FIFO:
+ case TAR_FILETYPE_HARDLINK:
+ break;
+ default:
+ ohshit(_("archive contained object '%.255s' of unknown type 0x%x"),
+ ti->name, ti->type);
+ }
+
+ keepexisting = false;
+ refcounting = false;
+ if (!existingdir) {
+ struct fsys_node_pkgs_iter *iter;
+
+ iter = fsys_node_pkgs_iter_new(nifd->namenode);
+ while ((otherpkg = fsys_node_pkgs_iter_next(iter))) {
+ if (otherpkg == tc->pkg)
+ continue;
+ debug(dbg_eachfile, "tarobject ... found in %s",
+ pkg_name(otherpkg, pnaw_always));
+
+ /* A pkgset can share files between its instances. Overwriting
+ * is allowed when they are not getting in sync, otherwise the
+ * file content must match the installed file. */
+ if (otherpkg->set == tc->pkg->set &&
+ otherpkg->installed.multiarch == PKG_MULTIARCH_SAME &&
+ tc->pkg->available.multiarch == PKG_MULTIARCH_SAME) {
+ if (statr == 0 && tc->pkgset_getting_in_sync)
+ refcounting = true;
+ debug(dbg_eachfiledetail, "tarobject ... shared with %s %s (syncing=%d)",
+ pkg_name(otherpkg, pnaw_always),
+ versiondescribe(&otherpkg->installed.version, vdew_nonambig),
+ tc->pkgset_getting_in_sync);
+ continue;
+ }
+
+ if (nifd->namenode->divert && nifd->namenode->divert->useinstead) {
+ /* Right, so we may be diverting this file. This makes the conflict
+ * OK iff one of us is the diverting package (we don't need to
+ * check for both being the diverting package, obviously). */
+ divpkgset = nifd->namenode->divert->pkgset;
+ debug(dbg_eachfile, "tarobject ... diverted, divpkgset=%s",
+ divpkgset ? divpkgset->name : "<none>");
+ if (otherpkg->set == divpkgset || tc->pkg->set == divpkgset)
+ continue;
+ }
+
+ /* If the new object is a directory and the previous object does
+ * not exist assume it's also a directory and skip further checks.
+ * XXX: Ideally with more information about the installed files we
+ * could perform more clever checks. */
+ if (statr != 0 && ti->type == TAR_FILETYPE_DIR) {
+ debug(dbg_eachfile, "tarobject ... assuming shared directory");
+ continue;
+ }
+
+ ensure_package_clientdata(otherpkg);
+
+ /* Nope? Hmm, file conflict, perhaps. Check Replaces. */
+ switch (otherpkg->clientdata->replacingfilesandsaid) {
+ case 2:
+ keepexisting = true;
+ /* Fall through. */
+ case 1:
+ continue;
+ }
+
+ /* Is the package with the conflicting file in the “config files only”
+ * state? If so it must be a config file and we can silently take it
+ * over. */
+ if (otherpkg->status == PKG_STAT_CONFIGFILES)
+ continue;
+
+ /* Perhaps we're removing a conflicting package? */
+ if (otherpkg->clientdata->istobe == PKG_ISTOBE_REMOVE)
+ continue;
+
+ /* Is the file an obsolete conffile in the other package
+ * and a conffile in the new package? */
+ if ((nifd->namenode->flags & FNNF_NEW_CONFF) &&
+ !statr && S_ISREG(stab.st_mode)) {
+ for (conff = otherpkg->installed.conffiles;
+ conff;
+ conff = conff->next) {
+ if (!conff->obsolete)
+ continue;
+ if (strcmp(conff->name, nifd->namenode->name) == 0)
+ break;
+ }
+ if (conff) {
+ debug(dbg_eachfiledetail, "tarobject other's obsolete conffile");
+ /* process_archive() will have copied its hash already. */
+ continue;
+ }
+ }
+
+ if (does_replace(tc->pkg, &tc->pkg->available,
+ otherpkg, &otherpkg->installed)) {
+ printf(_("Replacing files in old package %s (%s) ...\n"),
+ pkg_name(otherpkg, pnaw_nonambig),
+ versiondescribe(&otherpkg->installed.version, vdew_nonambig));
+ otherpkg->clientdata->replacingfilesandsaid = 1;
+ } else if (does_replace(otherpkg, &otherpkg->installed,
+ tc->pkg, &tc->pkg->available)) {
+ printf(_("Replaced by files in installed package %s (%s) ...\n"),
+ pkg_name(otherpkg, pnaw_nonambig),
+ versiondescribe(&otherpkg->installed.version, vdew_nonambig));
+ otherpkg->clientdata->replacingfilesandsaid = 2;
+ nifd->namenode->flags &= ~FNNF_NEW_INARCHIVE;
+ keepexisting = true;
+ } else {
+ /* At this point we are replacing something without a Replaces. */
+ if (!statr && S_ISDIR(stab.st_mode)) {
+ forcibleerr(FORCE_OVERWRITE_DIR,
+ _("trying to overwrite directory '%.250s' "
+ "in package %.250s %.250s with nondirectory"),
+ nifd->namenode->name, pkg_name(otherpkg, pnaw_nonambig),
+ versiondescribe(&otherpkg->installed.version,
+ vdew_nonambig));
+ } else {
+ forcibleerr(FORCE_OVERWRITE,
+ _("trying to overwrite '%.250s', "
+ "which is also in package %.250s %.250s"),
+ nifd->namenode->name, pkg_name(otherpkg, pnaw_nonambig),
+ versiondescribe(&otherpkg->installed.version,
+ vdew_nonambig));
+ }
+ }
+ }
+ fsys_node_pkgs_iter_free(iter);
+ }
+
+ if (keepexisting) {
+ if (nifd->namenode->flags & FNNF_NEW_CONFF)
+ nifd->namenode->flags |= FNNF_OBS_CONFF;
+ tar_fsys_namenode_queue_pop(tc->newfiles_queue, oldnifd, nifd);
+ tarobject_skip_entry(tc, ti);
+ return 0;
+ }
+
+ if (filter_should_skip(ti)) {
+ nifd->namenode->flags &= ~FNNF_NEW_INARCHIVE;
+ nifd->namenode->flags |= FNNF_FILTERED;
+ tarobject_skip_entry(tc, ti);
+
+ return 0;
+ }
+
+ if (existingdir)
+ return 0;
+
+ /* Compute the hash of the previous object, before we might replace it
+ * with the new version on forced overwrites. */
+ if (refcounting) {
+ debug(dbg_eachfiledetail, "tarobject hashing on-disk file '%s', refcounting",
+ fnamevb.buf);
+ if (nifd->namenode->flags & FNNF_NEW_CONFF) {
+ md5hash_prev_conffile(tc->pkg, oldhash, fnamenewvb.buf, nifd->namenode);
+ } else if (S_ISREG(stab.st_mode)) {
+ md5hash(tc->pkg, oldhash, fnamevb.buf);
+ } else {
+ strcpy(oldhash, EMPTYHASHFLAG);
+ }
+ }
+
+ if (refcounting && !in_force(FORCE_OVERWRITE)) {
+ /* If we are not forced to overwrite the path and are refcounting,
+ * just compute the hash w/o extracting the object. */
+ tarobject_hash(tc, ti, nifd->namenode);
+ } else {
+ /* Now, at this stage we want to make sure neither of .dpkg-new and
+ * .dpkg-tmp are hanging around. */
+ path_remove_tree(fnamenewvb.buf);
+ path_remove_tree(fnametmpvb.buf);
+
+ /* Now we start to do things that we need to be able to undo
+ * if something goes wrong. Watch out for the CLEANUP comments to
+ * keep an eye on what's installed on the disk at each point. */
+ push_cleanup(cu_installnew, ~ehflag_normaltidy, 1, nifd->namenode);
+
+ /*
+ * CLEANUP: Now we either have the old file on the disk, or not, in
+ * its original filename.
+ */
+
+ /* Extract whatever it is as .dpkg-new ... */
+ tarobject_extract(tc, ti, fnamenewvb.buf, &nodestat, nifd->namenode);
+ }
+
+ /* For shared files, check now if the object matches. */
+ if (refcounting)
+ tarobject_matches(tc, fnamevb.buf, &stab, oldhash,
+ fnamenewvb.buf, ti, nifd->namenode);
+
+ /* If we didn't extract anything, there's nothing else to do. */
+ if (refcounting && !in_force(FORCE_OVERWRITE))
+ return 0;
+
+ tarobject_set_perms(ti, fnamenewvb.buf, &nodestat);
+ tarobject_set_mtime(ti, fnamenewvb.buf);
+ tarobject_set_se_context(fnamevb.buf, fnamenewvb.buf, nodestat.mode);
+
+ /*
+ * CLEANUP: Now we have extracted the new object in .dpkg-new (or,
+ * if the file already exists as a directory and we were trying to
+ * extract a directory or symlink, we returned earlier, so we don't
+ * need to worry about that here).
+ *
+ * The old file is still in the original filename,
+ */
+
+ /* First, check to see if it's a conffile. If so we don't install
+ * it now - we leave it in .dpkg-new for --configure to take care of. */
+ if (nifd->namenode->flags & FNNF_NEW_CONFF) {
+ debug(dbg_conffdetail,"tarobject conffile extracted");
+ nifd->namenode->flags |= FNNF_ELIDE_OTHER_LISTS;
+ return 0;
+ }
+
+ /* Now we move the old file out of the way, the backup file will
+ * be deleted later. */
+ if (statr) {
+ /* Don't try to back it up if it didn't exist. */
+ debug(dbg_eachfiledetail,"tarobject new - no backup");
+ } else {
+ if (ti->type == TAR_FILETYPE_DIR || S_ISDIR(stab.st_mode)) {
+ /* One of the two is a directory - can't do atomic install. */
+ debug(dbg_eachfiledetail,"tarobject directory, nonatomic");
+ nifd->namenode->flags |= FNNF_NO_ATOMIC_OVERWRITE;
+ if (rename(fnamevb.buf,fnametmpvb.buf))
+ ohshite(_("unable to move aside '%.255s' to install new version"),
+ ti->name);
+ } else if (S_ISLNK(stab.st_mode)) {
+ int rc;
+
+ /* We can't make a symlink with two hardlinks, so we'll have to
+ * copy it. (Pretend that making a copy of a symlink is the same
+ * as linking to it.) */
+ varbuf_reset(&symlinkfn);
+ varbuf_grow(&symlinkfn, stab.st_size + 1);
+ r = readlink(fnamevb.buf, symlinkfn.buf, symlinkfn.size);
+ if (r < 0)
+ ohshite(_("unable to read link '%.255s'"), ti->name);
+ else if (r > stab.st_size)
+ ohshit(_("symbolic link '%.250s' size has changed from %jd to %zd"),
+ fnamevb.buf, (intmax_t)stab.st_size, r);
+ else if (r < stab.st_size)
+ warning(_("symbolic link '%.250s' size has changed from %jd to %zd"),
+ fnamevb.buf, (intmax_t)stab.st_size, r);
+ varbuf_trunc(&symlinkfn, r);
+ varbuf_end_str(&symlinkfn);
+ if (symlink(symlinkfn.buf,fnametmpvb.buf))
+ ohshite(_("unable to make backup symlink for '%.255s'"), ti->name);
+ rc = lchown(fnametmpvb.buf, stab.st_uid, stab.st_gid);
+ if (forcible_nonroot_error(rc))
+ ohshite(_("unable to chown backup symlink for '%.255s'"), ti->name);
+ tarobject_set_se_context(fnamevb.buf, fnametmpvb.buf, stab.st_mode);
+ } else {
+ debug(dbg_eachfiledetail, "tarobject nondirectory, 'link' backup");
+ if (link(fnamevb.buf,fnametmpvb.buf))
+ ohshite(_("unable to make backup link of '%.255s' before installing new version"),
+ ti->name);
+ }
+ }
+
+ /*
+ * CLEANUP: Now the old file is in .dpkg-tmp, and the new file is still
+ * in .dpkg-new.
+ */
+
+ if (ti->type == TAR_FILETYPE_FILE || ti->type == TAR_FILETYPE_HARDLINK ||
+ ti->type == TAR_FILETYPE_SYMLINK) {
+ nifd->namenode->flags |= FNNF_DEFERRED_RENAME;
+
+ debug(dbg_eachfiledetail, "tarobject done and installation deferred");
+ } else {
+ if (rename(fnamenewvb.buf, fnamevb.buf))
+ ohshite(_("unable to install new version of '%.255s'"), ti->name);
+
+ /*
+ * CLEANUP: Now the new file is in the destination file, and the
+ * old file is in .dpkg-tmp to be cleaned up later. We now need
+ * to take a different attitude to cleanup, because we need to
+ * remove the new file.
+ */
+
+ nifd->namenode->flags |= FNNF_PLACED_ON_DISK;
+ nifd->namenode->flags |= FNNF_ELIDE_OTHER_LISTS;
+
+ debug(dbg_eachfiledetail, "tarobject done and installed");
+ }
+
+ return 0;
+}
+
+#if defined(SYNC_FILE_RANGE_WAIT_BEFORE)
+static void
+tar_writeback_barrier(struct fsys_namenode_list *files, struct pkginfo *pkg)
+{
+ struct fsys_namenode_list *cfile;
+
+ for (cfile = files; cfile; cfile = cfile->next) {
+ struct fsys_namenode *usenode;
+ int fd;
+
+ if (!(cfile->namenode->flags & FNNF_DEFERRED_FSYNC))
+ continue;
+
+ usenode = namenodetouse(cfile->namenode, pkg, &pkg->available);
+
+ setupfnamevbs(usenode->name);
+
+ fd = open(fnamenewvb.buf, O_WRONLY);
+ if (fd < 0)
+ ohshite(_("unable to open '%.255s'"), fnamenewvb.buf);
+ /* Ignore the return code as it should be considered equivalent to an
+ * asynchronous hint for the kernel, we are doing an fsync() later on
+ * anyway. */
+ sync_file_range(fd, 0, 0, SYNC_FILE_RANGE_WAIT_BEFORE);
+ if (close(fd))
+ ohshite(_("error closing/writing '%.255s'"), fnamenewvb.buf);
+ }
+}
+#else
+static void
+tar_writeback_barrier(struct fsys_namenode_list *files, struct pkginfo *pkg)
+{
+}
+#endif
+
+void
+tar_deferred_extract(struct fsys_namenode_list *files, struct pkginfo *pkg)
+{
+ struct fsys_namenode_list *cfile;
+ struct fsys_namenode *usenode;
+
+ tar_writeback_barrier(files, pkg);
+
+ for (cfile = files; cfile; cfile = cfile->next) {
+ debug(dbg_eachfile, "deferred extract of '%.255s'", cfile->namenode->name);
+
+ if (!(cfile->namenode->flags & FNNF_DEFERRED_RENAME))
+ continue;
+
+ usenode = namenodetouse(cfile->namenode, pkg, &pkg->available);
+
+ setupfnamevbs(usenode->name);
+
+ if (cfile->namenode->flags & FNNF_DEFERRED_FSYNC) {
+ int fd;
+
+ debug(dbg_eachfiledetail, "deferred extract needs fsync");
+
+ fd = open(fnamenewvb.buf, O_WRONLY);
+ if (fd < 0)
+ ohshite(_("unable to open '%.255s'"), fnamenewvb.buf);
+ if (fsync(fd))
+ ohshite(_("unable to sync file '%.255s'"), fnamenewvb.buf);
+ if (close(fd))
+ ohshite(_("error closing/writing '%.255s'"), fnamenewvb.buf);
+
+ cfile->namenode->flags &= ~FNNF_DEFERRED_FSYNC;
+ }
+
+ debug(dbg_eachfiledetail, "deferred extract needs rename");
+
+ if (rename(fnamenewvb.buf, fnamevb.buf))
+ ohshite(_("unable to install new version of '%.255s'"),
+ cfile->namenode->name);
+
+ cfile->namenode->flags &= ~FNNF_DEFERRED_RENAME;
+
+ /*
+ * CLEANUP: Now the new file is in the destination file, and the
+ * old file is in .dpkg-tmp to be cleaned up later. We now need
+ * to take a different attitude to cleanup, because we need to
+ * remove the new file.
+ */
+
+ cfile->namenode->flags |= FNNF_PLACED_ON_DISK;
+ cfile->namenode->flags |= FNNF_ELIDE_OTHER_LISTS;
+
+ debug(dbg_eachfiledetail, "deferred extract done and installed");
+ }
+}
+
+void
+enqueue_deconfigure(struct pkginfo *pkg, struct pkginfo *pkg_removal)
+{
+ struct pkg_deconf_list *newdeconf;
+
+ ensure_package_clientdata(pkg);
+ pkg->clientdata->istobe = PKG_ISTOBE_DECONFIGURE;
+ newdeconf = m_malloc(sizeof(*newdeconf));
+ newdeconf->next = deconfigure;
+ newdeconf->pkg = pkg;
+ newdeconf->pkg_removal = pkg_removal;
+ deconfigure = newdeconf;
+}
+
+void
+clear_deconfigure_queue(void)
+{
+ struct pkg_deconf_list *deconf, *deconf_next;
+
+ for (deconf = deconfigure; deconf; deconf = deconf_next) {
+ deconf_next = deconf->next;
+ free(deconf);
+ }
+ deconfigure = NULL;
+}
+
+/**
+ * Try if we can deconfigure the package and queue it if so.
+ *
+ * Also checks whether the pdep is forced, first, according to force_p.
+ * force_p may be NULL in which case nothing is considered forced.
+ *
+ * Action is a string describing the action which causes the
+ * deconfiguration:
+ *
+ * "removal of <package>" (due to Conflicts+Depends; removal != NULL)
+ * "installation of <package>" (due to Breaks; removal == NULL)
+ *
+ * @retval 0 Not possible (why is printed).
+ * @retval 1 Deconfiguration queued ok (no message printed).
+ * @retval 2 Forced (no deconfiguration needed, why is printed).
+ */
+static int
+try_deconfigure_can(bool (*force_p)(struct deppossi *), struct pkginfo *pkg,
+ struct deppossi *pdep, const char *action,
+ struct pkginfo *removal, const char *why)
+{
+ if (force_p && force_p(pdep)) {
+ warning(_("ignoring dependency problem with %s:\n%s"), action, why);
+ return 2;
+ } else if (f_autodeconf) {
+ if (pkg->installed.essential) {
+ if (in_force(FORCE_REMOVE_ESSENTIAL)) {
+ warning(_("considering deconfiguration of essential\n"
+ " package %s, to enable %s"),
+ pkg_name(pkg, pnaw_nonambig), action);
+ } else {
+ notice(_("no, %s is essential, will not deconfigure\n"
+ " it in order to enable %s"),
+ pkg_name(pkg, pnaw_nonambig), action);
+ return 0;
+ }
+ }
+ enqueue_deconfigure(pkg, removal);
+ return 1;
+ } else {
+ notice(_("no, cannot proceed with %s (--auto-deconfigure will help):\n%s"),
+ action, why);
+ return 0;
+ }
+}
+
+static int try_remove_can(struct deppossi *pdep,
+ struct pkginfo *fixbyrm,
+ const char *why) {
+ char action[512];
+ sprintf(action, _("removal of %.250s"), pkg_name(fixbyrm, pnaw_nonambig));
+ return try_deconfigure_can(force_depends, pdep->up->up, pdep,
+ action, fixbyrm, why);
+}
+
+void check_breaks(struct dependency *dep, struct pkginfo *pkg,
+ const char *pfilename) {
+ struct pkginfo *fixbydeconf;
+ struct varbuf why = VARBUF_INIT;
+ int ok;
+
+ fixbydeconf = NULL;
+ if (depisok(dep, &why, &fixbydeconf, NULL, false)) {
+ varbuf_destroy(&why);
+ return;
+ }
+
+ varbuf_end_str(&why);
+
+ if (fixbydeconf && f_autodeconf) {
+ char action[512];
+
+ ensure_package_clientdata(fixbydeconf);
+
+ if (fixbydeconf->clientdata->istobe != PKG_ISTOBE_NORMAL)
+ internerr("package %s being fixed by deconf is not to be normal, "
+ "is to be %d",
+ pkg_name(pkg, pnaw_always), fixbydeconf->clientdata->istobe);
+
+ sprintf(action, _("installation of %.250s"),
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig));
+ notice(_("considering deconfiguration of %s, which would be broken by %s ..."),
+ pkg_name(fixbydeconf, pnaw_nonambig), action);
+
+ ok= try_deconfigure_can(force_breaks, fixbydeconf, dep->list,
+ action, NULL, why.buf);
+ if (ok == 1) {
+ notice(_("yes, will deconfigure %s (broken by %s)"),
+ pkg_name(fixbydeconf, pnaw_nonambig),
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig));
+ }
+ } else {
+ notice(_("regarding %s containing %s:\n%s"), pfilename,
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig), why.buf);
+ ok= 0;
+ }
+ varbuf_destroy(&why);
+ if (ok > 0) return;
+
+ if (force_breaks(dep->list)) {
+ warning(_("ignoring breakage, may proceed anyway!"));
+ return;
+ }
+
+ if (fixbydeconf && !f_autodeconf) {
+ ohshit(_("installing %.250s would break %.250s, and\n"
+ " deconfiguration is not permitted (--auto-deconfigure might help)"),
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig),
+ pkg_name(fixbydeconf, pnaw_nonambig));
+ } else {
+ ohshit(_("installing %.250s would break existing software"),
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig));
+ }
+}
+
+void check_conflict(struct dependency *dep, struct pkginfo *pkg,
+ const char *pfilename) {
+ struct pkginfo *fixbyrm;
+ struct deppossi *pdep, flagdeppossi;
+ struct varbuf conflictwhy = VARBUF_INIT, removalwhy = VARBUF_INIT;
+ struct dependency *providecheck;
+
+ fixbyrm = NULL;
+ if (depisok(dep, &conflictwhy, &fixbyrm, NULL, false)) {
+ varbuf_destroy(&conflictwhy);
+ varbuf_destroy(&removalwhy);
+ return;
+ }
+ if (fixbyrm) {
+ ensure_package_clientdata(fixbyrm);
+ if (fixbyrm->clientdata->istobe == PKG_ISTOBE_INSTALLNEW) {
+ fixbyrm= dep->up;
+ ensure_package_clientdata(fixbyrm);
+ }
+ if (((pkg->available.essential && fixbyrm->installed.essential) ||
+ (((fixbyrm->want != PKG_WANT_INSTALL &&
+ fixbyrm->want != PKG_WANT_HOLD) ||
+ does_replace(pkg, &pkg->available, fixbyrm, &fixbyrm->installed)) &&
+ (!fixbyrm->installed.essential || in_force(FORCE_REMOVE_ESSENTIAL))))) {
+ if (fixbyrm->clientdata->istobe != PKG_ISTOBE_NORMAL &&
+ fixbyrm->clientdata->istobe != PKG_ISTOBE_DECONFIGURE)
+ internerr("package %s to be fixed by removal is not to be normal "
+ "nor deconfigure, is to be %d",
+ pkg_name(pkg, pnaw_always), fixbyrm->clientdata->istobe);
+ fixbyrm->clientdata->istobe = PKG_ISTOBE_REMOVE;
+ notice(_("considering removing %s in favour of %s ..."),
+ pkg_name(fixbyrm, pnaw_nonambig),
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig));
+ if (!(fixbyrm->status == PKG_STAT_INSTALLED ||
+ fixbyrm->status == PKG_STAT_TRIGGERSPENDING ||
+ fixbyrm->status == PKG_STAT_TRIGGERSAWAITED)) {
+ notice(_("%s is not properly installed; ignoring any dependencies on it"),
+ pkg_name(fixbyrm, pnaw_nonambig));
+ pdep = NULL;
+ } else {
+ for (pdep = fixbyrm->set->depended.installed;
+ pdep;
+ pdep = pdep->rev_next) {
+ if (pdep->up->type != dep_depends && pdep->up->type != dep_predepends)
+ continue;
+ if (depisok(pdep->up, &removalwhy, NULL, NULL, false))
+ continue;
+ varbuf_end_str(&removalwhy);
+ if (!try_remove_can(pdep,fixbyrm,removalwhy.buf))
+ break;
+ }
+ if (!pdep) {
+ /* If we haven't found a reason not to yet, let's look some more. */
+ for (providecheck= fixbyrm->installed.depends;
+ providecheck;
+ providecheck= providecheck->next) {
+ if (providecheck->type != dep_provides) continue;
+ for (pdep = providecheck->list->ed->depended.installed;
+ pdep;
+ pdep = pdep->rev_next) {
+ if (pdep->up->type != dep_depends && pdep->up->type != dep_predepends)
+ continue;
+ if (depisok(pdep->up, &removalwhy, NULL, NULL, false))
+ continue;
+ varbuf_end_str(&removalwhy);
+ notice(_("may have trouble removing %s, as it provides %s ..."),
+ pkg_name(fixbyrm, pnaw_nonambig),
+ providecheck->list->ed->name);
+ if (!try_remove_can(pdep,fixbyrm,removalwhy.buf))
+ goto break_from_both_loops_at_once;
+ }
+ }
+ break_from_both_loops_at_once:;
+ }
+ }
+ if (!pdep && skip_due_to_hold(fixbyrm)) {
+ pdep= &flagdeppossi;
+ }
+ if (!pdep && (fixbyrm->eflag & PKG_EFLAG_REINSTREQ)) {
+ if (in_force(FORCE_REMOVE_REINSTREQ)) {
+ notice(_("package %s requires reinstallation, but will "
+ "remove anyway as you requested"),
+ pkg_name(fixbyrm, pnaw_nonambig));
+ } else {
+ notice(_("package %s requires reinstallation, will not remove"),
+ pkg_name(fixbyrm, pnaw_nonambig));
+ pdep= &flagdeppossi;
+ }
+ }
+ if (!pdep) {
+ /* This conflict is OK - we'll remove the conflictor. */
+ enqueue_conflictor(fixbyrm);
+ varbuf_destroy(&conflictwhy); varbuf_destroy(&removalwhy);
+ notice(_("yes, will remove %s in favour of %s"),
+ pkg_name(fixbyrm, pnaw_nonambig),
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig));
+ return;
+ }
+ /* Put it back. */
+ fixbyrm->clientdata->istobe = PKG_ISTOBE_NORMAL;
+ }
+ }
+ varbuf_end_str(&conflictwhy);
+ notice(_("regarding %s containing %s:\n%s"), pfilename,
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig), conflictwhy.buf);
+ if (!force_conflicts(dep->list))
+ ohshit(_("conflicting packages - not installing %.250s"),
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig));
+ warning(_("ignoring conflict, may proceed anyway!"));
+ varbuf_destroy(&conflictwhy);
+
+ return;
+}
+
+void cu_cidir(int argc, void **argv) {
+ char *cidir= (char*)argv[0];
+ char *cidirrest= (char*)argv[1];
+ cidirrest[-1] = '\0';
+ path_remove_tree(cidir);
+}
+
+void cu_fileslist(int argc, void **argv) {
+ tar_pool_release();
+}
+
+int
+archivefiles(const char *const *argv)
+{
+ const char *const *volatile argp;
+ const char **volatile arglist = NULL;
+ int i;
+ jmp_buf ejbuf;
+ enum modstatdb_rw msdbflags;
+
+ trigproc_install_hooks();
+
+ if (f_noact)
+ msdbflags = msdbrw_readonly;
+ else if (cipaction->arg_int == act_avail)
+ msdbflags = msdbrw_readonly | msdbrw_available_write;
+ else if (in_force(FORCE_NON_ROOT))
+ msdbflags = msdbrw_write;
+ else
+ msdbflags = msdbrw_needsuperuser;
+
+ modstatdb_open(msdbflags);
+
+ checkpath();
+ pkg_infodb_upgrade();
+
+ log_message("startup archives %s", cipaction->olong);
+
+ if (f_recursive) {
+ const char *const *ap;
+ int nfiles = 0;
+
+ if (!*argv)
+ badusage(_("--%s --recursive needs at least one path argument"),cipaction->olong);
+
+ for (ap = argv; *ap; ap++) {
+ struct treeroot *tree;
+ struct treenode *node;
+
+ tree = treewalk_open((const char *)*ap, TREEWALK_FOLLOW_LINKS, NULL);
+
+ while ((node = treewalk_next(tree))) {
+ const char *nodename;
+
+ if (!S_ISREG(treenode_get_mode(node)))
+ continue;
+
+ /* Check if it looks like a .deb file. */
+ nodename = treenode_get_pathname(node);
+ if (strcmp(nodename + strlen(nodename) - 4, ".deb") != 0)
+ continue;
+
+ arglist = m_realloc(arglist, sizeof(char *) * (nfiles + 2));
+ arglist[nfiles++] = m_strdup(nodename);
+ }
+
+ treewalk_close(tree);
+ }
+
+ if (!nfiles)
+ ohshit(_("searched, but found no packages (files matching *.deb)"));
+
+ arglist[nfiles] = NULL;
+ argp= arglist;
+ } else {
+ if (!*argv) badusage(_("--%s needs at least one package archive file argument"),
+ cipaction->olong);
+ argp= argv;
+ }
+
+ /* Perform some sanity checks on the passed archives. */
+ for (i = 0; argp[i]; i++) {
+ struct stat st;
+
+ /* We need the filename to exist. */
+ if (stat(argp[i], &st) < 0)
+ ohshite(_("cannot access archive '%s'"), argp[i]);
+
+ /* We cannot work with anything that is not a regular file. */
+ if (!S_ISREG(st.st_mode))
+ ohshit(_("archive '%s' is not a regular file"), argp[i]);
+ }
+
+ currenttime = time(NULL);
+
+ /* Initialize fname variables contents. */
+
+ varbuf_reset(&fnamevb);
+ varbuf_reset(&fnametmpvb);
+ varbuf_reset(&fnamenewvb);
+
+ varbuf_add_str(&fnamevb, instdir);
+ varbuf_add_str(&fnametmpvb, instdir);
+ varbuf_add_str(&fnamenewvb, instdir);
+
+ varbuf_snapshot(&fnamevb, &fname_state);
+
+ ensure_diversions();
+ ensure_statoverrides(STATDB_PARSE_NORMAL);
+
+ for (i = 0; argp[i]; i++) {
+ if (setjmp(ejbuf)) {
+ pop_error_context(ehflag_bombout);
+ if (abort_processing)
+ break;
+ continue;
+ }
+ push_error_context_jump(&ejbuf, print_error_perarchive, argp[i]);
+
+ dpkg_selabel_load();
+
+ process_archive(argp[i]);
+ onerr_abort++;
+ m_output(stdout, _("<standard output>"));
+ m_output(stderr, _("<standard error>"));
+ onerr_abort--;
+
+ pop_error_context(ehflag_normaltidy);
+ }
+
+ dpkg_selabel_close();
+
+ free(arglist);
+
+ switch (cipaction->arg_int) {
+ case act_install:
+ case act_configure:
+ case act_triggers:
+ case act_remove:
+ case act_purge:
+ process_queue();
+ case act_unpack:
+ case act_avail:
+ break;
+ default:
+ internerr("unknown action '%d'", cipaction->arg_int);
+ }
+
+ trigproc_run_deferred();
+ modstatdb_shutdown();
+
+ return 0;
+}
+
+/**
+ * Decide whether we want to install a new version of the package.
+ *
+ * @param pkg The package with the version we might want to install
+ *
+ * @retval true If the package should be skipped.
+ * @retval false If the package should be installed.
+ */
+bool
+wanttoinstall(struct pkginfo *pkg)
+{
+ int rc;
+
+ if (pkg->want != PKG_WANT_INSTALL && pkg->want != PKG_WANT_HOLD) {
+ if (f_alsoselect) {
+ printf(_("Selecting previously unselected package %s.\n"),
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig));
+ return true;
+ } else {
+ printf(_("Skipping unselected package %s.\n"),
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig));
+ return false;
+ }
+ }
+
+ if (pkg->eflag & PKG_EFLAG_REINSTREQ)
+ return true;
+ if (pkg->status < PKG_STAT_UNPACKED)
+ return true;
+
+ rc = dpkg_version_compare(&pkg->available.version, &pkg->installed.version);
+ if (rc > 0) {
+ return true;
+ } else if (rc == 0) {
+ /* Same version fully installed. */
+ if (f_skipsame) {
+ notice(_("version %.250s of %.250s already installed, skipping"),
+ versiondescribe(&pkg->installed.version, vdew_nonambig),
+ pkg_name(pkg, pnaw_nonambig));
+ return false;
+ } else {
+ return true;
+ }
+ } else {
+ if (in_force(FORCE_DOWNGRADE)) {
+ warning(_("downgrading %.250s from %.250s to %.250s"),
+ pkg_name(pkg, pnaw_nonambig),
+ versiondescribe(&pkg->installed.version, vdew_nonambig),
+ versiondescribe(&pkg->available.version, vdew_nonambig));
+ return true;
+ } else {
+ notice(_("will not downgrade %.250s from %.250s to %.250s, skipping"),
+ pkg_name(pkg, pnaw_nonambig),
+ versiondescribe(&pkg->installed.version, vdew_nonambig),
+ versiondescribe(&pkg->available.version, vdew_nonambig));
+ return false;
+ }
+ }
+}
diff --git a/src/archives.h b/src/archives.h
new file mode 100644
index 0000000..2ceab9b
--- /dev/null
+++ b/src/archives.h
@@ -0,0 +1,95 @@
+/*
+ * dpkg - main program for package management
+ * archives.h - functions common to archives.c and unpack.c
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008-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 ARCHIVES_H
+#define ARCHIVES_H
+
+#include <stdbool.h>
+
+#include <dpkg/tarfn.h>
+
+struct tarcontext {
+ int backendpipe;
+ struct pkginfo *pkg;
+ /** A queue of fsys_namenode that have been extracted anew. */
+ struct fsys_namenode_queue *newfiles_queue;
+ /** Are all “Multi-arch: same” instances about to be in sync? */
+ bool pkgset_getting_in_sync;
+};
+
+struct pkg_deconf_list {
+ struct pkg_deconf_list *next;
+ struct pkginfo *pkg;
+ struct pkginfo *pkg_removal;
+};
+
+extern struct varbuf_state fname_state;
+extern struct varbuf fnamevb;
+extern struct varbuf fnametmpvb;
+extern struct varbuf fnamenewvb;
+extern struct pkg_deconf_list *deconfigure;
+
+void clear_deconfigure_queue(void);
+void enqueue_deconfigure(struct pkginfo *pkg, struct pkginfo *pkg_removal);
+void enqueue_conflictor(struct pkginfo *pkg);
+
+void cu_pathname(int argc, void **argv);
+void cu_cidir(int argc, void **argv);
+void cu_fileslist(int argc, void **argv);
+void cu_backendpipe(int argc, void **argv);
+
+void cu_installnew(int argc, void **argv);
+
+void cu_prermupgrade(int argc, void **argv);
+void cu_prerminfavour(int argc, void **argv);
+void cu_preinstverynew(int argc, void **argv);
+void cu_preinstnew(int argc, void **argv);
+void cu_preinstupgrade(int argc, void **argv);
+void cu_postrmupgrade(int argc, void **argv);
+
+void cu_prermdeconfigure(int argc, void **argv);
+void ok_prermdeconfigure(int argc, void **argv);
+
+void setupfnamevbs(const char *filename);
+
+int
+tarobject(struct tar_archive *tar, struct tar_entry *ti);
+int
+tarfileread(struct tar_archive *tar, char *buf, int len);
+void
+tar_deferred_extract(struct fsys_namenode_list *files, struct pkginfo *pkg);
+
+struct fsys_namenode_list *
+tar_fsys_namenode_queue_push(struct fsys_namenode_queue *queue,
+ struct fsys_namenode *namenode);
+
+bool
+filesavespackage(struct fsys_namenode_list *, struct pkginfo *,
+ struct pkginfo *pkgbeinginstalled);
+
+void check_conflict(struct dependency *dep, struct pkginfo *pkg,
+ const char *pfilename);
+void check_breaks(struct dependency *dep, struct pkginfo *pkg,
+ const char *pfilename);
+
+extern int cleanup_pkg_failed, cleanup_conflictor_failed;
+
+#endif /* ARCHIVES_H */
diff --git a/src/cleanup.c b/src/cleanup.c
new file mode 100644
index 0000000..3539fbf
--- /dev/null
+++ b/src/cleanup.c
@@ -0,0 +1,258 @@
+/*
+ * dpkg - main program for package management
+ * cleanup.c - cleanup functions, used when we need to unwind
+ *
+ * Copyright © 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/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <utime.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg.h>
+#include <dpkg/path.h>
+#include <dpkg/options.h>
+#include <dpkg/db-fsys.h>
+
+#include "main.h"
+#include "archives.h"
+
+int cleanup_pkg_failed=0, cleanup_conflictor_failed=0;
+
+/**
+ * Something went wrong and we're undoing.
+ *
+ * We have the following possible situations for non-conffiles:
+ * «pathname».dpkg-tmp exists - in this case we want to remove
+ * «pathname» if it exists and replace it with «pathname».dpkg-tmp.
+ * This undoes the backup operation.
+ * «pathname».dpkg-tmp does not exist - «pathname» may be on the disk,
+ * as a new file which didn't fail, remove it if it is.
+ *
+ * In both cases, we also make sure we delete «pathname».dpkg-new in
+ * case that's still hanging around.
+ *
+ * For conffiles, we simply delete «pathname».dpkg-new. For these,
+ * «pathname».dpkg-tmp shouldn't exist, as we don't make a backup
+ * at this stage. Just to be on the safe side, though, we don't
+ * look for it.
+ */
+void cu_installnew(int argc, void **argv) {
+ struct fsys_namenode *namenode = argv[0];
+ struct stat stab;
+
+ cleanup_pkg_failed++; cleanup_conflictor_failed++;
+
+ debug(dbg_eachfile, "cu_installnew '%s' flags=%o",
+ namenode->name, namenode->flags);
+
+ setupfnamevbs(namenode->name);
+
+ if (!(namenode->flags & FNNF_NEW_CONFF) && !lstat(fnametmpvb.buf,&stab)) {
+ /* OK, «pathname».dpkg-tmp exists. Remove «pathname» and
+ * restore «pathname».dpkg-tmp ... */
+ if (namenode->flags & FNNF_NO_ATOMIC_OVERWRITE) {
+ /* If we can't do an atomic overwrite we have to delete first any
+ * link to the new version we may have created. */
+ debug(dbg_eachfiledetail,"cu_installnew restoring nonatomic");
+ if (secure_remove(fnamevb.buf) && errno != ENOENT && errno != ENOTDIR)
+ ohshite(_("unable to remove newly-installed version of '%.250s' to allow"
+ " reinstallation of backup copy"),namenode->name);
+ } else {
+ debug(dbg_eachfiledetail,"cu_installnew restoring atomic");
+ }
+ /* Either we can do an atomic restore, or we've made room: */
+ if (rename(fnametmpvb.buf,fnamevb.buf))
+ ohshite(_("unable to restore backup version of '%.250s'"), namenode->name);
+ /* If «pathname».dpkg-tmp was still a hard link to «pathname», then the
+ * atomic rename did nothing, so we make sure to remove the backup. */
+ else if (unlink(fnametmpvb.buf) && errno != ENOENT)
+ ohshite(_("unable to remove backup copy of '%.250s'"), namenode->name);
+ } else if (namenode->flags & FNNF_PLACED_ON_DISK) {
+ debug(dbg_eachfiledetail,"cu_installnew removing new file");
+ if (secure_remove(fnamevb.buf) && errno != ENOENT && errno != ENOTDIR)
+ ohshite(_("unable to remove newly-installed version of '%.250s'"),
+ namenode->name);
+ } else {
+ debug(dbg_eachfiledetail,"cu_installnew not restoring");
+ }
+ /* Whatever, we delete «pathname».dpkg-new now, if it still exists. */
+ if (secure_remove(fnamenewvb.buf) && errno != ENOENT && errno != ENOTDIR)
+ ohshite(_("unable to remove newly-extracted version of '%.250s'"),
+ namenode->name);
+
+ cleanup_pkg_failed--; cleanup_conflictor_failed--;
+}
+
+void cu_prermupgrade(int argc, void **argv) {
+ struct pkginfo *pkg= (struct pkginfo*)argv[0];
+
+ if (cleanup_pkg_failed++) return;
+ maintscript_postinst(pkg, "abort-upgrade",
+ versiondescribe(&pkg->available.version, vdew_nonambig),
+ NULL);
+ pkg_clear_eflags(pkg, PKG_EFLAG_REINSTREQ);
+ post_postinst_tasks(pkg, PKG_STAT_INSTALLED);
+ cleanup_pkg_failed--;
+}
+
+/*
+ * Also has conflictor in argv[1] and infavour in argv[2].
+ * conflictor may be NULL if deconfigure was due to Breaks.
+ */
+void ok_prermdeconfigure(int argc, void **argv) {
+ struct pkginfo *deconf= (struct pkginfo*)argv[0];
+
+ if (cipaction->arg_int == act_install)
+ enqueue_package(deconf);
+}
+
+/*
+ * conflictor may be NULL.
+ */
+void cu_prermdeconfigure(int argc, void **argv) {
+ struct pkginfo *deconf= (struct pkginfo*)argv[0];
+ struct pkginfo *conflictor = (struct pkginfo *)argv[1];
+ struct pkginfo *infavour= (struct pkginfo*)argv[2];
+
+ if (conflictor) {
+ maintscript_postinst(deconf, "abort-deconfigure",
+ "in-favour",
+ pkgbin_name(infavour, &infavour->available,
+ pnaw_nonambig),
+ versiondescribe(&infavour->available.version,
+ vdew_nonambig),
+ "removing",
+ pkg_name(conflictor, pnaw_nonambig),
+ versiondescribe(&conflictor->installed.version,
+ vdew_nonambig),
+ NULL);
+ } else {
+ maintscript_postinst(deconf, "abort-deconfigure",
+ "in-favour",
+ pkgbin_name(infavour, &infavour->available,
+ pnaw_nonambig),
+ versiondescribe(&infavour->available.version,
+ vdew_nonambig),
+ NULL);
+ }
+
+ post_postinst_tasks(deconf, PKG_STAT_INSTALLED);
+}
+
+void cu_prerminfavour(int argc, void **argv) {
+ struct pkginfo *conflictor= (struct pkginfo*)argv[0];
+ struct pkginfo *infavour= (struct pkginfo*)argv[1];
+
+ if (cleanup_conflictor_failed++) return;
+ maintscript_postinst(conflictor, "abort-remove",
+ "in-favour",
+ pkgbin_name(infavour, &infavour->available,
+ pnaw_nonambig),
+ versiondescribe(&infavour->available.version,
+ vdew_nonambig),
+ NULL);
+ pkg_clear_eflags(conflictor, PKG_EFLAG_REINSTREQ);
+ post_postinst_tasks(conflictor, PKG_STAT_INSTALLED);
+ cleanup_conflictor_failed--;
+}
+
+void cu_preinstverynew(int argc, void **argv) {
+ struct pkginfo *pkg= (struct pkginfo*)argv[0];
+ char *cidir= (char*)argv[1];
+ char *cidirrest= (char*)argv[2];
+
+ if (cleanup_pkg_failed++) return;
+ maintscript_new(pkg, POSTRMFILE, "post-removal", cidir, cidirrest,
+ "abort-install", NULL);
+ pkg_set_status(pkg, PKG_STAT_NOTINSTALLED);
+ pkg_clear_eflags(pkg, PKG_EFLAG_REINSTREQ);
+ pkgbin_blank(&pkg->installed);
+ modstatdb_note(pkg);
+ cleanup_pkg_failed--;
+}
+
+void cu_preinstnew(int argc, void **argv) {
+ struct pkginfo *pkg= (struct pkginfo*)argv[0];
+ char *cidir= (char*)argv[1];
+ char *cidirrest= (char*)argv[2];
+
+ if (cleanup_pkg_failed++) return;
+ maintscript_new(pkg, POSTRMFILE, "post-removal", cidir, cidirrest,
+ "abort-install",
+ versiondescribe(&pkg->installed.version, vdew_nonambig),
+ versiondescribe(&pkg->available.version, vdew_nonambig),
+ NULL);
+ pkg_set_status(pkg, PKG_STAT_CONFIGFILES);
+ pkg_clear_eflags(pkg, PKG_EFLAG_REINSTREQ);
+ modstatdb_note(pkg);
+ cleanup_pkg_failed--;
+}
+
+void cu_preinstupgrade(int argc, void **argv) {
+ struct pkginfo *pkg= (struct pkginfo*)argv[0];
+ char *cidir= (char*)argv[1];
+ char *cidirrest= (char*)argv[2];
+ enum pkgstatus *oldstatusp= (enum pkgstatus*)argv[3];
+
+ if (cleanup_pkg_failed++) return;
+ maintscript_new(pkg, POSTRMFILE, "post-removal", cidir, cidirrest,
+ "abort-upgrade",
+ versiondescribe(&pkg->installed.version, vdew_nonambig),
+ versiondescribe(&pkg->available.version, vdew_nonambig),
+ NULL);
+ pkg_set_status(pkg, *oldstatusp);
+ pkg_clear_eflags(pkg, PKG_EFLAG_REINSTREQ);
+ modstatdb_note(pkg);
+ cleanup_pkg_failed--;
+}
+
+void cu_postrmupgrade(int argc, void **argv) {
+ struct pkginfo *pkg= (struct pkginfo*)argv[0];
+
+ if (cleanup_pkg_failed++) return;
+ maintscript_installed(pkg, PREINSTFILE, "pre-installation",
+ "abort-upgrade",
+ versiondescribe(&pkg->available.version, vdew_nonambig),
+ NULL);
+ cleanup_pkg_failed--;
+}
+
+void cu_prermremove(int argc, void **argv) {
+ struct pkginfo *pkg= (struct pkginfo*)argv[0];
+ enum pkgstatus *oldpkgstatus= (enum pkgstatus*)argv[1];
+
+ if (cleanup_pkg_failed++) return;
+ maintscript_postinst(pkg, "abort-remove", NULL);
+ pkg_clear_eflags(pkg, PKG_EFLAG_REINSTREQ);
+ post_postinst_tasks(pkg, *oldpkgstatus);
+ cleanup_pkg_failed--;
+}
diff --git a/src/configure.c b/src/configure.c
new file mode 100644
index 0000000..12f2904
--- /dev/null
+++ b/src/configure.c
@@ -0,0 +1,829 @@
+/*
+ * dpkg - main program for package management
+ * configure.c - configure packages
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 1999, 2002 Wichert Akkerman <wichert@deephackmode.org>
+ * Copyright © 2007-2015 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 <sys/wait.h>
+
+#include <errno.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <termios.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/macros.h>
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg.h>
+#include <dpkg/string.h>
+#include <dpkg/buffer.h>
+#include <dpkg/file.h>
+#include <dpkg/path.h>
+#include <dpkg/subproc.h>
+#include <dpkg/command.h>
+#include <dpkg/pager.h>
+#include <dpkg/triglib.h>
+#include <dpkg/db-fsys.h>
+
+#include "main.h"
+
+enum conffopt {
+ CFOF_PROMPT = DPKG_BIT(0),
+ CFOF_KEEP = DPKG_BIT(1),
+ CFOF_INSTALL = DPKG_BIT(2),
+ CFOF_BACKUP = DPKG_BIT(3),
+ CFOF_NEW_CONFF = DPKG_BIT(4),
+ CFOF_IS_NEW = DPKG_BIT(5),
+ CFOF_IS_OLD = DPKG_BIT(6),
+ CFOF_USER_DEL = DPKG_BIT(7),
+
+ CFO_KEEP = CFOF_KEEP,
+ CFO_IDENTICAL = CFOF_KEEP,
+ CFO_INSTALL = CFOF_INSTALL,
+ CFO_NEW_CONFF = CFOF_NEW_CONFF | CFOF_INSTALL,
+ CFO_PROMPT = CFOF_PROMPT,
+ CFO_PROMPT_KEEP = CFOF_PROMPT | CFOF_KEEP,
+ CFO_PROMPT_INSTALL = CFOF_PROMPT | CFOF_INSTALL,
+};
+
+static int conffoptcells[2][2] = {
+ /* Distro !edited. */ /* Distro edited. */
+ { CFO_KEEP, CFO_INSTALL }, /* User !edited. */
+ { CFO_KEEP, CFO_PROMPT_KEEP }, /* User edited. */
+};
+
+static int
+show_prompt(const char *cfgfile, const char *realold, const char *realnew,
+ int useredited, int distedited, enum conffopt what)
+{
+ const char *s;
+ int c, cc;
+
+ /* Flush the terminal's input in case the user involuntarily
+ * typed some characters. */
+ tcflush(STDIN_FILENO, TCIFLUSH);
+
+ fputs("\n", stderr);
+ if (strcmp(cfgfile, realold) == 0)
+ fprintf(stderr, _("Configuration file '%s'\n"), cfgfile);
+ else
+ fprintf(stderr, _("Configuration file '%s' (actually '%s')\n"),
+ cfgfile, realold);
+
+ if (what & CFOF_IS_NEW) {
+ fprintf(stderr,
+ _(" ==> File on system created by you or by a script.\n"
+ " ==> File also in package provided by package maintainer.\n"));
+ } else {
+ fprintf(stderr, !useredited ?
+ _(" Not modified since installation.\n") :
+ !(what & CFOF_USER_DEL) ?
+ _(" ==> Modified (by you or by a script) since installation.\n") :
+ _(" ==> Deleted (by you or by a script) since installation.\n"));
+
+ fprintf(stderr, distedited ?
+ _(" ==> Package distributor has shipped an updated version.\n") :
+ _(" Version in package is the same as at last installation.\n"));
+ }
+
+ /* No --force-confdef but a forcible situation. */
+ /* TODO: check if this condition can not be simplified to
+ * just !in_force(FORCE_CONFF_DEF) */
+ if (!(in_force(FORCE_CONFF_DEF) && (what & (CFOF_INSTALL | CFOF_KEEP)))) {
+ if (in_force(FORCE_CONFF_NEW)) {
+ fprintf(stderr,
+ _(" ==> Using new file as you requested.\n"));
+ return 'y';
+ } else if (in_force(FORCE_CONFF_OLD)) {
+ fprintf(stderr,
+ _(" ==> Using current old file as you requested.\n"));
+ return 'n';
+ }
+ }
+
+ /* Force the default action (if there is one. */
+ if (in_force(FORCE_CONFF_DEF)) {
+ if (what & CFOF_KEEP) {
+ fprintf(stderr,
+ _(" ==> Keeping old config file as default.\n"));
+ return 'n';
+ } else if (what & CFOF_INSTALL) {
+ fprintf(stderr,
+ _(" ==> Using new config file as default.\n"));
+ return 'y';
+ }
+ }
+
+ fprintf(stderr,
+ _(" What would you like to do about it ? Your options are:\n"
+ " Y or I : install the package maintainer's version\n"
+ " N or O : keep your currently-installed version\n"
+ " D : show the differences between the versions\n"
+ " Z : start a shell to examine the situation\n"));
+
+ if (what & CFOF_KEEP)
+ fprintf(stderr,
+ _(" The default action is to keep your current version.\n"));
+ else if (what & CFOF_INSTALL)
+ fprintf(stderr,
+ _(" The default action is to install the new version.\n"));
+
+ s = path_basename(cfgfile);
+ fprintf(stderr, "*** %s (Y/I/N/O/D/Z) %s ? ", s,
+ (what & CFOF_KEEP) ? _("[default=N]") :
+ (what & CFOF_INSTALL) ? _("[default=Y]") :
+ _("[no default]"));
+
+ if (ferror(stderr))
+ ohshite(_("error writing to stderr, discovered before conffile prompt"));
+
+ cc = 0;
+ while ((c = getchar()) != EOF && c != '\n')
+ if (!isspace(c) && !cc)
+ cc = tolower(c);
+
+ if (c == EOF) {
+ if (ferror(stdin))
+ ohshite(_("read error on stdin at conffile prompt"));
+ ohshit(_("end of file on stdin at conffile prompt"));
+ }
+
+ if (!cc) {
+ if (what & CFOF_KEEP)
+ return 'n';
+ else if (what & CFOF_INSTALL)
+ return 'y';
+ }
+
+ return cc;
+}
+
+/**
+ * Show a diff between two files.
+ *
+ * @param old The path to the old file.
+ * @param new The path to the new file.
+ */
+static void
+show_diff(const char *old, const char *new)
+{
+ struct pager *pager;
+ pid_t pid;
+
+ pager = pager_spawn(_("conffile difference visualizer"));
+
+ pid = subproc_fork();
+ if (!pid) {
+ /* Child process. */
+ struct command cmd;
+
+ command_init(&cmd, DIFF, _("conffile difference visualizer"));
+ command_add_arg(&cmd, DIFF);
+ command_add_arg(&cmd, "-Nu");
+ command_add_arg(&cmd, old);
+ command_add_arg(&cmd, new);
+ command_exec(&cmd);
+ }
+
+ /* Parent process. */
+ subproc_reap(pid, _("conffile difference visualizer"), SUBPROC_NOCHECK);
+ pager_reap(pager);
+}
+
+/**
+ * Spawn a new shell.
+ *
+ * Create a subprocess and execute a shell to allow the user to manually
+ * solve the conffile conflict.
+ *
+ * @param confold The path to the old conffile.
+ * @param confnew The path to the new conffile.
+ */
+static void
+spawn_shell(const char *confold, const char *confnew)
+{
+ pid_t pid;
+
+ fputs(_("Useful environment variables:\n"), stderr);
+ fputs(" - DPKG_SHELL_REASON\n", stderr);
+ fputs(" - DPKG_CONFFILE_OLD\n", stderr);
+ fputs(" - DPKG_CONFFILE_NEW\n", stderr);
+ fputs(_("Type 'exit' when you're done.\n"), stderr);
+
+ pid = subproc_fork();
+ if (!pid) {
+ /* Set useful variables for the user. */
+ setenv("DPKG_SHELL_REASON", "conffile-prompt", 1);
+ setenv("DPKG_CONFFILE_OLD", confold, 1);
+ setenv("DPKG_CONFFILE_NEW", confnew, 1);
+
+ command_shell(NULL, _("conffile shell"));
+ }
+
+ /* Parent process. */
+ subproc_reap(pid, _("conffile shell"), SUBPROC_NOCHECK);
+}
+
+/**
+ * Prompt the user for how to resolve a conffile conflict.
+ *
+ * When encountering a conffile conflict during configuration, the user will
+ * normally be presented with a textual menu of possible actions. This
+ * behavior is modified via various --force flags and perhaps on whether
+ * or not a terminal is available to do the prompting.
+ *
+ * @param pkg The package owning the conffile.
+ * @param cfgfile The path to the old conffile.
+ * @param realold The path to the old conffile, dereferenced in case of a
+ * symlink, otherwise equal to cfgfile.
+ * @param realnew The path to the new conffile, dereferenced in case of a
+ * symlink).
+ * @param useredited A flag to indicate whether the file has been edited
+ * locally. Set to nonzero to indicate that the file has been modified.
+ * @param distedited A flag to indicate whether the file has been updated
+ * between package versions. Set to nonzero to indicate that the file
+ * has been updated.
+ * @param what Hints on what action should be taken by default.
+ *
+ * @return The action which should be taken based on user input and/or the
+ * default actions as configured by cmdline/configuration options.
+ */
+static enum conffopt
+promptconfaction(struct pkginfo *pkg, const char *cfgfile,
+ const char *realold, const char *realnew,
+ int useredited, int distedited, enum conffopt what)
+{
+ int cc;
+
+ if (!(what & CFOF_PROMPT))
+ return what;
+
+ statusfd_send("status: %s : %s : '%s' '%s' %i %i ",
+ cfgfile, "conffile-prompt",
+ realold, realnew, useredited, distedited);
+
+ do {
+ cc = show_prompt(cfgfile, realold, realnew,
+ useredited, distedited, what);
+
+ /* FIXME: Say something if silently not install. */
+ if (cc == 'd')
+ show_diff(realold, realnew);
+
+ if (cc == 'z')
+ spawn_shell(realold, realnew);
+ } while (!strchr("yino", cc));
+
+ log_message("conffile %s %s", cfgfile,
+ (cc == 'i' || cc == 'y') ? "install" : "keep");
+
+ what &= CFOF_USER_DEL;
+
+ switch (cc) {
+ case 'i':
+ case 'y':
+ what |= CFOF_INSTALL | CFOF_BACKUP;
+ break;
+
+ case 'n':
+ case 'o':
+ what |= CFOF_KEEP | CFOF_BACKUP;
+ break;
+
+ default:
+ internerr("unknown response '%d'", cc);
+ }
+
+ return what;
+}
+
+/**
+ * Configure the ghost conffile instance.
+ *
+ * When the first instance of a package set is configured, the *.dpkg-new
+ * files gets installed into their destination, which makes configuration of
+ * conffiles from subsequent package instances be skipped along with updates
+ * to the Conffiles field hash.
+ *
+ * In case the conffile has already been processed, sync the hash from an
+ * already configured package instance conffile.
+ *
+ * @param pkg The current package being configured.
+ * @param conff The current conffile being configured.
+ */
+static void
+deferred_configure_ghost_conffile(struct pkginfo *pkg, struct conffile *conff)
+{
+ struct pkginfo *otherpkg;
+ struct conffile *otherconff;
+
+ for (otherpkg = &pkg->set->pkg; otherpkg; otherpkg = otherpkg->arch_next) {
+ if (otherpkg == pkg)
+ continue;
+ if (otherpkg->status <= PKG_STAT_HALFCONFIGURED)
+ continue;
+
+ for (otherconff = otherpkg->installed.conffiles; otherconff;
+ otherconff = otherconff->next) {
+ if (otherconff->obsolete)
+ continue;
+
+ /* Check if we need to propagate the new hash from
+ * an already processed conffile in another package
+ * instance. */
+ if (strcmp(otherconff->name, conff->name) == 0) {
+ conff->hash = otherconff->hash;
+ modstatdb_note(pkg);
+ return;
+ }
+ }
+ }
+}
+
+static void
+deferred_configure_conffile(struct pkginfo *pkg, struct conffile *conff)
+{
+ struct fsys_namenode *usenode;
+ char currenthash[MD5HASHLEN + 1], newdisthash[MD5HASHLEN + 1];
+ int useredited, distedited;
+ enum conffopt what;
+ struct stat stab;
+ struct varbuf cdr = VARBUF_INIT, cdr2 = VARBUF_INIT;
+ char *cdr2rest;
+ int rc;
+
+ usenode = namenodetouse(fsys_hash_find_node(conff->name, FHFF_NOCOPY),
+ pkg, &pkg->installed);
+
+ rc = conffderef(pkg, &cdr, usenode->name);
+ if (rc == -1) {
+ conff->hash = EMPTYHASHFLAG;
+ return;
+ }
+ md5hash(pkg, currenthash, cdr.buf);
+
+ varbuf_reset(&cdr2);
+ varbuf_add_str(&cdr2, cdr.buf);
+ varbuf_end_str(&cdr2);
+ /* XXX: Make sure there's enough room for extensions. */
+ varbuf_grow(&cdr2, 50);
+ cdr2rest = cdr2.buf + strlen(cdr.buf);
+ /* From now on we can just strcpy(cdr2rest, extension); */
+
+ strcpy(cdr2rest, DPKGNEWEXT);
+ /* If the .dpkg-new file is no longer there, ignore this one. */
+ if (lstat(cdr2.buf, &stab)) {
+ if (errno == ENOENT) {
+ /* But, sync the conffile hash value from another
+ * package set instance. */
+ deferred_configure_ghost_conffile(pkg, conff);
+ return;
+ }
+ ohshite(_("unable to stat new distributed conffile '%.250s'"),
+ cdr2.buf);
+ }
+ md5hash(pkg, newdisthash, cdr2.buf);
+
+ /* Copy the permissions from the installed version to the new
+ * distributed version. */
+ if (!stat(cdr.buf, &stab))
+ file_copy_perms(cdr.buf, cdr2.buf);
+ else if (errno != ENOENT)
+ ohshite(_("unable to stat current installed conffile '%.250s'"),
+ cdr.buf);
+
+ /* Select what to do. */
+ if (strcmp(currenthash, newdisthash) == 0) {
+ /* They're both the same so there's no point asking silly
+ * questions. */
+ useredited = -1;
+ distedited = -1;
+ what = CFO_IDENTICAL;
+ } else if (strcmp(currenthash, NONEXISTENTFLAG) == 0 && in_force(FORCE_CONFF_MISS)) {
+ fprintf(stderr,
+ _("\n"
+ "Configuration file '%s', does not exist on system.\n"
+ "Installing new config file as you requested.\n"),
+ usenode->name);
+ what = CFO_NEW_CONFF;
+ useredited = -1;
+ distedited = -1;
+ } else if (strcmp(conff->hash, NEWCONFFILEFLAG) == 0) {
+ if (strcmp(currenthash, NONEXISTENTFLAG) == 0) {
+ what = CFO_NEW_CONFF;
+ useredited = -1;
+ distedited = -1;
+ } else {
+ useredited = 1;
+ distedited = 1;
+ what = conffoptcells[useredited][distedited] |
+ CFOF_IS_NEW;
+ }
+ } else {
+ useredited = strcmp(conff->hash, currenthash) != 0;
+ distedited = strcmp(conff->hash, newdisthash) != 0;
+
+ if (in_force(FORCE_CONFF_ASK) && useredited)
+ what = CFO_PROMPT_KEEP;
+ else
+ what = conffoptcells[useredited][distedited];
+
+ if (strcmp(currenthash, NONEXISTENTFLAG) == 0)
+ what |= CFOF_USER_DEL;
+ }
+
+ debug(dbg_conff,
+ "deferred_configure '%s' (= '%s') useredited=%d distedited=%d what=%o",
+ usenode->name, cdr.buf, useredited, distedited, what);
+
+ what = promptconfaction(pkg, usenode->name, cdr.buf, cdr2.buf,
+ useredited, distedited, what);
+
+ switch (what & ~(CFOF_IS_NEW | CFOF_USER_DEL)) {
+ case CFO_KEEP | CFOF_BACKUP:
+ strcpy(cdr2rest, DPKGOLDEXT);
+ if (unlink(cdr2.buf) && errno != ENOENT)
+ warning(_("%s: failed to remove old backup '%.250s': %s"),
+ pkg_name(pkg, pnaw_nonambig), cdr2.buf,
+ strerror(errno));
+
+ varbuf_add_str(&cdr, DPKGDISTEXT);
+ varbuf_end_str(&cdr);
+ strcpy(cdr2rest, DPKGNEWEXT);
+ trig_path_activate(usenode, pkg);
+ if (rename(cdr2.buf, cdr.buf))
+ warning(_("%s: failed to rename '%.250s' to '%.250s': %s"),
+ pkg_name(pkg, pnaw_nonambig), cdr2.buf, cdr.buf,
+ strerror(errno));
+ break;
+ case CFO_KEEP:
+ strcpy(cdr2rest, DPKGNEWEXT);
+ if (unlink(cdr2.buf))
+ warning(_("%s: failed to remove '%.250s': %s"),
+ pkg_name(pkg, pnaw_nonambig), cdr2.buf,
+ strerror(errno));
+ break;
+ case CFO_INSTALL | CFOF_BACKUP:
+ strcpy(cdr2rest, DPKGDISTEXT);
+ if (unlink(cdr2.buf) && errno != ENOENT)
+ warning(_("%s: failed to remove old distributed version '%.250s': %s"),
+ pkg_name(pkg, pnaw_nonambig), cdr2.buf,
+ strerror(errno));
+ strcpy(cdr2rest, DPKGOLDEXT);
+ if (unlink(cdr2.buf) && errno != ENOENT)
+ warning(_("%s: failed to remove '%.250s' (before overwrite): %s"),
+ pkg_name(pkg, pnaw_nonambig), cdr2.buf,
+ strerror(errno));
+ if (!(what & CFOF_USER_DEL))
+ if (link(cdr.buf, cdr2.buf))
+ warning(_("%s: failed to link '%.250s' to '%.250s': %s"),
+ pkg_name(pkg, pnaw_nonambig), cdr.buf,
+ cdr2.buf, strerror(errno));
+ /* Fall through. */
+ case CFO_INSTALL:
+ printf(_("Installing new version of config file %s ...\n"),
+ usenode->name);
+ /* Fall through. */
+ case CFO_NEW_CONFF:
+ strcpy(cdr2rest, DPKGNEWEXT);
+ trig_path_activate(usenode, pkg);
+ if (rename(cdr2.buf, cdr.buf))
+ ohshite(_("unable to install '%.250s' as '%.250s'"),
+ cdr2.buf, cdr.buf);
+ break;
+ default:
+ internerr("unknown conffopt '%d'", what);
+ }
+
+ conff->hash = nfstrsave(newdisthash);
+ modstatdb_note(pkg);
+
+ varbuf_destroy(&cdr);
+ varbuf_destroy(&cdr2);
+}
+
+/**
+ * Process the deferred configure package.
+ *
+ * @param pkg The package to act on.
+ */
+void
+deferred_configure(struct pkginfo *pkg)
+{
+ struct varbuf aemsgs = VARBUF_INIT;
+ struct conffile *conff;
+ struct pkginfo *otherpkg;
+ enum dep_check ok;
+
+ if (pkg->status == PKG_STAT_NOTINSTALLED)
+ ohshit(_("no package named '%s' is installed, cannot configure"),
+ pkg_name(pkg, pnaw_nonambig));
+ if (pkg->status == PKG_STAT_INSTALLED)
+ ohshit(_("package %.250s is already installed and configured"),
+ pkg_name(pkg, pnaw_nonambig));
+ if (pkg->status != PKG_STAT_UNPACKED &&
+ pkg->status != PKG_STAT_HALFCONFIGURED)
+ ohshit(_("package %.250s is not ready for configuration\n"
+ " cannot configure (current status '%.250s')"),
+ pkg_name(pkg, pnaw_nonambig),
+ pkg_status_name(pkg));
+
+ for (otherpkg = &pkg->set->pkg; otherpkg; otherpkg = otherpkg->arch_next) {
+ if (otherpkg == pkg)
+ continue;
+ if (otherpkg->status <= PKG_STAT_CONFIGFILES)
+ continue;
+
+ if (otherpkg->status < PKG_STAT_UNPACKED)
+ ohshit(_("package %s cannot be configured because "
+ "%s is not ready (current status '%s')"),
+ pkg_name(pkg, pnaw_always),
+ pkg_name(otherpkg, pnaw_always),
+ pkg_status_name(otherpkg));
+
+ if (dpkg_version_compare(&pkg->installed.version,
+ &otherpkg->installed.version))
+ ohshit(_("package %s %s cannot be configured because "
+ "%s is at a different version (%s)"),
+ pkg_name(pkg, pnaw_always),
+ versiondescribe(&pkg->installed.version,
+ vdew_nonambig),
+ pkg_name(otherpkg, pnaw_always),
+ versiondescribe(&otherpkg->installed.version,
+ vdew_nonambig));
+ }
+
+ if (dependtry >= DEPEND_TRY_CYCLES)
+ if (findbreakcycle(pkg))
+ sincenothing = 0;
+
+ ok = dependencies_ok(pkg, NULL, &aemsgs);
+ if (ok == DEP_CHECK_DEFER) {
+ varbuf_destroy(&aemsgs);
+ ensure_package_clientdata(pkg);
+ pkg->clientdata->istobe = PKG_ISTOBE_INSTALLNEW;
+ enqueue_package(pkg);
+ return;
+ }
+
+ trigproc_reset_cycle();
+
+ /*
+ * At this point removal from the queue is confirmed. This
+ * represents irreversible progress wrt trigger cycles. Only
+ * packages in PKG_STAT_UNPACKED are automatically added to the
+ * configuration queue, and during configuration and trigger
+ * processing new packages can't enter into unpacked.
+ */
+
+ ok = breakses_ok(pkg, &aemsgs) ? ok : DEP_CHECK_HALT;
+ if (ok == DEP_CHECK_HALT) {
+ sincenothing = 0;
+ varbuf_end_str(&aemsgs);
+ notice(_("dependency problems prevent configuration of %s:\n%s"),
+ pkg_name(pkg, pnaw_nonambig), aemsgs.buf);
+ varbuf_destroy(&aemsgs);
+ ohshit(_("dependency problems - leaving unconfigured"));
+ } else if (aemsgs.used) {
+ varbuf_end_str(&aemsgs);
+ notice(_("%s: dependency problems, but configuring anyway as you requested:\n%s"),
+ pkg_name(pkg, pnaw_nonambig), aemsgs.buf);
+ }
+ varbuf_destroy(&aemsgs);
+ sincenothing = 0;
+
+ if (pkg->eflag & PKG_EFLAG_REINSTREQ)
+ forcibleerr(FORCE_REMOVE_REINSTREQ,
+ _("package is in a very bad inconsistent state; you should\n"
+ " reinstall it before attempting configuration"));
+
+ printf(_("Setting up %s (%s) ...\n"), pkg_name(pkg, pnaw_nonambig),
+ versiondescribe(&pkg->installed.version, vdew_nonambig));
+ log_action("configure", pkg, &pkg->installed);
+
+ trig_activate_packageprocessing(pkg);
+
+ if (f_noact) {
+ pkg_set_status(pkg, PKG_STAT_INSTALLED);
+ ensure_package_clientdata(pkg);
+ pkg->clientdata->istobe = PKG_ISTOBE_NORMAL;
+ return;
+ }
+
+ if (pkg->status == PKG_STAT_UNPACKED) {
+ debug(dbg_general, "deferred_configure updating conffiles");
+ /* This will not do at all the right thing with overridden
+ * conffiles or conffiles that are the ‘target’ of an override;
+ * all the references here would be to the ‘contested’
+ * filename, and in any case there'd only be one hash for both
+ * ‘versions’ of the conffile.
+ *
+ * Overriding conffiles is a silly thing to do anyway :-). */
+
+ modstatdb_note(pkg);
+
+ /* On entry, the ‘new’ version of each conffile has been
+ * unpacked as ‘*.dpkg-new’, and the ‘installed’ version is
+ * as-yet untouched in ‘*’. The hash of the ‘old distributed’
+ * version is in the conffiles data for the package. If
+ * ‘*.dpkg-new’ no longer exists we assume that we've
+ * already processed this one. */
+ for (conff = pkg->installed.conffiles; conff; conff = conff->next) {
+ if (conff->obsolete)
+ continue;
+ deferred_configure_conffile(pkg, conff);
+ }
+
+ pkg_set_status(pkg, PKG_STAT_HALFCONFIGURED);
+ }
+
+ if (pkg->status != PKG_STAT_HALFCONFIGURED)
+ internerr("package %s in state %s, instead of half-configured",
+ pkg_name(pkg, pnaw_always), pkg_status_name(pkg));
+
+ modstatdb_note(pkg);
+
+ maintscript_postinst(pkg, "configure",
+ dpkg_version_is_informative(&pkg->configversion) ?
+ versiondescribe(&pkg->configversion,
+ vdew_nonambig) : "",
+ NULL);
+
+ pkg_reset_eflags(pkg);
+ pkg->trigpend_head = NULL;
+ post_postinst_tasks(pkg, PKG_STAT_INSTALLED);
+}
+
+/**
+ * Dereference a file by following all possibly used symlinks.
+ *
+ * @param[in] pkg The package to act on.
+ * @param[out] result The dereference conffile path.
+ * @param[in] in The conffile path to dereference.
+ *
+ * @return An error code for the operation.
+ * @retval 0 Everything went ok.
+ * @retval -1 Otherwise.
+ */
+int
+conffderef(struct pkginfo *pkg, struct varbuf *result, const char *in)
+{
+ static struct varbuf target = VARBUF_INIT;
+ struct stat stab;
+ ssize_t r;
+ int loopprotect;
+
+ varbuf_reset(result);
+ varbuf_add_str(result, instdir);
+ varbuf_add_str(result, in);
+ varbuf_end_str(result);
+
+ loopprotect = 0;
+
+ for (;;) {
+ debug(dbg_conffdetail, "conffderef in='%s' current working='%s'",
+ in, result->buf);
+ if (lstat(result->buf, &stab)) {
+ if (errno != ENOENT)
+ warning(_("%s: unable to stat config file '%s'\n"
+ " (= '%s'): %s"),
+ pkg_name(pkg, pnaw_nonambig), in,
+ result->buf, strerror(errno));
+ debug(dbg_conffdetail, "conffderef nonexistent");
+ return 0;
+ } else if (S_ISREG(stab.st_mode)) {
+ debug(dbg_conff, "conffderef in='%s' result='%s'",
+ in, result->buf);
+ return 0;
+ } else if (S_ISLNK(stab.st_mode)) {
+ debug(dbg_conffdetail, "conffderef symlink loopprotect=%d",
+ loopprotect);
+ if (loopprotect++ >= 25) {
+ warning(_("%s: config file '%s' is a circular link\n"
+ " (= '%s')"),
+ pkg_name(pkg, pnaw_nonambig), in,
+ result->buf);
+ return -1;
+ }
+
+ varbuf_reset(&target);
+ varbuf_grow(&target, stab.st_size + 1);
+ r = readlink(result->buf, target.buf, target.size);
+ if (r < 0) {
+ warning(_("%s: unable to readlink conffile '%s'\n"
+ " (= '%s'): %s"),
+ pkg_name(pkg, pnaw_nonambig), in,
+ result->buf, strerror(errno));
+ return -1;
+ } else if (r != stab.st_size) {
+ warning(_("symbolic link '%.250s' size has "
+ "changed from %jd to %zd"),
+ result->buf, (intmax_t)stab.st_size, r);
+ /* If the returned size is smaller, let's
+ * proceed, otherwise error out. */
+ if (r > stab.st_size)
+ return -1;
+ }
+ varbuf_trunc(&target, r);
+ varbuf_end_str(&target);
+
+ debug(dbg_conffdetail,
+ "conffderef readlink gave %zd, '%s'",
+ r, target.buf);
+
+ if (target.buf[0] == '/') {
+ varbuf_reset(result);
+ varbuf_add_str(result, instdir);
+ debug(dbg_conffdetail,
+ "conffderef readlink absolute");
+ } else {
+ for (r = result->used - 1; r > 0 && result->buf[r] != '/'; r--)
+ ;
+ if (r < 0) {
+ warning(_("%s: conffile '%.250s' resolves to degenerate filename\n"
+ " ('%s' is a symlink to '%s')"),
+ pkg_name(pkg, pnaw_nonambig),
+ in, result->buf, target.buf);
+ return -1;
+ }
+ if (result->buf[r] == '/')
+ r++;
+ varbuf_trunc(result, r);
+ debug(dbg_conffdetail,
+ "conffderef readlink relative to '%.*s'",
+ (int)result->used, result->buf);
+ }
+ varbuf_add_buf(result, target.buf, target.used);
+ varbuf_end_str(result);
+ } else {
+ warning(_("%s: conffile '%.250s' is not a plain file or symlink (= '%s')"),
+ pkg_name(pkg, pnaw_nonambig), in, result->buf);
+ return -1;
+ }
+ }
+}
+
+/**
+ * Generate a file contents MD5 hash.
+ *
+ * The caller is responsible for providing a buffer for the hash result
+ * at least MD5HASHLEN + 1 characters long.
+ *
+ * @param[in] pkg The package to act on.
+ * @param[out] hashbuf The buffer to store the generated hash.
+ * @param[in] fn The filename.
+ */
+void
+md5hash(struct pkginfo *pkg, char *hashbuf, const char *fn)
+{
+ struct dpkg_error err;
+ static int fd;
+
+ fd = open(fn, O_RDONLY);
+
+ if (fd >= 0) {
+ push_cleanup(cu_closefd, ehflag_bombout, 1, &fd);
+ if (fd_md5(fd, hashbuf, -1, &err) < 0)
+ ohshit(_("cannot compute MD5 hash for file '%s': %s"),
+ fn, err.str);
+ pop_cleanup(ehflag_normaltidy); /* fd = open(cdr.buf) */
+ close(fd);
+ } else if (errno == ENOENT) {
+ strcpy(hashbuf, NONEXISTENTFLAG);
+ } else {
+ warning(_("%s: unable to open %s for hash: %s"),
+ pkg_name(pkg, pnaw_nonambig), fn, strerror(errno));
+ strcpy(hashbuf, EMPTYHASHFLAG);
+ }
+}
diff --git a/src/depcon.c b/src/depcon.c
new file mode 100644
index 0000000..593edfb
--- /dev/null
+++ b/src/depcon.c
@@ -0,0 +1,699 @@
+/*
+ * dpkg - main program for package management
+ * depcon.c - dependency and conflict checking
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2006-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/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+
+#include "main.h"
+
+struct deppossi_pkg_iterator {
+ struct deppossi *possi;
+ struct pkginfo *pkg_next;
+ enum which_pkgbin which_pkgbin;
+};
+
+struct deppossi_pkg_iterator *
+deppossi_pkg_iter_new(struct deppossi *possi, enum which_pkgbin wpb)
+{
+ struct deppossi_pkg_iterator *iter;
+
+ iter = m_malloc(sizeof(*iter));
+ iter->possi = possi;
+ iter->pkg_next = &possi->ed->pkg;
+ iter->which_pkgbin = wpb;
+
+ return iter;
+}
+
+struct pkginfo *
+deppossi_pkg_iter_next(struct deppossi_pkg_iterator *iter)
+{
+ struct pkginfo *pkg_cur;
+ struct pkgbin *pkgbin;
+
+ while ((pkg_cur = iter->pkg_next)) {
+ iter->pkg_next = pkg_cur->arch_next;
+
+ switch (iter->which_pkgbin) {
+ case wpb_installed:
+ pkgbin = &pkg_cur->installed;
+ break;
+ case wpb_available:
+ pkgbin = &pkg_cur->available;
+ break;
+ case wpb_by_istobe:
+ if (pkg_cur->clientdata &&
+ pkg_cur->clientdata->istobe == PKG_ISTOBE_INSTALLNEW)
+ pkgbin = &pkg_cur->available;
+ else
+ pkgbin = &pkg_cur->installed;
+ break;
+ default:
+ internerr("unknown which_pkgbin %d", iter->which_pkgbin);
+ }
+
+ if (archsatisfied(pkgbin, iter->possi))
+ return pkg_cur;
+ }
+
+ return NULL;
+}
+
+void
+deppossi_pkg_iter_free(struct deppossi_pkg_iterator *iter)
+{
+ free(iter);
+}
+
+struct cyclesofarlink {
+ struct cyclesofarlink *prev;
+ struct pkginfo *pkg;
+ struct deppossi *possi;
+};
+
+static bool findbreakcyclerecursive(struct pkginfo *pkg,
+ struct cyclesofarlink *sofar);
+
+static bool
+foundcyclebroken(struct cyclesofarlink *thislink, struct cyclesofarlink *sofar,
+ struct pkginfo *dependedon, struct deppossi *possi)
+{
+ struct cyclesofarlink *sol;
+
+ if(!possi)
+ return false;
+
+ /* We're investigating the dependency ‘possi’ to see if it
+ * is part of a loop. To this end we look to see whether the
+ * depended-on package is already one of the packages whose
+ * dependencies we're searching. */
+ for (sol = sofar; sol && sol->pkg != dependedon; sol = sol->prev);
+
+ /* If not, we do a recursive search on it to see what we find. */
+ if (!sol)
+ return findbreakcyclerecursive(dependedon, thislink);
+
+ debug(dbg_depcon,"found cycle");
+ /* Right, we now break one of the links. We prefer to break
+ * a dependency of a package without a postinst script, as
+ * this is a null operation. If this is not possible we break
+ * the other link in the recursive calling tree which mentions
+ * this package (this being the first package involved in the
+ * cycle). It doesn't particularly matter which we pick, but if
+ * we break the earliest dependency we came across we may be
+ * able to do something straight away when findbreakcycle returns. */
+ sofar= thislink;
+ for (sol = sofar; !(sol != sofar && sol->pkg == dependedon); sol = sol->prev) {
+ if (!pkg_infodb_has_file(sol->pkg, &sol->pkg->installed, POSTINSTFILE))
+ break;
+ }
+
+ /* Now we have either a package with no postinst, or the other
+ * occurrence of the current package in the list. */
+ sol->possi->cyclebreak = true;
+
+ debug(dbg_depcon, "cycle broken at %s -> %s",
+ pkg_name(sol->possi->up->up, pnaw_always), sol->possi->ed->name);
+
+ return true;
+}
+
+/**
+ * Cycle breaking works recursively down the package dependency tree.
+ *
+ * ‘sofar’ is the list of packages we've descended down already - if we
+ * encounter any of its packages again in a dependency we have found a cycle.
+ */
+static bool
+findbreakcyclerecursive(struct pkginfo *pkg, struct cyclesofarlink *sofar)
+{
+ struct cyclesofarlink thislink, *sol;
+ struct dependency *dep;
+ struct deppossi *possi, *providelink;
+ struct pkginfo *provider, *pkg_pos;
+
+ if (pkg->clientdata->color == PKG_CYCLE_BLACK)
+ return false;
+ pkg->clientdata->color = PKG_CYCLE_GRAY;
+
+ if (debug_has_flag(dbg_depcondetail)) {
+ struct varbuf str_pkgs = VARBUF_INIT;
+
+ for (sol = sofar; sol; sol = sol->prev) {
+ varbuf_add_str(&str_pkgs, " <- ");
+ varbuf_add_pkgbin_name(&str_pkgs, sol->pkg, &sol->pkg->installed, pnaw_nonambig);
+ }
+ varbuf_end_str(&str_pkgs);
+ debug(dbg_depcondetail, "findbreakcyclerecursive %s %s",
+ pkg_name(pkg, pnaw_always), str_pkgs.buf);
+ varbuf_destroy(&str_pkgs);
+ }
+ thislink.pkg= pkg;
+ thislink.prev = sofar;
+ thislink.possi = NULL;
+ for (dep= pkg->installed.depends; dep; dep= dep->next) {
+ if (dep->type != dep_depends && dep->type != dep_predepends) continue;
+ for (possi= dep->list; possi; possi= possi->next) {
+ struct deppossi_pkg_iterator *possi_iter;
+
+ /* Don't find the same cycles again. */
+ if (possi->cyclebreak) continue;
+ thislink.possi= possi;
+
+ possi_iter = deppossi_pkg_iter_new(possi, wpb_installed);
+ while ((pkg_pos = deppossi_pkg_iter_next(possi_iter)))
+ if (foundcyclebroken(&thislink, sofar, pkg_pos, possi)) {
+ deppossi_pkg_iter_free(possi_iter);
+ return true;
+ }
+ deppossi_pkg_iter_free(possi_iter);
+
+ /* Right, now we try all the providers ... */
+ for (providelink = possi->ed->depended.installed;
+ providelink;
+ providelink = providelink->rev_next) {
+ if (providelink->up->type != dep_provides) continue;
+ provider= providelink->up->up;
+ if (provider->clientdata->istobe == PKG_ISTOBE_NORMAL)
+ continue;
+ /* We don't break things at ‘provides’ links, so ‘possi’ is
+ * still the one we use. */
+ if (foundcyclebroken(&thislink, sofar, provider, possi))
+ return true;
+ }
+ }
+ }
+ /* Nope, we didn't find a cycle to break. */
+ pkg->clientdata->color = PKG_CYCLE_BLACK;
+ return false;
+}
+
+bool
+findbreakcycle(struct pkginfo *pkg)
+{
+ struct pkg_hash_iter *iter;
+ struct pkginfo *tpkg;
+
+ /* Clear the visited flag of all packages before we traverse them. */
+ iter = pkg_hash_iter_new();
+ while ((tpkg = pkg_hash_iter_next_pkg(iter))) {
+ ensure_package_clientdata(tpkg);
+ tpkg->clientdata->color = PKG_CYCLE_WHITE;
+ }
+ pkg_hash_iter_free(iter);
+
+ return findbreakcyclerecursive(pkg, NULL);
+}
+
+void describedepcon(struct varbuf *addto, struct dependency *dep) {
+ const char *fmt;
+ struct varbuf depstr = VARBUF_INIT;
+
+ switch (dep->type) {
+ case dep_depends:
+ fmt = _("%s depends on %s");
+ break;
+ case dep_predepends:
+ fmt = _("%s pre-depends on %s");
+ break;
+ case dep_recommends:
+ fmt = _("%s recommends %s");
+ break;
+ case dep_suggests:
+ fmt = _("%s suggests %s");
+ break;
+ case dep_breaks:
+ fmt = _("%s breaks %s");
+ break;
+ case dep_conflicts:
+ fmt = _("%s conflicts with %s");
+ break;
+ case dep_enhances:
+ fmt = _("%s enhances %s");
+ break;
+ default:
+ internerr("unknown deptype '%d'", dep->type);
+ }
+
+ varbufdependency(&depstr, dep);
+ varbuf_end_str(&depstr);
+
+ varbuf_printf(addto, fmt, pkg_name(dep->up, pnaw_nonambig), depstr.buf);
+ varbuf_destroy(&depstr);
+}
+
+/*
+ * *whynot must already have been initialized; it need not be
+ * empty though - it will be reset before use.
+ *
+ * If depisok returns false for ‘not OK’ it will contain a description,
+ * newline-terminated BUT NOT NUL-TERMINATED, of the reason.
+ *
+ * If depisok returns true it will contain garbage.
+ * allowunconfigd should be non-zero during the ‘Pre-Depends’ checking
+ * before a package is unpacked, when it is sufficient for the package
+ * to be unpacked provided that both the unpacked and previously-configured
+ * versions are acceptable.
+ *
+ * On false return (‘not OK’), *canfixbyremove refers to a package which
+ * if removed (dep_conflicts) or deconfigured (dep_breaks) will fix
+ * the problem. Caller may pass NULL for canfixbyremove and need not
+ * initialize *canfixbyremove.
+ *
+ * On false return (‘not OK’), *canfixbytrigaw refers to a package which
+ * can fix the problem if all the packages listed in Triggers-Awaited have
+ * their triggers processed. Caller may pass NULL for canfixbytrigaw and
+ * need not initialize *canfixbytrigaw.
+ */
+bool
+depisok(struct dependency *dep, struct varbuf *whynot,
+ struct pkginfo **canfixbyremove, struct pkginfo **canfixbytrigaw,
+ bool allowunconfigd)
+{
+ struct deppossi *possi;
+ struct deppossi *provider;
+ struct pkginfo *pkg_pos;
+ int nconflicts;
+
+ /* Use this buffer so that when internationalisation comes along we
+ * don't have to rewrite the code completely, only redo the sprintf strings
+ * (assuming we have the fancy argument-number-specifiers).
+ * Allow 250x3 for package names, versions, &c, + 250 for ourselves. */
+ char linebuf[1024];
+
+ if (dep->type != dep_depends &&
+ dep->type != dep_predepends &&
+ dep->type != dep_breaks &&
+ dep->type != dep_conflicts &&
+ dep->type != dep_recommends &&
+ dep->type != dep_suggests &&
+ dep->type != dep_enhances)
+ internerr("unknown dependency type %d", dep->type);
+
+ if (canfixbyremove)
+ *canfixbyremove = NULL;
+ if (canfixbytrigaw)
+ *canfixbytrigaw = NULL;
+
+ /* The dependency is always OK if we're trying to remove the depend*ing*
+ * package. */
+ switch (dep->up->clientdata->istobe) {
+ case PKG_ISTOBE_REMOVE:
+ case PKG_ISTOBE_DECONFIGURE:
+ return true;
+ case PKG_ISTOBE_NORMAL:
+ /* Only installed packages can be made dependency problems. */
+ switch (dep->up->status) {
+ case PKG_STAT_INSTALLED:
+ case PKG_STAT_TRIGGERSPENDING:
+ case PKG_STAT_TRIGGERSAWAITED:
+ break;
+ case PKG_STAT_HALFCONFIGURED:
+ case PKG_STAT_UNPACKED:
+ case PKG_STAT_HALFINSTALLED:
+ if (dep->type == dep_predepends ||
+ dep->type == dep_conflicts ||
+ dep->type == dep_breaks)
+ break;
+ /* Fall through. */
+ case PKG_STAT_CONFIGFILES:
+ case PKG_STAT_NOTINSTALLED:
+ return true;
+ default:
+ internerr("unknown status depending '%d'", dep->up->status);
+ }
+ break;
+ case PKG_ISTOBE_INSTALLNEW:
+ case PKG_ISTOBE_PREINSTALL:
+ break;
+ default:
+ internerr("unknown istobe depending '%d'", dep->up->clientdata->istobe);
+ }
+
+ /* Describe the dependency, in case we have to moan about it. */
+ varbuf_reset(whynot);
+ varbuf_add_char(whynot, ' ');
+ describedepcon(whynot, dep);
+ varbuf_add_char(whynot, '\n');
+
+ /* TODO: Check dep_enhances as well. */
+ if (dep->type == dep_depends || dep->type == dep_predepends ||
+ dep->type == dep_recommends || dep->type == dep_suggests ) {
+ /* Go through the alternatives. As soon as we find one that
+ * we like, we return ‘true’ straight away. Otherwise, when we get to
+ * the end we'll have accumulated all the reasons in whynot and
+ * can return ‘false’. */
+
+ for (possi= dep->list; possi; possi= possi->next) {
+ struct deppossi_pkg_iterator *possi_iter;
+
+ possi_iter = deppossi_pkg_iter_new(possi, wpb_by_istobe);
+ while ((pkg_pos = deppossi_pkg_iter_next(possi_iter))) {
+ switch (pkg_pos->clientdata->istobe) {
+ case PKG_ISTOBE_REMOVE:
+ sprintf(linebuf, _(" %.250s is to be removed.\n"),
+ pkg_name(pkg_pos, pnaw_nonambig));
+ break;
+ case PKG_ISTOBE_DECONFIGURE:
+ sprintf(linebuf, _(" %.250s is to be deconfigured.\n"),
+ pkg_name(pkg_pos, pnaw_nonambig));
+ break;
+ case PKG_ISTOBE_INSTALLNEW:
+ if (versionsatisfied(&pkg_pos->available, possi)) {
+ deppossi_pkg_iter_free(possi_iter);
+ return true;
+ }
+ sprintf(linebuf, _(" %.250s is to be installed, but is version "
+ "%.250s.\n"),
+ pkgbin_name(pkg_pos, &pkg_pos->available, pnaw_nonambig),
+ versiondescribe(&pkg_pos->available.version, vdew_nonambig));
+ break;
+ case PKG_ISTOBE_NORMAL:
+ case PKG_ISTOBE_PREINSTALL:
+ switch (pkg_pos->status) {
+ case PKG_STAT_INSTALLED:
+ case PKG_STAT_TRIGGERSPENDING:
+ if (versionsatisfied(&pkg_pos->installed, possi)) {
+ deppossi_pkg_iter_free(possi_iter);
+ return true;
+ }
+ sprintf(linebuf, _(" %.250s is installed, but is version "
+ "%.250s.\n"),
+ pkg_name(pkg_pos, pnaw_nonambig),
+ versiondescribe(&pkg_pos->installed.version, vdew_nonambig));
+ break;
+ case PKG_STAT_NOTINSTALLED:
+ /* Don't say anything about this yet - it might be a virtual package.
+ * Later on, if nothing has put anything in linebuf, we know that it
+ * isn't and issue a diagnostic then. */
+ *linebuf = '\0';
+ break;
+ case PKG_STAT_TRIGGERSAWAITED:
+ if (canfixbytrigaw && versionsatisfied(&pkg_pos->installed, possi))
+ *canfixbytrigaw = pkg_pos;
+ /* Fall through. */
+ case PKG_STAT_UNPACKED:
+ case PKG_STAT_HALFCONFIGURED:
+ if (allowunconfigd) {
+ if (!dpkg_version_is_informative(&pkg_pos->configversion)) {
+ sprintf(linebuf, _(" %.250s is unpacked, but has never been "
+ "configured.\n"),
+ pkg_name(pkg_pos, pnaw_nonambig));
+ break;
+ } else if (!versionsatisfied(&pkg_pos->installed, possi)) {
+ sprintf(linebuf, _(" %.250s is unpacked, but is version "
+ "%.250s.\n"),
+ pkg_name(pkg_pos, pnaw_nonambig),
+ versiondescribe(&pkg_pos->installed.version,
+ vdew_nonambig));
+ break;
+ } else if (!dpkg_version_relate(&pkg_pos->configversion,
+ possi->verrel,
+ &possi->version)) {
+ sprintf(linebuf, _(" %.250s latest configured version is "
+ "%.250s.\n"),
+ pkg_name(pkg_pos, pnaw_nonambig),
+ versiondescribe(&pkg_pos->configversion, vdew_nonambig));
+ break;
+ } else {
+ deppossi_pkg_iter_free(possi_iter);
+ return true;
+ }
+ }
+ /* Fall through. */
+ default:
+ sprintf(linebuf, _(" %.250s is %s.\n"),
+ pkg_name(pkg_pos, pnaw_nonambig),
+ gettext(statusstrings[pkg_pos->status]));
+ break;
+ }
+ break;
+ default:
+ internerr("unknown istobe depended '%d'", pkg_pos->clientdata->istobe);
+ }
+ varbuf_add_str(whynot, linebuf);
+ }
+ deppossi_pkg_iter_free(possi_iter);
+
+ /* See if the package we're about to install Provides it. */
+ for (provider = possi->ed->depended.available;
+ provider;
+ provider = provider->rev_next) {
+ if (provider->up->type != dep_provides) continue;
+ if (!pkg_virtual_deppossi_satisfied(possi, provider))
+ continue;
+ if (provider->up->up->clientdata->istobe == PKG_ISTOBE_INSTALLNEW)
+ return true;
+ }
+
+ /* Now look at the packages already on the system. */
+ for (provider = possi->ed->depended.installed;
+ provider;
+ provider = provider->rev_next) {
+ if (provider->up->type != dep_provides) continue;
+ if (!pkg_virtual_deppossi_satisfied(possi, provider))
+ continue;
+
+ switch (provider->up->up->clientdata->istobe) {
+ case PKG_ISTOBE_INSTALLNEW:
+ /* Don't pay any attention to the Provides field of the
+ * currently-installed version of the package we're trying
+ * to install. We dealt with that by using the available
+ * information above. */
+ continue;
+ case PKG_ISTOBE_REMOVE:
+ sprintf(linebuf, _(" %.250s provides %.250s but is to be removed.\n"),
+ pkg_name(provider->up->up, pnaw_nonambig),
+ possi->ed->name);
+ break;
+ case PKG_ISTOBE_DECONFIGURE:
+ sprintf(linebuf, _(" %.250s provides %.250s but is to be deconfigured.\n"),
+ pkg_name(provider->up->up, pnaw_nonambig),
+ possi->ed->name);
+ break;
+ case PKG_ISTOBE_NORMAL:
+ case PKG_ISTOBE_PREINSTALL:
+ if (provider->up->up->status == PKG_STAT_INSTALLED ||
+ provider->up->up->status == PKG_STAT_TRIGGERSPENDING)
+ return true;
+ if (provider->up->up->status == PKG_STAT_TRIGGERSAWAITED)
+ *canfixbytrigaw = provider->up->up;
+ sprintf(linebuf, _(" %.250s provides %.250s but is %s.\n"),
+ pkg_name(provider->up->up, pnaw_nonambig),
+ possi->ed->name,
+ gettext(statusstrings[provider->up->up->status]));
+ break;
+ default:
+ internerr("unknown istobe provider '%d'",
+ provider->up->up->clientdata->istobe);
+ }
+ varbuf_add_str(whynot, linebuf);
+ }
+
+ if (!*linebuf) {
+ /* If the package wasn't installed at all, and we haven't said
+ * yet why this isn't satisfied, we should say so now. */
+ sprintf(linebuf, _(" %.250s is not installed.\n"), possi->ed->name);
+ varbuf_add_str(whynot, linebuf);
+ }
+ }
+
+ return false;
+ } else {
+ /* It's conflicts or breaks. There's only one main alternative,
+ * but we also have to consider Providers. We return ‘false’ as soon
+ * as we find something that matches the conflict, and only describe
+ * it then. If we get to the end without finding anything we return
+ * ‘true’. */
+
+ possi= dep->list;
+ nconflicts= 0;
+
+ if (possi->ed != possi->up->up->set) {
+ struct deppossi_pkg_iterator *possi_iter;
+
+ /* If the package conflicts with or breaks itself it must mean
+ * other packages which provide the same virtual name. We
+ * therefore don't look at the real package and go on to the
+ * virtual ones. */
+
+ possi_iter = deppossi_pkg_iter_new(possi, wpb_by_istobe);
+ while ((pkg_pos = deppossi_pkg_iter_next(possi_iter))) {
+ switch (pkg_pos->clientdata->istobe) {
+ case PKG_ISTOBE_REMOVE:
+ break;
+ case PKG_ISTOBE_INSTALLNEW:
+ if (!versionsatisfied(&pkg_pos->available, possi))
+ break;
+ sprintf(linebuf, _(" %.250s (version %.250s) is to be installed.\n"),
+ pkgbin_name(pkg_pos, &pkg_pos->available, pnaw_nonambig),
+ versiondescribe(&pkg_pos->available.version, vdew_nonambig));
+ varbuf_add_str(whynot, linebuf);
+ if (!canfixbyremove) {
+ deppossi_pkg_iter_free(possi_iter);
+ return false;
+ }
+ nconflicts++;
+ *canfixbyremove = pkg_pos;
+ break;
+ case PKG_ISTOBE_DECONFIGURE:
+ if (dep->type == dep_breaks)
+ break; /* Already deconfiguring this. */
+ /* Fall through. */
+ case PKG_ISTOBE_NORMAL:
+ case PKG_ISTOBE_PREINSTALL:
+ switch (pkg_pos->status) {
+ case PKG_STAT_NOTINSTALLED:
+ case PKG_STAT_CONFIGFILES:
+ break;
+ case PKG_STAT_HALFINSTALLED:
+ case PKG_STAT_UNPACKED:
+ case PKG_STAT_HALFCONFIGURED:
+ if (dep->type == dep_breaks)
+ break; /* No problem. */
+ /* Fall through. */
+ case PKG_STAT_INSTALLED:
+ case PKG_STAT_TRIGGERSPENDING:
+ case PKG_STAT_TRIGGERSAWAITED:
+ if (!versionsatisfied(&pkg_pos->installed, possi))
+ break;
+ sprintf(linebuf, _(" %.250s (version %.250s) is present and %s.\n"),
+ pkg_name(pkg_pos, pnaw_nonambig),
+ versiondescribe(&pkg_pos->installed.version, vdew_nonambig),
+ gettext(statusstrings[pkg_pos->status]));
+ varbuf_add_str(whynot, linebuf);
+ if (!canfixbyremove) {
+ deppossi_pkg_iter_free(possi_iter);
+ return false;
+ }
+ nconflicts++;
+ *canfixbyremove = pkg_pos;
+ }
+ break;
+ default:
+ internerr("unknown istobe conflict '%d'", pkg_pos->clientdata->istobe);
+ }
+ }
+ deppossi_pkg_iter_free(possi_iter);
+ }
+
+ /* See if the package we're about to install Provides it. */
+ for (provider = possi->ed->depended.available;
+ provider;
+ provider = provider->rev_next) {
+ if (provider->up->type != dep_provides) continue;
+ if (provider->up->up->clientdata->istobe != PKG_ISTOBE_INSTALLNEW)
+ continue;
+ if (provider->up->up->set == dep->up->set)
+ continue; /* Conflicts and provides the same. */
+ if (!pkg_virtual_deppossi_satisfied(possi, provider))
+ continue;
+ sprintf(linebuf, _(" %.250s provides %.250s and is to be installed.\n"),
+ pkgbin_name(provider->up->up, &provider->up->up->available,
+ pnaw_nonambig), possi->ed->name);
+ varbuf_add_str(whynot, linebuf);
+ /* We can't remove the one we're about to install: */
+ if (canfixbyremove)
+ *canfixbyremove = NULL;
+ return false;
+ }
+
+ /* Now look at the packages already on the system. */
+ for (provider = possi->ed->depended.installed;
+ provider;
+ provider = provider->rev_next) {
+ if (provider->up->type != dep_provides) continue;
+
+ if (provider->up->up->set == dep->up->set)
+ continue; /* Conflicts and provides the same. */
+
+ if (!pkg_virtual_deppossi_satisfied(possi, provider))
+ continue;
+
+ switch (provider->up->up->clientdata->istobe) {
+ case PKG_ISTOBE_INSTALLNEW:
+ /* Don't pay any attention to the Provides field of the
+ * currently-installed version of the package we're trying
+ * to install. We dealt with that package by using the
+ * available information above. */
+ continue;
+ case PKG_ISTOBE_REMOVE:
+ continue;
+ case PKG_ISTOBE_DECONFIGURE:
+ if (dep->type == dep_breaks)
+ continue; /* Already deconfiguring. */
+ /* Fall through. */
+ case PKG_ISTOBE_NORMAL:
+ case PKG_ISTOBE_PREINSTALL:
+ switch (provider->up->up->status) {
+ case PKG_STAT_NOTINSTALLED:
+ case PKG_STAT_CONFIGFILES:
+ continue;
+ case PKG_STAT_HALFINSTALLED:
+ case PKG_STAT_UNPACKED:
+ case PKG_STAT_HALFCONFIGURED:
+ if (dep->type == dep_breaks)
+ break; /* No problem. */
+ /* Fall through. */
+ case PKG_STAT_INSTALLED:
+ case PKG_STAT_TRIGGERSPENDING:
+ case PKG_STAT_TRIGGERSAWAITED:
+ sprintf(linebuf,
+ _(" %.250s provides %.250s and is present and %s.\n"),
+ pkg_name(provider->up->up, pnaw_nonambig), possi->ed->name,
+ gettext(statusstrings[provider->up->up->status]));
+ varbuf_add_str(whynot, linebuf);
+ if (!canfixbyremove)
+ return false;
+ nconflicts++;
+ *canfixbyremove= provider->up->up;
+ break;
+ }
+ break;
+ default:
+ internerr("unknown istobe conflict provider '%d'",
+ provider->up->up->clientdata->istobe);
+ }
+ }
+
+ if (!nconflicts)
+ return true;
+ if (nconflicts > 1)
+ *canfixbyremove = NULL;
+ return false;
+
+ } /* if (dependency) {...} else {...} */
+}
diff --git a/src/divertcmd.c b/src/divertcmd.c
new file mode 100644
index 0000000..61b382f
--- /dev/null
+++ b/src/divertcmd.c
@@ -0,0 +1,860 @@
+/*
+ * dpkg-divert - override a package's version of a file
+ *
+ * Copyright © 1995 Ian Jackson
+ * Copyright © 2000, 2001 Wichert Akkerman
+ * Copyright © 2006-2015, 2017-2018 Guillem Jover <guillem@debian.org>
+ * Copyright © 2011 Linaro Limited
+ * Copyright © 2011 Raphaël Hertzog <hertzog@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/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#if HAVE_LOCALE_H
+#include <locale.h>
+#endif
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/arch.h>
+#include <dpkg/file.h>
+#include <dpkg/glob.h>
+#include <dpkg/buffer.h>
+#include <dpkg/options.h>
+#include <dpkg/db-fsys.h>
+
+
+static const char printforhelp[] = N_(
+"Use --help for help about diverting files.");
+
+static const char *admindir;
+const char *instdir;
+
+static bool opt_pkgname_match_any = true;
+static const char *opt_pkgname = NULL;
+static const char *opt_divertto = NULL;
+
+static int opt_verbose = 1;
+static int opt_test = 0;
+static int opt_rename = -1;
+
+
+static void
+printversion(const struct cmdinfo *cip, const char *value)
+{
+ printf(_("Debian %s version %s.\n"), dpkg_get_progname(),
+ PACKAGE_RELEASE);
+
+ printf(_(
+"This is free software; see the GNU General Public License version 2 or\n"
+"later for copying conditions. There is NO warranty.\n"));
+
+ m_output(stdout, _("<standard output>"));
+
+ exit(0);
+}
+
+static void
+usage(const struct cmdinfo *cip, const char *value)
+{
+ printf(_(
+"Usage: %s [<option> ...] <command>\n"
+"\n"), dpkg_get_progname());
+
+ printf(_(
+"Commands:\n"
+" [--add] <file> add a diversion.\n"
+" --remove <file> remove the diversion.\n"
+" --list [<glob-pattern>] show file diversions.\n"
+" --listpackage <file> show what package diverts the file.\n"
+" --truename <file> return the diverted file.\n"
+"\n"));
+
+ printf(_(
+"Options:\n"
+" --package <package> name of the package whose copy of <file> will not\n"
+" be diverted.\n"
+" --local all packages' versions are diverted.\n"
+" --divert <divert-to> the name used by other packages' versions.\n"
+" --rename actually move the file aside (or back).\n"
+" --no-rename do not move the file aside (or back) (default).\n"
+" --admindir <directory> set the directory with the diversions file.\n"
+" --instdir <directory> set the root directory, but not the admin dir.\n"
+" --root <directory> set the directory of the root filesystem.\n"
+" --test don't do anything, just demonstrate.\n"
+" --quiet quiet operation, minimal output.\n"
+" --help show this help message.\n"
+" --version show the version.\n"
+"\n"));
+
+ printf(_(
+"When adding, default is --local and --divert <original>.distrib.\n"
+"When removing, --package or --local and --divert must match if specified.\n"
+"Package preinst/postrm scripts should always specify --package and --divert.\n"));
+
+ m_output(stdout, _("<standard output>"));
+
+ exit(0);
+}
+
+static void
+opt_rename_setup(void)
+{
+ if (opt_rename >= 0)
+ return;
+
+ opt_rename = 0;
+ warning(_("please specify --no-rename explicitly, the default "
+ "will change to --rename in 1.20.x"));
+}
+
+struct file {
+ const char *name;
+ enum {
+ FILE_STAT_INVALID,
+ FILE_STAT_VALID,
+ FILE_STAT_NOFILE,
+ } stat_state;
+ struct stat stat;
+};
+
+static void
+file_init(struct file *f, const char *filename)
+{
+ struct varbuf usefilename = VARBUF_INIT;
+
+ varbuf_add_str(&usefilename, instdir);
+ varbuf_add_str(&usefilename, filename);
+ varbuf_end_str(&usefilename);
+
+ f->name = varbuf_detach(&usefilename);
+ f->stat_state = FILE_STAT_INVALID;
+}
+
+static void
+file_stat(struct file *f)
+{
+ int ret;
+
+ if (f->stat_state != FILE_STAT_INVALID)
+ return;
+
+ ret = lstat(f->name, &f->stat);
+ if (ret && errno != ENOENT)
+ ohshite(_("cannot stat file '%s'"), f->name);
+
+ if (ret == 0)
+ f->stat_state = FILE_STAT_VALID;
+ else
+ f->stat_state = FILE_STAT_NOFILE;
+}
+
+static void
+check_writable_dir(struct file *f)
+{
+ char *tmpname;
+ int tmpfd;
+
+ tmpname = str_fmt("%s%s", f->name, ".dpkg-divert.tmp");
+
+ tmpfd = creat(tmpname, 0600);
+ if (tmpfd < 0)
+ ohshite(_("error checking '%s'"), f->name);
+ close(tmpfd);
+ (void)unlink(tmpname);
+
+ free(tmpname);
+}
+
+static bool
+check_rename(struct file *src, struct file *dst)
+{
+ file_stat(src);
+
+ /* If the source file is not present and we are not going to do
+ * the rename anyway there's no point in checking any further. */
+ if (src->stat_state == FILE_STAT_NOFILE)
+ return false;
+
+ file_stat(dst);
+
+ /*
+ * Unfortunately we have to check for write access in both places,
+ * just having +w is not enough, since people do mount things RO,
+ * and we need to fail before we start mucking around with things.
+ * So we open a file with the same name as the diversions but with
+ * an extension that (hopefully) won't overwrite anything. If it
+ * succeeds, we assume a writable filesystem.
+ */
+
+ check_writable_dir(src);
+ check_writable_dir(dst);
+
+ if (src->stat_state == FILE_STAT_VALID &&
+ dst->stat_state == FILE_STAT_VALID &&
+ !(src->stat.st_dev == dst->stat.st_dev &&
+ src->stat.st_ino == dst->stat.st_ino))
+ ohshit(_("rename involves overwriting '%s' with\n"
+ " different file '%s', not allowed"),
+ dst->name, src->name);
+
+ return true;
+}
+
+static void
+file_copy(const char *src, const char *dst)
+{
+ struct dpkg_error err;
+ char *tmp;
+ int srcfd, dstfd;
+
+ srcfd = open(src, O_RDONLY);
+ if (srcfd < 0)
+ ohshite(_("unable to open file '%s'"), src);
+
+ tmp = str_fmt("%s%s", dst, ".dpkg-divert.tmp");
+ dstfd = creat(tmp, 0600);
+ if (dstfd < 0)
+ ohshite(_("unable to create file '%s'"), tmp);
+
+ push_cleanup(cu_filename, ~ehflag_normaltidy, 1, tmp);
+
+ if (fd_fd_copy(srcfd, dstfd, -1, &err) < 0)
+ ohshit(_("cannot copy '%s' to '%s': %s"), src, tmp, err.str);
+
+ close(srcfd);
+
+ if (fsync(dstfd))
+ ohshite(_("unable to sync file '%s'"), tmp);
+ if (close(dstfd))
+ ohshite(_("unable to close file '%s'"), tmp);
+
+ file_copy_perms(src, tmp);
+
+ if (rename(tmp, dst) != 0)
+ ohshite(_("cannot rename '%s' to '%s'"), tmp, dst);
+
+ free(tmp);
+
+ pop_cleanup(ehflag_normaltidy);
+}
+
+static void
+file_rename(struct file *src, struct file *dst)
+{
+ if (src->stat_state == FILE_STAT_NOFILE)
+ return;
+
+ if (dst->stat_state == FILE_STAT_VALID) {
+ if (unlink(src->name))
+ ohshite(_("rename: remove duplicate old link '%s'"),
+ src->name);
+ } else {
+ if (rename(src->name, dst->name) == 0)
+ return;
+
+ /* If a rename didn't work try moving the file instead. */
+ file_copy(src->name, dst->name);
+
+ if (unlink(src->name))
+ ohshite(_("unable to remove copied source file '%s'"),
+ src->name);
+ }
+}
+
+static void
+diversion_check_filename(const char *filename)
+{
+ if (filename[0] != '/')
+ badusage(_("filename \"%s\" is not absolute"), filename);
+ if (strchr(filename, '\n') != NULL)
+ badusage(_("file may not contain newlines"));
+}
+
+static const char *
+diversion_pkg_name(struct fsys_diversion *d)
+{
+ if (d->pkgset == NULL)
+ return ":";
+ else
+ return d->pkgset->name;
+}
+
+static const char *
+varbuf_diversion(struct varbuf *str, const char *pkgname,
+ const char *filename, const char *divertto)
+{
+ varbuf_reset(str);
+
+ if (pkgname == NULL) {
+ if (divertto == NULL)
+ varbuf_printf(str, _("local diversion of %s"), filename);
+ else
+ varbuf_printf(str, _("local diversion of %s to %s"),
+ filename, divertto);
+ } else {
+ if (divertto == NULL)
+ varbuf_printf(str, _("diversion of %s by %s"),
+ filename, pkgname);
+ else
+ varbuf_printf(str, _("diversion of %s to %s by %s"),
+ filename, divertto, pkgname);
+ }
+
+ return str->buf;
+}
+
+static const char *
+diversion_current(const char *filename)
+{
+ static struct varbuf str = VARBUF_INIT;
+
+ if (opt_pkgname_match_any) {
+ varbuf_reset(&str);
+
+ if (opt_divertto == NULL)
+ varbuf_printf(&str, _("any diversion of %s"), filename);
+ else
+ varbuf_printf(&str, _("any diversion of %s to %s"),
+ filename, opt_divertto);
+ } else {
+ return varbuf_diversion(&str, opt_pkgname, filename, opt_divertto);
+ }
+
+ return str.buf;
+}
+
+static const char *
+diversion_describe(struct fsys_diversion *d)
+{
+ static struct varbuf str = VARBUF_INIT;
+ const char *pkgname;
+ const char *name_from, *name_to;
+
+ if (d->camefrom) {
+ name_from = d->camefrom->name;
+ name_to = d->camefrom->divert->useinstead->name;
+ } else {
+ name_from = d->useinstead->divert->camefrom->name;
+ name_to = d->useinstead->name;
+ }
+
+ if (d->pkgset == NULL)
+ pkgname = NULL;
+ else
+ pkgname = d->pkgset->name;
+
+ return varbuf_diversion(&str, pkgname, name_from, name_to);
+}
+
+static void
+divertdb_write(void)
+{
+ char *dbname;
+ struct atomic_file *file;
+ struct fsys_hash_iter *iter;
+ struct fsys_namenode *namenode;
+
+ dbname = dpkg_db_get_path(DIVERSIONSFILE);
+
+ file = atomic_file_new(dbname, ATOMIC_FILE_BACKUP);
+ atomic_file_open(file);
+
+ iter = fsys_hash_iter_new();
+ while ((namenode = fsys_hash_iter_next(iter))) {
+ struct fsys_diversion *d = namenode->divert;
+
+ if (d == NULL || d->useinstead == NULL)
+ continue;
+
+ fprintf(file->fp, "%s\n%s\n%s\n",
+ d->useinstead->divert->camefrom->name,
+ d->useinstead->name,
+ diversion_pkg_name(d));
+ }
+ fsys_hash_iter_free(iter);
+
+ atomic_file_sync(file);
+ atomic_file_close(file);
+ atomic_file_commit(file);
+ atomic_file_free(file);
+
+ free(dbname);
+}
+
+static bool
+diversion_is_essential(struct fsys_namenode *namenode)
+{
+ struct pkginfo *pkg;
+ struct pkg_hash_iter *pkg_iter;
+ struct fsys_node_pkgs_iter *iter;
+ bool essential = false;
+
+ pkg_iter = pkg_hash_iter_new();
+ while ((pkg = pkg_hash_iter_next_pkg(pkg_iter))) {
+ if (pkg->installed.essential)
+ ensure_packagefiles_available(pkg);
+ }
+ pkg_hash_iter_free(pkg_iter);
+
+ iter = fsys_node_pkgs_iter_new(namenode);
+ while ((pkg = fsys_node_pkgs_iter_next(iter))) {
+ if (pkg->installed.essential) {
+ essential = true;
+ break;
+ }
+ }
+ fsys_node_pkgs_iter_free(iter);
+
+ return essential;
+}
+
+static bool
+diversion_is_owned_by_self(struct pkgset *set, struct fsys_namenode *namenode)
+{
+ struct pkginfo *pkg;
+ struct fsys_node_pkgs_iter *iter;
+ bool owned = false;
+
+ if (set == NULL)
+ return false;
+
+ for (pkg = &set->pkg; pkg; pkg = pkg->arch_next)
+ ensure_packagefiles_available(pkg);
+
+ iter = fsys_node_pkgs_iter_new(namenode);
+ while ((pkg = fsys_node_pkgs_iter_next(iter))) {
+ if (pkg->set == set) {
+ owned = true;
+ break;
+ }
+ }
+ fsys_node_pkgs_iter_free(iter);
+
+ return owned;
+}
+
+static int
+diversion_add(const char *const *argv)
+{
+ const char *filename = argv[0];
+ struct file file_from, file_to;
+ struct fsys_diversion *contest, *altname;
+ struct fsys_namenode *fnn_from, *fnn_to;
+ struct pkgset *pkgset;
+
+ opt_pkgname_match_any = false;
+ opt_rename_setup();
+
+ /* Handle filename. */
+ if (!filename || argv[1])
+ badusage(_("--%s needs a single argument"), cipaction->olong);
+
+ diversion_check_filename(filename);
+
+ file_init(&file_from, filename);
+ file_stat(&file_from);
+
+ if (file_from.stat_state == FILE_STAT_VALID &&
+ S_ISDIR(file_from.stat.st_mode))
+ badusage(_("cannot divert directories"));
+
+ fnn_from = fsys_hash_find_node(filename, 0);
+
+ /* Handle divertto. */
+ if (opt_divertto == NULL)
+ opt_divertto = str_fmt("%s.distrib", filename);
+
+ if (strcmp(filename, opt_divertto) == 0)
+ badusage(_("cannot divert file '%s' to itself"), filename);
+
+ file_init(&file_to, opt_divertto);
+
+ fnn_to = fsys_hash_find_node(opt_divertto, 0);
+
+ /* Handle package name. */
+ if (opt_pkgname == NULL)
+ pkgset = NULL;
+ else
+ pkgset = pkg_hash_find_set(opt_pkgname);
+
+ /* Check we are not stomping over an existing diversion. */
+ if (fnn_from->divert || fnn_to->divert) {
+ if (fnn_to->divert && fnn_to->divert->camefrom &&
+ strcmp(fnn_to->divert->camefrom->name, filename) == 0 &&
+ fnn_from->divert && fnn_from->divert->useinstead &&
+ strcmp(fnn_from->divert->useinstead->name, opt_divertto) == 0 &&
+ fnn_from->divert->pkgset == pkgset) {
+ if (opt_verbose > 0)
+ printf(_("Leaving '%s'\n"),
+ diversion_describe(fnn_from->divert));
+ return 0;
+ }
+
+ ohshit(_("'%s' clashes with '%s'"),
+ diversion_current(filename),
+ fnn_from->divert ?
+ diversion_describe(fnn_from->divert) :
+ diversion_describe(fnn_to->divert));
+ }
+
+ /* Create new diversion. */
+ contest = nfmalloc(sizeof(*contest));
+ altname = nfmalloc(sizeof(*altname));
+
+ altname->camefrom = fnn_from;
+ altname->camefrom->divert = contest;
+ altname->useinstead = NULL;
+ altname->pkgset = pkgset;
+
+ contest->useinstead = fnn_to;
+ contest->useinstead->divert = altname;
+ contest->camefrom = NULL;
+ contest->pkgset = pkgset;
+
+ /* Update database and file system if needed. */
+ if (opt_verbose > 0)
+ printf(_("Adding '%s'\n"), diversion_describe(contest));
+ if (opt_rename)
+ opt_rename = check_rename(&file_from, &file_to);
+ /* Check we are not renaming a file owned by the diverting pkgset. */
+ if (opt_rename && diversion_is_owned_by_self(pkgset, fnn_from)) {
+ if (opt_verbose > 0)
+ printf(_("Ignoring request to rename file '%s' "
+ "owned by diverting package '%s'\n"),
+ filename, pkgset->name);
+ opt_rename = false;
+ }
+ if (opt_rename && diversion_is_essential(fnn_from))
+ warning(_("diverting file '%s' from an Essential package with "
+ "rename is dangerous, use --no-rename"), filename);
+ if (!opt_test) {
+ divertdb_write();
+ if (opt_rename)
+ file_rename(&file_from, &file_to);
+ }
+
+ return 0;
+}
+
+static bool
+diversion_is_shared(struct pkgset *set, struct fsys_namenode *namenode)
+{
+ const char *archname;
+ struct pkginfo *pkg;
+ struct dpkg_arch *arch;
+ struct fsys_node_pkgs_iter *iter;
+ bool shared = false;
+
+ if (set == NULL)
+ return false;
+
+ archname = getenv("DPKG_MAINTSCRIPT_ARCH");
+ arch = dpkg_arch_find(archname);
+ if (arch->type == DPKG_ARCH_NONE || arch->type == DPKG_ARCH_EMPTY)
+ return false;
+
+ for (pkg = &set->pkg; pkg; pkg = pkg->arch_next)
+ ensure_packagefiles_available(pkg);
+
+ iter = fsys_node_pkgs_iter_new(namenode);
+ while ((pkg = fsys_node_pkgs_iter_next(iter))) {
+ if (pkg->set == set && pkg->installed.arch != arch) {
+ shared = true;
+ break;
+ }
+ }
+ fsys_node_pkgs_iter_free(iter);
+
+ return shared;
+}
+
+static int
+diversion_remove(const char *const *argv)
+{
+ const char *filename = argv[0];
+ struct fsys_namenode *namenode;
+ struct fsys_diversion *contest, *altname;
+ struct file file_from, file_to;
+ struct pkgset *pkgset;
+
+ opt_rename_setup();
+
+ if (!filename || argv[1])
+ badusage(_("--%s needs a single argument"), cipaction->olong);
+
+ diversion_check_filename(filename);
+
+ namenode = fsys_hash_find_node(filename, FHFF_NONE);
+
+ if (namenode == NULL || namenode->divert == NULL ||
+ namenode->divert->useinstead == NULL) {
+ if (opt_verbose > 0)
+ printf(_("No diversion '%s', none removed.\n"),
+ diversion_current(filename));
+ return 0;
+ }
+
+ if (opt_pkgname == NULL)
+ pkgset = NULL;
+ else
+ pkgset = pkg_hash_find_set(opt_pkgname);
+
+ contest = namenode->divert;
+ altname = contest->useinstead->divert;
+
+ if (opt_divertto != NULL &&
+ strcmp(opt_divertto, contest->useinstead->name) != 0)
+ ohshit(_("mismatch on divert-to\n"
+ " when removing '%s'\n"
+ " found '%s'"),
+ diversion_current(filename),
+ diversion_describe(contest));
+
+ if (!opt_pkgname_match_any && pkgset != contest->pkgset)
+ ohshit(_("mismatch on package\n"
+ " when removing '%s'\n"
+ " found '%s'"),
+ diversion_current(filename),
+ diversion_describe(contest));
+
+ /* Ignore removal request if the diverted file is still owned
+ * by another package in the same set. */
+ if (diversion_is_shared(pkgset, namenode)) {
+ if (opt_verbose > 0)
+ printf(_("Ignoring request to remove shared diversion '%s'.\n"),
+ diversion_describe(contest));
+ return 0;
+ }
+
+ if (opt_verbose > 0)
+ printf(_("Removing '%s'\n"), diversion_describe(contest));
+
+ file_init(&file_from, altname->camefrom->name);
+ file_init(&file_to, contest->useinstead->name);
+
+ /* Remove entries from database. */
+ contest->useinstead->divert = NULL;
+ altname->camefrom->divert = NULL;
+
+ if (opt_rename)
+ opt_rename = check_rename(&file_to, &file_from);
+ if (opt_rename && !opt_test)
+ file_rename(&file_to, &file_from);
+
+ if (!opt_test)
+ divertdb_write();
+
+ return 0;
+}
+
+static int
+diversion_list(const char *const *argv)
+{
+ struct fsys_hash_iter *iter;
+ struct fsys_namenode *namenode;
+ struct glob_node *glob_list = NULL;
+ const char *pattern;
+
+ while ((pattern = *argv++))
+ glob_list_prepend(&glob_list, m_strdup(pattern));
+
+ if (glob_list == NULL)
+ glob_list_prepend(&glob_list, m_strdup("*"));
+
+ iter = fsys_hash_iter_new();
+ while ((namenode = fsys_hash_iter_next(iter))) {
+ struct glob_node *g;
+ struct fsys_diversion *contest = namenode->divert;
+ struct fsys_diversion *altname;
+ const char *pkgname;
+
+ if (contest == NULL || contest->useinstead == NULL)
+ continue;
+
+ altname = contest->useinstead->divert;
+
+ pkgname = diversion_pkg_name(contest);
+
+ for (g = glob_list; g; g = g->next) {
+ if (fnmatch(g->pattern, pkgname, 0) == 0 ||
+ fnmatch(g->pattern, contest->useinstead->name, 0) == 0 ||
+ fnmatch(g->pattern, altname->camefrom->name, 0) == 0) {
+ printf("%s\n", diversion_describe(contest));
+ break;
+ }
+ }
+ }
+ fsys_hash_iter_free(iter);
+
+ glob_list_free(glob_list);
+
+ return 0;
+}
+
+static int
+diversion_truename(const char *const *argv)
+{
+ const char *filename = argv[0];
+ struct fsys_namenode *namenode;
+
+ if (!filename || argv[1])
+ badusage(_("--%s needs a single argument"), cipaction->olong);
+
+ diversion_check_filename(filename);
+
+ namenode = fsys_hash_find_node(filename, FHFF_NONE);
+
+ /* Print the given name if file is not diverted. */
+ if (namenode && namenode->divert && namenode->divert->useinstead)
+ printf("%s\n", namenode->divert->useinstead->name);
+ else
+ printf("%s\n", filename);
+
+ return 0;
+}
+
+static int
+diversion_listpackage(const char *const *argv)
+{
+ const char *filename = argv[0];
+ struct fsys_namenode *namenode;
+
+ if (!filename || argv[1])
+ badusage(_("--%s needs a single argument"), cipaction->olong);
+
+ diversion_check_filename(filename);
+
+ namenode = fsys_hash_find_node(filename, FHFF_NONE);
+
+ /* Print nothing if file is not diverted. */
+ if (namenode == NULL || namenode->divert == NULL)
+ return 0;
+
+ if (namenode->divert->pkgset == NULL)
+ /* Indicate package is local using something not in package
+ * namespace. */
+ printf("LOCAL\n");
+ else
+ printf("%s\n", namenode->divert->pkgset->name);
+
+ return 0;
+}
+
+static void
+set_package(const struct cmdinfo *cip, const char *value)
+{
+ opt_pkgname_match_any = false;
+
+ /* If value is NULL we are being called from --local. */
+ opt_pkgname = value;
+
+ if (opt_pkgname && strchr(opt_pkgname, '\n') != NULL)
+ badusage(_("package may not contain newlines"));
+}
+
+static void
+set_divertto(const struct cmdinfo *cip, const char *value)
+{
+ opt_divertto = value;
+
+ if (opt_divertto[0] != '/')
+ badusage(_("filename \"%s\" is not absolute"), opt_divertto);
+ if (strchr(opt_divertto, '\n') != NULL)
+ badusage(_("divert-to may not contain newlines"));
+}
+
+static void
+set_instdir(const struct cmdinfo *cip, const char *value)
+{
+ instdir = dpkg_fsys_set_dir(value);
+}
+
+static void
+set_root(const struct cmdinfo *cip, const char *value)
+{
+ instdir = dpkg_fsys_set_dir(value);
+ admindir = dpkg_fsys_get_path(ADMINDIR);
+}
+
+static const struct cmdinfo cmdinfo_add =
+ ACTION("add", 0, 0, diversion_add);
+
+static const struct cmdinfo cmdinfos[] = {
+ ACTION("add", 0, 0, diversion_add),
+ ACTION("remove", 0, 0, diversion_remove),
+ ACTION("list", 0, 0, diversion_list),
+ ACTION("listpackage", 0, 0, diversion_listpackage),
+ ACTION("truename", 0, 0, diversion_truename),
+
+ { "admindir", 0, 1, NULL, &admindir, NULL },
+ { "instdir", 0, 1, NULL, NULL, set_instdir, 0 },
+ { "root", 0, 1, NULL, NULL, set_root, 0 },
+ { "divert", 0, 1, NULL, NULL, set_divertto },
+ { "package", 0, 1, NULL, NULL, set_package },
+ { "local", 0, 0, NULL, NULL, set_package },
+ { "quiet", 0, 0, &opt_verbose, NULL, NULL, 0 },
+ { "rename", 0, 0, &opt_rename, NULL, NULL, 1 },
+ { "no-rename", 0, 0, &opt_rename, NULL, NULL, 0 },
+ { "test", 0, 0, &opt_test, NULL, NULL, 1 },
+ { "help", '?', 0, NULL, NULL, usage },
+ { "version", 0, 0, NULL, NULL, printversion },
+ { NULL, 0 }
+};
+
+int
+main(int argc, const char * const *argv)
+{
+ const char *env_pkgname;
+ int ret;
+
+ dpkg_locales_init(PACKAGE);
+ dpkg_program_init("dpkg-divert");
+ dpkg_options_parse(&argv, cmdinfos, printforhelp);
+
+ admindir = dpkg_db_set_dir(admindir);
+ instdir = dpkg_fsys_set_dir(instdir);
+
+ env_pkgname = getenv("DPKG_MAINTSCRIPT_PACKAGE");
+ if (opt_pkgname_match_any && env_pkgname)
+ set_package(NULL, env_pkgname);
+
+ if (!cipaction)
+ setaction(&cmdinfo_add, NULL);
+
+ modstatdb_open(msdbrw_readonly);
+ fsys_hash_init();
+ ensure_diversions();
+
+ ret = cipaction->action(argv);
+
+ modstatdb_shutdown();
+
+ dpkg_program_done();
+
+ return ret;
+}
diff --git a/src/enquiry.c b/src/enquiry.c
new file mode 100644
index 0000000..dd7069e
--- /dev/null
+++ b/src/enquiry.c
@@ -0,0 +1,754 @@
+/*
+ * dpkg - main program for package management
+ * enquiry.c - status enquiry and listing options
+ *
+ * Copyright © 1995,1996 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2006, 2008-2016 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 <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/arch.h>
+#include <dpkg/pkg-array.h>
+#include <dpkg/pkg-show.h>
+#include <dpkg/triglib.h>
+#include <dpkg/string.h>
+#include <dpkg/options.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+
+#include "main.h"
+
+struct audit_problem {
+ bool (*check)(struct pkginfo *, const struct audit_problem *problem);
+ union {
+ int number;
+ const char *string;
+ } value;
+ const char *explanation;
+};
+
+static bool
+audit_reinstreq(struct pkginfo *pkg, const struct audit_problem *problem)
+{
+ return pkg->eflag & PKG_EFLAG_REINSTREQ;
+}
+
+static bool
+audit_status(struct pkginfo *pkg, const struct audit_problem *problem)
+{
+ if (pkg->eflag & PKG_EFLAG_REINSTREQ)
+ return false;
+ return (int)pkg->status == problem->value.number;
+}
+
+static bool
+audit_infofile(struct pkginfo *pkg, const struct audit_problem *problem)
+{
+ if (pkg->status < PKG_STAT_HALFINSTALLED)
+ return false;
+ return !pkg_infodb_has_file(pkg, &pkg->installed, problem->value.string);
+}
+
+static bool
+audit_arch(struct pkginfo *pkg, const struct audit_problem *problem)
+{
+ if (pkg->status < PKG_STAT_HALFINSTALLED)
+ return false;
+ return pkg->installed.arch->type == (enum dpkg_arch_type)problem->value.number;
+}
+
+static const struct audit_problem audit_problems[] = {
+ {
+ .check = audit_reinstreq,
+ .value.number = 0,
+ .explanation = N_(
+ "The following packages are in a mess due to serious problems during\n"
+ "installation. They must be reinstalled for them (and any packages\n"
+ "that depend on them) to function properly:\n")
+ }, {
+ .check = audit_status,
+ .value.number = PKG_STAT_UNPACKED,
+ .explanation = N_(
+ "The following packages have been unpacked but not yet configured.\n"
+ "They must be configured using dpkg --configure or the configure\n"
+ "menu option in dselect for them to work:\n")
+ }, {
+ .check = audit_status,
+ .value.number = PKG_STAT_HALFCONFIGURED,
+ .explanation = N_(
+ "The following packages are only half configured, probably due to problems\n"
+ "configuring them the first time. The configuration should be retried using\n"
+ "dpkg --configure <package> or the configure menu option in dselect:\n")
+ }, {
+ .check = audit_status,
+ .value.number = PKG_STAT_HALFINSTALLED,
+ .explanation = N_(
+ "The following packages are only half installed, due to problems during\n"
+ "installation. The installation can probably be completed by retrying it;\n"
+ "the packages can be removed using dselect or dpkg --remove:\n")
+ }, {
+ .check = audit_status,
+ .value.number = PKG_STAT_TRIGGERSAWAITED,
+ .explanation = N_(
+ "The following packages are awaiting processing of triggers that they\n"
+ "have activated in other packages. This processing can be requested using\n"
+ "dselect or dpkg --configure --pending (or dpkg --triggers-only):\n")
+ }, {
+ .check = audit_status,
+ .value.number = PKG_STAT_TRIGGERSPENDING,
+ .explanation = N_(
+ "The following packages have been triggered, but the trigger processing\n"
+ "has not yet been done. Trigger processing can be requested using\n"
+ "dselect or dpkg --configure --pending (or dpkg --triggers-only):\n")
+ }, {
+ .check = audit_infofile,
+ .value.string = LISTFILE,
+ .explanation = N_(
+ "The following packages are missing the list control file in the\n"
+ "database, they need to be reinstalled:\n")
+ }, {
+ .check = audit_infofile,
+ .value.string = HASHFILE,
+ .explanation = N_(
+ "The following packages are missing the md5sums control file in the\n"
+ "database, they need to be reinstalled:\n")
+ }, {
+ .check = audit_arch,
+ .value.number = DPKG_ARCH_EMPTY,
+ .explanation = N_("The following packages do not have an architecture:\n")
+ }, {
+ .check = audit_arch,
+ .value.number = DPKG_ARCH_ILLEGAL,
+ .explanation = N_("The following packages have an illegal architecture:\n")
+ }, {
+ .check = audit_arch,
+ .value.number = DPKG_ARCH_UNKNOWN,
+ .explanation = N_(
+ "The following packages have an unknown foreign architecture, which will\n"
+ "cause dependency issues on front-ends. This can be fixed by registering\n"
+ "the foreign architecture with dpkg --add-architecture:\n")
+ }, {
+ .check = NULL
+ }
+};
+
+static void describebriefly(struct pkginfo *pkg) {
+ int maxl, l;
+ const char *pdesc;
+
+ maxl= 57;
+ l= strlen(pkg->set->name);
+ if (l>20) maxl -= (l-20);
+
+ pdesc = pkgbin_synopsis(pkg, &pkg->installed, &l);
+ l = min(l, maxl);
+
+ printf(" %-20s %.*s\n", pkg_name(pkg, pnaw_nonambig), l, pdesc);
+}
+
+static struct pkginfo *
+pkg_array_mapper(const char *name)
+{
+ struct pkginfo *pkg;
+
+ pkg = dpkg_options_parse_pkgname(cipaction, name);
+ if (pkg->status == PKG_STAT_NOTINSTALLED)
+ notice(_("package '%s' is not installed"), pkg_name(pkg, pnaw_nonambig));
+
+ return pkg;
+}
+
+int
+audit(const char *const *argv)
+{
+ const struct audit_problem *problem;
+ struct pkg_array array;
+ bool head_running = false;
+ int i;
+
+ modstatdb_open(msdbrw_readonly);
+
+ if (!*argv)
+ pkg_array_init_from_hash(&array);
+ else
+ pkg_array_init_from_names(&array, pkg_array_mapper, (const char **)argv);
+
+ pkg_array_sort(&array, pkg_sorter_by_nonambig_name_arch);
+
+ for (problem = audit_problems; problem->check; problem++) {
+ bool head = false;
+
+ for (i = 0; i < array.n_pkgs; i++) {
+ struct pkginfo *pkg = array.pkgs[i];
+
+ if (!problem->check(pkg, problem))
+ continue;
+ if (!head_running) {
+ if (modstatdb_is_locked())
+ puts(_(
+"Another process has locked the database for writing, and might currently be\n"
+"modifying it, some of the following problems might just be due to that.\n"));
+ head_running = true;
+ }
+ if (!head) {
+ fputs(gettext(problem->explanation), stdout);
+ head = true;
+ }
+ describebriefly(pkg);
+ }
+
+ if (head) putchar('\n');
+ }
+
+ pkg_array_destroy(&array);
+
+ m_output(stdout, _("<standard output>"));
+
+ return 0;
+}
+
+struct sectionentry {
+ struct sectionentry *next;
+ const char *name;
+ int count;
+};
+
+static bool
+yettobeunpacked(struct pkginfo *pkg, const char **thissect)
+{
+ if (pkg->want != PKG_WANT_INSTALL)
+ return false;
+
+ switch (pkg->status) {
+ case PKG_STAT_UNPACKED:
+ case PKG_STAT_INSTALLED:
+ case PKG_STAT_HALFCONFIGURED:
+ case PKG_STAT_TRIGGERSPENDING:
+ case PKG_STAT_TRIGGERSAWAITED:
+ return false;
+ case PKG_STAT_NOTINSTALLED:
+ case PKG_STAT_HALFINSTALLED:
+ case PKG_STAT_CONFIGFILES:
+ if (thissect)
+ *thissect = str_is_set(pkg->section) ? pkg->section :
+ C_("section", "<unknown>");
+ return true;
+ default:
+ internerr("unknown package status '%d'", pkg->status);
+ }
+ return false;
+}
+
+int
+unpackchk(const char *const *argv)
+{
+ int totalcount, sects;
+ struct sectionentry *sectionentries, *se, **sep;
+ struct pkg_hash_iter *iter;
+ struct pkginfo *pkg;
+ const char *thissect;
+ char buf[20];
+ int width;
+
+ if (*argv)
+ badusage(_("--%s takes no arguments"), cipaction->olong);
+
+ modstatdb_open(msdbrw_readonly);
+
+ totalcount= 0;
+ sectionentries = NULL;
+ sects= 0;
+ iter = pkg_hash_iter_new();
+ while ((pkg = pkg_hash_iter_next_pkg(iter))) {
+ if (!yettobeunpacked(pkg, &thissect)) continue;
+ for (se= sectionentries; se && strcasecmp(thissect,se->name); se= se->next);
+ if (!se) {
+ se = nfmalloc(sizeof(*se));
+ for (sep= &sectionentries;
+ *sep && strcasecmp(thissect,(*sep)->name) > 0;
+ sep= &(*sep)->next);
+ se->name= thissect;
+ se->count= 0;
+ se->next= *sep;
+ *sep= se;
+ sects++;
+ }
+ se->count++; totalcount++;
+ }
+ pkg_hash_iter_free(iter);
+
+ if (totalcount == 0)
+ return 0;
+
+ if (totalcount <= 12) {
+ iter = pkg_hash_iter_new();
+ while ((pkg = pkg_hash_iter_next_pkg(iter))) {
+ if (!yettobeunpacked(pkg, NULL))
+ continue;
+ describebriefly(pkg);
+ }
+ pkg_hash_iter_free(iter);
+ } else if (sects <= 12) {
+ for (se= sectionentries; se; se= se->next) {
+ sprintf(buf,"%d",se->count);
+ printf(_(" %d in %s: "),se->count,se->name);
+ width= 70-strlen(se->name)-strlen(buf);
+ while (width > 59) { putchar(' '); width--; }
+ iter = pkg_hash_iter_new();
+ while ((pkg = pkg_hash_iter_next_pkg(iter))) {
+ const char *pkgname;
+
+ if (!yettobeunpacked(pkg,&thissect)) continue;
+ if (strcasecmp(thissect,se->name)) continue;
+ pkgname = pkg_name(pkg, pnaw_nonambig);
+ width -= strlen(pkgname);
+ width--;
+ if (width < 4) { printf(" ..."); break; }
+ printf(" %s", pkgname);
+ }
+ pkg_hash_iter_free(iter);
+ putchar('\n');
+ }
+ } else {
+ printf(P_(" %d package, from the following section:",
+ " %d packages, from the following sections:", totalcount),
+ totalcount);
+ width= 0;
+ for (se= sectionentries; se; se= se->next) {
+ sprintf(buf,"%d",se->count);
+ width -= (6 + strlen(se->name) + strlen(buf));
+ if (width < 0) { putchar('\n'); width= 73 - strlen(se->name) - strlen(buf); }
+ printf(" %s (%d)",se->name,se->count);
+ }
+ putchar('\n');
+ }
+
+ m_output(stdout, _("<standard output>"));
+
+ return 0;
+}
+
+static int
+assert_version_support(const char *const *argv,
+ struct dpkg_version *version,
+ const char *feature_name)
+{
+ struct pkginfo *pkg;
+
+ if (*argv)
+ badusage(_("--%s takes no arguments"), cipaction->olong);
+
+ modstatdb_open(msdbrw_readonly);
+
+ pkg = pkg_hash_find_singleton("dpkg");
+ switch (pkg->status) {
+ case PKG_STAT_INSTALLED:
+ case PKG_STAT_TRIGGERSPENDING:
+ return 0;
+ case PKG_STAT_UNPACKED:
+ case PKG_STAT_HALFCONFIGURED:
+ case PKG_STAT_HALFINSTALLED:
+ case PKG_STAT_TRIGGERSAWAITED:
+ if (dpkg_version_relate(&pkg->configversion, DPKG_RELATION_GE, version))
+ return 0;
+ printf(_("Version of dpkg with working %s support not yet configured.\n"
+ " Please use 'dpkg --configure dpkg', and then try again.\n"),
+ feature_name);
+ return 1;
+ default:
+ printf(_("dpkg not recorded as installed, cannot check for %s support!\n"),
+ feature_name);
+ return 1;
+ }
+}
+
+int
+assertpredep(const char *const *argv)
+{
+ struct dpkg_version version = { 0, "1.1.0", NULL };
+
+ return assert_version_support(argv, &version, _("Pre-Depends field"));
+}
+
+int
+assertepoch(const char *const *argv)
+{
+ struct dpkg_version version = { 0, "1.4.0.7", NULL };
+
+ return assert_version_support(argv, &version, _("epoch"));
+}
+
+int
+assertlongfilenames(const char *const *argv)
+{
+ struct dpkg_version version = { 0, "1.4.1.17", NULL };
+
+ return assert_version_support(argv, &version, _("long filenames"));
+}
+
+int
+assertmulticonrep(const char *const *argv)
+{
+ struct dpkg_version version = { 0, "1.4.1.19", NULL };
+
+ return assert_version_support(argv, &version,
+ _("multiple Conflicts and Replaces"));
+}
+
+int
+assertmultiarch(const char *const *argv)
+{
+ struct dpkg_version version = { 0, "1.16.2", NULL };
+
+ return assert_version_support(argv, &version, _("multi-arch"));
+}
+
+int
+assertverprovides(const char *const *argv)
+{
+ struct dpkg_version version = { 0, "1.17.11", NULL };
+
+ return assert_version_support(argv, &version, _("versioned Provides"));
+}
+
+/**
+ * Print a single package which:
+ * (a) is the target of one or more relevant predependencies.
+ * (b) has itself no unsatisfied pre-dependencies.
+ *
+ * If such a package is present output is the Packages file entry,
+ * which can be massaged as appropriate.
+ *
+ * Exit status:
+ * 0 = a package printed, OK
+ * 1 = no suitable package available
+ * 2 = error
+ */
+int
+predeppackage(const char *const *argv)
+{
+ static struct varbuf vb;
+
+ struct pkg_hash_iter *iter;
+ struct pkginfo *pkg = NULL, *startpkg, *trypkg;
+ struct dependency *dep;
+ struct deppossi *possi, *provider;
+
+ if (*argv)
+ badusage(_("--%s takes no arguments"), cipaction->olong);
+
+ modstatdb_open(msdbrw_readonly | msdbrw_available_readonly);
+ /* We use clientdata->istobe to detect loops. */
+ clear_istobes();
+
+ dep = NULL;
+ iter = pkg_hash_iter_new();
+ while (!dep && (pkg = pkg_hash_iter_next_pkg(iter))) {
+ /* Ignore packages user doesn't want. */
+ if (pkg->want != PKG_WANT_INSTALL)
+ continue;
+ /* Ignore packages not available. */
+ if (!pkg->archives)
+ continue;
+ pkg->clientdata->istobe = PKG_ISTOBE_PREINSTALL;
+ for (dep= pkg->available.depends; dep; dep= dep->next) {
+ if (dep->type != dep_predepends) continue;
+ if (depisok(dep, &vb, NULL, NULL, true))
+ continue;
+ /* This will leave dep non-NULL, and so exit the loop. */
+ break;
+ }
+ pkg->clientdata->istobe = PKG_ISTOBE_NORMAL;
+ /* If dep is NULL we go and get the next package. */
+ }
+ pkg_hash_iter_free(iter);
+
+ if (!dep)
+ return 1; /* Not found. */
+ if (pkg == NULL)
+ internerr("unexpected unfound package");
+
+ startpkg= pkg;
+ pkg->clientdata->istobe = PKG_ISTOBE_PREINSTALL;
+
+ /* OK, we have found an unsatisfied predependency.
+ * Now go and find the first thing we need to install, as a first step
+ * towards satisfying it. */
+ do {
+ /* We search for a package which would satisfy dep, and put it in pkg. */
+ for (possi = dep->list, pkg = NULL;
+ !pkg && possi;
+ possi=possi->next) {
+ struct deppossi_pkg_iterator *possi_iter;
+
+ possi_iter = deppossi_pkg_iter_new(possi, wpb_available);
+ while (!pkg && (trypkg = deppossi_pkg_iter_next(possi_iter))) {
+ if (trypkg->archives &&
+ trypkg->clientdata->istobe == PKG_ISTOBE_NORMAL &&
+ versionsatisfied(&trypkg->available, possi)) {
+ pkg = trypkg;
+ break;
+ }
+ for (provider = possi->ed->depended.available;
+ !pkg && provider;
+ provider = provider->next) {
+ if (provider->up->type != dep_provides)
+ continue;
+ if (!pkg_virtual_deppossi_satisfied(possi, provider))
+ continue;
+ trypkg = provider->up->up;
+ if (!trypkg->archives)
+ continue;
+ if (trypkg->clientdata->istobe == PKG_ISTOBE_NORMAL) {
+ pkg = trypkg;
+ break;
+ }
+ }
+ }
+ deppossi_pkg_iter_free(possi_iter);
+ }
+ if (!pkg) {
+ varbuf_reset(&vb);
+ describedepcon(&vb,dep);
+ varbuf_end_str(&vb);
+ notice(_("cannot see how to satisfy pre-dependency:\n %s"), vb.buf);
+ ohshit(_("cannot satisfy pre-dependencies for %.250s (wanted due to %.250s)"),
+ pkgbin_name(dep->up, &dep->up->available, pnaw_nonambig),
+ pkgbin_name(startpkg, &startpkg->available, pnaw_nonambig));
+ }
+ pkg->clientdata->istobe = PKG_ISTOBE_PREINSTALL;
+ for (dep= pkg->available.depends; dep; dep= dep->next) {
+ if (dep->type != dep_predepends) continue;
+ if (depisok(dep, &vb, NULL, NULL, true))
+ continue;
+ /* This will leave dep non-NULL, and so exit the loop. */
+ break;
+ }
+ } while (dep);
+
+ /* OK, we've found it - pkg has no unsatisfied pre-dependencies! */
+ writerecord(stdout, _("<standard output>"), pkg, &pkg->available);
+
+ m_output(stdout, _("<standard output>"));
+
+ return 0;
+}
+
+int
+printarch(const char *const *argv)
+{
+ if (*argv)
+ badusage(_("--%s takes no arguments"), cipaction->olong);
+
+ printf("%s\n", dpkg_arch_get(DPKG_ARCH_NATIVE)->name);
+
+ m_output(stdout, _("<standard output>"));
+
+ return 0;
+}
+
+int
+print_foreign_arches(const char *const *argv)
+{
+ struct dpkg_arch *arch;
+
+ if (*argv)
+ badusage(_("--%s takes no arguments"), cipaction->olong);
+
+ dpkg_arch_load_list();
+
+ for (arch = dpkg_arch_get_list(); arch; arch = arch->next) {
+ if (arch->type != DPKG_ARCH_FOREIGN)
+ continue;
+
+ printf("%s\n", arch->name);
+ }
+
+ m_output(stdout, _("<standard output>"));
+
+ return 0;
+}
+
+int
+validate_pkgname(const char *const *argv)
+{
+ const char *emsg;
+
+ if (!argv[0] || argv[1])
+ badusage(_("--%s takes one <pkgname> argument"), cipaction->olong);
+
+ emsg = pkg_name_is_illegal(argv[0]);
+ if (emsg)
+ ohshit(_("package name '%s' is invalid: %s"), argv[0], emsg);
+
+ return 0;
+}
+
+int
+validate_trigname(const char *const *argv)
+{
+ const char *emsg;
+
+ if (!argv[0] || argv[1])
+ badusage(_("--%s takes one <trigname> argument"), cipaction->olong);
+
+ emsg = trig_name_is_illegal(argv[0]);
+ if (emsg)
+ ohshit(_("trigger name '%s' is invalid: %s"), argv[0], emsg);
+
+ return 0;
+}
+
+int
+validate_archname(const char *const *argv)
+{
+ const char *emsg;
+
+ if (!argv[0] || argv[1])
+ badusage(_("--%s takes one <archname> argument"), cipaction->olong);
+
+ emsg = dpkg_arch_name_is_illegal(argv[0]);
+ if (emsg)
+ ohshit(_("architecture name '%s' is invalid: %s"), argv[0], emsg);
+
+ return 0;
+}
+
+int
+validate_version(const char *const *argv)
+{
+ struct dpkg_version version;
+ struct dpkg_error err;
+
+ if (!argv[0] || argv[1])
+ badusage(_("--%s takes one <version> argument"), cipaction->olong);
+
+ if (parseversion(&version, argv[0], &err) < 0) {
+ dpkg_error_print(&err, _("version '%s' has bad syntax"), argv[0]);
+ dpkg_error_destroy(&err);
+
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+cmpversions(const char *const *argv)
+{
+ struct relationinfo {
+ const char *string;
+ /* These values are exit status codes, so 0 = true, 1 = false. */
+ int if_lesser, if_equal, if_greater;
+ int if_none_a, if_none_both, if_none_b;
+ bool obsolete;
+ };
+
+ static const struct relationinfo relationinfos[]= {
+ /* < = > !a!2!b */
+ { "le", 0,0,1, 0,0,1 },
+ { "lt", 0,1,1, 0,1,1 },
+ { "eq", 1,0,1, 1,0,1 },
+ { "ne", 0,1,0, 0,1,0 },
+ { "ge", 1,0,0, 1,0,0 },
+ { "gt", 1,1,0, 1,1,0 },
+
+ /* These treat an empty version as later than any version. */
+ { "le-nl", 0,0,1, 1,0,0 },
+ { "lt-nl", 0,1,1, 1,1,0 },
+ { "ge-nl", 1,0,0, 0,0,1 },
+ { "gt-nl", 1,1,0, 0,1,1 },
+
+ /* For compatibility with dpkg control file syntax. */
+ { "<", 0,0,1, 0,0,1, .obsolete = true },
+ { "<=", 0,0,1, 0,0,1 },
+ { "<<", 0,1,1, 0,1,1 },
+ { "=", 1,0,1, 1,0,1 },
+ { ">", 1,0,0, 1,0,0, .obsolete = true },
+ { ">=", 1,0,0, 1,0,0 },
+ { ">>", 1,1,0, 1,1,0 },
+ { NULL }
+ };
+
+ const struct relationinfo *rip;
+ struct dpkg_version a, b;
+ struct dpkg_error err;
+ int rc;
+
+ if (!argv[0] || !argv[1] || !argv[2] || argv[3])
+ badusage(_("--compare-versions takes three arguments:"
+ " <version> <relation> <version>"));
+
+ for (rip=relationinfos; rip->string && strcmp(rip->string,argv[1]); rip++);
+
+ if (!rip->string) badusage(_("--compare-versions bad relation"));
+
+ if (rip->obsolete)
+ warning(_("--%s used with obsolete relation operator '%s'"),
+ cipaction->olong, rip->string);
+
+ if (*argv[0] && strcmp(argv[0],"<unknown>")) {
+ if (parseversion(&a, argv[0], &err) < 0) {
+ dpkg_error_print(&err, _("version '%s' has bad syntax"), argv[0]);
+ dpkg_error_destroy(&err);
+ }
+ } else {
+ dpkg_version_blank(&a);
+ }
+ if (*argv[2] && strcmp(argv[2],"<unknown>")) {
+ if (parseversion(&b, argv[2], &err) < 0) {
+ dpkg_error_print(&err, _("version '%s' has bad syntax"), argv[2]);
+ dpkg_error_destroy(&err);
+ }
+ } else {
+ dpkg_version_blank(&b);
+ }
+ if (!dpkg_version_is_informative(&a)) {
+ if (dpkg_version_is_informative(&b))
+ return rip->if_none_a;
+ else
+ return rip->if_none_both;
+ } else if (!dpkg_version_is_informative(&b)) {
+ return rip->if_none_b;
+ }
+ rc = dpkg_version_compare(&a, &b);
+ debug(dbg_general, "cmpversions a='%s' b='%s' r=%d",
+ versiondescribe(&a,vdew_always),
+ versiondescribe(&b,vdew_always),
+ rc);
+ if (rc > 0)
+ return rip->if_greater;
+ else if (rc < 0)
+ return rip->if_lesser;
+ else
+ return rip->if_equal;
+}
diff --git a/src/errors.c b/src/errors.c
new file mode 100644
index 0000000..50d4155
--- /dev/null
+++ b/src/errors.c
@@ -0,0 +1,137 @@
+/*
+ * dpkg - main program for package management
+ * errors.c - per package error handling
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2007-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 <sys/wait.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <string.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/options.h>
+
+#include "main.h"
+
+bool abort_processing = false;
+
+static int nerrs = 0;
+
+struct error_report {
+ struct error_report *next;
+ char *what;
+};
+
+static struct error_report *reports = NULL;
+static struct error_report **lastreport= &reports;
+static struct error_report emergency;
+
+static void
+enqueue_error_report(const char *arg)
+{
+ struct error_report *nr;
+
+ nr = malloc(sizeof(*nr));
+ if (!nr) {
+ notice(_("failed to allocate memory for new entry in list of failed packages: %s"),
+ strerror(errno));
+ abort_processing = true;
+ nr= &emergency;
+ }
+ nr->what = m_strdup(arg);
+ nr->next = NULL;
+ *lastreport= nr;
+ lastreport= &nr->next;
+
+ if (++nerrs < errabort)
+ return;
+ notice(_("too many errors, stopping"));
+ abort_processing = true;
+}
+
+void
+print_error_perpackage(const char *emsg, const void *data)
+{
+ const char *pkgname = data;
+
+ notice(_("error processing package %s (--%s):\n %s"),
+ pkgname, cipaction->olong, emsg);
+
+ statusfd_send("status: %s : %s : %s", pkgname, "error", emsg);
+
+ enqueue_error_report(pkgname);
+}
+
+void
+print_error_perarchive(const char *emsg, const void *data)
+{
+ const char *filename = data;
+
+ notice(_("error processing archive %s (--%s):\n %s"),
+ filename, cipaction->olong, emsg);
+
+ statusfd_send("status: %s : %s : %s", filename, "error", emsg);
+
+ enqueue_error_report(filename);
+}
+
+int
+reportbroken_retexitstatus(int ret)
+{
+ if (reports) {
+ fputs(_("Errors were encountered while processing:\n"),stderr);
+ while (reports) {
+ fprintf(stderr," %s\n",reports->what);
+ free(reports->what);
+ reports= reports->next;
+ }
+ }
+ if (abort_processing) {
+ fputs(_("Processing was halted because there were too many errors.\n"),stderr);
+ }
+ return nerrs ? 1 : ret;
+}
+
+bool
+skip_due_to_hold(struct pkginfo *pkg)
+{
+ if (pkg->want != PKG_WANT_HOLD)
+ return false;
+ if (in_force(FORCE_HOLD)) {
+ notice(_("package %s was on hold, processing it anyway as you requested"),
+ pkg_name(pkg, pnaw_nonambig));
+ return false;
+ }
+ printf(_("Package %s is on hold, not touching it. Use --force-hold to override.\n"),
+ pkg_name(pkg, pnaw_nonambig));
+ return true;
+}
+
diff --git a/src/file-match.c b/src/file-match.c
new file mode 100644
index 0000000..51bdb0d
--- /dev/null
+++ b/src/file-match.c
@@ -0,0 +1,49 @@
+/*
+ * dpkg - main program for package management
+ * file-match.c - file name/type match tracking 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 <stdlib.h>
+
+#include <dpkg/dpkg.h>
+
+#include "file-match.h"
+
+struct match_node *
+match_node_new(const char *name, const char *type, struct match_node *next)
+{
+ struct match_node *node;
+
+ node = m_malloc(sizeof(*node));
+ node->next = next;
+ node->filename = m_strdup(name);
+ node->filetype = m_strdup(type);
+
+ return node;
+}
+
+void
+match_node_free(struct match_node *node)
+{
+ free(node->filetype);
+ free(node->filename);
+ free(node);
+}
diff --git a/src/file-match.h b/src/file-match.h
new file mode 100644
index 0000000..db7a40b
--- /dev/null
+++ b/src/file-match.h
@@ -0,0 +1,35 @@
+/*
+ * dpkg - main program for package management
+ * file-match.h - file name/type match tracking 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 DPKG_FILE_MATCH_H
+#define DPKG_FILE_MATCH_H
+
+struct match_node {
+ struct match_node *next;
+ char *filetype;
+ char *filename;
+};
+
+struct match_node *
+match_node_new(const char *name, const char *type, struct match_node *next);
+void
+match_node_free(struct match_node *node);
+
+#endif /* DPKG_FILE_MATCH_H */
diff --git a/src/filters.c b/src/filters.c
new file mode 100644
index 0000000..f770fb7
--- /dev/null
+++ b/src/filters.c
@@ -0,0 +1,133 @@
+/*
+ * dpkg - main program for package management
+ * filters.c - filtering routines for excluding bits of packages
+ *
+ * Copyright © 2007, 2008 Tollef Fog Heen <tfheen@err.no>
+ * Copyright © 2008, 2010, 2012-2014 Guillem Jover <guillem@debian.org>
+ * Copyright © 2010 Canonical Ltd.
+ * written by Martin Pitt <martin.pitt@canonical.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 <fnmatch.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/db-fsys.h>
+
+#include "main.h"
+#include "filters.h"
+
+struct filter_node {
+ struct filter_node *next;
+ char *pattern;
+ bool include;
+};
+
+static struct filter_node *filter_head = NULL;
+static struct filter_node **filter_tail = &filter_head;
+
+void
+filter_add(const char *pattern, bool include)
+{
+ struct filter_node *filter;
+
+ debug(dbg_general, "adding %s filter for '%s'",
+ include ? "include" : "exclude", pattern);
+
+ filter = m_malloc(sizeof(*filter));
+ filter->pattern = m_strdup(pattern);
+ filter->include = include;
+ filter->next = NULL;
+
+ *filter_tail = filter;
+ filter_tail = &filter->next;
+}
+
+bool
+filter_should_skip(struct tar_entry *ti)
+{
+ struct filter_node *f;
+ bool skip = false;
+
+ if (!filter_head)
+ return false;
+
+ /* Last match wins. */
+ for (f = filter_head; f != NULL; f = f->next) {
+ debug(dbg_eachfile, "filter comparing '%s' and '%s'",
+ &ti->name[1], f->pattern);
+
+ if (fnmatch(f->pattern, &ti->name[1], 0) == 0) {
+ if (f->include) {
+ skip = false;
+ debug(dbg_eachfile, "filter including %s",
+ ti->name);
+ } else {
+ skip = true;
+ debug(dbg_eachfile, "filter removing %s",
+ ti->name);
+ }
+ }
+ }
+
+ /* We need to keep directories (or symlinks to directories) if a
+ * glob excludes them, but a more specific include glob brings back
+ * files; XXX the current implementation will probably include more
+ * directories than necessary, but better err on the side of caution
+ * than failing with “no such file or directory” (which would leave
+ * the package in a very bad state). */
+ if (skip && (ti->type == TAR_FILETYPE_DIR ||
+ ti->type == TAR_FILETYPE_SYMLINK)) {
+ debug(dbg_eachfile,
+ "filter seeing if '%s' needs to be reincluded",
+ &ti->name[1]);
+
+ for (f = filter_head; f != NULL; f = f->next) {
+ const char *wildcard;
+ int path_len;
+
+ if (!f->include)
+ continue;
+
+ /* Calculate the offset of the first wildcard
+ * character in the pattern. */
+ wildcard = strpbrk(f->pattern, "*?[\\");
+ if (wildcard)
+ path_len = wildcard - f->pattern;
+ else
+ path_len = strlen(f->pattern);
+
+ /* Ignore any trailing slash for the comparison. */
+ while (path_len && f->pattern[path_len - 1] == '/')
+ path_len--;
+
+ debug(dbg_eachfiledetail,
+ "filter subpattern '%.*s'", path_len, f->pattern);
+
+ if (strncmp(&ti->name[1], f->pattern, path_len) == 0) {
+ debug(dbg_eachfile, "filter reincluding %s",
+ ti->name);
+ return false;
+ }
+ }
+ }
+
+ return skip;
+}
diff --git a/src/filters.h b/src/filters.h
new file mode 100644
index 0000000..b878e7e
--- /dev/null
+++ b/src/filters.h
@@ -0,0 +1,37 @@
+/*
+ * dpkg - main program for package management
+ * filters.h - external definitions for filter handling
+ *
+ * Copyright © 2007, 2008 Tollef Fog Heen <tfheen@err.no>
+ * 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 DPKG_FILTERS_H
+#define DPKG_FILTERS_H
+
+#include <stdbool.h>
+
+#include <dpkg/macros.h>
+#include <dpkg/tarfn.h>
+
+DPKG_BEGIN_DECLS
+
+void filter_add(const char *glob, bool include);
+bool filter_should_skip(struct tar_entry *ti);
+
+DPKG_END_DECLS
+
+#endif
diff --git a/src/force.c b/src/force.c
new file mode 100644
index 0000000..5fa19ee
--- /dev/null
+++ b/src/force.c
@@ -0,0 +1,395 @@
+/*
+ * dpkg - main program for package management
+ * force.c - force operation support
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2006-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 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 <stdbool.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/macros.h>
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/options.h>
+
+#include "force.h"
+
+static int force_mask;
+static int force_flags;
+
+enum forcetype {
+ FORCETYPE_DISABLED,
+ FORCETYPE_ENABLED,
+ FORCETYPE_DAMAGE,
+};
+
+static const char *
+forcetype_str(enum forcetype type)
+{
+ switch (type) {
+ case FORCETYPE_DISABLED:
+ return " ";
+ case FORCETYPE_ENABLED:
+ return "[*]";
+ case FORCETYPE_DAMAGE:
+ return "[!]";
+ default:
+ internerr("unknown force type '%d'", type);
+ }
+}
+
+static const struct forceinfo {
+ const char *name;
+ int flag;
+ char type;
+ const char *desc;
+} forceinfos[] = {
+ {
+ "all",
+ FORCE_ALL,
+ FORCETYPE_DAMAGE,
+ N_("Set all force options"),
+ }, {
+ "security-mac",
+ FORCE_SECURITY_MAC,
+ FORCETYPE_ENABLED,
+ N_("Use MAC based security if available"),
+ }, {
+ "downgrade",
+ FORCE_DOWNGRADE,
+ FORCETYPE_ENABLED,
+ N_("Replace a package with a lower version"),
+ }, {
+ "configure-any",
+ FORCE_CONFIGURE_ANY,
+ FORCETYPE_DISABLED,
+ N_("Configure any package which may help this one"),
+ }, {
+ "hold",
+ FORCE_HOLD,
+ FORCETYPE_DISABLED,
+ N_("Process incidental packages even when on hold"),
+ }, {
+ "not-root",
+ FORCE_NON_ROOT,
+ FORCETYPE_DISABLED,
+ N_("Try to (de)install things even when not root"),
+ }, {
+ "bad-path",
+ FORCE_BAD_PATH,
+ FORCETYPE_DISABLED,
+ N_("PATH is missing important programs, problems likely"),
+ }, {
+ "bad-verify",
+ FORCE_BAD_VERIFY,
+ FORCETYPE_DISABLED,
+ N_("Install a package even if it fails authenticity check"),
+ }, {
+ "bad-version",
+ FORCE_BAD_VERSION,
+ FORCETYPE_DISABLED,
+ N_("Process even packages with wrong versions"),
+ }, {
+ "statoverride-add",
+ FORCE_STATOVERRIDE_ADD,
+ FORCETYPE_DISABLED,
+ N_("Overwrite an existing stat override when adding it"),
+ }, {
+ "statoverride-remove",
+ FORCE_STATOVERRIDE_DEL,
+ FORCETYPE_DISABLED,
+ N_("Ignore a missing stat override when removing it"),
+ }, {
+ "overwrite",
+ FORCE_OVERWRITE,
+ FORCETYPE_DISABLED,
+ N_("Overwrite a file from one package with another"),
+ }, {
+ "overwrite-diverted",
+ FORCE_OVERWRITE_DIVERTED,
+ FORCETYPE_DISABLED,
+ N_("Overwrite a diverted file with an undiverted version"),
+ }, {
+ "overwrite-dir",
+ FORCE_OVERWRITE_DIR,
+ FORCETYPE_DAMAGE,
+ N_("Overwrite one package's directory with another's file"),
+ }, {
+ "unsafe-io",
+ FORCE_UNSAFE_IO,
+ FORCETYPE_DAMAGE,
+ N_("Do not perform safe I/O operations when unpacking"),
+ }, {
+ "script-chrootless",
+ FORCE_SCRIPT_CHROOTLESS,
+ FORCETYPE_DAMAGE,
+ N_("Do not chroot into maintainer script environment"),
+ }, {
+ "confnew",
+ FORCE_CONFF_NEW,
+ FORCETYPE_DAMAGE,
+ N_("Always use the new config files, don't prompt"),
+ }, {
+ "confold",
+ FORCE_CONFF_OLD,
+ FORCETYPE_DAMAGE,
+ N_("Always use the old config files, don't prompt"),
+ }, {
+ "confdef",
+ FORCE_CONFF_DEF,
+ FORCETYPE_DAMAGE,
+ N_("Use the default option for new config files if one\n"
+ "is available, don't prompt. If no default can be found,\n"
+ "you will be prompted unless one of the confold or\n"
+ "confnew options is also given"),
+ }, {
+ "confmiss",
+ FORCE_CONFF_MISS,
+ FORCETYPE_DAMAGE,
+ N_("Always install missing config files"),
+ }, {
+ "confask",
+ FORCE_CONFF_ASK,
+ FORCETYPE_DAMAGE,
+ N_("Offer to replace config files with no new versions"),
+ }, {
+ "architecture",
+ FORCE_ARCHITECTURE,
+ FORCETYPE_DAMAGE,
+ N_("Process even packages with wrong or no architecture"),
+ }, {
+ "breaks",
+ FORCE_BREAKS,
+ FORCETYPE_DAMAGE,
+ N_("Install even if it would break another package"),
+ }, {
+ "conflicts",
+ FORCE_CONFLICTS,
+ FORCETYPE_DAMAGE,
+ N_("Allow installation of conflicting packages"),
+ }, {
+ "depends",
+ FORCE_DEPENDS,
+ FORCETYPE_DAMAGE,
+ N_("Turn all dependency problems into warnings"),
+ }, {
+ "depends-version",
+ FORCE_DEPENDS_VERSION,
+ FORCETYPE_DAMAGE,
+ N_("Turn dependency version problems into warnings"),
+ }, {
+ "remove-reinstreq",
+ FORCE_REMOVE_REINSTREQ,
+ FORCETYPE_DAMAGE,
+ N_("Remove packages which require installation"),
+ }, {
+ "remove-essential",
+ FORCE_REMOVE_ESSENTIAL,
+ FORCETYPE_DAMAGE,
+ N_("Remove an essential package"),
+ }, {
+ NULL
+ }
+};
+
+bool
+in_force(int flags)
+{
+ return (flags & force_flags) == flags;
+}
+
+void
+set_force(int flags)
+{
+ force_flags |= flags;
+}
+
+void
+reset_force(int flags)
+{
+ force_flags &= ~flags;
+}
+
+char *
+get_force_string(void)
+{
+ const struct forceinfo *fip;
+ struct varbuf vb = VARBUF_INIT;
+
+ for (fip = forceinfos; fip->name; fip++) {
+ if ((enum force_flags)fip->flag == FORCE_ALL ||
+ (fip->flag & force_mask) != fip->flag ||
+ !in_force(fip->flag))
+ continue;
+
+ if (vb.used)
+ varbuf_add_char(&vb, ',');
+ varbuf_add_str(&vb, fip->name);
+ }
+ varbuf_end_str(&vb);
+
+ return varbuf_detach(&vb);
+}
+
+static inline void
+print_forceinfo_line(int type, const char *name, const char *desc)
+{
+ printf(" %s %-18s %s\n", forcetype_str(type), name, desc);
+}
+
+static void
+print_forceinfo(const struct forceinfo *fi)
+{
+ char *desc, *line;
+
+ desc = m_strdup(gettext(fi->desc));
+
+ line = strtok(desc, "\n");
+ print_forceinfo_line(fi->type, fi->name, line);
+ while ((line = strtok(NULL, "\n")))
+ print_forceinfo_line(FORCETYPE_DISABLED, "", line);
+
+ free(desc);
+}
+
+void
+parse_force(const char *value, bool set)
+{
+ const char *comma;
+ size_t l;
+ const struct forceinfo *fip;
+
+ if (strcmp(value, "help") == 0) {
+ printf(_(
+"%s forcing options - control behaviour when problems found:\n"
+" warn but continue: --force-<thing>,<thing>,...\n"
+" stop with error: --refuse-<thing>,<thing>,... | --no-force-<thing>,...\n"
+" Forcing things:\n"), dpkg_get_progname());
+
+ for (fip = forceinfos; fip->name; fip++)
+ if ((enum force_flags)fip->flag == FORCE_ALL ||
+ (fip->flag & force_mask) == fip->flag)
+ print_forceinfo(fip);
+
+ printf(_(
+"\n"
+"WARNING - use of options marked [!] can seriously damage your installation.\n"
+"Forcing options marked [*] are enabled by default.\n"));
+ m_output(stdout, _("<standard output>"));
+
+ printf(_(
+"\n"
+"Currently enabled options:\n"
+" %s\n"), get_force_string());
+
+ exit(0);
+ }
+
+ for (;;) {
+ comma = strchrnul(value, ',');
+ l = (size_t)(comma - value);
+ for (fip = forceinfos; fip->name; fip++)
+ if (strncmp(fip->name, value, l) == 0 &&
+ strlen(fip->name) == l)
+ break;
+
+ if (!fip->name) {
+ badusage(_("unknown force/refuse option '%.*s'"),
+ (int)min(l, 250), value);
+ } else if (fip->flag) {
+ if (set)
+ set_force(fip->flag);
+ else
+ reset_force(fip->flag);
+ } else {
+ warning(_("obsolete force/refuse option '%s'"),
+ fip->name);
+ }
+
+ if (*comma == '\0')
+ break;
+ value = ++comma;
+ }
+}
+
+void
+set_force_default(int mask)
+{
+ const char *force_env;
+ const struct forceinfo *fip;
+
+ force_mask = mask;
+
+ /* If we get passed force options from the environment, do not
+ * initialize from the built-in defaults. */
+ force_env = getenv("DPKG_FORCE");
+ if (force_env != NULL) {
+ if (force_env[0] != '\0')
+ parse_force(force_env, 1);
+ return;
+ }
+
+ for (fip = forceinfos; fip->name; fip++)
+ if (fip->type == FORCETYPE_ENABLED)
+ set_force(fip->flag);
+}
+
+void
+set_force_option(const struct cmdinfo *cip, const char *value)
+{
+ bool set = cip->arg_int;
+
+ parse_force(value, set);
+}
+
+void
+reset_force_option(const struct cmdinfo *cip, const char *value)
+{
+ reset_force(cip->arg_int);
+}
+
+void
+forcibleerr(int forceflag, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ if (in_force(forceflag)) {
+ warning(_("overriding problem because --force enabled:"));
+ warningv(fmt, args);
+ } else {
+ ohshitv(fmt, args);
+ }
+ va_end(args);
+}
+
+int
+forcible_nonroot_error(int rc)
+{
+ if (in_force(FORCE_NON_ROOT) && errno == EPERM)
+ return 0;
+ return rc;
+}
diff --git a/src/force.h b/src/force.h
new file mode 100644
index 0000000..986e7fa
--- /dev/null
+++ b/src/force.h
@@ -0,0 +1,84 @@
+/*
+ * dpkg - main program for package management
+ * force.h - forced operation support
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2006, 2008-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 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_FORCE_H
+#define DPKG_FORCE_H
+
+#include <dpkg/dpkg.h>
+#include <dpkg/options.h>
+
+enum force_flags {
+ FORCE_ARCHITECTURE = DPKG_BIT(0),
+ FORCE_BAD_PATH = DPKG_BIT(1),
+ FORCE_BAD_VERIFY = DPKG_BIT(2),
+ FORCE_BAD_VERSION = DPKG_BIT(3),
+ FORCE_BREAKS = DPKG_BIT(4),
+ FORCE_CONFF_ASK = DPKG_BIT(5),
+ FORCE_CONFF_DEF = DPKG_BIT(6),
+ FORCE_CONFF_MISS = DPKG_BIT(7),
+ FORCE_CONFF_NEW = DPKG_BIT(8),
+ FORCE_CONFF_OLD = DPKG_BIT(9),
+ FORCE_CONFIGURE_ANY = DPKG_BIT(10),
+ FORCE_CONFLICTS = DPKG_BIT(11),
+ FORCE_DEPENDS = DPKG_BIT(12),
+ FORCE_DEPENDS_VERSION = DPKG_BIT(13),
+ FORCE_DOWNGRADE = DPKG_BIT(14),
+ FORCE_HOLD = DPKG_BIT(15),
+ FORCE_NON_ROOT = DPKG_BIT(16),
+ FORCE_OVERWRITE = DPKG_BIT(17),
+ FORCE_OVERWRITE_DIR = DPKG_BIT(18),
+ FORCE_OVERWRITE_DIVERTED = DPKG_BIT(19),
+ FORCE_REMOVE_ESSENTIAL = DPKG_BIT(20),
+ FORCE_REMOVE_REINSTREQ = DPKG_BIT(21),
+ FORCE_SCRIPT_CHROOTLESS = DPKG_BIT(22),
+ FORCE_UNSAFE_IO = DPKG_BIT(23),
+ FORCE_STATOVERRIDE_ADD = DPKG_BIT(24),
+ FORCE_STATOVERRIDE_DEL = DPKG_BIT(25),
+ FORCE_SECURITY_MAC = DPKG_BIT(26),
+ FORCE_ALL = 0xffffffff,
+};
+
+bool
+in_force(int flags);
+void
+set_force(int flags);
+void
+reset_force(int flags);
+
+char *
+get_force_string(void);
+
+void
+parse_force(const char *value, bool set);
+
+void
+set_force_default(int mask);
+void
+set_force_option(const struct cmdinfo *cip, const char *value);
+void
+reset_force_option(const struct cmdinfo *cip, const char *value);
+
+void
+forcibleerr(int forceflag, const char *format, ...) DPKG_ATTR_PRINTF(2);
+int
+forcible_nonroot_error(int rc);
+
+#endif /* DPKG_FORCE_H */
diff --git a/src/help.c b/src/help.c
new file mode 100644
index 0000000..382b363
--- /dev/null
+++ b/src/help.c
@@ -0,0 +1,356 @@
+/*
+ * dpkg - main program for package management
+ * help.c - various helper routines
+ *
+ * Copyright © 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/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/path.h>
+#include <dpkg/db-fsys.h>
+
+#include "main.h"
+
+const char *const statusstrings[]= {
+ [PKG_STAT_NOTINSTALLED] = N_("not installed"),
+ [PKG_STAT_CONFIGFILES] = N_("not installed but configs remain"),
+ [PKG_STAT_HALFINSTALLED] = N_("broken due to failed removal or installation"),
+ [PKG_STAT_UNPACKED] = N_("unpacked but not configured"),
+ [PKG_STAT_HALFCONFIGURED] = N_("broken due to postinst failure"),
+ [PKG_STAT_TRIGGERSAWAITED] = N_("awaiting trigger processing by another package"),
+ [PKG_STAT_TRIGGERSPENDING] = N_("triggered"),
+ [PKG_STAT_INSTALLED] = N_("installed")
+};
+
+struct fsys_namenode *
+namenodetouse(struct fsys_namenode *namenode, struct pkginfo *pkg,
+ struct pkgbin *pkgbin)
+{
+ struct fsys_namenode *fnn;
+
+ if (!namenode->divert)
+ return namenode;
+
+ debug(dbg_eachfile, "namenodetouse namenode='%s' pkg=%s",
+ namenode->name, pkgbin_name(pkg, pkgbin, pnaw_always));
+
+ fnn = (namenode->divert->useinstead && namenode->divert->pkgset != pkg->set)
+ ? namenode->divert->useinstead : namenode;
+
+ debug(dbg_eachfile,
+ "namenodetouse ... useinstead=%s camefrom=%s pkg=%s return %s",
+ namenode->divert->useinstead ? namenode->divert->useinstead->name : "<none>",
+ namenode->divert->camefrom ? namenode->divert->camefrom->name : "<none>",
+ namenode->divert->pkgset ? namenode->divert->pkgset->name : "<none>",
+ fnn->name);
+
+ return fnn;
+}
+
+bool
+find_command(const char *prog)
+{
+ struct varbuf filename = VARBUF_INIT;
+ struct stat stab;
+ const char *path_list;
+ const char *path, *path_end;
+ size_t path_len;
+
+ path_list = getenv("PATH");
+ if (!path_list)
+ ohshit(_("PATH is not set"));
+
+ for (path = path_list; path; path = *path_end ? path_end + 1 : NULL) {
+ path_end = strchrnul(path, ':');
+ path_len = (size_t)(path_end - path);
+
+ varbuf_reset(&filename);
+ varbuf_add_buf(&filename, path, path_len);
+ if (path_len)
+ varbuf_add_char(&filename, '/');
+ varbuf_add_str(&filename, prog);
+ varbuf_end_str(&filename);
+
+ if (stat(filename.buf, &stab) == 0 && (stab.st_mode & 0111)) {
+ varbuf_destroy(&filename);
+ return true;
+ }
+ }
+
+ varbuf_destroy(&filename);
+ return false;
+}
+
+/**
+ * Verify that some programs can be found in the PATH.
+ */
+void checkpath(void) {
+ static const char *const prog_list[] = {
+ DEFAULTSHELL,
+ RM,
+ TAR,
+ DIFF,
+ BACKEND,
+ /* Mac OS X uses dyld (Mach-O) instead of ld.so (ELF), and does not have
+ * an ldconfig. */
+#if defined(__APPLE__) && defined(__MACH__)
+ "update_dyld_shared_cache",
+#elif defined(__GLIBC__) || defined(__UCLIBC__) || \
+ defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
+ "ldconfig",
+#endif
+#if BUILD_START_STOP_DAEMON
+ "start-stop-daemon",
+#endif
+ NULL
+ };
+
+ const char *const *prog;
+ int warned= 0;
+
+ for (prog = prog_list; *prog; prog++) {
+ if (!find_command(*prog)) {
+ warning(_("'%s' not found in PATH or not executable"), *prog);
+ warned++;
+ }
+ }
+
+ if (warned)
+ forcibleerr(FORCE_BAD_PATH,
+ P_("%d expected program not found in PATH or not executable\n%s",
+ "%d expected programs not found in PATH or not executable\n%s",
+ warned),
+ warned, _("Note: root's PATH should usually contain "
+ "/usr/local/sbin, /usr/sbin and /sbin"));
+}
+
+bool
+ignore_depends(struct pkginfo *pkg)
+{
+ struct pkg_list *id;
+ for (id= ignoredependss; id; id= id->next)
+ if (id->pkg == pkg)
+ return true;
+ return false;
+}
+
+static bool
+ignore_depends_possi(struct deppossi *possi)
+{
+ struct deppossi_pkg_iterator *possi_iter;
+ struct pkginfo *pkg;
+
+ possi_iter = deppossi_pkg_iter_new(possi, wpb_installed);
+ while ((pkg = deppossi_pkg_iter_next(possi_iter))) {
+ if (ignore_depends(pkg)) {
+ deppossi_pkg_iter_free(possi_iter);
+ return true;
+ }
+ }
+ deppossi_pkg_iter_free(possi_iter);
+
+ return false;
+}
+
+bool
+force_depends(struct deppossi *possi)
+{
+ return in_force(FORCE_DEPENDS) ||
+ ignore_depends_possi(possi) ||
+ ignore_depends(possi->up->up);
+}
+
+bool
+force_breaks(struct deppossi *possi)
+{
+ return in_force(FORCE_BREAKS) ||
+ ignore_depends_possi(possi) ||
+ ignore_depends(possi->up->up);
+}
+
+bool
+force_conflicts(struct deppossi *possi)
+{
+ return in_force(FORCE_CONFLICTS);
+}
+
+void clear_istobes(void) {
+ struct pkg_hash_iter *iter;
+ struct pkginfo *pkg;
+
+ iter = pkg_hash_iter_new();
+ while ((pkg = pkg_hash_iter_next_pkg(iter)) != NULL) {
+ ensure_package_clientdata(pkg);
+ pkg->clientdata->istobe = PKG_ISTOBE_NORMAL;
+ pkg->clientdata->replacingfilesandsaid= 0;
+ }
+ pkg_hash_iter_free(iter);
+}
+
+/*
+ * Returns true if the directory contains conffiles belonging to pkg,
+ * false otherwise.
+ */
+bool
+dir_has_conffiles(struct fsys_namenode *file, struct pkginfo *pkg)
+{
+ struct conffile *conff;
+ size_t namelen;
+
+ debug(dbg_veryverbose, "dir_has_conffiles '%s' (from %s)", file->name,
+ pkg_name(pkg, pnaw_always));
+ namelen = strlen(file->name);
+ for (conff= pkg->installed.conffiles; conff; conff= conff->next) {
+ if (conff->obsolete)
+ continue;
+ if (strncmp(file->name, conff->name, namelen) == 0 &&
+ strlen(conff->name) > namelen && conff->name[namelen] == '/') {
+ debug(dbg_veryverbose, "directory %s has conffile %s from %s",
+ file->name, conff->name, pkg_name(pkg, pnaw_always));
+ return true;
+ }
+ }
+ debug(dbg_veryverbose, "dir_has_conffiles no");
+ return false;
+}
+
+/*
+ * Returns true if the file is used by packages other than pkg,
+ * false otherwise.
+ */
+bool
+dir_is_used_by_others(struct fsys_namenode *file, struct pkginfo *pkg)
+{
+ struct fsys_node_pkgs_iter *iter;
+ struct pkginfo *other_pkg;
+
+ debug(dbg_veryverbose, "dir_is_used_by_others '%s' (except %s)", file->name,
+ pkg ? pkg_name(pkg, pnaw_always) : "<none>");
+
+ iter = fsys_node_pkgs_iter_new(file);
+ while ((other_pkg = fsys_node_pkgs_iter_next(iter))) {
+ debug(dbg_veryverbose, "dir_is_used_by_others considering %s ...",
+ pkg_name(other_pkg, pnaw_always));
+ if (other_pkg == pkg)
+ continue;
+
+ fsys_node_pkgs_iter_free(iter);
+ debug(dbg_veryverbose, "dir_is_used_by_others yes");
+ return true;
+ }
+ fsys_node_pkgs_iter_free(iter);
+
+ debug(dbg_veryverbose, "dir_is_used_by_others no");
+ return false;
+}
+
+/*
+ * Returns true if the file is used by pkg, false otherwise.
+ */
+bool
+dir_is_used_by_pkg(struct fsys_namenode *file, struct pkginfo *pkg,
+ struct fsys_namenode_list *list)
+{
+ struct fsys_namenode_list *node;
+ size_t namelen;
+
+ debug(dbg_veryverbose, "dir_is_used_by_pkg '%s' (by %s)",
+ file->name, pkg ? pkg_name(pkg, pnaw_always) : "<none>");
+
+ namelen = strlen(file->name);
+
+ for (node = list; node; node = node->next) {
+ debug(dbg_veryverbose, "dir_is_used_by_pkg considering %s ...",
+ node->namenode->name);
+
+ if (strncmp(file->name, node->namenode->name, namelen) == 0 &&
+ strlen(node->namenode->name) > namelen &&
+ node->namenode->name[namelen] == '/') {
+ debug(dbg_veryverbose, "dir_is_used_by_pkg yes");
+ return true;
+ }
+ }
+
+ debug(dbg_veryverbose, "dir_is_used_by_pkg no");
+
+ return false;
+}
+
+/**
+ * Mark a conffile as obsolete.
+ *
+ * @param pkg The package owning the conffile.
+ * @param namenode The namenode for the obsolete conffile.
+ */
+void
+conffile_mark_obsolete(struct pkginfo *pkg, struct fsys_namenode *namenode)
+{
+ struct conffile *conff;
+
+ for (conff = pkg->installed.conffiles; conff; conff = conff->next) {
+ if (strcmp(conff->name, namenode->name) == 0) {
+ debug(dbg_conff, "marking %s conffile %s as obsolete",
+ pkg_name(pkg, pnaw_always), conff->name);
+ conff->obsolete = true;
+ return;
+ }
+ }
+}
+
+/**
+ * Mark all package conffiles as old.
+ *
+ * @param pkg The package owning the conffiles.
+ */
+void
+pkg_conffiles_mark_old(struct pkginfo *pkg)
+{
+ const struct conffile *conff;
+ struct fsys_namenode *namenode;
+
+ for (conff = pkg->installed.conffiles; conff; conff = conff->next) {
+ namenode = fsys_hash_find_node(conff->name, 0); /* XXX */
+ namenode->flags |= FNNF_OLD_CONFF;
+ if (!namenode->oldhash)
+ namenode->oldhash = conff->hash;
+ debug(dbg_conffdetail, "%s '%s' namenode '%s' flags %o", __func__,
+ conff->name, namenode->name, namenode->flags);
+ }
+}
+
+void
+log_action(const char *action, struct pkginfo *pkg, struct pkgbin *pkgbin)
+{
+ log_message("%s %s %s %s", action, pkgbin_name(pkg, pkgbin, pnaw_always),
+ versiondescribe(&pkg->installed.version, vdew_nonambig),
+ versiondescribe(&pkg->available.version, vdew_nonambig));
+ statusfd_send("processing: %s: %s", action,
+ pkgbin_name(pkg, pkgbin, pnaw_nonambig));
+}
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..2bb0a22
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,788 @@
+/*
+ * dpkg - main program for package management
+ * main.c - main program
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2006-2016 Guillem Jover <guillem@debian.org>
+ * Copyright © 2010 Canonical Ltd.
+ * written by Martin Pitt <martin.pitt@canonical.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/wait.h>
+
+#include <errno.h>
+#include <limits.h>
+#if HAVE_LOCALE_H
+#include <locale.h>
+#endif
+#include <string.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <stdbool.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/arch.h>
+#include <dpkg/subproc.h>
+#include <dpkg/command.h>
+#include <dpkg/pager.h>
+#include <dpkg/options.h>
+#include <dpkg/db-fsys.h>
+
+#include "main.h"
+#include "filters.h"
+
+static void DPKG_ATTR_NORET
+printversion(const struct cmdinfo *ci, const char *value)
+{
+ printf(_("Debian '%s' package management program version %s.\n"),
+ DPKG, PACKAGE_RELEASE);
+ printf(_(
+"This is free software; see the GNU General Public License version 2 or\n"
+"later for copying conditions. There is NO warranty.\n"));
+
+ m_output(stdout, _("<standard output>"));
+
+ exit(0);
+}
+
+/*
+ * FIXME: Options that need fixing:
+ * dpkg --command-fd
+ */
+
+static void DPKG_ATTR_NORET
+usage(const struct cmdinfo *ci, const char *value)
+{
+ printf(_(
+"Usage: %s [<option> ...] <command>\n"
+"\n"), DPKG);
+
+ printf(_(
+"Commands:\n"
+" -i|--install <.deb file name>... | -R|--recursive <directory>...\n"
+" --unpack <.deb file name>... | -R|--recursive <directory>...\n"
+" -A|--record-avail <.deb file name>... | -R|--recursive <directory>...\n"
+" --configure <package>... | -a|--pending\n"
+" --triggers-only <package>... | -a|--pending\n"
+" -r|--remove <package>... | -a|--pending\n"
+" -P|--purge <package>... | -a|--pending\n"
+" -V|--verify [<package>...] Verify the integrity of package(s).\n"
+" --get-selections [<pattern>...] Get list of selections to stdout.\n"
+" --set-selections Set package selections from stdin.\n"
+" --clear-selections Deselect every non-essential package.\n"
+" --update-avail [<Packages-file>] Replace available packages info.\n"
+" --merge-avail [<Packages-file>] Merge with info from file.\n"
+" --clear-avail Erase existing available info.\n"
+" --forget-old-unavail Forget uninstalled unavailable pkgs.\n"
+" -s|--status [<package>...] Display package status details.\n"
+" -p|--print-avail [<package>...] Display available version details.\n"
+" -L|--listfiles <package>... List files 'owned' by package(s).\n"
+" -l|--list [<pattern>...] List packages concisely.\n"
+" -S|--search <pattern>... Find package(s) owning file(s).\n"
+" -C|--audit [<package>...] Check for broken package(s).\n"
+" --yet-to-unpack Print packages selected for installation.\n"
+" --predep-package Print pre-dependencies to unpack.\n"
+" --add-architecture <arch> Add <arch> to the list of architectures.\n"
+" --remove-architecture <arch> Remove <arch> from the list of architectures.\n"
+" --print-architecture Print dpkg architecture.\n"
+" --print-foreign-architectures Print allowed foreign architectures.\n"
+" --assert-<feature> Assert support for the specified feature.\n"
+" --validate-<thing> <string> Validate a <thing>'s <string>.\n"
+" --compare-versions <a> <op> <b> Compare version numbers - see below.\n"
+" --force-help Show help on forcing.\n"
+" -Dh|--debug=help Show help on debugging.\n"
+"\n"));
+
+ printf(_(
+" -?, --help Show this help message.\n"
+" --version Show the version.\n"
+"\n"));
+
+ printf(_(
+"Assertable features: support-predepends, working-epoch, long-filenames,\n"
+" multi-conrep, multi-arch, versioned-provides.\n"
+"\n"));
+
+ printf(_(
+"Validatable things: pkgname, archname, trigname, version.\n"
+"\n"));
+
+ printf(_(
+"Use dpkg with -b, --build, -c, --contents, -e, --control, -I, --info,\n"
+" -f, --field, -x, --extract, -X, --vextract, --ctrl-tarfile, --fsys-tarfile\n"
+"on archives (type %s --help).\n"
+"\n"), BACKEND);
+
+ printf(_(
+"Options:\n"
+" --admindir=<directory> Use <directory> instead of %s.\n"
+" --root=<directory> Install on a different root directory.\n"
+" --instdir=<directory> Change installation dir without changing admin dir.\n"
+" --path-exclude=<pattern> Do not install paths which match a shell pattern.\n"
+" --path-include=<pattern> Re-include a pattern after a previous exclusion.\n"
+" -O|--selected-only Skip packages not selected for install/upgrade.\n"
+" -E|--skip-same-version Skip packages whose same version is installed.\n"
+" -G|--refuse-downgrade Skip packages with earlier version than installed.\n"
+" -B|--auto-deconfigure Install even if it would break some other package.\n"
+" --[no-]triggers Skip or force consequential trigger processing.\n"
+" --verify-format=<format> Verify output format (supported: 'rpm').\n"
+" --no-debsig Do not try to verify package signatures.\n"
+" --no-act|--dry-run|--simulate\n"
+" Just say what we would do - don't do it.\n"
+" -D|--debug=<octal> Enable debugging (see -Dhelp or --debug=help).\n"
+" --status-fd <n> Send status change updates to file descriptor <n>.\n"
+" --status-logger=<command> Send status change updates to <command>'s stdin.\n"
+" --log=<filename> Log status changes and actions to <filename>.\n"
+" --ignore-depends=<package>,...\n"
+" Ignore dependencies involving <package>.\n"
+" --force-... Override problems (see --force-help).\n"
+" --no-force-...|--refuse-...\n"
+" Stop when problems encountered.\n"
+" --abort-after <n> Abort after encountering <n> errors.\n"
+"\n"), ADMINDIR);
+
+ printf(_(
+"Comparison operators for --compare-versions are:\n"
+" lt le eq ne ge gt (treat empty version as earlier than any version);\n"
+" lt-nl le-nl ge-nl gt-nl (treat empty version as later than any version);\n"
+" < << <= = >= >> > (only for compatibility with control file syntax).\n"
+"\n"));
+
+ printf(_(
+"Use 'apt' or 'aptitude' for user-friendly package management.\n"));
+
+ m_output(stdout, _("<standard output>"));
+
+ exit(0);
+}
+
+static const char printforhelp[] = N_(
+"Type dpkg --help for help about installing and deinstalling packages [*];\n"
+"Use 'apt' or 'aptitude' for user-friendly package management;\n"
+"Type dpkg -Dhelp for a list of dpkg debug flag values;\n"
+"Type dpkg --force-help for a list of forcing options;\n"
+"Type dpkg-deb --help for help about manipulating *.deb files;\n"
+"\n"
+"Options marked [*] produce a lot of output - pipe it through 'less' or 'more' !");
+
+int f_pending=0, f_recursive=0, f_alsoselect=1, f_skipsame=0, f_noact=0;
+int f_autodeconf=0, f_nodebsig=0;
+int f_triggers = 0;
+
+int errabort = 50;
+static const char *admindir = ADMINDIR;
+const char *instdir= "";
+struct pkg_list *ignoredependss = NULL;
+
+#define DBG_DEF(n, d) \
+ { .flag = dbg_##n, .name = #n, .desc = d }
+
+static const struct debuginfo {
+ int flag;
+ const char *name;
+ const char *desc;
+} debuginfos[] = {
+ DBG_DEF(general, N_("Generally helpful progress information")),
+ DBG_DEF(scripts, N_("Invocation and status of maintainer scripts")),
+ DBG_DEF(eachfile, N_("Output for each file processed")),
+ DBG_DEF(eachfiledetail, N_("Lots of output for each file processed")),
+ DBG_DEF(conff, N_("Output for each configuration file")),
+ DBG_DEF(conffdetail, N_("Lots of output for each configuration file")),
+ DBG_DEF(depcon, N_("Dependencies and conflicts")),
+ DBG_DEF(depcondetail, N_("Lots of dependencies/conflicts output")),
+ DBG_DEF(triggers, N_("Trigger activation and processing")),
+ DBG_DEF(triggersdetail, N_("Lots of output regarding triggers")),
+ DBG_DEF(triggersstupid, N_("Silly amounts of output regarding triggers")),
+ DBG_DEF(veryverbose, N_("Lots of drivel about eg the dpkg/info directory")),
+ DBG_DEF(stupidlyverbose, N_("Insane amounts of drivel")),
+ { 0, NULL, NULL }
+};
+
+static void
+set_debug(const struct cmdinfo *cpi, const char *value)
+{
+ char *endp;
+ long mask;
+ const struct debuginfo *dip;
+
+ if (*value == 'h') {
+ printf(_(
+"%s debugging option, --debug=<octal> or -D<octal>:\n"
+"\n"
+" Number Ref. in source Description\n"), DPKG);
+
+ for (dip = debuginfos; dip->name; dip++)
+ printf(" %6o %-16s %s\n", dip->flag, dip->name, gettext(dip->desc));
+
+ printf(_("\n"
+"Debugging options can be mixed using bitwise-or.\n"
+"Note that the meanings and values are subject to change.\n"));
+ m_output(stdout, _("<standard output>"));
+ exit(0);
+ }
+
+ errno = 0;
+ mask = strtol(value, &endp, 8);
+ if (value == endp || *endp || mask < 0 || errno == ERANGE)
+ badusage(_("--%s requires a positive octal argument"), cpi->olong);
+
+ debug_set_mask(mask);
+}
+
+static void
+set_no_pager(const struct cmdinfo *ci, const char *value)
+{
+ pager_enable(false);
+
+ /* Let's communicate this to our backends. */
+ setenv("DPKG_PAGER", "cat", 1);
+}
+
+static void
+set_filter(const struct cmdinfo *cip, const char *value)
+{
+ filter_add(value, cip->arg_int);
+}
+
+static void
+set_verify_format(const struct cmdinfo *cip, const char *value)
+{
+ if (!verify_set_output(value))
+ badusage(_("unknown verify output format '%s'"), value);
+}
+
+static void
+set_instdir(const struct cmdinfo *cip, const char *value)
+{
+ instdir = dpkg_fsys_set_dir(value);
+}
+
+static void
+set_root(const struct cmdinfo *cip, const char *value)
+{
+ instdir = dpkg_fsys_set_dir(value);
+ admindir = dpkg_fsys_get_path(ADMINDIR);
+}
+
+static void
+set_ignore_depends(const struct cmdinfo *cip, const char *value)
+{
+ char *copy, *p;
+
+ copy= m_malloc(strlen(value)+2);
+ strcpy(copy,value);
+ copy[strlen(value) + 1] = '\0';
+ for (p=copy; *p; p++) {
+ if (*p != ',') continue;
+ *p++ = '\0';
+ if (!*p || *p==',' || p==copy+1)
+ badusage(_("null package name in --%s comma-separated list '%.250s'"),
+ cip->olong, value);
+ }
+ p= copy;
+ while (*p) {
+ struct pkginfo *pkg;
+
+ pkg = dpkg_options_parse_pkgname(cip, p);
+ pkg_list_prepend(&ignoredependss, pkg);
+
+ p+= strlen(p)+1;
+ }
+
+ free(copy);
+}
+
+static void
+set_integer(const struct cmdinfo *cip, const char *value)
+{
+ *cip->iassignto = dpkg_options_parse_arg_int(cip, value);
+}
+
+static void
+set_pipe(const struct cmdinfo *cip, const char *value)
+{
+ long v;
+
+ v = dpkg_options_parse_arg_int(cip, value);
+
+ statusfd_add(v);
+}
+
+static bool
+is_invoke_action(enum action action)
+{
+ switch (action) {
+ case act_unpack:
+ case act_configure:
+ case act_install:
+ case act_triggers:
+ case act_remove:
+ case act_purge:
+ case act_arch_add:
+ case act_arch_remove:
+ return true;
+ default:
+ return false;
+ }
+}
+
+struct invoke_list pre_invoke_hooks = { .head = NULL, .tail = &pre_invoke_hooks.head };
+struct invoke_list post_invoke_hooks = { .head = NULL, .tail = &post_invoke_hooks.head };
+struct invoke_list status_loggers = { .head = NULL, .tail = &status_loggers.head };
+
+static void
+set_invoke_hook(const struct cmdinfo *cip, const char *value)
+{
+ struct invoke_list *hook_list = cip->arg_ptr;
+ struct invoke_hook *hook_new;
+
+ hook_new = m_malloc(sizeof(*hook_new));
+ hook_new->command = m_strdup(value);
+ hook_new->next = NULL;
+
+ /* Add the new hook at the tail of the list to preserve the order. */
+ *hook_list->tail = hook_new;
+ hook_list->tail = &hook_new->next;
+}
+
+static void
+run_invoke_hooks(const char *action, struct invoke_list *hook_list)
+{
+ struct invoke_hook *hook;
+
+ setenv("DPKG_HOOK_ACTION", action, 1);
+
+ for (hook = hook_list->head; hook; hook = hook->next) {
+ int status;
+
+ /* XXX: As an optimization, use exec instead if no shell metachar are
+ * used “!$=&|\\`'"^~;<>{}[]()?*#”. */
+ status = system(hook->command);
+ if (status != 0)
+ ohshit(_("error executing hook '%s', exit code %d"), hook->command,
+ status);
+ }
+
+ unsetenv("DPKG_HOOK_ACTION");
+}
+
+static void
+free_invoke_hooks(struct invoke_list *hook_list)
+{
+ struct invoke_hook *hook, *hook_next;
+
+ for (hook = hook_list->head; hook; hook = hook_next) {
+ hook_next = hook->next;
+ free(hook->command);
+ free(hook);
+ }
+}
+
+static int
+run_logger(struct invoke_hook *hook, const char *name)
+{
+ pid_t pid;
+ int p[2];
+
+ m_pipe(p);
+
+ pid = subproc_fork();
+ if (pid == 0) {
+ /* Setup stdin and stdout. */
+ m_dup2(p[0], 0);
+ close(1);
+
+ close(p[0]);
+ close(p[1]);
+
+ command_shell(hook->command, name);
+ }
+ close(p[0]);
+
+ return p[1];
+}
+
+static void
+run_status_loggers(struct invoke_list *hook_list)
+{
+ struct invoke_hook *hook;
+
+ for (hook = hook_list->head; hook; hook = hook->next) {
+ int fd;
+
+ fd = run_logger(hook, _("status logger"));
+ statusfd_add(fd);
+ }
+}
+
+static int
+arch_add(const char *const *argv)
+{
+ struct dpkg_arch *arch;
+ const char *archname = *argv++;
+
+ if (archname == NULL || *argv)
+ badusage(_("--%s takes exactly one argument"), cipaction->olong);
+
+ dpkg_arch_load_list();
+
+ arch = dpkg_arch_add(archname);
+ switch (arch->type) {
+ case DPKG_ARCH_NATIVE:
+ case DPKG_ARCH_FOREIGN:
+ break;
+ case DPKG_ARCH_ILLEGAL:
+ ohshit(_("architecture '%s' is illegal: %s"), archname,
+ dpkg_arch_name_is_illegal(archname));
+ default:
+ ohshit(_("architecture '%s' is reserved and cannot be added"), archname);
+ }
+
+ dpkg_arch_save_list();
+
+ return 0;
+}
+
+static int
+arch_remove(const char *const *argv)
+{
+ const char *archname = *argv++;
+ struct dpkg_arch *arch;
+ struct pkg_hash_iter *iter;
+ struct pkginfo *pkg;
+
+ if (archname == NULL || *argv)
+ badusage(_("--%s takes exactly one argument"), cipaction->olong);
+
+ modstatdb_open(msdbrw_readonly);
+
+ arch = dpkg_arch_find(archname);
+ if (arch->type != DPKG_ARCH_FOREIGN) {
+ warning(_("cannot remove non-foreign architecture '%s'"), arch->name);
+ return 0;
+ }
+
+ /* Check if it's safe to remove the architecture from the db. */
+ iter = pkg_hash_iter_new();
+ while ((pkg = pkg_hash_iter_next_pkg(iter))) {
+ if (pkg->status < PKG_STAT_HALFINSTALLED)
+ continue;
+ if (pkg->installed.arch == arch) {
+ if (in_force(FORCE_ARCHITECTURE))
+ warning(_("removing architecture '%s' currently in use by database"),
+ arch->name);
+ else
+ ohshit(_("cannot remove architecture '%s' currently in use by the database"),
+ arch->name);
+ break;
+ }
+ }
+ pkg_hash_iter_free(iter);
+
+ dpkg_arch_unmark(arch);
+ dpkg_arch_save_list();
+
+ modstatdb_shutdown();
+
+ return 0;
+}
+
+int execbackend(const char *const *argv) DPKG_ATTR_NORET;
+int commandfd(const char *const *argv);
+
+/* This table has both the action entries in it and the normal options.
+ * The action entries are made with the ACTION macro, as they all
+ * have a very similar structure. */
+static const struct cmdinfo cmdinfos[]= {
+#define ACTIONBACKEND(longopt, shortopt, backend) \
+ { longopt, shortopt, 0, NULL, NULL, setaction, 0, (void *)backend, execbackend }
+
+ ACTION( "install", 'i', act_install, archivefiles ),
+ ACTION( "unpack", 0, act_unpack, archivefiles ),
+ ACTION( "record-avail", 'A', act_avail, archivefiles ),
+ ACTION( "configure", 0, act_configure, packages ),
+ ACTION( "remove", 'r', act_remove, packages ),
+ ACTION( "purge", 'P', act_purge, packages ),
+ ACTION( "triggers-only", 0, act_triggers, packages ),
+ ACTION( "verify", 'V', act_verify, verify ),
+ ACTIONBACKEND( "listfiles", 'L', DPKGQUERY),
+ ACTIONBACKEND( "status", 's', DPKGQUERY),
+ ACTION( "get-selections", 0, act_getselections, getselections ),
+ ACTION( "set-selections", 0, act_setselections, setselections ),
+ ACTION( "clear-selections", 0, act_clearselections, clearselections ),
+ ACTIONBACKEND( "print-avail", 'p', DPKGQUERY),
+ ACTION( "update-avail", 0, act_avreplace, updateavailable ),
+ ACTION( "merge-avail", 0, act_avmerge, updateavailable ),
+ ACTION( "clear-avail", 0, act_avclear, updateavailable ),
+ ACTION( "forget-old-unavail", 0, act_forgetold, forgetold ),
+ ACTION( "audit", 'C', act_audit, audit ),
+ ACTION( "yet-to-unpack", 0, act_unpackchk, unpackchk ),
+ ACTIONBACKEND( "list", 'l', DPKGQUERY),
+ ACTIONBACKEND( "search", 'S', DPKGQUERY),
+ ACTION( "assert-support-predepends", 0, act_assertpredep, assertpredep ),
+ ACTION( "assert-working-epoch", 0, act_assertepoch, assertepoch ),
+ ACTION( "assert-long-filenames", 0, act_assertlongfilenames, assertlongfilenames ),
+ ACTION( "assert-multi-conrep", 0, act_assertmulticonrep, assertmulticonrep ),
+ ACTION( "assert-multi-arch", 0, act_assertmultiarch, assertmultiarch ),
+ ACTION( "assert-versioned-provides", 0, act_assertverprovides, assertverprovides ),
+ ACTION( "add-architecture", 0, act_arch_add, arch_add ),
+ ACTION( "remove-architecture", 0, act_arch_remove, arch_remove ),
+ ACTION( "print-architecture", 0, act_printarch, printarch ),
+ ACTION( "print-foreign-architectures", 0, act_printforeignarches, print_foreign_arches ),
+ ACTION( "predep-package", 0, act_predeppackage, predeppackage ),
+ ACTION( "validate-pkgname", 0, act_validate_pkgname, validate_pkgname ),
+ ACTION( "validate-trigname", 0, act_validate_trigname, validate_trigname ),
+ ACTION( "validate-archname", 0, act_validate_archname, validate_archname ),
+ ACTION( "validate-version", 0, act_validate_version, validate_version ),
+ ACTION( "compare-versions", 0, act_cmpversions, cmpversions ),
+/*
+ ACTION( "command-fd", 'c', act_commandfd, commandfd ),
+*/
+
+ { "pre-invoke", 0, 1, NULL, NULL, set_invoke_hook, 0, &pre_invoke_hooks },
+ { "post-invoke", 0, 1, NULL, NULL, set_invoke_hook, 0, &post_invoke_hooks },
+ { "path-exclude", 0, 1, NULL, NULL, set_filter, 0 },
+ { "path-include", 0, 1, NULL, NULL, set_filter, 1 },
+ { "verify-format", 0, 1, NULL, NULL, set_verify_format },
+ { "status-logger", 0, 1, NULL, NULL, set_invoke_hook, 0, &status_loggers },
+ { "status-fd", 0, 1, NULL, NULL, set_pipe, 0 },
+ { "log", 0, 1, NULL, &log_file, NULL, 0 },
+ { "pending", 'a', 0, &f_pending, NULL, NULL, 1 },
+ { "recursive", 'R', 0, &f_recursive, NULL, NULL, 1 },
+ { "no-act", 0, 0, &f_noact, NULL, NULL, 1 },
+ { "dry-run", 0, 0, &f_noact, NULL, NULL, 1 },
+ { "simulate", 0, 0, &f_noact, NULL, NULL, 1 },
+ { "no-pager", 0, 0, NULL, NULL, set_no_pager, 0 },
+ { "no-debsig", 0, 0, &f_nodebsig, NULL, NULL, 1 },
+ /* Alias ('G') for --refuse. */
+ { NULL, 'G', 0, NULL, NULL, reset_force_option, FORCE_DOWNGRADE },
+ { "selected-only", 'O', 0, &f_alsoselect, NULL, NULL, 0 },
+ { "triggers", 0, 0, &f_triggers, NULL, NULL, 1 },
+ { "no-triggers", 0, 0, &f_triggers, NULL, NULL, -1 },
+ /* FIXME: Remove ('N') sometime. */
+ { "no-also-select", 'N', 0, &f_alsoselect, NULL, NULL, 0 },
+ { "skip-same-version", 'E', 0, &f_skipsame, NULL, NULL, 1 },
+ { "auto-deconfigure", 'B', 0, &f_autodeconf, NULL, NULL, 1 },
+ { "root", 0, 1, NULL, NULL, set_root, 0 },
+ { "abort-after", 0, 1, &errabort, NULL, set_integer, 0 },
+ { "admindir", 0, 1, NULL, &admindir, NULL, 0 },
+ { "instdir", 0, 1, NULL, NULL, set_instdir, 0 },
+ { "ignore-depends", 0, 1, NULL, NULL, set_ignore_depends, 0 },
+ { "force", 0, 2, NULL, NULL, set_force_option, 1 },
+ { "refuse", 0, 2, NULL, NULL, set_force_option, 0 },
+ { "no-force", 0, 2, NULL, NULL, set_force_option, 0 },
+ { "debug", 'D', 1, NULL, NULL, set_debug, 0 },
+ { "help", '?', 0, NULL, NULL, usage, 0 },
+ { "version", 0, 0, NULL, NULL, printversion, 0 },
+ ACTIONBACKEND( "build", 'b', BACKEND),
+ ACTIONBACKEND( "contents", 'c', BACKEND),
+ ACTIONBACKEND( "control", 'e', BACKEND),
+ ACTIONBACKEND( "info", 'I', BACKEND),
+ ACTIONBACKEND( "field", 'f', BACKEND),
+ ACTIONBACKEND( "extract", 'x', BACKEND),
+ ACTIONBACKEND( "vextract", 'X', BACKEND),
+ ACTIONBACKEND( "ctrl-tarfile", 0, BACKEND),
+ ACTIONBACKEND( "fsys-tarfile", 0, BACKEND),
+ { NULL, 0, 0, NULL, NULL, NULL, 0 }
+};
+
+int
+execbackend(const char *const *argv)
+{
+ struct command cmd;
+
+ command_init(&cmd, cipaction->arg_ptr, NULL);
+ command_add_arg(&cmd, cipaction->arg_ptr);
+ command_add_arg(&cmd, str_fmt("--%s", cipaction->olong));
+
+ /* Explicitly separate arguments from options as any user-supplied
+ * separator got stripped by the option parser */
+ command_add_arg(&cmd, "--");
+ command_add_argl(&cmd, (const char **)argv);
+
+ command_exec(&cmd);
+}
+
+int
+commandfd(const char *const *argv)
+{
+ struct varbuf linevb = VARBUF_INIT;
+ const char * pipein;
+ const char **newargs = NULL, **endargs;
+ char *ptr, *endptr;
+ FILE *in;
+ long infd;
+ int ret = 0;
+ int c, lno, i;
+ bool skipchar;
+
+ pipein = *argv++;
+ if (pipein == NULL || *argv)
+ badusage(_("--%s takes exactly one argument"), cipaction->olong);
+
+ infd = dpkg_options_parse_arg_int(cipaction, pipein);
+ in = fdopen(infd, "r");
+ if (in == NULL)
+ ohshite(_("couldn't open '%i' for stream"), (int)infd);
+
+ for (;;) {
+ bool mode = false;
+ int argc= 1;
+ lno= 0;
+
+ push_error_context();
+
+ do {
+ c = getc(in);
+ if (c == '\n')
+ lno++;
+ } while (c != EOF && c_isspace(c));
+ if (c == EOF) break;
+ if (c == '#') {
+ do { c= getc(in); if (c == '\n') lno++; } while (c != EOF && c != '\n');
+ continue;
+ }
+ varbuf_reset(&linevb);
+ do {
+ varbuf_add_char(&linevb, c);
+ c= getc(in);
+ if (c == '\n') lno++;
+
+ /* This isn't fully accurate, but overestimating can't hurt. */
+ if (c_isspace(c))
+ argc++;
+ } while (c != EOF && c != '\n');
+ if (c == EOF)
+ ohshit(_("unexpected end of file before end of line %d"), lno);
+ if (!argc) continue;
+ varbuf_end_str(&linevb);
+ newargs = m_realloc(newargs, sizeof(const char *) * (argc + 1));
+ argc= 1;
+ ptr= linevb.buf;
+ endptr = ptr + linevb.used + 1;
+ skipchar = false;
+ while(ptr < endptr) {
+ if (skipchar) {
+ skipchar = false;
+ } else if (*ptr == '\\') {
+ memmove(ptr, (ptr+1), (linevb.used-(linevb.buf - ptr)-1));
+ endptr--;
+ skipchar = true;
+ continue;
+ } else if (c_isspace(*ptr)) {
+ if (mode == true) {
+ *ptr = '\0';
+ mode = false;
+ }
+ } else {
+ if (mode == false) {
+ newargs[argc]= ptr;
+ argc++;
+ mode = true;
+ }
+ }
+ ptr++;
+ }
+ *ptr = '\0';
+ newargs[argc++] = NULL;
+
+ /* We strdup() each argument, but never free it, because the
+ * error messages contain references back to these strings.
+ * Freeing them, and reusing the memory, would make those
+ * error messages confusing, to say the least. */
+ for(i=1;i<argc;i++)
+ if (newargs[i])
+ newargs[i] = m_strdup(newargs[i]);
+ endargs = newargs;
+
+ setaction(NULL, NULL);
+ dpkg_options_parse((const char *const **)&endargs, cmdinfos, printforhelp);
+ if (!cipaction) badusage(_("need an action option"));
+
+ fsys_hash_init();
+
+ ret |= cipaction->action(endargs);
+
+ fsys_hash_reset();
+
+ pop_error_context(ehflag_normaltidy);
+ }
+
+ fclose(in);
+
+ return ret;
+}
+
+int main(int argc, const char *const *argv) {
+ int ret;
+
+ dpkg_locales_init(PACKAGE);
+ dpkg_program_init("dpkg");
+ set_force_default(FORCE_ALL);
+ dpkg_options_load(DPKG, cmdinfos);
+ dpkg_options_parse(&argv, cmdinfos, printforhelp);
+
+ /* When running as root, make sure our primary group is also root, so
+ * that files created by maintainer scripts have correct ownership. */
+ if (!in_force(FORCE_NON_ROOT) && getuid() == 0)
+ if (setgid(0) < 0)
+ ohshite(_("cannot set primary group ID to root"));
+
+ if (!cipaction) badusage(_("need an action option"));
+
+ admindir = dpkg_db_set_dir(admindir);
+
+ /* Always set environment, to avoid possible security risks. */
+ if (setenv("DPKG_ADMINDIR", admindir, 1) < 0)
+ ohshite(_("unable to setenv for subprocesses"));
+ if (setenv("DPKG_ROOT", instdir, 1) < 0)
+ ohshite(_("unable to setenv for subprocesses"));
+ if (setenv("DPKG_FORCE", get_force_string(), 1) < 0)
+ ohshite(_("unable to setenv for subprocesses"));
+
+ if (!f_triggers)
+ f_triggers = (cipaction->arg_int == act_triggers && *argv) ? -1 : 1;
+
+ if (is_invoke_action(cipaction->arg_int)) {
+ run_invoke_hooks(cipaction->olong, &pre_invoke_hooks);
+ run_status_loggers(&status_loggers);
+ }
+
+ fsys_hash_init();
+
+ ret = cipaction->action(argv);
+
+ if (is_invoke_action(cipaction->arg_int))
+ run_invoke_hooks(cipaction->olong, &post_invoke_hooks);
+
+ free_invoke_hooks(&pre_invoke_hooks);
+ free_invoke_hooks(&post_invoke_hooks);
+
+ dpkg_program_done();
+
+ return reportbroken_retexitstatus(ret);
+}
diff --git a/src/main.h b/src/main.h
new file mode 100644
index 0000000..3418c7b
--- /dev/null
+++ b/src/main.h
@@ -0,0 +1,367 @@
+/*
+ * dpkg - main program for package management
+ * main.h - external definitions for this program
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2006, 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/>.
+ */
+
+#ifndef MAIN_H
+#define MAIN_H
+
+#include <dpkg/debug.h>
+#include <dpkg/pkg-list.h>
+
+#include "force.h"
+
+/* These two are defined in <dpkg/fsys.h>. */
+struct fsys_namenode_list;
+struct fsys_namenode;
+
+enum pkg_istobe {
+ /** Package is to be left in a normal state. */
+ PKG_ISTOBE_NORMAL,
+ /** Package is to be removed. */
+ PKG_ISTOBE_REMOVE,
+ /** Package is to be installed, configured or triggered. */
+ PKG_ISTOBE_INSTALLNEW,
+ /** Package is to be deconfigured. */
+ PKG_ISTOBE_DECONFIGURE,
+ /** Package is to be checked for Pre-Depends satisfiability. */
+ PKG_ISTOBE_PREINSTALL,
+};
+
+enum pkg_cycle_color {
+ PKG_CYCLE_WHITE,
+ PKG_CYCLE_GRAY,
+ PKG_CYCLE_BLACK,
+};
+
+struct perpackagestate {
+ enum pkg_istobe istobe;
+
+ /** Used during cycle detection. */
+ enum pkg_cycle_color color;
+
+ bool enqueued;
+
+ int replacingfilesandsaid;
+ int cmdline_seen;
+
+ /** Non-NULL iff in trigproc.c:deferred. */
+ struct pkg_list *trigprocdeferred;
+};
+
+enum action {
+ act_unset,
+
+ act_unpack,
+ act_configure,
+ act_install,
+ act_triggers,
+ act_remove,
+ act_purge,
+ act_verify,
+ act_commandfd,
+
+ act_status,
+ act_listpackages,
+ act_listfiles,
+ act_searchfiles,
+ act_controlpath,
+ act_controllist,
+ act_controlshow,
+
+ act_cmpversions,
+
+ act_arch_add,
+ act_arch_remove,
+ act_printarch,
+ act_printforeignarches,
+
+ act_assertpredep,
+ act_assertepoch,
+ act_assertlongfilenames,
+ act_assertmulticonrep,
+ act_assertmultiarch,
+ act_assertverprovides,
+
+ act_validate_pkgname,
+ act_validate_trigname,
+ act_validate_archname,
+ act_validate_version,
+
+ act_audit,
+ act_unpackchk,
+ act_predeppackage,
+
+ act_getselections,
+ act_setselections,
+ act_clearselections,
+
+ act_avail,
+ act_printavail,
+ act_avclear,
+ act_avreplace,
+ act_avmerge,
+ act_forgetold,
+};
+
+extern const char *const statusstrings[];
+
+extern int f_pending, f_recursive, f_alsoselect, f_skipsame, f_noact;
+extern int f_autodeconf, f_nodebsig;
+extern int f_triggers;
+
+extern bool abort_processing;
+extern int errabort;
+extern const char *instdir;
+extern struct pkg_list *ignoredependss;
+
+struct invoke_hook {
+ struct invoke_hook *next;
+ char *command;
+};
+
+struct invoke_list {
+ struct invoke_hook *head, **tail;
+};
+
+/* from perpkgstate.c */
+
+void ensure_package_clientdata(struct pkginfo *pkg);
+
+/* from archives.c */
+
+int archivefiles(const char *const *argv);
+void process_archive(const char *filename);
+bool wanttoinstall(struct pkginfo *pkg);
+
+/* from update.c */
+
+int forgetold(const char *const *argv);
+int updateavailable(const char *const *argv);
+
+/* from enquiry.c */
+
+int audit(const char *const *argv);
+int unpackchk(const char *const *argv);
+int assertepoch(const char *const *argv);
+int assertpredep(const char *const *argv);
+int assertlongfilenames(const char *const *argv);
+int assertmulticonrep(const char *const *argv);
+int assertmultiarch(const char *const *argv);
+int assertverprovides(const char *const *argv);
+int validate_pkgname(const char *const *argv);
+int validate_trigname(const char *const *argv);
+int validate_archname(const char *const *argv);
+int validate_version(const char *const *argv);
+int predeppackage(const char *const *argv);
+int printarch(const char *const *argv);
+int printinstarch(const char *const *argv);
+int print_foreign_arches(const char *const *argv);
+int cmpversions(const char *const *argv);
+
+/* from verify.c */
+
+bool verify_set_output(const char *name);
+int verify(const char *const *argv);
+
+/* from select.c */
+
+int getselections(const char *const *argv);
+int setselections(const char *const *argv);
+int clearselections(const char *const *argv);
+
+/* from packages.c, remove.c and configure.c */
+
+void md5hash(struct pkginfo *pkg, char *hashbuf, const char *fn);
+void enqueue_package(struct pkginfo *pkg);
+void enqueue_package_mark_seen(struct pkginfo *pkg);
+void process_queue(void);
+int packages(const char *const *argv);
+void removal_bulk(struct pkginfo *pkg);
+int conffderef(struct pkginfo *pkg, struct varbuf *result, const char *in);
+
+enum dep_check {
+ DEP_CHECK_HALT = 0,
+ DEP_CHECK_DEFER = 1,
+ DEP_CHECK_OK = 2,
+};
+
+enum dep_check dependencies_ok(struct pkginfo *pkg, struct pkginfo *removing,
+ struct varbuf *aemsgs);
+enum dep_check breakses_ok(struct pkginfo *pkg, struct varbuf *aemsgs);
+
+void deferred_remove(struct pkginfo *pkg);
+void deferred_configure(struct pkginfo *pkg);
+
+/*
+ * During the packages queue processing, the algorithm for deciding what to
+ * configure first is as follows:
+ *
+ * Loop through all packages doing a ‘try 1’ until we've been round and
+ * nothing has been done, then do ‘try 2’, and subsequent ones likewise.
+ * The incrementing of ‘dependtry’ is done by process_queue().
+ *
+ * Try 1:
+ * Are all dependencies of this package done? If so, do it.
+ * Are any of the dependencies missing or the wrong version?
+ * If so, abort (unless --force-depends, in which case defer).
+ * Will we need to configure a package we weren't given as an
+ * argument? If so, abort ─ except if --force-configure-any,
+ * in which case we add the package to the argument list.
+ * If none of the above, defer the package.
+ *
+ * Try 2:
+ * Find a cycle and break it (see above).
+ * Do as for try 1.
+ *
+ * Try 3:
+ * Start processing triggers if necessary.
+ * Do as for try 2.
+ *
+ * Try 4:
+ * Same as for try 3, but check trigger cycles even when deferring
+ * processing due to unsatisfiable dependencies.
+ *
+ * Try 5 (only if --force-depends-version):
+ * Same as for try 2, but don't mind version number in dependencies.
+ *
+ * Try 6 (only if --force-depends):
+ * Do anyway.
+ */
+enum dependtry {
+ DEPEND_TRY_NORMAL = 1,
+ DEPEND_TRY_CYCLES = 2,
+ DEPEND_TRY_TRIGGERS = 3,
+ DEPEND_TRY_TRIGGERS_CYCLES = 4,
+ DEPEND_TRY_FORCE_DEPENDS_VERSION = 5,
+ DEPEND_TRY_FORCE_DEPENDS = 6,
+ DEPEND_TRY_LAST,
+};
+
+extern enum dependtry dependtry;
+extern int sincenothing;
+
+/* from cleanup.c (most of these are declared in archives.h) */
+
+void cu_prermremove(int argc, void **argv);
+
+/* from errors.c */
+
+void print_error_perpackage(const char *emsg, const void *data);
+void print_error_perarchive(const char *emsg, const void *data);
+int reportbroken_retexitstatus(int ret);
+bool skip_due_to_hold(struct pkginfo *pkg);
+
+/* from help.c */
+
+struct stat;
+
+bool ignore_depends(struct pkginfo *pkg);
+bool force_breaks(struct deppossi *possi);
+bool force_depends(struct deppossi *possi);
+bool force_conflicts(struct deppossi *possi);
+void
+conffile_mark_obsolete(struct pkginfo *pkg, struct fsys_namenode *namenode);
+void pkg_conffiles_mark_old(struct pkginfo *pkg);
+bool find_command(const char *prog);
+void checkpath(void);
+
+struct fsys_namenode *
+namenodetouse(struct fsys_namenode *namenode,
+ struct pkginfo *pkg, struct pkgbin *pkgbin);
+
+int maintscript_installed(struct pkginfo *pkg, const char *scriptname,
+ const char *desc, ...) DPKG_ATTR_SENTINEL;
+int maintscript_new(struct pkginfo *pkg,
+ const char *scriptname, const char *desc,
+ const char *cidir, char *cidirrest, ...)
+ DPKG_ATTR_SENTINEL;
+int maintscript_fallback(struct pkginfo *pkg,
+ const char *scriptname, const char *desc,
+ const char *cidir, char *cidirrest,
+ const char *ifok, const char *iffallback);
+
+/* Callers wanting to run the postinst use these two as they want to postpone
+ * trigger incorporation until after updating the package status. The effect
+ * is that a package can trigger itself. */
+int maintscript_postinst(struct pkginfo *pkg, ...) DPKG_ATTR_SENTINEL;
+void post_postinst_tasks(struct pkginfo *pkg, enum pkgstatus new_status);
+
+void clear_istobes(void);
+bool
+dir_is_used_by_others(struct fsys_namenode *namenode, struct pkginfo *pkg);
+bool
+dir_is_used_by_pkg(struct fsys_namenode *namenode, struct pkginfo *pkg,
+ struct fsys_namenode_list *list);
+bool
+dir_has_conffiles(struct fsys_namenode *namenode, struct pkginfo *pkg);
+
+void log_action(const char *action, struct pkginfo *pkg, struct pkgbin *pkgbin);
+
+/* From selinux.c */
+
+void dpkg_selabel_load(void);
+void dpkg_selabel_set_context(const char *matchpath, const char *path, mode_t mode);
+void dpkg_selabel_close(void);
+
+/* from trigproc.c */
+
+enum trigproc_type {
+ /** Opportunistic deferred trigger processing. */
+ TRIGPROC_TRY_DEFERRED,
+ /** Opportunistic queued trigger processing. */
+ TRIGPROC_TRY_QUEUED,
+ /** Required trigger processing. */
+ TRIGPROC_REQUIRED,
+};
+
+void trigproc_install_hooks(void);
+void trigproc_populate_deferred(void);
+void trigproc_run_deferred(void);
+void trigproc_reset_cycle(void);
+
+void trigproc(struct pkginfo *pkg, enum trigproc_type type);
+
+void trig_activate_packageprocessing(struct pkginfo *pkg);
+
+/* from depcon.c */
+
+enum which_pkgbin {
+ wpb_installed,
+ wpb_available,
+ wpb_by_istobe,
+};
+
+struct deppossi_pkg_iterator;
+
+struct deppossi_pkg_iterator *
+deppossi_pkg_iter_new(struct deppossi *possi, enum which_pkgbin wpb);
+struct pkginfo *
+deppossi_pkg_iter_next(struct deppossi_pkg_iterator *iter);
+void
+deppossi_pkg_iter_free(struct deppossi_pkg_iterator *iter);
+
+bool depisok(struct dependency *dep, struct varbuf *whynot,
+ struct pkginfo **fixbyrm, struct pkginfo **fixbytrigaw,
+ bool allowunconfigd);
+struct cyclesofarlink;
+bool findbreakcycle(struct pkginfo *pkg);
+void describedepcon(struct varbuf *addto, struct dependency *dep);
+
+#endif /* MAIN_H */
diff --git a/src/packages.c b/src/packages.c
new file mode 100644
index 0000000..bd8047a
--- /dev/null
+++ b/src/packages.c
@@ -0,0 +1,756 @@
+/*
+ * dpkg - main program for package management
+ * packages.c - common to actions that process packages
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2006-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 <string.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg-list.h>
+#include <dpkg/pkg-queue.h>
+#include <dpkg/string.h>
+#include <dpkg/options.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+
+#include "main.h"
+
+static struct pkginfo *progress_bytrigproc;
+static struct pkg_queue queue = PKG_QUEUE_INIT;
+
+enum dependtry dependtry = DEPEND_TRY_NORMAL;
+int sincenothing = 0;
+
+void
+enqueue_package(struct pkginfo *pkg)
+{
+ ensure_package_clientdata(pkg);
+ if (pkg->clientdata->enqueued)
+ return;
+ pkg->clientdata->enqueued = true;
+ pkg_queue_push(&queue, pkg);
+}
+
+void
+enqueue_package_mark_seen(struct pkginfo *pkg)
+{
+ enqueue_package(pkg);
+ pkg->clientdata->cmdline_seen++;
+}
+
+static void
+enqueue_pending(void)
+{
+ struct pkg_hash_iter *iter;
+ struct pkginfo *pkg;
+
+ iter = pkg_hash_iter_new();
+ while ((pkg = pkg_hash_iter_next_pkg(iter)) != NULL) {
+ switch (cipaction->arg_int) {
+ case act_configure:
+ if (!(pkg->status == PKG_STAT_UNPACKED ||
+ pkg->status == PKG_STAT_HALFCONFIGURED ||
+ pkg->trigpend_head))
+ continue;
+ if (pkg->want != PKG_WANT_INSTALL)
+ continue;
+ break;
+ case act_triggers:
+ if (!pkg->trigpend_head)
+ continue;
+ if (pkg->want != PKG_WANT_INSTALL)
+ continue;
+ break;
+ case act_remove:
+ case act_purge:
+ if (pkg->want != PKG_WANT_PURGE) {
+ if (pkg->want != PKG_WANT_DEINSTALL)
+ continue;
+ if (pkg->status == PKG_STAT_CONFIGFILES)
+ continue;
+ }
+ if (pkg->status == PKG_STAT_NOTINSTALLED)
+ continue;
+ break;
+ default:
+ internerr("unknown action '%d'", cipaction->arg_int);
+ }
+ enqueue_package(pkg);
+ }
+ pkg_hash_iter_free(iter);
+}
+
+static void
+enqueue_specified(const char *const *argv)
+{
+ const char *thisarg;
+
+ while ((thisarg = *argv++) != NULL) {
+ struct pkginfo *pkg;
+
+ pkg = dpkg_options_parse_pkgname(cipaction, thisarg);
+ if (pkg->status == PKG_STAT_NOTINSTALLED &&
+ str_match_end(pkg->set->name, DEBEXT)) {
+ badusage(_("you must specify packages by their own names, "
+ "not by quoting the names of the files they come in"));
+ }
+ enqueue_package_mark_seen(pkg);
+ }
+
+ if (cipaction->arg_int == act_configure)
+ trigproc_populate_deferred();
+}
+
+int
+packages(const char *const *argv)
+{
+ trigproc_install_hooks();
+
+ modstatdb_open(f_noact ? msdbrw_readonly :
+ in_force(FORCE_NON_ROOT) ? msdbrw_write :
+ msdbrw_needsuperuser);
+ checkpath();
+ pkg_infodb_upgrade();
+
+ log_message("startup packages %s", cipaction->olong);
+
+ if (f_pending) {
+ if (*argv)
+ badusage(_("--%s --pending does not take any non-option arguments"),cipaction->olong);
+
+ enqueue_pending();
+ } else {
+ if (!*argv)
+ badusage(_("--%s needs at least one package name argument"), cipaction->olong);
+
+ enqueue_specified(argv);
+ }
+
+ ensure_diversions();
+
+ process_queue();
+ trigproc_run_deferred();
+
+ modstatdb_shutdown();
+
+ return 0;
+}
+
+void process_queue(void) {
+ struct pkg_list *rundown;
+ struct pkginfo *volatile pkg;
+ volatile enum action action_todo;
+ jmp_buf ejbuf;
+ enum pkg_istobe istobe = PKG_ISTOBE_NORMAL;
+
+ if (abort_processing)
+ return;
+
+ clear_istobes();
+
+ switch (cipaction->arg_int) {
+ case act_triggers:
+ case act_configure:
+ case act_install:
+ istobe = PKG_ISTOBE_INSTALLNEW;
+ break;
+ case act_remove:
+ case act_purge:
+ istobe = PKG_ISTOBE_REMOVE;
+ break;
+ default:
+ internerr("unknown action '%d'", cipaction->arg_int);
+ }
+ for (rundown = queue.head; rundown; rundown = rundown->next) {
+ ensure_package_clientdata(rundown->pkg);
+
+ /* We have processed this package more than once. There are no duplicates
+ * as we make sure of that when enqueuing them. */
+ if (rundown->pkg->clientdata->cmdline_seen > 1) {
+ switch (cipaction->arg_int) {
+ case act_triggers:
+ case act_configure: case act_remove: case act_purge:
+ printf(_("Package %s listed more than once, only processing once.\n"),
+ pkg_name(rundown->pkg, pnaw_nonambig));
+ break;
+ case act_install:
+ printf(_("More than one copy of package %s has been unpacked\n"
+ " in this run ! Only configuring it once.\n"),
+ pkg_name(rundown->pkg, pnaw_nonambig));
+ break;
+ default:
+ internerr("unknown action '%d'", cipaction->arg_int);
+ }
+ }
+ rundown->pkg->clientdata->istobe = istobe;
+ }
+
+ while (!pkg_queue_is_empty(&queue)) {
+ pkg = pkg_queue_pop(&queue);
+ if (!pkg)
+ continue; /* Duplicate, which we removed earlier. */
+
+ ensure_package_clientdata(pkg);
+ pkg->clientdata->enqueued = false;
+
+ action_todo = cipaction->arg_int;
+
+ if (sincenothing++ > queue.length * 3 + 2) {
+ /* Make sure that even if we have exceeded the queue since not having
+ * made any progress, we are not getting stuck trying to progress by
+ * trigger processing, w/o jumping into the next dependtry. */
+ dependtry++;
+ sincenothing = 0;
+ if (dependtry >= DEPEND_TRY_LAST)
+ internerr("exceeded dependtry %d (sincenothing=%d; queue.length=%d)",
+ dependtry, sincenothing, queue.length);
+ } else if (sincenothing > queue.length * 2 + 2) {
+ if (dependtry >= DEPEND_TRY_TRIGGERS &&
+ progress_bytrigproc && progress_bytrigproc->trigpend_head) {
+ enqueue_package(pkg);
+ pkg = progress_bytrigproc;
+ progress_bytrigproc = NULL;
+ action_todo = act_configure;
+ } else {
+ dependtry++;
+ sincenothing = 0;
+ if (dependtry >= DEPEND_TRY_LAST)
+ internerr("exceeded dependtry %d (sincenothing=%d, queue.length=%d)",
+ dependtry, sincenothing, queue.length);
+ }
+ }
+
+ debug(dbg_general, "process queue pkg %s queue.len %d progress %d, try %d",
+ pkg_name(pkg, pnaw_always), queue.length, sincenothing, dependtry);
+
+ if (pkg->status > PKG_STAT_INSTALLED)
+ internerr("package %s status %d is out-of-bounds",
+ pkg_name(pkg, pnaw_always), pkg->status);
+
+ if (setjmp(ejbuf)) {
+ /* Give up on it from the point of view of other packages, i.e. reset
+ * istobe. */
+ pkg->clientdata->istobe = PKG_ISTOBE_NORMAL;
+
+ pop_error_context(ehflag_bombout);
+ if (abort_processing)
+ return;
+ continue;
+ }
+ push_error_context_jump(&ejbuf, print_error_perpackage,
+ pkg_name(pkg, pnaw_nonambig));
+
+ switch (action_todo) {
+ case act_triggers:
+ if (!pkg->trigpend_head)
+ ohshit(_("package %.250s is not ready for trigger processing\n"
+ " (current status '%.250s' with no pending triggers)"),
+ pkg_name(pkg, pnaw_nonambig), pkg_status_name(pkg));
+ /* Fall through. */
+ case act_install:
+ /* Don't try to configure pkgs that we've just disappeared. */
+ if (pkg->status == PKG_STAT_NOTINSTALLED)
+ break;
+ /* Fall through. */
+ case act_configure:
+ /* Do whatever is most needed. */
+ if (pkg->trigpend_head)
+ trigproc(pkg, TRIGPROC_TRY_QUEUED);
+ else
+ deferred_configure(pkg);
+ break;
+ case act_remove: case act_purge:
+ deferred_remove(pkg);
+ break;
+ default:
+ internerr("unknown action '%d'", cipaction->arg_int);
+ }
+ m_output(stdout, _("<standard output>"));
+ m_output(stderr, _("<standard error>"));
+
+ pop_error_context(ehflag_normaltidy);
+ }
+
+ if (queue.length)
+ internerr("finished package processing with non-empty queue length %d",
+ queue.length);
+}
+
+/*** Dependency processing - common to --configure and --remove. ***/
+
+/*
+ * The algorithm for deciding what to configure or remove first is as
+ * follows:
+ *
+ * Loop through all packages doing a ‘try 1’ until we've been round and
+ * nothing has been done, then do ‘try 2’ and ‘try 3’ likewise.
+ *
+ * When configuring, in each try we check to see whether all
+ * dependencies of this package are done. If so we do it. If some of
+ * the dependencies aren't done yet but will be later we defer the
+ * package, otherwise it is an error.
+ *
+ * When removing, in each try we check to see whether there are any
+ * packages that would have dependencies missing if we removed this
+ * one. If not we remove it now. If some of these packages are
+ * themselves scheduled for removal we defer the package until they
+ * have been done.
+ *
+ * The criteria for satisfying a dependency vary with the various
+ * tries. In try 1 we treat the dependencies as absolute. In try 2 we
+ * check break any cycles in the dependency graph involving the package
+ * we are trying to process before trying to process the package
+ * normally. In try 3 (which should only be reached if
+ * --force-depends-version is set) we ignore version number clauses in
+ * Depends lines. In try 4 (only reached if --force-depends is set) we
+ * say "ok" regardless.
+ *
+ * If we are configuring and one of the packages we depend on is
+ * awaiting configuration but wasn't specified in the argument list we
+ * will add it to the argument list if --configure-any is specified.
+ * In this case we note this as having "done something" so that we
+ * don't needlessly escalate to higher levels of dependency checking
+ * and breaking.
+ */
+
+enum found_status {
+ FOUND_NONE = 0,
+ FOUND_DEFER = 1,
+ FOUND_FORCED = 2,
+ FOUND_OK = 3,
+};
+
+static enum found_status
+found_forced_on(enum dependtry dependtry_forced)
+{
+ if (dependtry >= dependtry_forced)
+ return FOUND_FORCED;
+ else
+ return FOUND_DEFER;
+}
+
+/*
+ * Return values:
+ * 0: cannot be satisfied.
+ * 1: defer: may be satisfied later, when other packages are better or
+ * at higher dependtry due to --force
+ * will set *fixbytrig to package whose trigger processing would help
+ * if applicable (and leave it alone otherwise).
+ * 2: not satisfied but forcing
+ * (*interestingwarnings >= 0 on exit? caller is to print oemsgs).
+ * 3: satisfied now.
+ */
+static enum found_status
+deppossi_ok_found(struct pkginfo *possdependee, struct pkginfo *requiredby,
+ struct pkginfo *removing, struct deppossi *provider,
+ struct pkginfo **fixbytrig,
+ bool *matched, struct deppossi *checkversion,
+ int *interestingwarnings, struct varbuf *oemsgs)
+{
+ enum found_status thisf;
+
+ if (ignore_depends(possdependee)) {
+ debug(dbg_depcondetail," ignoring depended package so ok and found");
+ return FOUND_OK;
+ }
+ thisf = FOUND_NONE;
+ if (possdependee == removing) {
+ if (provider) {
+ varbuf_printf(oemsgs,
+ _(" Package %s which provides %s is to be removed.\n"),
+ pkg_name(possdependee, pnaw_nonambig),
+ provider->ed->name);
+ } else {
+ varbuf_printf(oemsgs, _(" Package %s is to be removed.\n"),
+ pkg_name(possdependee, pnaw_nonambig));
+ }
+
+ *matched = true;
+ debug(dbg_depcondetail," removing possdependee, returning %d",thisf);
+ return thisf;
+ }
+ switch (possdependee->status) {
+ case PKG_STAT_UNPACKED:
+ case PKG_STAT_HALFCONFIGURED:
+ case PKG_STAT_TRIGGERSAWAITED:
+ case PKG_STAT_TRIGGERSPENDING:
+ case PKG_STAT_INSTALLED:
+ if (checkversion) {
+ if (provider) {
+ debug(dbg_depcondetail, " checking package %s provided by pkg %s",
+ checkversion->ed->name, pkg_name(possdependee, pnaw_always));
+ if (!pkg_virtual_deppossi_satisfied(checkversion, provider)) {
+ varbuf_printf(oemsgs,
+ _(" Version of %s on system, provided by %s, is %s.\n"),
+ checkversion->ed->name,
+ pkg_name(possdependee, pnaw_always),
+ versiondescribe(&provider->version, vdew_nonambig));
+ if (in_force(FORCE_DEPENDS_VERSION))
+ thisf = found_forced_on(DEPEND_TRY_FORCE_DEPENDS_VERSION);
+ debug(dbg_depcondetail, " bad version");
+ goto unsuitable;
+ }
+ } else {
+ debug(dbg_depcondetail, " checking non-provided pkg %s",
+ pkg_name(possdependee, pnaw_always));
+ if (!versionsatisfied(&possdependee->installed, checkversion)) {
+ varbuf_printf(oemsgs, _(" Version of %s on system is %s.\n"),
+ pkg_name(possdependee, pnaw_nonambig),
+ versiondescribe(&possdependee->installed.version,
+ vdew_nonambig));
+ if (in_force(FORCE_DEPENDS_VERSION))
+ thisf = found_forced_on(DEPEND_TRY_FORCE_DEPENDS_VERSION);
+ debug(dbg_depcondetail, " bad version");
+ goto unsuitable;
+ }
+ }
+ }
+ if (possdependee->status == PKG_STAT_INSTALLED ||
+ possdependee->status == PKG_STAT_TRIGGERSPENDING) {
+ debug(dbg_depcondetail," is installed, ok and found");
+ return FOUND_OK;
+ }
+ if (possdependee->status == PKG_STAT_TRIGGERSAWAITED) {
+ if (possdependee->trigaw.head == NULL)
+ internerr("package %s in state %s, has no awaited triggers",
+ pkg_name(possdependee, pnaw_always),
+ pkg_status_name(possdependee));
+
+ if (removing ||
+ !(f_triggers ||
+ (possdependee->clientdata &&
+ possdependee->clientdata->istobe == PKG_ISTOBE_INSTALLNEW))) {
+ if (provider) {
+ varbuf_printf(oemsgs,
+ _(" Package %s which provides %s awaits trigger processing.\n"),
+ pkg_name(possdependee, pnaw_nonambig),
+ provider->ed->name);
+ } else {
+ varbuf_printf(oemsgs,
+ _(" Package %s awaits trigger processing.\n"),
+ pkg_name(possdependee, pnaw_nonambig));
+ }
+ debug(dbg_depcondetail, " triggers-awaited, no fixbytrig");
+ goto unsuitable;
+ }
+ /* We don't check the status of trigaw.head->pend here, just in case
+ * we get into the pathological situation where Triggers-Awaited but
+ * the named package doesn't actually have any pending triggers. In
+ * that case we queue the non-pending package for trigger processing
+ * anyway, and that trigger processing will be a noop except for
+ * sorting out all of the packages which name it in Triggers-Awaited.
+ *
+ * (This situation can only arise if modstatdb_note success in
+ * clearing the triggers-pending status of the pending package
+ * but then fails to go on to update the awaiters.) */
+ *fixbytrig = possdependee->trigaw.head->pend;
+ debug(dbg_depcondetail,
+ " triggers-awaited, fixbytrig '%s', defer",
+ pkg_name(*fixbytrig, pnaw_always));
+ return FOUND_DEFER;
+ }
+ if (possdependee->clientdata &&
+ possdependee->clientdata->istobe == PKG_ISTOBE_INSTALLNEW) {
+ debug(dbg_depcondetail," unpacked/halfconfigured, defer");
+ return FOUND_DEFER;
+ } else if (!removing && in_force(FORCE_CONFIGURE_ANY) &&
+ !skip_due_to_hold(possdependee) &&
+ !(possdependee->status == PKG_STAT_HALFCONFIGURED)) {
+ notice(_("also configuring '%s' (required by '%s')"),
+ pkg_name(possdependee, pnaw_nonambig),
+ pkg_name(requiredby, pnaw_nonambig));
+ enqueue_package(possdependee);
+ sincenothing = 0;
+ return FOUND_DEFER;
+ } else {
+ if (provider) {
+ varbuf_printf(oemsgs,
+ _(" Package %s which provides %s is not configured yet.\n"),
+ pkg_name(possdependee, pnaw_nonambig),
+ provider->ed->name);
+ } else {
+ varbuf_printf(oemsgs, _(" Package %s is not configured yet.\n"),
+ pkg_name(possdependee, pnaw_nonambig));
+ }
+
+ debug(dbg_depcondetail, " not configured/able");
+ goto unsuitable;
+ }
+
+ default:
+ if (provider) {
+ varbuf_printf(oemsgs,
+ _(" Package %s which provides %s is not installed.\n"),
+ pkg_name(possdependee, pnaw_nonambig),
+ provider->ed->name);
+ } else {
+ varbuf_printf(oemsgs, _(" Package %s is not installed.\n"),
+ pkg_name(possdependee, pnaw_nonambig));
+ }
+
+ debug(dbg_depcondetail, " not installed");
+ goto unsuitable;
+ }
+
+unsuitable:
+ debug(dbg_depcondetail, " returning %d", thisf);
+ (*interestingwarnings)++;
+
+ return thisf;
+}
+
+static void
+breaks_check_one(struct varbuf *aemsgs, enum dep_check *ok,
+ struct deppossi *breaks, struct pkginfo *broken,
+ struct pkginfo *breaker, struct deppossi *virtbroken)
+{
+ struct varbuf depmsg = VARBUF_INIT;
+
+ debug(dbg_depcondetail, " checking breaker %s virtbroken %s",
+ pkg_name(breaker, pnaw_always),
+ virtbroken ? virtbroken->ed->name : "<none>");
+
+ if (breaker->status == PKG_STAT_NOTINSTALLED ||
+ breaker->status == PKG_STAT_CONFIGFILES)
+ return;
+ if (broken == breaker) return;
+ if (!versionsatisfied(&broken->installed, breaks)) return;
+ /* The test below can only trigger if dep_breaks start having
+ * arch qualifiers different from “any”. */
+ if (!archsatisfied(&broken->installed, breaks))
+ return;
+ if (ignore_depends(breaker)) return;
+ if (virtbroken && ignore_depends(&virtbroken->ed->pkg))
+ return;
+ if (virtbroken && !pkg_virtual_deppossi_satisfied(breaks, virtbroken))
+ return;
+
+ varbufdependency(&depmsg, breaks->up);
+ varbuf_end_str(&depmsg);
+ varbuf_printf(aemsgs, _(" %s (%s) breaks %s and is %s.\n"),
+ pkg_name(breaker, pnaw_nonambig),
+ versiondescribe(&breaker->installed.version, vdew_nonambig),
+ depmsg.buf, gettext(statusstrings[breaker->status]));
+ varbuf_destroy(&depmsg);
+
+ if (virtbroken) {
+ varbuf_printf(aemsgs, _(" %s (%s) provides %s.\n"),
+ pkg_name(broken, pnaw_nonambig),
+ versiondescribe(&broken->installed.version, vdew_nonambig),
+ virtbroken->ed->name);
+ } else if (breaks->verrel != DPKG_RELATION_NONE) {
+ varbuf_printf(aemsgs, _(" Version of %s to be configured is %s.\n"),
+ pkg_name(broken, pnaw_nonambig),
+ versiondescribe(&broken->installed.version, vdew_nonambig));
+ if (in_force(FORCE_DEPENDS_VERSION))
+ return;
+ }
+ if (force_breaks(breaks)) return;
+ *ok = DEP_CHECK_HALT;
+}
+
+static void
+breaks_check_target(struct varbuf *aemsgs, enum dep_check *ok,
+ struct pkginfo *broken, struct pkgset *target,
+ struct deppossi *virtbroken)
+{
+ struct deppossi *possi;
+
+ for (possi = target->depended.installed; possi; possi = possi->rev_next) {
+ if (possi->up->type != dep_breaks) continue;
+ breaks_check_one(aemsgs, ok, possi, broken, possi->up->up, virtbroken);
+ }
+}
+
+enum dep_check
+breakses_ok(struct pkginfo *pkg, struct varbuf *aemsgs)
+{
+ struct dependency *dep;
+ struct deppossi *virtbroken;
+ enum dep_check ok = DEP_CHECK_OK;
+
+ debug(dbg_depcon, " checking Breaks");
+
+ breaks_check_target(aemsgs, &ok, pkg, pkg->set, NULL);
+
+ for (dep= pkg->installed.depends; dep; dep= dep->next) {
+ if (dep->type != dep_provides) continue;
+ virtbroken = dep->list;
+ debug(dbg_depcondetail, " checking virtbroken %s", virtbroken->ed->name);
+ breaks_check_target(aemsgs, &ok, pkg, virtbroken->ed, virtbroken);
+ }
+ return ok;
+}
+
+/*
+ * Checks [Pre]-Depends only.
+ */
+enum dep_check
+dependencies_ok(struct pkginfo *pkg, struct pkginfo *removing,
+ struct varbuf *aemsgs)
+{
+ /* Valid values: 2 = ok, 1 = defer, 0 = halt. */
+ enum dep_check ok;
+ /* Valid values: 0 = none, 1 = defer, 2 = withwarning, 3 = ok. */
+ enum found_status found, thisf;
+ int interestingwarnings;
+ bool matched, anycannotfixbytrig;
+ struct varbuf oemsgs = VARBUF_INIT;
+ struct dependency *dep;
+ struct deppossi *possi, *provider;
+ struct pkginfo *possfixbytrig, *canfixbytrig;
+
+ interestingwarnings= 0;
+ ok = DEP_CHECK_OK;
+ debug(dbg_depcon,"checking dependencies of %s (- %s)",
+ pkg_name(pkg, pnaw_always),
+ removing ? pkg_name(removing, pnaw_always) : "<none>");
+
+ anycannotfixbytrig = false;
+ canfixbytrig = NULL;
+ for (dep= pkg->installed.depends; dep; dep= dep->next) {
+ if (dep->type != dep_depends && dep->type != dep_predepends) continue;
+ debug(dbg_depcondetail," checking group ...");
+ matched = false;
+ varbuf_reset(&oemsgs);
+ found = FOUND_NONE;
+ possfixbytrig = NULL;
+ for (possi = dep->list; found != FOUND_OK && possi; possi = possi->next) {
+ struct deppossi_pkg_iterator *possi_iter;
+ struct pkginfo *pkg_pos;
+
+ debug(dbg_depcondetail," checking possibility -> %s",possi->ed->name);
+ if (possi->cyclebreak) {
+ debug(dbg_depcondetail," break cycle so ok and found");
+ found = FOUND_OK;
+ break;
+ }
+
+ thisf = FOUND_NONE;
+ possi_iter = deppossi_pkg_iter_new(possi, wpb_installed);
+ while ((pkg_pos = deppossi_pkg_iter_next(possi_iter))) {
+ thisf = deppossi_ok_found(pkg_pos, pkg, removing, NULL,
+ &possfixbytrig, &matched, possi,
+ &interestingwarnings, &oemsgs);
+ if (thisf > found)
+ found = thisf;
+ if (found == FOUND_OK)
+ break;
+ }
+ deppossi_pkg_iter_free(possi_iter);
+
+ if (found != FOUND_OK) {
+ for (provider = possi->ed->depended.installed;
+ found != FOUND_OK && provider;
+ provider = provider->rev_next) {
+ if (provider->up->type != dep_provides)
+ continue;
+ debug(dbg_depcondetail, " checking provider %s",
+ pkg_name(provider->up->up, pnaw_always));
+ if (!deparchsatisfied(&provider->up->up->installed, provider->arch,
+ possi)) {
+ debug(dbg_depcondetail, " provider does not satisfy arch");
+ continue;
+ }
+ thisf = deppossi_ok_found(provider->up->up, pkg, removing, provider,
+ &possfixbytrig, &matched, possi,
+ &interestingwarnings, &oemsgs);
+ if (thisf == FOUND_DEFER && provider->up->up == pkg && !removing) {
+ /* IOW, if the pkg satisfies its own dep (via a provide), then
+ * we let it pass, even if it isn't configured yet (as we're
+ * installing it). */
+ thisf = FOUND_OK;
+ }
+ if (thisf > found)
+ found = thisf;
+ }
+ }
+ debug(dbg_depcondetail," found %d",found);
+ if (thisf > found) found= thisf;
+ }
+ if (in_force(FORCE_DEPENDS)) {
+ thisf = found_forced_on(DEPEND_TRY_FORCE_DEPENDS);
+ if (thisf > found) {
+ found = thisf;
+ debug(dbg_depcondetail, " rescued by force-depends, found %d", found);
+ }
+ }
+ debug(dbg_depcondetail, " found %d matched %d possfixbytrig %s",
+ found, matched,
+ possfixbytrig ? pkg_name(possfixbytrig, pnaw_always) : "-");
+ if (removing && !matched) continue;
+ switch (found) {
+ case FOUND_NONE:
+ anycannotfixbytrig = true;
+ ok = DEP_CHECK_HALT;
+ /* Fall through. */
+ case FOUND_FORCED:
+ varbuf_add_str(aemsgs, " ");
+ varbuf_add_pkgbin_name(aemsgs, pkg, &pkg->installed, pnaw_nonambig);
+ varbuf_add_str(aemsgs, _(" depends on "));
+ varbufdependency(aemsgs, dep);
+ if (interestingwarnings) {
+ /* Don't print the line about the package to be removed if
+ * that's the only line. */
+ varbuf_end_str(&oemsgs);
+ varbuf_add_str(aemsgs, _("; however:\n"));
+ varbuf_add_str(aemsgs, oemsgs.buf);
+ } else {
+ varbuf_add_str(aemsgs, ".\n");
+ }
+ break;
+ case FOUND_DEFER:
+ if (possfixbytrig)
+ canfixbytrig = possfixbytrig;
+ else
+ anycannotfixbytrig = true;
+ if (ok > DEP_CHECK_DEFER)
+ ok = DEP_CHECK_DEFER;
+ break;
+ case FOUND_OK:
+ break;
+ default:
+ internerr("unknown value for found '%d'", found);
+ }
+ }
+ if (ok == DEP_CHECK_HALT &&
+ (pkg->clientdata && pkg->clientdata->istobe == PKG_ISTOBE_REMOVE))
+ ok = DEP_CHECK_DEFER;
+ if (!anycannotfixbytrig && canfixbytrig)
+ progress_bytrigproc = canfixbytrig;
+
+ varbuf_destroy(&oemsgs);
+ debug(dbg_depcon,"ok %d msgs >>%.*s<<", ok, (int)aemsgs->used, aemsgs->buf);
+ return ok;
+}
diff --git a/src/perpkgstate.c b/src/perpkgstate.c
new file mode 100644
index 0000000..7542112
--- /dev/null
+++ b/src/perpkgstate.c
@@ -0,0 +1,43 @@
+/*
+ * dpkg - main program for package management
+ * perpkgstate.c - struct perpackagestate and function handling
+ *
+ * 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/dpkg.h>
+#include <dpkg/dpkg-db.h>
+
+#include "main.h"
+
+void
+ensure_package_clientdata(struct pkginfo *pkg)
+{
+ if (pkg->clientdata)
+ return;
+ pkg->clientdata = nfmalloc(sizeof(*pkg->clientdata));
+ pkg->clientdata->istobe = PKG_ISTOBE_NORMAL;
+ pkg->clientdata->color = PKG_CYCLE_WHITE;
+ pkg->clientdata->enqueued = false;
+ pkg->clientdata->replacingfilesandsaid = 0;
+ pkg->clientdata->cmdline_seen = 0;
+ pkg->clientdata->trigprocdeferred = NULL;
+}
diff --git a/src/querycmd.c b/src/querycmd.c
new file mode 100644
index 0000000..6ee555e
--- /dev/null
+++ b/src/querycmd.c
@@ -0,0 +1,867 @@
+/*
+ * dpkg-query - program for query the dpkg database
+ * querycmd.c - status enquiry and listing options
+ *
+ * Copyright © 1995,1996 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2000,2001 Wichert Akkerman <wakkerma@debian.org>
+ * Copyright © 2006-2015 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>
+
+#if HAVE_LOCALE_H
+#include <locale.h>
+#endif
+#include <errno.h>
+#include <limits.h>
+#include <string.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <fnmatch.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg-array.h>
+#include <dpkg/pkg-spec.h>
+#include <dpkg/pkg-format.h>
+#include <dpkg/pkg-show.h>
+#include <dpkg/string.h>
+#include <dpkg/path.h>
+#include <dpkg/file.h>
+#include <dpkg/pager.h>
+#include <dpkg/options.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+
+#include "main.h"
+
+static const char *showformat = "${binary:Package}\t${Version}\n";
+
+static int opt_loadavail = 0;
+
+static int
+pkg_array_match_patterns(struct pkg_array *array,
+ pkg_array_visitor_func *pkg_visitor, void *pkg_data,
+ const char *const *argv)
+{
+ int argc, i, ip, *found;
+ int rc = 0;
+ struct pkg_spec *ps;
+
+ for (argc = 0; argv[argc]; argc++);
+ found = m_calloc(argc, sizeof(int));
+
+ ps = m_malloc(sizeof(*ps) * argc);
+ for (ip = 0; ip < argc; ip++) {
+ pkg_spec_init(&ps[ip], PKG_SPEC_PATTERNS | PKG_SPEC_ARCH_WILDCARD);
+ pkg_spec_parse(&ps[ip], argv[ip]);
+ }
+
+ for (i = 0; i < array->n_pkgs; i++) {
+ struct pkginfo *pkg;
+ bool pkg_found = false;
+
+ pkg = array->pkgs[i];
+ for (ip = 0; ip < argc; ip++) {
+ if (pkg_spec_match_pkg(&ps[ip], pkg, &pkg->installed)) {
+ pkg_found = true;
+ found[ip]++;
+ }
+ }
+ if (!pkg_found)
+ array->pkgs[i] = NULL;
+ }
+
+ pkg_array_foreach(array, pkg_visitor, pkg_data);
+
+ for (ip = 0; ip < argc; ip++) {
+ if (!found[ip]) {
+ notice(_("no packages found matching %s"), argv[ip]);
+ rc++;
+ }
+ pkg_spec_destroy(&ps[ip]);
+ }
+
+ free(ps);
+ free(found);
+
+ return rc;
+}
+
+struct list_format {
+ bool head;
+ int nw;
+ int vw;
+ int aw;
+ int dw;
+};
+
+static void
+list_format_init(struct list_format *fmt, struct pkg_array *array)
+{
+ int i;
+
+ if (fmt->nw != 0)
+ return;
+
+ fmt->nw = 14;
+ fmt->vw = 12;
+ fmt->aw = 12;
+ fmt->dw = 33;
+
+ for (i = 0; i < array->n_pkgs; i++) {
+ int plen, vlen, alen, dlen;
+
+ if (array->pkgs[i] == NULL)
+ continue;
+
+ plen = str_width(pkg_name(array->pkgs[i], pnaw_nonambig));
+ vlen = str_width(versiondescribe(&array->pkgs[i]->installed.version,
+ vdew_nonambig));
+ alen = str_width(dpkg_arch_describe(array->pkgs[i]->installed.arch));
+ pkgbin_synopsis(array->pkgs[i], &array->pkgs[i]->installed, &dlen);
+
+ if (plen > fmt->nw)
+ fmt->nw = plen;
+ if (vlen > fmt->vw)
+ fmt->vw = vlen;
+ if (alen > fmt->aw)
+ fmt->aw = alen;
+ if (dlen > fmt->dw)
+ fmt->dw = dlen;
+ }
+}
+
+static void
+list_format_print(struct list_format *fmt,
+ int c_want, int c_status, int c_eflag,
+ const char *name, const char *version, const char *arch,
+ const char *desc, int desc_len)
+{
+ struct str_crop_info ns, vs, as, ds;
+
+ str_gen_crop(name, fmt->nw, &ns);
+ str_gen_crop(version, fmt->vw, &vs);
+ str_gen_crop(arch, fmt->aw, &as);
+ str_gen_crop(desc, desc_len, &ds);
+
+ printf("%c%c%c %-*.*s %-*.*s %-*.*s %.*s\n", c_want, c_status, c_eflag,
+ ns.max_bytes, ns.str_bytes, name,
+ vs.max_bytes, vs.str_bytes, version,
+ as.max_bytes, as.str_bytes, arch,
+ ds.str_bytes, desc);
+}
+
+static void
+list_format_print_header(struct list_format *fmt)
+{
+ int l;
+
+ if (fmt->head)
+ return;
+
+ /* TRANSLATORS: This is the header that appears on 'dpkg-query -l'. The
+ * string should remain under 80 characters. The uppercase letters in
+ * the state values denote the abbreviated letter that will appear on
+ * the first three columns, which should ideally match the English one
+ * (e.g. Remove → supRimeix), see dpkg-query(1) for further details. The
+ * translated message can use additional lines if needed. */
+ fputs(_("\
+Desired=Unknown/Install/Remove/Purge/Hold\n\
+| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend\n\
+|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)\n"), stdout);
+ list_format_print(fmt, '|', '|', '/', _("Name"), _("Version"),
+ _("Architecture"), _("Description"), fmt->dw);
+
+ /* Status */
+ printf("+++-");
+
+ /* Package name. */
+ for (l = 0; l < fmt->nw; l++)
+ printf("=");
+ printf("-");
+
+ /* Version. */
+ for (l = 0; l < fmt->vw; l++)
+ printf("=");
+ printf("-");
+
+ /* Architecture. */
+ for (l = 0; l < fmt->aw; l++)
+ printf("=");
+ printf("-");
+
+ /* Description. */
+ for (l = 0; l < fmt->dw; l++)
+ printf("=");
+ printf("\n");
+
+ fmt->head = true;
+}
+
+static void
+pkg_array_list_item(struct pkg_array *array, struct pkginfo *pkg, void *pkg_data)
+{
+ struct list_format *fmt = pkg_data;
+ int l;
+ const char *pdesc;
+
+ list_format_init(fmt, array);
+ list_format_print_header(fmt);
+
+ pdesc = pkgbin_synopsis(pkg, &pkg->installed, &l);
+ l = min(l, fmt->dw);
+
+ list_format_print(fmt,
+ pkg_abbrev_want(pkg),
+ pkg_abbrev_status(pkg),
+ pkg_abbrev_eflag(pkg),
+ pkg_name(pkg, pnaw_nonambig),
+ versiondescribe(&pkg->installed.version, vdew_nonambig),
+ dpkg_arch_describe(pkg->installed.arch),
+ pdesc, l);
+}
+
+static int
+listpackages(const char *const *argv)
+{
+ struct pkg_array array;
+ struct pkginfo *pkg;
+ int i;
+ int rc = 0;
+ struct list_format fmt;
+ struct pager *pager;
+
+ if (!opt_loadavail)
+ modstatdb_open(msdbrw_readonly);
+ else
+ modstatdb_open(msdbrw_readonly | msdbrw_available_readonly);
+
+ pkg_array_init_from_hash(&array);
+ pkg_array_sort(&array, pkg_sorter_by_nonambig_name_arch);
+
+ memset(&fmt, 0, sizeof(fmt));
+
+ pager = pager_spawn(_("showing package list on pager"));
+
+ if (!*argv) {
+ for (i = 0; i < array.n_pkgs; i++) {
+ pkg = array.pkgs[i];
+ if (pkg->status == PKG_STAT_NOTINSTALLED)
+ array.pkgs[i] = NULL;
+ }
+
+ pkg_array_foreach(&array, pkg_array_list_item, &fmt);
+ } else {
+ rc = pkg_array_match_patterns(&array, pkg_array_list_item, &fmt, argv);
+ }
+
+ m_output(stdout, _("<standard output>"));
+ m_output(stderr, _("<standard error>"));
+
+ pager_reap(pager);
+
+ pkg_array_destroy(&array);
+ modstatdb_shutdown();
+
+ return rc;
+}
+
+static int
+searchoutput(struct fsys_namenode *namenode)
+{
+ struct fsys_node_pkgs_iter *iter;
+ struct pkginfo *pkg_owner;
+ int found;
+
+ if (namenode->divert) {
+ const char *name_from = namenode->divert->camefrom ?
+ namenode->divert->camefrom->name : namenode->name;
+ const char *name_to = namenode->divert->useinstead ?
+ namenode->divert->useinstead->name : namenode->name;
+
+ if (namenode->divert->pkgset) {
+ printf(_("diversion by %s from: %s\n"),
+ namenode->divert->pkgset->name, name_from);
+ printf(_("diversion by %s to: %s\n"),
+ namenode->divert->pkgset->name, name_to);
+ } else {
+ printf(_("local diversion from: %s\n"), name_from);
+ printf(_("local diversion to: %s\n"), name_to);
+ }
+ }
+ found= 0;
+
+ iter = fsys_node_pkgs_iter_new(namenode);
+ while ((pkg_owner = fsys_node_pkgs_iter_next(iter))) {
+ if (found)
+ fputs(", ", stdout);
+ fputs(pkg_name(pkg_owner, pnaw_nonambig), stdout);
+ found++;
+ }
+ fsys_node_pkgs_iter_free(iter);
+
+ if (found) printf(": %s\n",namenode->name);
+ return found + (namenode->divert ? 1 : 0);
+}
+
+static int
+searchfiles(const char *const *argv)
+{
+ struct fsys_namenode *namenode;
+ struct fsys_hash_iter *iter;
+ const char *thisarg;
+ int found;
+ int failures = 0;
+ struct varbuf path = VARBUF_INIT;
+ static struct varbuf vb;
+
+ if (!*argv)
+ badusage(_("--search needs at least one file name pattern argument"));
+
+ modstatdb_open(msdbrw_readonly);
+ ensure_allinstfiles_available_quiet();
+ ensure_diversions();
+
+ while ((thisarg = *argv++) != NULL) {
+ found= 0;
+
+ if (!strchr("*[?/",*thisarg)) {
+ varbuf_reset(&vb);
+ varbuf_add_char(&vb, '*');
+ varbuf_add_str(&vb, thisarg);
+ varbuf_add_char(&vb, '*');
+ varbuf_end_str(&vb);
+ thisarg= vb.buf;
+ }
+ if (!strpbrk(thisarg, "*[?\\")) {
+ /* Trim trailing ‘/’ and ‘/.’ from the argument if it is not
+ * a pattern, just a pathname. */
+ varbuf_reset(&path);
+ varbuf_add_str(&path, thisarg);
+ varbuf_end_str(&path);
+ varbuf_trunc(&path, path_trim_slash_slashdot(path.buf));
+
+ namenode = fsys_hash_find_node(path.buf, 0);
+ found += searchoutput(namenode);
+ } else {
+ iter = fsys_hash_iter_new();
+ while ((namenode = fsys_hash_iter_next(iter)) != NULL) {
+ if (fnmatch(thisarg,namenode->name,0)) continue;
+ found+= searchoutput(namenode);
+ }
+ fsys_hash_iter_free(iter);
+ }
+ if (!found) {
+ notice(_("no path found matching pattern %s"), thisarg);
+ failures++;
+ m_output(stderr, _("<standard error>"));
+ } else {
+ m_output(stdout, _("<standard output>"));
+ }
+ }
+ modstatdb_shutdown();
+
+ varbuf_destroy(&path);
+
+ return failures;
+}
+
+static int
+print_status(const char *const *argv)
+{
+ const char *thisarg;
+ struct pkginfo *pkg;
+ int failures = 0;
+
+ modstatdb_open(msdbrw_readonly);
+
+ if (!*argv) {
+ writedb_records(stdout, _("<standard output>"), 0);
+ } else {
+ while ((thisarg = *argv++) != NULL) {
+ pkg = dpkg_options_parse_pkgname(cipaction, thisarg);
+
+ if (pkg->status == PKG_STAT_NOTINSTALLED &&
+ pkg->priority == PKG_PRIO_UNKNOWN &&
+ str_is_unset(pkg->section) &&
+ !pkg->archives &&
+ pkg->want == PKG_WANT_UNKNOWN &&
+ !pkg_is_informative(pkg, &pkg->installed)) {
+ notice(_("package '%s' is not installed and no information is available"),
+ pkg_name(pkg, pnaw_nonambig));
+ failures++;
+ } else {
+ writerecord(stdout, _("<standard output>"), pkg, &pkg->installed);
+ }
+
+ if (*argv != NULL)
+ putchar('\n');
+ }
+ }
+
+ m_output(stdout, _("<standard output>"));
+ if (failures) {
+ fputs(_("Use dpkg --info (= dpkg-deb --info) to examine archive files.\n"),
+ stderr);
+ m_output(stderr, _("<standard error>"));
+ }
+
+ modstatdb_shutdown();
+
+ return failures;
+}
+
+static int
+print_avail(const char *const *argv)
+{
+ const char *thisarg;
+ struct pkginfo *pkg;
+ int failures = 0;
+
+ modstatdb_open(msdbrw_readonly | msdbrw_available_readonly);
+
+ if (!*argv) {
+ writedb_records(stdout, _("<standard output>"), wdb_dump_available);
+ } else {
+ while ((thisarg = *argv++) != NULL) {
+ pkg = dpkg_options_parse_pkgname(cipaction, thisarg);
+
+ if (!pkg_is_informative(pkg, &pkg->available)) {
+ notice(_("package '%s' is not available"),
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig));
+ failures++;
+ } else {
+ writerecord(stdout, _("<standard output>"), pkg, &pkg->available);
+ }
+
+ if (*argv != NULL)
+ putchar('\n');
+ }
+ }
+
+ m_output(stdout, _("<standard output>"));
+ if (failures)
+ m_output(stderr, _("<standard error>"));
+
+ modstatdb_shutdown();
+
+ return failures;
+}
+
+static int
+list_files(const char *const *argv)
+{
+ const char *thisarg;
+ struct fsys_namenode_list *file;
+ struct pkginfo *pkg;
+ struct fsys_namenode *namenode;
+ int failures = 0;
+
+ if (!*argv)
+ badusage(_("--%s needs at least one package name argument"), cipaction->olong);
+
+ modstatdb_open(msdbrw_readonly);
+
+ while ((thisarg = *argv++) != NULL) {
+ pkg = dpkg_options_parse_pkgname(cipaction, thisarg);
+
+ switch (pkg->status) {
+ case PKG_STAT_NOTINSTALLED:
+ notice(_("package '%s' is not installed"),
+ pkg_name(pkg, pnaw_nonambig));
+ failures++;
+ break;
+ default:
+ ensure_packagefiles_available(pkg);
+ ensure_diversions();
+ file = pkg->files;
+ if (!file) {
+ printf(_("Package '%s' does not contain any files (!)\n"),
+ pkg_name(pkg, pnaw_nonambig));
+ } else {
+ while (file) {
+ namenode = file->namenode;
+ puts(namenode->name);
+ if (namenode->divert && !namenode->divert->camefrom) {
+ if (!namenode->divert->pkgset)
+ printf(_("locally diverted to: %s\n"),
+ namenode->divert->useinstead->name);
+ else if (pkg->set == namenode->divert->pkgset)
+ printf(_("package diverts others to: %s\n"),
+ namenode->divert->useinstead->name);
+ else
+ printf(_("diverted by %s to: %s\n"),
+ namenode->divert->pkgset->name,
+ namenode->divert->useinstead->name);
+ }
+ file = file->next;
+ }
+ }
+ break;
+ }
+
+ if (*argv != NULL)
+ putchar('\n');
+ }
+
+ m_output(stdout, _("<standard output>"));
+ if (failures) {
+ fputs(_("Use dpkg --contents (= dpkg-deb --contents) to list archive files contents.\n"),
+ stderr);
+ m_output(stderr, _("<standard error>"));
+ }
+
+ modstatdb_shutdown();
+
+ return failures;
+}
+
+static void
+pkg_array_show_item(struct pkg_array *array, struct pkginfo *pkg, void *pkg_data)
+{
+ struct pkg_format_node *fmt = pkg_data;
+
+ pkg_format_show(fmt, pkg, &pkg->installed);
+}
+
+static int
+showpackages(const char *const *argv)
+{
+ struct dpkg_error err;
+ struct pkg_array array;
+ struct pkginfo *pkg;
+ struct pkg_format_node *fmt;
+ int i;
+ int rc = 0;
+
+ fmt = pkg_format_parse(showformat, &err);
+ if (!fmt) {
+ notice(_("error in show format: %s"), err.str);
+ dpkg_error_destroy(&err);
+ rc++;
+ return rc;
+ }
+
+ if (!opt_loadavail)
+ modstatdb_open(msdbrw_readonly);
+ else
+ modstatdb_open(msdbrw_readonly | msdbrw_available_readonly);
+
+ pkg_array_init_from_hash(&array);
+ pkg_array_sort(&array, pkg_sorter_by_nonambig_name_arch);
+
+ if (!*argv) {
+ for (i = 0; i < array.n_pkgs; i++) {
+ pkg = array.pkgs[i];
+ if (pkg->status == PKG_STAT_NOTINSTALLED)
+ continue;
+ pkg_format_show(fmt, pkg, &pkg->installed);
+ }
+ } else {
+ rc = pkg_array_match_patterns(&array, pkg_array_show_item, fmt, argv);
+ }
+
+ m_output(stdout, _("<standard output>"));
+ m_output(stderr, _("<standard error>"));
+
+ pkg_array_destroy(&array);
+ pkg_format_free(fmt);
+ modstatdb_shutdown();
+
+ return rc;
+}
+
+static bool
+pkg_infodb_is_internal(const char *filetype)
+{
+ /* Do not expose internal database files. */
+ if (strcmp(filetype, LISTFILE) == 0 ||
+ strcmp(filetype, CONFFILESFILE) == 0)
+ return true;
+
+ if (strlen(filetype) > MAXCONTROLFILENAME)
+ return true;
+
+ return false;
+}
+
+static void
+pkg_infodb_check_filetype(const char *filetype)
+{
+ const char *c;
+
+ /* Validate control file name for sanity. */
+ for (c = "/."; *c; c++)
+ if (strchr(filetype, *c))
+ badusage(_("control file contains %c"), *c);
+}
+
+static void
+pkg_infodb_print_filename(const char *filename, const char *filetype)
+{
+ if (pkg_infodb_is_internal(filetype))
+ return;
+
+ printf("%s\n", filename);
+}
+
+static void
+pkg_infodb_print_filetype(const char *filename, const char *filetype)
+{
+ if (pkg_infodb_is_internal(filetype))
+ return;
+
+ printf("%s\n", filetype);
+}
+
+static void
+control_path_file(struct pkginfo *pkg, const char *control_file)
+{
+ const char *control_pathname;
+ struct stat st;
+
+ control_pathname = pkg_infodb_get_file(pkg, &pkg->installed, control_file);
+ if (stat(control_pathname, &st) < 0)
+ return;
+ if (!S_ISREG(st.st_mode))
+ return;
+
+ pkg_infodb_print_filename(control_pathname, control_file);
+}
+
+static int
+control_path(const char *const *argv)
+{
+ struct pkginfo *pkg;
+ const char *pkgname;
+ const char *control_file;
+
+ pkgname = *argv++;
+ if (!pkgname)
+ badusage(_("--%s needs at least one package name argument"),
+ cipaction->olong);
+
+ control_file = *argv++;
+ if (control_file && *argv)
+ badusage(_("--%s takes at most two arguments"), cipaction->olong);
+
+ if (control_file)
+ pkg_infodb_check_filetype(control_file);
+
+ modstatdb_open(msdbrw_readonly);
+
+ pkg = dpkg_options_parse_pkgname(cipaction, pkgname);
+ if (pkg->status == PKG_STAT_NOTINSTALLED)
+ ohshit(_("package '%s' is not installed"),
+ pkg_name(pkg, pnaw_nonambig));
+
+ if (control_file)
+ control_path_file(pkg, control_file);
+ else
+ pkg_infodb_foreach(pkg, &pkg->installed, pkg_infodb_print_filename);
+
+ modstatdb_shutdown();
+
+ return 0;
+}
+
+static int
+control_list(const char *const *argv)
+{
+ struct pkginfo *pkg;
+ const char *pkgname;
+
+ pkgname = *argv++;
+ if (!pkgname || *argv)
+ badusage(_("--%s takes one package name argument"), cipaction->olong);
+
+ modstatdb_open(msdbrw_readonly);
+
+ pkg = dpkg_options_parse_pkgname(cipaction, pkgname);
+ if (pkg->status == PKG_STAT_NOTINSTALLED)
+ ohshit(_("package '%s' is not installed"), pkg_name(pkg, pnaw_nonambig));
+
+ pkg_infodb_foreach(pkg, &pkg->installed, pkg_infodb_print_filetype);
+
+ modstatdb_shutdown();
+
+ return 0;
+}
+
+static int
+control_show(const char *const *argv)
+{
+ struct pkginfo *pkg;
+ const char *pkgname;
+ const char *filename;
+ const char *control_file;
+
+ pkgname = *argv++;
+ if (!pkgname || !*argv)
+ badusage(_("--%s takes exactly two arguments"),
+ cipaction->olong);
+
+ control_file = *argv++;
+ if (!control_file || *argv)
+ badusage(_("--%s takes exactly two arguments"), cipaction->olong);
+
+ pkg_infodb_check_filetype(control_file);
+
+ modstatdb_open(msdbrw_readonly);
+
+ pkg = dpkg_options_parse_pkgname(cipaction, pkgname);
+ if (pkg->status == PKG_STAT_NOTINSTALLED)
+ ohshit(_("package '%s' is not installed"), pkg_name(pkg, pnaw_nonambig));
+
+ if (pkg_infodb_has_file(pkg, &pkg->installed, control_file))
+ filename = pkg_infodb_get_file(pkg, &pkg->installed, control_file);
+ else
+ ohshit(_("control file '%s' does not exist"), control_file);
+
+ modstatdb_shutdown();
+
+ file_show(filename);
+
+ return 0;
+}
+
+static void
+set_no_pager(const struct cmdinfo *ci, const char *value)
+{
+ pager_enable(false);
+}
+
+static void DPKG_ATTR_NORET
+printversion(const struct cmdinfo *ci, const char *value)
+{
+ printf(_("Debian %s package management program query tool version %s.\n"),
+ DPKGQUERY, PACKAGE_RELEASE);
+ printf(_(
+"This is free software; see the GNU General Public License version 2 or\n"
+"later for copying conditions. There is NO warranty.\n"));
+
+ m_output(stdout, _("<standard output>"));
+
+ exit(0);
+}
+
+static void DPKG_ATTR_NORET
+usage(const struct cmdinfo *ci, const char *value)
+{
+ printf(_(
+"Usage: %s [<option> ...] <command>\n"
+"\n"), DPKGQUERY);
+
+ printf(_(
+"Commands:\n"
+" -s, --status [<package>...] Display package status details.\n"
+" -p, --print-avail [<package>...] Display available version details.\n"
+" -L, --listfiles <package>... List files 'owned' by package(s).\n"
+" -l, --list [<pattern>...] List packages concisely.\n"
+" -W, --show [<pattern>...] Show information on package(s).\n"
+" -S, --search <pattern>... Find package(s) owning file(s).\n"
+" --control-list <package> Print the package control file list.\n"
+" --control-show <package> <file>\n"
+" Show the package control file.\n"
+" -c, --control-path <package> [<file>]\n"
+" Print path for package control file.\n"
+"\n"));
+
+ printf(_(
+" -?, --help Show this help message.\n"
+" --version Show the version.\n"
+"\n"));
+
+ printf(_(
+"Options:\n"
+" --admindir=<directory> Use <directory> instead of %s.\n"
+" --load-avail Use available file on --show and --list.\n"
+" -f|--showformat=<format> Use alternative format for --show.\n"
+"\n"), ADMINDIR);
+
+ printf(_(
+"Format syntax:\n"
+" A format is a string that will be output for each package. The format\n"
+" can include the standard escape sequences \\n (newline), \\r (carriage\n"
+" return) or \\\\ (plain backslash). Package information can be included\n"
+" by inserting variable references to package fields using the ${var[;width]}\n"
+" syntax. Fields will be right-aligned unless the width is negative in which\n"
+" case left alignment will be used.\n"));
+
+ m_output(stdout, _("<standard output>"));
+
+ exit(0);
+}
+
+static const char printforhelp[] = N_(
+"Use --help for help about querying packages.");
+
+static const char *admindir;
+
+/* This table has both the action entries in it and the normal options.
+ * The action entries are made with the ACTION macro, as they all
+ * have a very similar structure. */
+static const struct cmdinfo cmdinfos[]= {
+ ACTION( "listfiles", 'L', act_listfiles, list_files ),
+ ACTION( "status", 's', act_status, print_status ),
+ ACTION( "print-avail", 'p', act_printavail, print_avail ),
+ ACTION( "list", 'l', act_listpackages, listpackages ),
+ ACTION( "search", 'S', act_searchfiles, searchfiles ),
+ ACTION( "show", 'W', act_listpackages, showpackages ),
+ ACTION( "control-path", 'c', act_controlpath, control_path ),
+ ACTION( "control-list", 0, act_controllist, control_list ),
+ ACTION( "control-show", 0, act_controlshow, control_show ),
+
+ { "admindir", 0, 1, NULL, &admindir, NULL },
+ { "load-avail", 0, 0, &opt_loadavail, NULL, NULL, 1 },
+ { "showformat", 'f', 1, NULL, &showformat, NULL },
+ { "no-pager", 0, 0, NULL, NULL, set_no_pager },
+ { "help", '?', 0, NULL, NULL, usage },
+ { "version", 0, 0, NULL, NULL, printversion },
+ { NULL, 0, 0, NULL, NULL, NULL }
+};
+
+int main(int argc, const char *const *argv) {
+ int ret;
+
+ dpkg_set_report_piped_mode(_IOFBF);
+ dpkg_locales_init(PACKAGE);
+ dpkg_program_init("dpkg-query");
+ dpkg_options_parse(&argv, cmdinfos, printforhelp);
+
+ admindir = dpkg_db_set_dir(admindir);
+
+ if (!cipaction) badusage(_("need an action option"));
+
+ fsys_hash_init();
+
+ ret = cipaction->action(argv);
+
+ dpkg_program_done();
+
+ return !!ret;
+}
diff --git a/src/remove.c b/src/remove.c
new file mode 100644
index 0000000..b63d699
--- /dev/null
+++ b/src/remove.c
@@ -0,0 +1,686 @@
+/*
+ * dpkg - main program for package management
+ * remove.c - functionality for removing packages
+ *
+ * Copyright © 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/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <unistd.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/pkg.h>
+#include <dpkg/path.h>
+#include <dpkg/dir.h>
+#include <dpkg/options.h>
+#include <dpkg/triglib.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+
+#include "main.h"
+
+/*
+ * pkgdepcheck may be a virtual pkg.
+ */
+static void checkforremoval(struct pkginfo *pkgtoremove,
+ struct pkgset *pkgdepcheck,
+ enum dep_check *rokp, struct varbuf *raemsgs)
+{
+ struct deppossi *possi;
+ struct pkginfo *depender;
+ enum dep_check ok;
+ struct varbuf_state raemsgs_state;
+
+ for (possi = pkgdepcheck->depended.installed; possi; possi = possi->rev_next) {
+ if (possi->up->type != dep_depends && possi->up->type != dep_predepends) continue;
+ depender= possi->up->up;
+ debug(dbg_depcon, "checking depending package '%s'",
+ pkg_name(depender, pnaw_always));
+ if (depender->status < PKG_STAT_UNPACKED)
+ continue;
+ if (ignore_depends(depender)) {
+ debug(dbg_depcon, "ignoring depending package '%s'",
+ pkg_name(depender, pnaw_always));
+ continue;
+ }
+ if (dependtry >= DEPEND_TRY_CYCLES) {
+ if (findbreakcycle(pkgtoremove))
+ sincenothing = 0;
+ }
+ varbuf_snapshot(raemsgs, &raemsgs_state);
+ ok= dependencies_ok(depender,pkgtoremove,raemsgs);
+ if (ok == DEP_CHECK_HALT &&
+ depender->clientdata &&
+ depender->clientdata->istobe == PKG_ISTOBE_REMOVE)
+ ok = DEP_CHECK_DEFER;
+ if (ok == DEP_CHECK_DEFER)
+ /* Don't burble about reasons for deferral. */
+ varbuf_rollback(raemsgs, &raemsgs_state);
+ if (ok < *rokp) *rokp= ok;
+ }
+}
+
+void deferred_remove(struct pkginfo *pkg) {
+ struct varbuf raemsgs = VARBUF_INIT;
+ struct dependency *dep;
+ enum dep_check rok;
+
+ debug(dbg_general, "deferred_remove package %s",
+ pkg_name(pkg, pnaw_always));
+
+ if (!f_pending && pkg->want != PKG_WANT_UNKNOWN) {
+ if (cipaction->arg_int == act_purge)
+ pkg_set_want(pkg, PKG_WANT_PURGE);
+ else
+ pkg_set_want(pkg, PKG_WANT_DEINSTALL);
+
+ if (!f_noact)
+ modstatdb_note(pkg);
+ }
+
+ ensure_package_clientdata(pkg);
+
+ if (pkg->status == PKG_STAT_NOTINSTALLED) {
+ sincenothing = 0;
+ warning(_("ignoring request to remove %.250s which isn't installed"),
+ pkg_name(pkg, pnaw_nonambig));
+ pkg->clientdata->istobe = PKG_ISTOBE_NORMAL;
+ return;
+ } else if (!f_pending &&
+ pkg->status == PKG_STAT_CONFIGFILES &&
+ cipaction->arg_int != act_purge) {
+ sincenothing = 0;
+ warning(_("ignoring request to remove %.250s, only the config\n"
+ " files of which are on the system; use --purge to remove them too"),
+ pkg_name(pkg, pnaw_nonambig));
+ pkg->clientdata->istobe = PKG_ISTOBE_NORMAL;
+ return;
+ }
+
+ if (pkg->installed.essential && pkg->status != PKG_STAT_CONFIGFILES)
+ forcibleerr(FORCE_REMOVE_ESSENTIAL,
+ _("this is an essential package; it should not be removed"));
+
+ debug(dbg_general, "checking dependencies for remove '%s'",
+ pkg_name(pkg, pnaw_always));
+ rok = DEP_CHECK_OK;
+ checkforremoval(pkg, pkg->set, &rok, &raemsgs);
+ for (dep= pkg->installed.depends; dep; dep= dep->next) {
+ if (dep->type != dep_provides) continue;
+ debug(dbg_depcon, "checking virtual package '%s'", dep->list->ed->name);
+ checkforremoval(pkg, dep->list->ed, &rok, &raemsgs);
+ }
+
+ if (rok == DEP_CHECK_DEFER) {
+ varbuf_destroy(&raemsgs);
+ pkg->clientdata->istobe = PKG_ISTOBE_REMOVE;
+ enqueue_package(pkg);
+ return;
+ } else if (rok == DEP_CHECK_HALT) {
+ sincenothing= 0;
+ varbuf_end_str(&raemsgs);
+ notice(_("dependency problems prevent removal of %s:\n%s"),
+ pkg_name(pkg, pnaw_nonambig), raemsgs.buf);
+ ohshit(_("dependency problems - not removing"));
+ } else if (raemsgs.used) {
+ varbuf_end_str(&raemsgs);
+ notice(_("%s: dependency problems, but removing anyway as you requested:\n%s"),
+ pkg_name(pkg, pnaw_nonambig), raemsgs.buf);
+ }
+ varbuf_destroy(&raemsgs);
+ sincenothing= 0;
+
+ if (pkg->eflag & PKG_EFLAG_REINSTREQ)
+ forcibleerr(FORCE_REMOVE_REINSTREQ,
+ _("package is in a very bad inconsistent state; you should\n"
+ " reinstall it before attempting a removal"));
+
+ ensure_allinstfiles_available();
+ fsys_hash_init();
+
+ if (f_noact) {
+ printf(_("Would remove or purge %s (%s) ...\n"),
+ pkg_name(pkg, pnaw_nonambig),
+ versiondescribe(&pkg->installed.version, vdew_nonambig));
+ pkg_set_status(pkg, PKG_STAT_NOTINSTALLED);
+ pkg->clientdata->istobe = PKG_ISTOBE_NORMAL;
+ return;
+ }
+
+ pkg_conffiles_mark_old(pkg);
+
+ /* Only print and log removal action once. This avoids duplication when
+ * using --remove and --purge in sequence. */
+ if (pkg->status > PKG_STAT_CONFIGFILES) {
+ printf(_("Removing %s (%s) ...\n"), pkg_name(pkg, pnaw_nonambig),
+ versiondescribe(&pkg->installed.version, vdew_nonambig));
+ log_action("remove", pkg, &pkg->installed);
+ }
+
+ trig_activate_packageprocessing(pkg);
+ if (pkg->status >= PKG_STAT_HALFCONFIGURED) {
+ static enum pkgstatus oldpkgstatus;
+
+ oldpkgstatus= pkg->status;
+ pkg_set_status(pkg, PKG_STAT_HALFCONFIGURED);
+ modstatdb_note(pkg);
+ push_cleanup(cu_prermremove, ~ehflag_normaltidy, 2,
+ (void *)pkg, (void *)&oldpkgstatus);
+ maintscript_installed(pkg, PRERMFILE, "pre-removal", "remove", NULL);
+
+ /* Will turn into ‘half-installed’ soon ... */
+ pkg_set_status(pkg, PKG_STAT_UNPACKED);
+ }
+
+ removal_bulk(pkg);
+}
+
+static void
+push_leftover(struct fsys_namenode_list **leftoverp,
+ struct fsys_namenode *namenode)
+{
+ struct fsys_namenode_list *newentry;
+
+ newentry = nfmalloc(sizeof(*newentry));
+ newentry->next= *leftoverp;
+ newentry->namenode= namenode;
+ *leftoverp= newentry;
+}
+
+static void
+removal_bulk_remove_file(const char *filename, const char *filetype)
+{
+ /* We need the postrm and list files for --purge. */
+ if (strcmp(filetype, LISTFILE) == 0 ||
+ strcmp(filetype, POSTRMFILE) == 0)
+ return;
+
+ debug(dbg_stupidlyverbose, "removal_bulk info not postrm or list");
+
+ if (unlink(filename))
+ ohshite(_("unable to delete control info file '%.250s'"), filename);
+
+ debug(dbg_scripts, "removal_bulk info unlinked %s", filename);
+}
+
+static bool
+removal_bulk_file_is_shared(struct pkginfo *pkg, struct fsys_namenode *namenode)
+{
+ struct fsys_node_pkgs_iter *iter;
+ struct pkginfo *otherpkg;
+ bool shared = false;
+
+ if (pkgset_installed_instances(pkg->set) <= 1)
+ return false;
+
+ iter = fsys_node_pkgs_iter_new(namenode);
+ while ((otherpkg = fsys_node_pkgs_iter_next(iter))) {
+ if (otherpkg == pkg)
+ continue;
+ if (otherpkg->set != pkg->set)
+ continue;
+
+ debug(dbg_eachfiledetail, "removal_bulk file shared with %s, skipping",
+ pkg_name(otherpkg, pnaw_always));
+ shared = true;
+ break;
+ }
+ fsys_node_pkgs_iter_free(iter);
+
+ return shared;
+}
+
+static void
+removal_bulk_remove_files(struct pkginfo *pkg)
+{
+ struct fsys_hash_rev_iter rev_iter;
+ struct fsys_namenode_list *leftover;
+ struct fsys_namenode *namenode;
+ static struct varbuf fnvb;
+ struct varbuf_state fnvb_state;
+ struct stat stab;
+
+ pkg_set_status(pkg, PKG_STAT_HALFINSTALLED);
+ modstatdb_note(pkg);
+ push_checkpoint(~ehflag_bombout, ehflag_normaltidy);
+
+ fsys_hash_rev_iter_init(&rev_iter, pkg->files);
+ leftover = NULL;
+ while ((namenode = fsys_hash_rev_iter_next(&rev_iter))) {
+ struct fsys_namenode *usenode;
+ bool is_dir;
+
+ debug(dbg_eachfile, "removal_bulk '%s' flags=%o",
+ namenode->name, namenode->flags);
+
+ usenode = namenodetouse(namenode, pkg, &pkg->installed);
+
+ varbuf_reset(&fnvb);
+ varbuf_add_str(&fnvb, instdir);
+ varbuf_add_str(&fnvb, usenode->name);
+ varbuf_end_str(&fnvb);
+ varbuf_snapshot(&fnvb, &fnvb_state);
+
+ is_dir = stat(fnvb.buf, &stab) == 0 && S_ISDIR(stab.st_mode);
+
+ /* A pkgset can share files between its instances that we
+ * don't want to remove, we just want to forget them. This
+ * applies to shared conffiles too. */
+ if (!is_dir && removal_bulk_file_is_shared(pkg, namenode))
+ continue;
+
+ /* Non-shared conffiles are kept. */
+ if (namenode->flags & FNNF_OLD_CONFF) {
+ push_leftover(&leftover, namenode);
+ continue;
+ }
+
+ if (is_dir) {
+ debug(dbg_eachfiledetail, "removal_bulk is a directory");
+ /* Only delete a directory or a link to one if we're the only
+ * package which uses it. Other files should only be listed
+ * in this package (but we don't check). */
+ if (dir_has_conffiles(namenode, pkg)) {
+ push_leftover(&leftover,namenode);
+ continue;
+ }
+ if (dir_is_used_by_pkg(namenode, pkg, leftover)) {
+ push_leftover(&leftover, namenode);
+ continue;
+ }
+ if (dir_is_used_by_others(namenode, pkg))
+ continue;
+
+ if (strcmp(usenode->name, "/.") == 0) {
+ debug(dbg_eachfiledetail,
+ "removal_bulk '%s' root directory, cannot remove", fnvb.buf);
+ push_leftover(&leftover, namenode);
+ continue;
+ }
+ }
+
+ trig_path_activate(usenode, pkg);
+
+ varbuf_rollback(&fnvb, &fnvb_state);
+ varbuf_add_str(&fnvb, DPKGTEMPEXT);
+ varbuf_end_str(&fnvb);
+ debug(dbg_eachfiledetail, "removal_bulk cleaning temp '%s'", fnvb.buf);
+ path_remove_tree(fnvb.buf);
+
+ varbuf_rollback(&fnvb, &fnvb_state);
+ varbuf_add_str(&fnvb, DPKGNEWEXT);
+ varbuf_end_str(&fnvb);
+ debug(dbg_eachfiledetail, "removal_bulk cleaning new '%s'", fnvb.buf);
+ path_remove_tree(fnvb.buf);
+
+ varbuf_rollback(&fnvb, &fnvb_state);
+ varbuf_end_str(&fnvb);
+
+ debug(dbg_eachfiledetail, "removal_bulk removing '%s'", fnvb.buf);
+ if (!rmdir(fnvb.buf) || errno == ENOENT || errno == ELOOP) continue;
+ if (errno == ENOTEMPTY || errno == EEXIST) {
+ debug(dbg_eachfiledetail,
+ "removal_bulk '%s' was not empty, will try again later",
+ fnvb.buf);
+ push_leftover(&leftover,namenode);
+ continue;
+ } else if (errno == EBUSY || errno == EPERM) {
+ warning(_("while removing %.250s, unable to remove directory '%.250s': "
+ "%s - directory may be a mount point?"),
+ pkg_name(pkg, pnaw_nonambig), namenode->name, strerror(errno));
+ push_leftover(&leftover,namenode);
+ continue;
+ }
+ if (errno != ENOTDIR)
+ ohshite(_("cannot remove '%.250s'"), fnvb.buf);
+ debug(dbg_eachfiledetail, "removal_bulk unlinking '%s'", fnvb.buf);
+ if (secure_unlink(fnvb.buf))
+ ohshite(_("unable to securely remove '%.250s'"), fnvb.buf);
+ }
+ write_filelist_except(pkg, &pkg->installed, leftover, 0);
+ maintscript_installed(pkg, POSTRMFILE, "post-removal", "remove", NULL);
+
+ trig_parse_ci(pkg_infodb_get_file(pkg, &pkg->installed, TRIGGERSCIFILE),
+ trig_cicb_interest_delete, NULL, pkg, &pkg->installed);
+ trig_file_interests_save();
+
+ debug(dbg_general, "removal_bulk cleaning info directory");
+ pkg_infodb_foreach(pkg, &pkg->installed, removal_bulk_remove_file);
+ dir_sync_path(pkg_infodb_get_dir());
+
+ pkg_set_status(pkg, PKG_STAT_CONFIGFILES);
+ pkg->installed.essential = false;
+ modstatdb_note(pkg);
+ push_checkpoint(~ehflag_bombout, ehflag_normaltidy);
+}
+
+static void removal_bulk_remove_leftover_dirs(struct pkginfo *pkg) {
+ struct fsys_hash_rev_iter rev_iter;
+ struct fsys_namenode_list *leftover;
+ struct fsys_namenode *namenode;
+ static struct varbuf fnvb;
+ struct stat stab;
+
+ /* We may have modified this previously. */
+ ensure_packagefiles_available(pkg);
+
+ modstatdb_note(pkg);
+ push_checkpoint(~ehflag_bombout, ehflag_normaltidy);
+
+ fsys_hash_rev_iter_init(&rev_iter, pkg->files);
+ leftover = NULL;
+ while ((namenode = fsys_hash_rev_iter_next(&rev_iter))) {
+ struct fsys_namenode *usenode;
+
+ debug(dbg_eachfile, "removal_bulk '%s' flags=%o",
+ namenode->name, namenode->flags);
+ if (namenode->flags & FNNF_OLD_CONFF) {
+ /* This can only happen if removal_bulk_remove_configfiles() got
+ * interrupted half way. */
+ debug(dbg_eachfiledetail, "removal_bulk expecting only left over dirs, "
+ "ignoring conffile '%s'", namenode->name);
+ continue;
+ }
+
+ usenode = namenodetouse(namenode, pkg, &pkg->installed);
+
+ varbuf_reset(&fnvb);
+ varbuf_add_str(&fnvb, instdir);
+ varbuf_add_str(&fnvb, usenode->name);
+ varbuf_end_str(&fnvb);
+
+ if (!stat(fnvb.buf,&stab) && S_ISDIR(stab.st_mode)) {
+ debug(dbg_eachfiledetail, "removal_bulk is a directory");
+ /* Only delete a directory or a link to one if we're the only
+ * package which uses it. Other files should only be listed
+ * in this package (but we don't check). */
+ if (dir_is_used_by_pkg(namenode, pkg, leftover)) {
+ push_leftover(&leftover, namenode);
+ continue;
+ }
+ if (dir_is_used_by_others(namenode, pkg))
+ continue;
+
+ if (strcmp(usenode->name, "/.") == 0) {
+ debug(dbg_eachfiledetail,
+ "removal_bulk '%s' root directory, cannot remove", fnvb.buf);
+ push_leftover(&leftover, namenode);
+ continue;
+ }
+ }
+
+ trig_path_activate(usenode, pkg);
+
+ debug(dbg_eachfiledetail, "removal_bulk removing '%s'", fnvb.buf);
+ if (!rmdir(fnvb.buf) || errno == ENOENT || errno == ELOOP) continue;
+ if (errno == ENOTEMPTY || errno == EEXIST) {
+ warning(_("while removing %.250s, directory '%.250s' not empty so not removed"),
+ pkg_name(pkg, pnaw_nonambig), namenode->name);
+ push_leftover(&leftover,namenode);
+ continue;
+ } else if (errno == EBUSY || errno == EPERM) {
+ warning(_("while removing %.250s, unable to remove directory '%.250s': "
+ "%s - directory may be a mount point?"),
+ pkg_name(pkg, pnaw_nonambig), namenode->name, strerror(errno));
+ push_leftover(&leftover,namenode);
+ continue;
+ }
+ if (errno != ENOTDIR)
+ ohshite(_("cannot remove '%.250s'"), fnvb.buf);
+
+ if (lstat(fnvb.buf, &stab) == 0 && S_ISLNK(stab.st_mode)) {
+ debug(dbg_eachfiledetail, "removal_bulk is a symlink to a directory");
+
+ if (unlink(fnvb.buf))
+ ohshite(_("cannot remove '%.250s'"), fnvb.buf);
+
+ continue;
+ }
+
+ push_leftover(&leftover,namenode);
+ continue;
+ }
+ write_filelist_except(pkg, &pkg->installed, leftover, 0);
+
+ modstatdb_note(pkg);
+ push_checkpoint(~ehflag_bombout, ehflag_normaltidy);
+}
+
+static void removal_bulk_remove_configfiles(struct pkginfo *pkg) {
+ static const char *const removeconffexts[] = { REMOVECONFFEXTS, NULL };
+ int rc;
+ int conffnameused, conffbasenamelen;
+ char *conffbasename;
+ struct conffile *conff, **lconffp;
+ struct fsys_namenode_list *searchfile;
+ DIR *dsd;
+ struct dirent *de;
+ char *p;
+ const char *const *ext;
+
+ printf(_("Purging configuration files for %s (%s) ...\n"),
+ pkg_name(pkg, pnaw_nonambig),
+ versiondescribe(&pkg->installed.version, vdew_nonambig));
+ log_action("purge", pkg, &pkg->installed);
+ trig_activate_packageprocessing(pkg);
+
+ /* We may have modified this above. */
+ ensure_packagefiles_available(pkg);
+
+ /* We're about to remove the configuration, so remove the note
+ * about which version it was ... */
+ dpkg_version_blank(&pkg->configversion);
+ modstatdb_note(pkg);
+
+ /* Remove from our list any conffiles that aren't ours any more or
+ * are involved in diversions, except if we are the package doing the
+ * diverting. */
+ for (lconffp = &pkg->installed.conffiles; (conff = *lconffp) != NULL; ) {
+ for (searchfile = pkg->files;
+ searchfile && strcmp(searchfile->namenode->name,conff->name);
+ searchfile= searchfile->next);
+ if (!searchfile) {
+ debug(dbg_conff, "removal_bulk conffile not ours any more '%s'",
+ conff->name);
+ *lconffp= conff->next;
+ } else if (searchfile->namenode->divert &&
+ (searchfile->namenode->divert->camefrom ||
+ (searchfile->namenode->divert->useinstead &&
+ searchfile->namenode->divert->pkgset != pkg->set))) {
+ debug(dbg_conff, "removal_bulk conffile '%s' ignored due to diversion",
+ conff->name);
+ *lconffp= conff->next;
+ } else {
+ debug(dbg_conffdetail, "removal_bulk set to new conffile '%s'",
+ conff->name);
+ conff->hash = NEWCONFFILEFLAG;
+ lconffp= &conff->next;
+ }
+ }
+ modstatdb_note(pkg);
+
+ for (conff= pkg->installed.conffiles; conff; conff= conff->next) {
+ struct fsys_namenode *namenode, *usenode;
+ static struct varbuf fnvb, removevb;
+ struct varbuf_state removevb_state;
+
+ if (conff->obsolete) {
+ debug(dbg_conffdetail, "removal_bulk conffile obsolete %s",
+ conff->name);
+ }
+ varbuf_reset(&fnvb);
+ rc = conffderef(pkg, &fnvb, conff->name);
+ debug(dbg_conffdetail, "removal_bulk conffile '%s' (= '%s')",
+ conff->name, rc == -1 ? "<rc == -1>" : fnvb.buf);
+ if (rc == -1)
+ continue;
+
+ namenode = fsys_hash_find_node(conff->name, 0);
+ usenode = namenodetouse(namenode, pkg, &pkg->installed);
+
+ trig_path_activate(usenode, pkg);
+
+ conffnameused = fnvb.used;
+ if (unlink(fnvb.buf) && errno != ENOENT && errno != ENOTDIR)
+ ohshite(_("cannot remove old config file '%.250s' (= '%.250s')"),
+ conff->name, fnvb.buf);
+ p= strrchr(fnvb.buf,'/'); if (!p) continue;
+ *p = '\0';
+ varbuf_reset(&removevb);
+ varbuf_add_str(&removevb, fnvb.buf);
+ varbuf_add_char(&removevb, '/');
+ varbuf_end_str(&removevb);
+ varbuf_snapshot(&removevb, &removevb_state);
+
+ dsd= opendir(removevb.buf);
+ if (!dsd) {
+ int e=errno;
+ debug(dbg_conffdetail, "removal_bulk conffile no dsd %s %s",
+ fnvb.buf, strerror(e)); errno= e;
+ if (errno == ENOENT || errno == ENOTDIR) continue;
+ ohshite(_("cannot read config file directory '%.250s' (from '%.250s')"),
+ fnvb.buf, conff->name);
+ }
+ debug(dbg_conffdetail, "removal_bulk conffile cleaning dsd %s", fnvb.buf);
+ push_cleanup(cu_closedir, ~0, 1, (void *)dsd);
+ *p= '/';
+ conffbasenamelen= strlen(++p);
+ conffbasename= fnvb.buf+conffnameused-conffbasenamelen;
+ while ((de = readdir(dsd)) != NULL) {
+ debug(dbg_stupidlyverbose, "removal_bulk conffile dsd entry='%s'"
+ " conffbasename='%s' conffnameused=%d conffbasenamelen=%d",
+ de->d_name, conffbasename, conffnameused, conffbasenamelen);
+ if (strncmp(de->d_name, conffbasename, conffbasenamelen) == 0) {
+ debug(dbg_stupidlyverbose, "removal_bulk conffile dsd entry starts right");
+ for (ext= removeconffexts; *ext; ext++)
+ if (strcmp(*ext, de->d_name + conffbasenamelen) == 0)
+ goto yes_remove;
+ p= de->d_name+conffbasenamelen;
+ if (*p++ == '~') {
+ while (*p && c_isdigit(*p))
+ p++;
+ if (*p == '~' && !*++p) goto yes_remove;
+ }
+ }
+ debug(dbg_stupidlyverbose, "removal_bulk conffile dsd entry starts wrong");
+ if (de->d_name[0] == '#' &&
+ strncmp(de->d_name + 1, conffbasename, conffbasenamelen) == 0 &&
+ strcmp(de->d_name + 1 + conffbasenamelen, "#") == 0)
+ goto yes_remove;
+ debug(dbg_stupidlyverbose, "removal_bulk conffile dsd entry not it");
+ continue;
+ yes_remove:
+ varbuf_rollback(&removevb, &removevb_state);
+ varbuf_add_str(&removevb, de->d_name);
+ varbuf_end_str(&removevb);
+ debug(dbg_conffdetail, "removal_bulk conffile dsd entry removing '%s'",
+ removevb.buf);
+ if (unlink(removevb.buf) && errno != ENOENT && errno != ENOTDIR)
+ ohshite(_("cannot remove old backup config file '%.250s' (of '%.250s')"),
+ removevb.buf, conff->name);
+ }
+ pop_cleanup(ehflag_normaltidy); /* closedir */
+ }
+
+ /* Remove the conffiles from the file list file. */
+ write_filelist_except(pkg, &pkg->installed, pkg->files,
+ FNNF_OLD_CONFF);
+
+ pkg->installed.conffiles = NULL;
+ modstatdb_note(pkg);
+
+ maintscript_installed(pkg, POSTRMFILE, "post-removal", "purge", NULL);
+}
+
+/*
+ * This is used both by deferred_remove() in this file, and at the end of
+ * process_archive() in archives.c if it needs to finish removing a
+ * conflicting package.
+ */
+void removal_bulk(struct pkginfo *pkg) {
+ bool foundpostrm;
+
+ debug(dbg_general, "removal_bulk package %s", pkg_name(pkg, pnaw_always));
+
+ if (pkg->status == PKG_STAT_HALFINSTALLED ||
+ pkg->status == PKG_STAT_UNPACKED) {
+ removal_bulk_remove_files(pkg);
+ }
+
+ foundpostrm = pkg_infodb_has_file(pkg, &pkg->installed, POSTRMFILE);
+
+ debug(dbg_general, "removal_bulk purging? foundpostrm=%d",foundpostrm);
+
+ if (!foundpostrm && !pkg->installed.conffiles) {
+ /* If there are no config files and no postrm script then we
+ * go straight into ‘purge’. */
+ debug(dbg_general, "removal_bulk no postrm, no conffiles, purging");
+
+ pkg_set_want(pkg, PKG_WANT_PURGE);
+ dpkg_version_blank(&pkg->configversion);
+ } else if (pkg->want == PKG_WANT_PURGE) {
+
+ removal_bulk_remove_configfiles(pkg);
+
+ }
+
+ /* I.e., either of the two branches above. */
+ if (pkg->want == PKG_WANT_PURGE) {
+ const char *filename;
+
+ /* Retry empty directories, and warn on any leftovers that aren't. */
+ removal_bulk_remove_leftover_dirs(pkg);
+
+ filename = pkg_infodb_get_file(pkg, &pkg->installed, LISTFILE);
+ debug(dbg_general, "removal_bulk purge done, removing list '%s'",
+ filename);
+ if (unlink(filename) && errno != ENOENT)
+ ohshite(_("cannot remove old files list"));
+
+ filename = pkg_infodb_get_file(pkg, &pkg->installed, POSTRMFILE);
+ debug(dbg_general, "removal_bulk purge done, removing postrm '%s'",
+ filename);
+ if (unlink(filename) && errno != ENOENT)
+ ohshite(_("can't remove old postrm script"));
+
+ pkg_set_status(pkg, PKG_STAT_NOTINSTALLED);
+ pkg_set_want(pkg, PKG_WANT_UNKNOWN);
+
+ /* This will mess up reverse links, but if we follow them
+ * we won't go back because pkg->status is PKG_STAT_NOTINSTALLED. */
+ pkgbin_blank(&pkg->installed);
+ }
+
+ pkg_reset_eflags(pkg);
+ modstatdb_note(pkg);
+
+ debug(dbg_general, "removal done");
+}
diff --git a/src/script.c b/src/script.c
new file mode 100644
index 0000000..abe65b6
--- /dev/null
+++ b/src/script.c
@@ -0,0 +1,399 @@
+/*
+ * dpkg - main program for package management
+ * script.c - maintainer script routines
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2007-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 <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#ifdef WITH_LIBSELINUX
+#include <selinux/selinux.h>
+#endif
+
+#include <dpkg/i18n.h>
+#include <dpkg/debug.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg.h>
+#include <dpkg/subproc.h>
+#include <dpkg/command.h>
+#include <dpkg/triglib.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+
+#include "main.h"
+
+void
+post_postinst_tasks(struct pkginfo *pkg, enum pkgstatus new_status)
+{
+ if (new_status < PKG_STAT_TRIGGERSAWAITED)
+ pkg_set_status(pkg, new_status);
+ else if (pkg->trigaw.head)
+ pkg_set_status(pkg, PKG_STAT_TRIGGERSAWAITED);
+ else if (pkg->trigpend_head)
+ pkg_set_status(pkg, PKG_STAT_TRIGGERSPENDING);
+ else
+ pkg_set_status(pkg, PKG_STAT_INSTALLED);
+ modstatdb_note(pkg);
+
+ debug(dbg_triggersdetail, "post_postinst_tasks - trig_incorporate");
+ trig_incorporate(modstatdb_get_status());
+}
+
+static void
+post_script_tasks(void)
+{
+ debug(dbg_triggersdetail, "post_script_tasks - ensure_diversions");
+ ensure_diversions();
+
+ debug(dbg_triggersdetail, "post_script_tasks - trig_incorporate");
+ trig_incorporate(modstatdb_get_status());
+}
+
+static void
+cu_post_script_tasks(int argc, void **argv)
+{
+ post_script_tasks();
+}
+
+static void
+setexecute(const char *path, struct stat *stab)
+{
+ if ((stab->st_mode & 0555) == 0555)
+ return;
+ if (!chmod(path, 0755))
+ return;
+ ohshite(_("unable to set execute permissions on '%.250s'"), path);
+}
+
+/**
+ * Returns the path to the script inside the chroot.
+ */
+static const char *
+maintscript_pre_exec(struct command *cmd)
+{
+ const char *admindir = dpkg_db_get_dir();
+ const char *changedir;
+ size_t instdirlen = strlen(instdir);
+
+ if (instdirlen > 0 && in_force(FORCE_SCRIPT_CHROOTLESS))
+ changedir = instdir;
+ else
+ changedir = "/";
+
+ if (instdirlen > 0 && !in_force(FORCE_SCRIPT_CHROOTLESS)) {
+ int rc;
+
+ if (strncmp(admindir, instdir, instdirlen) != 0)
+ ohshit(_("admindir must be inside instdir for dpkg to work properly"));
+ if (setenv("DPKG_ADMINDIR", admindir + instdirlen, 1) < 0)
+ ohshite(_("unable to setenv for subprocesses"));
+ if (setenv("DPKG_ROOT", "", 1) < 0)
+ ohshite(_("unable to setenv for subprocesses"));
+
+ rc = chroot(instdir);
+ if (rc && in_force(FORCE_NON_ROOT) && errno == EPERM)
+ ohshit(_("not enough privileges to change root "
+ "directory with --force-not-root, consider "
+ "using --force-script-chrootless?"));
+ else if (rc)
+ ohshite(_("failed to chroot to '%.250s'"), instdir);
+ }
+ /* Switch to a known good directory to give the maintainer script
+ * a saner environment, also needed after the chroot(). */
+ if (chdir(changedir))
+ ohshite(_("failed to chdir to '%.255s'"), changedir);
+ if (debug_has_flag(dbg_scripts)) {
+ struct varbuf args = VARBUF_INIT;
+ const char **argv = cmd->argv;
+
+ while (*++argv) {
+ varbuf_add_char(&args, ' ');
+ varbuf_add_str(&args, *argv);
+ }
+ varbuf_end_str(&args);
+ debug(dbg_scripts, "fork/exec %s (%s )", cmd->filename,
+ args.buf);
+ varbuf_destroy(&args);
+ }
+ if (instdirlen == 0 || in_force(FORCE_SCRIPT_CHROOTLESS))
+ return cmd->filename;
+
+ if (strlen(cmd->filename) < instdirlen)
+ internerr("maintscript name '%s' length < instdir length %zd",
+ cmd->filename, instdirlen);
+
+ return cmd->filename + instdirlen;
+}
+
+/**
+ * Set a new security execution context for the maintainer script.
+ *
+ * Try to create a new execution context based on the current one and the
+ * specific maintainer script filename. If it's the same as the current
+ * one, use the given fallback.
+ */
+static int
+maintscript_set_exec_context(struct command *cmd)
+{
+#ifdef WITH_LIBSELINUX
+ return setexecfilecon(cmd->filename, "dpkg_script_t");
+#else
+ return 0;
+#endif
+
+}
+
+static int
+maintscript_exec(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ struct command *cmd, struct stat *stab, int warn)
+{
+ pid_t pid;
+ int rc;
+
+ setexecute(cmd->filename, stab);
+
+ push_cleanup(cu_post_script_tasks, ehflag_bombout, 0);
+
+ pid = subproc_fork();
+ if (pid == 0) {
+ char *pkg_count;
+ const char *maintscript_debug;
+
+ pkg_count = str_fmt("%d", pkgset_installed_instances(pkg->set));
+
+ maintscript_debug = debug_has_flag(dbg_scripts) ? "1" : "0";
+
+ if (setenv("DPKG_MAINTSCRIPT_PACKAGE", pkg->set->name, 1) ||
+ setenv("DPKG_MAINTSCRIPT_PACKAGE_REFCOUNT", pkg_count, 1) ||
+ setenv("DPKG_MAINTSCRIPT_ARCH", pkgbin->arch->name, 1) ||
+ setenv("DPKG_MAINTSCRIPT_NAME", cmd->argv[0], 1) ||
+ setenv("DPKG_MAINTSCRIPT_DEBUG", maintscript_debug, 1) ||
+ setenv("DPKG_RUNNING_VERSION", PACKAGE_VERSION, 1))
+ ohshite(_("unable to setenv for maintainer script"));
+
+ cmd->filename = cmd->argv[0] = maintscript_pre_exec(cmd);
+
+ if (maintscript_set_exec_context(cmd) < 0)
+ ohshite(_("cannot set security execution context for "
+ "maintainer script"));
+
+ command_exec(cmd);
+ }
+ subproc_signals_ignore(cmd->name);
+ rc = subproc_reap(pid, cmd->name, warn);
+ subproc_signals_restore();
+
+ pop_cleanup(ehflag_normaltidy);
+
+ return rc;
+}
+
+static int
+vmaintscript_installed(struct pkginfo *pkg, const char *scriptname,
+ const char *desc, va_list args)
+{
+ struct command cmd;
+ const char *scriptpath;
+ struct stat stab;
+ char *buf;
+
+ scriptpath = pkg_infodb_get_file(pkg, &pkg->installed, scriptname);
+ m_asprintf(&buf, _("installed %s package %s script"),
+ pkg_name(pkg, pnaw_nonambig), desc);
+
+ command_init(&cmd, scriptpath, buf);
+ command_add_arg(&cmd, scriptname);
+ command_add_argv(&cmd, args);
+
+ if (stat(scriptpath, &stab)) {
+ command_destroy(&cmd);
+
+ if (errno == ENOENT) {
+ debug(dbg_scripts,
+ "vmaintscript_installed nonexistent %s",
+ scriptname);
+ free(buf);
+ return 0;
+ }
+ ohshite(_("unable to stat %s '%.250s'"), buf, scriptpath);
+ }
+ maintscript_exec(pkg, &pkg->installed, &cmd, &stab, 0);
+
+ command_destroy(&cmd);
+ free(buf);
+
+ return 1;
+}
+
+/*
+ * All ...'s in maintscript_* are const char *'s.
+ */
+
+int
+maintscript_installed(struct pkginfo *pkg, const char *scriptname,
+ const char *desc, ...)
+{
+ va_list args;
+ int rc;
+
+ va_start(args, desc);
+ rc = vmaintscript_installed(pkg, scriptname, desc, args);
+ va_end(args);
+
+ if (rc)
+ post_script_tasks();
+
+ return rc;
+}
+
+int
+maintscript_postinst(struct pkginfo *pkg, ...)
+{
+ va_list args;
+ int rc;
+
+ va_start(args, pkg);
+ rc = vmaintscript_installed(pkg, POSTINSTFILE, "post-installation", args);
+ va_end(args);
+
+ if (rc)
+ ensure_diversions();
+
+ return rc;
+}
+
+int
+maintscript_new(struct pkginfo *pkg, const char *scriptname,
+ const char *desc, const char *cidir, char *cidirrest, ...)
+{
+ struct command cmd;
+ struct stat stab;
+ va_list args;
+ char *buf;
+
+ strcpy(cidirrest, scriptname);
+ m_asprintf(&buf, _("new %s package %s script"),
+ pkg_name(pkg, pnaw_nonambig), desc);
+
+ va_start(args, cidirrest);
+ command_init(&cmd, cidir, buf);
+ command_add_arg(&cmd, scriptname);
+ command_add_argv(&cmd, args);
+ va_end(args);
+
+ if (stat(cidir, &stab)) {
+ command_destroy(&cmd);
+
+ if (errno == ENOENT) {
+ debug(dbg_scripts,
+ "maintscript_new nonexistent %s '%s'",
+ scriptname, cidir);
+ free(buf);
+ return 0;
+ }
+ ohshite(_("unable to stat %s '%.250s'"), buf, cidir);
+ }
+ maintscript_exec(pkg, &pkg->available, &cmd, &stab, 0);
+
+ command_destroy(&cmd);
+ free(buf);
+ post_script_tasks();
+
+ return 1;
+}
+
+int
+maintscript_fallback(struct pkginfo *pkg,
+ const char *scriptname, const char *desc,
+ const char *cidir, char *cidirrest,
+ const char *ifok, const char *iffallback)
+{
+ struct command cmd;
+ const char *oldscriptpath;
+ struct stat stab;
+ char *buf;
+
+ oldscriptpath = pkg_infodb_get_file(pkg, &pkg->installed, scriptname);
+ m_asprintf(&buf, _("old %s package %s script"),
+ pkg_name(pkg, pnaw_nonambig), desc);
+
+ command_init(&cmd, oldscriptpath, buf);
+ command_add_args(&cmd, scriptname, ifok,
+ versiondescribe(&pkg->available.version, vdew_nonambig),
+ NULL);
+
+ if (stat(oldscriptpath, &stab)) {
+ if (errno == ENOENT) {
+ debug(dbg_scripts,
+ "maintscript_fallback nonexistent %s '%s'",
+ scriptname, oldscriptpath);
+ command_destroy(&cmd);
+ free(buf);
+ return 0;
+ }
+ warning(_("unable to stat %s '%.250s': %s"),
+ cmd.name, oldscriptpath, strerror(errno));
+ } else {
+ if (!maintscript_exec(pkg, &pkg->installed, &cmd, &stab, SUBPROC_WARN)) {
+ command_destroy(&cmd);
+ free(buf);
+ post_script_tasks();
+ return 1;
+ }
+ }
+ notice(_("trying script from the new package instead ..."));
+
+ strcpy(cidirrest, scriptname);
+ m_asprintf(&buf, _("new %s package %s script"),
+ pkg_name(pkg, pnaw_nonambig), desc);
+
+ command_destroy(&cmd);
+ command_init(&cmd, cidir, buf);
+ command_add_args(&cmd, scriptname, iffallback,
+ versiondescribe(&pkg->installed.version, vdew_nonambig),
+ versiondescribe(&pkg->available.version, vdew_nonambig),
+ NULL);
+
+ if (stat(cidir, &stab)) {
+ command_destroy(&cmd);
+
+ if (errno == ENOENT)
+ ohshit(_("there is no script in the new version of the package - giving up"));
+ else
+ ohshite(_("unable to stat %s '%.250s'"), buf, cidir);
+ }
+
+ maintscript_exec(pkg, &pkg->available, &cmd, &stab, 0);
+ notice(_("... it looks like that went OK"));
+
+ command_destroy(&cmd);
+ free(buf);
+ post_script_tasks();
+
+ return 1;
+}
diff --git a/src/select.c b/src/select.c
new file mode 100644
index 0000000..a49d6c3
--- /dev/null
+++ b/src/select.c
@@ -0,0 +1,240 @@
+/*
+ * dpkg - main program for package management
+ * select.c - by-hand (rather than dselect-based) package selection
+ *
+ * Copyright © 1995,1996 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2006, 2008-2015 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 <fnmatch.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/pkg-array.h>
+#include <dpkg/pkg-show.h>
+#include <dpkg/pkg-spec.h>
+#include <dpkg/options.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+
+#include "main.h"
+
+static void getsel1package(struct pkginfo *pkg) {
+ const char *pkgname;
+ int l;
+
+ if (pkg->want == PKG_WANT_UNKNOWN)
+ return;
+ pkgname = pkg_name(pkg, pnaw_nonambig);
+ l = strlen(pkgname);
+ l >>= 3;
+ l = 6 - l;
+ if (l < 1)
+ l = 1;
+ printf("%s%.*s%s\n", pkgname, l, "\t\t\t\t\t\t", pkg_want_name(pkg));
+}
+
+int
+getselections(const char *const *argv)
+{
+ struct pkg_array array;
+ struct pkginfo *pkg;
+ const char *thisarg;
+ int i, found;
+
+ modstatdb_open(msdbrw_readonly);
+
+ pkg_array_init_from_hash(&array);
+ pkg_array_sort(&array, pkg_sorter_by_nonambig_name_arch);
+
+ if (!*argv) {
+ for (i = 0; i < array.n_pkgs; i++) {
+ pkg = array.pkgs[i];
+ if (pkg->status == PKG_STAT_NOTINSTALLED)
+ continue;
+ getsel1package(pkg);
+ }
+ } else {
+ while ((thisarg= *argv++)) {
+ struct pkg_spec pkgspec;
+
+ found= 0;
+ pkg_spec_init(&pkgspec, PKG_SPEC_PATTERNS | PKG_SPEC_ARCH_WILDCARD);
+ pkg_spec_parse(&pkgspec, thisarg);
+
+ for (i = 0; i < array.n_pkgs; i++) {
+ pkg = array.pkgs[i];
+ if (!pkg_spec_match_pkg(&pkgspec, pkg, &pkg->installed))
+ continue;
+ getsel1package(pkg); found++;
+ }
+ if (!found)
+ notice(_("no packages found matching %s"), thisarg);
+
+ pkg_spec_destroy(&pkgspec);
+ }
+ }
+
+ m_output(stdout, _("<standard output>"));
+ m_output(stderr, _("<standard error>"));
+
+ pkg_array_destroy(&array);
+
+ modstatdb_shutdown();
+
+ return 0;
+}
+
+int
+setselections(const char *const *argv)
+{
+ enum modstatdb_rw msdbflags;
+ const struct namevalue *nv;
+ struct pkginfo *pkg;
+ int c, lno;
+ struct varbuf namevb = VARBUF_INIT;
+ struct varbuf selvb = VARBUF_INIT;
+ bool db_possibly_outdated = false;
+
+ if (*argv)
+ badusage(_("--%s takes no arguments"), cipaction->olong);
+
+ msdbflags = msdbrw_available_readonly;
+ if (f_noact)
+ msdbflags |= msdbrw_readonly;
+ else
+ msdbflags |= msdbrw_write;
+
+ modstatdb_open(msdbflags);
+ pkg_infodb_upgrade();
+
+ lno= 1;
+ for (;;) {
+ struct dpkg_error err;
+
+ do {
+ c = getchar();
+ if (c == '\n')
+ lno++;
+ } while (c != EOF && c_isspace(c));
+ if (c == EOF) break;
+ if (c == '#') {
+ do { c= getchar(); if (c == '\n') lno++; } while (c != EOF && c != '\n');
+ continue;
+ }
+
+ varbuf_reset(&namevb);
+ while (!c_isspace(c)) {
+ varbuf_add_char(&namevb, c);
+ c= getchar();
+ if (c == EOF)
+ ohshit(_("unexpected end of file in package name at line %d"), lno);
+ if (c == '\n') ohshit(_("unexpected end of line in package name at line %d"),lno);
+ }
+ varbuf_end_str(&namevb);
+
+ while (c != EOF && c_isspace(c)) {
+ c= getchar();
+ if (c == EOF)
+ ohshit(_("unexpected end of file after package name at line %d"), lno);
+ if (c == '\n') ohshit(_("unexpected end of line after package name at line %d"),lno);
+ }
+
+ varbuf_reset(&selvb);
+ while (c != EOF && !c_isspace(c)) {
+ varbuf_add_char(&selvb, c);
+ c= getchar();
+ }
+ varbuf_end_str(&selvb);
+
+ while (c != EOF && c != '\n') {
+ c= getchar();
+ if (!c_isspace(c))
+ ohshit(_("unexpected data after package and selection at line %d"),lno);
+ }
+ pkg = pkg_spec_parse_pkg(namevb.buf, &err);
+ if (pkg == NULL)
+ ohshit(_("illegal package name at line %d: %.250s"), lno, err.str);
+
+ if (!pkg_is_informative(pkg, &pkg->installed) &&
+ !pkg_is_informative(pkg, &pkg->available)) {
+ db_possibly_outdated = true;
+ warning(_("package not in status nor available database at line %d: %.250s"), lno, namevb.buf);
+ lno++;
+ continue;
+ }
+
+ nv = namevalue_find_by_name(wantinfos, selvb.buf);
+ if (nv == NULL)
+ ohshit(_("unknown wanted status at line %d: %.250s"), lno, selvb.buf);
+
+ pkg_set_want(pkg, nv->value);
+ if (c == EOF) break;
+ lno++;
+ }
+ if (ferror(stdin)) ohshite(_("read error on standard input"));
+ modstatdb_shutdown();
+ varbuf_destroy(&namevb);
+ varbuf_destroy(&selvb);
+
+ if (db_possibly_outdated)
+ warning(_("found unknown packages; this might mean the available database\n"
+ "is outdated, and needs to be updated through a frontend method;\n"
+ "please see the FAQ <https://wiki.debian.org/Teams/Dpkg/FAQ>"));
+
+ return 0;
+}
+
+int
+clearselections(const char *const *argv)
+{
+ enum modstatdb_rw msdbflags;
+ struct pkg_hash_iter *iter;
+ struct pkginfo *pkg;
+
+ if (*argv)
+ badusage(_("--%s takes no arguments"), cipaction->olong);
+
+ if (f_noact)
+ msdbflags = msdbrw_readonly;
+ else
+ msdbflags = msdbrw_write;
+
+ modstatdb_open(msdbflags);
+ pkg_infodb_upgrade();
+
+ iter = pkg_hash_iter_new();
+ while ((pkg = pkg_hash_iter_next_pkg(iter))) {
+ if (!pkg->installed.essential)
+ pkg_set_want(pkg, PKG_WANT_DEINSTALL);
+ }
+ pkg_hash_iter_free(iter);
+
+ modstatdb_shutdown();
+
+ return 0;
+}
+
diff --git a/src/selinux.c b/src/selinux.c
new file mode 100644
index 0000000..2218bd2
--- /dev/null
+++ b/src/selinux.c
@@ -0,0 +1,128 @@
+/*
+ * dpkg - main program for package management
+ * selinux.c - SE Linux support
+ *
+ * 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/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <unistd.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+
+#ifdef WITH_LIBSELINUX
+#include <selinux/selinux.h>
+#include <selinux/avc.h>
+#include <selinux/label.h>
+#endif
+
+#include "main.h"
+
+#ifdef WITH_LIBSELINUX
+static struct selabel_handle *sehandle;
+#endif
+
+void
+dpkg_selabel_load(void)
+{
+#ifdef WITH_LIBSELINUX
+ static int selinux_enabled = -1;
+
+ if (selinux_enabled < 0) {
+ int rc;
+
+ /* Set selinux_enabled if it is not already set (singleton). */
+ selinux_enabled = (in_force(FORCE_SECURITY_MAC) &&
+ is_selinux_enabled() > 0);
+ if (!selinux_enabled)
+ return;
+
+ /* Open the SELinux status notification channel, with fallback
+ * enabled for older kernels. */
+ rc = selinux_status_open(1);
+ if (rc < 0)
+ ohshit(_("cannot open security status notification channel"));
+
+ /* XXX: We could use selinux_set_callback() to redirect the
+ * errors from the other SELinux calls, but that does not seem
+ * worth it right now. */
+ } else if (selinux_enabled && selinux_status_updated()) {
+ /* The SELinux policy got updated in the kernel, usually after
+ * upgrading the package shipping it, we need to reload. */
+ selabel_close(sehandle);
+ } else {
+ /* SELinux is either disabled or it does not need a reload. */
+ return;
+ }
+
+ sehandle = selabel_open(SELABEL_CTX_FILE, NULL, 0);
+ if (sehandle == NULL && security_getenforce() == 1)
+ ohshite(_("cannot get security labeling handle"));
+#endif
+}
+
+void
+dpkg_selabel_set_context(const char *matchpath, const char *path, mode_t mode)
+{
+#ifdef WITH_LIBSELINUX
+ security_context_t scontext = NULL;
+ int ret;
+
+ /* If SELinux is not enabled just do nothing. */
+ if (sehandle == NULL)
+ return;
+
+ /*
+ * We use the _raw function variants here so that no translation
+ * happens from computer to human readable forms, to avoid issues
+ * when mcstransd has disappeared during the unpack process.
+ */
+
+ /* Do nothing if we can't figure out what the context is, or if it has
+ * no context; in which case the default context shall be applied. */
+ ret = selabel_lookup_raw(sehandle, &scontext, matchpath, mode & S_IFMT);
+ if (ret == -1 || (ret == 0 && scontext == NULL))
+ return;
+
+ ret = lsetfilecon_raw(path, scontext);
+ if (ret < 0 && errno != ENOTSUP)
+ ohshite(_("cannot set security context for file object '%s'"),
+ path);
+
+ freecon(scontext);
+#endif /* WITH_LIBSELINUX */
+}
+
+void
+dpkg_selabel_close(void)
+{
+#ifdef WITH_LIBSELINUX
+ if (sehandle == NULL)
+ return;
+
+ selinux_status_close();
+ selabel_close(sehandle);
+ sehandle = NULL;
+#endif
+}
diff --git a/src/statcmd.c b/src/statcmd.c
new file mode 100644
index 0000000..33a426a
--- /dev/null
+++ b/src/statcmd.c
@@ -0,0 +1,427 @@
+/*
+ * dpkg-statoverride - override ownership and mode of files
+ *
+ * Copyright © 2000, 2001 Wichert Akkerman <wakkerma@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/>.
+ *
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#if HAVE_LOCALE_H
+#include <locale.h>
+#endif
+#include <string.h>
+#include <grp.h>
+#include <pwd.h>
+#include <fnmatch.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.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/glob.h>
+#include <dpkg/db-fsys.h>
+#include <dpkg/options.h>
+
+#include "force.h"
+#include "main.h"
+
+static const char printforhelp[] = N_(
+"Use --help for help about overriding file stat information.");
+
+static void DPKG_ATTR_NORET
+printversion(const struct cmdinfo *cip, const char *value)
+{
+ printf(_("Debian %s version %s.\n"), dpkg_get_progname(),
+ PACKAGE_RELEASE);
+
+ printf(_(
+"This is free software; see the GNU General Public License version 2 or\n"
+"later for copying conditions. There is NO warranty.\n"));
+
+ m_output(stdout, _("<standard output>"));
+
+ exit(0);
+}
+
+static void DPKG_ATTR_NORET
+usage(const struct cmdinfo *cip, const char *value)
+{
+ printf(_(
+"Usage: %s [<option> ...] <command>\n"
+"\n"), dpkg_get_progname());
+
+ printf(_(
+"Commands:\n"
+" --add <owner> <group> <mode> <path>\n"
+" add a new <path> entry into the database.\n"
+" --remove <path> remove <path> from the database.\n"
+" --list [<glob-pattern>] list current overrides in the database.\n"
+"\n"));
+
+ printf(_(
+"Options:\n"
+" --admindir <directory> set the directory with the statoverride file.\n"
+" --instdir <directory> set the root directory, but not the admin dir.\n"
+" --root <directory> set the directory of the root filesystem.\n"
+" --update immediately update <path> permissions.\n"
+" --force deprecated alias for --force-all.\n"
+" --force-... override problems (see --force-help).\n"
+" --no-force-... stop when problems encountered.\n"
+" --refuse-... ditto.\n"
+" --quiet quiet operation, minimal output.\n"
+" --help show this help message.\n"
+" --version show the version.\n"
+"\n"));
+
+ m_output(stdout, _("<standard output>"));
+
+ exit(0);
+}
+
+#define FORCE_STATCMD_MASK \
+ FORCE_SECURITY_MAC | FORCE_STATOVERRIDE_ADD | FORCE_STATOVERRIDE_DEL
+
+static const char *admindir;
+const char *instdir;
+
+static int opt_verbose = 1;
+static int opt_update = 0;
+
+static void
+set_instdir(const struct cmdinfo *cip, const char *value)
+{
+ instdir = dpkg_fsys_set_dir(value);
+}
+
+static void
+set_root(const struct cmdinfo *cip, const char *value)
+{
+ instdir = dpkg_fsys_set_dir(value);
+ admindir = dpkg_fsys_get_path(ADMINDIR);
+}
+
+static char *
+path_cleanup(const char *path)
+{
+ char *new_path = m_strdup(path);
+
+ path_trim_slash_slashdot(new_path);
+ if (opt_verbose && strcmp(path, new_path) != 0)
+ warning(_("stripping trailing /"));
+
+ return new_path;
+}
+
+static struct file_stat *
+statdb_node_new(const char *user, const char *group, const char *mode)
+{
+ struct file_stat *filestat;
+
+ filestat = nfmalloc(sizeof(*filestat));
+
+ filestat->uid = statdb_parse_uid(user);
+ if (filestat->uid == (uid_t)-1)
+ ohshit(_("user '%s' does not exist"), user);
+ filestat->uname = NULL;
+ filestat->gid = statdb_parse_gid(group);
+ if (filestat->gid == (gid_t)-1)
+ ohshit(_("group '%s' does not exist"), group);
+ filestat->gname = NULL;
+ filestat->mode = statdb_parse_mode(mode);
+
+ return filestat;
+}
+
+static struct file_stat **
+statdb_node_find(const char *filename)
+{
+ struct fsys_namenode *file;
+
+ file = fsys_hash_find_node(filename, 0);
+
+ return &file->statoverride;
+}
+
+static int
+statdb_node_remove(const char *filename)
+{
+ struct fsys_namenode *file;
+
+ file = fsys_hash_find_node(filename, FHFF_NONE);
+ if (!file || !file->statoverride)
+ return 0;
+
+ file->statoverride = NULL;
+
+ return 1;
+}
+
+static void
+statdb_node_apply(const char *filename, struct file_stat *filestat)
+{
+ if (chown(filename, filestat->uid, filestat->gid) < 0)
+ ohshite(_("error setting ownership of '%.255s'"), filename);
+ if (chmod(filename, filestat->mode & ~S_IFMT))
+ ohshite(_("error setting permissions of '%.255s'"), filename);
+
+ dpkg_selabel_load();
+ dpkg_selabel_set_context(filename, filename, filestat->mode);
+ dpkg_selabel_close();
+}
+
+static void
+statdb_node_print(FILE *out, struct fsys_namenode *file)
+{
+ struct file_stat *filestat = file->statoverride;
+ struct passwd *pw;
+ struct group *gr;
+
+ if (!filestat)
+ return;
+
+ pw = getpwuid(filestat->uid);
+ if (pw)
+ fprintf(out, "%s ", pw->pw_name);
+ else if (filestat->uname)
+ fprintf(out, "%s ", filestat->uname);
+ else
+ fprintf(out, "#%d ", filestat->uid);
+
+ gr = getgrgid(filestat->gid);
+ if (gr)
+ fprintf(out, "%s ", gr->gr_name);
+ else if (filestat->gname)
+ fprintf(out, "%s ", filestat->gname);
+ else
+ fprintf(out, "#%d ", filestat->gid);
+
+ fprintf(out, "%o %s\n", filestat->mode & ~S_IFMT, file->name);
+}
+
+static void
+statdb_write(void)
+{
+ char *dbname;
+ struct atomic_file *dbfile;
+ struct fsys_hash_iter *iter;
+ struct fsys_namenode *file;
+
+ dbname = dpkg_db_get_path(STATOVERRIDEFILE);
+ dbfile = atomic_file_new(dbname, ATOMIC_FILE_BACKUP);
+ atomic_file_open(dbfile);
+
+ iter = fsys_hash_iter_new();
+ while ((file = fsys_hash_iter_next(iter)))
+ statdb_node_print(dbfile->fp, file);
+ fsys_hash_iter_free(iter);
+
+ atomic_file_sync(dbfile);
+ atomic_file_close(dbfile);
+ atomic_file_commit(dbfile);
+ atomic_file_free(dbfile);
+
+ dir_sync_path(dpkg_db_get_dir());
+
+ free(dbname);
+}
+
+static int
+statoverride_add(const char *const *argv)
+{
+ const char *user = argv[0];
+ const char *group = argv[1];
+ const char *mode = argv[2];
+ const char *path = argv[3];
+ char *filename;
+ struct file_stat **filestat;
+
+ if (!user || !group || !mode || !path || argv[4])
+ badusage(_("--%s needs four arguments"), cipaction->olong);
+
+ if (strchr(path, '\n'))
+ badusage(_("path may not contain newlines"));
+
+ filename = path_cleanup(path);
+
+ filestat = statdb_node_find(filename);
+ if (*filestat != NULL) {
+ if (in_force(FORCE_STATOVERRIDE_ADD))
+ warning(_("an override for '%s' already exists, "
+ "but --force specified so will be ignored"),
+ filename);
+ else
+ ohshit(_("an override for '%s' already exists; "
+ "aborting"), filename);
+ }
+
+ *filestat = statdb_node_new(user, group, mode);
+
+ if (opt_update) {
+ struct stat st;
+ struct varbuf realfilename = VARBUF_INIT;
+
+ varbuf_add_str(&realfilename, instdir);
+ varbuf_add_str(&realfilename, filename);
+ varbuf_end_str(&realfilename);
+
+ if (stat(realfilename.buf, &st) == 0) {
+ (*filestat)->mode |= st.st_mode & S_IFMT;
+ statdb_node_apply(realfilename.buf, *filestat);
+ } else if (opt_verbose) {
+ warning(_("--update given but %s does not exist"),
+ realfilename.buf);
+ }
+
+ varbuf_destroy(&realfilename);
+ }
+
+ statdb_write();
+
+ free(filename);
+
+ return 0;
+}
+
+static int
+statoverride_remove(const char *const *argv)
+{
+ const char *path = argv[0];
+ char *filename;
+
+ if (!path || argv[1])
+ badusage(_("--%s needs a single argument"), "remove");
+
+ filename = path_cleanup(path);
+
+ if (!statdb_node_remove(filename)) {
+ if (opt_verbose)
+ warning(_("no override present"));
+ if (in_force(FORCE_STATOVERRIDE_DEL))
+ return 0;
+ else
+ return 2;
+ }
+
+ if (opt_update && opt_verbose)
+ warning(_("--update is useless for --remove"));
+
+ statdb_write();
+
+ free(filename);
+
+ return 0;
+}
+
+static int
+statoverride_list(const char *const *argv)
+{
+ struct fsys_hash_iter *iter;
+ struct fsys_namenode *file;
+ const char *thisarg;
+ struct glob_node *glob_list = NULL;
+ int ret = 1;
+
+ while ((thisarg = *argv++)) {
+ char *pattern = path_cleanup(thisarg);
+
+ glob_list_prepend(&glob_list, pattern);
+ }
+ if (glob_list == NULL)
+ glob_list_prepend(&glob_list, m_strdup("*"));
+
+ iter = fsys_hash_iter_new();
+ while ((file = fsys_hash_iter_next(iter))) {
+ struct glob_node *g;
+
+ for (g = glob_list; g; g = g->next) {
+ if (fnmatch(g->pattern, file->name, 0) == 0) {
+ statdb_node_print(stdout, file);
+ ret = 0;
+ break;
+ }
+ }
+ }
+ fsys_hash_iter_free(iter);
+
+ glob_list_free(glob_list);
+
+ return ret;
+}
+
+static void
+set_force_obsolete(const struct cmdinfo *cip, const char *value)
+{
+ warning(_("deprecated --%s option; use --%s instead"),
+ cip->olong, "force-all");
+ set_force(FORCE_ALL);
+}
+
+static const struct cmdinfo cmdinfos[] = {
+ ACTION("add", 0, act_install, statoverride_add),
+ ACTION("remove", 0, act_remove, statoverride_remove),
+ ACTION("list", 0, act_listfiles, statoverride_list),
+
+ { "admindir", 0, 1, NULL, &admindir, NULL },
+ { "instdir", 0, 1, NULL, NULL, set_instdir, 0 },
+ { "root", 0, 1, NULL, NULL, set_root, 0 },
+ { "quiet", 0, 0, &opt_verbose, NULL, NULL, 0 },
+ { "force", 0, 0, NULL, NULL, set_force_obsolete },
+ { "force", 0, 2, NULL, NULL, set_force_option, 1 },
+ { "no-force", 0, 2, NULL, NULL, set_force_option, 0 },
+ { "refuse", 0, 2, NULL, NULL, set_force_option, 0 },
+ { "update", 0, 0, &opt_update, NULL, NULL, 1 },
+ { "help", '?', 0, NULL, NULL, usage },
+ { "version", 0, 0, NULL, NULL, printversion },
+ { NULL, 0 }
+};
+
+int
+main(int argc, const char *const *argv)
+{
+ int ret;
+
+ dpkg_locales_init(PACKAGE);
+ dpkg_program_init("dpkg-statoverride");
+ set_force_default(FORCE_STATCMD_MASK);
+ dpkg_options_parse(&argv, cmdinfos, printforhelp);
+
+ admindir = dpkg_db_set_dir(admindir);
+ instdir = dpkg_fsys_set_dir(instdir);
+
+ if (!cipaction)
+ badusage(_("need an action option"));
+
+ fsys_hash_init();
+ ensure_statoverrides(STATDB_PARSE_LAX);
+
+ ret = cipaction->action(argv);
+
+ dpkg_program_done();
+
+ return ret;
+}
diff --git a/src/t/dpkg_divert.t b/src/t/dpkg_divert.t
new file mode 100644
index 0000000..cf118e2
--- /dev/null
+++ b/src/t/dpkg_divert.t
@@ -0,0 +1,644 @@
+#!/usr/bin/perl
+#
+# 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 Test::More;
+
+use File::Spec;
+
+use Dpkg::File;
+use Dpkg::IPC;
+
+# Cleanup environment from variables that pollute the test runs.
+delete $ENV{DPKG_MAINTSCRIPT_PACKAGE};
+delete $ENV{DPKG_MAINTSCRIPT_ARCH};
+
+my $srcdir = $ENV{srcdir} || '.';
+my $builddir = $ENV{builddir} || '.';
+my $tmpdir = 't.tmp/dpkg_divert';
+my $admindir = File::Spec->rel2abs("$tmpdir/admindir");
+my $testdir = File::Spec->rel2abs("$tmpdir/testdir");
+
+my @dd = ("$builddir/../src/dpkg-divert");
+
+if (! -x "@dd") {
+ plan skip_all => 'dpkg-divert not available';
+ exit(0);
+}
+
+plan tests => 257;
+
+sub cleanup {
+ # On FreeBSD «rm -rf» cannot traverse a directory with mode 000.
+ system("test -d $testdir/nadir && rmdir $testdir/nadir");
+ system("rm -rf $tmpdir && mkdir -p $testdir");
+ system("mkdir -p $admindir/updates");
+ system("rm -f $admindir/status && touch $admindir/status");
+ system("rm -rf $admindir/info && mkdir -p $admindir/info");
+}
+
+sub install_diversions {
+ my ($txt) = @_;
+ open(my $db_fh, '>', "$admindir/diversions")
+ or die "cannot create $admindir/diversions";
+ print { $db_fh } $txt;
+ close($db_fh);
+}
+
+sub install_filelist {
+ my ($pkg, $arch, @files) = @_;
+ open(my $fileslist_fh, '>', "$admindir/info/$pkg.list")
+ or die "cannot create $admindir/info/$pkg.list";
+ for my $file (@files) {
+ print { $fileslist_fh } "$file\n";
+ }
+ close($fileslist_fh);
+ # Only installed packages have their files list considered.
+ open(my $status_fh, '>>', "$admindir/status")
+ or die "cannot append to $admindir/status";
+ print { $status_fh } <<"EOF";
+Package: $pkg
+Status: install ok installed
+Version: 0
+Architecture: $arch
+Maintainer: dummy
+Description: dummy
+
+EOF
+ close($status_fh);
+}
+
+sub call {
+ my ($prog, $args, %opts) = @_;
+
+ my ($output, $error);
+ spawn(exec => [@$prog, @$args], wait_child => 1, nocheck => 1,
+ to_pipe => \$output, error_to_pipe => \$error, %opts);
+
+ if ($opts{expect_failure}) {
+ ok($? != 0, "@$args should fail");
+ } else {
+ ok($? == 0, "@$args should not fail");
+ }
+
+ if (defined $opts{expect_stdout}) {
+ my (@output) = <$output>;
+ my (@expect) = split(/^/, $opts{expect_stdout});
+ if (defined $opts{expect_sorted_stdout}) {
+ @output = sort @output;
+ @expect = sort @expect;
+ }
+ is(join('', @output), join('', @expect), "@$args stdout");
+ }
+ if (defined $opts{expect_stdout_like}) {
+ like(file_slurp($output), $opts{expect_stdout_like}, "@$args stdout");
+ }
+ if (defined $opts{expect_stderr}) {
+ is(file_slurp($error), $opts{expect_stderr}, "@$args stderr");
+ }
+ if (defined $opts{expect_stderr_like}) {
+ like(file_slurp($error), $opts{expect_stderr_like}, "@$args stderr");
+ }
+
+ close($output);
+ close($error);
+}
+
+sub call_divert {
+ my ($params, %opts) = @_;
+ call([@dd, '--admindir', $admindir], $params, %opts);
+}
+
+sub call_divert_sort {
+ my ($params, %opts) = @_;
+ $opts{expect_sorted_stdout} = 1;
+ call_divert($params, %opts);
+}
+
+sub diversions_pack {
+ my (@data) = @_;
+ my @data_packed;
+
+ ## no critic (ControlStructures::ProhibitCStyleForLoops)
+ for (my ($i) = 0; $i < $#data; $i += 3) {
+ push @data_packed, [ @data[$i .. $i + 2] ];
+ }
+ ## use critic
+ my @list = sort { $a->[0] cmp $b->[0] } @data_packed;
+
+ return @list;
+}
+
+sub diversions_eq {
+ my (@expected) = split /^/, shift;
+ open(my $db_fh, '<', "$admindir/diversions")
+ or die "cannot open $admindir/diversions";
+ my (@contents) = <$db_fh>;
+ close($db_fh);
+
+ my (@expected_pack) = diversions_pack(@expected);
+ my (@contents_pack) = diversions_pack(@contents);
+
+ is_deeply(\@contents_pack, \@expected_pack, 'diversions contents');
+}
+
+### Tests
+
+cleanup();
+
+note('Command line parsing testing');
+
+my $usagere = qr/.*Usage.*dpkg-divert.*Commands.*Options.*/s;
+
+sub call_divert_badusage {
+ my ($args, $err) = @_;
+ call_divert($args, expect_failure => 1, expect_stderr_like => $err);
+}
+
+call_divert(['--help'], expect_stdout_like => $usagere,
+ expect_stderr => '');
+call_divert(['--version'], expect_stdout_like => qr/.*dpkg-divert.*free software.*/s,
+ expect_stderr => '');
+
+call_divert_badusage(['--jachsmitbju'], qr/unknown option/);
+call_divert_badusage(['--add', '--remove'], qr/(conflicting|two).*remove.*add.*/s);
+call_divert_badusage(['--divert'], qr/(takes a value|needs.*argument)/);
+call_divert_badusage(['--divert', 'foo'], qr/absolute/);
+call_divert_badusage(['--divert', "/foo\nbar"], qr/newline/);
+call_divert_badusage(['--package'], qr/(takes a value|needs.*argument)/);
+call_divert_badusage(['--package', "foo\nbar"], qr/newline/);
+
+install_diversions('');
+
+call_divert_badusage(['--add',], qr/needs a single argument/);
+call_divert_badusage(['--add', 'foo'], qr/absolute/);
+call_divert_badusage(['--add', "/foo\nbar"], qr/newline/);
+call_divert_badusage(['--add', "$testdir"], qr/director(y|ies)/);
+call_divert_badusage(['--add', '--divert', 'bar', '/foo/bar'], qr/absolute/);
+call_divert_badusage(['--remove'], qr/needs a single argument/);
+call_divert_badusage(['--remove', 'foo'], qr/absolute/);
+call_divert_badusage(['--remove', "/foo\nbar"], qr/newline/);
+call_divert_badusage(['--listpackage'], qr/needs a single argument/);
+call_divert_badusage(['--listpackage', 'foo'], qr/absolute/);
+call_divert_badusage(['--listpackage', "/foo\nbar"], qr/newline/);
+call_divert_badusage(['--truename'], qr/needs a single argument/);
+call_divert_badusage(['--truename', 'foo'], qr/absolute/);
+call_divert_badusage(['--truename', "/foo\nbar"], qr/newline/);
+call([@dd, '--admindir'], [],
+ expect_failure => 1, expect_stderr_like => qr/(takes a value|needs.*argument)/);
+
+cleanup();
+
+note('Querying information from diverts db (empty one)');
+
+install_diversions('');
+
+call_divert_sort(['--list'], expect_stdout => '', expect_stderr => '');
+call_divert_sort(['--list', '*'], expect_stdout => '', expect_stderr => '');
+call_divert_sort(['--list', 'baz'], expect_stdout => '', expect_stderr => '');
+
+cleanup();
+
+note('Querying information from diverts db (1)');
+
+install_diversions(<<'EOF');
+/bin/sh
+/bin/sh.distrib
+dash
+/usr/share/man/man1/sh.1.gz
+/usr/share/man/man1/sh.distrib.1.gz
+dash
+/usr/bin/nm
+/usr/bin/nm.single
+binutils-multiarch
+EOF
+
+my $di_dash = "diversion of /bin/sh to /bin/sh.distrib by dash\n";
+my $di_dashman = "diversion of /usr/share/man/man1/sh.1.gz to /usr/share/man/man1/sh.distrib.1.gz by dash\n";
+my $di_nm = "diversion of /usr/bin/nm to /usr/bin/nm.single by binutils-multiarch\n";
+
+my $all_di = $di_dash . $di_dashman . $di_nm;
+
+call_divert_sort(['--list'], expect_stdout => $all_di, expect_stderr => '');
+call_divert_sort(['--list', '*'], expect_stdout => $all_di, expect_stderr => '');
+call_divert_sort(['--list', ''], expect_stdout => '', expect_stderr => '');
+
+call_divert_sort(['--list', '???????'], expect_stdout => $di_dash, expect_stderr => '');
+call_divert_sort(['--list', '*/sh'], expect_stdout => $di_dash, expect_stderr => '');
+call_divert_sort(['--list', '/bin/*'], expect_stdout => $di_dash, expect_stderr => '');
+call_divert_sort(['--list', 'binutils-multiarch'], expect_stdout => $di_nm, expect_stderr => '');
+call_divert_sort(['--list', '/bin/sh'], expect_stdout => $di_dash, expect_stderr => '');
+call_divert_sort(['--list', '--', '/bin/sh'], expect_stdout => $di_dash, expect_stderr => '');
+call_divert_sort(['--list', '/usr/bin/nm.single'], expect_stdout => $di_nm, expect_stderr => '');
+call_divert_sort(['--list', '/bin/sh', '/usr/share/man/man1/sh.1.gz'], expect_stdout => $di_dash . $di_dashman,
+ expect_stderr => '');
+
+cleanup();
+
+note('Querying information from diverts db (2)');
+
+install_diversions(<<'EOF');
+/bin/sh
+/bin/sh.distrib
+dash
+/bin/true
+/bin/true.coreutils
+:
+EOF
+
+call_divert(['--listpackage', 'foo', 'bar'], expect_failure => 1);
+call_divert(['--listpackage', '/bin/sh'], expect_stdout => "dash\n", expect_stderr => '');
+call_divert(['--listpackage', '/bin/true'], expect_stdout => "LOCAL\n", expect_stderr => '');
+call_divert(['--listpackage', '/bin/false'], expect_stdout => '', expect_stderr => '');
+
+call_divert(['--truename', '/bin/sh'], expect_stdout => "/bin/sh.distrib\n", expect_stderr => '');
+call_divert(['--truename', '/bin/sh.distrib'], expect_stdout => "/bin/sh.distrib\n", expect_stderr => '');
+call_divert(['--truename', '/bin/something'], expect_stdout => "/bin/something\n", expect_stderr => '');
+
+cleanup();
+
+note('Adding diversion');
+
+my $diversions_added_foo_local = <<"EOF";
+$testdir/foo
+$testdir/foo.distrib
+:
+EOF
+
+install_diversions('');
+
+system("touch $testdir/foo");
+call_divert(['--rename', '--add', "$testdir/foo"],
+ expect_stdout_like => qr{
+ Adding.*local.*diversion.*
+ \Q$testdir\E/foo.*
+ \Q$testdir\E/foo.distrib
+ }x,
+ expect_stderr => '');
+ok(-e "$testdir/foo.distrib", 'foo diverted');
+ok(!-e "$testdir/foo", 'foo diverted');
+diversions_eq($diversions_added_foo_local);
+
+cleanup();
+
+note('Adding diversion (2)');
+
+install_diversions('');
+
+system("touch $testdir/foo");
+call_divert(['--no-rename', '--add', "$testdir/foo"],
+ expect_stdout_like => qr{
+ Adding.*local.*diversion.*
+ \Q$testdir\E/foo.*
+ \Q$testdir\E/foo.distrib
+ }x,
+ expect_stderr => '');
+ok(!-e "$testdir/foo.distrib", 'foo diverted');
+ok(-e "$testdir/foo", 'foo diverted');
+diversions_eq($diversions_added_foo_local);
+
+cleanup();
+
+note('Adding diversion (3)');
+
+install_diversions('');
+
+system("touch $testdir/foo");
+call_divert(['--quiet', '--rename', '--add', "$testdir/foo"],
+ expect_stdout => '', expect_stderr => '');
+ok(-e "$testdir/foo.distrib", 'foo diverted');
+ok(!-e "$testdir/foo", 'foo diverted');
+diversions_eq($diversions_added_foo_local);
+
+cleanup();
+
+note('Adding diversion (4)');
+
+install_diversions('');
+system("touch $testdir/foo");
+call_divert(['--quiet', '--rename', '--test', "$testdir/foo"],
+ expect_stdout => '', expect_stderr => '');
+ok(-e "$testdir/foo", 'foo not diverted');
+ok(!-e "$testdir/foo.distrib", 'foo diverted');
+diversions_eq('');
+
+cleanup();
+
+note('Adding diversion (5)');
+
+install_diversions('');
+call_divert(['--quiet', '--rename', "$testdir/foo"],
+ expect_stdout => '', expect_stderr => '');
+ok(!-e "$testdir/foo", 'foo does not exist');
+ok(!-e "$testdir/foo.distrib", 'foo was not created out of thin air');
+
+cleanup();
+
+note('Adding diversion (6)');
+
+install_diversions('');
+system("touch $testdir/foo");
+call_divert(['--quiet', '--local', '--rename', "$testdir/foo"],
+ expect_stdout => '', expect_stderr => '');
+
+ok(-e "$testdir/foo.distrib", 'foo diverted');
+ok(!-e "$testdir/foo", 'foo diverted');
+diversions_eq($diversions_added_foo_local);
+
+cleanup();
+
+note('Adding diversion (7)');
+
+install_diversions('');
+call_divert(['--quiet', '--rename', '--package', 'bash', "$testdir/foo"],
+ expect_stdout => '', expect_stderr => '');
+diversions_eq(<<"EOF");
+$testdir/foo
+$testdir/foo.distrib
+bash
+EOF
+
+note('Adding diversion (8)');
+
+install_diversions('');
+system("touch $testdir/foo; ln $testdir/foo $testdir/foo.distrib");
+call_divert(['--rename', "$testdir/foo"]);
+diversions_eq($diversions_added_foo_local);
+ok(!-e "$testdir/foo", 'foo diverted');
+ok(-e "$testdir/foo.distrib", 'foo diverted');
+
+cleanup();
+
+note('Adding diversion (9)');
+
+install_diversions('');
+system("touch $testdir/foo $testdir/foo.distrib");
+call_divert(['--rename', "$testdir/foo"], expect_failure => 1,
+ expect_stderr_like => qr/overwriting/);
+diversions_eq('');
+
+cleanup();
+
+note('Adding second diversion');
+
+install_diversions('');
+call_divert(["$testdir/foo"]);
+
+call_divert(["$testdir/foo"], expect_stdout_like => qr/Leaving/);
+call_divert(['--quiet', "$testdir/foo"], expect_stdout => '');
+call_divert(['--divert', "$testdir/foo.bar", "$testdir/foo"],
+ expect_failure => 1, expect_stderr_like => qr/clashes/);
+call_divert(['--package', 'foobar', "$testdir/foo"], expect_failure => 1,
+ expect_stderr_like => qr/clashes/);
+call_divert(['--divert', "$testdir/foo.distrib", "$testdir/bar"],
+ expect_failure => 1, expect_stderr_like => qr/clashes/);
+call_divert(["$testdir/foo.distrib"],
+ expect_failure => 1, expect_stderr_like => qr/clashes/);
+call_divert(['--divert', "$testdir/foo", "$testdir/bar"],
+ expect_failure => 1, expect_stderr_like => qr/clashes/);
+
+cleanup();
+
+note('Adding third diversion');
+
+install_diversions('');
+call_divert(["$testdir/foo"]);
+call_divert(["$testdir/bar"]);
+
+call_divert(['--quiet', "$testdir/foo"], expect_stdout => '');
+call_divert(['--package', 'foobar', "$testdir/bar"], expect_failure => 1,
+ expect_stderr_like => qr/clashes/);
+
+cleanup();
+
+note('Adding diversion in non-existing directory');
+
+install_diversions('');
+
+call_divert(['--quiet', '--rename', '--add', "$testdir/zoo/foo"],
+ expect_stderr => '', expect_stdout => '');
+diversions_eq(<<"EOF");
+$testdir/zoo/foo
+$testdir/zoo/foo.distrib
+:
+EOF
+
+cleanup();
+
+note('Adding diversion of file owned by --package');
+
+install_filelist('coreutils', 'i386', "$testdir/foo");
+install_diversions('');
+system("touch $testdir/foo");
+
+call_divert(['--quiet', '--rename', '--add', '--package', 'coreutils', "$testdir/foo"],
+ expect_stderr => '', expect_stdout => '');
+ok(-e "$testdir/foo", 'foo not renamed');
+ok(!-e "$testdir/foo.distrib", 'foo renamed');
+diversions_eq(<<"EOF");
+$testdir/foo
+$testdir/foo.distrib
+coreutils
+EOF
+
+cleanup();
+
+note('Remove diversions');
+
+install_diversions('');
+
+call_divert(['--no-rename', '--remove', '/bin/sh'], expect_stdout_like => qr/No diversion/, expect_stderr => '');
+call_divert(['--no-rename', '--remove', '--quiet', '/bin/sh'], expect_stdout => '', expect_stderr => '');
+
+cleanup();
+
+note('Remove diversion (2)');
+
+install_diversions('');
+call_divert(["$testdir/foo"]);
+call_divert(["$testdir/bar"]);
+call_divert(["$testdir/baz"]);
+
+call_divert(['--divert', "$testdir/foo.my", '--remove', "$testdir/foo"],
+ expect_failure => 1, expect_stderr_like => qr/mismatch on divert-to/);
+call_divert(['--package', 'baz', '--remove', "$testdir/foo"],
+ expect_failure => 1, expect_stderr_like => qr/mismatch on package/);
+call_divert(['--package', 'baz', '--divert', "$testdir/foo.my", '--remove', "$testdir/foo"],
+ expect_failure => 1, expect_stderr_like => qr/mismatch on (package|divert-to)/);
+
+call_divert(['--divert', "$testdir/foo.distrib", '--remove', "$testdir/foo"],
+ expect_stdout_like => qr{Removing.*\Q$testdir\E/foo});
+diversions_eq(<<"EOF");
+$testdir/bar
+$testdir/bar.distrib
+:
+$testdir/baz
+$testdir/baz.distrib
+:
+EOF
+
+cleanup();
+
+note('Remove diversion (3)');
+
+install_diversions('');
+
+call_divert(["$testdir/foo"]);
+call_divert(["$testdir/bar"]);
+call_divert(["$testdir/baz"]);
+
+call_divert(['--remove', "$testdir/bar"],
+ expect_stdout_like => qr{Removing.*\Q$testdir\E/bar});
+diversions_eq(<<"EOF");
+$testdir/foo
+$testdir/foo.distrib
+:
+$testdir/baz
+$testdir/baz.distrib
+:
+EOF
+
+cleanup();
+
+note('Remove diversion (4)');
+
+install_diversions('');
+
+call_divert(["$testdir/foo"]);
+call_divert(["$testdir/bar"]);
+call_divert(['--package', 'bash', "$testdir/baz"]);
+
+call_divert(['--no-rename', '--quiet', '--package', 'bash',
+ '--remove', "$testdir/baz"],
+ expect_stdout => '', expect_stderr => '');
+diversions_eq(<<"EOF");
+$testdir/foo
+$testdir/foo.distrib
+:
+$testdir/bar
+$testdir/bar.distrib
+:
+EOF
+
+cleanup();
+
+note('Remove diversion(5)');
+
+install_diversions('');
+system("touch $testdir/foo");
+call_divert(['--rename', "$testdir/foo"]);
+
+call_divert(['--test', '--rename', '--remove', "$testdir/foo"],
+ expect_stdout_like => qr{Removing.*\Q$testdir\E/foo}, expect_stderr => '');
+ok(-e "$testdir/foo.distrib", 'foo diversion not removed');
+ok(!-e "$testdir/foo", 'foo diversion not removed');
+diversions_eq($diversions_added_foo_local);
+
+call_divert(['--quiet', '--rename', '--remove', "$testdir/foo"],
+ expect_stdout => '', expect_stderr => '');
+ok(-e "$testdir/foo", 'foo diversion removed');
+ok(!-e "$testdir/foo.distrib", 'foo diversion removed');
+diversions_eq('');
+
+cleanup();
+
+note('Corrupted diversions db handling');
+
+SKIP: {
+ skip 'running as root or similar', 3, if (defined($ENV{FAKEROOTKEY}) or $> == 0);
+
+ # An inexistent diversions db file should not be considered a failure,
+ # but a failure to open it should be.
+ install_diversions('');
+ system("chmod 000 $admindir/diversions");
+ call_divert_sort(['--list'], expect_failure => 1,
+ expect_stderr_like => qr/(cannot|failed).*open/, expect_stdout => '');
+ system("chmod 644 $admindir/diversions");
+}
+
+install_diversions(<<'EOF');
+/bin/sh
+EOF
+
+call_divert_sort(['--list'], expect_failure => 1,
+ expect_stderr_like => qr/(corrupt|unexpected end of file)/,
+ expect_stdout => '');
+
+install_diversions(<<'EOF');
+/bin/sh
+bash
+EOF
+
+call_divert_sort(['--list'], expect_failure => 1,
+ expect_stderr_like => qr/(corrupt|unexpected end of file)/,
+ expect_stdout => '');
+
+cleanup();
+
+SKIP: {
+ skip 'running as root or similar', 10, if (defined($ENV{FAKEROOTKEY}) or $> == 0);
+
+ note('R/O directory');
+
+ install_diversions('');
+ system("mkdir $testdir/rodir && touch $testdir/rodir/foo $testdir/bar && chmod 500 $testdir/rodir");
+ call_divert(['--rename', '--add', "$testdir/rodir/foo"],
+ expect_failure => 1, expect_stderr_like => qr/error/);
+ call_divert(['--rename', '--divert', "$testdir/rodir/bar", '--add', "$testdir/bar"],
+ expect_failure => 1, expect_stderr_like => qr/error/);
+ diversions_eq('');
+
+ system("chmod 755 $testdir/rodir");
+ cleanup();
+
+ note('Unavailable file');
+
+ install_diversions('');
+ system("mkdir $testdir/nadir && chmod 000 $testdir/nadir");
+ call_divert(['--rename', '--add', "$testdir/nadir/foo"],
+ expect_failure => 1, expect_stderr_like => qr/Permission denied/);
+ system("touch $testdir/foo");
+ call_divert(['--rename', '--divert', "$testdir/nadir/foo", '--add', "$testdir/foo"],
+ expect_failure => 1, expect_stderr_like => qr/Permission denied/);
+ diversions_eq('');
+
+ cleanup();
+}
+
+note('Errors during saving diversions db');
+
+install_diversions('');
+
+SKIP: {
+ skip 'running as root or similar', 4, if (defined($ENV{FAKEROOTKEY}) or $> == 0);
+
+ system("chmod 500 $admindir");
+ call_divert(["$testdir/foo"], expect_failure => 1, expect_stderr_like => qr/create.*new/);
+
+ system("chmod 755 $admindir");
+
+ SKIP: {
+ skip 'device /dev/full is not available', 2 if not -c '/dev/full';
+
+ system("ln -s /dev/full $admindir/diversions-new");
+ call_divert(["$testdir/foo"], expect_failure => 1,
+ expect_stderr_like => qr/(write|flush|close).*new/);
+ }
+}
+
+system("rm -f $admindir/diversions-new; mkdir $admindir/diversions-old");
+call_divert(["$testdir/foo"], expect_failure => 1, expect_stderr_like => qr/remov.*old/);
diff --git a/src/trigcmd.c b/src/trigcmd.c
new file mode 100644
index 0000000..af8119c
--- /dev/null
+++ b/src/trigcmd.c
@@ -0,0 +1,256 @@
+/*
+ * dpkg-trigger - trigger management utility
+ *
+ * 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 <sys/types.h>
+
+#include <fcntl.h>
+#if HAVE_LOCALE_H
+#include <locale.h>
+#endif
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/options.h>
+#include <dpkg/trigdeferred.h>
+#include <dpkg/triglib.h>
+#include <dpkg/pkg-spec.h>
+
+static const char printforhelp[] = N_(
+"Type dpkg-trigger --help for help about this utility.");
+
+static void DPKG_ATTR_NORET
+printversion(const struct cmdinfo *ci, const char *value)
+{
+ printf(_("Debian %s package trigger utility version %s.\n"),
+ dpkg_get_progname(), PACKAGE_RELEASE);
+
+ printf(_(
+"This is free software; see the GNU General Public License version 2 or\n"
+"later for copying conditions. There is NO warranty.\n"));
+
+ m_output(stdout, _("<standard output>"));
+
+ exit(0);
+}
+
+static void DPKG_ATTR_NORET
+usage(const struct cmdinfo *ci, const char *value)
+{
+ printf(_(
+"Usage: %s [<options> ...] <trigger-name>\n"
+" %s [<options> ...] <command>\n"
+"\n"), dpkg_get_progname(), dpkg_get_progname());
+
+ printf(_(
+"Commands:\n"
+" --check-supported Check if the running dpkg supports triggers.\n"
+"\n"));
+
+ printf(_(
+" -?, --help Show this help message.\n"
+" --version Show the version.\n"
+"\n"));
+
+ printf(_(
+"Options:\n"
+" --admindir=<directory> Use <directory> instead of %s.\n"
+" --by-package=<package> Override trigger awaiter (normally set\n"
+" by dpkg).\n"
+" --await Package needs to await the processing.\n"
+" --no-await No package needs to await the processing.\n"
+" --no-act Just test - don't actually change anything.\n"
+"\n"), ADMINDIR);
+
+ m_output(stdout, _("<standard output>"));
+
+ exit(0);
+}
+
+static const char *admindir;
+static int f_noact, f_check;
+static int f_await = 1;
+
+static const char *bypackage, *activate;
+static bool done_trig, ctrig;
+
+static void
+yespackage(const char *awname)
+{
+ trigdef_update_printf(" %s", awname);
+}
+
+static const char *
+parse_awaiter_package(void)
+{
+ struct dpkg_error err = DPKG_ERROR_INIT;
+ struct pkginfo *pkg;
+
+ if (!f_await)
+ bypackage = "-";
+
+ if (bypackage == NULL) {
+ const char *pkgname, *archname;
+
+ pkgname = getenv("DPKG_MAINTSCRIPT_PACKAGE");
+ archname = getenv("DPKG_MAINTSCRIPT_ARCH");
+ if (pkgname == NULL || archname == NULL)
+ badusage(_("must be called from a maintainer script"
+ " (or with a --by-package option)"));
+
+ pkg = pkg_spec_find_pkg(pkgname, archname, &err);
+ } else if (strcmp(bypackage, "-") == 0) {
+ pkg = NULL;
+ } else {
+ pkg = pkg_spec_parse_pkg(bypackage, &err);
+ }
+
+ /* Normalize the bypackage name if there was no error. */
+ if (pkg)
+ bypackage = pkg_name(pkg, pnaw_nonambig);
+
+ return err.str;
+}
+
+static void
+tdm_add_trig_begin(const char *trig)
+{
+ ctrig = strcmp(trig, activate) == 0;
+ trigdef_update_printf("%s", trig);
+ if (!ctrig || done_trig)
+ return;
+ yespackage(bypackage);
+ done_trig = true;
+}
+
+static void
+tdm_add_package(const char *awname)
+{
+ if (ctrig && strcmp(awname, bypackage) == 0)
+ return;
+ yespackage(awname);
+}
+
+static void
+tdm_add_trig_end(void)
+{
+ trigdef_update_printf("\n");
+}
+
+static const struct trigdefmeths tdm_add = {
+ .trig_begin = tdm_add_trig_begin,
+ .package = tdm_add_package,
+ .trig_end = tdm_add_trig_end,
+};
+
+static int
+do_check(void)
+{
+ enum trigdef_update_status uf;
+
+ uf = trigdef_update_start(TDUF_NO_LOCK_OK);
+ switch (uf) {
+ case TDUS_ERROR_NO_DIR:
+ notice(_("triggers data directory not yet created"));
+ return 1;
+ case TDUS_ERROR_NO_DEFERRED:
+ notice(_("trigger records not yet in existence"));
+ return 1;
+ case TDUS_OK:
+ case TDUS_ERROR_EMPTY_DEFERRED:
+ return 0;
+ default:
+ internerr("unknown trigdef_update_start return value '%d'", uf);
+ }
+}
+
+static const struct cmdinfo cmdinfos[] = {
+ { "admindir", 0, 1, NULL, &admindir },
+ { "by-package", 'f', 1, NULL, &bypackage },
+ { "await", 0, 0, &f_await, NULL, NULL, 1 },
+ { "no-await", 0, 0, &f_await, NULL, NULL, 0 },
+ { "no-act", 0, 0, &f_noact, NULL, NULL, 1 },
+ { "check-supported", 0, 0, &f_check, NULL, NULL, 1 },
+ { "help", '?', 0, NULL, NULL, usage },
+ { "version", 0, 0, NULL, NULL, printversion },
+ { NULL }
+};
+
+int
+main(int argc, const char *const *argv)
+{
+ const char *badname;
+ enum trigdef_update_flags tduf;
+ enum trigdef_update_status tdus;
+
+ dpkg_locales_init(PACKAGE);
+ dpkg_program_init("dpkg-trigger");
+ dpkg_options_parse(&argv, cmdinfos, printforhelp);
+
+ admindir = dpkg_db_set_dir(admindir);
+
+ if (f_check) {
+ if (*argv)
+ badusage(_("--%s takes no arguments"),
+ "check-supported");
+ return do_check();
+ }
+
+ if (!*argv || argv[1])
+ badusage(_("takes one argument, the trigger name"));
+
+ badname = parse_awaiter_package();
+ if (badname)
+ badusage(_("illegal awaited package name '%.250s': %.250s"),
+ bypackage, badname);
+
+ fsys_hash_init();
+
+ activate = argv[0];
+ badname = trig_name_is_illegal(activate);
+ if (badname)
+ badusage(_("invalid trigger name '%.250s': %.250s"),
+ activate, badname);
+
+ trigdef_set_methods(&tdm_add);
+
+ tduf = TDUF_NO_LOCK_OK;
+ if (!f_noact)
+ tduf |= TDUF_WRITE | TDUF_WRITE_IF_EMPTY;
+ tdus = trigdef_update_start(tduf);
+ if (tdus >= 0) {
+ trigdef_parse();
+ if (!done_trig)
+ trigdef_update_printf("%s %s\n", activate, bypackage);
+ trigdef_process_done();
+ }
+
+ dpkg_program_done();
+
+ return 0;
+}
diff --git a/src/trigproc.c b/src/trigproc.c
new file mode 100644
index 0000000..54b15bb
--- /dev/null
+++ b/src/trigproc.c
@@ -0,0 +1,575 @@
+/*
+ * dpkg - main program for package management
+ * trigproc.c - trigger processing
+ *
+ * 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 <sys/stat.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg.h>
+#include <dpkg/pkg-queue.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+#include <dpkg/triglib.h>
+
+#include "main.h"
+
+/*
+ * Trigger processing algorithms:
+ *
+ *
+ * There is a separate queue (‘deferred trigproc list’) for triggers
+ * ‘relevant’ to what we just did; when we find something triggered ‘now’
+ * we add it to that queue (unless --no-triggers).
+ *
+ *
+ * We want to prefer configuring packages where possible to doing trigger
+ * processing, although it would be better to prefer trigger processing
+ * to cycle-breaking we need to do the latter first or we might generate
+ * artificial trigger cycles, but we always want to prefer trigger
+ * processing to dependency forcing. This is achieved as follows:
+ *
+ * Each time during configure processing where a package D is blocked by
+ * only (ie Depends unsatisfied but would be satisfied by) a t-awaiter W
+ * we make a note of (one of) W's t-pending, T. (Only the last such T.)
+ * (If --no-triggers and nonempty argument list and package isn't in
+ * argument list then we don't do this.)
+ *
+ * Each time in process_queue() where we increment dependtry, we instead
+ * see if we have encountered such a t-pending T. If we do and are in
+ * a trigger processing try, we trigproc T instead of incrementing
+ * dependtry and this counts as having done something so we reset
+ * sincenothing.
+ *
+ *
+ * For --triggers-only and --configure, we go through each thing in the
+ * argument queue (the enqueue_package queue) and check what its state is
+ * and if appropriate we trigproc it. If we didn't have a queue (or had
+ * just --pending) we search all triggers-pending packages and add them
+ * to the deferred trigproc list.
+ *
+ *
+ * Before quitting from most operations, we trigproc each package in the
+ * deferred trigproc list. This may (if not --no-triggers) of course add
+ * new things to the deferred trigproc list.
+ *
+ *
+ * Note that ‘we trigproc T’ must involve trigger cycle detection and
+ * also automatic setting of t-awaiters to t-pending or installed. In
+ * particular, we do cycle detection even for trigger processing in the
+ * configure dependtry loop (and it is OK to do it for explicitly
+ * specified packages from the command line arguments; duplicates are
+ * removed by packages.c:process_queue).
+ */
+
+/*========== Deferred trigger queue. ==========*/
+
+static struct pkg_queue deferred = PKG_QUEUE_INIT;
+
+static void
+trigproc_enqueue_deferred(struct pkginfo *pend)
+{
+ if (f_triggers < 0)
+ return;
+ ensure_package_clientdata(pend);
+ if (pend->clientdata->trigprocdeferred)
+ return;
+ pend->clientdata->trigprocdeferred = pkg_queue_push(&deferred, pend);
+ debug(dbg_triggers, "trigproc_enqueue_deferred pend=%s",
+ pkg_name(pend, pnaw_always));
+}
+
+/**
+ * Populate the deferred trigger queue.
+ *
+ * When dpkg is called with a specific set of packages to act on, we might
+ * have packages pending trigger processing. But because there are frontends
+ * that do not perform a final «dpkg --configure --pending» call (i.e. apt),
+ * the system is left in a state with packages not fully installed.
+ *
+ * We have to populate the deferred trigger queue from the entire package
+ * database, so that we might try to do opportunistic trigger processing
+ * when going through the deferred trigger queue, because a fixed apt will
+ * not request the necessary processing anyway.
+ *
+ * XXX: This can be removed once apt is fixed in the next stable release.
+ */
+void
+trigproc_populate_deferred(void)
+{
+ struct pkg_hash_iter *iter;
+ struct pkginfo *pkg;
+
+ iter = pkg_hash_iter_new();
+ while ((pkg = pkg_hash_iter_next_pkg(iter))) {
+ if (!pkg->trigpend_head)
+ continue;
+
+ if (pkg->status != PKG_STAT_TRIGGERSAWAITED &&
+ pkg->status != PKG_STAT_TRIGGERSPENDING)
+ continue;
+
+ if (pkg->want != PKG_WANT_INSTALL)
+ continue;
+
+ trigproc_enqueue_deferred(pkg);
+ }
+ pkg_hash_iter_free(iter);
+}
+
+void
+trigproc_run_deferred(void)
+{
+ jmp_buf ejbuf;
+
+ debug(dbg_triggers, "trigproc_run_deferred");
+ while (!pkg_queue_is_empty(&deferred)) {
+ struct pkginfo *pkg;
+
+ pkg = pkg_queue_pop(&deferred);
+ if (!pkg)
+ continue;
+
+ if (setjmp(ejbuf)) {
+ pop_error_context(ehflag_bombout);
+ continue;
+ }
+ push_error_context_jump(&ejbuf, print_error_perpackage,
+ pkg_name(pkg, pnaw_nonambig));
+
+ ensure_package_clientdata(pkg);
+ pkg->clientdata->trigprocdeferred = NULL;
+ trigproc(pkg, TRIGPROC_TRY_DEFERRED);
+
+ pop_error_context(ehflag_normaltidy);
+ }
+}
+
+/*
+ * Called by modstatdb_note.
+ */
+void
+trig_activate_packageprocessing(struct pkginfo *pkg)
+{
+ debug(dbg_triggersdetail, "trigproc_activate_packageprocessing pkg=%s",
+ pkg_name(pkg, pnaw_always));
+
+ trig_parse_ci(pkg_infodb_get_file(pkg, &pkg->installed, TRIGGERSCIFILE),
+ NULL, trig_cicb_statuschange_activate,
+ pkg, &pkg->installed);
+}
+
+/*========== Actual trigger processing. ==========*/
+
+struct trigcyclenode {
+ struct trigcyclenode *next;
+ struct trigcycleperpkg *pkgs;
+ struct pkginfo *then_processed;
+};
+
+struct trigcycleperpkg {
+ struct trigcycleperpkg *next;
+ struct pkginfo *pkg;
+ struct trigpend *then_trigs;
+};
+
+static bool tortoise_advance;
+static struct trigcyclenode *tortoise, *hare;
+
+void
+trigproc_reset_cycle(void)
+{
+ tortoise_advance = false;
+ tortoise = hare = NULL;
+}
+
+static bool
+tortoise_in_hare(struct pkginfo *processing_now,
+ struct trigcycleperpkg *tortoise_pkg)
+{
+ const char *processing_now_name, *tortoise_name;
+ struct trigpend *hare_trig, *tortoise_trig;
+
+ processing_now_name = pkg_name(processing_now, pnaw_nonambig);
+ tortoise_name = pkg_name(tortoise_pkg->pkg, pnaw_nonambig);
+
+ debug(dbg_triggersdetail, "%s pnow=%s tortoise=%s", __func__,
+ processing_now_name, tortoise_name);
+ for (tortoise_trig = tortoise_pkg->then_trigs;
+ tortoise_trig;
+ tortoise_trig = tortoise_trig->next) {
+ debug(dbg_triggersdetail,
+ "%s pnow=%s tortoise=%s tortoisetrig=%s", __func__,
+ processing_now_name, tortoise_name, tortoise_trig->name);
+
+ /* hare is now so we can just look up in the actual data. */
+ for (hare_trig = tortoise_pkg->pkg->trigpend_head;
+ hare_trig;
+ hare_trig = hare_trig->next) {
+ debug(dbg_triggersstupid, "%s pnow=%s tortoise=%s"
+ " tortoisetrig=%s haretrig=%s", __func__,
+ processing_now_name, tortoise_name,
+ tortoise_trig->name, hare_trig->name);
+ if (strcmp(hare_trig->name, tortoise_trig->name) == 0)
+ break;
+ }
+
+ if (hare_trig == NULL) {
+ /* Not found in hare, yay! */
+ debug(dbg_triggersdetail, "%s pnow=%s tortoise=%s OK",
+ __func__, processing_now_name, tortoise_name);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static struct trigcyclenode *
+trigproc_new_cyclenode(struct pkginfo *processing_now)
+{
+ struct trigcyclenode *tcn;
+ struct trigcycleperpkg *tcpp;
+ struct pkginfo *pkg;
+ struct pkg_hash_iter *iter;
+
+ tcn = nfmalloc(sizeof(*tcn));
+ tcn->pkgs = NULL;
+ tcn->next = NULL;
+ tcn->then_processed = processing_now;
+
+ iter = pkg_hash_iter_new();
+ while ((pkg = pkg_hash_iter_next_pkg(iter))) {
+ if (!pkg->trigpend_head)
+ continue;
+ tcpp = nfmalloc(sizeof(*tcpp));
+ tcpp->pkg = pkg;
+ tcpp->then_trigs = pkg->trigpend_head;
+ tcpp->next = tcn->pkgs;
+ tcn->pkgs = tcpp;
+ }
+ pkg_hash_iter_free(iter);
+
+ return tcn;
+}
+
+/*
+ * Returns package we are to give up on.
+ */
+static struct pkginfo *
+check_trigger_cycle(struct pkginfo *processing_now)
+{
+ struct trigcyclenode *tcn;
+ struct trigcycleperpkg *tortoise_pkg;
+ struct trigpend *tortoise_trig;
+ struct pkginfo *giveup;
+ const char *sep;
+
+ debug(dbg_triggers, "check_triggers_cycle pnow=%s",
+ pkg_name(processing_now, pnaw_always));
+
+ tcn = trigproc_new_cyclenode(processing_now);
+
+ if (!hare) {
+ debug(dbg_triggersdetail, "check_triggers_cycle pnow=%s first",
+ pkg_name(processing_now, pnaw_always));
+ hare = tortoise = tcn;
+ return NULL;
+ }
+
+ hare->next = tcn;
+ hare = hare->next;
+ if (tortoise_advance)
+ tortoise = tortoise->next;
+ tortoise_advance = !tortoise_advance;
+
+ /* Now we compare hare to tortoise.
+ * We want to find a trigger pending in tortoise which is not in hare
+ * if we find such a thing we have proved that hare isn't a superset
+ * of tortoise and so that we haven't found a loop (yet). */
+ for (tortoise_pkg = tortoise->pkgs;
+ tortoise_pkg;
+ tortoise_pkg = tortoise_pkg->next) {
+ if (!tortoise_in_hare(processing_now, tortoise_pkg))
+ return NULL;
+ }
+ /* Oh dear. hare is a superset of tortoise. We are making no
+ * progress. */
+ notice(_("cycle found while processing triggers:\n"
+ " chain of packages whose triggers are or may be responsible:"));
+ sep = " ";
+ for (tcn = tortoise; tcn; tcn = tcn->next) {
+ fprintf(stderr, "%s%s", sep,
+ pkg_name(tcn->then_processed, pnaw_nonambig));
+ sep = " -> ";
+ }
+ fprintf(stderr, _("\n" " packages' pending triggers which are"
+ " or may be unresolvable:\n"));
+ for (tortoise_pkg = tortoise->pkgs;
+ tortoise_pkg;
+ tortoise_pkg = tortoise_pkg->next) {
+ fprintf(stderr, " %s",
+ pkg_name(tortoise_pkg->pkg, pnaw_nonambig));
+ sep = ": ";
+ for (tortoise_trig = tortoise_pkg->then_trigs;
+ tortoise_trig;
+ tortoise_trig = tortoise_trig->next) {
+ fprintf(stderr, "%s%s", sep, tortoise_trig->name);
+ }
+ fprintf(stderr, "\n");
+ }
+
+ /* We give up on the _earliest_ package involved. */
+ giveup = tortoise->pkgs->pkg;
+ debug(dbg_triggers, "check_triggers_cycle pnow=%s giveup=%s",
+ pkg_name(processing_now, pnaw_always),
+ pkg_name(giveup, pnaw_always));
+ if (giveup->status != PKG_STAT_TRIGGERSAWAITED &&
+ giveup->status != PKG_STAT_TRIGGERSPENDING)
+ internerr("package %s in non-trigger state %s",
+ pkg_name(giveup, pnaw_always),
+ pkg_status_name(giveup));
+ giveup->clientdata->istobe = PKG_ISTOBE_NORMAL;
+ pkg_set_status(giveup, PKG_STAT_HALFCONFIGURED);
+ modstatdb_note(giveup);
+ print_error_perpackage(_("triggers looping, abandoned"),
+ pkg_name(giveup, pnaw_nonambig));
+
+ return giveup;
+}
+
+/*
+ * Does cycle checking. Doesn't mind if pkg has no triggers pending - in
+ * that case does nothing but fix up any stale awaiters.
+ */
+void
+trigproc(struct pkginfo *pkg, enum trigproc_type type)
+{
+ static struct varbuf namesarg;
+
+ struct varbuf depwhynot = VARBUF_INIT;
+ struct trigpend *tp;
+ struct pkginfo *gaveup;
+
+ debug(dbg_triggers, "trigproc %s", pkg_name(pkg, pnaw_always));
+
+ ensure_package_clientdata(pkg);
+ if (pkg->clientdata->trigprocdeferred)
+ pkg->clientdata->trigprocdeferred->pkg = NULL;
+ pkg->clientdata->trigprocdeferred = NULL;
+
+ if (pkg->trigpend_head) {
+ enum dep_check ok;
+
+ if (pkg->status != PKG_STAT_TRIGGERSPENDING &&
+ pkg->status != PKG_STAT_TRIGGERSAWAITED)
+ internerr("package %s in non-trigger state %s",
+ pkg_name(pkg, pnaw_always),
+ pkg_status_name(pkg));
+
+ if (dependtry < DEPEND_TRY_TRIGGERS &&
+ type == TRIGPROC_TRY_QUEUED) {
+ /* We are not yet in a triggers run, so postpone this
+ * package completely. */
+ enqueue_package(pkg);
+ return;
+ }
+
+ if (dependtry >= DEPEND_TRY_CYCLES) {
+ if (findbreakcycle(pkg))
+ sincenothing = 0;
+ }
+
+ ok = dependencies_ok(pkg, NULL, &depwhynot);
+ if (ok == DEP_CHECK_DEFER) {
+ if (dependtry >= DEPEND_TRY_TRIGGERS_CYCLES) {
+ gaveup = check_trigger_cycle(pkg);
+ if (gaveup == pkg)
+ return;
+ }
+
+ varbuf_destroy(&depwhynot);
+ enqueue_package(pkg);
+ return;
+ } else if (ok == DEP_CHECK_HALT) {
+ /* When doing opportunistic deferred trigger processing,
+ * nothing requires us to be able to make progress;
+ * skip the package and silently ignore the error due
+ * to unsatisfiable dependencies. And because we can
+ * end up here repeatedly, if this package is required
+ * to make progress for other packages, we need to
+ * reset the trigger cycle tracking to avoid detecting
+ * bogus cycles*/
+ if (type == TRIGPROC_TRY_DEFERRED) {
+ trigproc_reset_cycle();
+
+ varbuf_destroy(&depwhynot);
+ return;
+ }
+
+ sincenothing = 0;
+ varbuf_end_str(&depwhynot);
+ notice(_("dependency problems prevent processing "
+ "triggers for %s:\n%s"),
+ pkg_name(pkg, pnaw_nonambig), depwhynot.buf);
+ varbuf_destroy(&depwhynot);
+ ohshit(_("dependency problems - leaving triggers unprocessed"));
+ } else if (depwhynot.used) {
+ varbuf_end_str(&depwhynot);
+ notice(_("%s: dependency problems, but processing "
+ "triggers anyway as you requested:\n%s"),
+ pkg_name(pkg, pnaw_nonambig), depwhynot.buf);
+ varbuf_destroy(&depwhynot);
+ }
+
+ gaveup = check_trigger_cycle(pkg);
+ if (gaveup == pkg)
+ return;
+
+ printf(_("Processing triggers for %s (%s) ...\n"),
+ pkg_name(pkg, pnaw_nonambig),
+ versiondescribe(&pkg->installed.version, vdew_nonambig));
+ log_action("trigproc", pkg, &pkg->installed);
+
+ varbuf_reset(&namesarg);
+ for (tp = pkg->trigpend_head; tp; tp = tp->next) {
+ varbuf_add_char(&namesarg, ' ');
+ varbuf_add_str(&namesarg, tp->name);
+ }
+ varbuf_end_str(&namesarg);
+
+ /* Setting the status to half-configured
+ * causes modstatdb_note to clear pending triggers. */
+ pkg_set_status(pkg, PKG_STAT_HALFCONFIGURED);
+ modstatdb_note(pkg);
+
+ if (!f_noact) {
+ sincenothing = 0;
+ maintscript_postinst(pkg, "triggered",
+ namesarg.buf + 1, NULL);
+ }
+
+ post_postinst_tasks(pkg, PKG_STAT_INSTALLED);
+ } else {
+ /* In other branch is done by modstatdb_note(), from inside
+ * post_postinst_tasks(). */
+ trig_clear_awaiters(pkg);
+ }
+}
+
+/*========== Transitional global activation. ==========*/
+
+static void
+transitional_interest_callback_ro(const char *trig, struct pkginfo *pkg,
+ struct pkgbin *pkgbin, enum trig_options opts)
+{
+ struct pkginfo *pend = pkg;
+ struct pkgbin *pendbin = pkgbin;
+
+ debug(dbg_triggersdetail,
+ "trig_transitional_interest_callback trig=%s pend=%s",
+ trig, pkgbin_name(pend, pendbin, pnaw_always));
+ if (pend->status >= PKG_STAT_TRIGGERSAWAITED)
+ trig_note_pend(pend, nfstrsave(trig));
+}
+
+static void
+transitional_interest_callback(const char *trig, struct pkginfo *pkg,
+ struct pkgbin *pkgbin, enum trig_options opts)
+{
+ struct pkginfo *pend = pkg;
+ struct pkgbin *pendbin = pkgbin;
+
+ trig_cicb_interest_add(trig, pend, pendbin, opts);
+ transitional_interest_callback_ro(trig, pend, pendbin, opts);
+}
+
+/*
+ * cstatus might be msdbrw_readonly if we're in --no-act mode, in which
+ * case we don't write out all of the interest files etc. but we do
+ * invent all of the activations for our own benefit.
+ */
+static void
+trig_transitional_activate(enum modstatdb_rw cstatus)
+{
+ struct pkg_hash_iter *iter;
+ struct pkginfo *pkg;
+
+ iter = pkg_hash_iter_new();
+ while ((pkg = pkg_hash_iter_next_pkg(iter))) {
+ if (pkg->status <= PKG_STAT_HALFINSTALLED)
+ continue;
+ debug(dbg_triggersdetail, "trig_transitional_activate %s %s",
+ pkg_name(pkg, pnaw_always),
+ pkg_status_name(pkg));
+ pkg->trigpend_head = NULL;
+ trig_parse_ci(pkg_infodb_get_file(pkg, &pkg->installed,
+ TRIGGERSCIFILE),
+ cstatus >= msdbrw_write ?
+ transitional_interest_callback :
+ transitional_interest_callback_ro, NULL,
+ pkg, &pkg->installed);
+ /* Ensure we're not creating incoherent data that can't
+ * be written down. This should never happen in theory but
+ * can happen if you restore an old status file that is
+ * not in sync with the infodb files. */
+ if (pkg->status < PKG_STAT_TRIGGERSAWAITED)
+ continue;
+
+ if (pkg->trigaw.head)
+ pkg_set_status(pkg, PKG_STAT_TRIGGERSAWAITED);
+ else if (pkg->trigpend_head)
+ pkg_set_status(pkg, PKG_STAT_TRIGGERSPENDING);
+ else
+ pkg_set_status(pkg, PKG_STAT_INSTALLED);
+ }
+ pkg_hash_iter_free(iter);
+
+ if (cstatus >= msdbrw_write) {
+ modstatdb_checkpoint();
+ trig_file_interests_save();
+ }
+}
+
+/*========== Hook setup. ==========*/
+
+TRIGHOOKS_DEFINE_NAMENODE_ACCESSORS
+
+static const struct trig_hooks trig_our_hooks = {
+ .enqueue_deferred = trigproc_enqueue_deferred,
+ .transitional_activate = trig_transitional_activate,
+ .namenode_find = th_nn_find,
+ .namenode_interested = th_nn_interested,
+ .namenode_name = th_nn_name,
+};
+
+void
+trigproc_install_hooks(void)
+{
+ trig_override_hooks(&trig_our_hooks);
+}
diff --git a/src/unpack.c b/src/unpack.c
new file mode 100644
index 0000000..ee453a8
--- /dev/null
+++ b/src/unpack.c
@@ -0,0 +1,1599 @@
+/*
+ * dpkg - main program for package management
+ * unpack.c - .deb archive unpacking
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2006-2015 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 <sys/wait.h>
+
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <utime.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <stdint.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/pkg.h>
+#include <dpkg/pkg-queue.h>
+#include <dpkg/path.h>
+#include <dpkg/buffer.h>
+#include <dpkg/subproc.h>
+#include <dpkg/dir.h>
+#include <dpkg/tarfn.h>
+#include <dpkg/options.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+#include <dpkg/triglib.h>
+
+#include "file-match.h"
+#include "main.h"
+#include "archives.h"
+
+static const char *
+summarize_filename(const char *filename)
+{
+ const char *pfilename;
+ char *pfilenamebuf;
+
+ for (pfilename = filename;
+ pfilename && strlen(pfilename) > 30 && strchr(pfilename, '/') != NULL;
+ pfilename++)
+ pfilename = strchr(pfilename, '/');
+
+ if (pfilename && pfilename != filename) {
+ pfilenamebuf = nfmalloc(strlen(pfilename) + 5);
+ sprintf(pfilenamebuf, _(".../%s"), pfilename);
+ pfilename = pfilenamebuf;
+ } else {
+ pfilename = filename;
+ }
+
+ return pfilename;
+}
+
+static bool
+deb_reassemble(const char **filename, const char **pfilename)
+{
+ static char *reasmbuf = NULL;
+ struct stat stab;
+ int status;
+ pid_t pid;
+
+ if (!reasmbuf)
+ reasmbuf = dpkg_db_get_path(REASSEMBLETMP);
+ if (unlink(reasmbuf) && errno != ENOENT)
+ ohshite(_("error ensuring '%.250s' doesn't exist"), reasmbuf);
+
+ push_cleanup(cu_pathname, ~0, 1, (void *)reasmbuf);
+
+ pid = subproc_fork();
+ if (!pid) {
+ execlp(SPLITTER, SPLITTER, "-Qao", reasmbuf, *filename, NULL);
+ ohshite(_("unable to execute %s (%s)"),
+ _("split package reassembly"), SPLITTER);
+ }
+ status = subproc_reap(pid, SPLITTER, SUBPROC_RETERROR);
+ switch (status) {
+ case 0:
+ /* It was a part - is it complete? */
+ if (!stat(reasmbuf, &stab)) {
+ /* Yes. */
+ *filename = reasmbuf;
+ *pfilename = _("reassembled package file");
+ break;
+ } else if (errno == ENOENT) {
+ /* No. That's it, we skip it. */
+ return false;
+ }
+ case 1:
+ /* No, it wasn't a part. */
+ break;
+ default:
+ ohshit(_("subprocess %s returned error exit status %d"), SPLITTER, status);
+ }
+
+ return true;
+}
+
+static void
+deb_verify(const char *filename)
+{
+ pid_t pid;
+
+ /* We have to check on every unpack, in case the debsig-verify package
+ * gets installed or removed. */
+ if (!find_command(DEBSIGVERIFY))
+ return;
+
+ printf(_("Authenticating %s ...\n"), filename);
+ fflush(stdout);
+ pid = subproc_fork();
+ if (!pid) {
+ execlp(DEBSIGVERIFY, DEBSIGVERIFY, "-q", filename, NULL);
+ ohshite(_("unable to execute %s (%s)"),
+ _("package signature verification"), DEBSIGVERIFY);
+ } else {
+ int status;
+
+ status = subproc_reap(pid, "debsig-verify", SUBPROC_NOCHECK);
+ if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0)) {
+ if (!in_force(FORCE_BAD_VERIFY))
+ ohshit(_("verification on package %s failed!"), filename);
+ else
+ notice(_("verification on package %s failed; "
+ "but installing anyway as you requested"), filename);
+ } else {
+ printf(_("passed\n"));
+ }
+ }
+}
+
+static char *
+get_control_dir(char *cidir)
+{
+ if (f_noact) {
+ char *tmpdir;
+
+ tmpdir = mkdtemp(path_make_temp_template("dpkg"));
+ if (tmpdir == NULL)
+ ohshite(_("unable to create temporary directory"));
+
+ cidir = m_realloc(cidir, strlen(tmpdir) + MAXCONTROLFILENAME + 10);
+
+ strcpy(cidir, tmpdir);
+
+ free(tmpdir);
+ } else {
+ const char *admindir;
+
+ admindir = dpkg_db_get_dir();
+
+ /* The admindir length is always constant on a dpkg execution run. */
+ if (cidir == NULL)
+ cidir = m_malloc(strlen(admindir) + sizeof(CONTROLDIRTMP) +
+ MAXCONTROLFILENAME + 10);
+
+ /* We want it to be on the same filesystem so that we can
+ * use rename(2) to install the postinst &c. */
+ strcpy(cidir, admindir);
+ strcat(cidir, "/" CONTROLDIRTMP);
+
+ /* Make sure the control information directory is empty. */
+ path_remove_tree(cidir);
+ }
+
+ strcat(cidir, "/");
+
+ return cidir;
+}
+
+static void
+pkg_check_depcon(struct pkginfo *pkg, const char *pfilename)
+{
+ struct dependency *dsearch;
+ struct deppossi *psearch;
+ struct pkginfo *fixbytrigaw;
+ static struct varbuf depprobwhy;
+
+ /* Check if anything is installed that we conflict with, or not installed
+ * that we need. */
+ ensure_package_clientdata(pkg);
+ pkg->clientdata->istobe = PKG_ISTOBE_INSTALLNEW;
+
+ for (dsearch = pkg->available.depends; dsearch; dsearch = dsearch->next) {
+ switch (dsearch->type) {
+ case dep_conflicts:
+ /* Look for things we conflict with. */
+ check_conflict(dsearch, pkg, pfilename);
+ break;
+ case dep_breaks:
+ /* Look for things we break. */
+ check_breaks(dsearch, pkg, pfilename);
+ break;
+ case dep_provides:
+ /* Look for things that conflict with what we provide. */
+ for (psearch = dsearch->list->ed->depended.installed;
+ psearch;
+ psearch = psearch->rev_next) {
+ if (psearch->up->type != dep_conflicts)
+ continue;
+ check_conflict(psearch->up, pkg, pfilename);
+ }
+ break;
+ case dep_suggests:
+ case dep_recommends:
+ case dep_depends:
+ case dep_replaces:
+ case dep_enhances:
+ /* Ignore these here. */
+ break;
+ case dep_predepends:
+ if (!depisok(dsearch, &depprobwhy, NULL, &fixbytrigaw, true)) {
+ if (fixbytrigaw) {
+ while (fixbytrigaw->trigaw.head)
+ trigproc(fixbytrigaw->trigaw.head->pend, TRIGPROC_REQUIRED);
+ } else {
+ varbuf_end_str(&depprobwhy);
+ notice(_("regarding %s containing %s, pre-dependency problem:\n%s"),
+ pfilename, pkgbin_name(pkg, &pkg->available, pnaw_nonambig),
+ depprobwhy.buf);
+ if (!force_depends(dsearch->list))
+ ohshit(_("pre-dependency problem - not installing %.250s"),
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig));
+ warning(_("ignoring pre-dependency problem!"));
+ }
+ }
+ }
+ }
+
+ /* Look for things that conflict with us. */
+ for (psearch = pkg->set->depended.installed; psearch; psearch = psearch->rev_next) {
+ if (psearch->up->type != dep_conflicts)
+ continue;
+
+ check_conflict(psearch->up, pkg, pfilename);
+ }
+}
+
+static void
+pkg_deconfigure_others(struct pkginfo *pkg)
+{
+ struct pkg_deconf_list *deconpil;
+
+ for (deconpil = deconfigure; deconpil; deconpil = deconpil->next) {
+ struct pkginfo *removing = deconpil->pkg_removal;
+
+ if (removing)
+ printf(_("De-configuring %s (%s), to allow removal of %s (%s) ...\n"),
+ pkg_name(deconpil->pkg, pnaw_nonambig),
+ versiondescribe(&deconpil->pkg->installed.version, vdew_nonambig),
+ pkg_name(removing, pnaw_nonambig),
+ versiondescribe(&removing->installed.version, vdew_nonambig));
+ else
+ printf(_("De-configuring %s (%s) ...\n"),
+ pkg_name(deconpil->pkg, pnaw_nonambig),
+ versiondescribe(&deconpil->pkg->installed.version, vdew_nonambig));
+
+ trig_activate_packageprocessing(deconpil->pkg);
+ pkg_set_status(deconpil->pkg, PKG_STAT_HALFCONFIGURED);
+ modstatdb_note(deconpil->pkg);
+
+ /* This means that we *either* go and run postinst abort-deconfigure,
+ * *or* queue the package for later configure processing, depending
+ * on which error cleanup route gets taken. */
+ push_cleanup_fallback(cu_prermdeconfigure, ~ehflag_normaltidy,
+ ok_prermdeconfigure, ehflag_normaltidy,
+ 3, (void *)deconpil->pkg, (void *)removing, (void *)pkg);
+
+ if (removing) {
+ maintscript_installed(deconpil->pkg, PRERMFILE, "pre-removal",
+ "deconfigure", "in-favour",
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig),
+ versiondescribe(&pkg->available.version,
+ vdew_nonambig),
+ "removing",
+ pkg_name(removing, pnaw_nonambig),
+ versiondescribe(&removing->installed.version,
+ vdew_nonambig),
+ NULL);
+ } else {
+ maintscript_installed(deconpil->pkg, PRERMFILE, "pre-removal",
+ "deconfigure", "in-favour",
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig),
+ versiondescribe(&pkg->available.version,
+ vdew_nonambig),
+ NULL);
+ }
+ }
+}
+
+/**
+ * Read the conffiles, and copy the hashes across.
+ */
+static void
+deb_parse_conffiles(struct pkginfo *pkg, const char *control_conffiles,
+ struct fsys_namenode_queue *newconffiles)
+{
+ FILE *conff;
+ char conffilenamebuf[MAXCONFFILENAME];
+
+ conff = fopen(control_conffiles, "r");
+ if (conff == NULL) {
+ if (errno == ENOENT)
+ return;
+ ohshite(_("error trying to open %.250s"), control_conffiles);
+ }
+
+ push_cleanup(cu_closestream, ehflag_bombout, 1, conff);
+
+ while (fgets(conffilenamebuf, MAXCONFFILENAME - 2, conff)) {
+ struct pkginfo *otherpkg;
+ struct fsys_node_pkgs_iter *iter;
+ struct fsys_namenode *namenode;
+ struct fsys_namenode_list *newconff;
+ struct conffile *searchconff;
+ char *p;
+
+ p = conffilenamebuf + strlen(conffilenamebuf);
+ if (p == conffilenamebuf)
+ ohshit(_("conffile file contains an empty line"));
+ if (p[-1] != '\n')
+ ohshit(_("conffile name '%s' is too long, or missing final newline"),
+ conffilenamebuf);
+ while (p > conffilenamebuf && c_isspace(p[-1]))
+ --p;
+ if (p == conffilenamebuf)
+ continue;
+ *p = '\0';
+
+ namenode = fsys_hash_find_node(conffilenamebuf, 0);
+ namenode->oldhash = NEWCONFFILEFLAG;
+ newconff = tar_fsys_namenode_queue_push(newconffiles, namenode);
+
+ /*
+ * Let's see if any packages have this file.
+ *
+ * If they do we check to see if they listed it as a conffile,
+ * and if they did we copy the hash across. Since (for plain
+ * file conffiles, which is the only kind we are supposed to
+ * have) there will only be one package which ‘has’ the file,
+ * this will usually mean we only look in the package which
+ * we are installing now.
+ *
+ * The ‘conffiles’ data in the status file is ignored when a
+ * package is not also listed in the file ownership database as
+ * having that file. If several packages are listed as owning
+ * the file we pick one at random.
+ */
+ searchconff = NULL;
+
+ iter = fsys_node_pkgs_iter_new(newconff->namenode);
+ while ((otherpkg = fsys_node_pkgs_iter_next(iter))) {
+ debug(dbg_conffdetail,
+ "process_archive conffile '%s' in package %s - conff ?",
+ newconff->namenode->name, pkg_name(otherpkg, pnaw_always));
+ for (searchconff = otherpkg->installed.conffiles;
+ searchconff && strcmp(newconff->namenode->name, searchconff->name);
+ searchconff = searchconff->next)
+ debug(dbg_conffdetail,
+ "process_archive conffile '%s' in package %s - conff ? not '%s'",
+ newconff->namenode->name, pkg_name(otherpkg, pnaw_always),
+ searchconff->name);
+ if (searchconff) {
+ debug(dbg_conff,
+ "process_archive conffile '%s' package=%s %s hash=%s",
+ newconff->namenode->name, pkg_name(otherpkg, pnaw_always),
+ otherpkg == pkg ? "same" : "different!",
+ searchconff->hash);
+ if (otherpkg == pkg)
+ break;
+ }
+ }
+ fsys_node_pkgs_iter_free(iter);
+
+ if (searchconff) {
+ /* We don't copy ‘obsolete’; it's not obsolete in the new package. */
+ newconff->namenode->oldhash = searchconff->hash;
+ } else {
+ debug(dbg_conff, "process_archive conffile '%s' no package, no hash",
+ newconff->namenode->name);
+ }
+ newconff->namenode->flags |= FNNF_NEW_CONFF;
+ }
+
+ if (ferror(conff))
+ ohshite(_("read error in %.250s"), control_conffiles);
+ pop_cleanup(ehflag_normaltidy); /* conff = fopen() */
+ if (fclose(conff))
+ ohshite(_("error closing %.250s"), control_conffiles);
+}
+
+static struct pkg_queue conflictors = PKG_QUEUE_INIT;
+
+void
+enqueue_conflictor(struct pkginfo *pkg)
+{
+ pkg_queue_push(&conflictors, pkg);
+}
+
+static void
+pkg_infodb_remove_file(const char *filename, const char *filetype)
+{
+ if (unlink(filename))
+ ohshite(_("unable to delete control info file '%.250s'"), filename);
+
+ debug(dbg_scripts, "removal_bulk info unlinked %s", filename);
+}
+
+static struct match_node *match_head = NULL;
+
+static void
+pkg_infodb_update_file(const char *filename, const char *filetype)
+{
+ if (strlen(filetype) > MAXCONTROLFILENAME)
+ ohshit(_("old version of package has overly-long info file name starting '%.250s'"),
+ filename);
+
+ /* We do the list separately. */
+ if (strcmp(filetype, LISTFILE) == 0)
+ return;
+
+ /* We keep files to rename in a list as doing the rename immediately
+ * might influence the current readdir(), the just renamed file might
+ * be returned a second time as it's actually a new file from the
+ * point of view of the filesystem. */
+ match_head = match_node_new(filename, filetype, match_head);
+}
+
+static void
+pkg_infodb_update(struct pkginfo *pkg, char *cidir, char *cidirrest)
+{
+ struct match_node *match_node;
+ DIR *dsd;
+ struct dirent *de;
+
+ /* Deallocate the match list in case we aborted previously. */
+ while ((match_node = match_head)) {
+ match_head = match_node->next;
+ match_node_free(match_node);
+ }
+
+ pkg_infodb_foreach(pkg, &pkg->available, pkg_infodb_update_file);
+
+ while ((match_node = match_head)) {
+ strcpy(cidirrest, match_node->filetype);
+
+ if (!rename(cidir, match_node->filename)) {
+ debug(dbg_scripts, "process_archive info installed %s as %s",
+ cidir, match_node->filename);
+ } else if (errno == ENOENT) {
+ /* Right, no new version. */
+ if (unlink(match_node->filename))
+ ohshite(_("unable to remove obsolete info file '%.250s'"),
+ match_node->filename);
+ debug(dbg_scripts, "process_archive info unlinked %s",
+ match_node->filename);
+ } else {
+ ohshite(_("unable to install (supposed) new info file '%.250s'"), cidir);
+ }
+ match_head = match_node->next;
+ match_node_free(match_node);
+ }
+
+ /* The control directory itself. */
+ cidirrest[0] = '\0';
+ dsd = opendir(cidir);
+ if (!dsd)
+ ohshite(_("unable to open temp control directory"));
+ push_cleanup(cu_closedir, ~0, 1, (void *)dsd);
+ while ((de = readdir(dsd))) {
+ const char *newinfofilename;
+
+ if (strchr(de->d_name, '.')) {
+ debug(dbg_scripts, "process_archive tmp.ci script/file '%s' contains dot",
+ de->d_name);
+ continue;
+ }
+ if (strlen(de->d_name) > MAXCONTROLFILENAME)
+ ohshit(_("package contains overly-long control info file name (starting '%.50s')"),
+ de->d_name);
+
+ strcpy(cidirrest, de->d_name);
+
+ /* First we check it's not a directory. */
+ if (rmdir(cidir) == 0)
+ ohshit(_("package control info contained directory '%.250s'"), cidir);
+ else if (errno != ENOTDIR)
+ ohshite(_("package control info rmdir of '%.250s' didn't say not a dir"),
+ de->d_name);
+
+ /* Ignore the control file. */
+ if (strcmp(de->d_name, CONTROLFILE) == 0) {
+ debug(dbg_scripts, "process_archive tmp.ci script/file '%s' is control",
+ cidir);
+ continue;
+ }
+ if (strcmp(de->d_name, LISTFILE) == 0) {
+ warning(_("package %s contained list as info file"),
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig));
+ continue;
+ }
+
+ /* Right, install it */
+ newinfofilename = pkg_infodb_get_file(pkg, &pkg->available, de->d_name);
+ if (rename(cidir, newinfofilename))
+ ohshite(_("unable to install new info file '%.250s' as '%.250s'"),
+ cidir, newinfofilename);
+
+ debug(dbg_scripts,
+ "process_archive tmp.ci script/file '%s' installed as '%s'",
+ cidir, newinfofilename);
+ }
+ pop_cleanup(ehflag_normaltidy); /* closedir */
+
+ /* If the old and new versions use a different infodb layout, get rid
+ * of the files using the old layout. */
+ if (pkg->installed.multiarch != pkg->available.multiarch &&
+ (pkg->installed.multiarch == PKG_MULTIARCH_SAME ||
+ pkg->available.multiarch == PKG_MULTIARCH_SAME)) {
+ debug(dbg_scripts,
+ "process_archive remove old info files after db layout switch");
+ pkg_infodb_foreach(pkg, &pkg->installed, pkg_infodb_remove_file);
+ }
+
+ dir_sync_path(pkg_infodb_get_dir());
+}
+
+static void
+pkg_remove_old_files(struct pkginfo *pkg,
+ struct fsys_namenode_queue *newfiles_queue,
+ struct fsys_namenode_queue *newconffiles)
+{
+ struct fsys_hash_rev_iter rev_iter;
+ struct fsys_namenode *namenode;
+ struct stat stab, oldfs;
+
+ fsys_hash_rev_iter_init(&rev_iter, pkg->files);
+
+ while ((namenode = fsys_hash_rev_iter_next(&rev_iter))) {
+ struct fsys_namenode *usenode;
+
+ if ((namenode->flags & FNNF_NEW_CONFF) ||
+ (namenode->flags & FNNF_NEW_INARCHIVE))
+ continue;
+
+ usenode = namenodetouse(namenode, pkg, &pkg->installed);
+
+ varbuf_rollback(&fnamevb, &fname_state);
+ varbuf_add_str(&fnamevb, usenode->name);
+ varbuf_end_str(&fnamevb);
+
+ if (!stat(namenode->name, &stab) && S_ISDIR(stab.st_mode)) {
+ debug(dbg_eachfiledetail, "process_archive: %s is a directory",
+ namenode->name);
+ if (dir_is_used_by_others(namenode, pkg))
+ continue;
+ }
+
+ if (lstat(fnamevb.buf, &oldfs)) {
+ if (!(errno == ENOENT || errno == ELOOP || errno == ENOTDIR))
+ warning(_("could not stat old file '%.250s' so not deleting it: %s"),
+ fnamevb.buf, strerror(errno));
+ continue;
+ }
+ if (S_ISDIR(oldfs.st_mode)) {
+ trig_path_activate(usenode, pkg);
+
+ /* Do not try to remove the root directory. */
+ if (strcmp(usenode->name, "/.") == 0)
+ continue;
+
+ if (rmdir(fnamevb.buf)) {
+ warning(_("unable to delete old directory '%.250s': %s"),
+ namenode->name, strerror(errno));
+ } else if ((namenode->flags & FNNF_OLD_CONFF)) {
+ warning(_("old conffile '%.250s' was an empty directory "
+ "(and has now been deleted)"), namenode->name);
+ }
+ } else {
+ struct fsys_namenode_list *sameas = NULL;
+ struct fsys_namenode_list *cfile;
+ static struct file_ondisk_id empty_ondisk_id;
+ struct varbuf cfilename = VARBUF_INIT;
+
+ /*
+ * Ok, it's an old file, but is it really not in the new package?
+ * It might be known by a different name because of symlinks.
+ *
+ * We need to check to make sure, so we stat the file, then compare
+ * it to the new list. If we find a dev/inode match, we assume they
+ * are the same file, and leave it alone. NOTE: we don't check in
+ * other packages for sanity reasons (we don't want to stat _all_
+ * the files on the system).
+ *
+ * We run down the list of _new_ files in this package. This keeps
+ * the process a little leaner. We are only worried about new ones
+ * since ones that stayed the same don't really apply here.
+ */
+
+ /* If we can't stat the old or new file, or it's a directory,
+ * we leave it up to the normal code. */
+ debug(dbg_eachfile, "process_archive: checking %s for same files on "
+ "upgrade/downgrade", fnamevb.buf);
+
+ for (cfile = newfiles_queue->head; cfile; cfile = cfile->next) {
+ /* If the file has been filtered then treat it as if it didn't exist
+ * on the file system. */
+ if (cfile->namenode->flags & FNNF_FILTERED)
+ continue;
+
+ if (cfile->namenode->file_ondisk_id == NULL) {
+ struct stat tmp_stat;
+
+ varbuf_reset(&cfilename);
+ varbuf_add_str(&cfilename, instdir);
+ varbuf_add_str(&cfilename, cfile->namenode->name);
+ varbuf_end_str(&cfilename);
+
+ if (lstat(cfilename.buf, &tmp_stat) == 0) {
+ struct file_ondisk_id *file_ondisk_id;
+
+ file_ondisk_id = nfmalloc(sizeof(*file_ondisk_id));
+ file_ondisk_id->id_dev = tmp_stat.st_dev;
+ file_ondisk_id->id_ino = tmp_stat.st_ino;
+ cfile->namenode->file_ondisk_id = file_ondisk_id;
+ } else {
+ if (!(errno == ENOENT || errno == ELOOP || errno == ENOTDIR))
+ ohshite(_("unable to stat other new file '%.250s'"),
+ cfile->namenode->name);
+ cfile->namenode->file_ondisk_id = &empty_ondisk_id;
+ continue;
+ }
+ }
+
+ if (cfile->namenode->file_ondisk_id == &empty_ondisk_id)
+ continue;
+
+ if (oldfs.st_dev == cfile->namenode->file_ondisk_id->id_dev &&
+ oldfs.st_ino == cfile->namenode->file_ondisk_id->id_ino) {
+ if (sameas)
+ warning(_("old file '%.250s' is the same as several new files! "
+ "(both '%.250s' and '%.250s')"), fnamevb.buf,
+ sameas->namenode->name, cfile->namenode->name);
+ sameas = cfile;
+ debug(dbg_eachfile, "process_archive: not removing %s, "
+ "since it matches %s", fnamevb.buf, cfile->namenode->name);
+ }
+ }
+
+ varbuf_destroy(&cfilename);
+
+ if ((namenode->flags & FNNF_OLD_CONFF)) {
+ if (sameas) {
+ if (sameas->namenode->flags & FNNF_NEW_CONFF) {
+ if (strcmp(sameas->namenode->oldhash, NEWCONFFILEFLAG) == 0) {
+ sameas->namenode->oldhash = namenode->oldhash;
+ debug(dbg_eachfile, "process_archive: old conff %s "
+ "is same as new conff %s, copying hash",
+ namenode->name, sameas->namenode->name);
+ } else {
+ debug(dbg_eachfile, "process_archive: old conff %s "
+ "is same as new conff %s but latter already has hash",
+ namenode->name, sameas->namenode->name);
+ }
+ }
+ } else {
+ debug(dbg_eachfile, "process_archive: old conff %s "
+ "is disappearing", namenode->name);
+ namenode->flags |= FNNF_OBS_CONFF;
+ tar_fsys_namenode_queue_push(newconffiles, namenode);
+ tar_fsys_namenode_queue_push(newfiles_queue, namenode);
+ }
+ continue;
+ }
+
+ if (sameas)
+ continue;
+
+ trig_path_activate(usenode, pkg);
+
+ if (secure_unlink_statted(fnamevb.buf, &oldfs)) {
+ warning(_("unable to securely remove old file '%.250s': %s"),
+ namenode->name, strerror(errno));
+ }
+ } /* !S_ISDIR */
+ }
+}
+
+static void
+pkg_update_fields(struct pkginfo *pkg, struct fsys_namenode_queue *newconffiles)
+{
+ struct dependency *newdeplist, **newdeplistlastp;
+ struct dependency *newdep, *dep;
+ struct deppossi **newpossilastp, *possi, *newpossi;
+ struct conffile **iconffileslastp, *newiconff;
+ struct fsys_namenode_list *cfile;
+
+ /* The dependencies are the most difficult. We have to build
+ * a whole new forward dependency tree. At least the reverse
+ * links (linking our deppossi's into the reverse chains)
+ * can be done by copy_dependency_links. */
+ newdeplist = NULL;
+ newdeplistlastp = &newdeplist;
+ for (dep = pkg->available.depends; dep; dep = dep->next) {
+ newdep = nfmalloc(sizeof(*newdep));
+ newdep->up = pkg;
+ newdep->next = NULL;
+ newdep->list = NULL;
+ newpossilastp = &newdep->list;
+
+ for (possi = dep->list; possi; possi = possi->next) {
+ newpossi = nfmalloc(sizeof(*newpossi));
+ newpossi->up = newdep;
+ newpossi->ed = possi->ed;
+ newpossi->next = NULL;
+ newpossi->rev_next = newpossi->rev_prev = NULL;
+ newpossi->arch_is_implicit = possi->arch_is_implicit;
+ newpossi->arch = possi->arch;
+ newpossi->verrel = possi->verrel;
+ if (possi->verrel != DPKG_RELATION_NONE)
+ newpossi->version = possi->version;
+ else
+ dpkg_version_blank(&newpossi->version);
+ newpossi->cyclebreak = false;
+ *newpossilastp = newpossi;
+ newpossilastp = &newpossi->next;
+ }
+ newdep->type = dep->type;
+ *newdeplistlastp = newdep;
+ newdeplistlastp = &newdep->next;
+ }
+
+ /* Right, now we've replicated the forward tree, we
+ * get copy_dependency_links to remove all the old dependency
+ * structures from the reverse links and add the new dependency
+ * structures in instead. It also copies the new dependency
+ * structure pointer for this package into the right field. */
+ copy_dependency_links(pkg, &pkg->installed.depends, newdeplist, 0);
+
+ /* We copy the text fields. */
+ pkg->installed.essential = pkg->available.essential;
+ pkg->installed.multiarch = pkg->available.multiarch;
+ pkg->installed.description = pkg->available.description;
+ pkg->installed.maintainer = pkg->available.maintainer;
+ pkg->installed.source = pkg->available.source;
+ pkg->installed.arch = pkg->available.arch;
+ pkg->installed.pkgname_archqual = pkg->available.pkgname_archqual;
+ pkg->installed.installedsize = pkg->available.installedsize;
+ pkg->installed.version = pkg->available.version;
+ pkg->installed.origin = pkg->available.origin;
+ pkg->installed.bugs = pkg->available.bugs;
+
+ /* We have to generate our own conffiles structure. */
+ pkg->installed.conffiles = NULL;
+ iconffileslastp = &pkg->installed.conffiles;
+ for (cfile = newconffiles->head; cfile; cfile = cfile->next) {
+ newiconff = nfmalloc(sizeof(*newiconff));
+ newiconff->next = NULL;
+ newiconff->name = nfstrsave(cfile->namenode->name);
+ newiconff->hash = nfstrsave(cfile->namenode->oldhash);
+ newiconff->obsolete = !!(cfile->namenode->flags & FNNF_OBS_CONFF);
+ *iconffileslastp = newiconff;
+ iconffileslastp = &newiconff->next;
+ }
+
+ /* We can just copy the arbitrary fields list, because it is
+ * never even rearranged. Phew! */
+ pkg->installed.arbs = pkg->available.arbs;
+}
+
+static void
+pkg_disappear(struct pkginfo *pkg, struct pkginfo *infavour)
+{
+ printf(_("(Noting disappearance of %s, which has been completely replaced.)\n"),
+ pkg_name(pkg, pnaw_nonambig));
+ log_action("disappear", pkg, &pkg->installed);
+ debug(dbg_general, "pkg_disappear disappearing %s",
+ pkg_name(pkg, pnaw_always));
+
+ trig_activate_packageprocessing(pkg);
+ maintscript_installed(pkg, POSTRMFILE,
+ "post-removal script (for disappearance)",
+ "disappear",
+ pkgbin_name(infavour, &infavour->available,
+ pnaw_nonambig),
+ versiondescribe(&infavour->available.version,
+ vdew_nonambig),
+ NULL);
+
+ /* OK, now we delete all the stuff in the ‘info’ directory ... */
+ debug(dbg_general, "pkg_disappear cleaning info directory");
+ pkg_infodb_foreach(pkg, &pkg->installed, pkg_infodb_remove_file);
+ dir_sync_path(pkg_infodb_get_dir());
+
+ pkg_set_status(pkg, PKG_STAT_NOTINSTALLED);
+ pkg_set_want(pkg, PKG_WANT_UNKNOWN);
+ pkg_reset_eflags(pkg);
+
+ dpkg_version_blank(&pkg->configversion);
+ pkgbin_blank(&pkg->installed);
+
+ pkg->files_list_valid = false;
+
+ modstatdb_note(pkg);
+}
+
+static void
+pkg_disappear_others(struct pkginfo *pkg)
+{
+ struct pkg_hash_iter *iter;
+ struct pkginfo *otherpkg;
+ struct fsys_namenode_list *cfile;
+ struct deppossi *pdep;
+ struct dependency *providecheck;
+ struct varbuf depprobwhy = VARBUF_INIT;
+
+ iter = pkg_hash_iter_new();
+ while ((otherpkg = pkg_hash_iter_next_pkg(iter)) != NULL) {
+ ensure_package_clientdata(otherpkg);
+
+ if (otherpkg == pkg ||
+ otherpkg->status == PKG_STAT_NOTINSTALLED ||
+ otherpkg->status == PKG_STAT_CONFIGFILES ||
+ otherpkg->clientdata->istobe == PKG_ISTOBE_REMOVE ||
+ !otherpkg->files)
+ continue;
+
+ /* Do not try to disappear other packages from the same set
+ * if they are Multi-Arch: same */
+ if (pkg->installed.multiarch == PKG_MULTIARCH_SAME &&
+ otherpkg->installed.multiarch == PKG_MULTIARCH_SAME &&
+ otherpkg->set == pkg->set)
+ continue;
+
+ debug(dbg_veryverbose, "process_archive checking disappearance %s",
+ pkg_name(otherpkg, pnaw_always));
+
+ if (otherpkg->clientdata->istobe != PKG_ISTOBE_NORMAL &&
+ otherpkg->clientdata->istobe != PKG_ISTOBE_DECONFIGURE)
+ internerr("disappearing package %s is not to be normal or deconfigure, "
+ "is to be %d",
+ pkg_name(otherpkg, pnaw_always), otherpkg->clientdata->istobe);
+
+ for (cfile = otherpkg->files;
+ cfile && strcmp(cfile->namenode->name, "/.") == 0;
+ cfile = cfile->next);
+ if (!cfile) {
+ debug(dbg_stupidlyverbose, "process_archive no non-root, no disappear");
+ continue;
+ }
+ for (cfile = otherpkg->files;
+ cfile && !filesavespackage(cfile, otherpkg, pkg);
+ cfile = cfile->next);
+ if (cfile)
+ continue;
+
+ /* So dependency things will give right answers ... */
+ otherpkg->clientdata->istobe = PKG_ISTOBE_REMOVE;
+ debug(dbg_veryverbose, "process_archive disappear checking dependencies");
+ for (pdep = otherpkg->set->depended.installed;
+ pdep;
+ pdep = pdep->rev_next) {
+ if (pdep->up->type != dep_depends &&
+ pdep->up->type != dep_predepends &&
+ pdep->up->type != dep_recommends)
+ continue;
+
+ if (depisok(pdep->up, &depprobwhy, NULL, NULL, false))
+ continue;
+
+ varbuf_end_str(&depprobwhy);
+ debug(dbg_veryverbose,"process_archive cannot disappear: %s",
+ depprobwhy.buf);
+ break;
+ }
+ if (!pdep) {
+ /* If we haven't found a reason not to yet, let's look some more. */
+ for (providecheck = otherpkg->installed.depends;
+ providecheck;
+ providecheck = providecheck->next) {
+ if (providecheck->type != dep_provides)
+ continue;
+
+ for (pdep = providecheck->list->ed->depended.installed;
+ pdep;
+ pdep = pdep->rev_next) {
+ if (pdep->up->type != dep_depends &&
+ pdep->up->type != dep_predepends &&
+ pdep->up->type != dep_recommends)
+ continue;
+
+ if (depisok(pdep->up, &depprobwhy, NULL, NULL, false))
+ continue;
+
+ varbuf_end_str(&depprobwhy);
+ debug(dbg_veryverbose,
+ "process_archive cannot disappear (provides %s): %s",
+ providecheck->list->ed->name, depprobwhy.buf);
+ goto break_from_both_loops_at_once;
+ }
+ }
+ break_from_both_loops_at_once:;
+ }
+ otherpkg->clientdata->istobe = PKG_ISTOBE_NORMAL;
+ if (pdep)
+ continue;
+
+ /* No, we're disappearing it. This is the wrong time to go and
+ * run maintainer scripts and things, as we can't back out. But
+ * what can we do ? It has to be run this late. */
+ pkg_disappear(otherpkg, pkg);
+ } /* while (otherpkg= ... */
+ pkg_hash_iter_free(iter);
+}
+
+/**
+ * Check if all instances of a pkgset are getting in sync.
+ *
+ * If that's the case, the extraction is going to ensure consistency
+ * of shared files.
+ */
+static bool
+pkgset_getting_in_sync(struct pkginfo *pkg)
+{
+ struct pkginfo *otherpkg;
+
+ for (otherpkg = &pkg->set->pkg; otherpkg; otherpkg = otherpkg->arch_next) {
+ if (otherpkg == pkg)
+ continue;
+ if (otherpkg->status <= PKG_STAT_CONFIGFILES)
+ continue;
+ if (dpkg_version_compare(&pkg->available.version,
+ &otherpkg->installed.version)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void
+pkg_remove_files_from_others(struct pkginfo *pkg, struct fsys_namenode_list *newfileslist)
+{
+ struct fsys_namenode_list *cfile;
+ struct pkginfo *otherpkg;
+
+ for (cfile = newfileslist; cfile; cfile = cfile->next) {
+ struct fsys_node_pkgs_iter *iter;
+ struct pkgset *divpkgset;
+
+ if (!(cfile->namenode->flags & FNNF_ELIDE_OTHER_LISTS))
+ continue;
+
+ if (cfile->namenode->divert && cfile->namenode->divert->useinstead) {
+ divpkgset = cfile->namenode->divert->pkgset;
+ if (divpkgset == pkg->set) {
+ debug(dbg_eachfile,
+ "process_archive not overwriting any '%s' (overriding, '%s')",
+ cfile->namenode->name, cfile->namenode->divert->useinstead->name);
+ continue;
+ } else {
+ debug(dbg_eachfile,
+ "process_archive looking for overwriting '%s' (overridden by %s)",
+ cfile->namenode->name, divpkgset ? divpkgset->name : "<local>");
+ }
+ } else {
+ divpkgset = NULL;
+ debug(dbg_eachfile, "process_archive looking for overwriting '%s'",
+ cfile->namenode->name);
+ }
+
+ iter = fsys_node_pkgs_iter_new(cfile->namenode);
+ while ((otherpkg = fsys_node_pkgs_iter_next(iter))) {
+ debug(dbg_eachfiledetail, "process_archive ... found in %s",
+ pkg_name(otherpkg, pnaw_always));
+
+ /* A pkgset can share files between instances, so there's no point
+ * in rewriting the file that's already in place. */
+ if (otherpkg->set == pkg->set)
+ continue;
+
+ if (otherpkg->set == divpkgset) {
+ debug(dbg_eachfiledetail, "process_archive ... diverted, skipping");
+ continue;
+ }
+
+ if (cfile->namenode->flags & FNNF_NEW_CONFF)
+ conffile_mark_obsolete(otherpkg, cfile->namenode);
+
+ /* If !files_list_valid then it's one of the disappeared packages above
+ * or we have already updated the files list file, and we don't bother
+ * with it here, clearly. */
+ if (!otherpkg->files_list_valid)
+ continue;
+
+ /* Found one. We delete the list entry for this file,
+ * (and any others in the same package) and then mark the package
+ * as requiring a reread. */
+ write_filelist_except(otherpkg, &otherpkg->installed,
+ otherpkg->files, FNNF_ELIDE_OTHER_LISTS);
+ debug(dbg_veryverbose, "process_archive overwrote from %s",
+ pkg_name(otherpkg, pnaw_always));
+ }
+ fsys_node_pkgs_iter_free(iter);
+ }
+}
+
+static void
+pkg_remove_backup_files(struct pkginfo *pkg, struct fsys_namenode_list *newfileslist)
+{
+ struct fsys_namenode_list *cfile;
+
+ for (cfile = newfileslist; cfile; cfile = cfile->next) {
+ struct fsys_namenode *usenode;
+
+ if (cfile->namenode->flags & FNNF_NEW_CONFF)
+ continue;
+
+ usenode = namenodetouse(cfile->namenode, pkg, &pkg->installed);
+
+ /* Do not try to remove backups for the root directory. */
+ if (strcmp(usenode->name, "/.") == 0)
+ continue;
+
+ varbuf_rollback(&fnametmpvb, &fname_state);
+ varbuf_add_str(&fnametmpvb, usenode->name);
+ varbuf_add_str(&fnametmpvb, DPKGTEMPEXT);
+ varbuf_end_str(&fnametmpvb);
+ path_remove_tree(fnametmpvb.buf);
+ }
+}
+
+void process_archive(const char *filename) {
+ static const struct tar_operations tf = {
+ .read = tarfileread,
+ .extract_file = tarobject,
+ .link = tarobject,
+ .symlink = tarobject,
+ .mkdir = tarobject,
+ .mknod = tarobject,
+ };
+
+ /* These need to be static so that we can pass their addresses to
+ * push_cleanup as arguments to the cu_xxx routines; if an error occurs
+ * we unwind the stack before processing the cleanup list, and these
+ * variables had better still exist ... */
+ static int p1[2];
+ static enum pkgstatus oldversionstatus;
+ static struct tarcontext tc;
+
+ struct tar_archive tar;
+ struct dpkg_error err;
+ enum parsedbflags parsedb_flags;
+ int rc;
+ pid_t pid;
+ struct pkginfo *pkg, *otherpkg;
+ struct pkg_list *conflictor_iter;
+ char *cidir = NULL;
+ char *cidirrest;
+ char *psize;
+ const char *pfilename;
+ struct fsys_namenode_queue newconffiles, newfiles_queue;
+ struct stat stab;
+
+ cleanup_pkg_failed= cleanup_conflictor_failed= 0;
+
+ pfilename = summarize_filename(filename);
+
+ if (stat(filename, &stab))
+ ohshite(_("cannot access archive '%s'"), filename);
+
+ /* We can't ‘tentatively-reassemble’ packages. */
+ if (!f_noact) {
+ if (!deb_reassemble(&filename, &pfilename))
+ return;
+ }
+
+ /* Verify the package. */
+ if (!f_nodebsig)
+ deb_verify(filename);
+
+ /* Get the control information directory. */
+ cidir = get_control_dir(cidir);
+ cidirrest = cidir + strlen(cidir);
+ push_cleanup(cu_cidir, ~0, 2, (void *)cidir, (void *)cidirrest);
+
+ pid = subproc_fork();
+ if (pid == 0) {
+ cidirrest[-1] = '\0';
+ execlp(BACKEND, BACKEND, "--control", filename, cidir, NULL);
+ ohshite(_("unable to execute %s (%s)"),
+ _("package control information extraction"), BACKEND);
+ }
+ subproc_reap(pid, BACKEND " --control", 0);
+
+ /* We want to guarantee the extracted files are on the disk, so that the
+ * subsequent renames to the info database do not end up with old or zero
+ * length files in case of a system crash. As neither dpkg-deb nor tar do
+ * explicit fsync()s, we have to do them here.
+ * XXX: This could be avoided by switching to an internal tar extractor. */
+ dir_sync_contents(cidir);
+
+ strcpy(cidirrest,CONTROLFILE);
+
+ if (cipaction->arg_int == act_avail)
+ parsedb_flags = pdb_parse_available;
+ else
+ parsedb_flags = pdb_parse_binary;
+ parsedb_flags |= pdb_ignore_archives;
+ if (in_force(FORCE_BAD_VERSION))
+ parsedb_flags |= pdb_lax_version_parser;
+
+ parsedb(cidir, parsedb_flags, &pkg);
+
+ if (!pkg->archives) {
+ pkg->archives = nfmalloc(sizeof(*pkg->archives));
+ pkg->archives->next = NULL;
+ pkg->archives->name = NULL;
+ pkg->archives->msdosname = NULL;
+ pkg->archives->md5sum = NULL;
+ }
+ /* Always nfmalloc. Otherwise, we may overwrite some other field (like
+ * md5sum). */
+ psize = nfmalloc(30);
+ sprintf(psize, "%jd", (intmax_t)stab.st_size);
+ pkg->archives->size = psize;
+
+ if (cipaction->arg_int == act_avail) {
+ printf(_("Recorded info about %s from %s.\n"),
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig), pfilename);
+ pop_cleanup(ehflag_normaltidy);
+ return;
+ }
+
+ if (pkg->available.arch->type != DPKG_ARCH_ALL &&
+ pkg->available.arch->type != DPKG_ARCH_NATIVE &&
+ pkg->available.arch->type != DPKG_ARCH_FOREIGN)
+ forcibleerr(FORCE_ARCHITECTURE,
+ _("package architecture (%s) does not match system (%s)"),
+ pkg->available.arch->name,
+ dpkg_arch_get(DPKG_ARCH_NATIVE)->name);
+
+ clear_deconfigure_queue();
+ clear_istobes();
+
+ if (wanttoinstall(pkg)) {
+ pkg_set_want(pkg, PKG_WANT_INSTALL);
+ } else {
+ pop_cleanup(ehflag_normaltidy);
+ return;
+ }
+
+ /* Deconfigure other instances from a pkgset if they are not in sync. */
+ for (otherpkg = &pkg->set->pkg; otherpkg; otherpkg = otherpkg->arch_next) {
+ if (otherpkg == pkg)
+ continue;
+ if (otherpkg->status <= PKG_STAT_HALFCONFIGURED)
+ continue;
+
+ if (dpkg_version_compare(&pkg->available.version,
+ &otherpkg->installed.version))
+ enqueue_deconfigure(otherpkg, NULL);
+ }
+
+ pkg_check_depcon(pkg, pfilename);
+
+ ensure_allinstfiles_available();
+ fsys_hash_init();
+ trig_file_interests_ensure();
+
+ printf(_("Preparing to unpack %s ...\n"), pfilename);
+
+ if (pkg->status != PKG_STAT_NOTINSTALLED &&
+ pkg->status != PKG_STAT_CONFIGFILES) {
+ log_action("upgrade", pkg, &pkg->installed);
+ } else {
+ log_action("install", pkg, &pkg->available);
+ }
+
+ if (f_noact) {
+ pop_cleanup(ehflag_normaltidy);
+ return;
+ }
+
+ /*
+ * OK, we're going ahead.
+ */
+
+ trig_activate_packageprocessing(pkg);
+ strcpy(cidirrest, TRIGGERSCIFILE);
+ trig_parse_ci(cidir, NULL, trig_cicb_statuschange_activate, pkg, &pkg->available);
+
+ /* Read the conffiles, and copy the hashes across. */
+ newconffiles.head = NULL;
+ newconffiles.tail = &newconffiles.head;
+ push_cleanup(cu_fileslist, ~0, 0);
+ strcpy(cidirrest,CONFFILESFILE);
+ deb_parse_conffiles(pkg, cidir, &newconffiles);
+
+ /* All the old conffiles are marked with a flag, so that we don't delete
+ * them if they seem to disappear completely. */
+ pkg_conffiles_mark_old(pkg);
+ for (conflictor_iter = conflictors.head;
+ conflictor_iter;
+ conflictor_iter = conflictor_iter->next)
+ pkg_conffiles_mark_old(conflictor_iter->pkg);
+
+ oldversionstatus= pkg->status;
+
+ if (oldversionstatus > PKG_STAT_INSTALLED)
+ internerr("package %s state %d is out-of-bounds",
+ pkg_name(pkg, pnaw_always), oldversionstatus);
+
+ debug(dbg_general,"process_archive oldversionstatus=%s",
+ statusstrings[oldversionstatus]);
+
+ if (oldversionstatus == PKG_STAT_HALFCONFIGURED ||
+ oldversionstatus == PKG_STAT_TRIGGERSAWAITED ||
+ oldversionstatus == PKG_STAT_TRIGGERSPENDING ||
+ oldversionstatus == PKG_STAT_INSTALLED) {
+ pkg_set_eflags(pkg, PKG_EFLAG_REINSTREQ);
+ pkg_set_status(pkg, PKG_STAT_HALFCONFIGURED);
+ modstatdb_note(pkg);
+ push_cleanup(cu_prermupgrade, ~ehflag_normaltidy, 1, (void *)pkg);
+ if (dpkg_version_compare(&pkg->available.version,
+ &pkg->installed.version) >= 0)
+ /* Upgrade or reinstall. */
+ maintscript_fallback(pkg, PRERMFILE, "pre-removal", cidir, cidirrest,
+ "upgrade", "failed-upgrade");
+ else /* Downgrade => no fallback */
+ maintscript_installed(pkg, PRERMFILE, "pre-removal",
+ "upgrade",
+ versiondescribe(&pkg->available.version,
+ vdew_nonambig),
+ NULL);
+ pkg_set_status(pkg, PKG_STAT_UNPACKED);
+ oldversionstatus = PKG_STAT_UNPACKED;
+ modstatdb_note(pkg);
+ }
+
+ pkg_deconfigure_others(pkg);
+
+ for (conflictor_iter = conflictors.head;
+ conflictor_iter;
+ conflictor_iter = conflictor_iter->next) {
+ struct pkginfo *conflictor = conflictor_iter->pkg;
+
+ if (!(conflictor->status == PKG_STAT_HALFCONFIGURED ||
+ conflictor->status == PKG_STAT_TRIGGERSAWAITED ||
+ conflictor->status == PKG_STAT_TRIGGERSPENDING ||
+ conflictor->status == PKG_STAT_INSTALLED))
+ continue;
+
+ trig_activate_packageprocessing(conflictor);
+ pkg_set_status(conflictor, PKG_STAT_HALFCONFIGURED);
+ modstatdb_note(conflictor);
+ push_cleanup(cu_prerminfavour, ~ehflag_normaltidy,
+ 2, conflictor, pkg);
+ maintscript_installed(conflictor, PRERMFILE, "pre-removal",
+ "remove", "in-favour",
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig),
+ versiondescribe(&pkg->available.version,
+ vdew_nonambig),
+ NULL);
+ pkg_set_status(conflictor, PKG_STAT_HALFINSTALLED);
+ modstatdb_note(conflictor);
+ }
+
+ pkg_set_eflags(pkg, PKG_EFLAG_REINSTREQ);
+ if (pkg->status == PKG_STAT_NOTINSTALLED) {
+ pkg->installed.version= pkg->available.version;
+ pkg->installed.multiarch = pkg->available.multiarch;
+ }
+ pkg_set_status(pkg, PKG_STAT_HALFINSTALLED);
+ modstatdb_note(pkg);
+ if (oldversionstatus == PKG_STAT_NOTINSTALLED) {
+ push_cleanup(cu_preinstverynew, ~ehflag_normaltidy,
+ 3,(void*)pkg,(void*)cidir,(void*)cidirrest);
+ maintscript_new(pkg, PREINSTFILE, "pre-installation", cidir, cidirrest,
+ "install", NULL);
+ } else if (oldversionstatus == PKG_STAT_CONFIGFILES) {
+ push_cleanup(cu_preinstnew, ~ehflag_normaltidy,
+ 3,(void*)pkg,(void*)cidir,(void*)cidirrest);
+ maintscript_new(pkg, PREINSTFILE, "pre-installation", cidir, cidirrest,
+ "install",
+ versiondescribe(&pkg->installed.version, vdew_nonambig),
+ versiondescribe(&pkg->available.version, vdew_nonambig),
+ NULL);
+ } else {
+ push_cleanup(cu_preinstupgrade, ~ehflag_normaltidy,
+ 4,(void*)pkg,(void*)cidir,(void*)cidirrest,(void*)&oldversionstatus);
+ maintscript_new(pkg, PREINSTFILE, "pre-installation", cidir, cidirrest,
+ "upgrade",
+ versiondescribe(&pkg->installed.version, vdew_nonambig),
+ versiondescribe(&pkg->available.version, vdew_nonambig),
+ NULL);
+ }
+
+ if (oldversionstatus == PKG_STAT_NOTINSTALLED ||
+ oldversionstatus == PKG_STAT_CONFIGFILES) {
+ printf(_("Unpacking %s (%s) ...\n"),
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig),
+ versiondescribe(&pkg->available.version, vdew_nonambig));
+ } else {
+ printf(_("Unpacking %s (%s) over (%s) ...\n"),
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig),
+ versiondescribe(&pkg->available.version, vdew_nonambig),
+ versiondescribe(&pkg->installed.version, vdew_nonambig));
+ }
+
+ /*
+ * Now we unpack the archive, backing things up as we go.
+ * For each file, we check to see if it already exists.
+ * There are several possibilities:
+ *
+ * + We are trying to install a non-directory ...
+ * - It doesn't exist. In this case we simply extract it.
+ * - It is a plain file, device, symlink, &c. We do an ‘atomic
+ * overwrite’ using link() and rename(), but leave a backup copy.
+ * Later, when we delete the backup, we remove it from any other
+ * packages' lists.
+ * - It is a directory. In this case it depends on whether we're
+ * trying to install a symlink or something else.
+ * = If we're not trying to install a symlink we move the directory
+ * aside and extract the node. Later, when we recursively remove
+ * the backed-up directory, we remove it from any other packages'
+ * lists.
+ * = If we are trying to install a symlink we do nothing - ie,
+ * dpkg will never replace a directory tree with a symlink. This
+ * is to avoid embarrassing effects such as replacing a directory
+ * tree with a link to a link to the original directory tree.
+ * + We are trying to install a directory ...
+ * - It doesn't exist. We create it with the appropriate modes.
+ * - It exists as a directory or a symlink to one. We do nothing.
+ * - It is a plain file or a symlink (other than to a directory).
+ * We move it aside and create the directory. Later, when we
+ * delete the backup, we remove it from any other packages' lists.
+ *
+ * Install non-dir Install symlink Install dir
+ * Exists not X X X
+ * File/node/symlink LXR LXR BXR
+ * Directory BXR - -
+ *
+ * X: extract file/node/link/directory
+ * LX: atomic overwrite leaving backup
+ * B: ordinary backup
+ * R: later remove from other packages' lists
+ * -: do nothing
+ *
+ * After we've done this we go through the remaining things in the
+ * lists of packages we're trying to remove (including the old
+ * version of the current package). This happens in reverse order,
+ * so that we process files before the directories (or symlinks-to-
+ * directories) containing them.
+ *
+ * + If the thing is a conffile then we leave it alone for the purge
+ * operation.
+ * + Otherwise, there are several possibilities too:
+ * - The listed thing does not exist. We ignore it.
+ * - The listed thing is a directory or a symlink to a directory.
+ * We delete it only if it isn't listed in any other package.
+ * - The listed thing is not a directory, but was part of the package
+ * that was upgraded, we check to make sure the files aren't the
+ * same ones from the old package by checking dev/inode
+ * - The listed thing is not a directory or a symlink to one (ie,
+ * it's a plain file, device, pipe, &c, or a symlink to one, or a
+ * dangling symlink). We delete it.
+ *
+ * The removed packages' list becomes empty (of course, the new
+ * version of the package we're installing will have a new list,
+ * which replaces the old version's list).
+ *
+ * If at any stage we remove a file from a package's list, and the
+ * package isn't one we're already processing, and the package's
+ * list becomes empty as a result, we ‘vanish’ the package. This
+ * means that we run its postrm with the ‘disappear’ argument, and
+ * put the package in the ‘not-installed’ state. If it had any
+ * conffiles, their hashes and ownership will have been transferred
+ * already, so we just ignore those and forget about them from the
+ * point of view of the disappearing package.
+ *
+ * NOTE THAT THE OLD POSTRM IS RUN AFTER THE NEW PREINST, since the
+ * files get replaced ‘as we go’.
+ */
+
+ m_pipe(p1);
+ push_cleanup(cu_closepipe, ehflag_bombout, 1, (void *)&p1[0]);
+ pid = subproc_fork();
+ if (pid == 0) {
+ m_dup2(p1[1],1); close(p1[0]); close(p1[1]);
+ execlp(BACKEND, BACKEND, "--fsys-tarfile", filename, NULL);
+ ohshite(_("unable to execute %s (%s)"),
+ _("package filesystem archive extraction"), BACKEND);
+ }
+ close(p1[1]);
+ p1[1] = -1;
+
+ newfiles_queue.head = NULL;
+ newfiles_queue.tail = &newfiles_queue.head;
+ tc.newfiles_queue = &newfiles_queue;
+ push_cleanup(cu_fileslist, ~0, 0);
+ tc.pkg= pkg;
+ tc.backendpipe= p1[0];
+ tc.pkgset_getting_in_sync = pkgset_getting_in_sync(pkg);
+
+ /* Setup the tar archive. */
+ tar.err = DPKG_ERROR_OBJECT;
+ tar.ctx = &tc;
+ tar.ops = &tf;
+
+ rc = tar_extractor(&tar);
+ if (rc)
+ dpkg_error_print(&tar.err,
+ _("corrupted filesystem tarfile in package archive"));
+ if (fd_skip(p1[0], -1, &err) < 0)
+ ohshit(_("cannot zap possible trailing zeros from dpkg-deb: %s"), err.str);
+ close(p1[0]);
+ p1[0] = -1;
+ subproc_reap(pid, BACKEND " --fsys-tarfile", SUBPROC_NOPIPE);
+
+ tar_deferred_extract(newfiles_queue.head, pkg);
+
+ if (oldversionstatus == PKG_STAT_HALFINSTALLED ||
+ oldversionstatus == PKG_STAT_UNPACKED) {
+ /* Packages that were in ‘installed’ and ‘postinstfailed’ have been
+ * reduced to ‘unpacked’ by now, by the running of the prerm script. */
+ pkg_set_status(pkg, PKG_STAT_HALFINSTALLED);
+ modstatdb_note(pkg);
+ push_cleanup(cu_postrmupgrade, ~ehflag_normaltidy, 1, (void *)pkg);
+ maintscript_fallback(pkg, POSTRMFILE, "post-removal", cidir, cidirrest,
+ "upgrade", "failed-upgrade");
+ }
+
+ /* If anything goes wrong while tidying up it's a bit late to do
+ * anything about it. However, we don't install the new status
+ * info yet, so that a future dpkg installation will put everything
+ * right (we hope).
+ *
+ * If something does go wrong later the ‘conflictor’ package will be
+ * left in the ‘removal_failed’ state. Removing or installing it
+ * will be impossible if it was required because of the conflict with
+ * the package we're installing now and (presumably) the dependency
+ * by other packages. This means that the files it contains in
+ * common with this package will hang around until we successfully
+ * get this package installed, after which point we can trust the
+ * conflicting package's file list, which will have been updated to
+ * remove any files in this package. */
+ push_checkpoint(~ehflag_bombout, ehflag_normaltidy);
+
+ /* Now we delete all the files that were in the old version of
+ * the package only, except (old or new) conffiles, which we leave
+ * alone. */
+ pkg_remove_old_files(pkg, &newfiles_queue, &newconffiles);
+
+ /* OK, now we can write the updated files-in-this package list,
+ * since we've done away (hopefully) with all the old junk. */
+ write_filelist_except(pkg, &pkg->available, newfiles_queue.head, 0);
+
+ /* Trigger interests may have changed.
+ * Firstly we go through the old list of interests deleting them.
+ * Then we go through the new list adding them. */
+ strcpy(cidirrest, TRIGGERSCIFILE);
+ trig_parse_ci(pkg_infodb_get_file(pkg, &pkg->installed, TRIGGERSCIFILE),
+ trig_cicb_interest_delete, NULL, pkg, &pkg->installed);
+ trig_parse_ci(cidir, trig_cicb_interest_add, NULL, pkg, &pkg->available);
+ trig_file_interests_save();
+
+ /* We also install the new maintainer scripts, and any other
+ * cruft that may have come along with the package. First
+ * we go through the existing scripts replacing or removing
+ * them as appropriate; then we go through the new scripts
+ * (any that are left) and install them. */
+ debug(dbg_general, "process_archive updating info directory");
+ pkg_infodb_update(pkg, cidir, cidirrest);
+
+ /* We store now the checksums dynamically computed while unpacking. */
+ write_filehash_except(pkg, &pkg->available, newfiles_queue.head, 0);
+
+ /*
+ * Update the status database.
+ *
+ * This involves copying each field across from the ‘available’
+ * to the ‘installed’ half of the pkg structure.
+ * For some of the fields we have to do a complicated construction
+ * operation; for others we can just copy the value.
+ * We tackle the fields in the order they appear, so that
+ * we don't miss any out :-).
+ * At least we don't have to copy any strings that are referred
+ * to, because these are never modified and never freed.
+ */
+ pkg_update_fields(pkg, &newconffiles);
+
+ /* In case this was an architecture cross-grade, the in-core pkgset might
+ * be in an inconsistent state, with two pkginfo entries having the same
+ * architecture, so let's fix that. Note that this does not lose data,
+ * as the pkg.available member parsed from the binary should replace the
+ * to be cleared duplicate in the other instance. */
+ for (otherpkg = &pkg->set->pkg; otherpkg; otherpkg = otherpkg->arch_next) {
+ if (otherpkg == pkg)
+ continue;
+ if (otherpkg->installed.arch != pkg->installed.arch)
+ continue;
+
+ if (otherpkg->status != PKG_STAT_NOTINSTALLED)
+ internerr("other package %s instance in state %s instead of not-installed",
+ pkg_name(otherpkg, pnaw_always), pkg_status_name(otherpkg));
+
+ pkg_blank(otherpkg);
+ }
+
+ /* Check for disappearing packages:
+ * We go through all the packages on the system looking for ones
+ * whose files are entirely part of the one we've just unpacked
+ * (and which actually *have* some files!).
+ *
+ * Any that we find are removed - we run the postrm with ‘disappear’
+ * as an argument, and remove their info/... files and status info.
+ * Conffiles are ignored (the new package had better do something
+ * with them!). */
+ pkg_disappear_others(pkg);
+
+ /* Delete files from any other packages' lists.
+ * We have to do this before we claim this package is in any
+ * sane kind of state, as otherwise we might delete by mistake
+ * a file that we overwrote, when we remove the package which
+ * had the version we overwrote. To prevent this we make
+ * sure that we don't claim this package is OK until we
+ * have claimed ‘ownership’ of all its files. */
+ pkg_remove_files_from_others(pkg, newfiles_queue.head);
+
+ /* Right, the package we've unpacked is now in a reasonable state.
+ * The only thing that we have left to do with it is remove
+ * backup files, and we can leave the user to fix that if and when
+ * it happens (we leave the reinstall required flag, of course). */
+ pkg_set_status(pkg, PKG_STAT_UNPACKED);
+ modstatdb_note(pkg);
+
+ /* Now we delete all the backup files that we made when
+ * extracting the archive - except for files listed as conffiles
+ * in the new package.
+ * This time we count it as an error if something goes wrong.
+ *
+ * Note that we don't ever delete things that were in the old
+ * package as a conffile and don't appear at all in the new.
+ * They stay recorded as obsolete conffiles and will eventually
+ * (if not taken over by another package) be forgotten. */
+ pkg_remove_backup_files(pkg, newfiles_queue.head);
+
+ /* OK, we're now fully done with the main package.
+ * This is quite a nice state, so we don't unwind past here. */
+ pkg_reset_eflags(pkg);
+ modstatdb_note(pkg);
+ push_checkpoint(~ehflag_bombout, ehflag_normaltidy);
+
+ /* Only the removal of the conflictor left to do.
+ * The files list for the conflictor is still a little inconsistent in-core,
+ * as we have not yet updated the filename->packages mappings; however,
+ * the package->filenames mapping is. */
+ while (!pkg_queue_is_empty(&conflictors)) {
+ struct pkginfo *conflictor = pkg_queue_pop(&conflictors);
+
+ /* We need to have the most up-to-date info about which files are
+ * what ... */
+ ensure_allinstfiles_available();
+ removal_bulk(conflictor);
+ }
+
+ if (cipaction->arg_int == act_install)
+ enqueue_package_mark_seen(pkg);
+}
diff --git a/src/update.c b/src/update.c
new file mode 100644
index 0000000..313f09d
--- /dev/null
+++ b/src/update.c
@@ -0,0 +1,124 @@
+/*
+ * dpkg - main program for package management
+ * update.c - options which update the ‘available’ database
+ *
+ * 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 <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/options.h>
+
+#include "main.h"
+
+int
+updateavailable(const char *const *argv)
+{
+ const char *sourcefile= argv[0];
+ char *availfile;
+ int count= 0;
+
+ modstatdb_init();
+
+ switch (cipaction->arg_int) {
+ case act_avclear:
+ if (sourcefile) badusage(_("--%s takes no arguments"),cipaction->olong);
+ break;
+ case act_avreplace: case act_avmerge:
+ if (sourcefile == NULL)
+ sourcefile = "-";
+ else if (sourcefile && argv[1])
+ badusage(_("--%s takes at most one Packages-file argument"),
+ cipaction->olong);
+ break;
+ default:
+ internerr("unknown action '%d'", cipaction->arg_int);
+ }
+
+ if (!f_noact) {
+ const char *dbdir = dpkg_db_get_dir();
+
+ if (access(dbdir, W_OK)) {
+ if (errno != EACCES)
+ ohshite(_("unable to access dpkg database directory '%s' for bulk available update"),
+ dbdir);
+ else
+ ohshit(_("required write access to dpkg database directory '%s' for bulk available update"),
+ dbdir);
+ }
+ modstatdb_lock();
+ }
+
+ switch (cipaction->arg_int) {
+ case act_avreplace:
+ printf(_("Replacing available packages info, using %s.\n"),sourcefile);
+ break;
+ case act_avmerge:
+ printf(_("Updating available packages info, using %s.\n"),sourcefile);
+ break;
+ case act_avclear:
+ break;
+ default:
+ internerr("unknown action '%d'", cipaction->arg_int);
+ }
+
+ availfile = dpkg_db_get_path(AVAILFILE);
+
+ if (cipaction->arg_int == act_avmerge)
+ parsedb(availfile, pdb_parse_available, NULL);
+
+ if (cipaction->arg_int != act_avclear)
+ count += parsedb(sourcefile,
+ pdb_parse_available | pdb_ignoreolder | pdb_dash_is_stdin,
+ NULL);
+
+ if (!f_noact) {
+ writedb(availfile, wdb_dump_available);
+ modstatdb_unlock();
+ }
+
+ free(availfile);
+
+ if (cipaction->arg_int != act_avclear)
+ printf(P_("Information about %d package was updated.\n",
+ "Information about %d packages was updated.\n", count), count);
+
+ modstatdb_done();
+
+ return 0;
+}
+
+int
+forgetold(const char *const *argv)
+{
+ if (*argv)
+ badusage(_("--%s takes no arguments"), cipaction->olong);
+
+ warning(_("obsolete '--%s' option; unavailable packages are automatically cleaned up"),
+ cipaction->olong);
+
+ return 0;
+}
diff --git a/src/verify.c b/src/verify.c
new file mode 100644
index 0000000..9b6e18a
--- /dev/null
+++ b/src/verify.c
@@ -0,0 +1,177 @@
+/*
+ * dpkg - main program for package management
+ * verify.c - verify package integrity
+ *
+ * Copyright © 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/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/options.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+
+#include "main.h"
+
+
+enum verify_result {
+ VERIFY_NONE,
+ VERIFY_PASS,
+ VERIFY_FAIL,
+};
+
+struct verify_checks {
+ enum verify_result md5sum;
+};
+
+typedef void verify_output_func(struct fsys_namenode *, struct verify_checks *);
+
+static int
+verify_result_rpm(enum verify_result result, int check)
+{
+ switch (result) {
+ case VERIFY_FAIL:
+ return check;
+ case VERIFY_PASS:
+ return '.';
+ case VERIFY_NONE:
+ default:
+ return '?';
+ }
+}
+
+static void
+verify_output_rpm(struct fsys_namenode *namenode, struct verify_checks *checks)
+{
+ char result[9];
+ int attr;
+
+ memset(result, '?', sizeof(result));
+
+ result[2] = verify_result_rpm(checks->md5sum, '5');
+
+ if (namenode->flags & FNNF_OLD_CONFF)
+ attr = 'c';
+ else
+ attr = ' ';
+
+ printf("%.9s %c %s\n", result, attr, namenode->name);
+}
+
+static verify_output_func *verify_output = verify_output_rpm;
+
+bool
+verify_set_output(const char *name)
+{
+ if (strcmp(name, "rpm") == 0)
+ verify_output = verify_output_rpm;
+ else
+ return false;
+
+ return true;
+}
+
+static void
+verify_package(struct pkginfo *pkg)
+{
+ struct fsys_namenode_list *file;
+ struct varbuf filename = VARBUF_INIT;
+
+ ensure_packagefiles_available(pkg);
+ parse_filehash(pkg, &pkg->installed);
+ pkg_conffiles_mark_old(pkg);
+
+ for (file = pkg->files; file; file = file->next) {
+ struct verify_checks checks;
+ struct fsys_namenode *fnn;
+ char hash[MD5HASHLEN + 1];
+ int failures = 0;
+
+ fnn = namenodetouse(file->namenode, pkg, &pkg->installed);
+
+ if (strcmp(fnn->newhash, EMPTYHASHFLAG) == 0) {
+ if (fnn->oldhash == NULL)
+ continue;
+ else
+ fnn->newhash = fnn->oldhash;
+ }
+
+ varbuf_reset(&filename);
+ varbuf_add_str(&filename, instdir);
+ varbuf_add_str(&filename, fnn->name);
+ varbuf_end_str(&filename);
+
+ memset(&checks, 0, sizeof(checks));
+
+ md5hash(pkg, hash, filename.buf);
+ if (strcmp(hash, fnn->newhash) != 0) {
+ checks.md5sum = VERIFY_FAIL;
+ failures++;
+ }
+
+ if (failures)
+ verify_output(fnn, &checks);
+ }
+
+ varbuf_destroy(&filename);
+}
+
+int
+verify(const char *const *argv)
+{
+ struct pkginfo *pkg;
+ int rc = 0;
+
+ modstatdb_open(msdbrw_readonly);
+ ensure_diversions();
+
+ if (!*argv) {
+ struct pkg_hash_iter *iter;
+
+ iter = pkg_hash_iter_new();
+ while ((pkg = pkg_hash_iter_next_pkg(iter)))
+ verify_package(pkg);
+ pkg_hash_iter_free(iter);
+ } else {
+ const char *thisarg;
+
+ while ((thisarg = *argv++)) {
+ pkg = dpkg_options_parse_pkgname(cipaction, thisarg);
+ if (pkg->status == PKG_STAT_NOTINSTALLED) {
+ notice(_("package '%s' is not installed"),
+ pkg_name(pkg, pnaw_nonambig));
+ rc = 1;
+ continue;
+ }
+
+ verify_package(pkg);
+ }
+ }
+
+ modstatdb_shutdown();
+
+ m_output(stdout, _("<standard output>"));
+
+ return rc;
+}