1#!/bin/ksh 2# 3# $OpenBSD: rcctl.sh,v 1.105 2016/09/07 13:13:13 ajacoutot Exp $ 4# 5# Copyright (c) 2014, 2015 Antoine Jacoutot <ajacoutot@openbsd.org> 6# Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org> 7# 8# Permission to use, copy, modify, and distribute this software for any 9# purpose with or without fee is hereby granted, provided that the above 10# copyright notice and this permission notice appear in all copies. 11# 12# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 15# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 18# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 20_special_svcs="accounting check_quotas ipsec multicast pf spamd_black" 21readonly _special_svcs 22 23# get local functions from rc.subr(8) 24FUNCS_ONLY=1 25. /etc/rc.d/rc.subr 26_rc_parse_conf 27 28usage() 29{ 30 local _a _i 31 for _i in ${_rc_actions}; do _a="$(echo -n ${_i}${_a:+|${_a}})"; done 32 33 _rc_err \ 34 "usage: rcctl get|getdef|set service | daemon [variable [arguments]] 35 rcctl [-df] ${_a} daemon ... 36 rcctl disable|enable|order [daemon ...] 37 rcctl ls all|failed|off|on|started|stopped" 38} 39 40needs_root() 41{ 42 [ "$(id -u)" -ne 0 ] && _rc_err "${0##*/}: \"$*\" needs root privileges" 43} 44 45rcctl_err() 46{ 47 _rc_err "${0##*/}: ${1}" ${2} 48} 49 50ls_rcscripts() 51{ 52 local _s 53 54 cd /etc/rc.d && set -- * 55 for _s; do 56 [[ ${_s} = *.* ]] && continue 57 [ ! -d "${_s}" ] && echo "${_s}" 58 done 59} 60 61pkg_scripts_append() 62{ 63 local _svc=$1 64 [ -n "${_svc}" ] || return 65 66 rcconf_edit_begin 67 if [ -z "${pkg_scripts}" ]; then 68 echo pkg_scripts="${_svc}" >>${_TMP_RCCONF} 69 elif ! echo ${pkg_scripts} | grep -qw -- ${_svc}; then 70 grep -v "^pkg_scripts.*=" /etc/rc.conf.local >${_TMP_RCCONF} 71 echo pkg_scripts="${pkg_scripts} ${_svc}" >>${_TMP_RCCONF} 72 fi 73 rcconf_edit_end 74} 75 76pkg_scripts_order() 77{ 78 local _svcs="$*" 79 [ -n "${_svcs}" ] || return 80 81 needs_root ${action} 82 local _pkg_scripts _svc 83 for _svc in ${_svcs}; do 84 if svc_is_base ${_svc} || svc_is_special ${_svc}; then 85 rcctl_err "${_svc} is not a pkg script" 86 elif ! svc_get ${_svc} status; then 87 rcctl_err "${_svc} is not enabled" 88 fi 89 done 90 _pkg_scripts=$(echo "${_svcs} ${pkg_scripts}" | tr "[:blank:]" "\n" | \ 91 awk -v ORS=' ' '!x[$0]++') 92 rcconf_edit_begin 93 grep -v "^pkg_scripts.*=" /etc/rc.conf.local >${_TMP_RCCONF} 94 echo pkg_scripts=${_pkg_scripts} >>${_TMP_RCCONF} 95 rcconf_edit_end 96} 97 98pkg_scripts_rm() 99{ 100 local _svc=$1 101 [ -n "${_svc}" ] || return 102 103 [ -z "${pkg_scripts}" ] && return 104 105 rcconf_edit_begin 106 sed "/^pkg_scripts[[:>:]]/{s/[[:<:]]${_svc}[[:>:]]//g 107 s/['\"]//g;s/ *= */=/;s/ */ /g;s/ $//;/=$/d;}" \ 108 /etc/rc.conf.local >${_TMP_RCCONF} 109 rcconf_edit_end 110} 111 112rcconf_edit_begin() 113{ 114 _TMP_RCCONF=$(mktemp -p /etc -t rc.conf.local.XXXXXXXXXX) || \ 115 rcctl_err "cannot create temporary file under /etc" 116 if [ -f /etc/rc.conf.local ]; then 117 cat /etc/rc.conf.local >${_TMP_RCCONF} || \ 118 rcctl_err "cannot append to ${_TMP_RCCONF}" 119 else 120 touch /etc/rc.conf.local || \ 121 rcctl_err "cannot create /etc/rc.conf.local" 122 fi 123} 124 125rcconf_edit_end() 126{ 127 sort -u -o ${_TMP_RCCONF} ${_TMP_RCCONF} || \ 128 rcctl_err "cannot modify ${_TMP_RCCONF}" 129 cat ${_TMP_RCCONF} >/etc/rc.conf.local || \ 130 rcctl_err "cannot append to /etc/rc.conf.local" 131 if [ ! -s /etc/rc.conf.local ]; then 132 rm /etc/rc.conf.local || \ 133 rcctl_err "cannot remove /etc/rc.conf.local" 134 fi 135 rm -f ${_TMP_RCCONF} 136 _rc_parse_conf # reload new values 137} 138 139svc_is_avail() 140{ 141 local _svc=$1 142 _rc_check_name "${_svc}" || return 143 144 [ -x "/etc/rc.d/${_svc}" ] && return 145 svc_is_special ${_svc} 146} 147 148svc_is_base() 149{ 150 local _svc=$1 151 [ -n "${_svc}" ] || return 152 153 local _cached _ret 154 155 _cached=$(eval echo \${cached_svc_is_base_${_svc}}) 156 [ "${_cached}" ] && return "${_cached}" 157 158 grep -qw "^${_svc}_flags" /etc/rc.conf 159 _ret=$? 160 161 set -A cached_svc_is_base_${_svc} -- ${_ret} 162 return ${_ret} 163} 164 165svc_is_meta() 166{ 167 local _svc=$1 168 [ -n "${_svc}" ] || return 169 170 local _cached _ret 171 172 _cached=$(eval echo \${cached_svc_is_meta_${_svc}}) 173 [ "${_cached}" ] && return "${_cached}" 174 175 [ -r "/etc/rc.d/${_svc}" ] && ! grep -qw "^rc_cmd" /etc/rc.d/${_svc} 176 _ret=$? 177 178 set -A cached_svc_is_meta_${_svc} -- ${_ret} 179 return ${_ret} 180} 181 182svc_is_special() 183{ 184 local _svc=$1 185 [ -n "${_svc}" ] || return 186 187 local _cached _ret 188 189 _cached=$(eval echo \${cached_svc_is_special_${_svc}}) 190 [ "${_cached}" ] && return "${_cached}" 191 192 echo ${_special_svcs} | grep -qw -- ${_svc} 193 _ret=$? 194 195 set -A cached_svc_is_special_${_svc} -- ${_ret} 196 return ${_ret} 197} 198 199svc_ls() 200{ 201 local _lsarg=$1 202 [ -n "${_lsarg}" ] || return 203 204 # we do not want to return the "status" nor the rc.d(8) script retcode 205 local _ret=0 _on _svc _started 206 207 case ${_lsarg} in 208 all) 209 ( 210 ls_rcscripts 211 echo ${_special_svcs} | tr "[:blank:]" "\n" 212 ) | sort 213 ;; 214 failed) 215 for _svc in $(svc_ls on); do 216 ! svc_is_special ${_svc} && \ 217 ! /etc/rc.d/${_svc} check >/dev/null && \ 218 echo ${_svc} && _ret=1 219 done 220 ;; 221 off|on) 222 for _svc in $(svc_ls all); do 223 svc_get ${_svc} status && _on=1 224 [ "${_lsarg}" = "on" -a -n "${_on}" ] || \ 225 [ "${_lsarg}" = "off" -a -z "${_on}" ] && \ 226 echo ${_svc} 227 unset _on 228 done 229 ;; 230 started|stopped) 231 for _svc in $(ls_rcscripts); do 232 /etc/rc.d/${_svc} check >/dev/null && _started=1 233 [ "${_lsarg}" = "started" -a -n "${_started}" ] || \ 234 [ "${_lsarg}" = "stopped" -a -z "${_started}" ] && \ 235 echo ${_svc} 236 unset _started 237 done 238 ;; 239 *) 240 _ret=1 241 esac 242 243 return ${_ret} 244} 245 246svc_get() 247{ 248 local _svc=$1 249 [ -n "${_svc}" ] || return 250 251 local _status=0 _val _var=$2 252 local daemon_class daemon_flags daemon_rtable daemon_timeout daemon_user 253 254 if svc_is_special ${_svc}; then 255 daemon_flags="$(eval echo \${${_svc}})" 256 else 257 # set pkg daemon_flags to "NO" to match base svc 258 if ! svc_is_base ${_svc}; then 259 if ! echo ${pkg_scripts} | grep -qw -- ${_svc}; then 260 daemon_flags="NO" 261 fi 262 fi 263 264 if ! svc_is_meta ${_svc}; then 265 # these are expensive, make sure they are explicitely requested 266 if [ -z "${_var}" -o "${_var}" = "class" ]; then 267 getcap -f /etc/login.conf ${_svc} 1>/dev/null 2>&1 && \ 268 daemon_class=${_svc} 269 [ -z "${daemon_class}" ] && \ 270 daemon_class="$(svc_getdef ${_svc} class)" 271 fi 272 if [[ -z ${_var} || ${_var} == @(flags|status) ]]; then 273 [ -z "${daemon_flags}" ] && \ 274 daemon_flags="$(eval echo \"\${${_svc}_flags}\")" 275 [ -z "${daemon_flags}" ] && \ 276 daemon_flags="$(svc_getdef ${_svc} flags)" 277 fi 278 if [ -z "${_var}" -o "${_var}" = "rtable" ]; then 279 [ -z "${daemon_rtable}" ] && \ 280 daemon_rtable="$(eval echo \"\${${_svc}_rtable}\")" 281 [ -z "${daemon_rtable}" ] && \ 282 daemon_rtable="$(svc_getdef ${_svc} rtable)" 283 fi 284 if [ -z "${_var}" -o "${_var}" = "timeout" ]; then 285 [ -z "${daemon_timeout}" ] && \ 286 daemon_timeout="$(eval echo \"\${${_svc}_timeout}\")" 287 [ -z "${daemon_timeout}" ] && \ 288 daemon_timeout="$(svc_getdef ${_svc} timeout)" 289 fi 290 if [ -z "${_var}" -o "${_var}" = "user" ]; then 291 [ -z "${daemon_user}" ] && \ 292 daemon_user="$(eval echo \"\${${_svc}_user}\")" 293 [ -z "${daemon_user}" ] && \ 294 daemon_user="$(svc_getdef ${_svc} user)" 295 fi 296 fi 297 fi 298 299 [ "${daemon_flags}" = "NO" ] && _status=1 300 301 if [ -n "${_var}" ]; then 302 [ "${_var}" = "status" ] && return ${_status} 303 eval _val=\${daemon_${_var}} 304 [ -z "${_val}" ] || print -r -- "${_val}" 305 else 306 svc_is_meta ${_svc} && return ${_status} 307 if svc_is_special ${_svc}; then 308 echo "${_svc}=${daemon_flags}" 309 else 310 echo "${_svc}_class=${daemon_class}" 311 echo "${_svc}_flags=${daemon_flags}" 312 echo "${_svc}_rtable=${daemon_rtable}" 313 echo "${_svc}_timeout=${daemon_timeout}" 314 echo "${_svc}_user=${daemon_user}" 315 fi 316 return ${_status} 317 fi 318} 319 320# to prevent namespace pollution, only call in a subshell 321svc_getdef() 322{ 323 local _svc=$1 324 [ -n "${_svc}" ] || return 325 326 local _status=0 _val _var=$2 327 local daemon_class daemon_flags daemon_rtable daemon_timeout daemon_user 328 329 if svc_is_special ${_svc}; then 330 # unconditionally parse: we always output flags and/or status 331 _rc_parse_conf /etc/rc.conf 332 daemon_flags="$(eval echo \${${_svc}})" 333 [ "${daemon_flags}" = "NO" ] && _status=1 334 else 335 if ! svc_is_base ${_svc}; then 336 _status=1 # all pkg_scripts are off by default 337 else 338 # abuse /etc/rc.conf behavior of only setting flags 339 # to empty or "NO" to get our default status; 340 # we'll get our default flags from the rc.d script 341 [[ -z ${_var} || ${_var} == status ]] && \ 342 _rc_parse_conf /etc/rc.conf 343 [ "$(eval echo \${${_svc}_flags})" = "NO" ] && _status=1 344 fi 345 346 if ! svc_is_meta ${_svc}; then 347 rc_cmd() { } 348 . /etc/rc.d/${_svc} >/dev/null 2>&1 349 350 daemon_class=daemon 351 [ -z "${daemon_rtable}" ] && daemon_rtable=0 352 [ -z "${daemon_timeout}" ] && daemon_timeout=30 353 [ -z "${daemon_user}" ] && daemon_user=root 354 fi 355 fi 356 357 if [ -n "${_var}" ]; then 358 [ "${_var}" = "status" ] && return ${_status} 359 eval _val=\${daemon_${_var}} 360 [ -z "${_val}" ] || print -r -- "${_val}" 361 else 362 svc_is_meta ${_svc} && return ${_status} 363 if svc_is_special ${_svc}; then 364 echo "${_svc}=${daemon_flags}" 365 else 366 echo "${_svc}_class=${daemon_class}" 367 echo "${_svc}_flags=${daemon_flags}" 368 echo "${_svc}_rtable=${daemon_rtable}" 369 echo "${_svc}_timeout=${daemon_timeout}" 370 echo "${_svc}_user=${daemon_user}" 371 fi 372 return ${_status} 373 fi 374} 375 376svc_rm() 377{ 378 local _svc=$1 379 [ -n "${_svc}" ] || return 380 381 rcconf_edit_begin 382 if svc_is_special ${_svc}; then 383 grep -v "^${_svc}.*=" /etc/rc.conf.local >${_TMP_RCCONF} 384 ( svc_getdef ${_svc} status ) && \ 385 echo "${_svc}=NO" >>${_TMP_RCCONF} 386 else 387 grep -Ev "^${_svc}_(flags|rtable|timeout|user).*=" \ 388 /etc/rc.conf.local >${_TMP_RCCONF} 389 ( svc_getdef ${_svc} status ) && \ 390 echo "${_svc}_flags=NO" >>${_TMP_RCCONF} 391 fi 392 rcconf_edit_end 393} 394 395svc_set() 396{ 397 local _svc=$1 _var=$2 398 [ -n "${_svc}" -a -n "${_var}" ] || return 399 400 shift 2 401 local _args="$*" 402 403 # don't check if we are already enabled or disabled because rc.conf(8) 404 # defaults may have changed in which case we may have a matching 405 # redundant entry in rc.conf.local that we can drop 406 if [ "${_var}" = "status" ]; then 407 if [ "${_args}" = "on" ]; then 408 _var="flags" 409 # keep our flags if we're already enabled 410 eval "_args=\"\${${_svc}_${_var}}\"" 411 [ "${_args}" = "NO" ] && unset _args 412 if ! svc_is_base ${_svc} && ! svc_is_special ${_svc}; then 413 pkg_scripts_append ${_svc} 414 fi 415 elif [ "${_args}" = "off" ]; then 416 if ! svc_is_base ${_svc} && ! svc_is_special ${_svc}; then 417 pkg_scripts_rm ${_svc} 418 fi 419 svc_rm ${_svc} 420 return 421 else 422 rcctl_err "invalid status \"${_args}\"" 423 fi 424 else 425 svc_get ${_svc} status || \ 426 rcctl_err "${svc} is not enabled" 427 fi 428 429 if svc_is_special ${_svc}; then 430 [ "${_var}" = "flags" ] || return 431 rcconf_edit_begin 432 grep -v "^${_svc}.*=" /etc/rc.conf.local >${_TMP_RCCONF} 433 ( svc_getdef ${_svc} status ) || \ 434 echo "${_svc}=YES" >>${_TMP_RCCONF} 435 rcconf_edit_end 436 return 437 fi 438 439 if [ -n "${_args}" ]; then 440 if [ "${_var}" = "rtable" ]; then 441 [[ ${_args} != +([[:digit:]]) || ${_args} -lt 0 ]] && \ 442 rcctl_err "\"${_args}\" is not an integer" 443 fi 444 if [ "${_var}" = "timeout" ]; then 445 [[ ${_args} != +([[:digit:]]) || ${_args} -le 0 ]] && \ 446 rcctl_err "\"${_args}\" is not a positive integer" 447 fi 448 if [ "${_var}" = "user" ]; then 449 getent passwd "${_args}" >/dev/null || \ 450 rcctl_err "user \"${_args}\" does not exist" 451 fi 452 # unset flags if they match the default enabled ones 453 [ "${_args}" = "$(svc_getdef ${_svc} ${_var})" ] && \ 454 unset _args 455 fi 456 457 # protect leading whitespace 458 [ "${_args}" = "${_args# }" ] || _args="\"${_args}\"" 459 460 # reset: value may have changed 461 unset ${_svc}_${_var} 462 463 rcconf_edit_begin 464 grep -v "^${_svc}_${_var}.*=" /etc/rc.conf.local >${_TMP_RCCONF} 465 if [ -n "${_args}" ] || \ 466 ( svc_is_base ${_svc} && ! svc_getdef ${_svc} status && [ "${_var}" == "flags" ] ); then 467 echo "${_svc}_${_var}=${_args}" >>${_TMP_RCCONF} 468 fi 469 rcconf_edit_end 470} 471 472unset _RC_DEBUG _RC_FORCE 473while getopts "df" c; do 474 case "$c" in 475 d) _RC_DEBUG=-d;; 476 f) _RC_FORCE=-f;; 477 *) usage;; 478 esac 479done 480shift $((OPTIND-1)) 481[ $# -gt 0 ] || usage 482 483action=$1 484ret=0 485 486case ${action} in 487 ls) 488 lsarg=$2 489 [[ ${lsarg} == @(all|failed|off|on|started|stopped) ]] || usage 490 ;; 491 order) 492 shift 1 493 svcs="$*" 494 for svc in ${svcs}; do 495 svc_is_avail ${svc} || \ 496 rcctl_err "service ${svc} does not exist" 2 497 done 498 ;; 499 disable|enable|start|stop|restart|reload|check) 500 shift 1 501 svcs="$*" 502 [ -z "${svcs}" ] && usage 503 # it's ok to disable a non-existing daemon 504 if [ "${action}" != "disable" ]; then 505 for svc in ${svcs}; do 506 svc_is_avail ${svc} || \ 507 rcctl_err "service ${svc} does not exist" 2 508 done 509 fi 510 ;; 511 get|getdef) 512 svc=$2 513 var=$3 514 [ -z "${svc}" ] && usage 515 [ "${svc}" = "all" ] || svc_is_avail ${svc} || \ 516 rcctl_err "service ${svc} does not exist" 2 517 if [ -n "${var}" ]; then 518 [ "${svc}" = "all" ] && usage 519 [[ ${var} != @(class|flags|status|rtable|timeout|user) ]] && usage 520 if svc_is_meta ${svc}; then 521 [ "${var}" != "status" ] && \ 522 rcctl_err "/etc/rc.d/${svc} is a meta script, cannot \"${action} ${var}\"" 523 fi 524 if svc_is_special ${svc}; then 525 [[ ${var} == @(class|rtable|timeout|user) ]] && \ 526 rcctl_err "\"${svc}\" is a special variable, cannot \"${action} ${var}\"" 527 fi 528 fi 529 ;; 530 set) 531 svc=$2 532 var=$3 533 [ $# -ge 3 ] && shift 3 || shift $# 534 args="$*" 535 [ -z "${svc}" ] && usage 536 # it's ok to disable a non-existing daemon 537 if [ "${action} ${var} ${args}" != "set status off" ]; then 538 svc_is_avail ${svc} || \ 539 rcctl_err "service ${svc} does not exist" 2 540 fi 541 [[ ${var} != @(class|flags|rtable|status|timeout|user) ]] && usage 542 svc_is_meta ${svc} && [ "${var}" != "status" ] && \ 543 rcctl_err "/etc/rc.d/${svc} is a meta script, cannot \"${action} ${var}\"" 544 [[ ${var} = flags && ${args} = NO ]] && \ 545 rcctl_err "\"flags NO\" contradicts \"${action}\"" 546 if svc_is_special ${svc}; then 547 [[ ${var} != status ]] && \ 548 rcctl_err "\"${svc}\" is a special variable, cannot \"${action} ${var}\"" 549 fi 550 [[ ${var} == class ]] && \ 551 rcctl_err "\"${svc}_class\" is a read-only variable set in login.conf(5)" 552 ;; 553 *) 554 usage 555 ;; 556esac 557 558case ${action} in 559 disable) 560 needs_root ${action} 561 for svc in ${svcs}; do 562 svc_set ${svc} status off || ret=$?; 563 done 564 exit ${ret} 565 ;; 566 enable) 567 needs_root ${action} 568 for svc in ${svcs}; do 569 svc_set ${svc} status on || ret=$?; 570 done 571 exit ${ret} 572 ;; 573 get|getdef) 574 if [ "${svc}" = "all" ]; then 575 for svc in $(svc_ls all); do 576 ( svc_${action} ${svc} "${var}" ) 577 done 578 return 0 # we do not want the svc status 579 else 580 ( svc_${action} ${svc} "${var}" ) 581 fi 582 ;; 583 ls) 584 # some rc.d(8) scripts need root for rc_check() 585 [[ ${lsarg} == @(started|stopped|failed) ]] && needs_root ${action} ${lsarg} 586 svc_ls ${lsarg} 587 ;; 588 order) 589 if [ -n "${svcs}" ]; then 590 needs_root ${action} 591 pkg_scripts_order ${svcs} 592 else 593 [[ -z ${pkg_scripts} ]] || echo ${pkg_scripts} 594 fi 595 ;; 596 set) 597 needs_root ${action} 598 svc_set ${svc} "${var}" "${args}" 599 ;; 600 start|stop|restart|reload|check) 601 for svc in ${svcs}; do 602 if svc_is_special ${svc}; then 603 rcctl_err "\"${svc}\" is a special variable, no rc.d(8) script" 604 fi 605 /etc/rc.d/${svc} ${_RC_DEBUG} ${_RC_FORCE} ${action} || ret=$?; 606 done 607 exit ${ret} 608 ;; 609 *) 610 usage 611 ;; 612esac 613