diff options
Diffstat (limited to 'utils')
-rw-r--r-- | utils/Makefile.am | 103 | ||||
-rw-r--r-- | utils/Makefile.in | 998 | ||||
-rw-r--r-- | utils/README.alternatives | 2 | ||||
-rw-r--r-- | utils/start-stop-daemon.c | 2917 | ||||
-rw-r--r-- | utils/t/update_alternatives.t | 719 | ||||
-rw-r--r-- | utils/update-alternatives.c | 3225 | ||||
-rw-r--r-- | utils/update-alternatives.polkit.in | 20 |
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(¬ify_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(¬ify_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, ¶m) == -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, ¬ify_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> |