#!/bin/sh # Filename: systemctl # Location: /usr/sbin/systemctl # /usr/bin/hostnamectl # /usr/bin/systemd-detect-virt # Author: bgstack15@gmail.com # Startdate: 2020-01-10 13:02:14 # SPDX-License-Identifier: CC-BY-SA-4.0 # Title: # Purpose: # Package: systemctl-service-shim # History: # 2020-05-14 place framework.sh contents inline so as not to depend on it. # 2021-01-10 adapted for inclusion in devuan-sanity # 2021-10-20 add /bin/systemctl symlink control logic # 2022-07-12 Convert try-restart to restart # 2022-07-14 Add preset, which runs update-rc.d ${service} defaults # Usage: # Should be mostly like systemctl from systemd. # Reference: ftemplate.sh 2019-05-02a ; framework.sh 2018-05-02a # man 1 systemctl # Improve: # Return 1 if status output is failed # Dependencies: # req-devuan: moreutils # Documentation: # Be aware that real systemd systemctl is file /bin/systemctl but # this systemdtl is file /usr/sbin/systemctl to prevent a recursive loop # in some service scripts that look for /bin/systemctl # vim: set sw=3 sts=3 ts=3 et: fiversion="2019-05-02a" systemctlversion="2022-07-14a" usage() { ${PAGER:-/usr/bin/less -F} >&2 <> "${logfile}" } parseFlag() { flag="$1" hasval=0 case ${flag} in # INSERT FLAGS HERE "d" | "debug" | "DEBUG" | "dd" ) setdebug ; ferror "debug level ${debug}" ; __debug_set_by_param=1 ;; "u" | "usage" | "help" | "h" ) usage ; exit 0 ;; "V" | "fcheck" | "version" ) ferror "${scriptfile} version ${systemctlversion}" ; exit 0 ;; "c" | "conf" | "conffile" | "config" ) getval ; conffile="${tempval}" ;; "now" ) export SYSTEMCTL_NOW=1 ;; "full") export SYSTEMCTL_FULL=1 ;; "system") export SYSTEMCTL_SYSTEM=1 ;; esac debuglev 10 && { test ${hasval} -eq 1 && ferror "flag: ${flag} = ${tempval}" || ferror "flag: ${flag}" ; } } # INITIALIZE VARIABLES # variables set in framework: # server scriptdir scriptfile scripttrim ### BEGIN IMPORT OF FRAMEWORK.SH fversion="2020-04-24x" # DEFINE FUNCTIONS isflag() { # input: $1=word to parse case "$1" in --*) retval=2 ;; -*) retval=1 ;; *) retval=0 ;; esac echo $retval } parseParam() { # determines if --longname or -shortflagS that need individual parsing trimParam=$( printf '%s' "${param}" | sed -n 's/--//p' ) _rest= if test -n "$trimParam" ; then parseFlag $trimParam else #splitShortStrings _i=2 while test ${_i} -le ${#param} ; do _j=$( expr ${_i} + 1) #_char=$(expr substr "$param" $_i 1) #_rest=$(expr substr "$param" $_j 255) _char=$( printf '%s' "${param}" | cut -c ${_i}) _rest=$( printf '%s' "${param}" | cut -c ${_j}-255) parseFlag $_char _i=$( expr ${_i} + 1) done fi } getval() { tempval= if test -n "${_rest}" ; then tempval="${_rest}" hasval=1 _i=255 # skip rest of splitShortStrings because found the value! elif test -n "$nextparam" && test $(isflag "$nextparam") -eq 0 ; then tempval="$nextparam" hasval=1 #DNE ; is affected by ftemplate! paramnum=$nextparamnum fi } debuglev() { # call: debuglev 5 && ferror "debug level is at least a five!" # added 2015-11-17 localdebug=0 ; localcheck=0 ; fisnum ${debug} && localdebug=${debug} fisnum ${1} && localcheck=${1} test $localdebug -ge $localcheck && return 0 || return 1 } debuglevoutput() { # call: commandthatgeneratesstdout | debuglevoutput 8 # output: output to standard error prepended with "debug8: " the contents of the pipe ___dlo_threshold="${1}" ___dlo_silent="${2}" if debuglev "${___dlo_threshold}" ; then if test -n "${___dlo_silent}" ; then cat 1>&2 else sed -r -e "s/^/debug${___dlo_threshold}: /;" 1>&2 fi else cat 1>/dev/null 2>&1 fi } fisnum() { # call: fisnum $1 && debug=$1 || debug=10 fisnum= ; case $1 in ''|*[!0-9]*) fisnum=1 ;; # invalid *) fisnum=0 ;; # valid number esac return ${fisnum} } fistruthy() { # call: if fistruthy "$val" ; then local _return= case "$( echo "${1}" | tr '[:upper:]' '[:lower:]' )" in yes|1|y|true|always) _return=true ;; esac test -n "${_return}" ; return $? } setval() { # call: setval 0 value1 value2 value3 ... <&2 } setdebug() { # call: setdebug debug=10 getval if test $hasval -eq 1 ; then if fisnum ${tempval} ; then debug=${tempval} else #test paramnum -le paramcount && paramnum=$( expr ${paramnum} - 1 ) hasval=0 fi elif fisnum ${_rest} ; then debug=${_rest} _i=255 else test $paramnum -le $paramcount && test -z ${nextparam} && paramnum=$( expr ${paramnum} - 1 ) fi } define_if_new() { # call: define_if_new IFW_IN_LOG_FILE "/var/log/messages" eval thisval="\${${1}}" test -z "${thisval}" && eval "$1"=\"$2\" } # INITIALIZE VARIABLES server=$( hostname -s ) thisos="$( uname -s )" # get thisflavor and thisflavorversion. Examples: centos, ubuntu, redhat if test -f /etc/os-release ; then eval thisflavor=$( grep -iE "^\s*ID=" /etc/os-release 2>/dev/null | sed 's/^.*=//;' | tr 'A-Z' 'a-z' ) eval thisflavorversion=$( grep -iE "^\s*PRETTY_NAME=" /etc/os-release 2>/dev/null | sed -e 's/^.*=//;' | tr -dc '0-9.' ) elif test -f /etc/system-release && test $( wc -l < /etc/system-release 2>/dev/null ) -eq 1 ; then eval thisflavor=$( awk '{print $1}' < /etc/system-release 2>/dev/null | tr 'A-Z' 'a-z' ) eval thisflavorversion=$( /dev/null | tr -dc '0-9.' ) else if test "${thisos}" = "FreeBSD" ; then thisflavor="$( uname -i )" ; thisflavorversion="$( uname -r )" ; else thisflavor="other" thisflavorversion="unknown" fi fi case "${thisos}" in FreeBSD) sed=gsed ;; *) sed=sed ;; esac # if framework is dot sourced then $0 will be "-bash" and screw things up case ${0} in "-bash") scriptdir="$( pwd )" scriptfile="dot-sourced" ;; *) scriptdir="$( cd $( dirname ${0} ) ; pwd )" scriptfile="$( basename ${0} | sed 's!/./!/!g;s!\./!!g' )" scripttrim="${scriptfile%%.sh}" ;; esac # SPECIAL RUNTIME-RELATED VARIABLES { test "$USER" = "root" || test "$( stat -c '%u' /proc/$$/exe 2>/dev/null )" = 0 ; } && is_root=1 test -n "$SUDO_USER" && is_root="sudo" nullflagcount=0 validateparams() { # VALIDATE PARAMETERS # scroll through all parameters and check for isflag. # if isflag, get all flags listed. Also grab param#. paramcount=$# thiscount=0 ;thisopt=0 ;freeopt=0 ; varsyet=0 paramnum=0 debug=0 fallopts= while test $paramnum -lt $paramcount ; do paramnum=$( expr ${paramnum} + 1 ) eval param=\${$paramnum} nextparamnum=$( expr ${paramnum} + 1 ) eval nextparam=\${$nextparamnum} case $param in "-") if test "$varsyet" = "0" ; then # first instance marks beginning of flags and parameters. #Until then it was the names of variables to fill. varsyet=1 else nullflagcount=$( expr ${nullflagcount} + 1 ) #useful for separating flags from something else? debuglev 10 && ferror "null flag!" # second instance is null flag. fi ;; esac if test -n "$param" ; then # parameter $param exists. if test $(isflag $param) -gt 0 ; then # IS FLAG parseParam else # IS VALUE if test "$varsyet" = "0" ; then thisopt=$( expr ${thisopt} + 1 ) test "${param}" = "DEBUG" && debug=10 && thisopt=$( expr ${thisopt} - 1 ) || \ eval "varname${thisopt}=${param}" #varname[${thisopt}]="${param}" debuglev 10 && ferror "var \"${param}\" named" else thiscount=$( expr ${thiscount} + 1 ) test $thiscount -gt $thisopt && freeopt=$( expr ${freeopt} + 1 ) #eval ${varname[${thiscount}]:-opt${freeopt}}="\"${param}\"" eval "thisvarname=\${varname${thiscount}}" test -z "${thisvarname}" && eval "thisvarname=opt${freeopt}" eval "${thisvarname}=\"${param}\"" eval fallopts=\"${fallopts} ${param}\" debuglev 10 && ferror "${thisvarname} value: ${param}" fi fi fi done fallopts="${fallopts# }" if debuglev 10 ; then ferror "thiscount=$thiscount" ferror "fallopts=$fallopts" ferror "Framework $fversion" ferror "Finput $fiversion" fi } ### END IMPORT OF FRAMEWORK.SH define_if_new logfile "/var/log/systemctl.log" # VALIDATE PARAMETERS # objects before the dash are options, which get filled with the optvals # to debug flags, use option DEBUG. Variables set in framework: fallopts validateparams action - "$@" # LEARN EX_DEBUG test -z "${__debug_set_by_param}" && fisnum "${SYSTEMCTL_DEBUG}" && debug="${SYSTEMCTL_DEBUG}" debug=10 # CONFIGURE VARIABLES AFTER PARAMETERS _trimmed="$( echo "${@}" | tr -d '\r\n' )" log_to_file "${0} ${_trimmed}" case "${0}" in *hostnamectl|*systemd-detect-virt) exit 0 ;; # always just short-circuit esac # MAIN LOOP # actions actionlist="" case "${action}" in restart|start|stop|status|reload|condrestart|try-restart|reload-or-try-restart) # re-map a few actions case "${action}" in "reload-or-try-restart"|try-restart) action=restart ;; esac x=1 while test ${x:-${thiscount}} -le $(( thiscount - 1 )) && test ${thiscount} -gt 1 ; do eval thisopt="\${opt${x}}" thisopt="$( echo "${thisopt}" | sed -r -e 's/\.service$//;' )" actionstatement="$( printf "%s" "service ${thisopt} ${action};" )" actionlist="${actionlist:+${actionlist} }${actionstatement}" x=$(( x + 1 )) done ;; preset) x=1 while test ${x:-${thiscount}} -le $(( thiscount - 1 )) && test ${thiscount} -gt 1 ; do eval thisopt="\${opt${x}}" thisopt="$( echo "${thisopt}" | sed -r -e 's/\.service$//;' )" actionstatement="$( printf "%s" "update-rc.d ${thisopt} defaults;" )" actionlist="${actionlist:+${actionlist} }${actionstatement}" x=$(( x + 1 )) done ;; enable|disable|mask|unmask) case "${action}" in mask) action=disable ;; unmask) action=enable ;; esac x=1 while test ${x:-${thiscount}} -le $(( thiscount - 1 )) && test ${thiscount} -gt 1 ; do eval thisopt="\${opt${x}}" thisopt="$( echo "${thisopt}" | sed -r -e 's/\.service$//;' )" actionstatement="$( printf "%s" "update-rc.d ${thisopt} ${action};" )" actionlist="${actionlist:+${actionlist} }${actionstatement}" test "${SYSTEMCTL_NOW}" = "1" && { case "${action}" in enable) nowaction=start ;; disable) nowaction=stop ;; esac actionstatement="$( printf "%s" "service ${thisopt} ${nowaction:-stop};" )" actionlist="${actionlist:+${actionlist} }${actionstatement}" } x=$(( x + 1 )) done ;; daemon-reload) debuglev 1 && echo "${action} is a NOP." ;; list-unit-files) # Future improvement: can consume --full, but I do not care enough to deal with it now. ls -Al /etc/init.d ;; is-enabled) currentrunlevel="$( who -r | grep -oE 'run-level\s+[[:digit:]]+' | awk '{print $NF}' )" responsenumber=1 # loop through each service on the command line x=1 while test ${x:-${thiscount}} -le $(( thiscount - 1 )) && test ${thiscount} -gt 1 ; do eval thisopt="\${opt${x}}" thisopt="$( echo "${thisopt}" | sed -r -e 's/\.service$//;' )" #actionstatement="$( printf "%s" "service ${thisopt} ${action};" )" scriptfile="$( find "/etc/rc${currentrunlevel}.d" -mindepth 1 -maxdepth 1 -name "S??${thisopt}" 2>/dev/null )" responsetext="disabled" # if file exists, let us return 0. if test -n "${scriptfile}" ; then debuglev 2 && echo "${scriptfile}" responsenumber=0 # any "enabled" response makes systemctl return 0 responsetext="enabled" fi echo "${responsetext:-UNKNOWN}" x=$(( x + 1 )) done exit "${responsenumber}" ;; is-active) responsenumber=3 x=1 while test ${x:-${thiscount}} -le $(( thiscount - 1 )) && test ${thiscount} -gt 1 ; do eval thisopt="\${opt${x}}" thisopt="$( echo "${thisopt}" | sed -r -e 's/\.service$//;' )" #actionstatement="$( printf "%s" "service ${thisopt} ${action};" )" servicestatus="$( service "${thisopt}" status 1>/dev/null 2>&1 ; echo "${?}" )" responsetext="stopped" # if file exists, let us return 0. if test ${servicestatus:-1} -eq 0 ; then responsenumber=0 responsetext="active" fi echo "${responsetext:-unknown}" x=$(( x + 1 )) done exit "${responsenumber}" ;; *) ferror "Fatal! 2. Unable to understand action ${action}. Aborted." exit 2 ;; esac if test "${0}" = "/bin/systemctl" && test "$( readlink -f /bin/systemctl )" = "/usr/sbin/systemctl" ; then log_to_file "META: removing /bin/systemctl symlink" unlink /bin/systemctl export FIX_BIN_SYSTEMCTL=1 fi # list of actions if test -n "${actionlist}" ; then #debuglev 1 && ferror "Full list: ${actionlist}" printf "%s" "${actionlist}" | tr ';' '\n' | while read thisaction ; do log_to_file "ACTION: ${thisaction}" debuglev 5 && ferror "${thisaction}" eval "${thisaction}" done fi if test "${FIX_BIN_SYSTEMCTL}" = "1" ; then log_to_file "META: restoring /bin/systemctl symlink" ln -s /usr/sbin/systemctl /bin/systemctl fi # exit cleanly :