You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

563 lines
17 KiB

#!/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
# 2023-02-11 Add /usr/bin/systemctl symlink control
# 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="2023-02-11a"
usage() {
${PAGER:-/usr/bin/less -F} >&2 <<ENDUSAGE
usage: systemctl [-duV] [-c conffile]
Provides a systemctl-like interface to sysvinit. Simulates various
actions on services: start, stop, restart, enable, disable, status,
mask, unmask, is-enabled, is-active, condrestart.
version ${systemctlversion}
-d debug Show debugging info, including parsed variables.
-u usage Show this usage block.
-V version Show script version number.
-c conf Read in this config file.
Return values:
0 Normal
1 Queried service is disabled/inactive
2 Count or type of flaglessvals is incorrect
3 Incorrect OS type
4 Unable to find dependency
5 Not run as root or sudo
ENDUSAGE
}
# DEFINE FUNCTIONS
log_to_file() {
flecho "${@}" >> "${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 ... <<EOFOPTIONS
# /bin/foo1 --optforfoo1
# /usr/bin/foo2 --optforfoo2
# EOFOPTIONS
# ^ 0 = soft fail, 1 = critical-fail
quitonfail="${1}" ; shift
_vars="${@}"
#echo "_vars=${_vars}"
_varcount=0
for _word in ${_vars} ; do _varcount=$( expr $_varcount + 1 ) ; eval "_var${_varcount}=${_word}" ; done
_usethis=0
while read line ;
do
_varcount=0
if test ! "${_usethis}x" = "0x" ; then break ; fi
#echo "line=${line}" ;
for _word in ${line} ;
do
_varcount=$( expr $_varcount + 1 )
#echo "word ${_varcount}=${_word}" ;
case "${_varcount}" in
1)
#echo "Testing for existence of file ${_word}"
if test -f "${_word}" ;
then
_usethis=1
#echo "${_var1}=${_word}"
eval "${_var1}=${_word}"
fi
;;
*)
#echo "just an option: ${_word}"
if test "${_usethis}x" = "1x" ;
then
#eval echo "\${_var${_varcount}}=${_word}"
eval eval "\${_var${_varcount}}=${_word}"
fi
;;
esac
done
done
#eval echo "testfile=\$${_var1}"
eval _testfile=\$${_var1}
if test ! -f "${_testfile}" ;
then
case "${quitonfail}" in 1) _failval="critical-fail" ;; *) _failval="fail" ;; esac
eval "${_var1}=${_failval}"
setvalout=${_failval}
else
eval setvalout="valid-${_var1}"
fi
}
flecho() {
# requires moreutils
if echo "${@}" | grep -qiE '.' ;
then
printf "%s\n" "${@}" | TZ=UTC ts "[%FT%TZ]${USER}@${server}:"
else
printf '' | TZ=UTC ts "[%FT%TZ]${USER}@${server}"
fi
}
ferror() {
# call: ferror "$scriptfile: 2. Something bad happened-- error message 2."
echo "$@" 1>&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=$( </etc/system-release sed -e 's/^.*=//;' 2>/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
if test "${0}" = "/usr/bin/systemctl" && test "$( readlink -f /usr/bin/systemctl )" = "/usr/sbin/systemctl" ;
then
log_to_file "META: removing /usr/bin/systemctl symlink"
unlink /usr/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
ln -s /usr/sbin/systemctl /usr/bin/systemctl 2>/dev/null # might fail if /usr/bin is symlink to /bin
fi
# exit cleanly
: