summaryrefslogtreecommitdiffstats
path: root/utils
diff options
context:
space:
mode:
Diffstat (limited to 'utils')
-rw-r--r--utils/Makefile.am103
-rw-r--r--utils/Makefile.in998
-rw-r--r--utils/README.alternatives2
-rw-r--r--utils/start-stop-daemon.c2917
-rw-r--r--utils/t/update_alternatives.t719
-rw-r--r--utils/update-alternatives.c3225
-rw-r--r--utils/update-alternatives.polkit.in20
7 files changed, 7984 insertions, 0 deletions
diff --git a/utils/Makefile.am b/utils/Makefile.am
new file mode 100644
index 0000000..71873a5
--- /dev/null
+++ b/utils/Makefile.am
@@ -0,0 +1,103 @@
+## Process this file with automake to produce Makefile.in
+
+AM_CPPFLAGS = \
+ -DADMINDIR=\"$(admindir)\" \
+ -DLOCALEDIR=\"$(localedir)\" \
+ -DLOGDIR=\"$(logdir)\" \
+ -DSYSCONFDIR=\"$(sysconfdir)\" \
+ -DRUNSTATEDIR=\"$(runstatedir)\" \
+ -idirafter $(top_srcdir)/lib/compat \
+ -I$(top_builddir) \
+ -I$(top_srcdir)/lib \
+ # EOL
+
+CLEANFILES = \
+ org.dpkg.pkexec.update-alternatives.policy \
+ # EOL
+
+EXTRA_DIST = \
+ README.alternatives \
+ update-alternatives.polkit.in \
+ $(test_scripts) \
+ # EOL
+
+pkexecdir = $(datadir)/polkit-1/actions
+pkexec_DATA =
+
+bin_PROGRAMS =
+
+if BUILD_UPDATE_ALTERNATIVES
+pkexec_DATA += org.dpkg.pkexec.update-alternatives.policy
+bin_PROGRAMS += update-alternatives
+endif
+
+do_polkit_subst = $(SED) \
+ -e 's,[@]bindir[@],$(bindir),g' \
+ # EOL
+
+org.dpkg.pkexec.update-alternatives.policy: update-alternatives.polkit.in Makefile
+ @test -d `dirname $@` || $(MKDIR_P) `dirname $@`
+ $(AM_V_GEN) GETTEXTDATADIR="$(top_srcdir)/po" \
+ $(MSGFMT) --xml --template $< -d $(top_srcdir)/po -o - \
+ | $(do_polkit_subst) >$@
+
+update_alternatives_SOURCES = \
+ update-alternatives.c \
+ # EOL
+
+update_alternatives_CPPFLAGS = \
+ -DALT_TMP_EXT=\".dpkg-tmp\" \
+ -DADMINDIR_ENVVAR=\"DPKG_ADMINDIR\" \
+ -DINSTDIR_ENVVAR=\"DPKG_ROOT\" \
+ $(AM_CPPFLAGS) \
+ # EOL
+
+update_alternatives_LDADD = \
+ ../lib/compat/libcompat.la \
+ $(LIBINTL) \
+ # EOL
+
+sbin_PROGRAMS =
+
+if BUILD_START_STOP_DAEMON
+sbin_PROGRAMS += start-stop-daemon
+
+start_stop_daemon_SOURCES = \
+ start-stop-daemon.c \
+ # EOL
+
+start_stop_daemon_LDADD = \
+ ../lib/compat/libcompat.la \
+ $(SOCKET_LIBS) \
+ $(PS_LIBS) \
+ $(KVM_LIBS) \
+ # EOL
+endif
+
+install-data-local:
+if BUILD_UPDATE_ALTERNATIVES
+ $(MKDIR_P) $(DESTDIR)$(sysconfdir)/alternatives
+ $(MKDIR_P) $(DESTDIR)$(admindir)/alternatives
+ $(INSTALL_DATA) $(srcdir)/README.alternatives $(DESTDIR)$(sysconfdir)/alternatives/README
+endif
+
+uninstall-local:
+ rm -f $(DESTDIR)$(sysconfdir)/alternatives/README
+
+TEST_ENV_VARS = \
+ DPKG_DATADIR=$(top_srcdir)/data \
+ UA_ROOTDIR_ENVVAR=DPKG_ROOT \
+ UA_ADMINDIR_ENVVAR=DPKG_ADMINDIR \
+ UA_ADMINDIR_DEFAULT=$(admindir) \
+ # EOL
+
+test_tmpdir = t.tmp
+test_scripts = \
+ t/update_alternatives.t \
+ # EOL
+
+include $(top_srcdir)/build-aux/tap.am
+
+check-local: tap-check
+
+clean-local: tap-clean
diff --git a/utils/Makefile.in b/utils/Makefile.in
new file mode 100644
index 0000000..f58aeb0
--- /dev/null
+++ b/utils/Makefile.in
@@ -0,0 +1,998 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+# Variables to be defined:
+#
+# TEST_VERBOSE - set to 0 (default) or 1 to control test suite verbosity
+# TEST_PARALLEL - set to 1 (default) or N to control the parallel jobs
+# TEST_ENV_VARS - environment variables to be set for the test suite
+# TEST_COVERAGE - set to the perl module in charge of getting test coverage
+# test_tmpdir - test suite temporary directory
+# test_scripts - list of test case scripts
+# test_programs - list of test case programs
+# test_data - list of test data files
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+bin_PROGRAMS = $(am__EXEEXT_1)
+@BUILD_UPDATE_ALTERNATIVES_TRUE@am__append_1 = org.dpkg.pkexec.update-alternatives.policy
+@BUILD_UPDATE_ALTERNATIVES_TRUE@am__append_2 = update-alternatives
+sbin_PROGRAMS = $(am__EXEEXT_2)
+@BUILD_START_STOP_DAEMON_TRUE@am__append_3 = start-stop-daemon
+subdir = utils
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dpkg-arch.m4 \
+ $(top_srcdir)/m4/dpkg-build.m4 \
+ $(top_srcdir)/m4/dpkg-compiler.m4 \
+ $(top_srcdir)/m4/dpkg-coverage.m4 \
+ $(top_srcdir)/m4/dpkg-funcs.m4 \
+ $(top_srcdir)/m4/dpkg-headers.m4 $(top_srcdir)/m4/dpkg-libs.m4 \
+ $(top_srcdir)/m4/dpkg-linker.m4 $(top_srcdir)/m4/dpkg-progs.m4 \
+ $(top_srcdir)/m4/dpkg-types.m4 \
+ $(top_srcdir)/m4/dpkg-unicode.m4 $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/host-cpu-c-abi.m4 $(top_srcdir)/m4/iconv.m4 \
+ $(top_srcdir)/m4/intlmacosx.m4 $(top_srcdir)/m4/lib-ld.m4 \
+ $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+@BUILD_UPDATE_ALTERNATIVES_TRUE@am__EXEEXT_1 = \
+@BUILD_UPDATE_ALTERNATIVES_TRUE@ update-alternatives$(EXEEXT)
+am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(sbindir)" \
+ "$(DESTDIR)$(pkexecdir)"
+@BUILD_START_STOP_DAEMON_TRUE@am__EXEEXT_2 = \
+@BUILD_START_STOP_DAEMON_TRUE@ start-stop-daemon$(EXEEXT)
+PROGRAMS = $(bin_PROGRAMS) $(sbin_PROGRAMS)
+am__start_stop_daemon_SOURCES_DIST = start-stop-daemon.c
+@BUILD_START_STOP_DAEMON_TRUE@am_start_stop_daemon_OBJECTS = \
+@BUILD_START_STOP_DAEMON_TRUE@ start-stop-daemon.$(OBJEXT)
+start_stop_daemon_OBJECTS = $(am_start_stop_daemon_OBJECTS)
+am__DEPENDENCIES_1 =
+@BUILD_START_STOP_DAEMON_TRUE@start_stop_daemon_DEPENDENCIES = \
+@BUILD_START_STOP_DAEMON_TRUE@ ../lib/compat/libcompat.la \
+@BUILD_START_STOP_DAEMON_TRUE@ $(am__DEPENDENCIES_1) \
+@BUILD_START_STOP_DAEMON_TRUE@ $(am__DEPENDENCIES_1) \
+@BUILD_START_STOP_DAEMON_TRUE@ $(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_update_alternatives_OBJECTS = \
+ update_alternatives-update-alternatives.$(OBJEXT)
+update_alternatives_OBJECTS = $(am_update_alternatives_OBJECTS)
+update_alternatives_DEPENDENCIES = ../lib/compat/libcompat.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)/start-stop-daemon.Po \
+ ./$(DEPDIR)/update_alternatives-update-alternatives.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 = $(start_stop_daemon_SOURCES) $(update_alternatives_SOURCES)
+DIST_SOURCES = $(am__start_stop_daemon_SOURCES_DIST) \
+ $(update_alternatives_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+DATA = $(pkexec_DATA)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+ $(top_srcdir)/build-aux/depcomp $(top_srcdir)/build-aux/tap.am
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOM4TE = @AUTOM4TE@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BUILD_DEVEL_DOCS = @BUILD_DEVEL_DOCS@
+BZ2_LIBS = @BZ2_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CURSES_LIBS = @CURSES_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEB_DEFAULT_COMPRESSOR = @DEB_DEFAULT_COMPRESSOR@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOXYGEN = @DOXYGEN@
+DPKG_DEFAULT_PAGER = @DPKG_DEFAULT_PAGER@
+DPKG_DEFAULT_SHELL = @DPKG_DEFAULT_SHELL@
+DPKG_PAGER = @DPKG_PAGER@
+DPKG_SHELL = @DPKG_SHELL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GCOV = @GCOV@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_DOT = @HAVE_DOT@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+KVM_LIBS = @KVM_LIBS@
+LCOV = @LCOV@
+LCOV_GENHTML = @LCOV_GENHTML@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LZMA_LIBS = @LZMA_LIBS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MD_LIBS = @MD_LIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGMERGE = @MSGMERGE@
+MSGMERGE_FOR_MSGFMT_OPTION = @MSGMERGE_FOR_MSGFMT_OPTION@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_BUG_WEB = @PACKAGE_BUG_WEB@
+PACKAGE_COPYRIGHT_HOLDER = @PACKAGE_COPYRIGHT_HOLDER@
+PACKAGE_CPAN_NAME = @PACKAGE_CPAN_NAME@
+PACKAGE_DIST_IS_RELEASE = @PACKAGE_DIST_IS_RELEASE@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_RELEASE_DATE = @PACKAGE_RELEASE_DATE@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VCS_TYPE = @PACKAGE_VCS_TYPE@
+PACKAGE_VCS_URL = @PACKAGE_VCS_URL@
+PACKAGE_VCS_WEB = @PACKAGE_VCS_WEB@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATCH = @PATCH@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PERL = @PERL@
+PERL_COVER = @PERL_COVER@
+PERL_COVERAGE = @PERL_COVERAGE@
+PERL_LIBDIR = @PERL_LIBDIR@
+PERL_MIN_VERSION = @PERL_MIN_VERSION@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PO4A = @PO4A@
+POD2MAN = @POD2MAN@
+POSUB = @POSUB@
+PS_LIBS = @PS_LIBS@
+RANLIB = @RANLIB@
+RT_LIBS = @RT_LIBS@
+SED = @SED@
+SELINUX_CFLAGS = @SELINUX_CFLAGS@
+SELINUX_LIBS = @SELINUX_LIBS@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SOCKET_LIBS = @SOCKET_LIBS@
+STRIP = @STRIP@
+TAR = @TAR@
+USE_NLS = @USE_NLS@
+USE_PO4A = @USE_PO4A@
+USE_UNICODE = @USE_UNICODE@
+VERSION = @VERSION@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+ZSTD_LIBS = @ZSTD_LIBS@
+Z_LIBS = @Z_LIBS@
+Z_NG_LIBS = @Z_NG_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+admindir = @admindir@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+backupsdir = @backupsdir@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+devlibdir = @devlibdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+logdir = @logdir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgconfdir = @pkgconfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+zshcompletionsdir = @zshcompletionsdir@
+AM_CPPFLAGS = \
+ -DADMINDIR=\"$(admindir)\" \
+ -DLOCALEDIR=\"$(localedir)\" \
+ -DLOGDIR=\"$(logdir)\" \
+ -DSYSCONFDIR=\"$(sysconfdir)\" \
+ -DRUNSTATEDIR=\"$(runstatedir)\" \
+ -idirafter $(top_srcdir)/lib/compat \
+ -I$(top_builddir) \
+ -I$(top_srcdir)/lib \
+ # EOL
+
+CLEANFILES = \
+ org.dpkg.pkexec.update-alternatives.policy \
+ # EOL
+
+EXTRA_DIST = \
+ README.alternatives \
+ update-alternatives.polkit.in \
+ $(test_scripts) \
+ # EOL
+
+pkexecdir = $(datadir)/polkit-1/actions
+pkexec_DATA = $(am__append_1)
+do_polkit_subst = $(SED) \
+ -e 's,[@]bindir[@],$(bindir),g' \
+ # EOL
+
+update_alternatives_SOURCES = \
+ update-alternatives.c \
+ # EOL
+
+update_alternatives_CPPFLAGS = \
+ -DALT_TMP_EXT=\".dpkg-tmp\" \
+ -DADMINDIR_ENVVAR=\"DPKG_ADMINDIR\" \
+ -DINSTDIR_ENVVAR=\"DPKG_ROOT\" \
+ $(AM_CPPFLAGS) \
+ # EOL
+
+update_alternatives_LDADD = \
+ ../lib/compat/libcompat.la \
+ $(LIBINTL) \
+ # EOL
+
+@BUILD_START_STOP_DAEMON_TRUE@start_stop_daemon_SOURCES = \
+@BUILD_START_STOP_DAEMON_TRUE@ start-stop-daemon.c \
+@BUILD_START_STOP_DAEMON_TRUE@ # EOL
+
+@BUILD_START_STOP_DAEMON_TRUE@start_stop_daemon_LDADD = \
+@BUILD_START_STOP_DAEMON_TRUE@ ../lib/compat/libcompat.la \
+@BUILD_START_STOP_DAEMON_TRUE@ $(SOCKET_LIBS) \
+@BUILD_START_STOP_DAEMON_TRUE@ $(PS_LIBS) \
+@BUILD_START_STOP_DAEMON_TRUE@ $(KVM_LIBS) \
+@BUILD_START_STOP_DAEMON_TRUE@ # EOL
+
+TEST_ENV_VARS = \
+ DPKG_DATADIR=$(top_srcdir)/data \
+ UA_ROOTDIR_ENVVAR=DPKG_ROOT \
+ UA_ADMINDIR_ENVVAR=DPKG_ADMINDIR \
+ UA_ADMINDIR_DEFAULT=$(admindir) \
+ # EOL
+
+test_tmpdir = t.tmp
+test_scripts = \
+ t/update_alternatives.t \
+ # EOL
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(top_srcdir)/build-aux/tap.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign utils/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign utils/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+$(top_srcdir)/build-aux/tap.am $(am__empty):
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+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
+install-sbinPROGRAMS: $(sbin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(sbindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(sbindir)" || 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)$(sbindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(sbindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-sbinPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || 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)$(sbindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(sbindir)" && rm -f $$files
+
+clean-sbinPROGRAMS:
+ @list='$(sbin_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
+
+start-stop-daemon$(EXEEXT): $(start_stop_daemon_OBJECTS) $(start_stop_daemon_DEPENDENCIES) $(EXTRA_start_stop_daemon_DEPENDENCIES)
+ @rm -f start-stop-daemon$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(start_stop_daemon_OBJECTS) $(start_stop_daemon_LDADD) $(LIBS)
+
+update-alternatives$(EXEEXT): $(update_alternatives_OBJECTS) $(update_alternatives_DEPENDENCIES) $(EXTRA_update_alternatives_DEPENDENCIES)
+ @rm -f update-alternatives$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(update_alternatives_OBJECTS) $(update_alternatives_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/start-stop-daemon.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/update_alternatives-update-alternatives.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+update_alternatives-update-alternatives.o: update-alternatives.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(update_alternatives_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT update_alternatives-update-alternatives.o -MD -MP -MF $(DEPDIR)/update_alternatives-update-alternatives.Tpo -c -o update_alternatives-update-alternatives.o `test -f 'update-alternatives.c' || echo '$(srcdir)/'`update-alternatives.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/update_alternatives-update-alternatives.Tpo $(DEPDIR)/update_alternatives-update-alternatives.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='update-alternatives.c' object='update_alternatives-update-alternatives.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(update_alternatives_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o update_alternatives-update-alternatives.o `test -f 'update-alternatives.c' || echo '$(srcdir)/'`update-alternatives.c
+
+update_alternatives-update-alternatives.obj: update-alternatives.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(update_alternatives_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT update_alternatives-update-alternatives.obj -MD -MP -MF $(DEPDIR)/update_alternatives-update-alternatives.Tpo -c -o update_alternatives-update-alternatives.obj `if test -f 'update-alternatives.c'; then $(CYGPATH_W) 'update-alternatives.c'; else $(CYGPATH_W) '$(srcdir)/update-alternatives.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/update_alternatives-update-alternatives.Tpo $(DEPDIR)/update_alternatives-update-alternatives.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='update-alternatives.c' object='update_alternatives-update-alternatives.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(update_alternatives_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o update_alternatives-update-alternatives.obj `if test -f 'update-alternatives.c'; then $(CYGPATH_W) 'update-alternatives.c'; else $(CYGPATH_W) '$(srcdir)/update-alternatives.c'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkexecDATA: $(pkexec_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(pkexec_DATA)'; test -n "$(pkexecdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkexecdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkexecdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(pkexecdir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(pkexecdir)" || exit $$?; \
+ done
+
+uninstall-pkexecDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkexec_DATA)'; test -n "$(pkexecdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkexecdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-local
+check: check-am
+all-am: Makefile $(PROGRAMS) $(DATA)
+installdirs:
+ for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(pkexecdir)"; 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:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+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 \
+ clean-sbinPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/start-stop-daemon.Po
+ -rm -f ./$(DEPDIR)/update_alternatives-update-alternatives.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-pkexecDATA \
+ install-sbinPROGRAMS
+
+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)/start-stop-daemon.Po
+ -rm -f ./$(DEPDIR)/update_alternatives-update-alternatives.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 uninstall-local \
+ uninstall-pkexecDATA uninstall-sbinPROGRAMS
+
+.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 clean-sbinPROGRAMS 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-pkexecDATA install-ps \
+ install-ps-am install-sbinPROGRAMS 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 \
+ uninstall-local uninstall-pkexecDATA uninstall-sbinPROGRAMS
+
+.PRECIOUS: Makefile
+
+
+org.dpkg.pkexec.update-alternatives.policy: update-alternatives.polkit.in Makefile
+ @test -d `dirname $@` || $(MKDIR_P) `dirname $@`
+ $(AM_V_GEN) GETTEXTDATADIR="$(top_srcdir)/po" \
+ $(MSGFMT) --xml --template $< -d $(top_srcdir)/po -o - \
+ | $(do_polkit_subst) >$@
+
+install-data-local:
+@BUILD_UPDATE_ALTERNATIVES_TRUE@ $(MKDIR_P) $(DESTDIR)$(sysconfdir)/alternatives
+@BUILD_UPDATE_ALTERNATIVES_TRUE@ $(MKDIR_P) $(DESTDIR)$(admindir)/alternatives
+@BUILD_UPDATE_ALTERNATIVES_TRUE@ $(INSTALL_DATA) $(srcdir)/README.alternatives $(DESTDIR)$(sysconfdir)/alternatives/README
+
+uninstall-local:
+ rm -f $(DESTDIR)$(sysconfdir)/alternatives/README
+
+TEST_VERBOSE ?= 0
+TEST_PARALLEL ?= 1
+
+tap-clean:
+ [ -z "$(test_tmpdir)" ] || rm -fr $(test_tmpdir)
+
+tap-check: $(test_data) $(test_programs) $(test_scripts)
+ [ -z "$(test_tmpdir)" ] || $(MKDIR_P) $(test_tmpdir)
+ $(TEST_ENV_VARS) \
+ abs_top_srcdir=$(abs_top_srcdir) \
+ abs_top_builddir=$(abs_top_builddir) \
+ srcdir=$(srcdir) builddir=$(builddir) \
+ CC=$(CC) \
+ SHELL=$(SHELL) \
+ PERL=$(PERL) \
+ PERL_DL_NONLAZY=1 \
+ PERL5OPT=$(TEST_COVERAGE) \
+ $(PERL) $(top_srcdir)/build-aux/test-runner \
+ $(addprefix $(builddir)/,$(test_programs)) \
+ $(addprefix $(srcdir)/,$(test_scripts))
+
+authorcheck:
+ AUTHOR_TESTING=1 $(MAKE) check
+
+check-local: tap-check
+
+clean-local: tap-clean
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/utils/README.alternatives b/utils/README.alternatives
new file mode 100644
index 0000000..4c4d215
--- /dev/null
+++ b/utils/README.alternatives
@@ -0,0 +1,2 @@
+Please read the update-alternatives(1) man page for information on this
+directory and its contents.
diff --git a/utils/start-stop-daemon.c b/utils/start-stop-daemon.c
new file mode 100644
index 0000000..203bbb6
--- /dev/null
+++ b/utils/start-stop-daemon.c
@@ -0,0 +1,2917 @@
+/*
+ * A rewrite of the original Debian's start-stop-daemon Perl script
+ * in C (faster - it is executed many times during system startup).
+ *
+ * Written by Marek Michalkiewicz <marekm@i17linuxb.ists.pwr.wroc.pl>,
+ * public domain. Based conceptually on start-stop-daemon.pl, by Ian
+ * Jackson <ijackson@gnu.ai.mit.edu>. May be used and distributed
+ * freely for any purpose. Changes by Christian Schwarz
+ * <schwarz@monet.m.isar.de>, to make output conform to the Debian
+ * Console Message Standard, also placed in public domain. Minor
+ * changes by Klee Dienes <klee@debian.org>, also placed in the Public
+ * Domain.
+ *
+ * Changes by Ben Collins <bcollins@debian.org>, added --chuid, --background
+ * and --make-pidfile options, placed in public domain as well.
+ *
+ * Port to OpenBSD by Sontri Tomo Huynh <huynh.29@osu.edu>
+ * and Andreas Schuldei <andreas@schuldei.org>
+ *
+ * Changes by Ian Jackson: added --retry (and associated rearrangements).
+ */
+
+#include <config.h>
+/* On at least Solaris <= 11.3 procfs is not compatible with LFS. */
+#if !DPKG_STRUCTURED_PROCFS_SUPPORTS_LFS
+#undef _FILE_OFFSET_BITS
+#endif
+#include <compat.h>
+
+#include <dpkg/macros.h>
+
+#if defined(__linux__)
+# define OS_Linux
+#elif defined(__GNU__)
+# define OS_Hurd
+#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+# define OS_FreeBSD
+#elif defined(__NetBSD__)
+# define OS_NetBSD
+#elif defined(__OpenBSD__)
+# define OS_OpenBSD
+#elif defined(__DragonFly__)
+# define OS_DragonFlyBSD
+#elif defined(__APPLE__) && defined(__MACH__)
+# define OS_Darwin
+#elif defined(__sun)
+# define OS_Solaris
+#elif defined(_AIX)
+# define OS_AIX
+#elif defined(__hpux)
+# define OS_HPUX
+#else
+# error Unknown architecture - cannot build start-stop-daemon
+#endif
+
+#if defined(OS_NetBSD)
+/* NetBSD needs this to expose struct proc. */
+#define _KMEMUSER 1
+#elif defined(OS_Solaris)
+/* Solaris needs this to expose the new structured procfs API. */
+#define _STRUCTURED_PROC 1
+#endif
+
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#ifdef HAVE_SYS_SYSCALL_H
+#include <sys/syscall.h>
+#endif
+#ifdef HAVE_SYS_SYSCTL_H
+#include <sys/sysctl.h>
+#endif
+#ifdef HAVE_SYS_PROCFS_H
+#include <sys/procfs.h>
+#endif
+#ifdef HAVE_SYS_PROC_H
+#include <sys/proc.h>
+#endif
+#ifdef HAVE_SYS_USER_H
+#include <sys/user.h>
+#endif
+#ifdef HAVE_SYS_PSTAT_H
+#include <sys/pstat.h>
+#endif
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/select.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <time.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+#include <signal.h>
+#include <termios.h>
+#include <unistd.h>
+#ifdef HAVE_STDDEF_H
+#include <stddef.h>
+#endif
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+#ifdef HAVE_ERROR_H
+#include <error.h>
+#endif
+#ifdef HAVE_ERR_H
+#include <err.h>
+#endif
+
+#if defined(OS_Hurd)
+#include <hurd.h>
+#include <ps.h>
+#endif
+
+#if defined(OS_Darwin)
+#include <libproc.h>
+#endif
+
+#ifdef HAVE_KVM_H
+#include <kvm.h>
+#if defined(OS_FreeBSD)
+#define KVM_MEMFILE "/dev/null"
+#else
+#define KVM_MEMFILE NULL
+#endif
+#endif
+
+#if defined(_POSIX_PRIORITY_SCHEDULING) && _POSIX_PRIORITY_SCHEDULING > 0
+#include <sched.h>
+#else
+#define SCHED_OTHER -1
+#define SCHED_FIFO -1
+#define SCHED_RR -1
+#endif
+
+/* At least macOS and AIX do not define this. */
+#ifndef SOCK_NONBLOCK
+#define SOCK_NONBLOCK 0
+#endif
+
+#if defined(OS_Linux)
+/* This comes from TASK_COMM_LEN defined in Linux' include/linux/sched.h. */
+#define PROCESS_NAME_SIZE 15
+#elif defined(OS_Solaris)
+#define PROCESS_NAME_SIZE 15
+#elif defined(OS_Darwin)
+#define PROCESS_NAME_SIZE 16
+#elif defined(OS_AIX)
+/* This comes from PRFNSZ defined in AIX's <sys/procfs.h>. */
+#define PROCESS_NAME_SIZE 16
+#elif defined(OS_NetBSD)
+#define PROCESS_NAME_SIZE 16
+#elif defined(OS_OpenBSD)
+#define PROCESS_NAME_SIZE 16
+#elif defined(OS_FreeBSD)
+#define PROCESS_NAME_SIZE 19
+#elif defined(OS_DragonFlyBSD)
+/* On DragonFlyBSD MAXCOMLEN expands to 16. */
+#define PROCESS_NAME_SIZE MAXCOMLEN
+#endif
+
+#if defined(SYS_ioprio_set) && defined(linux)
+#define HAVE_IOPRIO_SET
+#endif
+
+#define IOPRIO_CLASS_SHIFT 13
+#define IOPRIO_PRIO_VALUE(class, prio) (((class) << IOPRIO_CLASS_SHIFT) | (prio))
+#define IO_SCHED_PRIO_MIN 0
+#define IO_SCHED_PRIO_MAX 7
+
+enum {
+ IOPRIO_WHO_PROCESS = 1,
+ IOPRIO_WHO_PGRP,
+ IOPRIO_WHO_USER,
+};
+
+enum {
+ IOPRIO_CLASS_NONE,
+ IOPRIO_CLASS_RT,
+ IOPRIO_CLASS_BE,
+ IOPRIO_CLASS_IDLE,
+};
+
+enum action_code {
+ ACTION_NONE,
+ ACTION_START,
+ ACTION_STOP,
+ ACTION_STATUS,
+};
+
+enum LIBCOMPAT_ATTR_ENUM_FLAGS match_code {
+ MATCH_NONE = 0,
+ MATCH_PID = 1 << 0,
+ MATCH_PPID = 1 << 1,
+ MATCH_PIDFILE = 1 << 2,
+ MATCH_EXEC = 1 << 3,
+ MATCH_NAME = 1 << 4,
+ MATCH_USER = 1 << 5,
+};
+
+/* Time conversion constants. */
+enum {
+ NANOSEC_IN_SEC = 1000000000L,
+ NANOSEC_IN_MILLISEC = 1000000L,
+ NANOSEC_IN_MICROSEC = 1000L,
+};
+
+/* The minimum polling interval, 20ms. */
+static const long MIN_POLL_INTERVAL = 20L * NANOSEC_IN_MILLISEC;
+
+static enum action_code action;
+static enum match_code match_mode;
+static bool testmode = false;
+static int quietmode = 0;
+static int exitnodo = 1;
+static bool background = false;
+static bool close_io = true;
+static const char *output_io;
+static bool notify_await = false;
+static int notify_timeout = 60;
+static char *notify_sockdir;
+static char *notify_socket;
+static bool mpidfile = false;
+static bool rpidfile = false;
+static int signal_nr = SIGTERM;
+static int user_id = -1;
+static int runas_uid = -1;
+static int runas_gid = -1;
+static const char *userspec = NULL;
+static char *changeuser = NULL;
+static const char *changegroup = NULL;
+static char *changeroot = NULL;
+static const char *changedir = "/";
+static const char *cmdname = NULL;
+static char *execname = NULL;
+static char *startas = NULL;
+static pid_t match_pid = -1;
+static pid_t match_ppid = -1;
+static const char *pidfile = NULL;
+static char *what_stop = NULL;
+static const char *progname = "";
+static int nicelevel = 0;
+static int umask_value = -1;
+
+static struct stat exec_stat;
+#if defined(OS_Hurd)
+static struct proc_stat_list *procset = NULL;
+#endif
+
+/* LSB Init Script process status exit codes. */
+enum status_code {
+ STATUS_OK = 0,
+ STATUS_DEAD_PIDFILE = 1,
+ STATUS_DEAD_LOCKFILE = 2,
+ STATUS_DEAD = 3,
+ STATUS_UNKNOWN = 4,
+};
+
+struct pid_list {
+ struct pid_list *next;
+ pid_t pid;
+};
+
+static struct pid_list *found = NULL;
+static struct pid_list *killed = NULL;
+
+/* Resource scheduling policy. */
+struct res_schedule {
+ const char *policy_name;
+ int policy;
+ int priority;
+};
+
+struct schedule_item {
+ enum {
+ sched_timeout,
+ sched_signal,
+ sched_goto,
+ /* Only seen within parse_schedule and callees. */
+ sched_forever,
+ } type;
+ /* Seconds, signal no., or index into array. */
+ int value;
+};
+
+static struct res_schedule *proc_sched = NULL;
+static struct res_schedule *io_sched = NULL;
+
+static int schedule_length;
+static struct schedule_item *schedule = NULL;
+
+
+static void LIBCOMPAT_ATTR_PRINTF(1)
+debug(const char *format, ...)
+{
+ va_list arglist;
+
+ if (quietmode >= 0)
+ return;
+
+ va_start(arglist, format);
+ vprintf(format, arglist);
+ va_end(arglist);
+}
+
+static void LIBCOMPAT_ATTR_PRINTF(1)
+info(const char *format, ...)
+{
+ va_list arglist;
+
+ if (quietmode > 0)
+ return;
+
+ va_start(arglist, format);
+ vprintf(format, arglist);
+ va_end(arglist);
+}
+
+static void LIBCOMPAT_ATTR_PRINTF(1)
+warning(const char *format, ...)
+{
+ va_list arglist;
+
+ fprintf(stderr, "%s: warning: ", progname);
+ va_start(arglist, format);
+ vfprintf(stderr, format, arglist);
+ va_end(arglist);
+}
+
+static void LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_VPRINTF(2)
+fatalv(int errno_fatal, const char *format, va_list args)
+{
+ va_list args_copy;
+
+ fprintf(stderr, "%s: ", progname);
+ va_copy(args_copy, args);
+ vfprintf(stderr, format, args_copy);
+ va_end(args_copy);
+ if (errno_fatal)
+ fprintf(stderr, " (%s)\n", strerror(errno_fatal));
+ else
+ fprintf(stderr, "\n");
+
+ if (action == ACTION_STATUS)
+ exit(STATUS_UNKNOWN);
+ else
+ exit(2);
+}
+
+static void LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_PRINTF(1)
+fatal(const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ fatalv(0, format, args);
+ /* cppcheck-suppress[va_end_missing]:
+ * False positive, fatalv() is non-returning. */
+}
+
+static void LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_PRINTF(1)
+fatale(const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ fatalv(errno, format, args);
+ /* cppcheck-suppress[va_end_missing]:
+ * False positive, fatalv() is non-returning. */
+}
+
+#define BUG(...) bug(__FILE__, __LINE__, __func__, __VA_ARGS__)
+
+static void LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_PRINTF(4)
+bug(const char *file, int line, const char *func, const char *format, ...)
+{
+ va_list arglist;
+
+ fprintf(stderr, "%s:%s:%d:%s: internal error: ",
+ progname, file, line, func);
+ va_start(arglist, format);
+ vfprintf(stderr, format, arglist);
+ va_end(arglist);
+
+ if (action == ACTION_STATUS)
+ exit(STATUS_UNKNOWN);
+ else
+ exit(3);
+}
+
+static void *
+xmalloc(int size)
+{
+ void *ptr;
+
+ ptr = malloc(size);
+ if (ptr)
+ return ptr;
+ fatale("malloc(%d) failed", size);
+}
+
+static char *
+xstrndup(const char *str, size_t n)
+{
+ char *new_str;
+
+ new_str = strndup(str, n);
+ if (new_str)
+ return new_str;
+ fatale("strndup(%s, %zu) failed", str, n);
+}
+
+static void
+timespec_gettime(struct timespec *ts)
+{
+#if defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0 && \
+ defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK > 0
+ if (clock_gettime(CLOCK_MONOTONIC, ts) < 0)
+ fatale("clock_gettime failed");
+#else
+ struct timeval tv;
+
+ if (gettimeofday(&tv, NULL) != 0)
+ fatale("gettimeofday failed");
+
+ ts->tv_sec = tv.tv_sec;
+ ts->tv_nsec = tv.tv_usec * NANOSEC_IN_MICROSEC;
+#endif
+}
+
+#define timespec_cmp(a, b, OP) \
+ (((a)->tv_sec == (b)->tv_sec) ? \
+ ((a)->tv_nsec OP (b)->tv_nsec) : \
+ ((a)->tv_sec OP (b)->tv_sec))
+
+static void
+timespec_sub(struct timespec *a, struct timespec *b, struct timespec *res)
+{
+ res->tv_sec = a->tv_sec - b->tv_sec;
+ res->tv_nsec = a->tv_nsec - b->tv_nsec;
+ if (res->tv_nsec < 0) {
+ res->tv_sec--;
+ res->tv_nsec += NANOSEC_IN_SEC;
+ }
+}
+
+static void
+timespec_mul(struct timespec *a, int b)
+{
+ long nsec = a->tv_nsec * b;
+
+ a->tv_sec *= b;
+ a->tv_sec += nsec / NANOSEC_IN_SEC;
+ a->tv_nsec = nsec % NANOSEC_IN_SEC;
+}
+
+static char *
+newpath(const char *dirname, const char *filename)
+{
+ char *path;
+ size_t path_len;
+
+ path_len = strlen(dirname) + 1 + strlen(filename) + 1;
+ path = xmalloc(path_len);
+ snprintf(path, path_len, "%s/%s", dirname, filename);
+
+ return path;
+}
+
+static int
+parse_unsigned(const char *string, int base, int *value_r)
+{
+ long value;
+ char *endptr;
+
+ errno = 0;
+ if (!string[0])
+ return -1;
+
+ value = strtol(string, &endptr, base);
+ if (string == endptr || *endptr != '\0' || errno != 0)
+ return -1;
+ if (value < 0 || value > INT_MAX)
+ return -1;
+
+ *value_r = value;
+ return 0;
+}
+
+static long
+get_open_fd_max(void)
+{
+#ifdef HAVE_GETDTABLESIZE
+ return getdtablesize();
+#else
+ return sysconf(_SC_OPEN_MAX);
+#endif
+}
+
+#ifndef HAVE_SETSID
+static void
+detach_controlling_tty(void)
+{
+#ifdef HAVE_TIOCNOTTY
+ int tty_fd;
+
+ tty_fd = open("/dev/tty", O_RDWR);
+
+ /* The current process does not have a controlling tty. */
+ if (tty_fd < 0)
+ return;
+
+ if (ioctl(tty_fd, TIOCNOTTY, 0) != 0)
+ fatale("unable to detach controlling tty");
+
+ close(tty_fd);
+#endif
+}
+
+static pid_t
+setsid(void)
+{
+ if (setpgid(0, 0) < 0)
+ return -1:
+
+ detach_controlling_tty();
+
+ return 0;
+}
+#endif
+
+static void
+wait_for_child(pid_t pid)
+{
+ pid_t child;
+ int status;
+
+ do {
+ child = waitpid(pid, &status, 0);
+ } while (child == -1 && errno == EINTR);
+
+ if (child != pid)
+ fatal("error waiting for child");
+
+ if (WIFEXITED(status)) {
+ int ret = WEXITSTATUS(status);
+
+ if (ret != 0)
+ fatal("child returned error exit status %d", ret);
+ } else if (WIFSIGNALED(status)) {
+ int signo = WTERMSIG(status);
+
+ fatal("child was killed by signal %d", signo);
+ } else {
+ fatal("unexpected status %d waiting for child", status);
+ }
+}
+
+static void
+cleanup_socket_dir(void)
+{
+ (void)unlink(notify_socket);
+ (void)rmdir(notify_sockdir);
+}
+
+static char *
+setup_socket_name(const char *suffix)
+{
+ const char *basedir;
+
+ if (getuid() == 0 && access(RUNSTATEDIR, F_OK) == 0) {
+ basedir = RUNSTATEDIR;
+ } else {
+ basedir = getenv("TMPDIR");
+ if (basedir == NULL)
+ basedir = P_tmpdir;
+ }
+
+ if (asprintf(&notify_sockdir, "%s/%s.XXXXXX", basedir, suffix) < 0)
+ fatale("cannot allocate socket directory name");
+
+ if (mkdtemp(notify_sockdir) == NULL)
+ fatale("cannot create socket directory %s", notify_sockdir);
+
+ atexit(cleanup_socket_dir);
+
+ if (chown(notify_sockdir, runas_uid, runas_gid))
+ fatale("cannot change socket directory ownership");
+
+ if (asprintf(&notify_socket, "%s/notify", notify_sockdir) < 0)
+ fatale("cannot allocate socket name");
+
+ setenv("NOTIFY_SOCKET", notify_socket, 1);
+
+ return notify_socket;
+}
+
+static void
+set_socket_passcred(int fd)
+{
+#ifdef SO_PASSCRED
+ static const int enable = 1;
+
+ (void)setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &enable, sizeof(enable));
+#endif
+}
+
+static int
+create_notify_socket(void)
+{
+ const char *sockname;
+ struct sockaddr_un su;
+ int fd, rc, flags;
+
+ /* Create notification socket. */
+ fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0);
+ if (fd < 0)
+ fatale("cannot create notification socket");
+
+ /* We could set SOCK_CLOEXEC instead, but then we would need to
+ * check whether the socket call failed, try and then do this anyway,
+ * when we have no threading problems to worry about. */
+ flags = fcntl(fd, F_GETFD);
+ if (flags < 0)
+ fatale("cannot read fd flags for notification socket");
+ if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0)
+ fatale("cannot set close-on-exec flag for notification socket");
+
+ sockname = setup_socket_name(".s-s-d-notify");
+
+ /* Bind to a socket in a temporary directory, selected based on
+ * the platform. */
+ memset(&su, 0, sizeof(su));
+ su.sun_family = AF_UNIX;
+ strncpy(su.sun_path, sockname, sizeof(su.sun_path) - 1);
+
+ rc = bind(fd, (struct sockaddr *)&su, sizeof(su));
+ if (rc < 0)
+ fatale("cannot bind to notification socket");
+
+ rc = chmod(su.sun_path, 0660);
+ if (rc < 0)
+ fatale("cannot change notification socket permissions");
+
+ rc = chown(su.sun_path, runas_uid, runas_gid);
+ if (rc < 0)
+ fatale("cannot change notification socket ownership");
+
+ /* XXX: Verify we are talking to an expected child? Although it is not
+ * clear whether this is feasible given the knowledge we have got. */
+ set_socket_passcred(fd);
+
+ return fd;
+}
+
+static void
+wait_for_notify(int fd)
+{
+ struct timespec startat, now, elapsed, timeout, timeout_orig;
+ fd_set fdrs;
+
+ timeout.tv_sec = notify_timeout;
+ timeout.tv_nsec = 0;
+ timeout_orig = timeout;
+
+ timespec_gettime(&startat);
+
+ while (timeout.tv_sec >= 0 && timeout.tv_nsec >= 0) {
+ int rc;
+
+ FD_ZERO(&fdrs);
+ FD_SET(fd, &fdrs);
+
+ /* Wait for input. */
+ debug("Waiting for notifications... (timeout %lusec %lunsec)\n",
+ timeout.tv_sec, timeout.tv_nsec);
+ rc = pselect(fd + 1, &fdrs, NULL, NULL, &timeout, NULL);
+
+ /* Catch non-restartable errors, that is, not signals nor
+ * kernel out of resources. */
+ if (rc < 0 && (errno != EINTR && errno != EAGAIN))
+ fatale("cannot monitor notification socket for activity");
+
+ /* Timed-out. */
+ if (rc == 0)
+ fatal("timed out waiting for a notification");
+
+ /* Update the timeout, as should not rely on pselect() having
+ * done that for us, which is an unportable assumption. */
+ timespec_gettime(&now);
+ timespec_sub(&now, &startat, &elapsed);
+ timespec_sub(&timeout_orig, &elapsed, &timeout);
+
+ /* Restartable error, a signal or kernel out of resources. */
+ if (rc < 0)
+ continue;
+
+ /* Parse it and check for a supported notification message,
+ * once we get a READY=1, we exit. */
+ for (;;) {
+ ssize_t nrecv;
+ char buf[4096];
+ char *line, *line_next;
+
+ nrecv = recv(fd, buf, sizeof(buf), 0);
+ if (nrecv < 0 && (errno != EINTR && errno != EAGAIN))
+ fatale("cannot receive notification packet");
+ if (nrecv < 0)
+ break;
+
+ buf[nrecv] = '\0';
+
+ for (line = buf; *line; line = line_next) {
+ line_next = strchrnul(line, '\n');
+ if (*line_next == '\n')
+ *line_next++ = '\0';
+
+ debug("Child sent some notification...\n");
+ if (strncmp(line, "EXTEND_TIMEOUT_USEC=", 20) == 0) {
+ int extend_usec = 0;
+
+ if (parse_unsigned(line + 20, 10, &extend_usec) != 0)
+ fatale("cannot parse extended timeout notification %s", line);
+
+ /* Reset the current timeout. */
+ timeout.tv_sec = extend_usec / 1000L;
+ timeout.tv_nsec = (extend_usec % 1000L) *
+ NANOSEC_IN_MILLISEC;
+ timeout_orig = timeout;
+
+ timespec_gettime(&startat);
+ } else if (strncmp(line, "ERRNO=", 6) == 0) {
+ int suberrno = 0;
+
+ if (parse_unsigned(line + 6, 10, &suberrno) != 0)
+ fatale("cannot parse errno notification %s", line);
+ errno = suberrno;
+ fatale("program failed to initialize");
+ } else if (strcmp(line, "READY=1") == 0) {
+ debug("-> Notification => ready for service.\n");
+ return;
+ } else {
+ debug("-> Notification line '%s' received\n", line);
+ }
+ }
+ }
+ }
+}
+
+static void
+write_pidfile(const char *filename, pid_t pid)
+{
+ FILE *fp;
+ int fd;
+
+ fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC | O_NOFOLLOW, 0666);
+ if (fd < 0)
+ fp = NULL;
+ else
+ fp = fdopen(fd, "w");
+
+ if (fp == NULL)
+ fatale("unable to open pidfile '%s' for writing", filename);
+
+ fprintf(fp, "%d\n", pid);
+
+ if (fclose(fp))
+ fatale("unable to close pidfile '%s'", filename);
+}
+
+static void
+remove_pidfile(const char *filename)
+{
+ if (unlink(filename) < 0 && errno != ENOENT)
+ fatale("cannot remove pidfile '%s'", filename);
+}
+
+static void
+daemonize(void)
+{
+ int notify_fd = -1;
+ pid_t pid;
+ sigset_t mask;
+ sigset_t oldmask;
+
+ debug("Detaching to start %s...\n", startas);
+
+ /* Block SIGCHLD to allow waiting for the child process while it is
+ * performing actions, such as creating a pidfile. */
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGCHLD);
+ if (sigprocmask(SIG_BLOCK, &mask, &oldmask) == -1)
+ fatale("cannot block SIGCHLD");
+
+ if (notify_await)
+ notify_fd = create_notify_socket();
+
+ pid = fork();
+ if (pid < 0)
+ fatale("unable to do first fork");
+ else if (pid) { /* First Parent. */
+ /* Wait for the second parent to exit, so that if we need to
+ * perform any actions there, like creating a pidfile, we do
+ * not suffer from race conditions on return. */
+ wait_for_child(pid);
+
+ if (notify_await) {
+ /* Wait for a readiness notification from the second
+ * child, so that we can safely exit when the service
+ * is up. */
+ wait_for_notify(notify_fd);
+ close(notify_fd);
+ cleanup_socket_dir();
+ }
+
+ _exit(0);
+ }
+
+ /* Close the notification socket, even though it is close-on-exec. */
+ if (notify_await)
+ close(notify_fd);
+
+ /* Create a new session. */
+ if (setsid() < 0)
+ fatale("cannot set session ID");
+
+ pid = fork();
+ if (pid < 0)
+ fatale("unable to do second fork");
+ else if (pid) { /* Second parent. */
+ /* Set a default umask for dumb programs, which might get
+ * overridden by the --umask option later on, so that we get
+ * a defined umask when creating the pidfile. */
+ umask(022);
+
+ if (mpidfile && pidfile != NULL)
+ /* User wants _us_ to make the pidfile. */
+ write_pidfile(pidfile, pid);
+
+ _exit(0);
+ }
+
+ if (sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1)
+ fatale("cannot restore signal mask");
+
+ debug("Detaching complete...\n");
+}
+
+static void
+pid_list_push(struct pid_list **list, pid_t pid)
+{
+ struct pid_list *p;
+
+ p = xmalloc(sizeof(*p));
+ p->next = *list;
+ p->pid = pid;
+ *list = p;
+}
+
+static void
+pid_list_free(struct pid_list **list)
+{
+ struct pid_list *here, *next;
+
+ for (here = *list; here != NULL; here = next) {
+ next = here->next;
+ free(here);
+ }
+
+ *list = NULL;
+}
+
+static void
+usage(void)
+{
+ printf(
+"Usage: start-stop-daemon [<option>...] <command>\n"
+"\n");
+
+ printf(
+"Commands:\n"
+" -S, --start -- <argument>... start a program and pass <arguments> to it\n"
+" -K, --stop stop a program\n"
+" -T, --status get the program status\n"
+" -H, --help print help information\n"
+" -V, --version print version\n"
+"\n");
+
+ printf(
+"Matching options (at least one is required):\n"
+" --pid <pid> pid to check\n"
+" --ppid <ppid> parent pid to check\n"
+" -p, --pidfile <pid-file> pid file to check\n"
+" -x, --exec <executable> program to start/check if it is running\n"
+" -n, --name <process-name> process name to check\n"
+" -u, --user <username|uid> process owner to check\n"
+"\n");
+
+ printf(
+"Options:\n"
+" -g, --group <group|gid> run process as this group\n"
+" -c, --chuid <name|uid[:group|gid]>\n"
+" change to this user/group before starting\n"
+" process\n"
+" -s, --signal <signal> signal to send (default TERM)\n"
+" -a, --startas <pathname> program to start (default is <executable>)\n"
+" -r, --chroot <directory> chroot to <directory> before starting\n"
+" -d, --chdir <directory> change to <directory> (default is /)\n"
+" -N, --nicelevel <incr> add incr to the process' nice level\n"
+" -P, --procsched <policy[:prio]>\n"
+" use <policy> with <prio> for the kernel\n"
+" process scheduler (default prio is 0)\n"
+" -I, --iosched <class[:prio]> use <class> with <prio> to set the IO\n"
+" scheduler (default prio is 4)\n"
+" -k, --umask <mask> change the umask to <mask> before starting\n"
+" -b, --background force the process to detach\n"
+" --notify-await wait for a readiness notification\n"
+" --notify-timeout <int> timeout after <int> seconds of notify wait\n"
+" -C, --no-close do not close any file descriptor\n"
+" -O, --output <filename> send stdout and stderr to <filename>\n"
+" -m, --make-pidfile create the pidfile before starting\n"
+" --remove-pidfile delete the pidfile after stopping\n"
+" -R, --retry <schedule> check whether processes die, and retry\n"
+" -t, --test test mode, don't do anything\n"
+" -o, --oknodo exit status 0 (not 1) if nothing done\n"
+" -q, --quiet be more quiet\n"
+" -v, --verbose be more verbose\n"
+"\n");
+
+ printf(
+"Retry <schedule> is <item>|/<item>/... where <item> is one of\n"
+" -<signal-num>|[-]<signal-name> send that signal\n"
+" <timeout> wait that many seconds\n"
+" forever repeat remainder forever\n"
+"or <schedule> may be just <timeout>, meaning <signal>/<timeout>/KILL/<timeout>\n"
+"\n");
+
+ printf(
+"The process scheduler <policy> can be one of:\n"
+" other, fifo or rr\n"
+"\n");
+
+ printf(
+"The IO scheduler <class> can be one of:\n"
+" real-time, best-effort or idle\n"
+"\n");
+
+ printf(
+"Exit status:\n"
+" 0 = done\n"
+" 1 = nothing done (=> 0 if --oknodo)\n"
+" 2 = with --retry, processes would not die\n"
+" 3 = trouble\n"
+"Exit status with --status:\n"
+" 0 = program is running\n"
+" 1 = program is not running and the pid file exists\n"
+" 3 = program is not running\n"
+" 4 = unable to determine status\n");
+}
+
+static void
+do_version(void)
+{
+ printf("start-stop-daemon %s for Debian\n\n", VERSION);
+
+ printf("Written by Marek Michalkiewicz, public domain.\n");
+}
+
+static void LIBCOMPAT_ATTR_NORET
+badusage(const char *msg)
+{
+ if (msg)
+ fprintf(stderr, "%s: %s\n", progname, msg);
+ fprintf(stderr, "Try '%s --help' for more information.\n", progname);
+
+ if (action == ACTION_STATUS)
+ exit(STATUS_UNKNOWN);
+ else
+ exit(3);
+}
+
+struct sigpair {
+ const char *name;
+ int signal;
+};
+
+static const struct sigpair siglist[] = {
+ { "ABRT", SIGABRT },
+ { "ALRM", SIGALRM },
+ { "FPE", SIGFPE },
+ { "HUP", SIGHUP },
+ { "ILL", SIGILL },
+ { "INT", SIGINT },
+ { "KILL", SIGKILL },
+ { "PIPE", SIGPIPE },
+ { "QUIT", SIGQUIT },
+ { "SEGV", SIGSEGV },
+ { "TERM", SIGTERM },
+ { "USR1", SIGUSR1 },
+ { "USR2", SIGUSR2 },
+ { "CHLD", SIGCHLD },
+ { "CONT", SIGCONT },
+ { "STOP", SIGSTOP },
+ { "TSTP", SIGTSTP },
+ { "TTIN", SIGTTIN },
+ { "TTOU", SIGTTOU }
+};
+
+static int
+parse_pid(const char *pid_str, int *pid_num)
+{
+ if (parse_unsigned(pid_str, 10, pid_num) != 0)
+ return -1;
+ if (*pid_num == 0)
+ return -1;
+
+ return 0;
+}
+
+static int
+parse_signal(const char *sig_str, int *sig_num)
+{
+ unsigned int i;
+
+ if (parse_unsigned(sig_str, 10, sig_num) == 0)
+ return 0;
+
+ for (i = 0; i < array_count(siglist); i++) {
+ if (strcmp(sig_str, siglist[i].name) == 0) {
+ *sig_num = siglist[i].signal;
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static int
+parse_umask(const char *string, int *value_r)
+{
+ return parse_unsigned(string, 0, value_r);
+}
+
+static void
+validate_proc_schedule(void)
+{
+#if defined(_POSIX_PRIORITY_SCHEDULING) && _POSIX_PRIORITY_SCHEDULING > 0
+ int prio_min, prio_max;
+
+ prio_min = sched_get_priority_min(proc_sched->policy);
+ prio_max = sched_get_priority_max(proc_sched->policy);
+
+ if (proc_sched->priority < prio_min)
+ badusage("process scheduler priority less than min");
+ if (proc_sched->priority > prio_max)
+ badusage("process scheduler priority greater than max");
+#endif
+}
+
+static void
+parse_proc_schedule(const char *string)
+{
+ char *policy_str;
+ size_t policy_len;
+ int prio = 0;
+
+ policy_len = strcspn(string, ":");
+ policy_str = xstrndup(string, policy_len);
+
+ if (string[policy_len] == ':' &&
+ parse_unsigned(string + policy_len + 1, 10, &prio) != 0)
+ fatale("invalid process scheduler priority");
+
+ proc_sched = xmalloc(sizeof(*proc_sched));
+ proc_sched->policy_name = policy_str;
+
+ if (strcmp(policy_str, "other") == 0) {
+ proc_sched->policy = SCHED_OTHER;
+ proc_sched->priority = 0;
+ } else if (strcmp(policy_str, "fifo") == 0) {
+ proc_sched->policy = SCHED_FIFO;
+ proc_sched->priority = prio;
+ } else if (strcmp(policy_str, "rr") == 0) {
+ proc_sched->policy = SCHED_RR;
+ proc_sched->priority = prio;
+ } else
+ badusage("invalid process scheduler policy");
+
+ validate_proc_schedule();
+}
+
+static void
+parse_io_schedule(const char *string)
+{
+ char *class_str;
+ size_t class_len;
+ int prio = 4;
+
+ class_len = strcspn(string, ":");
+ class_str = xstrndup(string, class_len);
+
+ if (string[class_len] == ':' &&
+ parse_unsigned(string + class_len + 1, 10, &prio) != 0)
+ fatale("invalid IO scheduler priority");
+
+ io_sched = xmalloc(sizeof(*io_sched));
+ io_sched->policy_name = class_str;
+
+ if (strcmp(class_str, "real-time") == 0) {
+ io_sched->policy = IOPRIO_CLASS_RT;
+ io_sched->priority = prio;
+ } else if (strcmp(class_str, "best-effort") == 0) {
+ io_sched->policy = IOPRIO_CLASS_BE;
+ io_sched->priority = prio;
+ } else if (strcmp(class_str, "idle") == 0) {
+ io_sched->policy = IOPRIO_CLASS_IDLE;
+ io_sched->priority = 7;
+ } else
+ badusage("invalid IO scheduler policy");
+
+ if (io_sched->priority < IO_SCHED_PRIO_MIN)
+ badusage("IO scheduler priority less than min");
+ if (io_sched->priority > IO_SCHED_PRIO_MAX)
+ badusage("IO scheduler priority greater than max");
+}
+
+static void
+set_proc_schedule(struct res_schedule *sched)
+{
+#if defined(_POSIX_PRIORITY_SCHEDULING) && _POSIX_PRIORITY_SCHEDULING > 0
+ struct sched_param param;
+
+ param.sched_priority = sched->priority;
+
+ if (sched_setscheduler(getpid(), sched->policy, &param) == -1)
+ fatale("unable to set process scheduler");
+#endif
+}
+
+#ifdef HAVE_IOPRIO_SET
+static inline int
+ioprio_set(int which, int who, int ioprio)
+{
+ return syscall(SYS_ioprio_set, which, who, ioprio);
+}
+#endif
+
+static void
+set_io_schedule(struct res_schedule *sched)
+{
+#ifdef HAVE_IOPRIO_SET
+ int io_sched_mask;
+
+ io_sched_mask = IOPRIO_PRIO_VALUE(sched->policy, sched->priority);
+ if (ioprio_set(IOPRIO_WHO_PROCESS, getpid(), io_sched_mask) == -1)
+ warning("unable to alter IO priority to mask %i (%s)\n",
+ io_sched_mask, strerror(errno));
+#endif
+}
+
+static void
+parse_schedule_item(const char *string, struct schedule_item *item)
+{
+ const char *after_hyph;
+
+ if (strcmp(string, "forever") == 0) {
+ item->type = sched_forever;
+ } else if (isdigit(string[0])) {
+ item->type = sched_timeout;
+ if (parse_unsigned(string, 10, &item->value) != 0)
+ badusage("invalid timeout value in schedule");
+ } else if ((after_hyph = string + (string[0] == '-')) &&
+ parse_signal(after_hyph, &item->value) == 0) {
+ item->type = sched_signal;
+ } else {
+ badusage("invalid schedule item (must be [-]<signal-name>, "
+ "-<signal-number>, <timeout> or 'forever'");
+ }
+}
+
+static void
+parse_schedule(const char *schedule_str)
+{
+ const char *slash;
+ int count;
+
+ count = 0;
+ for (slash = schedule_str; *slash; slash++)
+ if (*slash == '/')
+ count++;
+
+ schedule_length = (count == 0) ? 4 : count + 1;
+ schedule = xmalloc(sizeof(*schedule) * schedule_length);
+
+ if (count == 0) {
+ schedule[0].type = sched_signal;
+ schedule[0].value = signal_nr;
+ parse_schedule_item(schedule_str, &schedule[1]);
+ if (schedule[1].type != sched_timeout) {
+ badusage("--retry takes timeout, or schedule list"
+ " of at least two items");
+ }
+ schedule[2].type = sched_signal;
+ schedule[2].value = SIGKILL;
+ schedule[3] = schedule[1];
+ } else {
+ int repeatat;
+
+ count = 0;
+ repeatat = -1;
+ while (*schedule_str) {
+ char item_buf[20];
+ size_t str_len;
+
+ slash = strchrnul(schedule_str, '/');
+ str_len = (size_t)(slash - schedule_str);
+ if (str_len >= sizeof(item_buf))
+ badusage("invalid schedule item: far too long"
+ " (you must delimit items with slashes)");
+ memcpy(item_buf, schedule_str, str_len);
+ item_buf[str_len] = '\0';
+ schedule_str = *slash ? slash + 1 : slash;
+
+ parse_schedule_item(item_buf, &schedule[count]);
+ if (schedule[count].type == sched_forever) {
+ if (repeatat >= 0)
+ badusage("invalid schedule: 'forever'"
+ " appears more than once");
+ repeatat = count;
+ continue;
+ }
+ count++;
+ }
+ if (repeatat == count)
+ badusage("invalid schedule: 'forever' appears last, "
+ "nothing to repeat");
+ if (repeatat >= 0) {
+ schedule[count].type = sched_goto;
+ schedule[count].value = repeatat;
+ count++;
+ }
+ if (count != schedule_length)
+ BUG("count=%d != schedule_length=%d",
+ count, schedule_length);
+ }
+}
+
+static void
+set_action(enum action_code new_action)
+{
+ if (action == new_action)
+ return;
+
+ if (action != ACTION_NONE)
+ badusage("only one command can be specified");
+
+ action = new_action;
+}
+
+#define OPT_PID 500
+#define OPT_PPID 501
+#define OPT_RM_PIDFILE 502
+#define OPT_NOTIFY_AWAIT 503
+#define OPT_NOTIFY_TIMEOUT 504
+
+static void
+parse_options(int argc, char * const *argv)
+{
+ static struct option longopts[] = {
+ { "help", 0, NULL, 'H'},
+ { "stop", 0, NULL, 'K'},
+ { "start", 0, NULL, 'S'},
+ { "status", 0, NULL, 'T'},
+ { "version", 0, NULL, 'V'},
+ { "startas", 1, NULL, 'a'},
+ { "name", 1, NULL, 'n'},
+ { "oknodo", 0, NULL, 'o'},
+ { "pid", 1, NULL, OPT_PID},
+ { "ppid", 1, NULL, OPT_PPID},
+ { "pidfile", 1, NULL, 'p'},
+ { "quiet", 0, NULL, 'q'},
+ { "signal", 1, NULL, 's'},
+ { "test", 0, NULL, 't'},
+ { "user", 1, NULL, 'u'},
+ { "group", 1, NULL, 'g'},
+ { "chroot", 1, NULL, 'r'},
+ { "verbose", 0, NULL, 'v'},
+ { "exec", 1, NULL, 'x'},
+ { "chuid", 1, NULL, 'c'},
+ { "nicelevel", 1, NULL, 'N'},
+ { "procsched", 1, NULL, 'P'},
+ { "iosched", 1, NULL, 'I'},
+ { "umask", 1, NULL, 'k'},
+ { "background", 0, NULL, 'b'},
+ { "notify-await", 0, NULL, OPT_NOTIFY_AWAIT},
+ { "notify-timeout", 1, NULL, OPT_NOTIFY_TIMEOUT},
+ { "no-close", 0, NULL, 'C'},
+ { "output", 1, NULL, 'O'},
+ { "make-pidfile", 0, NULL, 'm'},
+ { "remove-pidfile", 0, NULL, OPT_RM_PIDFILE},
+ { "retry", 1, NULL, 'R'},
+ { "chdir", 1, NULL, 'd'},
+ { NULL, 0, NULL, 0 }
+ };
+ const char *pid_str = NULL;
+ const char *ppid_str = NULL;
+ const char *umask_str = NULL;
+ const char *signal_str = NULL;
+ const char *schedule_str = NULL;
+ const char *proc_schedule_str = NULL;
+ const char *io_schedule_str = NULL;
+ const char *notify_timeout_str = NULL;
+ size_t changeuser_len;
+ int c;
+
+ for (;;) {
+ c = getopt_long(argc, argv,
+ "HKSVTa:n:op:qr:s:tu:vx:c:N:P:I:k:bCO:mR:g:d:",
+ longopts, NULL);
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'H': /* --help */
+ usage();
+ exit(0);
+ case 'K': /* --stop */
+ set_action(ACTION_STOP);
+ break;
+ case 'S': /* --start */
+ set_action(ACTION_START);
+ break;
+ case 'T': /* --status */
+ set_action(ACTION_STATUS);
+ break;
+ case 'V': /* --version */
+ do_version();
+ exit(0);
+ case 'a': /* --startas <pathname> */
+ startas = optarg;
+ break;
+ case 'n': /* --name <process-name> */
+ match_mode |= MATCH_NAME;
+ cmdname = optarg;
+ break;
+ case 'o': /* --oknodo */
+ exitnodo = 0;
+ break;
+ case OPT_PID: /* --pid <pid> */
+ match_mode |= MATCH_PID;
+ pid_str = optarg;
+ break;
+ case OPT_PPID: /* --ppid <ppid> */
+ match_mode |= MATCH_PPID;
+ ppid_str = optarg;
+ break;
+ case 'p': /* --pidfile <pid-file> */
+ match_mode |= MATCH_PIDFILE;
+ pidfile = optarg;
+ break;
+ case 'q': /* --quiet */
+ quietmode = true;
+ break;
+ case 's': /* --signal <signal> */
+ signal_str = optarg;
+ break;
+ case 't': /* --test */
+ testmode = true;
+ break;
+ case 'u': /* --user <username>|<uid> */
+ match_mode |= MATCH_USER;
+ userspec = optarg;
+ break;
+ case 'v': /* --verbose */
+ quietmode = -1;
+ break;
+ case 'x': /* --exec <executable> */
+ match_mode |= MATCH_EXEC;
+ execname = optarg;
+ break;
+ case 'c': /* --chuid <username>|<uid> */
+ free(changeuser);
+ /* We copy the string just in case we need the
+ * argument later. */
+ changeuser_len = strcspn(optarg, ":");
+ changeuser = xstrndup(optarg, changeuser_len);
+ if (optarg[changeuser_len] == ':') {
+ if (optarg[changeuser_len + 1] == '\0')
+ fatal("missing group name");
+ changegroup = optarg + changeuser_len + 1;
+ }
+ break;
+ case 'g': /* --group <group>|<gid> */
+ changegroup = optarg;
+ break;
+ case 'r': /* --chroot /new/root */
+ changeroot = optarg;
+ break;
+ case 'N': /* --nice */
+ nicelevel = atoi(optarg);
+ break;
+ case 'P': /* --procsched */
+ proc_schedule_str = optarg;
+ break;
+ case 'I': /* --iosched */
+ io_schedule_str = optarg;
+ break;
+ case 'k': /* --umask <mask> */
+ umask_str = optarg;
+ break;
+ case 'b': /* --background */
+ background = true;
+ break;
+ case OPT_NOTIFY_AWAIT:
+ notify_await = true;
+ break;
+ case OPT_NOTIFY_TIMEOUT:
+ notify_timeout_str = optarg;
+ break;
+ case 'C': /* --no-close */
+ close_io = false;
+ break;
+ case 'O': /* --outout <filename> */
+ output_io = optarg;
+ break;
+ case 'm': /* --make-pidfile */
+ mpidfile = true;
+ break;
+ case OPT_RM_PIDFILE: /* --remove-pidfile */
+ rpidfile = true;
+ break;
+ case 'R': /* --retry <schedule>|<timeout> */
+ schedule_str = optarg;
+ break;
+ case 'd': /* --chdir /new/dir */
+ changedir = optarg;
+ break;
+ default:
+ /* Message printed by getopt. */
+ badusage(NULL);
+ }
+ }
+
+ if (pid_str != NULL) {
+ if (parse_pid(pid_str, &match_pid) != 0)
+ badusage("pid value must be a number greater than 0");
+ }
+
+ if (ppid_str != NULL) {
+ if (parse_pid(ppid_str, &match_ppid) != 0)
+ badusage("ppid value must be a number greater than 0");
+ }
+
+ if (signal_str != NULL) {
+ if (parse_signal(signal_str, &signal_nr) != 0)
+ badusage("signal value must be numeric or name"
+ " of signal (KILL, INT, ...)");
+ }
+
+ if (schedule_str != NULL) {
+ parse_schedule(schedule_str);
+ }
+
+ if (proc_schedule_str != NULL)
+ parse_proc_schedule(proc_schedule_str);
+
+ if (io_schedule_str != NULL)
+ parse_io_schedule(io_schedule_str);
+
+ if (umask_str != NULL) {
+ if (parse_umask(umask_str, &umask_value) != 0)
+ badusage("umask value must be a positive number");
+ }
+
+ if (output_io != NULL && output_io[0] != '/')
+ badusage("--output file needs to be an absolute filename");
+
+ if (notify_timeout_str != NULL)
+ if (parse_unsigned(notify_timeout_str, 10, &notify_timeout) != 0)
+ badusage("invalid notify timeout value");
+
+ if (action == ACTION_NONE)
+ badusage("need one of --start or --stop or --status");
+
+ if (match_mode == MATCH_NONE ||
+ (!execname && !cmdname && !userspec &&
+ !pid_str && !ppid_str && !pidfile))
+ badusage("need at least one of --exec, --pid, --ppid, --pidfile, --user or --name");
+
+#ifdef PROCESS_NAME_SIZE
+ if (cmdname && strlen(cmdname) > PROCESS_NAME_SIZE)
+ warning("this system is not able to track process names\n"
+ "longer than %d characters, please use --exec "
+ "instead of --name.\n", PROCESS_NAME_SIZE);
+#endif
+
+ if (!startas)
+ startas = execname;
+
+ if (action == ACTION_START && !startas)
+ badusage("--start needs --exec or --startas");
+
+ if (mpidfile && pidfile == NULL)
+ badusage("--make-pidfile requires --pidfile");
+ if (rpidfile && pidfile == NULL)
+ badusage("--remove-pidfile requires --pidfile");
+
+ if (pid_str && pidfile)
+ badusage("need either --pid or --pidfile, not both");
+
+ if (background && action != ACTION_START)
+ badusage("--background is only relevant with --start");
+
+ if (!close_io && !background)
+ badusage("--no-close is only relevant with --background");
+ if (output_io && !background)
+ badusage("--output is only relevant with --background");
+
+ if (close_io && output_io == NULL)
+ output_io = "/dev/null";
+}
+
+static void
+setup_options(void)
+{
+ if (execname) {
+ char *fullexecname;
+
+ /* If it's a relative path, normalize it. */
+ if (execname[0] != '/')
+ execname = newpath(changedir, execname);
+
+ if (changeroot)
+ fullexecname = newpath(changeroot, execname);
+ else
+ fullexecname = execname;
+
+ if (stat(fullexecname, &exec_stat))
+ fatale("unable to stat %s", fullexecname);
+
+ if (fullexecname != execname)
+ free(fullexecname);
+ }
+
+ if (userspec && parse_unsigned(userspec, 10, &user_id) < 0) {
+ struct passwd *pw;
+
+ pw = getpwnam(userspec);
+ if (!pw)
+ fatale("user '%s' not found", userspec);
+
+ user_id = pw->pw_uid;
+ }
+
+ if (changegroup && parse_unsigned(changegroup, 10, &runas_gid) < 0) {
+ struct group *gr;
+
+ gr = getgrnam(changegroup);
+ if (!gr)
+ fatale("group '%s' not found", changegroup);
+ changegroup = gr->gr_name;
+ runas_gid = gr->gr_gid;
+ }
+ if (changeuser) {
+ struct passwd *pw;
+ struct stat st;
+
+ if (parse_unsigned(changeuser, 10, &runas_uid) == 0)
+ pw = getpwuid(runas_uid);
+ else
+ pw = getpwnam(changeuser);
+ if (!pw)
+ fatale("user '%s' not found", changeuser);
+ changeuser = pw->pw_name;
+ runas_uid = pw->pw_uid;
+ if (changegroup == NULL) {
+ /* Pass the default group of this user. */
+ changegroup = ""; /* Just empty. */
+ runas_gid = pw->pw_gid;
+ }
+ if (stat(pw->pw_dir, &st) == 0)
+ setenv("HOME", pw->pw_dir, 1);
+ }
+}
+
+#if defined(OS_Linux)
+static const char *
+proc_status_field(pid_t pid, const char *field)
+{
+ static char *line = NULL;
+ static size_t line_size = 0;
+
+ FILE *fp;
+ char filename[32];
+ char *value = NULL;
+ ssize_t line_len;
+ size_t field_len = strlen(field);
+
+ sprintf(filename, "/proc/%d/status", pid);
+ fp = fopen(filename, "r");
+ if (!fp)
+ return NULL;
+ while ((line_len = getline(&line, &line_size, fp)) >= 0) {
+ if (strncasecmp(line, field, field_len) == 0) {
+ line[line_len - 1] = '\0';
+
+ value = line + field_len;
+ while (isspace(*value))
+ value++;
+
+ break;
+ }
+ }
+ fclose(fp);
+
+ return value;
+}
+#elif (defined(OS_Solaris) || defined(OS_AIX)) && HAVE_STRUCT_PSINFO
+static bool
+proc_get_psinfo(pid_t pid, struct psinfo *psinfo)
+{
+ char filename[64];
+ FILE *fp;
+
+ sprintf(filename, "/proc/%d/psinfo", pid);
+ fp = fopen(filename, "r");
+ if (!fp)
+ return false;
+ if (fread(psinfo, sizeof(*psinfo), 1, fp) == 0) {
+ fclose(fp);
+ return false;
+ }
+ if (ferror(fp)) {
+ fclose(fp);
+ return false;
+ }
+
+ fclose(fp);
+
+ return true;
+}
+#elif defined(OS_Hurd)
+static void
+init_procset(void)
+{
+ struct ps_context *context;
+ error_t err;
+
+ err = ps_context_create(getproc(), &context);
+ if (err)
+ error(1, err, "ps_context_create");
+
+ err = proc_stat_list_create(context, &procset);
+ if (err)
+ error(1, err, "proc_stat_list_create");
+
+ err = proc_stat_list_add_all(procset, 0, 0);
+ if (err)
+ error(1, err, "proc_stat_list_add_all");
+}
+
+static struct proc_stat *
+get_proc_stat(pid_t pid, ps_flags_t flags)
+{
+ struct proc_stat *ps;
+ ps_flags_t wanted_flags = PSTAT_PID | flags;
+
+ if (!procset)
+ init_procset();
+
+ ps = proc_stat_list_pid_proc_stat(procset, pid);
+ if (!ps)
+ return NULL;
+ if (proc_stat_set_flags(ps, wanted_flags))
+ return NULL;
+ if ((proc_stat_flags(ps) & wanted_flags) != wanted_flags)
+ return NULL;
+
+ return ps;
+}
+#elif defined(HAVE_KVM_H)
+static kvm_t *
+ssd_kvm_open(void)
+{
+ kvm_t *kd;
+ char errbuf[_POSIX2_LINE_MAX];
+
+ kd = kvm_openfiles(NULL, KVM_MEMFILE, NULL, O_RDONLY, errbuf);
+ if (kd == NULL)
+ errx(1, "%s", errbuf);
+
+ return kd;
+}
+
+static struct kinfo_proc *
+ssd_kvm_get_procs(kvm_t *kd, int op, int arg, int *count)
+{
+ struct kinfo_proc *kp;
+ int lcount;
+
+ if (count == NULL)
+ count = &lcount;
+ *count = 0;
+
+#if defined(OS_OpenBSD)
+ kp = kvm_getprocs(kd, op, arg, sizeof(*kp), count);
+#else
+ kp = kvm_getprocs(kd, op, arg, count);
+#endif
+ if (kp == NULL && errno != ESRCH)
+ errx(1, "%s", kvm_geterr(kd));
+
+ return kp;
+}
+#endif
+
+#if defined(OS_Linux)
+static bool
+pid_is_exec(pid_t pid, const struct stat *esb)
+{
+ char lname[32];
+ char lcontents[_POSIX_PATH_MAX + 1];
+ char *filename;
+ const char deleted[] = " (deleted)";
+ int nread;
+ struct stat sb;
+
+ sprintf(lname, "/proc/%d/exe", pid);
+ nread = readlink(lname, lcontents, sizeof(lcontents) - 1);
+ if (nread == -1)
+ return false;
+
+ filename = lcontents;
+ filename[nread] = '\0';
+
+ /* OpenVZ kernels contain a bogus patch that instead of appending,
+ * prepends the deleted marker. Workaround those. Otherwise handle
+ * the normal appended marker. */
+ if (strncmp(filename, deleted, strlen(deleted)) == 0)
+ filename += strlen(deleted);
+ else if (strcmp(filename + nread - strlen(deleted), deleted) == 0)
+ filename[nread - strlen(deleted)] = '\0';
+
+ if (stat(filename, &sb) != 0)
+ return false;
+
+ return (sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino);
+}
+#elif (defined(OS_Solaris) || defined(OS_AIX)) && HAVE_STRUCT_PSINFO
+static bool
+pid_is_exec(pid_t pid, const struct stat *esb)
+{
+ struct stat sb;
+ char filename[64];
+
+ sprintf(filename, "/proc/%d/object/a.out", pid);
+
+ if (stat(filename, &sb) != 0)
+ return false;
+
+ return sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino;
+}
+#elif defined(OS_Hurd)
+static bool
+pid_is_exec(pid_t pid, const struct stat *esb)
+{
+ struct proc_stat *ps;
+ struct stat sb;
+ const char *filename;
+
+ ps = get_proc_stat(pid, PSTAT_ARGS);
+ if (ps == NULL)
+ return false;
+
+ /* On old Hurd systems we have to use the argv[0] value, because
+ * there is nothing better. */
+ filename = proc_stat_args(ps);
+#ifdef PSTAT_EXE
+ /* On new Hurd systems we can use the correct value, as long
+ * as it's not NULL nor empty, as it was the case on the first
+ * implementation. */
+ if (proc_stat_set_flags(ps, PSTAT_EXE) == 0 &&
+ proc_stat_flags(ps) & PSTAT_EXE &&
+ proc_stat_exe(ps) != NULL &&
+ proc_stat_exe(ps)[0] != '\0')
+ filename = proc_stat_exe(ps);
+#endif
+
+ if (stat(filename, &sb) != 0)
+ return false;
+
+ return (sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino);
+}
+#elif defined(OS_Darwin)
+static bool
+pid_is_exec(pid_t pid, const struct stat *esb)
+{
+ struct stat sb;
+ char pathname[_POSIX_PATH_MAX];
+
+ if (proc_pidpath(pid, pathname, sizeof(pathname)) < 0)
+ return false;
+
+ if (stat(pathname, &sb) != 0)
+ return false;
+
+ return (sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino);
+}
+#elif defined(OS_HPUX)
+static bool
+pid_is_exec(pid_t pid, const struct stat *esb)
+{
+ struct pst_status pst;
+
+ if (pstat_getproc(&pst, sizeof(pst), (size_t)0, (int)pid) < 0)
+ return false;
+ return ((dev_t)pst.pst_text.psf_fsid.psfs_id == esb->st_dev &&
+ (ino_t)pst.pst_text.psf_fileid == esb->st_ino);
+}
+#elif defined(OS_FreeBSD)
+static bool
+pid_is_exec(pid_t pid, const struct stat *esb)
+{
+ struct stat sb;
+ int error, mib[4];
+ size_t len;
+ char pathname[PATH_MAX];
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_PATHNAME;
+ mib[3] = pid;
+ len = sizeof(pathname);
+
+ error = sysctl(mib, 4, pathname, &len, NULL, 0);
+ if (error != 0 && errno != ESRCH)
+ return false;
+ if (len == 0)
+ pathname[0] = '\0';
+
+ if (stat(pathname, &sb) != 0)
+ return false;
+
+ return (sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino);
+}
+#elif defined(HAVE_KVM_H)
+static bool
+pid_is_exec(pid_t pid, const struct stat *esb)
+{
+ kvm_t *kd;
+ int argv_len = 0;
+ struct kinfo_proc *kp;
+ struct stat sb;
+ char buf[_POSIX2_LINE_MAX];
+ char **pid_argv_p;
+ char *start_argv_0_p, *end_argv_0_p;
+ bool res = false;
+
+ kd = ssd_kvm_open();
+ kp = ssd_kvm_get_procs(kd, KERN_PROC_PID, pid, NULL);
+ if (kp == NULL)
+ goto cleanup;
+
+ pid_argv_p = kvm_getargv(kd, kp, argv_len);
+ if (pid_argv_p == NULL)
+ errx(1, "%s", kvm_geterr(kd));
+
+ /* Find and compare string. */
+ start_argv_0_p = *pid_argv_p;
+
+ /* Find end of argv[0] then copy and cut of str there. */
+ end_argv_0_p = strchr(*pid_argv_p, ' ');
+ if (end_argv_0_p == NULL)
+ /* There seems to be no space, so we have the command
+ * already in its desired form. */
+ start_argv_0_p = *pid_argv_p;
+ else {
+ /* Tests indicate that this never happens, since
+ * kvm_getargv itself cuts of tailing stuff. This is
+ * not what the manual page says, however. */
+ strncpy(buf, *pid_argv_p, (end_argv_0_p - start_argv_0_p));
+ buf[(end_argv_0_p - start_argv_0_p) + 1] = '\0';
+ start_argv_0_p = buf;
+ }
+
+ if (stat(start_argv_0_p, &sb) != 0)
+ goto cleanup;
+
+ res = (sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino);
+
+cleanup:
+ kvm_close(kd);
+
+ return res;
+}
+#endif
+
+#if defined(OS_Linux)
+static bool
+pid_is_child(pid_t pid, pid_t ppid)
+{
+ const char *ppid_str;
+ pid_t proc_ppid;
+ int rc;
+
+ ppid_str = proc_status_field(pid, "PPid:");
+ if (ppid_str == NULL)
+ return false;
+
+ rc = parse_pid(ppid_str, &proc_ppid);
+ if (rc < 0)
+ return false;
+
+ return proc_ppid == ppid;
+}
+#elif defined(OS_Hurd)
+static bool
+pid_is_child(pid_t pid, pid_t ppid)
+{
+ struct proc_stat *ps;
+ struct procinfo *pi;
+
+ ps = get_proc_stat(pid, PSTAT_PROC_INFO);
+ if (ps == NULL)
+ return false;
+
+ pi = proc_stat_proc_info(ps);
+
+ return pi->ppid == ppid;
+}
+#elif defined(OS_Darwin)
+static bool
+pid_is_child(pid_t pid, pid_t ppid)
+{
+ struct proc_bsdinfo pbi;
+
+ if (proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &pbi, sizeof(pbi)) < 0)
+ return false;
+
+ return (pid_t)pbi.pbi_ppid == ppid;
+}
+#elif (defined(OS_Solaris) || defined(OS_AIX)) && HAVE_STRUCT_PSINFO
+static bool
+pid_is_child(pid_t pid, pid_t ppid)
+{
+ struct psinfo psi;
+
+ if (!proc_get_psinfo(pid, &psi))
+ return false;
+
+ return (pid_t)psi.pr_ppid == ppid;
+}
+#elif defined(OS_HPUX)
+static bool
+pid_is_child(pid_t pid, pid_t ppid)
+{
+ struct pst_status pst;
+
+ if (pstat_getproc(&pst, sizeof(pst), (size_t)0, (int)pid) < 0)
+ return false;
+
+ return pst.pst_ppid == ppid;
+}
+#elif defined(OS_FreeBSD)
+static bool
+pid_is_child(pid_t pid, pid_t ppid)
+{
+ struct kinfo_proc kp;
+ int rc, mib[4];
+ size_t len;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_PID;
+ mib[3] = pid;
+ len = sizeof(kp);
+
+ rc = sysctl(mib, 4, &kp, &len, NULL, 0);
+ if (rc != 0 && errno != ESRCH)
+ return false;
+ if (len == 0 || len != sizeof(kp))
+ return false;
+
+ return kp.ki_ppid == ppid;
+}
+#elif defined(HAVE_KVM_H)
+static bool
+pid_is_child(pid_t pid, pid_t ppid)
+{
+ kvm_t *kd;
+ struct kinfo_proc *kp;
+ pid_t proc_ppid;
+ bool res = false;
+
+ kd = ssd_kvm_open();
+ kp = ssd_kvm_get_procs(kd, KERN_PROC_PID, pid, NULL);
+ if (kp == NULL)
+ goto cleanup;
+
+#if defined(OS_FreeBSD)
+ proc_ppid = kp->ki_ppid;
+#elif defined(OS_OpenBSD)
+ proc_ppid = kp->p_ppid;
+#elif defined(OS_DragonFlyBSD)
+ proc_ppid = kp->kp_ppid;
+#else
+ proc_ppid = kp->kp_proc.p_ppid;
+#endif
+
+ res = (proc_ppid == ppid);
+
+cleanup:
+ kvm_close(kd);
+
+ return res;
+}
+#endif
+
+#if defined(OS_Linux)
+static bool
+pid_is_user(pid_t pid, uid_t uid)
+{
+ struct stat sb;
+ char buf[32];
+
+ sprintf(buf, "/proc/%d", pid);
+ if (stat(buf, &sb) != 0)
+ return false;
+ return (sb.st_uid == uid);
+}
+#elif defined(OS_Hurd)
+static bool
+pid_is_user(pid_t pid, uid_t uid)
+{
+ struct proc_stat *ps;
+
+ ps = get_proc_stat(pid, PSTAT_OWNER_UID);
+ return ps && (uid_t)proc_stat_owner_uid(ps) == uid;
+}
+#elif defined(OS_Darwin)
+static bool
+pid_is_user(pid_t pid, uid_t uid)
+{
+ struct proc_bsdinfo pbi;
+
+ if (proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &pbi, sizeof(pbi)) < 0)
+ return false;
+
+ return pbi.pbi_ruid == uid;
+}
+#elif (defined(OS_Solaris) || defined(OS_AIX)) && HAVE_STRUCT_PSINFO
+static bool
+pid_is_user(pid_t pid, uid_t uid)
+{
+ struct psinfo psi;
+
+ if (!proc_get_psinfo(pid, &psi))
+ return false;
+
+ return psi.pr_uid == uid;
+}
+#elif defined(OS_HPUX)
+static bool
+pid_is_user(pid_t pid, uid_t uid)
+{
+ struct pst_status pst;
+
+ if (pstat_getproc(&pst, sizeof(pst), (size_t)0, (int)pid) < 0)
+ return false;
+ return ((uid_t)pst.pst_uid == uid);
+}
+#elif defined(OS_FreeBSD)
+static bool
+pid_is_user(pid_t pid, uid_t uid)
+{
+ struct kinfo_proc kp;
+ int rc, mib[4];
+ size_t len;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_PID;
+ mib[3] = pid;
+ len = sizeof(kp);
+
+ rc = sysctl(mib, 4, &kp, &len, NULL, 0);
+ if (rc != 0 && errno != ESRCH)
+ return false;
+ if (len == 0 || len != sizeof(kp))
+ return false;
+
+ return kp.ki_ruid == uid;
+}
+#elif defined(HAVE_KVM_H)
+static bool
+pid_is_user(pid_t pid, uid_t uid)
+{
+ kvm_t *kd;
+ uid_t proc_uid;
+ struct kinfo_proc *kp;
+ bool res = false;
+
+ kd = ssd_kvm_open();
+ kp = ssd_kvm_get_procs(kd, KERN_PROC_PID, pid, NULL);
+ if (kp == NULL)
+ goto cleanup;
+
+#if defined(OS_FreeBSD)
+ proc_uid = kp->ki_ruid;
+#elif defined(OS_OpenBSD)
+ proc_uid = kp->p_ruid;
+#elif defined(OS_DragonFlyBSD)
+ proc_uid = kp->kp_ruid;
+#elif defined(OS_NetBSD)
+ proc_uid = kp->kp_eproc.e_pcred.p_ruid;
+#else
+ if (kp->kp_proc.p_cred)
+ kvm_read(kd, (u_long)&(kp->kp_proc.p_cred->p_ruid),
+ &proc_uid, sizeof(uid_t));
+ else
+ goto cleanup;
+#endif
+
+ res = (proc_uid == (uid_t)uid);
+
+cleanup:
+ kvm_close(kd);
+
+ return res;
+}
+#endif
+
+#if defined(OS_Linux)
+static bool
+pid_is_cmd(pid_t pid, const char *name)
+{
+ const char *comm;
+
+ comm = proc_status_field(pid, "Name:");
+ if (comm == NULL)
+ return false;
+
+ return strcmp(comm, name) == 0;
+}
+#elif defined(OS_Hurd)
+static bool
+pid_is_cmd(pid_t pid, const char *name)
+{
+ struct proc_stat *ps;
+ size_t argv0_len;
+ const char *argv0;
+ const char *binary_name;
+
+ ps = get_proc_stat(pid, PSTAT_ARGS);
+ if (ps == NULL)
+ return false;
+
+ argv0 = proc_stat_args(ps);
+ argv0_len = strlen(argv0) + 1;
+
+ binary_name = basename(argv0);
+ if (strcmp(binary_name, name) == 0)
+ return true;
+
+ /* XXX: This is all kinds of ugly, but on the Hurd there's no way to
+ * know the command name of a process, so we have to try to match
+ * also on argv[1] for the case of an interpreted script. */
+ if (proc_stat_args_len(ps) > argv0_len) {
+ const char *script_name = basename(argv0 + argv0_len);
+
+ return strcmp(script_name, name) == 0;
+ }
+
+ return false;
+}
+#elif (defined(OS_Solaris) || defined(OS_AIX)) && HAVE_STRUCT_PSINFO
+static bool
+pid_is_cmd(pid_t pid, const char *name)
+{
+ struct psinfo psi;
+
+ if (!proc_get_psinfo(pid, &psi))
+ return false;
+
+ return strcmp(psi.pr_fname, name) == 0;
+}
+#elif defined(OS_HPUX)
+static bool
+pid_is_cmd(pid_t pid, const char *name)
+{
+ struct pst_status pst;
+
+ if (pstat_getproc(&pst, sizeof(pst), (size_t)0, (int)pid) < 0)
+ return false;
+ return (strcmp(pst.pst_ucomm, name) == 0);
+}
+#elif defined(OS_Darwin)
+static bool
+pid_is_cmd(pid_t pid, const char *name)
+{
+ char pathname[_POSIX_PATH_MAX];
+
+ if (proc_pidpath(pid, pathname, sizeof(pathname)) < 0)
+ return false;
+
+ return strcmp(pathname, name) == 0;
+}
+#elif defined(OS_FreeBSD)
+static bool
+pid_is_cmd(pid_t pid, const char *name)
+{
+ struct kinfo_proc kp;
+ int rc, mib[4];
+ size_t len;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_PID;
+ mib[3] = pid;
+ len = sizeof(kp);
+
+ rc = sysctl(mib, 4, &kp, &len, NULL, 0);
+ if (rc != 0 && errno != ESRCH)
+ return false;
+ if (len == 0 || len != sizeof(kp))
+ return false;
+
+ return strcmp(kp.ki_comm, name) == 0;
+}
+#elif defined(HAVE_KVM_H)
+static bool
+pid_is_cmd(pid_t pid, const char *name)
+{
+ kvm_t *kd;
+ struct kinfo_proc *kp;
+ char *process_name;
+ bool res = false;
+
+ kd = ssd_kvm_open();
+ kp = ssd_kvm_get_procs(kd, KERN_PROC_PID, pid, NULL);
+ if (kp == NULL)
+ goto cleanup;
+
+#if defined(OS_FreeBSD)
+ process_name = kp->ki_comm;
+#elif defined(OS_OpenBSD)
+ process_name = kp->p_comm;
+#elif defined(OS_DragonFlyBSD)
+ process_name = kp->kp_comm;
+#else
+ process_name = kp->kp_proc.p_comm;
+#endif
+
+ res = (strcmp(name, process_name) == 0);
+
+cleanup:
+ kvm_close(kd);
+
+ return res;
+}
+#endif
+
+#if defined(OS_Hurd)
+static bool
+pid_is_running(pid_t pid)
+{
+ return get_proc_stat(pid, 0) != NULL;
+}
+#else /* !OS_Hurd */
+static bool
+pid_is_running(pid_t pid)
+{
+ if (kill(pid, 0) == 0 || errno == EPERM)
+ return true;
+ else if (errno == ESRCH)
+ return false;
+ else
+ fatale("error checking pid %u status", pid);
+}
+#endif
+
+static enum status_code
+pid_check(pid_t pid)
+{
+ if (execname && !pid_is_exec(pid, &exec_stat))
+ return STATUS_DEAD;
+ if (match_ppid > 0 && !pid_is_child(pid, match_ppid))
+ return STATUS_DEAD;
+ if (userspec && !pid_is_user(pid, user_id))
+ return STATUS_DEAD;
+ if (cmdname && !pid_is_cmd(pid, cmdname))
+ return STATUS_DEAD;
+ if (action != ACTION_STOP && !pid_is_running(pid))
+ return STATUS_DEAD;
+
+ pid_list_push(&found, pid);
+
+ return STATUS_OK;
+}
+
+static enum status_code
+do_pidfile(const char *name)
+{
+ FILE *f;
+ static pid_t pid = 0;
+
+ if (pid)
+ return pid_check(pid);
+
+ f = fopen(name, "r");
+ if (f) {
+ enum status_code pid_status;
+
+ /* If we are only matching on the pidfile, and it is owned by
+ * a non-root user, then this is a security risk, and the
+ * contents cannot be trusted, because the daemon might have
+ * been compromised.
+ *
+ * If the pidfile is world-writable we refuse to parse it.
+ *
+ * If we got /dev/null specified as the pidfile, we ignore the
+ * checks, as this is being used to run processes no matter
+ * what. */
+ if (strcmp(name, "/dev/null") != 0) {
+ struct stat st;
+ int fd = fileno(f);
+
+ if (fstat(fd, &st) < 0)
+ fatale("cannot stat pidfile %s", name);
+
+ if (match_mode == MATCH_PIDFILE &&
+ ((st.st_uid != getuid() && st.st_uid != 0) ||
+ (st.st_gid != getgid() && st.st_gid != 0)))
+ fatal("matching only on non-root pidfile %s is insecure", name);
+ if (st.st_mode & 0002)
+ fatal("matching on world-writable pidfile %s is insecure", name);
+ }
+
+ if (fscanf(f, "%d", &pid) == 1)
+ pid_status = pid_check(pid);
+ else
+ pid_status = STATUS_UNKNOWN;
+ fclose(f);
+
+ if (pid_status == STATUS_DEAD)
+ return STATUS_DEAD_PIDFILE;
+ else
+ return pid_status;
+ } else if (errno == ENOENT)
+ return STATUS_DEAD;
+ else
+ fatale("unable to open pidfile %s", name);
+}
+
+#if defined(OS_Linux) || defined(OS_Solaris) || defined(OS_AIX)
+static enum status_code
+do_procinit(void)
+{
+ DIR *procdir;
+ struct dirent *entry;
+ int foundany;
+ pid_t pid;
+ enum status_code prog_status = STATUS_DEAD;
+
+ procdir = opendir("/proc");
+ if (!procdir)
+ fatale("unable to opendir /proc");
+
+ foundany = 0;
+ while ((entry = readdir(procdir)) != NULL) {
+ enum status_code pid_status;
+
+ if (sscanf(entry->d_name, "%d", &pid) != 1)
+ continue;
+ foundany++;
+
+ pid_status = pid_check(pid);
+ if (pid_status < prog_status)
+ prog_status = pid_status;
+ }
+ closedir(procdir);
+ if (foundany == 0)
+ fatal("nothing in /proc - not mounted?");
+
+ return prog_status;
+}
+#elif defined(OS_Hurd)
+static int
+check_proc_stat(struct proc_stat *ps)
+{
+ pid_check(proc_stat_pid(ps));
+ return 0;
+}
+
+static enum status_code
+do_procinit(void)
+{
+ if (!procset)
+ init_procset();
+
+ proc_stat_list_for_each(procset, check_proc_stat);
+
+ if (found)
+ return STATUS_OK;
+ else
+ return STATUS_DEAD;
+}
+#elif defined(OS_Darwin)
+static enum status_code
+do_procinit(void)
+{
+ pid_t *pid_buf;
+ int i, npids, pid_bufsize;
+ enum status_code prog_status = STATUS_DEAD;
+
+ npids = proc_listallpids(NULL, 0);
+ if (npids == 0)
+ return STATUS_UNKNOWN;
+
+ /* Try to avoid sudden changes in number of PIDs. */
+ npids += 4096;
+ pid_bufsize = sizeof(pid_t) * npids;
+ pid_buf = xmalloc(pid_bufsize);
+
+ npids = proc_listallpids(pid_buf, pid_bufsize);
+ if (npids == 0)
+ return STATUS_UNKNOWN;
+
+ for (i = 0; i < npids; i++) {
+ enum status_code pid_status;
+
+ pid_status = pid_check(pid_buf[i]);
+ if (pid_status < prog_status)
+ prog_status = pid_status;
+ }
+
+ free(pid_buf);
+
+ return prog_status;
+}
+#elif defined(OS_HPUX)
+static enum status_code
+do_procinit(void)
+{
+ struct pst_status pst[10];
+ int i, count;
+ int idx = 0;
+ enum status_code prog_status = STATUS_DEAD;
+
+ while ((count = pstat_getproc(pst, sizeof(pst[0]), 10, idx)) > 0) {
+ for (i = 0; i < count; i++) {
+ enum status_code pid_status;
+
+ pid_status = pid_check(pst[i].pst_pid);
+ if (pid_status < prog_status)
+ prog_status = pid_status;
+ }
+ idx = pst[count - 1].pst_idx + 1;
+ }
+
+ return prog_status;
+}
+#elif defined(OS_FreeBSD)
+static enum status_code
+do_procinit(void)
+{
+ struct kinfo_proc *kp;
+ int rc, mib[3];
+ size_t len = 0;
+ int nentries, i;
+ enum status_code prog_status = STATUS_DEAD;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_PROC;
+
+ rc = sysctl(mib, 3, NULL, &len, NULL, 0);
+ if (rc != 0 && errno != ESRCH)
+ return STATUS_UNKNOWN;
+ if (len == 0)
+ return STATUS_UNKNOWN;
+
+ kp = xmalloc(len);
+ rc = sysctl(mib, 3, kp, &len, NULL, 0);
+ if (rc != 0 && errno != ESRCH)
+ return STATUS_UNKNOWN;
+ if (len == 0)
+ return STATUS_UNKNOWN;
+ nentries = len / sizeof(*kp);
+
+ for (i = 0; i < nentries; i++) {
+ enum status_code pid_status;
+
+ pid_status = pid_check(kp[i].ki_pid);
+ if (pid_status < prog_status)
+ prog_status = pid_status;
+ }
+
+ free(kp);
+
+ return prog_status;
+}
+#elif defined(HAVE_KVM_H)
+static enum status_code
+do_procinit(void)
+{
+ kvm_t *kd;
+ int nentries, i;
+ struct kinfo_proc *kp;
+ enum status_code prog_status = STATUS_DEAD;
+
+ kd = ssd_kvm_open();
+ kp = ssd_kvm_get_procs(kd, KERN_PROC_ALL, 0, &nentries);
+
+ for (i = 0; i < nentries; i++) {
+ enum status_code pid_status;
+ pid_t pid;
+
+#if defined(OS_FreeBSD)
+ pid = kp[i].ki_pid;
+#elif defined(OS_OpenBSD)
+ pid = kp[i].p_pid;
+#elif defined(OS_DragonFlyBSD)
+ pid = kp[i].kp_pid;
+#else
+ pid = kp[i].kp_proc.p_pid;
+#endif
+
+ pid_status = pid_check(pid);
+ if (pid_status < prog_status)
+ prog_status = pid_status;
+ }
+
+ kvm_close(kd);
+
+ return prog_status;
+}
+#endif
+
+static enum status_code
+do_findprocs(void)
+{
+ pid_list_free(&found);
+
+ if (match_pid > 0)
+ return pid_check(match_pid);
+ else if (pidfile)
+ return do_pidfile(pidfile);
+ else
+ return do_procinit();
+}
+
+static int
+do_start(int argc, char **argv)
+{
+ int devnull_fd = -1;
+ int output_fd = -1;
+ gid_t rgid;
+ uid_t ruid;
+
+ do_findprocs();
+
+ if (found) {
+ info("%s already running.\n", execname ? execname : "process");
+ return exitnodo;
+ }
+ if (testmode && quietmode <= 0) {
+ printf("Would start %s ", startas);
+ while (argc-- > 0)
+ printf("%s ", *argv++);
+ if (changeuser != NULL) {
+ printf(" (as user %s[%d]", changeuser, runas_uid);
+ if (changegroup != NULL)
+ printf(", and group %s[%d])", changegroup, runas_gid);
+ else
+ printf(")");
+ }
+ if (changeroot != NULL)
+ printf(" in directory %s", changeroot);
+ if (nicelevel)
+ printf(", and add %i to the priority", nicelevel);
+ if (proc_sched)
+ printf(", with scheduling policy %s with priority %i",
+ proc_sched->policy_name, proc_sched->priority);
+ if (io_sched)
+ printf(", with IO scheduling class %s with priority %i",
+ io_sched->policy_name, io_sched->priority);
+ printf(".\n");
+ }
+ if (testmode)
+ return 0;
+ debug("Starting %s...\n", startas);
+ *--argv = startas;
+ if (umask_value >= 0)
+ umask(umask_value);
+ if (background)
+ /* Ok, we need to detach this process. */
+ daemonize();
+ else if (mpidfile && pidfile != NULL)
+ /* User wants _us_ to make the pidfile, but detach themself! */
+ write_pidfile(pidfile, getpid());
+ if (background && close_io) {
+ devnull_fd = open("/dev/null", O_RDONLY);
+ if (devnull_fd < 0)
+ fatale("unable to open '%s'", "/dev/null");
+ }
+ if (background && output_io) {
+ output_fd = open(output_io, O_CREAT | O_WRONLY | O_APPEND, 0664);
+ if (output_fd < 0)
+ fatale("unable to open '%s'", output_io);
+ }
+ if (nicelevel) {
+ errno = 0;
+ if ((nice(nicelevel) == -1) && (errno != 0))
+ fatale("unable to alter nice level by %i", nicelevel);
+ }
+ if (proc_sched)
+ set_proc_schedule(proc_sched);
+ if (io_sched)
+ set_io_schedule(io_sched);
+ if (changeroot != NULL) {
+ if (chdir(changeroot) < 0)
+ fatale("unable to chdir() to %s", changeroot);
+ if (chroot(changeroot) < 0)
+ fatale("unable to chroot() to %s", changeroot);
+ }
+ if (chdir(changedir) < 0)
+ fatale("unable to chdir() to %s", changedir);
+
+ rgid = getgid();
+ ruid = getuid();
+ if (changegroup != NULL) {
+ if (rgid != (gid_t)runas_gid)
+ if (setgid(runas_gid))
+ fatale("unable to set gid to %d", runas_gid);
+ }
+ if (changeuser != NULL) {
+ /* We assume that if our real user and group are the same as
+ * the ones we should switch to, the supplementary groups
+ * will be already in place. */
+ if (rgid != (gid_t)runas_gid || ruid != (uid_t)runas_uid)
+ if (initgroups(changeuser, runas_gid))
+ fatale("unable to set initgroups() with gid %d",
+ runas_gid);
+
+ if (ruid != (uid_t)runas_uid)
+ if (setuid(runas_uid))
+ fatale("unable to set uid to %s", changeuser);
+ }
+
+ if (background && output_fd >= 0) {
+ dup2(output_fd, 1); /* stdout */
+ dup2(output_fd, 2); /* stderr */
+ }
+ if (background && close_io) {
+ int i;
+
+ dup2(devnull_fd, 0); /* stdin */
+
+ /* Now close all extra fds. */
+ for (i = get_open_fd_max() - 1; i >= 3; --i)
+ close(i);
+ }
+ execv(startas, argv);
+ fatale("unable to start %s", startas);
+}
+
+static void
+do_stop(int sig_num, int *n_killed, int *n_notkilled)
+{
+ struct pid_list *p;
+
+ do_findprocs();
+
+ *n_killed = 0;
+ *n_notkilled = 0;
+
+ if (!found)
+ return;
+
+ pid_list_free(&killed);
+
+ for (p = found; p; p = p->next) {
+ if (testmode) {
+ info("Would send signal %d to %d.\n", sig_num, p->pid);
+ (*n_killed)++;
+ } else if (kill(p->pid, sig_num) == 0) {
+ pid_list_push(&killed, p->pid);
+ (*n_killed)++;
+ } else {
+ if (sig_num)
+ warning("failed to kill %d: %s\n",
+ p->pid, strerror(errno));
+ (*n_notkilled)++;
+ }
+ }
+}
+
+static void
+do_stop_summary(int retry_nr)
+{
+ struct pid_list *p;
+
+ if (quietmode >= 0 || !killed)
+ return;
+
+ printf("Stopped %s (pid", what_stop);
+ for (p = killed; p; p = p->next)
+ printf(" %d", p->pid);
+ putchar(')');
+ if (retry_nr > 0)
+ printf(", retry #%d", retry_nr);
+ printf(".\n");
+}
+
+static void LIBCOMPAT_ATTR_PRINTF(1)
+set_what_stop(const char *format, ...)
+{
+ va_list arglist;
+ int rc;
+
+ va_start(arglist, format);
+ rc = vasprintf(&what_stop, format, arglist);
+ va_end(arglist);
+
+ if (rc < 0)
+ fatale("cannot allocate formatted string");
+}
+
+/*
+ * We want to keep polling for the processes, to see if they've exited, or
+ * until the timeout expires.
+ *
+ * This is a somewhat complicated algorithm to try to ensure that we notice
+ * reasonably quickly when all the processes have exited, but don't spend
+ * too much CPU time polling. In particular, on a fast machine with
+ * quick-exiting daemons we don't want to delay system shutdown too much,
+ * whereas on a slow one, or where processes are taking some time to exit,
+ * we want to increase the polling interval.
+ *
+ * The algorithm is as follows: we measure the elapsed time it takes to do
+ * one poll(), and wait a multiple of this time for the next poll. However,
+ * if that would put us past the end of the timeout period we wait only as
+ * long as the timeout period, but in any case we always wait at least
+ * MIN_POLL_INTERVAL (20ms). The multiple (‘ratio’) starts out as 2, and
+ * increases by 1 for each poll to a maximum of 10; so we use up to between
+ * 30% and 10% of the machine's resources (assuming a few reasonable things
+ * about system performance).
+ */
+static bool
+do_stop_timeout(int timeout, int *n_killed, int *n_notkilled)
+{
+ struct timespec stopat, before, after, interval, maxinterval;
+ int ratio;
+
+ timespec_gettime(&stopat);
+ stopat.tv_sec += timeout;
+ ratio = 1;
+ for (;;) {
+ int rc;
+
+ timespec_gettime(&before);
+ if (timespec_cmp(&before, &stopat, >))
+ return false;
+
+ do_stop(0, n_killed, n_notkilled);
+ if (!*n_killed)
+ return true;
+
+ timespec_gettime(&after);
+
+ if (!timespec_cmp(&after, &stopat, <))
+ return false;
+
+ if (ratio < 10)
+ ratio++;
+
+ timespec_sub(&stopat, &after, &maxinterval);
+ timespec_sub(&after, &before, &interval);
+ timespec_mul(&interval, ratio);
+
+ if (interval.tv_sec < 0 || interval.tv_nsec < 0)
+ interval.tv_sec = interval.tv_nsec = 0;
+
+ if (timespec_cmp(&interval, &maxinterval, >))
+ interval = maxinterval;
+
+ if (interval.tv_sec == 0 &&
+ interval.tv_nsec <= MIN_POLL_INTERVAL)
+ interval.tv_nsec = MIN_POLL_INTERVAL;
+
+ rc = pselect(0, NULL, NULL, NULL, &interval, NULL);
+ if (rc < 0 && errno != EINTR)
+ fatale("select() failed for pause");
+ }
+}
+
+static int
+finish_stop_schedule(bool anykilled)
+{
+ if (rpidfile && pidfile && !testmode)
+ remove_pidfile(pidfile);
+
+ if (anykilled)
+ return 0;
+
+ info("No %s found running; none killed.\n", what_stop);
+
+ return exitnodo;
+}
+
+static int
+run_stop_schedule(void)
+{
+ int position, n_killed, n_notkilled, value, retry_nr;
+ bool anykilled;
+
+ if (testmode) {
+ if (schedule != NULL) {
+ info("Ignoring --retry in test mode\n");
+ schedule = NULL;
+ }
+ }
+
+ if (cmdname)
+ set_what_stop("%s", cmdname);
+ else if (execname)
+ set_what_stop("%s", execname);
+ else if (pidfile)
+ set_what_stop("process in pidfile '%s'", pidfile);
+ else if (match_pid > 0)
+ set_what_stop("process with pid %d", match_pid);
+ else if (match_ppid > 0)
+ set_what_stop("process(es) with parent pid %d", match_ppid);
+ else if (userspec)
+ set_what_stop("process(es) owned by '%s'", userspec);
+ else
+ BUG("no match option, please report");
+
+ anykilled = false;
+ retry_nr = 0;
+ n_killed = 0;
+ n_notkilled = 0;
+
+ if (schedule == NULL) {
+ do_stop(signal_nr, &n_killed, &n_notkilled);
+ do_stop_summary(0);
+ if (n_notkilled > 0)
+ info("%d pids were not killed\n", n_notkilled);
+ if (n_killed)
+ anykilled = true;
+ return finish_stop_schedule(anykilled);
+ }
+
+ for (position = 0; position < schedule_length; position++) {
+ reposition:
+ value = schedule[position].value;
+ n_notkilled = 0;
+
+ switch (schedule[position].type) {
+ case sched_goto:
+ position = value;
+ goto reposition;
+ case sched_signal:
+ do_stop(value, &n_killed, &n_notkilled);
+ do_stop_summary(retry_nr++);
+ if (!n_killed)
+ return finish_stop_schedule(anykilled);
+ else
+ anykilled = true;
+ continue;
+ case sched_timeout:
+ if (do_stop_timeout(value, &n_killed, &n_notkilled))
+ return finish_stop_schedule(anykilled);
+ else
+ continue;
+ default:
+ BUG("schedule[%d].type value %d is not valid",
+ position, schedule[position].type);
+ }
+ }
+
+ info("Program %s, %d process(es), refused to die.\n",
+ what_stop, n_killed);
+
+ return 2;
+}
+
+int
+main(int argc, char **argv)
+{
+ progname = argv[0];
+
+ parse_options(argc, argv);
+ setup_options();
+
+ argc -= optind;
+ argv += optind;
+
+ if (action == ACTION_START)
+ return do_start(argc, argv);
+ else if (action == ACTION_STOP)
+ return run_stop_schedule();
+ else if (action == ACTION_STATUS)
+ return do_findprocs();
+
+ return 0;
+}
diff --git a/utils/t/update_alternatives.t b/utils/t/update_alternatives.t
new file mode 100644
index 0000000..ef7d66f
--- /dev/null
+++ b/utils/t/update_alternatives.t
@@ -0,0 +1,719 @@
+#!/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::IPC;
+use Dpkg::File qw(file_slurp);
+use Dpkg::Path qw(find_command);
+
+my $srcdir = $ENV{srcdir} || '.';
+my $tmpdir = 't.tmp/update_alternatives';
+my $admindir = File::Spec->rel2abs("$tmpdir/admindir"),
+my $altdir = File::Spec->rel2abs("$tmpdir/alternatives");
+my $bindir = File::Spec->rel2abs("$tmpdir/bin");
+my @ua = ("$ENV{builddir}/update-alternatives", '--log', '/dev/null',
+ '--quiet', '--admindir', "$admindir", '--altdir', "$altdir");
+
+my $rootdir_envvar = $ENV{UA_ROOTDIR_ENVVAR} // 'DPKG_ROOT';
+my $admindir_envvar = $ENV{UA_ADMINDIR_ENVVAR} // 'DPKG_ADMINDIR';
+
+delete $ENV{$rootdir_envvar};
+delete $ENV{$admindir_envvar};
+
+my %paths = (
+ true => find_command('true'),
+ false => find_command('false'),
+ yes => find_command('yes'),
+ cat => find_command('cat'),
+ date => find_command('date'),
+ sleep => find_command('sleep'),
+);
+
+if (! -x "$ENV{builddir}/update-alternatives") {
+ plan skip_all => 'update-alternatives not available';
+ exit(0);
+}
+
+my $main_link = "$bindir/generic-test";
+my $main_name = 'generic-test';
+my @choices = (
+ {
+ path => $paths{true},
+ priority => 20,
+ slaves => [
+ {
+ link => "$bindir/slave2",
+ name => 'slave2',
+ path => $paths{cat},
+ },
+ {
+ link => "$bindir/slave3",
+ name => 'slave3',
+ path => $paths{cat},
+ },
+ {
+ link => "$bindir/slave1",
+ name => 'slave1',
+ path => $paths{yes},
+ },
+ {
+ link => "$bindir/slave4",
+ name => 'slave4',
+ path => $paths{cat},
+ },
+ ],
+ },
+ {
+ path => $paths{false},
+ priority => 10,
+ slaves => [
+ {
+ link => "$bindir/slave1",
+ name => 'slave1',
+ path => $paths{date},
+ },
+ ],
+ },
+ {
+ path => $paths{sleep},
+ priority => 5,
+ slaves => [],
+ },
+);
+my $nb_slaves = 4;
+plan tests => (4 * ($nb_slaves + 1) + 2) * 26 # number of check_choices
+ + 30 # number of directory checks
+ + 110; # rest
+
+sub cleanup {
+ system("rm -rf $tmpdir && mkdir -p $admindir && mkdir -p $altdir");
+ system("mkdir -p $bindir/more");
+}
+
+sub call_ua {
+ my ($params, %opts) = @_;
+
+ my %env;
+ %env = %{delete $opts{env}} if exists $opts{env};
+ my @cmd;
+ if (exists $opts{cmd}) {
+ @cmd = @{delete $opts{cmd}};
+ } else {
+ @cmd = @ua;
+ }
+
+ spawn(exec => [ @cmd, @{$params} ], nocheck => 1,
+ wait_child => 1, env => { LC_ALL => 'C', %env }, %opts);
+ my $test_id = '';
+ $test_id = "$opts{test_id}: " if defined $opts{test_id};
+ if ($opts{expect_failure}) {
+ ok($? != 0, "${test_id}update-alternatives @$params should fail.") or
+ diag("Did not fail as expected: @ua @$params");
+ } else {
+ ok($? == 0, "${test_id}update-alternatives @$params should work.") or
+ diag("Did not succeed as expected: @ua @$params");
+ }
+}
+
+sub call_ua_dirs {
+ my (%opts) = @_;
+
+ $opts{cmd} = [ "$ENV{builddir}/update-alternatives" ];
+
+ my @params;
+ @params = @{delete $opts{params}} if exists $opts{params};
+ push @params, qw(--debug);
+ push @params, qw(--log /dev/null);
+ push @params, qw(--get-selections);
+
+ die unless exists $opts{expected};
+
+ my $stderr;
+ my $expected = delete $opts{expected};
+ $opts{to_file} = '/dev/null';
+ $opts{error_to_string} = \$stderr;
+
+ call_ua(\@params, %opts);
+
+ ok($stderr =~ $expected, "output '$stderr' does not match expected '$expected'");
+}
+
+sub install_choice {
+ my ($id, %opts) = @_;
+ my $alt = $choices[$id];
+ my @params;
+ push @params, @{$opts{params}} if exists $opts{params};
+ push @params, '--install', "$main_link", "$main_name",
+ $alt->{path}, $alt->{priority};
+ foreach my $slave (@{ $alt->{slaves} }) {
+ push @params, '--slave', $slave->{link}, $slave->{name}, $slave->{path};
+ }
+ call_ua(\@params, %opts);
+}
+
+sub remove_choice {
+ my ($id, %opts) = @_;
+ my $alt = $choices[$id];
+ my @params;
+ push @params, @{$opts{params}} if exists $opts{params};
+ push @params, '--remove', $main_name, $alt->{path};
+ call_ua(\@params, %opts);
+}
+
+sub remove_all_choices {
+ my (%opts) = @_;
+ my @params;
+ push @params, @{$opts{params}} if exists $opts{params};
+ push @params, '--remove-all', $main_name;
+ call_ua(\@params, %opts);
+}
+
+sub set_choice {
+ my ($id, %opts) = @_;
+ my $alt = $choices[$id];
+ my @params;
+ push @params, @{$opts{params}} if exists $opts{params};
+ push @params, '--set', $main_name, $alt->{path};
+ call_ua(\@params, %opts);
+}
+
+sub config_choice {
+ my ($id, %opts) = @_;
+ my ($input, $output) = ('', '');
+ if ($id >= 0) {
+ my $alt = $choices[$id];
+ $input = $alt->{path};
+ } else {
+ $input = '0';
+ }
+ $input .= "\n";
+ $opts{from_string} = \$input;
+ $opts{to_string} = \$output;
+ my @params;
+ push @params, @{$opts{params}} if exists $opts{params};
+ push @params, '--config', $main_name;
+ call_ua(\@params, %opts);
+}
+
+sub get_slaves_status {
+ my ($id) = @_;
+ my %slaves;
+ # None of the slaves are installed
+ foreach my $alt (@choices) {
+ for my $i (0 .. @{$alt->{slaves}} - 1) {
+ $slaves{$alt->{slaves}[$i]{name}} = $alt->{slaves}[$i];
+ $slaves{$alt->{slaves}[$i]{name}}{installed} = 0;
+ }
+ }
+ # except those of the current alternative (minus optional slaves)
+ if (defined($id)) {
+ my $alt = $choices[$id];
+ for my $i (0 .. @{$alt->{slaves}} - 1) {
+ $slaves{$alt->{slaves}[$i]{name}} = $alt->{slaves}[$i];
+ if (-e $alt->{slaves}[$i]{path}) {
+ $slaves{$alt->{slaves}[$i]{name}}{installed} = 1;
+ }
+ }
+ }
+ my @slaves = sort { $a->{name} cmp $b->{name} } values %slaves;
+
+ return @slaves;
+}
+
+sub check_link {
+ my ($link, $value, $msg) = @_;
+ ok(-l $link, "$msg: $link disappeared.");
+ is(readlink($link), $value, "$link doesn't point to $value.");
+}
+sub check_no_link {
+ my ($link, $msg) = @_;
+ lstat($link);
+ ok(!-e _, "$msg: $link still exists.");
+ ok(1, 'fake test'); # Same number of tests as check_link
+}
+
+sub check_slaves {
+ my ($id, $msg) = @_;
+ foreach my $slave (get_slaves_status($id)) {
+ if ($slave->{installed}) {
+ check_link("$altdir/$slave->{name}", $slave->{path}, $msg);
+ check_link($slave->{link}, "$altdir/$slave->{name}", $msg);
+ } else {
+ check_no_link("$altdir/$slave->{name}", $msg);
+ check_no_link($slave->{link}, $msg);
+ }
+ }
+}
+# (4 * (nb_slaves+1) + 2) tests in each check_choice() call
+sub check_choice {
+ my ($id, $mode, $msg) = @_;
+ my $output;
+ if (defined $id) {
+ # Check status
+ call_ua([ '--query', "$main_name" ], to_string => \$output, test_id => $msg);
+ my $status = q{};
+ if ($output =~ /^Status: (.*)$/im) {
+ $status = $1;
+ }
+ is($status, $mode, "$msg: status is not $mode.");
+ # Check links
+ my $alt = $choices[$id];
+ check_link("$altdir/$main_name", $alt->{path}, $msg);
+ check_link($main_link, "$altdir/$main_name", $msg);
+ check_slaves($id, $msg);
+ } else {
+ call_ua([ '--query', "$main_name" ], error_to_string => \$output,
+ expect_failure => 1, test_id => $msg);
+ ok($output =~ /no alternatives/, "$msg: bad error message for --query.");
+ # Check that all links have disappeared
+ check_no_link("$altdir/$main_name", $msg);
+ check_no_link($main_link, $msg);
+ check_slaves(undef, $msg);
+ }
+}
+
+### START OF TESTS
+cleanup();
+
+# check directory overrides
+
+my $DEFAULT_ROOTDIR = '';
+my $DEFAULT_ADMINDIR = $ENV{UA_ADMINDIR_DEFAULT} // '/var/lib/dpkg/alternatives';
+
+# ENV_ADMINDIR + defaults
+call_ua_dirs(
+ env => {
+ $admindir_envvar => '/admindir_env',
+ },
+ expected => "root=$DEFAULT_ROOTDIR admdir=/admindir_env",
+);
+
+# ENV_ROOT + defaults
+call_ua_dirs(
+ env => { $rootdir_envvar => '/rootdir_env' },
+ expected => "root=/rootdir_env admdir=/rootdir_env$DEFAULT_ADMINDIR",
+);
+
+# ENV_ROOT + ENV_ADMINDIR
+call_ua_dirs(
+ env => {
+ $rootdir_envvar => '/rootdir_env',
+ $admindir_envvar => '/admindir_env',
+ },
+ expected => 'root=/rootdir_env admdir=/admindir_env',
+);
+
+# ENV_ADMINDIR + options
+call_ua_dirs(
+ env => { $admindir_envvar => '/admindir_env' },
+ params => [ qw(--root /rootdir_opt) ],
+ expected => "root=/rootdir_opt admdir=/rootdir_opt$DEFAULT_ADMINDIR",
+);
+call_ua_dirs(
+ env => { $admindir_envvar => '/admindir_env' },
+ params => [ qw(--admindir /admindir_opt) ],
+ expected => "root=$DEFAULT_ROOTDIR admdir=/admindir_opt",
+);
+call_ua_dirs(
+ env => { $admindir_envvar => '/admindir_env' },
+ params => [ qw(--root /rootdir_opt --admindir /admindir_opt) ],
+ expected => 'root=/rootdir_opt admdir=/admindir_opt',
+);
+call_ua_dirs(
+ env => { $admindir_envvar => '/admindir_env' },
+ params => [ qw(--admindir /admindir_opt --root /rootdir_opt) ],
+ expected => "root=/rootdir_opt admdir=/rootdir_opt$DEFAULT_ADMINDIR",
+);
+
+# ENV_ROOT + options
+call_ua_dirs(
+ env => { $rootdir_envvar => '/rootdir_env' },
+ params => [ qw(--root /rootdir_opt) ],
+ expected => "root=/rootdir_opt admdir=/rootdir_opt$DEFAULT_ADMINDIR",
+);
+call_ua_dirs(
+ env => { $rootdir_envvar => '/rootdir_env' },
+ params => [ qw(--admindir /admindir_opt) ],
+ expected => 'root=/rootdir_env admdir=/admindir_opt',
+);
+call_ua_dirs(
+ env => { $rootdir_envvar => '/rootdir_env' },
+ params => [ qw(--root /rootdir_opt --admindir /admindir_opt) ],
+ expected => 'root=/rootdir_opt admdir=/admindir_opt',
+);
+call_ua_dirs(
+ env => { $rootdir_envvar => '/rootdir_env' },
+ params => [ qw(--admindir /admindir_opt --root /rootdir_opt) ],
+ expected => "root=/rootdir_opt admdir=/rootdir_opt$DEFAULT_ADMINDIR",
+);
+
+# ENV_ROOT + ENV_ADMINDIR + options
+call_ua_dirs(
+ env => {
+ $rootdir_envvar => '/rootdir_env',
+ $admindir_envvar => '/admindir_env',
+ },
+ params => [ qw(--root /rootdir_opt) ],
+ expected => "root=/rootdir_opt admdir=/rootdir_opt$DEFAULT_ADMINDIR",
+);
+call_ua_dirs(
+ env => {
+ $rootdir_envvar => '/rootdir_env',
+ $admindir_envvar => '/admindir_env',
+ },
+ params => [ qw(--admindir /admindir_opt) ],
+ expected => 'root=/rootdir_env admdir=/admindir_opt',
+);
+call_ua_dirs(
+ env => {
+ $rootdir_envvar => '/rootdir_env',
+ $admindir_envvar => '/admindir_env',
+ },
+ params => [ qw(--root /rootdir_opt --admindir /admindir_opt) ],
+ expected => 'root=/rootdir_opt admdir=/admindir_opt',
+);
+call_ua_dirs(
+ env => {
+ $rootdir_envvar => '/rootdir_env',
+ $admindir_envvar => '/admindir_env',
+ },
+ params => [ qw(--admindir /admindir_opt --root /rootdir_opt) ],
+ expected => "root=/rootdir_opt admdir=/rootdir_opt$DEFAULT_ADMINDIR",
+);
+
+cleanup();
+# removal when not installed should not fail
+remove_choice(0);
+# successive install in auto mode
+install_choice(1);
+check_choice(1, 'auto', 'initial install 1');
+install_choice(2); # 2 is lower prio, stays at 1
+check_choice(1, 'auto', 'initial install 2');
+install_choice(0); # 0 is higher priority
+check_choice(0, 'auto', 'initial install 3');
+
+# verify that the administrative file is sorted properly
+{
+ my $content = file_slurp("$admindir/generic-test");
+ my $expected =
+"auto
+$bindir/generic-test
+slave1
+$bindir/slave1
+slave2
+$bindir/slave2
+slave3
+$bindir/slave3
+slave4
+$bindir/slave4
+
+";
+
+ my %slaves;
+
+ # Store slaves in a hash to easily retrieve present and missing ones.
+ foreach my $alt (@choices) {
+ foreach my $slave (@{$alt->{slaves}}) {
+ $slaves{$slave->{name}}{$alt->{path}} = $slave;
+ }
+ }
+
+ foreach my $alt (sort { $a->{path} cmp $b->{path} } @choices) {
+ $expected .= $alt->{path} . "\n";
+ $expected .= $alt->{priority} . "\n";
+ foreach my $slave_name (sort keys %slaves) {
+ $expected .= $slaves{$slave_name}{$alt->{path}}{path} || '';
+ $expected .= "\n";
+ }
+ }
+ $expected .= "\n";
+
+ is($content, $expected, 'administrative file is as expected');
+}
+
+# manual change with --set-selections
+my $input = "doesntexist auto $paths{date}\ngeneric-test manual $paths{false}\n";
+my $output = '';
+call_ua(['--set-selections'], from_string => \$input,
+ to_string => \$output, test_id => 'manual update with --set-selections');
+check_choice(1, 'manual', 'manual update with --set-selections');
+$input = "generic-test auto $paths{true}\n";
+call_ua(['--set-selections'], from_string => \$input,
+ to_string => \$output, test_id => 'auto update with --set-selections');
+check_choice(0, 'auto', 'auto update with --set-selections');
+# manual change with set
+set_choice(2, test_id => 'manual update with --set');
+check_choice(2, 'manual', 'manual update with --set'); # test #388313
+remove_choice(2, test_id => 'remove manual, back to auto');
+check_choice(0, 'auto', 'remove manual, back to auto');
+remove_choice(0, test_id => 'remove best');
+check_choice(1, 'auto', 'remove best');
+remove_choice(1, test_id => 'no alternative left');
+check_choice(undef, '', 'no alternative left');
+# single choice in manual mode, to be removed
+install_choice(1);
+set_choice(1);
+check_choice(1, 'manual', 'single manual choice');
+remove_choice(1);
+check_choice(undef, '', 'removal single manual');
+# test --remove-all
+install_choice(0);
+install_choice(1);
+install_choice(2);
+remove_all_choices(test_id => 'remove all');
+check_choice(undef, '', 'no alternative left');
+# check auto-recovery of user mistakes (#100135)
+install_choice(1);
+ok(unlink("$bindir/generic-test"), 'failed removal');
+ok(unlink("$bindir/slave1"), 'failed removal');
+install_choice(1);
+check_choice(1, 'auto', 'recreate links in auto mode');
+set_choice(1);
+ok(unlink("$bindir/generic-test"), 'failed removal');
+ok(unlink("$bindir/slave1"), 'failed removal');
+install_choice(1);
+check_choice(1, 'manual', 'recreate links in manual mode');
+# check recovery of /etc/alternatives/*
+install_choice(0);
+ok(unlink("$altdir/generic-test"), 'failed removal');
+install_choice(1);
+check_choice(0, 'auto', '<altdir>/generic-test lost, back to auto');
+# test --config
+config_choice(0);
+check_choice(0, 'manual', 'config to best but manual');
+config_choice(1);
+check_choice(1, 'manual', 'config to manual');
+config_choice(-1);
+check_choice(0, 'auto', 'config auto');
+
+# test rename of links
+install_choice(0);
+my $old_slave = $choices[0]{slaves}[0]{link};
+my $old_link = $main_link;
+$choices[0]{slaves}[0]{link} = "$bindir/more/generic-slave";
+$main_link = "$bindir/more/mytest";
+install_choice(0);
+check_choice(0, 'auto', 'test rename of links');
+check_no_link($old_link, 'test rename of links');
+check_no_link($old_slave, 'test rename of links');
+# rename with installing other alternatives
+$old_link = $main_link;
+$main_link = "$bindir/generic-test";
+install_choice(1);
+check_choice(0, 'auto', 'rename link');
+check_no_link($old_link, 'rename link');
+# rename with lost file
+unlink($old_slave);
+$old_slave = $choices[0]{slaves}[0]{link};
+$choices[0]{slaves}[0]{link} = "$bindir/generic-slave-bis";
+install_choice(0);
+check_choice(0, 'auto', 'rename lost file');
+check_no_link($old_slave, 'rename lost file');
+# update of alternative with many slaves not currently installed
+# and the link of the renamed slave exists while it should not
+set_choice(1);
+symlink("$paths{cat}", "$bindir/generic-slave-bis");
+$choices[0]{slaves}[0]{link} = "$bindir/slave2";
+install_choice(0, test_id => 'update with non-installed slaves');
+check_no_link("$bindir/generic-slave-bis",
+ 'drop renamed symlink that should not be installed');
+
+# test install with empty admin file (#457863)
+cleanup();
+system("touch $admindir/generic-test");
+install_choice(0);
+# test install with garbage admin file
+cleanup();
+system("echo garbage > $admindir/generic-test");
+install_choice(0, error_to_file => '/dev/null', expect_failure => 1);
+
+# test invalid usages
+cleanup();
+install_choice(0);
+# try to install a slave alternative as new master
+call_ua(['--install', "$bindir/testmaster", 'slave1', "$paths{date}", '10'],
+ expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null');
+# try to install a master alternative as slave
+call_ua(['--install', "$bindir/testmaster", 'testmaster', "$paths{date}", '10',
+ '--slave', "$bindir/testslave", 'generic-test', "$paths{true}" ],
+ expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null');
+# try to reuse master link in slave
+call_ua(['--install', "$bindir/testmaster", 'testmaster', "$paths{date}", '10',
+ '--slave', "$bindir/testmaster", 'testslave', "$paths{true}" ],
+ expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null');
+# try to reuse links in master alternative
+call_ua(['--install', "$bindir/slave1", 'testmaster', "$paths{date}", '10'],
+ expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null');
+# try to reuse links in slave alternative
+call_ua(['--install', "$bindir/testmaster", 'testmaster', "$paths{date}", '10',
+ '--slave', "$bindir/generic-test", 'testslave', "$paths{true}" ],
+ expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null');
+# try to reuse slave link in another slave alternative of another choice of
+# the same main alternative
+call_ua(['--install', $main_link, $main_name, "$paths{date}", '10',
+ '--slave', "$bindir/slave1", 'testslave', "$paths{true}" ],
+ expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null');
+# lack of absolute filenames in links or file path, non-existing path,
+call_ua(['--install', '../testmaster', 'testmaster', "$paths{date}", '10'],
+ expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null');
+call_ua(['--install', "$bindir/testmaster", 'testmaster', './update-alternatives.pl', '10'],
+ expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null');
+# non-existing alternative path
+call_ua(['--install', "$bindir/testmaster", 'testmaster', "$bindir/doesntexist", '10'],
+ expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null');
+# invalid alternative name in master
+call_ua(['--install', "$bindir/testmaster", 'test/master', "$paths{date}", '10'],
+ expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null');
+# invalid alternative name in slave
+call_ua(['--install', "$bindir/testmaster", 'testmaster', "$paths{date}", '10',
+ '--slave', "$bindir/testslave", 'test slave', "$paths{true}" ],
+ expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null');
+# install in non-existing dir should fail
+call_ua(['--install', "$bindir/doesntexist/testmaster", 'testmaster', "$paths{date}", '10',
+ '--slave', "$bindir/testslave", 'testslave', "$paths{true}" ],
+ expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null');
+call_ua(['--install', "$bindir/testmaster", 'testmaster', "$paths{date}", '10',
+ '--slave', "$bindir/doesntexist/testslave", 'testslave', "$paths{true}" ],
+ expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null');
+
+# non-existing alternative path in slave is not a failure
+my $old_path = $choices[0]{slaves}[0]{path};
+$old_slave = $choices[0]{slaves}[0]{link};
+$choices[0]{slaves}[0]{path} = "$bindir/doesntexist";
+$choices[0]{slaves}[0]{link} = "$bindir/baddir/slave2";
+# test rename of slave link that existed but that doesn't anymore
+# and link is moved into non-existing dir at the same time
+install_choice(0);
+check_choice(0, 'auto', 'optional renamed slave2 in non-existing dir');
+# same but on fresh install
+cleanup();
+install_choice(0);
+check_choice(0, 'auto', 'optional slave2 in non-existing dir');
+$choices[0]{slaves}[0]{link} = $old_slave;
+# test fresh install with a non-existing slave file
+cleanup();
+install_choice(0);
+check_choice(0, 'auto', 'optional slave2');
+$choices[0]{slaves}[0]{path} = $old_path;
+
+# test management of pre-existing files
+cleanup();
+system("touch $main_link $bindir/slave1");
+install_choice(0);
+ok(!-l $main_link, 'install preserves files that should be links');
+ok(!-l "$bindir/slave1", 'install preserves files that should be slave links');
+remove_choice(0);
+ok(-f $main_link, 'removal keeps real file installed as master link');
+ok(-f "$bindir/slave1", 'removal keeps real files installed as slave links');
+install_choice(0, params => ['--force']);
+check_choice(0, 'auto', 'install --force replaces files with links');
+
+# test management of pre-existing files #2
+cleanup();
+system("touch $main_link $bindir/slave2");
+install_choice(0);
+install_choice(1);
+ok(!-l $main_link, 'inactive install preserves files that should be links');
+ok(!-l "$bindir/slave2", 'inactive install preserves files that should be slave links');
+ok(-f $main_link, 'inactive install keeps real file installed as master link');
+ok(-f "$bindir/slave2", 'inactive install keeps real files installed as slave links');
+set_choice(1);
+ok(!-l $main_link, 'manual switching preserves files that should be links');
+ok(!-l "$bindir/slave2", 'manual switching preserves files that should be slave links');
+ok(-f $main_link, 'manual switching keeps real file installed as master link');
+ok(-f "$bindir/slave2", 'manual switching keeps real files installed as slave links');
+remove_choice(1);
+ok(!-l $main_link, 'auto switching preserves files that should be links');
+ok(!-l "$bindir/slave2", 'auto switching preserves files that should be slave links');
+ok(-f $main_link, 'auto switching keeps real file installed as master link');
+ok(-f "$bindir/slave2", 'auto switching keeps real files installed as slave links');
+remove_all_choices(params => ['--force']);
+ok(!-e "$bindir/slave2", 'forced removeall drops real files installed as slave links');
+
+# test management of pre-existing files #3
+cleanup();
+system("touch $main_link $bindir/slave2");
+install_choice(0);
+install_choice(1);
+remove_choice(0);
+ok(!-l $main_link, 'removal + switching preserves files that should be links');
+ok(!-l "$bindir/slave2", 'removal + switching preserves files that should be slave links');
+ok(-f $main_link, 'removal + switching keeps real file installed as master link');
+ok(-f "$bindir/slave2", 'removal + switching keeps real files installed as slave links');
+install_choice(0);
+ok(!-l $main_link, 'install + switching preserves files that should be links');
+ok(!-l "$bindir/slave2", 'install + switching preserves files that should be slave links');
+ok(-f $main_link, 'install + switching keeps real file installed as master link');
+ok(-f "$bindir/slave2", 'install + switching keeps real files installed as slave links');
+set_choice(1, params => ['--force']);
+ok(!-e "$bindir/slave2", 'forced switching w/o slave drops real files installed as slave links');
+check_choice(1, 'manual', 'set --force replaces files with links');
+
+# check disappearence of obsolete slaves (#916799)
+cleanup();
+call_ua([
+ '--install', "$bindir/test-obsolete", 'test-obsolete', "$paths{date}", '10',
+ '--slave', "$bindir/test-slave-a", 'test-slave-a', "$bindir/impl-slave-a",
+ '--slave', "$bindir/test-slave-b", 'test-slave-b', "$bindir/impl-slave-b",
+ '--slave', "$bindir/test-slave-c", 'test-slave-c', "$bindir/impl-slave-c",
+], to_file => '/dev/null', error_to_file => '/dev/null');
+
+my $content;
+my $expected;
+
+$content = file_slurp("$admindir/test-obsolete");
+$expected =
+"auto
+$bindir/test-obsolete
+test-slave-a
+$bindir/test-slave-a
+test-slave-b
+$bindir/test-slave-b
+test-slave-c
+$bindir/test-slave-c
+
+$paths{date}
+10
+$bindir/impl-slave-a
+$bindir/impl-slave-b
+$bindir/impl-slave-c
+
+";
+is($content, $expected, 'administrative file for non-obsolete slaves is as expected');
+
+call_ua([
+ '--install', "$bindir/test-obsolete", 'test-obsolete', "$paths{date}", '20',
+ '--slave', "$bindir/test-slave-c", 'test-slave-c', "$bindir/impl-slave-c",
+], to_file => '/dev/null', error_to_file => '/dev/null');
+
+$content = file_slurp("$admindir/test-obsolete");
+$expected =
+"auto
+$bindir/test-obsolete
+test-slave-c
+$bindir/test-slave-c
+
+$paths{date}
+20
+$bindir/impl-slave-c
+
+";
+is($content, $expected, 'administrative file for obsolete slaves is as expected');
diff --git a/utils/update-alternatives.c b/utils/update-alternatives.c
new file mode 100644
index 0000000..7d21a4d
--- /dev/null
+++ b/utils/update-alternatives.c
@@ -0,0 +1,3225 @@
+/*
+ * update-alternatives
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2000-2002 Wichert Akkerman <wakkerma@debian.org>
+ * Copyright © 2006-2017 Guillem Jover <guillem@debian.org>
+ * Copyright © 2008 Pierre Habouzit <madcoder@debian.org>
+ * Copyright © 2009-2010 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/time.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <dirent.h>
+#include <time.h>
+#include <setjmp.h>
+#include <assert.h>
+#include <locale.h>
+#include <ctype.h>
+#include <limits.h>
+
+#include <dpkg/macros.h>
+#include <dpkg/i18n.h>
+
+/* Global variables: */
+
+#define PROGNAME "update-alternatives"
+
+static const char *altdir = SYSCONFDIR "/alternatives";
+static char *admdir = NULL;
+static const char *instdir = "";
+static size_t instdir_len;
+
+static const char *prog_path = "update-alternatives";
+
+enum action {
+ ACTION_NONE,
+ ACTION_INSTALL,
+ ACTION_SET,
+ ACTION_SET_SELECTIONS,
+ ACTION_GET_SELECTIONS,
+ ACTION_AUTO,
+ ACTION_CONFIG,
+ ACTION_CONFIG_ALL,
+ ACTION_REMOVE,
+ ACTION_REMOVE_ALL,
+ ACTION_LIST,
+ ACTION_QUERY,
+ ACTION_DISPLAY,
+};
+
+static struct action_name {
+ enum action action;
+ const char *name;
+} action_names[] = {
+ { ACTION_NONE, "" },
+ { ACTION_INSTALL, "install" },
+ { ACTION_SET, "set" },
+ { ACTION_SET_SELECTIONS, "set-selections" },
+ { ACTION_GET_SELECTIONS, "get-selections" },
+ { ACTION_AUTO, "auto" },
+ { ACTION_CONFIG, "config" },
+ { ACTION_CONFIG_ALL, "all" },
+ { ACTION_REMOVE, "remove" },
+ { ACTION_REMOVE_ALL, "remove-all" },
+ { ACTION_LIST, "list" },
+ { ACTION_QUERY, "query" },
+ { ACTION_DISPLAY, "display" },
+};
+
+enum output_mode {
+ OUTPUT_QUIET = -1,
+ OUTPUT_NORMAL = 0,
+ OUTPUT_VERBOSE = 1,
+ OUTPUT_DEBUG = 2,
+};
+
+/* Action to perform */
+static enum action action = ACTION_NONE;
+static char *log_file = NULL;
+static FILE *fh_log = NULL;
+/* Skip alternatives properly configured in auto mode (for --config) */
+static int opt_skip_auto = 0;
+static int opt_verbose = OUTPUT_NORMAL;
+static int opt_force = 0;
+
+/*
+ * Functions.
+ */
+
+static void
+version(void)
+{
+ printf(_("%s version %s.\n"), PROGNAME, VERSION);
+ printf("\n");
+
+ printf(_(
+"This is free software; see the GNU General Public License version 2 or\n"
+"later for copying conditions. There is NO warranty.\n"));
+}
+
+static void
+usage(void)
+{
+ printf(_(
+"Usage: %s [<option> ...] <command>\n"
+"\n"), PROGNAME);
+
+ printf(_(
+"Commands:\n"
+" --install <link> <name> <path> <priority>\n"
+" [--slave <link> <name> <path>] ...\n"
+" add a group of alternatives to the system.\n"
+" --remove <name> <path> remove <path> from the <name> group alternative.\n"
+" --remove-all <name> remove <name> group from the alternatives system.\n"
+" --auto <name> switch the master link <name> to automatic mode.\n"
+" --display <name> display information about the <name> group.\n"
+" --query <name> machine parseable version of --display <name>.\n"
+" --list <name> display all targets of the <name> group.\n"
+" --get-selections list master alternative names and their status.\n"
+" --set-selections read alternative status from standard input.\n"
+" --config <name> show alternatives for the <name> group and ask the\n"
+" user to select which one to use.\n"
+" --set <name> <path> set <path> as alternative for <name>.\n"
+" --all call --config on all alternatives.\n"
+"\n"));
+
+ printf(_(
+"<link> is the symlink pointing to %s/<name>.\n"
+" (e.g. /usr/bin/pager)\n"
+"<name> is the master name for this link group.\n"
+" (e.g. pager)\n"
+"<path> is the location of one of the alternative target files.\n"
+" (e.g. /usr/bin/less)\n"
+"<priority> is an integer; options with higher numbers have higher priority in\n"
+" automatic mode.\n"
+"\n"), altdir);
+
+ printf(_(
+"Options:\n"
+" --altdir <directory> change the alternatives directory\n"
+" (default is %s).\n"
+" --admindir <directory> change the administrative directory\n"
+" (default is %s).\n"
+" --instdir <directory> change the installation directory.\n"
+" --root <directory> change the filesystem root directory.\n"
+" --log <file> change the log file.\n"
+" --force allow replacing files with alternative links.\n"
+" --skip-auto skip prompt for alternatives correctly configured\n"
+" in automatic mode (relevant for --config only)\n"
+" --quiet quiet operation, minimal output.\n"
+" --verbose verbose operation, more output.\n"
+" --debug debug output, way more output.\n"
+" --help show this help message.\n"
+" --version show the version.\n"
+), altdir, admdir);
+}
+
+static void LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_PRINTF(1)
+error(char const *fmt, ...)
+{
+ va_list args;
+
+ fprintf(stderr, "%s: %s: ", PROGNAME, _("error"));
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+ fprintf(stderr, "\n");
+ exit(2);
+}
+
+static void LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_PRINTF(1)
+syserr(char const *fmt, ...)
+{
+ va_list args;
+
+ fprintf(stderr, "%s: %s: ", PROGNAME, _("error"));
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+ fprintf(stderr, ": %s\n", strerror(errno));
+ exit(2);
+}
+
+static void LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_PRINTF(1)
+badusage(char const *fmt, ...)
+{
+ va_list args;
+
+ fprintf(stderr, "%s: ", PROGNAME);
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+ fprintf(stderr, "\n\n");
+ fprintf(stderr, _("Use '%s --help' for program usage information."),
+ PROGNAME);
+ fprintf(stderr, "\n");
+ exit(2);
+}
+
+static void LIBCOMPAT_ATTR_PRINTF(1)
+warning(char const *fmt, ...)
+{
+ va_list args;
+
+ if (opt_verbose < OUTPUT_NORMAL)
+ return;
+
+ fprintf(stderr, "%s: %s: ", PROGNAME, _("warning"));
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+ fprintf(stderr, "\n");
+}
+
+static void LIBCOMPAT_ATTR_PRINTF(1)
+debug(char const *fmt, ...)
+{
+ va_list args;
+
+ if (opt_verbose < OUTPUT_DEBUG)
+ return;
+
+ fprintf(stderr, "DEBUG: ");
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+ fprintf(stderr, "\n");
+}
+
+static void LIBCOMPAT_ATTR_PRINTF(1)
+verbose(char const *fmt, ...)
+{
+ va_list args;
+
+ if (opt_verbose < OUTPUT_VERBOSE)
+ return;
+
+ printf("%s: ", PROGNAME);
+ va_start(args, fmt);
+ vprintf(fmt, args);
+ va_end(args);
+ printf("\n");
+}
+
+static void LIBCOMPAT_ATTR_PRINTF(1)
+info(char const *fmt, ...)
+{
+ va_list args;
+
+ if (opt_verbose < OUTPUT_NORMAL)
+ return;
+
+ printf("%s: ", PROGNAME);
+ va_start(args, fmt);
+ vprintf(fmt, args);
+ va_end(args);
+ printf("\n");
+}
+
+static void LIBCOMPAT_ATTR_PRINTF(1)
+pr(char const *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ vprintf(fmt, args);
+ va_end(args);
+ printf("\n");
+}
+
+static void *
+xmalloc(size_t size)
+{
+ void *ptr;
+
+ ptr = malloc(size);
+ if (!ptr)
+ error(_("malloc failed (%zu bytes)"), size);
+
+ return ptr;
+}
+
+static char *
+xstrdup(const char *str)
+{
+ char *new_str;
+
+ if (!str)
+ return NULL;
+
+ new_str = strdup(str);
+ if (!new_str)
+ error(_("failed to allocate memory"));
+
+ return new_str;
+}
+
+static char *
+xstrndup(const char *str, size_t n)
+{
+ char *new_str;
+
+ if (!str)
+ return NULL;
+
+ new_str = strndup(str, n);
+ if (!new_str)
+ error(_("failed to allocate memory"));
+
+ return new_str;
+}
+
+static char * LIBCOMPAT_ATTR_VPRINTF(1)
+xvasprintf(const char *fmt, va_list args)
+{
+ char *str;
+
+ if (vasprintf(&str, fmt, args) < 0)
+ error(_("failed to allocate memory"));
+
+ return str;
+}
+
+static char * LIBCOMPAT_ATTR_PRINTF(1)
+xasprintf(const char *fmt, ...)
+{
+ va_list args;
+ char *str;
+
+ va_start(args, fmt);
+ str = xvasprintf(fmt, args);
+ va_end(args);
+
+ return str;
+}
+
+static char *
+areadlink(const char *linkname)
+{
+ struct stat st;
+ char *buf;
+ ssize_t size;
+
+ /* Allocate required memory to store the value of the symlink */
+ if (lstat(linkname, &st))
+ return NULL;
+
+ if (!S_ISLNK(st.st_mode)) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ buf = xmalloc(st.st_size + 1);
+
+ /* Read it and terminate the string properly */
+ size = readlink(linkname, buf, st.st_size);
+ if (size == -1) {
+ int saved_errno = errno;
+
+ free(buf);
+ errno = saved_errno;
+
+ return NULL;
+ }
+ buf[size] = '\0';
+
+ return buf;
+}
+
+static int
+spawn(const char *prog, const char *args[])
+{
+ pid_t pid, dead_pid;
+ int status;
+
+ pid = fork();
+ if (pid == -1)
+ error(_("fork failed"));
+ if (pid == 0) {
+ execvp(prog, (char *const *)args);
+ syserr(_("unable to execute %s (%s)"), prog, prog);
+ }
+ while ((dead_pid = waitpid(pid, &status, 0)) == -1 && errno == EINTR) ;
+ if (dead_pid != pid)
+ error(_("wait for subprocess %s failed"), prog);
+
+ return status;
+}
+
+static bool
+rename_mv(const char *src, const char *dst)
+{
+ const char *args[] = { "mv", src, dst, NULL };
+ int rc;
+
+ if (rename(src, dst) == 0)
+ return true;
+ if (errno == ENOENT)
+ return false;
+
+ rc = spawn("mv", args);
+ if (WIFEXITED(rc) && WEXITSTATUS(rc) == 0)
+ return true;
+
+ return false;
+}
+
+static void
+xrename(const char *src, const char *dst)
+{
+ if (!rename_mv(src, dst))
+ syserr(_("unable to install '%.250s' as '%.250s'"), src, dst);
+}
+
+static void LIBCOMPAT_ATTR_PRINTF(1)
+xunlink_args(const char *fmt, ...)
+{
+ va_list args;
+ char *path;
+
+ va_start(args, fmt);
+ path = xvasprintf(fmt, args);
+ va_end(args);
+
+ if (unlink(path) < 0 && errno != ENOENT)
+ syserr(_("unable to remove '%s'"), path);
+
+ free(path);
+}
+
+static char *
+xdirname(const char *pathname)
+{
+ char *dirname, *slash;
+
+ slash = strrchr(pathname, '/');
+ if (slash)
+ dirname = xstrndup(pathname, slash - pathname);
+ else
+ dirname = xstrdup(".");
+
+ return dirname;
+}
+
+static int
+make_path(const char *pathname, mode_t mode)
+{
+ char *dirname, *slash;
+
+ dirname = xstrdup(pathname);
+
+ /* Find the first slash, and ignore it, as it will be either the
+ * slash for the root directory, for the current directory in a
+ * relative pathname or its parent. */
+ slash = strchr(dirname, '/');
+
+ while (slash != NULL) {
+ slash = strchr(slash + 1, '/');
+ if (slash)
+ *slash = '\0';
+
+ if (mkdir(dirname, mode) < 0 && errno != EEXIST) {
+ free(dirname);
+ return -1;
+ }
+ if (slash)
+ *slash = '/';
+ }
+
+ free(dirname);
+
+ return 0;
+}
+
+static void LIBCOMPAT_ATTR_PRINTF(1)
+log_msg(const char *fmt, ...)
+{
+ if (fh_log == NULL) {
+ fh_log = fopen(log_file, "a");
+ if (fh_log == NULL && errno == ENOENT) {
+ char *log_dir = xdirname(log_file);
+
+ if (make_path(log_dir, 0755) < 0)
+ syserr(_("cannot create log directory '%s'"),
+ log_dir);
+ free(log_dir);
+
+ fh_log = fopen(log_file, "a");
+ }
+ if (fh_log == NULL && errno != EACCES)
+ syserr(_("cannot append to '%s'"), log_file);
+ }
+
+ if (fh_log) {
+ va_list args;
+ char timestamp[64];
+ time_t now;
+ struct tm tm;
+
+ time(&now);
+ if (localtime_r(&now, &tm) == NULL)
+ syserr(_("cannot get local time to log into '%s'"), log_file);
+ strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S",
+ &tm);
+ fprintf(fh_log, "%s %s: ", PROGNAME, timestamp);
+ va_start(args, fmt);
+ vfprintf(fh_log, fmt, args);
+ va_end(args);
+ fprintf(fh_log, "\n");
+ }
+}
+
+/*
+ * Filesystem access for alternative handling.
+ */
+
+static char *
+fsys_get_path(const char *pathpart)
+{
+ return xasprintf("%s%s", instdir, pathpart);
+}
+
+static const char *
+fsys_set_dir(const char *dir)
+{
+ if (dir == NULL) {
+ const char *instdir_env;
+
+ instdir_env = getenv(INSTDIR_ENVVAR);
+ if (instdir_env)
+ dir = instdir_env;
+ else
+ dir = "";
+ }
+
+ instdir_len = strlen(dir);
+
+ return dir;
+}
+
+static char *
+fsys_gen_admindir(void)
+{
+ return fsys_get_path(ADMINDIR "/alternatives");
+}
+
+static bool
+fsys_pathname_is_missing(const char *pathname)
+{
+ struct stat st;
+ char *root_pathname;
+
+ root_pathname = fsys_get_path(pathname);
+
+ errno = 0;
+ if (stat(root_pathname, &st) < 0 && errno != ENOENT)
+ syserr(_("cannot stat file '%s'"), root_pathname);
+
+ free(root_pathname);
+
+ if (errno == ENOENT)
+ return true;
+
+ return false;
+}
+
+static int
+fsys_lstat(const char *linkname, struct stat *st)
+{
+ char *root_linkname;
+ int rc;
+
+ root_linkname = fsys_get_path(linkname);
+
+ errno = 0;
+ rc = lstat(root_linkname, st);
+
+ free(root_linkname);
+
+ return rc;
+}
+
+static char *
+fsys_areadlink(const char *linkname)
+{
+ char *root_linkname;
+ char *target;
+
+ root_linkname = fsys_get_path(linkname);
+ target = areadlink(root_linkname);
+ free(root_linkname);
+
+ return target;
+}
+
+static char *
+fsys_xreadlink(const char *linkname)
+{
+ char *buf;
+
+ buf = fsys_areadlink(linkname);
+ if (buf == NULL)
+ syserr(_("unable to read link '%s%.255s'"), instdir, linkname);
+
+ return buf;
+}
+
+static void
+fsys_set_ref_time(const char *linkname, const char *target)
+{
+#ifdef HAVE_LUTIMES
+ /* If the symlink did not exist, then copy the timestamps
+ * from the target. This is needed so we can get reproducible
+ * installations, for programs that track these timestamps on
+ * their databases. */
+ struct stat st;
+ struct timeval tv[2];
+ char *root_linkname;
+
+ if (fsys_lstat(target, &st) < 0) {
+ if (errno != ENOENT)
+ syserr(_("unable to get file '%s%s' metadata"),
+ instdir, target);
+ return;
+ }
+
+ tv[0].tv_sec = st.st_mtime;
+ tv[0].tv_usec = 0;
+ tv[1].tv_sec = st.st_mtime;
+ tv[1].tv_usec = 0;
+
+ root_linkname = fsys_get_path(linkname);
+ if (lutimes(root_linkname, tv) < 0 && errno != ENOSYS)
+ syserr(_("cannot set symlink '%s' timestamp"), root_linkname);
+ free(root_linkname);
+#endif
+}
+
+static void
+fsys_symlink(const char *filename, const char *linkname)
+{
+ char *root_linkname;
+
+ root_linkname = fsys_get_path(linkname);
+
+ if (unlink(root_linkname) < 0 && errno != ENOENT)
+ syserr(_("unable to remove '%s'"), root_linkname);
+
+ if (symlink(filename, root_linkname))
+ syserr(_("error creating symbolic link '%.255s'"), root_linkname);
+
+ free(root_linkname);
+}
+
+static void
+fsys_mv(const char *src, const char *dst)
+{
+ char *root_src;
+ char *root_dst;
+
+ root_src = fsys_get_path(src);
+ root_dst = fsys_get_path(dst);
+
+ xrename(root_src, root_dst);
+
+ free(root_src);
+ free(root_dst);
+}
+
+static void
+fsys_rm(const char *f)
+{
+ char *root_f;
+
+ root_f = fsys_get_path(f);
+
+ if (unlink(root_f) < 0 && errno != ENOENT)
+ syserr(_("unable to remove '%s'"), root_f);
+
+ free(root_f);
+}
+
+static void LIBCOMPAT_ATTR_PRINTF(1)
+fsys_rm_args(const char *fmt, ...)
+{
+ va_list args;
+ char *path;
+
+ va_start(args, fmt);
+ path = xvasprintf(fmt, args);
+ va_end(args);
+
+ fsys_rm(path);
+ free(path);
+}
+
+/*
+ * OBJECTS
+ */
+
+struct fileset {
+ struct fileset *next;
+
+ char *master_file;
+ int priority;
+
+ struct slave_file {
+ struct slave_file *next;
+ char *name;
+ char *file;
+ } *slaves;
+};
+
+static struct fileset *
+fileset_new(const char *master_file, int prio)
+{
+ struct fileset *fs;
+
+ fs = xmalloc(sizeof(*fs));
+ fs->next = NULL;
+ fs->master_file = xstrdup(master_file);
+ fs->priority = prio;
+ fs->slaves = NULL;
+
+ return fs;
+}
+
+static void
+fileset_free(struct fileset *fs)
+{
+ struct slave_file *slave, *next;
+
+ free(fs->master_file);
+ for (slave = fs->slaves; slave; slave = next) {
+ next = slave->next;
+ free(slave->name);
+ free(slave->file);
+ free(slave);
+ }
+ free(fs);
+}
+
+static void
+fileset_add_slave(struct fileset *fs, const char *name, const char *file)
+{
+ struct slave_file *sl, *cur, *prev = NULL;
+
+ /* Replace existing first */
+ for (cur = fs->slaves; cur; cur = cur->next) {
+ if (strcmp(cur->name, name) == 0) {
+ free(cur->file);
+ cur->file = xstrdup(file);
+ return;
+ }
+ prev = cur;
+ }
+
+ /* Otherwise add new at the end */
+ sl = xmalloc(sizeof(*sl));
+ sl->next = NULL;
+ sl->name = xstrdup(name);
+ sl->file = xstrdup(file);
+ if (prev)
+ prev->next = sl;
+ else
+ fs->slaves = sl;
+}
+
+static const char *
+fileset_get_slave(struct fileset *fs, const char *name)
+{
+ struct slave_file *slave;
+
+ for (slave = fs->slaves; slave; slave = slave->next) {
+ if (strcmp(slave->name, name) == 0)
+ return slave->file;
+ }
+
+ return NULL;
+}
+
+static bool
+fileset_has_slave(struct fileset *fs, const char *name)
+{
+ const char *file = fileset_get_slave(fs, name);
+
+ if (file == NULL)
+ return false;
+
+ return file[0] != '\0';
+}
+
+static bool
+fileset_can_install_slave(struct fileset *fs, const char *slave_name)
+{
+ /* Decide whether the slave alternative must be setup */
+ if (fileset_has_slave(fs, slave_name)) {
+ const char *slave = fileset_get_slave(fs, slave_name);
+
+ if (!fsys_pathname_is_missing(slave))
+ return true;
+ }
+
+ return false;
+}
+
+struct slave_link {
+ struct slave_link *next;
+ char *name;
+ char *link;
+ bool updated;
+};
+
+struct commit_operation {
+ struct commit_operation *next;
+
+ enum opcode {
+ OPCODE_NOP,
+ OPCODE_RM,
+ OPCODE_MV,
+ OPCODE_REF_TIME,
+ } opcode;
+
+ char *arg_a;
+ char *arg_b;
+};
+
+enum alternative_update_reason {
+ ALT_UPDATE_NO,
+ ALT_UPDATE_SLAVE_CHANGED,
+ ALT_UPDATE_LINK_BROKEN,
+};
+
+struct alternative {
+ char *master_name;
+ char *master_link;
+ char *current;
+
+ enum alternative_status {
+ ALT_ST_UNKNOWN,
+ ALT_ST_AUTO,
+ ALT_ST_MANUAL,
+ } status;
+
+ struct slave_link *slaves;
+ struct fileset *choices;
+
+ struct commit_operation *commit_ops;
+
+ int ref_count;
+ bool modified;
+ bool known_current;
+};
+
+static void
+slave_link_free(struct slave_link *slave)
+{
+ free(slave->name);
+ free(slave->link);
+ free(slave);
+}
+
+static void
+commit_operation_free(struct commit_operation *commit_op)
+{
+ free(commit_op->arg_a);
+ free(commit_op->arg_b);
+ free(commit_op);
+}
+
+static struct alternative *
+alternative_new(const char *name)
+{
+ struct alternative *alt;
+
+ alt = xmalloc(sizeof(*alt));
+ alt->master_name = xstrdup(name);
+ alt->master_link = NULL;
+ alt->current = NULL;
+ alt->status = ALT_ST_UNKNOWN;
+ alt->slaves = NULL;
+ alt->choices = NULL;
+ alt->commit_ops = NULL;
+ alt->modified = false;
+ alt->known_current = false;
+ alt->ref_count = 1;
+
+ return alt;
+}
+
+static inline void
+alternative_ref(struct alternative *a)
+{
+ a->ref_count++;
+}
+
+static inline bool
+alternative_unref(struct alternative *a)
+{
+ return --a->ref_count == 0;
+}
+
+static void
+alternative_choices_free(struct alternative *a)
+{
+ struct fileset *fs;
+
+ if (a->choices)
+ a->modified = true;
+
+ while (a->choices) {
+ fs = a->choices;
+ a->choices = fs->next;
+ fileset_free(fs);
+ }
+}
+
+static void
+alternative_commit_operations_free(struct alternative *a)
+{
+ struct commit_operation *op;
+
+ while (a->commit_ops) {
+ op = a->commit_ops;
+ a->commit_ops = op->next;
+ commit_operation_free(op);
+ }
+}
+
+static void
+alternative_reset(struct alternative *alt)
+{
+ struct slave_link *slave;
+
+ free(alt->current);
+ alt->current = NULL;
+ free(alt->master_link);
+ alt->master_link = NULL;
+ while (alt->slaves) {
+ slave = alt->slaves;
+ alt->slaves = slave->next;
+ slave_link_free(slave);
+ }
+ alternative_choices_free(alt);
+ alternative_commit_operations_free(alt);
+ alt->modified = false;
+ alt->known_current = false;
+}
+
+static void
+alternative_free(struct alternative *alt)
+{
+ if (!alternative_unref(alt))
+ return;
+
+ alternative_reset(alt);
+ free(alt->master_name);
+ free(alt);
+}
+
+static int
+alternative_choices_count(struct alternative *alt)
+{
+ struct fileset *fs;
+ int count = 0;
+
+ for (fs = alt->choices; fs; fs = fs->next)
+ count++;
+
+ return count;
+}
+
+static int
+alternative_slaves_count(struct alternative *alt)
+{
+ struct slave_link *sl;
+ int count = 0;
+
+ for (sl = alt->slaves; sl; sl = sl->next)
+ count++;
+
+ return count;
+}
+
+static int
+compare_fileset(const void *va, const void *vb)
+{
+ const struct fileset *a = *(const struct fileset **)va;
+ const struct fileset *b = *(const struct fileset **)vb;
+
+ assert(a && a->master_file);
+ assert(b && b->master_file);
+
+ return strcmp(a->master_file, b->master_file);
+}
+
+static int
+compare_slave_link(const void *va, const void *vb)
+{
+ const struct slave_link *a = *(const struct slave_link **)va;
+ const struct slave_link *b = *(const struct slave_link **)vb;
+
+ assert(a && a->name);
+ assert(b && b->name);
+
+ return strcmp(a->name, b->name);
+}
+
+static void
+alternative_sort_choices(struct alternative *a)
+{
+ int count, i;
+ struct fileset **table, *fs;
+
+ count = alternative_choices_count(a);
+ if (count < 2) /* Nothing to sort */
+ return;
+
+ /* Store objects in a table instead of a linked list */
+ table = xmalloc(sizeof(fs) * count);
+ for (fs = a->choices, i = 0; fs; fs = fs->next) {
+ assert(fs->master_file);
+ table[i++] = fs;
+ }
+
+ qsort(table, count, sizeof(fs), compare_fileset);
+
+ /* Rewrite the linked list from the sorted table */
+ a->choices = fs = table[0];
+ table[count - 1]->next = NULL;
+ for (i = 1; i < count; fs = fs->next, i++)
+ fs->next = table[i];
+ free(table);
+}
+
+static void
+alternative_sort_slaves(struct alternative *a)
+{
+ int count, i;
+ struct slave_link **table, *sl;
+
+ count = alternative_slaves_count(a);
+ if (count < 2) /* Nothing to sort */
+ return;
+
+ /* Store objects in a table instead of a linked list */
+ table = xmalloc(sizeof(sl) * count);
+ for (sl = a->slaves, i = 0; sl; sl = sl->next, i++) {
+ table[i] = sl;
+ }
+
+ qsort(table, count, sizeof(sl), compare_slave_link);
+
+ /* Rewrite the linked list from the sorted table */
+ a->slaves = sl = table[0];
+ table[count - 1]->next = NULL;
+ for (i = 1; i < count; sl = sl->next, i++)
+ sl->next = table[i];
+ free(table);
+}
+
+static struct fileset *
+alternative_get_fileset(struct alternative *a, const char *file)
+{
+ struct fileset *fs;
+
+ for (fs = a->choices; fs; fs = fs->next)
+ if (strcmp(fs->master_file, file) == 0)
+ return fs;
+
+ return NULL;
+}
+
+static struct slave_link *
+alternative_get_slave(struct alternative *a, const char *name)
+{
+ struct slave_link *sl;
+
+ for (sl = a->slaves; sl; sl = sl->next)
+ if (strcmp(sl->name, name) == 0)
+ return sl;
+
+ return NULL;
+}
+
+static bool
+alternative_has_slave(struct alternative *a, const char *name)
+{
+ return alternative_get_slave(a, name) != NULL;
+}
+
+static bool
+alternative_has_choice(struct alternative *a, const char *file)
+{
+ return alternative_get_fileset(a, file) != NULL;
+}
+
+static void
+alternative_add_choice(struct alternative *a, struct fileset *fs)
+{
+ struct fileset *cur, *prev = NULL;
+
+ /* Replace if already existing */
+ for (cur = a->choices; cur; cur = cur->next) {
+ if (strcmp(cur->master_file, fs->master_file) == 0) {
+ fs->next = cur->next;
+ fileset_free(cur);
+ if (prev)
+ prev->next = fs;
+ else
+ a->choices = fs;
+
+ /* XXX: Be smarter in detecting change? */
+ a->modified = true;
+ return;
+ }
+ prev = cur;
+ }
+
+ /* Otherwise add at the end */
+ if (prev == NULL)
+ a->choices = fs;
+ else
+ prev->next = fs;
+ fs->next = NULL;
+ a->modified = true;
+}
+
+static struct slave_link *
+alternative_add_slave(struct alternative *a,
+ const char *slave_name, const char *slave_link)
+{
+ struct slave_link *sl, *new;
+
+ /* Replace if already existing */
+ for (sl = a->slaves; sl; sl = sl->next) {
+ if (strcmp(sl->name, slave_name) == 0) {
+ free(sl->link);
+ sl->link = xstrdup(slave_link);
+ return sl;
+ }
+ if (sl->next == NULL)
+ break;
+ }
+
+ /* Otherwise create new and add at the end */
+ new = xmalloc(sizeof(*new));
+ new->name = xstrdup(slave_name);
+ new->link = xstrdup(slave_link);
+ new->updated = false;
+ new->next = NULL;
+ if (sl)
+ sl->next = new;
+ else
+ a->slaves = new;
+
+ return new;
+}
+
+static void
+alternative_copy_slave(struct alternative *a, struct slave_link *sl)
+{
+ struct slave_link *sl_new;
+
+ sl_new = alternative_add_slave(a, sl->name, sl->link);
+ sl_new->updated = sl->updated;
+}
+
+static const char *
+alternative_status_string(enum alternative_status status)
+{
+ return (status == ALT_ST_AUTO) ? "auto" : "manual";
+}
+
+static const char *
+alternative_status_describe(enum alternative_status status)
+{
+ return (status == ALT_ST_AUTO) ? _("auto mode") : _("manual mode");
+}
+
+static void
+alternative_set_status(struct alternative *a, enum alternative_status status)
+{
+ if (a->status == ALT_ST_UNKNOWN || status != a->status)
+ a->modified = true;
+
+ if (a->status != ALT_ST_UNKNOWN && status != a->status)
+ log_msg("status of link group %s set to %s", a->master_link,
+ alternative_status_string(status));
+
+ a->status = status;
+}
+
+static void
+alternative_set_link(struct alternative *a, const char *linkname)
+{
+ if (a->master_link == NULL || strcmp(linkname, a->master_link) != 0)
+ a->modified = true;
+
+ free(a->master_link);
+ a->master_link = xstrdup(linkname);
+}
+
+static bool
+alternative_remove_choice(struct alternative *a, const char *file)
+{
+ struct fileset *fs, *fs_prev;
+
+ fs_prev = NULL;
+ for (fs = a->choices; fs; fs = fs->next) {
+ if (strcmp(fs->master_file, file) != 0) {
+ fs_prev = fs;
+ continue;
+ }
+ if (fs_prev)
+ fs_prev->next = fs->next;
+ else
+ a->choices = fs->next;
+ fileset_free(fs);
+ a->modified = true;
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Alternatives Database Load/Store functions.
+ */
+
+enum LIBCOMPAT_ATTR_ENUM_FLAGS altdb_flags {
+ ALTDB_LAX_PARSER = 1 << 0,
+ ALTDB_WARN_PARSER = 1 << 1,
+};
+
+struct altdb_context {
+ FILE *fh;
+ char *filename;
+ enum altdb_flags flags;
+ bool modified;
+ void LIBCOMPAT_ATTR_NORET
+ (*bad_format)(struct altdb_context *, const char *msg);
+ jmp_buf on_error;
+};
+
+static void
+altdb_context_free(struct altdb_context *ctx)
+{
+ if (ctx->fh)
+ fclose(ctx->fh);
+ free(ctx->filename);
+}
+
+static void LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_PRINTF(2)
+altdb_bad_format(struct altdb_context *ctx, const char *format, ...)
+{
+ va_list args;
+ char *msg;
+
+ va_start(args, format);
+ msg = xvasprintf(format, args);
+ va_end(args);
+
+ ctx->bad_format(ctx, msg);
+
+ /* This cannot happen, but just to make sure the bad_format() function
+ * pointer is well implemented. */
+ error("(internal) non returning bad-format function returned");
+}
+
+static int
+altdb_filter_namelist(const struct dirent *entry)
+{
+ if (strcmp(entry->d_name, ".") == 0 ||
+ strcmp(entry->d_name, "..") == 0 ||
+ (strlen(entry->d_name) > strlen(ALT_TMP_EXT) &&
+ strcmp(entry->d_name + strlen(entry->d_name) -
+ strlen(ALT_TMP_EXT), ALT_TMP_EXT) == 0))
+ return 0;
+ return 1;
+}
+
+static int
+altdb_get_namelist(struct dirent ***table)
+{
+ int count;
+
+ count = scandir(admdir, table, altdb_filter_namelist, alphasort);
+ if (count < 0) {
+ if (errno != ENOENT)
+ syserr(_("cannot scan directory '%.255s'"), admdir);
+ /* The directory does not exist, proceed anyway. */
+ *table = NULL;
+ count = 0;
+ }
+
+ return count;
+}
+
+static void
+altdb_free_namelist(struct dirent **table, int n)
+{
+ while (n--)
+ free(table[n]);
+ free(table);
+}
+
+static char *
+altdb_get_line(struct altdb_context *ctx, const char *name)
+{
+ char *buf, *line;
+ size_t len, bufsz, i;
+
+ bufsz = 1024;
+ buf = xmalloc(bufsz);
+
+ for (i = 0; true; i += strlen(line)) {
+ errno = 0;
+ line = fgets(buf + i, bufsz - i, ctx->fh);
+ if (line) {
+ if (strlen(buf) < bufsz - 1 || buf[bufsz - 2] == '\n')
+ break;
+ /* Need more space */
+ bufsz *= 2;
+ buf = realloc(buf, bufsz);
+ if (!buf)
+ error(_("failed to allocate memory"));
+ continue;
+ }
+ if (feof(ctx->fh))
+ altdb_bad_format(ctx, _("unexpected end of file while trying "
+ "to read %s"), name);
+ altdb_bad_format(ctx, _("while reading %s: %s"),
+ name, strerror(errno));
+ }
+
+ len = strlen(buf);
+ if (len == 0 || buf[len - 1] != '\n') {
+ altdb_bad_format(ctx, _("line not terminated while trying "
+ "to read %s"), name);
+ }
+ line[len - 1] = '\0';
+
+ return buf;
+}
+
+static void LIBCOMPAT_ATTR_NORET
+altdb_parse_error(struct altdb_context *ctx, const char *msg)
+{
+ error(_("%s corrupt: %s"), ctx->filename, msg);
+}
+
+static void LIBCOMPAT_ATTR_NORET
+altdb_parse_stop(struct altdb_context *ctx, const char *msg)
+{
+ longjmp(ctx->on_error, 1);
+}
+
+static void
+altdb_print_line(struct altdb_context *ctx, const char *line)
+{
+ if (strchr(line, '\n') != NULL)
+ error(_("newlines prohibited in update-alternatives files (%s)"),
+ line);
+
+ if (fprintf(ctx->fh, "%s\n", line) < (int) strlen(line) + 1)
+ syserr(_("unable to write file '%s'"), ctx->filename);
+}
+
+static bool
+alternative_parse_slave(struct alternative *a, struct altdb_context *ctx)
+{
+ char *name, *linkname;
+ struct slave_link *sl;
+
+ name = altdb_get_line(ctx, _("slave name"));
+ if (!strlen(name)) { /* End of list */
+ free(name);
+ return false;
+ }
+ sl = alternative_get_slave(a, name);
+ if (sl) {
+ free(name);
+ altdb_bad_format(ctx, _("duplicate slave name %s"), sl->name);
+ }
+
+ linkname = altdb_get_line(ctx, _("slave link"));
+ if (strcmp(linkname, a->master_link) == 0) {
+ free(linkname);
+ free(name);
+ altdb_bad_format(ctx, _("slave link same as main link %s"),
+ a->master_link);
+ }
+ for (sl = a->slaves; sl; sl = sl->next) {
+ if (strcmp(linkname, sl->link) == 0) {
+ free(linkname);
+ free(name);
+ altdb_bad_format(ctx, _("duplicate slave link %s"),
+ sl->link);
+ }
+ }
+
+ alternative_add_slave(a, name, linkname);
+ free(linkname);
+ free(name);
+
+ return true;
+}
+
+static bool
+alternative_parse_fileset(struct alternative *a, struct altdb_context *ctx)
+{
+ struct fileset *fs;
+ struct slave_link *sl;
+ char *master_file;
+
+ master_file = altdb_get_line(ctx, _("master file"));
+ if (!strlen(master_file)) { /* End of list */
+ free(master_file);
+ return false;
+ }
+
+ fs = alternative_get_fileset(a, master_file);
+ if (fs)
+ altdb_bad_format(ctx, _("duplicate path %s"), master_file);
+
+ if (fsys_pathname_is_missing(master_file)) {
+ char *junk;
+
+ /* File not found - remove. */
+ if (ctx->flags & ALTDB_WARN_PARSER)
+ warning(_("alternative %s (part of link group %s) "
+ "doesn't exist; removing from list of "
+ "alternatives"), master_file, a->master_name);
+ junk = altdb_get_line(ctx, _("priority"));
+ free(junk);
+ for (sl = a->slaves; sl; sl = sl->next) {
+ junk = altdb_get_line(ctx, _("slave file"));
+ free(junk);
+ }
+ ctx->modified = true;
+ } else {
+ char *prio_str, *prio_end;
+ long prio;
+
+ prio_str = altdb_get_line(ctx, _("priority"));
+ errno = 0;
+ prio = strtol(prio_str, &prio_end, 10);
+ /* XXX: Leak master_file/prio_str on non-fatal error */
+ if (prio_str == prio_end || *prio_end != '\0')
+ altdb_bad_format(ctx, _("priority of %s: %s"),
+ master_file, prio_str);
+ if (prio < INT_MIN || prio > INT_MAX || errno == ERANGE)
+ altdb_bad_format(ctx,
+ _("priority of %s is out of range: %s"),
+ master_file, prio_str);
+ free(prio_str);
+
+ fs = fileset_new(master_file, prio);
+ for (sl = a->slaves; sl; sl = sl->next) {
+ char *slave_file = altdb_get_line(ctx, _("slave file"));
+ fileset_add_slave(fs, sl->name, slave_file);
+ free(slave_file);
+ }
+ alternative_add_choice(a, fs);
+ }
+ free(master_file);
+
+ return true;
+}
+
+static bool
+alternative_load(struct alternative *a, enum altdb_flags flags)
+{
+ struct altdb_context ctx;
+ struct stat st;
+ char *status;
+ char *master_link;
+
+ /* Initialize parse context */
+ ctx.modified = false;
+ ctx.flags = flags;
+ if (flags & ALTDB_LAX_PARSER)
+ ctx.bad_format = altdb_parse_stop;
+ else
+ ctx.bad_format = altdb_parse_error;
+ ctx.filename = xasprintf("%s/%s", admdir, a->master_name);
+
+ /* Open the alternative file. */
+ ctx.fh = fopen(ctx.filename, "r");
+ if (ctx.fh == NULL) {
+ if (errno == ENOENT) {
+ altdb_context_free(&ctx);
+ return false;
+ }
+
+ syserr(_("unable to open file '%s'"), ctx.filename);
+ }
+
+ if (setjmp(ctx.on_error)) {
+ altdb_context_free(&ctx);
+ alternative_reset(a);
+ return false;
+ }
+
+ /* Verify the alternative is not empty. */
+ if (fstat(fileno(ctx.fh), &st) == -1)
+ syserr(_("cannot stat file '%s'"), ctx.filename);
+ if (st.st_size == 0) {
+ altdb_context_free(&ctx);
+ alternative_reset(a);
+ return false;
+ }
+
+ /* Start parsing mandatory attributes (link+status) of the alternative */
+ alternative_reset(a);
+ status = altdb_get_line(&ctx, _("status"));
+ if (strcmp(status, "auto") != 0 && strcmp(status, "manual") != 0)
+ altdb_bad_format(&ctx, _("invalid status"));
+ alternative_set_status(a, (strcmp(status, "auto") == 0) ?
+ ALT_ST_AUTO : ALT_ST_MANUAL);
+ free(status);
+
+ master_link = altdb_get_line(&ctx, _("master link"));
+ alternative_set_link(a, master_link);
+ free(master_link);
+
+ /* Parse the description of the slaves links of the alternative */
+ while (alternative_parse_slave(a, &ctx));
+
+ /* Parse the available choices in the alternative */
+ while (alternative_parse_fileset(a, &ctx)) ;
+
+ /* Close database file */
+ if (fclose(ctx.fh))
+ syserr(_("unable to close file '%s'"), ctx.filename);
+ free(ctx.filename);
+
+ /* Initialize the modified field which has been erroneously changed
+ * by the various alternative_(add|set)_* calls:
+ * false unless a choice has been auto-cleaned */
+ a->modified = ctx.modified;
+
+ return true;
+}
+
+static void
+alternative_save(struct alternative *a)
+{
+ struct altdb_context ctx;
+ struct slave_link *sl, *sl_prev;
+ struct fileset *fs;
+ char *filenew, *file;
+
+ /* Cleanup unused slaves before writing admin file. */
+ sl_prev = NULL;
+ sl = a->slaves;
+ while (sl) {
+ bool has_slave = false;
+
+ for (fs = a->choices; fs; fs = fs->next) {
+ if (fileset_has_slave(fs, sl->name)) {
+ has_slave = true;
+ break;
+ }
+ }
+
+ if (!has_slave) {
+ struct slave_link *sl_rm;
+
+ verbose(_("discarding obsolete slave link %s (%s)"),
+ sl->name, sl->link);
+ if (sl_prev)
+ sl_prev->next = sl->next;
+ else
+ a->slaves = sl->next;
+ sl_rm = sl;
+ sl = sl->next;
+ slave_link_free(sl_rm);
+ } else {
+ sl_prev = sl;
+ sl = sl->next;
+ }
+ }
+
+ /* Sort entries */
+ alternative_sort_slaves(a);
+ alternative_sort_choices(a);
+
+ /* Write admin file. */
+ file = xasprintf("%s/%s", admdir, a->master_name);
+ filenew = xasprintf("%s" ALT_TMP_EXT, file);
+
+ ctx.filename = filenew;
+ ctx.fh = fopen(ctx.filename, "w");
+ if (ctx.fh == NULL && errno == ENOENT) {
+ if (make_path(admdir, 0755) < 0)
+ syserr(_("cannot create administrative directory '%s'"),
+ admdir);
+ ctx.fh = fopen(ctx.filename, "w");
+ }
+ if (ctx.fh == NULL)
+ syserr(_("unable to create file '%s'"), ctx.filename);
+
+ altdb_print_line(&ctx, alternative_status_string(a->status));
+ altdb_print_line(&ctx, a->master_link);
+ for (sl = a->slaves; sl; sl = sl->next) {
+ altdb_print_line(&ctx, sl->name);
+ altdb_print_line(&ctx, sl->link);
+ }
+ altdb_print_line(&ctx, "");
+
+ for (fs = a->choices; fs; fs = fs->next) {
+ char *prio;
+
+ altdb_print_line(&ctx, fs->master_file);
+
+ prio = xasprintf("%d", fs->priority);
+ altdb_print_line(&ctx, prio);
+ free(prio);
+
+ for (sl = a->slaves; sl; sl = sl->next) {
+ if (fileset_has_slave(fs, sl->name))
+ altdb_print_line(&ctx,
+ fileset_get_slave(fs, sl->name));
+ else
+ altdb_print_line(&ctx, "");
+ }
+ }
+ altdb_print_line(&ctx, "");
+
+ /* Close database file */
+ if (fflush(ctx.fh))
+ syserr(_("unable to flush file '%s'"), ctx.filename);
+ if (fsync(fileno(ctx.fh)))
+ syserr(_("unable to sync file '%s'"), ctx.filename);
+ if (fclose(ctx.fh))
+ syserr(_("unable to close file '%s'"), ctx.filename);
+
+ /* Put in place atomically. */
+ xrename(filenew, file);
+
+ free(filenew);
+ free(file);
+}
+
+static const char *
+alternative_set_current(struct alternative *a, char *new_choice)
+{
+ a->known_current = true;
+ a->current = new_choice;
+
+ return new_choice;
+}
+
+static const char *
+alternative_get_current(struct alternative *a)
+{
+ char *curlink;
+ char *file;
+
+ if (a->known_current)
+ return a->current;
+
+ curlink = xasprintf("%s/%s", altdir, a->master_name);
+ file = fsys_areadlink(curlink);
+ if (file == NULL && errno != ENOENT)
+ syserr(_("cannot stat file '%s%s'"), instdir, curlink);
+ free(curlink);
+
+ return alternative_set_current(a, file);
+}
+
+static struct fileset *
+alternative_get_best(struct alternative *a)
+{
+ struct fileset *fs, *best;
+ const char *current;
+
+ current = alternative_get_current(a);
+ if (current)
+ best = alternative_get_fileset(a, current);
+ else
+ best = NULL;
+
+ if (best == NULL)
+ best = a->choices;
+
+ for (fs = a->choices; fs; fs = fs->next)
+ if (fs->priority > best->priority)
+ best = fs;
+
+ return best;
+}
+
+static void
+alternative_display_query(struct alternative *a)
+{
+ struct fileset *best, *fs;
+ struct slave_link *sl;
+ const char *current;
+
+ pr("Name: %s", a->master_name);
+ pr("Link: %s", a->master_link);
+ if (alternative_slaves_count(a) > 0) {
+ pr("Slaves:");
+ for (sl = a->slaves; sl; sl = sl->next)
+ pr(" %s %s", sl->name, sl->link);
+ }
+ pr("Status: %s", alternative_status_string(a->status));
+ best = alternative_get_best(a);
+ if (best)
+ pr("Best: %s", best->master_file);
+ current = alternative_get_current(a);
+ pr("Value: %s", current ? current : "none");
+
+ for (fs = a->choices; fs; fs = fs->next) {
+ printf("\n");
+ pr("Alternative: %s", fs->master_file);
+ pr("Priority: %d", fs->priority);
+ if (alternative_slaves_count(a) == 0)
+ continue;
+ pr("Slaves:");
+ for (sl = a->slaves; sl; sl = sl->next) {
+ if (fileset_has_slave(fs, sl->name))
+ pr(" %s %s", sl->name,
+ fileset_get_slave(fs, sl->name));
+ }
+ }
+}
+
+static void
+alternative_display_user(struct alternative *a)
+{
+ const char *current;
+ struct fileset *fs;
+ struct slave_link *sl;
+
+ pr("%s - %s", a->master_name, alternative_status_describe(a->status));
+ fs = alternative_get_best(a);
+ if (fs)
+ pr(_(" link best version is %s"), fs->master_file);
+ else
+ pr(_(" link best version not available"));
+ current = alternative_get_current(a);
+ if (current) {
+ pr(_(" link currently points to %s"), current);
+ } else {
+ pr(_(" link currently absent"));
+ }
+ pr(_(" link %s is %s"), a->master_name, a->master_link);
+ for (sl = a->slaves; sl; sl = sl->next)
+ pr(_(" slave %s is %s"), sl->name, sl->link);
+
+ for (fs = a->choices; fs; fs = fs->next) {
+ pr(_("%s - priority %d"), fs->master_file, fs->priority);
+ for (sl = a->slaves; sl; sl = sl->next) {
+ if (fileset_has_slave(fs, sl->name))
+ pr(_(" slave %s: %s"), sl->name,
+ fileset_get_slave(fs, sl->name));
+ }
+ }
+}
+
+static void
+alternative_display_list(struct alternative *a)
+{
+ struct fileset *fs;
+
+ for (fs = a->choices; fs; fs = fs->next)
+ pr("%s", fs->master_file);
+}
+
+static void
+alternative_print_choice(struct alternative *a, enum alternative_status status,
+ struct fileset *fs, int idx, int len)
+{
+ const char *current = alternative_get_current(a);
+ int mark;
+
+ if (a->status == status &&
+ current && strcmp(current, fs->master_file) == 0)
+ mark = '*';
+ else
+ mark = ' ';
+
+ pr("%c %-12d %-*s % -10d %s", mark, idx, len,
+ fs->master_file, fs->priority, alternative_status_describe(status));
+}
+
+static char *
+alternative_select_choice(struct alternative *a)
+{
+ const char *current;
+ char *ret, selection[_POSIX_PATH_MAX];
+ struct fileset *best, *fs;
+ int n_choices;
+ int len;
+
+ n_choices = alternative_choices_count(a);
+ current = alternative_get_current(a);
+ best = alternative_get_best(a);
+ assert(best);
+
+ len = 15;
+ for (fs = a->choices; fs; fs = fs->next)
+ len = max(len, (int)strlen(fs->master_file) + 1);
+
+ for (;;) {
+ int idx;
+
+ pr(P_("There is %d choice for the alternative %s (providing %s).",
+ "There are %d choices for the alternative %s (providing %s).",
+ n_choices), n_choices, a->master_name, a->master_link);
+ printf("\n");
+
+ pr(" %-12.12s %-*.*s %-10.10s %s", _("Selection"), len, len,
+ _("Path"), _("Priority"), _("Status"));
+ pr("------------------------------------------------------------");
+ idx = 0;
+ alternative_print_choice(a, ALT_ST_AUTO, best, idx++, len);
+ for (fs = a->choices; fs; fs = fs->next, idx++)
+ alternative_print_choice(a, ALT_ST_MANUAL, fs, idx, len);
+ printf("\n");
+ printf(_("Press <enter> to keep the current choice[*], "
+ "or type selection number: "));
+ ret = fgets(selection, sizeof(selection), stdin);
+ if (ret == NULL || strlen(selection) == 0) {
+ return NULL;
+ }
+ selection[strlen(selection) - 1] = '\0';
+ if (strlen(selection) == 0)
+ return xstrdup(current);
+ errno = 0;
+ idx = strtol(selection, &ret, 10);
+ if (idx >= 0 && errno == 0 && *ret == '\0') {
+ /* Look up by index */
+ if (idx == 0) {
+ alternative_set_status(a, ALT_ST_AUTO);
+ return xstrdup(best->master_file);
+ }
+ idx--;
+ for (fs = a->choices; idx && fs; idx--)
+ fs = fs->next;
+ if (fs) {
+ alternative_set_status(a, ALT_ST_MANUAL);
+ return xstrdup(fs->master_file);
+ }
+ } else {
+ /* Look up by name */
+ fs = alternative_get_fileset(a, selection);
+ if (fs) {
+ alternative_set_status(a, ALT_ST_MANUAL);
+ return xstrdup(selection);
+ }
+ }
+ }
+}
+
+static char *
+alternative_config(struct alternative *a, const char *current_choice)
+{
+ char *new_choice = NULL;
+
+ if (alternative_choices_count(a) == 0) {
+ pr(_("There is no program which provides %s."),
+ a->master_name);
+ pr(_("Nothing to configure."));
+ } else if (opt_skip_auto && a->status == ALT_ST_AUTO) {
+ alternative_display_user(a);
+ } else {
+ new_choice = alternative_select_choice(a);
+ }
+
+ return new_choice;
+}
+
+static void
+alternative_add_commit_op(struct alternative *a, enum opcode opcode,
+ const char *arg_a, const char *arg_b)
+{
+ struct commit_operation *op, *cur;
+
+ op = xmalloc(sizeof(*op));
+ op->opcode = opcode;
+ op->arg_a = xstrdup(arg_a);
+ op->arg_b = xstrdup(arg_b);
+ op->next = NULL;
+
+ /* Add at the end */
+ cur = a->commit_ops;
+ while (cur && cur->next)
+ cur = cur->next;
+ if (cur)
+ cur->next = op;
+ else
+ a->commit_ops = op;
+}
+
+static void
+alternative_commit(struct alternative *a)
+{
+ struct commit_operation *op;
+
+ for (op = a->commit_ops; op; op = op->next) {
+ switch (op->opcode) {
+ case OPCODE_NOP:
+ break;
+ case OPCODE_RM:
+ fsys_rm(op->arg_a);
+ break;
+ case OPCODE_MV:
+ fsys_mv(op->arg_a, op->arg_b);
+ break;
+ case OPCODE_REF_TIME:
+ fsys_set_ref_time(op->arg_a, op->arg_b);
+ break;
+ }
+ }
+
+ alternative_commit_operations_free(a);
+}
+
+enum alternative_path_status {
+ ALT_PATH_SYMLINK,
+ ALT_PATH_MISSING,
+ ALT_PATH_OTHER,
+};
+
+static enum alternative_path_status
+alternative_path_classify(const char *linkname)
+{
+ struct stat st;
+
+ if (fsys_lstat(linkname, &st) == -1) {
+ if (errno != ENOENT)
+ syserr(_("cannot stat file '%s%s'"), instdir, linkname);
+ return ALT_PATH_MISSING;
+ } else if (S_ISLNK(st.st_mode)) {
+ return ALT_PATH_SYMLINK;
+ } else {
+ return ALT_PATH_OTHER;
+ }
+}
+
+static bool
+alternative_path_can_remove(const char *linkname)
+{
+ if (opt_force)
+ return true;
+
+ if (alternative_path_classify(linkname) == ALT_PATH_OTHER)
+ return false;
+ else
+ return true;
+}
+
+static bool
+alternative_path_needs_update(const char *linkname, const char *filename)
+{
+ char *linktarget;
+ bool update;
+
+ if (opt_force)
+ return true;
+
+ switch (alternative_path_classify(linkname)) {
+ case ALT_PATH_SYMLINK:
+ linktarget = fsys_xreadlink(linkname);
+ if (strcmp(linktarget, filename) == 0)
+ update = false;
+ else
+ update = true;
+ free(linktarget);
+
+ return update;
+ case ALT_PATH_OTHER:
+ warning(_("not replacing %s with a link"), linkname);
+ return false;
+ case ALT_PATH_MISSING:
+ default:
+ return true;
+ }
+}
+
+static void
+alternative_prepare_install_single(struct alternative *a, const char *name,
+ const char *linkname, const char *file)
+{
+ char *fntmp, *fn;
+
+ /* Create alternatives directory (/etc/alternatives) if missing. */
+ if (fsys_pathname_is_missing(altdir)) {
+ char *root_altdir = fsys_get_path(altdir);
+
+ if (make_path(root_altdir, 0755) < 0)
+ syserr(_("cannot create alternatives directory '%s'"),
+ root_altdir);
+
+ free(root_altdir);
+ }
+
+ fn = xasprintf("%s/%s", altdir, name);
+
+ /* Create link in /etc/alternatives. */
+ fntmp = xasprintf("%s/%s" ALT_TMP_EXT, altdir, name);
+ fsys_symlink(file, fntmp);
+ alternative_add_commit_op(a, OPCODE_MV, fntmp, fn);
+ if (fsys_pathname_is_missing(fn))
+ alternative_add_commit_op(a, OPCODE_REF_TIME, fn, file);
+ free(fntmp);
+
+ if (alternative_path_needs_update(linkname, fn)) {
+ /* Create alternative link. */
+ fntmp = xasprintf("%s" ALT_TMP_EXT, linkname);
+ fsys_symlink(fn, fntmp);
+ alternative_add_commit_op(a, OPCODE_MV, fntmp, linkname);
+ if (fsys_pathname_is_missing(linkname))
+ alternative_add_commit_op(a, OPCODE_REF_TIME, linkname, fn);
+ free(fntmp);
+ }
+ free(fn);
+}
+
+static void
+alternative_prepare_install(struct alternative *a, const char *choice)
+{
+ struct slave_link *sl;
+ struct fileset *fs;
+
+ fs = alternative_get_fileset(a, choice);
+ if (fs == NULL)
+ error(_("can't install unknown choice %s"), choice);
+
+ /* Take care of master alternative */
+ alternative_prepare_install_single(a, a->master_name, a->master_link,
+ choice);
+
+ /* Take care of slaves alternatives */
+ for (sl = a->slaves; sl; sl = sl->next) {
+ char *fn;
+
+ if (fileset_can_install_slave(fs, sl->name)) {
+ alternative_prepare_install_single(a, sl->name,
+ sl->link, fileset_get_slave(fs, sl->name));
+ continue;
+ }
+
+ /* Slave can't be installed */
+ if (fileset_has_slave(fs, sl->name))
+ warning(_("skip creation of %s because associated "
+ "file %s (of link group %s) doesn't exist"),
+ sl->link, fileset_get_slave(fs, sl->name),
+ a->master_name);
+
+ /* Drop unused slave. */
+ fn = xasprintf("%s/%s", altdir, sl->name);
+ if (alternative_path_can_remove(sl->link))
+ alternative_add_commit_op(a, OPCODE_RM, sl->link, NULL);
+ else
+ warning(_("not removing %s since it's not a symlink"),
+ sl->link);
+ alternative_add_commit_op(a, OPCODE_RM, fn, NULL);
+ free(fn);
+ }
+}
+
+static void
+alternative_remove_files(struct alternative *a)
+{
+ struct slave_link *sl;
+
+ fsys_rm_args("%s" ALT_TMP_EXT, a->master_link);
+ if (alternative_path_can_remove(a->master_link))
+ fsys_rm(a->master_link);
+
+ fsys_rm_args("%s/%s" ALT_TMP_EXT, altdir, a->master_name);
+ fsys_rm_args("%s/%s", altdir, a->master_name);
+
+ for (sl = a->slaves; sl; sl = sl->next) {
+ fsys_rm_args("%s" ALT_TMP_EXT, sl->link);
+ if (alternative_path_can_remove(sl->link))
+ fsys_rm(sl->link);
+
+ fsys_rm_args("%s/%s" ALT_TMP_EXT, altdir, sl->name);
+ fsys_rm_args("%s/%s", altdir, sl->name);
+ }
+ /* Drop admin file */
+ xunlink_args("%s/%s", admdir, a->master_name);
+}
+
+static char *
+alternative_remove(struct alternative *a, const char *current_choice,
+ const char *path)
+{
+ char *new_choice = NULL;
+
+ if (alternative_has_choice(a, path))
+ alternative_remove_choice(a, path);
+ else
+ verbose(_("alternative %s for %s not registered; not removing"),
+ path, a->master_name);
+
+ if (current_choice && strcmp(current_choice, path) == 0) {
+ struct fileset *best;
+
+ /* Current choice is removed. */
+ if (a->status == ALT_ST_MANUAL) {
+ /* And it was manual, switch to auto. */
+ info(_("removing manually selected alternative "
+ "- switching %s to auto mode"),
+ a->master_name);
+ alternative_set_status(a, ALT_ST_AUTO);
+ }
+ best = alternative_get_best(a);
+ if (best)
+ new_choice = xstrdup(best->master_file);
+ }
+
+ return new_choice;
+}
+
+static bool
+alternative_has_broken_symlink(const char *linkname, const char *ref_target)
+{
+ char *target;
+
+ target = fsys_areadlink(linkname);
+ if (!target)
+ return true;
+ if (strcmp(target, ref_target) != 0) {
+ free(target);
+ return true;
+ }
+ free(target);
+ return false;
+}
+
+static bool
+alternative_has_broken_slave(struct slave_link *sl, struct fileset *fs)
+{
+ if (fileset_can_install_slave(fs, sl->name)) {
+ char *wanted;
+ const char *sl_target;
+
+ /* Verify link -> /etc/alternatives/foo */
+ wanted = xasprintf("%s/%s", altdir, sl->name);
+ if (alternative_has_broken_symlink(sl->link, wanted)) {
+ free(wanted);
+ return true;
+ }
+
+ /* Verify /etc/alternatives/foo -> file */
+ sl_target = fileset_get_slave(fs, sl->name);
+ if (alternative_has_broken_symlink(wanted, sl_target)) {
+ free(wanted);
+ return true;
+ }
+
+ free(wanted);
+ } else {
+ char *sl_altlnk;
+
+ /* Slave link must not exist. */
+ if (alternative_path_classify(sl->link) != ALT_PATH_MISSING)
+ return true;
+ sl_altlnk = xasprintf("%s/%s", altdir, sl->name);
+ if (alternative_path_classify(sl_altlnk) != ALT_PATH_MISSING) {
+ free(sl_altlnk);
+ return true;
+ }
+ free(sl_altlnk);
+ }
+
+ return false;
+}
+
+static enum alternative_update_reason
+alternative_needs_update(struct alternative *a)
+{
+ enum alternative_update_reason reason = ALT_UPDATE_NO;
+ const char *current;
+ char *wanted;
+ struct fileset *fs;
+ struct slave_link *sl;
+
+ /* Check master link */
+ wanted = xasprintf("%s/%s", altdir, a->master_name);
+ if (alternative_has_broken_symlink(a->master_link, wanted)) {
+ free(wanted);
+ return ALT_UPDATE_LINK_BROKEN;
+ }
+ free(wanted);
+
+ /* Stop if we have an unmanaged alternative */
+ current = alternative_get_current(a);
+ if (current == NULL)
+ return ALT_UPDATE_LINK_BROKEN;
+
+ fs = alternative_get_fileset(a, current);
+
+ /* Stop if we do not have the choice. */
+ if (fs == NULL)
+ return ALT_UPDATE_NO;
+
+ /* Check slaves */
+ for (sl = a->slaves; sl; sl = sl->next) {
+ if (alternative_has_broken_slave(sl, fs)) {
+ if (sl->updated)
+ reason = ALT_UPDATE_SLAVE_CHANGED;
+ else
+ return ALT_UPDATE_LINK_BROKEN;
+ }
+ }
+
+ return reason;
+}
+
+struct alternative_map {
+ struct alternative_map *next;
+
+ const char *key;
+ struct alternative *item;
+};
+
+static struct alternative_map *
+alternative_map_new(const char *key, struct alternative *a)
+{
+ struct alternative_map *am;
+
+ am = xmalloc(sizeof(*am));
+ am->next = NULL;
+ am->key = key;
+ am->item = a;
+
+ return am;
+}
+
+static struct alternative *
+alternative_map_find(struct alternative_map *am, const char *key)
+{
+ for (; am; am = am->next)
+ if (am->key && strcmp(am->key, key) == 0)
+ return am->item;
+
+ return NULL;
+}
+
+static void
+alternative_map_add(struct alternative_map *am, const char *key,
+ struct alternative *a)
+{
+ alternative_ref(a);
+
+ if (am->key == NULL) {
+ am->key = key;
+ am->item = a;
+ } else {
+ struct alternative_map *new = alternative_map_new(key, a);
+
+ while (am->next)
+ am = am->next;
+ am->next = new;
+ }
+}
+
+static void
+alternative_map_load_names(struct alternative_map *alt_map_obj)
+{
+ struct dirent **table;
+ int i, count;
+
+ count = altdb_get_namelist(&table);
+ for (i = 0; i < count; i++) {
+ struct alternative *a_new = alternative_new(table[i]->d_name);
+
+ if (!alternative_load(a_new, ALTDB_LAX_PARSER)) {
+ alternative_free(a_new);
+ continue;
+ }
+ alternative_map_add(alt_map_obj, a_new->master_name, a_new);
+
+ alternative_unref(a_new);
+ }
+ altdb_free_namelist(table, count);
+}
+
+static void
+alternative_map_load_tree(struct alternative_map *alt_map_links,
+ struct alternative_map *alt_map_parent)
+{
+ struct dirent **table;
+ int i, count;
+
+ count = altdb_get_namelist(&table);
+ for (i = 0; i < count; i++) {
+ struct slave_link *sl;
+ struct alternative *a_new = alternative_new(table[i]->d_name);
+
+ if (!alternative_load(a_new, ALTDB_LAX_PARSER)) {
+ alternative_free(a_new);
+ continue;
+ }
+ alternative_map_add(alt_map_links, a_new->master_link, a_new);
+ alternative_map_add(alt_map_parent, a_new->master_name, a_new);
+ for (sl = a_new->slaves; sl; sl = sl->next) {
+ alternative_map_add(alt_map_links, sl->link, a_new);
+ alternative_map_add(alt_map_parent, sl->name, a_new);
+ }
+
+ alternative_unref(a_new);
+ }
+ altdb_free_namelist(table, count);
+}
+
+static void
+alternative_map_free(struct alternative_map *am)
+{
+ struct alternative_map *am_next;
+
+ while (am) {
+ am_next = am->next;
+ if (am->item)
+ alternative_free(am->item);
+ free(am);
+ am = am_next;
+ }
+}
+
+static char *
+alternative_set_manual(struct alternative *a, const char *path)
+{
+ char *new_choice = NULL;
+
+ if (alternative_has_choice(a, path))
+ new_choice = xstrdup(path);
+ else
+ error(_("alternative %s for %s not registered; "
+ "not setting"), path, a->master_name);
+ alternative_set_status(a, ALT_ST_MANUAL);
+
+ return new_choice;
+}
+
+static char *
+alternative_set_auto(struct alternative *a)
+{
+ char *new_choice = NULL;
+
+ alternative_set_status(a, ALT_ST_AUTO);
+ if (alternative_choices_count(a) == 0)
+ info(_("there is no program which provides %s"),
+ a->master_name);
+ else
+ new_choice = xstrdup(alternative_get_best(a)->master_file);
+
+ return new_choice;
+}
+
+static const char *
+get_argv_string(int argc, char **argv)
+{
+ static char string[2048];
+ size_t cur_len;
+ int i;
+
+ string[0] = '\0';
+ cur_len = 0;
+ for (i = 1; i < argc; i++) {
+ size_t arg_len = strlen(argv[i]);
+
+ if (cur_len + arg_len + 2 > sizeof(string))
+ break;
+ if (cur_len) {
+ strcpy(string + cur_len, " ");
+ cur_len++;
+ }
+ strcpy(string + cur_len, argv[i]);
+ cur_len += arg_len;
+ }
+
+ return string;
+}
+
+static void
+alternative_select_mode(struct alternative *a, const char *current_choice)
+{
+ if (current_choice) {
+ /* Detect manually modified alternative, switch to manual. */
+ if (!alternative_has_choice(a, current_choice)) {
+ if (fsys_pathname_is_missing(current_choice)) {
+ warning(_("%s%s/%s is dangling; it will be updated "
+ "with best choice"), instdir, altdir,
+ a->master_name);
+ alternative_set_status(a, ALT_ST_AUTO);
+ } else if (a->status != ALT_ST_MANUAL) {
+ warning(_("%s%s/%s has been changed (manually or by "
+ "a script); switching to manual "
+ "updates only"), instdir, altdir,
+ a->master_name);
+ alternative_set_status(a, ALT_ST_MANUAL);
+ }
+ }
+ } else {
+ /* Lack of alternative link => automatic mode. */
+ verbose(_("setting up automatic selection of %s"),
+ a->master_name);
+ alternative_set_status(a, ALT_ST_AUTO);
+ }
+}
+
+static void
+alternative_evolve_slave(struct alternative *a, const char *cur_choice,
+ struct slave_link *sl, struct fileset *fs)
+{
+ struct slave_link *sl_old;
+ char *new_file = NULL;
+ const char *old, *new;
+
+ sl_old = alternative_get_slave(a, sl->name);
+ if (sl_old == NULL) {
+ sl->updated = true;
+ return;
+ }
+
+ old = sl_old->link;
+ new = sl->link;
+
+ if (cur_choice && strcmp(cur_choice, fs->master_file) == 0) {
+ new_file = xstrdup(fileset_get_slave(fs, sl->name));
+ } else {
+ char *lnk;
+
+ lnk = xasprintf("%s/%s", altdir, sl->name);
+ new_file = fsys_areadlink(lnk);
+ free(lnk);
+ }
+ if (strcmp(old, new) != 0 &&
+ alternative_path_classify(old) == ALT_PATH_SYMLINK) {
+ bool rename_link = false;
+
+ if (new_file)
+ rename_link = !fsys_pathname_is_missing(new_file);
+
+ if (rename_link) {
+ info(_("renaming %s slave link from %s%s to %s%s"),
+ sl->name, instdir, old, instdir, new);
+ fsys_mv(old, new);
+ } else {
+ fsys_rm(old);
+ }
+
+ sl->updated = true;
+ }
+ free(new_file);
+}
+
+static void
+alternative_evolve(struct alternative *a, struct alternative *b,
+ const char *cur_choice, struct fileset *fs)
+{
+ struct slave_link *sl;
+ bool is_link;
+
+ is_link = alternative_path_classify(a->master_link) == ALT_PATH_SYMLINK;
+ if (is_link && strcmp(a->master_link, b->master_link) != 0) {
+ info(_("renaming %s link from %s%s to %s%s"), b->master_name,
+ instdir, a->master_link, instdir, b->master_link);
+ fsys_mv(a->master_link, b->master_link);
+ }
+ alternative_set_link(a, b->master_link);
+
+ /* Check if new slaves have been added, or existing
+ * ones renamed. */
+ for (sl = b->slaves; sl; sl = sl->next) {
+ alternative_evolve_slave(a, cur_choice, sl, fs);
+ alternative_copy_slave(a, sl);
+ }
+}
+
+static char *
+alternative_install(struct alternative **aptr, struct alternative *inst_alt,
+ const char *current_choice, struct fileset *fileset)
+{
+ struct alternative *a = *aptr;
+ char *new_choice = NULL;
+
+ if (a->master_link) {
+ /* Alternative already exists, check if anything got
+ * updated. */
+ alternative_evolve(a, inst_alt, current_choice, fileset);
+ alternative_free(inst_alt);
+ } else {
+ /* Alternative doesn't exist, create from parameters. */
+ alternative_free(a);
+ *aptr = a = inst_alt;
+ }
+ alternative_add_choice(a, fileset);
+ if (a->status == ALT_ST_AUTO) {
+ new_choice = xstrdup(alternative_get_best(a)->master_file);
+ } else {
+ verbose(_("automatic updates of %s/%s are disabled; "
+ "leaving it alone"), altdir, a->master_name);
+ verbose(_("to return to automatic updates use "
+ "'%s --auto %s'"), PROGNAME, a->master_name);
+ }
+ return new_choice;
+}
+
+static void
+alternative_update(struct alternative *a,
+ const char *current_choice, const char *new_choice)
+{
+ enum alternative_update_reason reason;
+
+ /* No choice left, remove everything. */
+ if (!alternative_choices_count(a)) {
+ log_msg("link group %s fully removed", a->master_name);
+ alternative_remove_files(a);
+ return;
+ }
+
+ /* New choice wanted. */
+ if (new_choice &&
+ (!current_choice || strcmp(new_choice, current_choice) != 0)) {
+ log_msg("link group %s updated to point to %s", a->master_name,
+ new_choice);
+ if (a->status == ALT_ST_AUTO)
+ info(_("using %s to provide %s (%s) in auto mode"),
+ new_choice, a->master_link, a->master_name);
+ else
+ info(_("using %s to provide %s (%s) in manual mode"),
+ new_choice, a->master_link, a->master_name);
+ debug("prepare_install(%s)", new_choice);
+ alternative_prepare_install(a, new_choice);
+ } else if ((reason = alternative_needs_update(a))) {
+ if (reason == ALT_UPDATE_SLAVE_CHANGED) {
+ log_msg("link group %s updated with changed slaves",
+ a->master_name);
+ info(_("updating alternative %s "
+ "because link group %s has changed slave links"),
+ current_choice, a->master_name);
+ } else {
+ log_msg("auto-repair link group %s", a->master_name);
+ warning(_("forcing reinstallation of alternative %s "
+ "because link group %s is broken"),
+ current_choice, a->master_name);
+ }
+
+ if (current_choice && !alternative_has_choice(a, current_choice)) {
+ struct fileset *best = alternative_get_best(a);
+
+ warning(_("current alternative %s is unknown, "
+ "switching to %s for link group %s"),
+ current_choice, best->master_file,
+ a->master_name);
+ current_choice = best->master_file;
+ alternative_set_status(a, ALT_ST_AUTO);
+ }
+
+ if (current_choice)
+ alternative_prepare_install(a, current_choice);
+ }
+
+ /* Save administrative file if needed. */
+ if (a->modified) {
+ debug("%s is modified and will be saved", a->master_name);
+ alternative_save(a);
+ }
+
+ /* Replace all symlinks in one pass. */
+ alternative_commit(a);
+}
+
+static void
+alternative_config_all(void)
+{
+ struct alternative_map *alt_map_obj;
+ struct alternative_map *am;
+
+ alt_map_obj = alternative_map_new(NULL, NULL);
+ alternative_map_load_names(alt_map_obj);
+
+ for (am = alt_map_obj; am && am->item; am = am->next) {
+ const char *current_choice;
+ char *new_choice;
+
+ current_choice = alternative_get_current(am->item);
+ alternative_select_mode(am->item, current_choice);
+
+ new_choice = alternative_config(am->item, current_choice);
+
+ alternative_update(am->item, current_choice, new_choice);
+
+ free(new_choice);
+ }
+
+ alternative_map_free(alt_map_obj);
+}
+
+static void
+alternative_get_selections(void)
+{
+ struct alternative_map *alt_map_obj;
+ struct alternative_map *am;
+
+ alt_map_obj = alternative_map_new(NULL, NULL);
+ alternative_map_load_names(alt_map_obj);
+
+ for (am = alt_map_obj; am && am->item; am = am->next) {
+ const char *current;
+
+ current = alternative_get_current(am->item);
+ printf("%-30s %-8s %s\n", am->key,
+ alternative_status_string(am->item->status),
+ current ? current : "");
+ }
+
+ alternative_map_free(alt_map_obj);
+}
+
+static void
+alternative_set_selection(struct alternative_map *all, const char *name,
+ const char *status, const char *choice)
+{
+ struct alternative *a;
+
+ debug("set_selection(%s, %s, %s)", name, status, choice);
+ a = alternative_map_find(all, name);
+ if (a) {
+ char *new_choice = NULL;
+
+ if (strcmp(status, "auto") == 0) {
+ info(_("selecting alternative %s as auto"), name);
+ new_choice = alternative_set_auto(a);
+ } else if (alternative_has_choice(a, choice)) {
+ info(_("selecting alternative %s as choice %s"), name,
+ choice);
+ new_choice = alternative_set_manual(a, choice);
+ } else {
+ info(_("alternative %s unchanged because choice "
+ "%s is not available"), name, choice);
+ }
+
+ if (new_choice) {
+ const char *current_choice;
+
+ current_choice = alternative_get_current(a);
+ alternative_select_mode(a, current_choice);
+
+ alternative_update(a, current_choice, new_choice);
+
+ free(new_choice);
+ }
+ } else {
+ info(_("skip unknown alternative %s"), name);
+ }
+}
+
+static void
+alternative_set_selections(FILE *input, const char *desc)
+{
+ struct alternative_map *alt_map_obj;
+
+ alt_map_obj = alternative_map_new(NULL, NULL);
+ alternative_map_load_names(alt_map_obj);
+
+ for (;;) {
+ char line[1024], *res, *name, *status, *choice;
+ size_t len, i;
+
+ errno = 0;
+ /* Can't use scanf("%s %s %s") because choice can
+ * contain a space */
+ res = fgets(line, sizeof(line), input);
+ if (res == NULL && errno) {
+ syserr(_("read error in %.250s"), desc);
+ } else if (res == NULL) {
+ break;
+ }
+ len = strlen(line);
+ if (len == 0 || line[len - 1] != '\n') {
+ error(_("line too long or not terminated while "
+ "trying to read %s"), desc);
+ }
+ line[len - 1] = '\0';
+ len--;
+
+ /* Delimit name string in line */
+ i = 0;
+ name = line;
+ while (i < len && !isblank(line[i]))
+ i++;
+ if (i >= len) {
+ info(_("skip invalid selection line: %s"), line);
+ continue;
+ }
+ line[i++] = '\0';
+ while (i < len && isblank(line[i]))
+ i++;
+
+ /* Delimit status string in line */
+ status = line + i;
+ while (i < len && !isblank(line[i]))
+ i++;
+ if (i >= len) {
+ info(_("skip invalid selection line: %s"), line);
+ continue;
+ }
+ line[i++] = '\0';
+ while (i < len && isblank(line[i]))
+ i++;
+
+ /* Delimit choice string in the line */
+ if (i >= len) {
+ info(_("skip invalid selection line: %s"), line);
+ continue;
+ }
+ choice = line + i;
+
+ alternative_set_selection(alt_map_obj, name, status, choice);
+ }
+
+ alternative_map_free(alt_map_obj);
+}
+
+static void
+alternative_check_name(const char *name)
+{
+ if (strpbrk(name, "/ \t"))
+ error(_("alternative name (%s) must not contain '/' "
+ "and spaces"), name);
+}
+
+static void
+alternative_check_link(const char *linkname)
+{
+ if (linkname[0] != '/')
+ error(_("alternative link is not absolute as it should be: %s"),
+ linkname);
+}
+
+static void
+alternative_check_path(const char *file)
+{
+ if (!file || file[0] != '/')
+ error(_("alternative path is not absolute as it should be: %s"),
+ file);
+}
+
+/**
+ * Check the alternative installation arguments.
+ *
+ * That the caller doesn't mix links between alternatives, doesn't mix
+ * alternatives between slave/master, and that the various parameters
+ * are fine.
+ */
+static void
+alternative_check_install_args(struct alternative *inst_alt,
+ struct fileset *fileset)
+{
+ struct alternative_map *alt_map_links, *alt_map_parent;
+ struct alternative *found;
+ struct slave_link *sl;
+
+ alternative_check_name(inst_alt->master_name);
+ alternative_check_link(inst_alt->master_link);
+ alternative_check_path(fileset->master_file);
+
+ /* Load information about all alternatives to check for mistakes. */
+ alt_map_links = alternative_map_new(NULL, NULL);
+ alt_map_parent = alternative_map_new(NULL, NULL);
+ alternative_map_load_tree(alt_map_links, alt_map_parent);
+
+ found = alternative_map_find(alt_map_parent, inst_alt->master_name);
+ if (found && strcmp(found->master_name, inst_alt->master_name) != 0) {
+ error(_("alternative %s can't be master: it is a slave of %s"),
+ inst_alt->master_name, found->master_name);
+ }
+
+ found = alternative_map_find(alt_map_links, inst_alt->master_link);
+ if (found && strcmp(found->master_name, inst_alt->master_name) != 0) {
+ found = alternative_map_find(alt_map_parent,
+ found->master_name);
+ error(_("alternative link %s is already managed by %s"),
+ inst_alt->master_link, found->master_name);
+ }
+
+ if (fsys_pathname_is_missing(fileset->master_file))
+ error(_("alternative path %s%s doesn't exist"),
+ instdir, fileset->master_file);
+
+ for (sl = inst_alt->slaves; sl; sl = sl->next) {
+ const char *file = fileset_get_slave(fileset, sl->name);
+
+ alternative_check_name(sl->name);
+ alternative_check_link(sl->link);
+ alternative_check_path(file);
+
+ found = alternative_map_find(alt_map_parent, sl->name);
+ if (found &&
+ strcmp(found->master_name, inst_alt->master_name) != 0) {
+ if (strcmp(found->master_name, sl->name) == 0)
+ error(_("alternative %s can't be slave of %s: "
+ "it is a master alternative"),
+ sl->name, inst_alt->master_name);
+ else
+ error(_("alternative %s can't be slave of %s: "
+ "it is a slave of %s"),
+ sl->name, inst_alt->master_name,
+ found->master_name);
+ }
+
+ found = alternative_map_find(alt_map_links, sl->link);
+ if (found &&
+ strcmp(found->master_name, inst_alt->master_name) != 0) {
+ error(_("alternative link %s is already "
+ "managed by %s"), sl->link,
+ found->master_name);
+ }
+ if (found) {
+ struct slave_link *sl2;
+
+ for (sl2 = found->slaves; sl2; sl2 = sl2->next)
+ if (strcmp(sl2->link, sl->link) == 0)
+ break;
+ if (sl2 && strcmp(sl2->name, sl->name) != 0)
+ error(_("alternative link %s is already "
+ "managed by %s (slave of %s)"),
+ sl->link, sl2->name,
+ found->master_name);
+ }
+ }
+
+ alternative_map_free(alt_map_links);
+ alternative_map_free(alt_map_parent);
+}
+
+/*
+ * Main program
+ */
+
+static void
+set_action(enum action new_action)
+{
+ if (action)
+ badusage(_("two commands specified: --%s and --%s"),
+ action_names[action].name, action_names[new_action].name);
+ action = new_action;
+}
+
+static void
+set_action_from_name(const char *new_action)
+{
+ size_t i;
+
+ for (i = 0; i < array_count(action_names); i++) {
+ if (strcmp(new_action, action_names[i].name) == 0) {
+ set_action(action_names[i].action);
+ return;
+ }
+ }
+
+ assert(!"unknown action name");
+}
+
+static const char *
+set_rootdir(const char *dir)
+{
+ instdir = fsys_set_dir(dir);
+ free(log_file);
+ log_file = fsys_get_path(LOGDIR "/alternatives.log");
+ altdir = SYSCONFDIR "/alternatives";
+ free(admdir);
+ admdir = fsys_gen_admindir();
+
+ return instdir;
+}
+
+static char *
+admindir_init(void)
+{
+ const char *basedir_env;
+
+ /* Try to get the admindir from an environment variable, usually set
+ * by the system package manager. */
+ basedir_env = getenv(ADMINDIR_ENVVAR);
+ if (basedir_env)
+ return xasprintf("%s%s", basedir_env, "/alternatives");
+ else
+ return fsys_gen_admindir();
+}
+
+#define MISSING_ARGS(nb) (argc < i + nb + 1)
+
+int
+main(int argc, char **argv)
+{
+ /* Alternative worked on. */
+ struct alternative *a = NULL;
+ /* Alternative to install. */
+ struct alternative *inst_alt = NULL;
+ /* Set of files to install in the alternative. */
+ struct fileset *fileset = NULL;
+ /* Path of alternative we are offering. */
+ const char *path = NULL;
+ const char *current_choice = NULL;
+ char *new_choice = NULL;
+ bool modifies_alt = false;
+ bool modifies_sys = false;
+ int i = 0;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+
+ tzset();
+ umask(022);
+
+ instdir = fsys_set_dir(NULL);
+ admdir = admindir_init();
+ log_file = fsys_get_path(LOGDIR "/alternatives.log");
+
+ if (setvbuf(stdout, NULL, _IONBF, 0))
+ syserr("setvbuf failed");
+
+ prog_path = argv[0];
+
+ for (i = 1; i < argc; i++) {
+ if (strstr(argv[i], "--") != argv[i]) {
+ error(_("unknown argument '%s'"), argv[i]);
+ } else if (strcmp("--help", argv[i]) == 0) {
+ usage();
+ exit(0);
+ } else if (strcmp("--version", argv[i]) == 0) {
+ version();
+ exit(0);
+ } else if (strcmp("--quiet", argv[i]) == 0) {
+ opt_verbose = OUTPUT_QUIET;
+ } else if (strcmp("--verbose", argv[i]) == 0) {
+ opt_verbose = OUTPUT_VERBOSE;
+ } else if (strcmp("--debug", argv[i]) == 0) {
+ opt_verbose = OUTPUT_DEBUG;
+ } else if (strcmp("--install", argv[i]) == 0) {
+ const char *alink, *aname, *apath;
+ char *prio_str, *prio_end;
+ long prio;
+
+ set_action(ACTION_INSTALL);
+ if (MISSING_ARGS(4))
+ badusage(_("--%s needs <link> <name> <path> "
+ "<priority>"), argv[i] + 2);
+
+ alink = argv[i + 1];
+ aname = argv[i + 2];
+ apath = argv[i + 3];
+ prio_str = argv[i + 4];
+
+ if (strcmp(alink, apath) == 0)
+ badusage(_("<link> '%s' is the same as <path>"),
+ alink);
+ errno = 0;
+ prio = strtol(prio_str, &prio_end, 10);
+ if (prio_str == prio_end || *prio_end != '\0')
+ badusage(_("priority '%s' must be an integer"),
+ prio_str);
+ if (prio < INT_MIN || prio > INT_MAX || errno == ERANGE)
+ badusage(_("priority '%s' is out of range"),
+ prio_str);
+
+ a = alternative_new(aname);
+ inst_alt = alternative_new(aname);
+ alternative_set_status(inst_alt, ALT_ST_AUTO);
+ alternative_set_link(inst_alt, alink);
+ fileset = fileset_new(apath, prio);
+
+ i += 4;
+ } else if (strcmp("--remove", argv[i]) == 0 ||
+ strcmp("--set", argv[i]) == 0) {
+ set_action_from_name(argv[i] + 2);
+ if (MISSING_ARGS(2))
+ badusage(_("--%s needs <name> <path>"), argv[i] + 2);
+
+ a = alternative_new(argv[i + 1]);
+ path = argv[i + 2];
+
+ alternative_check_name(a->master_name);
+ alternative_check_path(path);
+
+ i += 2;
+ } else if (strcmp("--display", argv[i]) == 0 ||
+ strcmp("--query", argv[i]) == 0 ||
+ strcmp("--auto", argv[i]) == 0 ||
+ strcmp("--config", argv[i]) == 0 ||
+ strcmp("--list", argv[i]) == 0 ||
+ strcmp("--remove-all", argv[i]) == 0) {
+ set_action_from_name(argv[i] + 2);
+ if (MISSING_ARGS(1))
+ badusage(_("--%s needs <name>"), argv[i] + 2);
+ a = alternative_new(argv[i + 1]);
+
+ alternative_check_name(a->master_name);
+
+ i++;
+ } else if (strcmp("--all", argv[i]) == 0 ||
+ strcmp("--get-selections", argv[i]) == 0 ||
+ strcmp("--set-selections", argv[i]) == 0) {
+ set_action_from_name(argv[i] + 2);
+ } else if (strcmp("--slave", argv[i]) == 0) {
+ const char *slink, *sname, *spath;
+ struct slave_link *sl;
+
+ if (action != ACTION_INSTALL)
+ badusage(_("--%s only allowed with --%s"),
+ argv[i] + 2, "install");
+ if (MISSING_ARGS(3))
+ badusage(_("--%s needs <link> <name> <path>"),
+ argv[i] + 2);
+
+ slink = argv[i + 1];
+ sname = argv[i + 2];
+ spath = argv[i + 3];
+
+ if (strcmp(slink, spath) == 0)
+ badusage(_("<link> '%s' is the same as <path>"),
+ slink);
+ if (strcmp(inst_alt->master_name, sname) == 0)
+ badusage(_("<name> '%s' is both primary and slave"),
+ sname);
+ if (strcmp(slink, inst_alt->master_link) == 0)
+ badusage(_("<link> '%s' is both primary and slave"),
+ slink);
+ if (alternative_has_slave(inst_alt, sname))
+ badusage(_("duplicate slave <name> '%s'"), sname);
+
+ for (sl = inst_alt->slaves; sl; sl = sl->next) {
+ const char *linkname = sl->link;
+ if (linkname == NULL)
+ linkname = "";
+ if (strcmp(linkname, slink) == 0)
+ badusage(_("duplicate slave <link> '%s'"),
+ slink);
+ }
+
+ alternative_add_slave(inst_alt, sname, slink);
+ fileset_add_slave(fileset, sname, spath);
+
+ i+= 3;
+ } else if (strcmp("--log", argv[i]) == 0) {
+ if (MISSING_ARGS(1))
+ badusage(_("--%s needs a <file> argument"),
+ argv[i] + 2);
+ free(log_file);
+ log_file = fsys_get_path(argv[i + 1]);
+ i++;
+ } else if (strcmp("--altdir", argv[i]) == 0) {
+ if (MISSING_ARGS(1))
+ badusage(_("--%s needs a <directory> argument"),
+ argv[i] + 2);
+ altdir = argv[i + 1];
+ i++;
+
+ /* If altdir is below instdir, convert it to a relative
+ * path, as we will prepend instdir as needed. */
+ if (strncmp(altdir, instdir, instdir_len) == 0)
+ altdir += instdir_len;
+ } else if (strcmp("--admindir", argv[i]) == 0) {
+ if (MISSING_ARGS(1))
+ badusage(_("--%s needs a <directory> argument"),
+ argv[i] + 2);
+ free(admdir);
+ admdir = xstrdup(argv[i + 1]);
+ i++;
+ } else if (strcmp("--instdir", argv[i]) == 0) {
+ if (MISSING_ARGS(1))
+ badusage(_("--%s needs a <directory> argument"),
+ argv[i] + 2);
+ fsys_set_dir(argv[i + 1]);
+ i++;
+
+ /* If altdir is below instdir, convert it to a relative
+ * path, as we will prepend instdir as needed. */
+ if (strncmp(altdir, instdir, instdir_len) == 0)
+ altdir += instdir_len;
+ } else if (strcmp("--root", argv[i]) == 0) {
+ if (MISSING_ARGS(1))
+ badusage(_("--%s needs a <directory> argument"),
+ argv[i] + 2);
+ set_rootdir(argv[i + 1]);
+ i++;
+ } else if (strcmp("--skip-auto", argv[i]) == 0) {
+ opt_skip_auto = 1;
+ } else if (strcmp("--force", argv[i]) == 0) {
+ opt_force = 1;
+ } else {
+ badusage(_("unknown option '%s'"), argv[i]);
+ }
+ }
+
+ if (action == ACTION_NONE)
+ badusage(_("need --%s, --%s, --%s, --%s, --%s, --%s, --%s, "
+ "--%s, --%s, --%s, --%s or --%s"),
+ "display", "query", "list", "get-selections",
+ "config", "set", "set-selections", "install",
+ "remove", "all", "remove-all", "auto");
+
+ debug("root=%s admdir=%s altdir=%s", instdir, admdir, altdir);
+
+ /* The following actions might modify the current alternative. */
+ if (action == ACTION_SET ||
+ action == ACTION_AUTO ||
+ action == ACTION_CONFIG ||
+ action == ACTION_REMOVE ||
+ action == ACTION_REMOVE_ALL ||
+ action == ACTION_INSTALL)
+ modifies_alt = true;
+
+ /* The following actions might modify the system somehow. */
+ if (modifies_alt ||
+ action == ACTION_CONFIG_ALL ||
+ action == ACTION_SET_SELECTIONS)
+ modifies_sys = true;
+
+ if (action == ACTION_INSTALL)
+ alternative_check_install_args(inst_alt, fileset);
+
+ if (action == ACTION_DISPLAY ||
+ action == ACTION_QUERY ||
+ action == ACTION_LIST ||
+ action == ACTION_SET ||
+ action == ACTION_AUTO ||
+ action == ACTION_CONFIG ||
+ action == ACTION_REMOVE_ALL) {
+ /* Load the alternative info, stop on failure. */
+ if (!alternative_load(a, ALTDB_WARN_PARSER))
+ error(_("no alternatives for %s"), a->master_name);
+ } else if (action == ACTION_REMOVE) {
+ /* XXX: Be consistent for now with the case when we
+ * try to remove a non-existing path from an existing
+ * link group file. */
+ if (!alternative_load(a, ALTDB_WARN_PARSER)) {
+ verbose(_("no alternatives for %s"), a->master_name);
+ alternative_free(a);
+ free(log_file);
+ free(admdir);
+ exit(0);
+ }
+ } else if (action == ACTION_INSTALL) {
+ /* Load the alternative info, ignore failures. */
+ alternative_load(a, ALTDB_WARN_PARSER);
+ }
+
+ if (modifies_sys)
+ log_msg("run with %s", get_argv_string(argc, argv));
+
+ if (modifies_alt) {
+ current_choice = alternative_get_current(a);
+ alternative_select_mode(a, current_choice);
+ }
+
+ /* Handle actions. */
+ if (action == ACTION_CONFIG_ALL) {
+ alternative_config_all();
+ } else if (action == ACTION_GET_SELECTIONS) {
+ alternative_get_selections();
+ } else if (action == ACTION_SET_SELECTIONS) {
+ alternative_set_selections(stdin, _("<standard input>"));
+ } else if (action == ACTION_DISPLAY) {
+ alternative_display_user(a);
+ } else if (action == ACTION_QUERY) {
+ alternative_display_query(a);
+ } else if (action == ACTION_LIST) {
+ alternative_display_list(a);
+ } else if (action == ACTION_SET) {
+ new_choice = alternative_set_manual(a, path);
+ } else if (action == ACTION_AUTO) {
+ new_choice = alternative_set_auto(a);
+ } else if (action == ACTION_CONFIG) {
+ new_choice = alternative_config(a, current_choice);
+ } else if (action == ACTION_REMOVE) {
+ new_choice = alternative_remove(a, current_choice, path);
+ } else if (action == ACTION_REMOVE_ALL) {
+ alternative_choices_free(a);
+ } else if (action == ACTION_INSTALL) {
+ new_choice = alternative_install(&a, inst_alt, current_choice,
+ fileset);
+ }
+
+ if (modifies_alt)
+ alternative_update(a, current_choice, new_choice);
+
+ if (a)
+ alternative_free(a);
+ free(new_choice);
+ free(log_file);
+ free(admdir);
+
+ return 0;
+}
diff --git a/utils/update-alternatives.polkit.in b/utils/update-alternatives.polkit.in
new file mode 100644
index 0000000..a712bc3
--- /dev/null
+++ b/utils/update-alternatives.polkit.in
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE policyconfig PUBLIC
+ "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
+<policyconfig>
+ <vendor>The Dpkg Project</vendor>
+ <vendor_url>https://wiki.debian.org/Teams/Dpkg</vendor_url>
+ <icon_name>update-alternatives</icon_name>
+
+ <action id="org.dpkg.pkexec.update-alternatives">
+ <description>Run update-alternatives to modify system alternative selections</description>
+ <message>Authentication is required to run update-alternatives</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.exec.path">@bindir@/update-alternatives</annotate>
+ </action>
+</policyconfig>