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
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
|
|
:
|
|
|