1# $NetBSD: rc.subr,v 1.111 2022/05/22 11:27:33 andvar Exp $
2#
3# Copyright (c) 1997-2011 The NetBSD Foundation, Inc.
4# All rights reserved.
5#
6# This code is derived from software contributed to The NetBSD Foundation
7# by Luke Mewburn.
8#
9# Redistribution and use in source and binary forms, with or without
10# modification, are permitted provided that the following conditions
11# are met:
12# 1. Redistributions of source code must retain the above copyright
13#    notice, this list of conditions and the following disclaimer.
14# 2. Redistributions in binary form must reproduce the above copyright
15#    notice, this list of conditions and the following disclaimer in the
16#    documentation and/or other materials provided with the distribution.
17#
18# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
19# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
22# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28# POSSIBILITY OF SUCH DAMAGE.
29#
30# rc.subr
31#         functions used by various rc scripts
32#
33
34: ${rcvar_manpage:='rc.conf(5)'}
35: ${RC_PID:=$$} ; export RC_PID
36nl='
37' # a literal newline
38
39# RC variables to clear on start.
40_env_clear_rc_vars="
41RC_PID=
42_rc_pid=
43_rc_original_stdout_fd=
44_rc_original_stderr_fd=
45_rc_postprocessor_fd=
46_rc_kill_ntries=
47"
48
49export PATH=/sbin:/bin:/usr/sbin:/usr/bin
50#
51#         functions
52#         ---------
53
54#
55# checkyesno var
56#         Test $1 variable.
57#         Return 0 if it's "yes" (et al), 1 if it's "no" (et al), 2 otherwise.
58#
59checkyesnox()
60{
61          eval _value=\$${1}
62          case $_value in
63
64                    #         "yes", "true", "on", or "1"
65          [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
66                    return 0
67                    ;;
68
69                    #         "no", "false", "off", or "0"
70          [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0)
71                    return 1
72                    ;;
73          *)
74                    return 2
75                    ;;
76          esac
77}
78
79#
80# checkyesno var
81#         Test $1 variable, and warn if not set to YES or NO.
82#         Return 0 if it's "yes" (et al), nonzero otherwise.
83#
84checkyesno()
85{
86          local var
87
88          checkyesnox $1
89          var=$?
90          case "${var}" in
91          ( 0 | 1 ) return $var;;
92          esac
93          warn "\$${1} is not set properly - see ${rcvar_manpage}."
94          return 1
95}
96
97#
98# yesno_to_truefalse var
99#         Convert the value of a variable from any of the values
100#         understood by checkyesno() to "true" or "false".
101#
102yesno_to_truefalse()
103{
104          local var=$1
105          if checkyesno $var; then
106                    eval $var=true
107                    return 0
108          else
109                    eval $var=false
110                    return 1
111          fi
112}
113
114#
115# reverse_list list
116#         print the list in reverse order
117#
118reverse_list()
119{
120          _revlist=
121          for _revfile; do
122                    _revlist="$_revfile $_revlist"
123          done
124          echo $_revlist
125}
126
127#
128# If booting directly to multiuser, send SIGTERM to
129# the parent (/etc/rc) to abort the boot.
130# Otherwise just exit.
131#
132stop_boot()
133{
134          if [ "$autoboot" = yes ]; then
135                    echo "ERROR: ABORTING BOOT (sending SIGTERM to parent)!"
136                    kill -TERM ${RC_PID}
137          fi
138          exit 1
139}
140
141#
142# mount_critical_filesystems type
143#         Go through the list of critical file systems as provided in
144#         the rc.conf(5) variable $critical_filesystems_${type}, checking
145#         each one to see if it is mounted, and if it is not, mounting it.
146#         It's not an error if file systems prefixed with "OPTIONAL:"
147#         are not mentioned in /etc/fstab.
148#
149mount_critical_filesystems()
150{
151          eval _fslist=\$critical_filesystems_${1}
152          _mountcrit_es=0
153          for _fs in $_fslist; do
154                    _optional=false
155                    case "$_fs" in
156                    OPTIONAL:*)
157                              _optional=true
158                              _fs="${_fs#*:}"
159                              ;;
160                    esac
161                    _ismounted=false
162                    # look for a line like "${fs} on * type *"
163                    # or "* on ${fs} type *" in the output from mount.
164                    case "${nl}$( mount )${nl}" in
165                    *" on ${_fs} type "*)
166                              _ismounted=true
167                              ;;
168                    *"${nl}${_fs} on "*)
169                              _ismounted=true
170                              ;;
171                    esac
172                    if $_ismounted; then
173                              print_rc_metadata \
174                              "note:File system ${_fs} was already mounted"
175                    else
176                              _mount_output=$( mount $_fs 2>&1 )
177                              _mount_es=$?
178                              case "$_mount_output" in
179                              *"${nl}"*)
180                                        # multiple lines can't be good,
181                                        # not even if $_optional is true
182                                        ;;
183                              *[uU]'nknown special file or file system'*)
184                                        if $_optional; then
185                                                  # ignore this error
186                                                  print_rc_metadata \
187                              "note:Optional file system ${_fs} is not present"
188                                                  _mount_es=0
189                                                  _mount_output=""
190                                        fi
191                                        ;;
192                              esac
193                              if [ -n "$_mount_output" ]; then
194                                        printf >&2 "%s\n" "$_mount_output"
195                              fi
196                              if [ "$_mount_es" != 0 ]; then
197                                        _mountcrit_es="$_mount_es"
198                              fi
199                    fi
200          done
201          return $_mountcrit_es
202}
203
204#
205# mount_critical_filesystems_zfs
206#         Go through the list of critical ZFS mountpoints as provided in
207#         the rc.conf(5) variable $critical_filesystems_zfs, checking
208#         each one to see if it is mounted, and if it is not, mounting it.
209#         It's not an error if file systems prefixed with "OPTIONAL:"
210#         aren't ZFS mountpoints.
211mount_critical_filesystems_zfs()
212{
213          _fslist=$critical_filesystems_zfs
214          _tab="    "
215          _mountcrit_es=0
216          for _fs in $_fslist; do
217                    _optional=false
218                    case "$_fs" in
219                    OPTIONAL:*)
220                              _optional=true
221                              _fs="${_fs#*:}"
222                              ;;
223                    esac
224
225                    _dataset=$(
226                              zfs list -H -o mountpoint,name |
227                              while read _line ; do
228                                        _dataset=''
229                                        case "$_line" in
230                                        "${_fs}${_tab}"*)
231                                                  _dataset="${_line#*${_tab}}"
232                                                  ;;
233                                        esac
234                                        if [ -n "$_dataset" ]; then
235                                                  case "$( zfs get -H -o value canmount $_dataset )" in
236                                                  on)
237                                                            echo -n "$_dataset"
238                                                            break ;;
239                                                  *) # noauto|off - dataset isn't supposed to be mounted
240                                                            ;;
241                                                  esac
242                                        fi
243                              done)
244
245                    if [ -z "$_dataset" ]; then
246                              if $_optional; then
247                                        # ignore this error
248                                        print_rc_metadata \
249                                        "note:Optional file system $_fs is not present"
250                              else
251                                        printf >&2 "%s\n" "No suitable ZFS dataset found for mountpoint $_fs"
252                                        _mountcrit_es=1
253                              fi
254                    else
255                              _mount_es=
256                              case "$( zfs get -H -o value mounted $_dataset )" in
257                              yes)
258                                        _mount_es=1
259                                        print_rc_metadata \
260                                        "note:File system $_fs was already mounted"
261                                        ;;
262                              *) # no
263                                        zfs mount "$_dataset" >/dev/null
264                                        _mount_es=$?
265                                        ;;
266                              esac
267
268                              if [ $_mount_es -ne 0 ]; then
269                                        _mountcrit_es="$_mount_es"
270                              fi
271                    fi
272          done
273          return $_mountcrit_es
274}
275
276#
277# check_pidfile pidfile procname [interpreter]
278#         Parses the first line of pidfile for a PID, and ensures
279#         that the process is running and matches procname.
280#         Prints the matching PID upon success, nothing otherwise.
281#         interpreter is optional; see _find_processes() for details.
282#
283check_pidfile()
284{
285          _pidfile=$1
286          _procname=$2
287          _interpreter=$3
288          if [ -z "$_pidfile" ] || [ -z "$_procname" ]; then
289                    err 3 'USAGE: check_pidfile pidfile procname [interpreter]'
290          fi
291          if [ ! -f $_pidfile ]; then
292                    return
293          fi
294          read _pid _junk < $_pidfile
295          if [ -z "$_pid" ]; then
296                    return
297          fi
298          _find_processes $_procname ${_interpreter:-.} '-p '"$_pid"
299}
300
301#
302# check_process procname [interpreter]
303#         Ensures that a process (or processes) named procname is running.
304#         Prints a list of matching PIDs.
305#         interpreter is optional; see _find_processes() for details.
306#
307check_process()
308{
309          _procname=$1
310          _interpreter=$2
311          if [ -z "$_procname" ]; then
312                    err 3 'USAGE: check_process procname [interpreter]'
313          fi
314          _find_processes $_procname ${_interpreter:-.} '-A'
315}
316
317#
318# _find_processes procname interpreter psargs
319#         Search for procname in the output of ps generated by psargs.
320#         Prints the PIDs of any matching processes, space separated.
321#
322#         If interpreter == ".", check the following variations of procname
323#         against the first word of each command:
324#                   procname
325#                   `basename procname`
326#                   `basename procname` + ":"
327#                   "(" + `basename procname` + ")"
328#
329#         If interpreter != ".", read the first line of procname, remove the
330#         leading #!, normalise whitespace, append procname, and attempt to
331#         match that against each command, either as is, or with extra words
332#         at the end.  As an alternative, to deal with interpreted daemons
333#         using perl, the basename of the interpreter plus a colon is also
334#         tried as the prefix to procname.
335#
336_find_processes()
337{
338          if [ $# -ne 3 ]; then
339                    err 3 'USAGE: _find_processes procname interpreter psargs'
340          fi
341          _procname=$1
342          _interpreter=$2
343          _psargs=$3
344
345          _pref=
346          _procnamebn=${_procname##*/}
347          if [ $_interpreter != "." ]; then       # an interpreted script
348                    read _interp < ${_chroot:-}/$_procname  # read interpreter name
349                    _interp=${_interp#\#!}                  # strip #!
350                    set -- $_interp
351                    if [ $1 = "/usr/bin/env" ]; then
352                              shift
353                              set -- $(type $1)
354                              shift $(($# - 1))
355                              _interp="${1##*/} $_procname"
356                    else
357                              _interp="$* $_procname"
358                    fi
359                    if [ $_interpreter != $1 ]; then
360                              warn "\$command_interpreter $_interpreter != $1"
361                    fi
362                    _interpbn=${1##*/}
363                    _fp_args='_argv'
364                    _fp_match='case "$_argv" in
365                        ${_interp}|"${_interp} "*|"${_interpbn}: "*${_procnamebn}*)'
366          else                                              # a normal daemon
367                    _fp_args='_arg0 _argv'
368                    _fp_match='case "$_arg0" in
369                        $_procname|$_procnamebn|${_procnamebn}:|"(${_procnamebn})")'
370          fi
371
372          _proccheck='
373                    ps -o "pid,args" '"$_psargs"' 2>&1 |
374                    while read _npid '"$_fp_args"'; do
375                              case "$_npid" in
376                              ps:|PID)
377                                        continue ;;
378                              esac ; '"$_fp_match"'
379                                        echo -n "$_pref$_npid" ;
380                                        _pref=" "
381                                        ;;
382                              esac
383                    done'
384
385#echo 1>&2 "proccheck is :$_proccheck:"
386          eval $_proccheck
387}
388
389#
390# kill_pids signal pid [pid ...]
391#         kills the given pids with signal.
392#         returns the list of pids killed successfully.
393#
394kill_pids()
395{
396          local signal=$1
397          shift
398          local list="$*"
399          local j=
400          local nlist=
401          for j in $list; do
402                    if kill -$signal $j 2>/dev/null; then
403                              nlist="${nlist}${nlist:+ }$j"
404                    fi
405          done
406          echo $nlist
407}
408
409#
410# wait_for_pids pid [pid ...]
411#         spins until none of the pids exist
412#         if _rc_kill_ntries is set and exceeded, it SIGKILLS the remaining
413#         pids
414#
415wait_for_pids()
416{
417          local ntries=0
418          local prefix=
419          local nlist=
420          local list="$*"
421
422          if [ -z "$list" ]; then
423                    return
424          fi
425
426          while true; do
427                    nlist=$(kill_pids 0 $list)
428                    if [ -z "$nlist" ]; then
429                              break
430                    fi
431                    if [ "$list" != "$nlist" ]; then
432                              list=$nlist
433                              echo -n ${prefix:-"Waiting for PIDS: "}$list
434                              prefix=", "
435                    fi
436                    # We want this to be a tight loop for a fast exit
437                    sleep 0.05
438                    ntries=$((ntries + 1))
439                    if [ -n "${_rc_kill_ntries}" ]; then
440                              if [ ${ntries} -gt ${_rc_kill_ntries} ]; then
441                                        kill_pids 9 $list > /dev/null
442                              fi
443                    fi
444          done
445          if [ -n "$prefix" ]; then
446                    echo "."
447          fi
448}
449
450#
451# run_rc_command argument [parameters]
452#         Search for argument in the list of supported commands, which is:
453#                   "start stop restart rcvar status poll ${extra_commands}"
454#         If there's a match, run ${argument}_cmd or the default method
455#         (see below), and pass the optional list of parameters to it.
456#
457#         If argument has a given prefix, then change the operation as follows:
458#                   Prefix    Operation
459#                   ------    ---------
460#                   fast      Skip the pid check, and set rc_fast=yes
461#                   force     Set ${rcvar} to YES, and set rc_force=yes
462#                   one       Set ${rcvar} to YES
463#
464#         The following globals are used:
465#
466#         Name                Needed    Purpose
467#         ----                ------    -------
468#         name                y         Name of script.
469#
470#         command             n         Full path to command.
471#                                       Not needed if ${rc_arg}_cmd is set for
472#                                       each keyword.
473#
474#         command_args        n         Optional args/shell directives for command.
475#
476#         command_interpreter n         If not empty, command is interpreted, so
477#                                       call check_{pidfile,process}() appropriately.
478#
479#         extra_commands      n         List of extra commands supported.
480#
481#         pidfile             n         If set, use check_pidfile $pidfile $command,
482#                                       otherwise use check_process $command.
483#                                       In either case, only check if $command is set.
484#
485#         procname  n         Process name to check for instead of $command.
486#
487#         rcvar               n         This is checked with checkyesno to determine
488#                                       if the action should be run.
489#
490#         ${name}_chroot      n         Directory to chroot to before running ${command}
491#                                       Requires /usr to be mounted.
492#
493#         ${name}_chdir       n         Directory to cd to before running ${command}
494#                                       (if not using ${name}_chroot).
495#
496#         ${name}_flags       n         Arguments to call ${command} with.
497#                                       NOTE:     $flags from the parent environment
498#                                                 can be used to override this.
499#
500#         ${name}_env         n         Additional environment variable settings
501#                                       for running ${command}
502#
503#         ${name}_nice        n         Nice level to run ${command} at.
504#
505#         ${name}_user        n         User to run ${command} as, using su(1) if not
506#                                       using ${name}_chroot.
507#                                       Requires /usr to be mounted.
508#
509#         ${name}_group       n         Group to run chrooted ${command} as.
510#                                       Requires /usr to be mounted.
511#
512#         ${name}_groups      n         Comma separated list of supplementary groups
513#                                       to run the chrooted ${command} with.
514#                                       Requires /usr to be mounted.
515#
516#         ${rc_arg}_cmd       n         If set, use this as the method when invoked;
517#                                       Otherwise, use default command (see below)
518#
519#         ${rc_arg}_precmd n  If set, run just before performing the
520#                                       ${rc_arg}_cmd method in the default
521#                                       operation (i.e, after checking for required
522#                                       bits and process (non)existence).
523#                                       If this completes with a non-zero exit code,
524#                                       don't run ${rc_arg}_cmd.
525#
526#         ${rc_arg}_postcmd n If set, run just after performing the
527#                                       ${rc_arg}_cmd method, if that method
528#                                       returned a zero exit code.
529#
530#         required_dirs       n         If set, check for the existence of the given
531#                                       directories before running the default
532#                                       (re)start command.
533#
534#         required_files      n         If set, check for the readability of the given
535#                                       files before running the default (re)start
536#                                       command.
537#
538#         required_vars       n         If set, perform checkyesno on each of the
539#                                       listed variables before running the default
540#                                       (re)start command.
541#
542#         Default behaviour for a given argument, if no override method is
543#         provided:
544#
545#         Argument  Default behaviour
546#         --------  -----------------
547#         start               if !running && checkyesno ${rcvar}
548#                                       ${command}
549#
550#         stop                if ${pidfile}
551#                                       rc_pid=$(check_pidfile $pidfile $command)
552#                             else
553#                                       rc_pid=$(check_process $command)
554#                             kill $sig_stop $rc_pid
555#                             wait_for_pids $rc_pid
556#                             ($sig_stop defaults to TERM.)
557#
558#         reload              Similar to stop, except use $sig_reload instead,
559#                             and doesn't wait_for_pids.
560#                             $sig_reload defaults to HUP.
561#
562#         restart             Run `stop' then `start'.
563#
564#         status              Show if ${command} is running, etc.
565#
566#         poll                Wait for ${command} to exit.
567#
568#         rcvar               Display what rc.conf variable is used (if any).
569#
570#         Variables available to methods, and after run_rc_command() has
571#         completed:
572#
573#         Variable  Purpose
574#         --------  -------
575#         rc_arg              Argument to command, after fast/force/one processing
576#                             performed
577#
578#         rc_flags  Flags to start the default command with.
579#                             Defaults to ${name}_flags, unless overridden
580#                             by $flags from the environment.
581#                             This variable may be changed by the precmd method.
582#
583#         rc_pid              PID of command (if appropriate)
584#
585#         rc_fast             Not empty if "fast" was provided (q.v.)
586#
587#         rc_force  Not empty if "force" was provided (q.v.)
588#
589#
590run_rc_command()
591{
592          rc_arg=$1
593          if [ -z "$name" ]; then
594                    err 3 'run_rc_command: $name is not set.'
595          fi
596
597          _rc_prefix=
598          case "$rc_arg" in
599          fast*)                                  # "fast" prefix; don't check pid
600                    rc_arg=${rc_arg#fast}
601                    rc_fast=yes
602                    ;;
603          force*)                                 # "force" prefix; always run
604                    rc_force=yes
605                    _rc_prefix=force
606                    rc_arg=${rc_arg#${_rc_prefix}}
607                    if [ -n "${rcvar}" ]; then
608                              eval ${rcvar}=YES
609                    fi
610                    ;;
611          one*)                                   # "one" prefix; set ${rcvar}=yes
612                    _rc_prefix=one
613                    rc_arg=${rc_arg#${_rc_prefix}}
614                    if [ -n "${rcvar}" ]; then
615                              eval ${rcvar}=YES
616                    fi
617                    ;;
618          esac
619
620          _keywords="start stop restart rcvar"
621          if [ -n "$extra_commands" ]; then
622                    _keywords="${_keywords} ${extra_commands}"
623          fi
624          rc_pid=
625          _pidcmd=
626          _procname=${procname:-${command}}
627
628                                                  # setup pid check command if not fast
629          if [ -z "$rc_fast" ] && [ -n "$_procname" ]; then
630                    if [ -n "$pidfile" ]; then
631                              _pidcmd='rc_pid=$(check_pidfile '"$pidfile $_procname $command_interpreter"')'
632                    else
633                              _pidcmd='rc_pid=$(check_process '"$_procname $command_interpreter"')'
634                    fi
635                    if [ -n "$_pidcmd" ]; then
636                              _keywords="${_keywords} status poll"
637                    fi
638          fi
639
640          if [ -z "$rc_arg" ]; then
641                    rc_usage "$_keywords"
642          fi
643          shift     # remove $rc_arg from the positional parameters
644
645          if [ -n "$flags" ]; then      # allow override from environment
646                    rc_flags=$flags
647          else
648                    eval rc_flags=\$${name}_flags
649          fi
650          eval _chdir=\$${name}_chdir   _chroot=\$${name}_chroot \
651              _nice=\$${name}_nice      _user=\$${name}_user \
652              _group=\$${name}_group    _groups=\$${name}_groups \
653              _env=\"\$${name}_env\"
654
655          if [ -n "$_user" ]; then      # unset $_user if running as that user
656                    if [ "$_user" = "$(id -un)" ]; then
657                              unset _user
658                    fi
659          fi
660
661                                                  # if ${rcvar} is set, and $1 is not
662                                                  # "rcvar", then run
663                                                  #         checkyesno ${rcvar}
664                                                  # and return if that failed or warn
665                                                  # user and exit when interactive
666                                                  #
667          if [ -n "${rcvar}" ] && [ "$rc_arg" != "rcvar" ]; then
668                    if ! checkyesno ${rcvar}; then
669                                                  # check whether interactive or not
670                              if [ -n "$_run_rc_script" ]; then
671                                        return 0
672                              fi
673                              for _elem in $_keywords; do
674                                        if [ "$_elem" = "$rc_arg" ]; then
675                                                  cat 1>&2 <<EOF
676\$${rcvar} is not enabled - see ${rcvar_manpage}.
677Use the following if you wish to perform the operation:
678  $0 one${rc_arg}
679EOF
680                                                  exit 1
681                                        fi
682                              done
683                              echo 1>&2 "$0: unknown directive '$rc_arg'."
684                              rc_usage "$_keywords"
685                    fi
686          fi
687
688          eval $_pidcmd                           # determine the pid if necessary
689
690          for _elem in $_keywords; do
691                    if [ "$_elem" != "$rc_arg" ]; then
692                              continue
693                    fi
694
695                                                  # if there's a custom ${XXX_cmd},
696                                                  # run that instead of the default
697                                                  #
698                    eval _cmd=\$${rc_arg}_cmd _precmd=\$${rc_arg}_precmd \
699                        _postcmd=\$${rc_arg}_postcmd
700                    if [ -n "$_cmd" ]; then
701                                                  # if the precmd failed and force
702                                                  # isn't set, exit
703                                                  #
704                              if ! eval $_precmd && [ -z "$rc_force" ]; then
705                                        return 1
706                              fi
707
708                              if ! eval $_cmd \"\${@}\" && [ -z "$rc_force" ]; then
709                                        return 1
710                              fi
711                              eval $_postcmd
712                              return 0
713                    fi
714
715                    if [ ${#} -gt 0 ]; then
716                              err 1 "the $rc_arg command does not take any parameters"
717                    fi
718
719                    case "$rc_arg" in   # default operations...
720
721                    status)
722                              if [ -n "$rc_pid" ]; then
723                                        echo "${name} is running as pid $rc_pid."
724                              else
725                                        echo "${name} is not running."
726                                        return 1
727                              fi
728                              ;;
729
730                    start)
731                              if [ -n "$rc_pid" ]; then
732                                        echo 1>&2 "${name} already running? (pid=$rc_pid)."
733                                        exit 1
734                              fi
735
736                              if [ ! -x ${_chroot}${command} ]; then
737                                        return 0
738                              fi
739
740                                                  # check for required variables,
741                                                  # directories, and files
742                                                  #
743                              for _f in $required_vars; do
744                                        if ! checkyesno $_f; then
745                                                  warn "\$${_f} is not enabled."
746                                                  if [ -z "$rc_force" ]; then
747                                                            return 1
748                                                  fi
749                                        fi
750                              done
751                              for _f in $required_dirs; do
752                                        if [ ! -d "${_f}/." ]; then
753                                                  warn "${_f} is not a directory."
754                                                  if [ -z "$rc_force" ]; then
755                                                            return 1
756                                                  fi
757                                        fi
758                              done
759                              for _f in $required_files; do
760                                        if [ ! -r "${_f}" ]; then
761                                                  warn "${_f} is not readable."
762                                                  if [ -z "$rc_force" ]; then
763                                                            return 1
764                                                  fi
765                                        fi
766                              done
767
768                                                  # if the precmd failed and force
769                                                  # isn't set, exit
770                                                  #
771                              if ! eval $_precmd && [ -z "$rc_force" ]; then
772                                        return 1
773                              fi
774
775                                                  # setup the command to run, and run it
776                                                  #
777                              echo "Starting ${name}."
778                              if [ -n "$_chroot" ]; then
779                                        _doit="\
780$_env_clear_rc_vars $_env \
781${_nice:+nice -n $_nice }\
782chroot ${_user:+-u $_user }${_group:+-g $_group }${_groups:+-G $_groups }\
783$_chroot $command $rc_flags $command_args"
784                              else
785                                        _doit="\
786${_chdir:+cd $_chdir; }\
787$_env_clear_rc_vars $_env \
788${_nice:+nice -n $_nice }\
789$command $rc_flags $command_args"
790                                        if [ -n "$_user" ]; then
791                                            _doit="su -m $_user -c 'sh -c \"$_doit\"'"
792                                        fi
793                              fi
794
795                                                  # if the cmd failed and force
796                                                  # isn't set, exit
797                                                  #
798                              if ! eval $_doit && [ -z "$rc_force" ]; then
799                                        return 1
800                              fi
801
802                                                  # finally, run postcmd
803                                                  #
804                              eval $_postcmd
805                              ;;
806
807                    stop)
808                              if [ -z "$rc_pid" ]; then
809                                        if [ -n "$pidfile" ]; then
810                                                  echo 1>&2 \
811                                            "${name} not running? (check $pidfile)."
812                                        else
813                                                  echo 1>&2 "${name} not running?"
814                                        fi
815                                        exit 1
816                              fi
817
818                                                  # if the precmd failed and force
819                                                  # isn't set, exit
820                                                  #
821                              if ! eval $_precmd && [ -z "$rc_force" ]; then
822                                        return 1
823                              fi
824
825                                                  # send the signal to stop
826                                                  #
827                              echo "Stopping ${name}."
828                              _doit="kill -${sig_stop:-TERM} $rc_pid"
829                              if [ -n "$_user" ]; then
830                                        _doit="su -m $_user -c 'sh -c \"$_doit\"'"
831                              fi
832
833                                                  # if the stop cmd failed and force
834                                                  # isn't set, exit
835                                                  #
836                              if ! eval $_doit && [ -z "$rc_force" ]; then
837                                        return 1
838                              fi
839
840                                                  # wait for the command to exit,
841                                                  # and run postcmd.
842                              wait_for_pids $rc_pid
843                              eval $_postcmd
844                              ;;
845
846                    reload)
847                              if [ -z "$rc_pid" ]; then
848                                        if [ -n "$pidfile" ]; then
849                                                  echo 1>&2 \
850                                            "${name} not running? (check $pidfile)."
851                                        else
852                                                  echo 1>&2 "${name} not running?"
853                                        fi
854                                        exit 1
855                              fi
856                              echo "Reloading ${name} config files."
857                              if ! eval $_precmd && [ -z "$rc_force" ]; then
858                                        return 1
859                              fi
860                              _doit="kill -${sig_reload:-HUP} $rc_pid"
861                              if [ -n "$_user" ]; then
862                                        _doit="su -m $_user -c 'sh -c \"$_doit\"'"
863                              fi
864                              if ! eval $_doit && [ -z "$rc_force" ]; then
865                                        return 1
866                              fi
867                              eval $_postcmd
868                              ;;
869
870                    restart)
871                              if ! eval $_precmd && [ -z "$rc_force" ]; then
872                                        return 1
873                              fi
874                                                  # prevent restart being called more
875                                                  # than once by any given script
876                                                  #
877                              if ${_rc_restart_done:-false}; then
878                                        return 0
879                              fi
880                              _rc_restart_done=true
881
882                              ( $0 ${_rc_prefix}stop )
883                              $0 ${_rc_prefix}start
884
885                              eval $_postcmd
886                              ;;
887
888                    poll)
889                              if [ -n "$rc_pid" ]; then
890                                        wait_for_pids $rc_pid
891                              fi
892                              ;;
893
894                    rcvar)
895                              echo "# $name"
896                              if [ -n "$rcvar" ]; then
897                                        if checkyesno ${rcvar}; then
898                                                  echo "${rcvar}=YES"
899                                        else
900                                                  echo "${rcvar}=NO"
901                                        fi
902                              fi
903                              ;;
904
905                    *)
906                              rc_usage "$_keywords"
907                              ;;
908
909                    esac
910                    return 0
911          done
912
913          echo 1>&2 "$0: unknown directive '$rc_arg'."
914          rc_usage "$_keywords"
915          exit 1
916}
917
918#
919# _have_rc_postprocessor
920#         Test whether the current script is running in a context that
921#         was invoked from /etc/rc with a postprocessor.
922#
923#         If the test fails, some variables may be unset to make
924#         such tests more efficient in future.
925#
926_have_rc_postprocessor()
927{
928          # Cheap tests that fd and pid are set, fd is writable.
929          [ -n "${_rc_pid}" ] || { unset _rc_pid; return 1; }
930          [ -n "${_rc_postprocessor_fd}" ] || { unset _rc_pid; return 1; }
931          eval ": >&${_rc_postprocessor_fd}" 2>/dev/null \
932          || { unset _rc_pid; return 1; }
933
934          return 0
935}
936
937#
938# run_rc_script file arg
939#         Start the script `file' with `arg', and correctly handle the
940#         return value from the script.  If `file' ends with `.sh', it's
941#         sourced into the current environment.  If `file' appears to be
942#         a backup or scratch file, ignore it.  Otherwise if it's
943#         executable run as a child process.
944#
945#         If `file' contains "KEYWORD: interactive" and if we are
946#         running inside /etc/rc with postprocessing, then the script's
947#         stdout and stderr are redirected to $_rc_original_stdout_fd and
948#         $_rc_original_stderr_fd, so the output will be displayed on the
949#         console but not intercepted by /etc/rc's postprocessor.
950#
951run_rc_script()
952{
953          _file=$1
954          _arg=$2
955          if [ -z "$_file" ] || [ -z "$_arg" ]; then
956                    err 3 'USAGE: run_rc_script file arg'
957          fi
958
959          _run_rc_script=true
960
961          unset     name command command_args command_interpreter \
962                    extra_commands pidfile procname \
963                    rcvar required_dirs required_files required_vars
964          eval unset ${_arg}_cmd ${_arg}_precmd ${_arg}_postcmd
965
966          _must_redirect=false
967          if _have_rc_postprocessor \
968              && _has_rcorder_keyword interactive $_file
969          then
970                    _must_redirect=true
971          fi
972
973          case "$_file" in
974          *.sh)                                   # run in current shell
975                    if $_must_redirect; then
976                              print_rc_metadata \
977                                  "note:Output from ${_file} is not logged"
978                              no_rc_postprocess eval \
979                                  'set $_arg ; . $_file'
980                    else
981                              set $_arg ; . $_file
982                    fi
983                    ;;
984          *[~#]|*.OLD|*.orig|*,v)                 # scratch file; skip
985                    warn "Ignoring scratch file $_file"
986                    ;;
987          *)                                      # run in subshell
988                    if [ -x $_file ] && $_must_redirect; then
989                              print_rc_metadata \
990                                  "note:Output from ${_file} is not logged"
991                              if [ -n "$rc_fast_and_loose" ]; then
992                                        no_rc_postprocess eval \
993                                            'set $_arg ; . $_file'
994                              else
995                                        no_rc_postprocess eval \
996                                            '( set $_arg ; . $_file )'
997                              fi
998                    elif [ -x $_file ]; then
999                              if [ -n "$rc_fast_and_loose" ]; then
1000                                        set $_arg ; . $_file
1001                              else
1002                                        ( set $_arg ; . $_file )
1003                              fi
1004                    else
1005                              warn "Ignoring non-executable file $_file"
1006                    fi
1007                    ;;
1008          esac
1009}
1010
1011#
1012# load_rc_config command
1013#         Source in the configuration file for a given command.
1014#
1015load_rc_config()
1016{
1017          _command=$1
1018          if [ -z "$_command" ]; then
1019                    err 3 'USAGE: load_rc_config command'
1020          fi
1021
1022          if ${_rc_conf_loaded:-false}; then
1023                    :
1024          else
1025                    . /etc/rc.conf
1026                    _rc_conf_loaded=true
1027          fi
1028          if [ -f /etc/rc.conf.d/"$_command" ]; then
1029                    . /etc/rc.conf.d/"$_command"
1030          fi
1031}
1032
1033#
1034# load_rc_config_var cmd var
1035#         Read the rc.conf(5) var for cmd and set in the
1036#         current shell, using load_rc_config in a subshell to prevent
1037#         unwanted side effects from other variable assignments.
1038#
1039load_rc_config_var()
1040{
1041          if [ $# -ne 2 ]; then
1042                    err 3 'USAGE: load_rc_config_var cmd var'
1043          fi
1044          eval $(eval '(
1045                    load_rc_config '$1' >/dev/null;
1046                    if [ -n "${'$2'}" ] || [ "${'$2'-UNSET}" != "UNSET" ]; then
1047                              echo '$2'=\'\''${'$2'}\'\'';
1048                    fi
1049          )' )
1050}
1051
1052#
1053# rc_usage commands
1054#         Print a usage string for $0, with `commands' being a list of
1055#         valid commands.
1056#
1057rc_usage()
1058{
1059          echo -n 1>&2 "Usage: $0 [fast|force|one]("
1060
1061          _sep=
1062          for _elem; do
1063                    echo -n 1>&2 "$_sep$_elem"
1064                    _sep="|"
1065          done
1066          echo 1>&2 ")"
1067          exit 1
1068}
1069
1070#
1071# err exitval message
1072#         Display message to stderr and log to the syslog, and exit with exitval.
1073#
1074err()
1075{
1076          exitval=$1
1077          shift
1078
1079          if [ -x /usr/bin/logger ]; then
1080                    logger "$0: ERROR: $*"
1081          fi
1082          echo 1>&2 "$0: ERROR: $*"
1083          exit $exitval
1084}
1085
1086#
1087# warn message
1088#         Display message to stderr and log to the syslog.
1089#
1090warn()
1091{
1092          if [ -x /usr/bin/logger ]; then
1093                    logger "$0: WARNING: $*"
1094          fi
1095          echo 1>&2 "$0: WARNING: $*"
1096}
1097
1098#
1099# backup_file action file cur backup
1100#         Make a backup copy of `file' into `cur', and save the previous
1101#         version of `cur' as `backup' or use rcs for archiving.
1102#
1103#         This routine checks the value of the backup_uses_rcs variable,
1104#         which can be either YES or NO.
1105#
1106#         The `action' keyword can be one of the following:
1107#
1108#         add                 `file' is now being backed up (and is possibly
1109#                             being reentered into the backups system).  `cur'
1110#                             is created and RCS files, if necessary, are
1111#                             created as well.
1112#
1113#         update              `file' has changed and needs to be backed up.
1114#                             If `cur' exists, it is copied to to `back' or
1115#                             checked into RCS (if the repository file is old),
1116#                             and then `file' is copied to `cur'.  Another RCS
1117#                             check in done here if RCS is being used.
1118#
1119#         remove              `file' is no longer being tracked by the backups
1120#                             system.  If RCS is not being used, `cur' is moved
1121#                             to `back', otherwise an empty file is checked in,
1122#                             and then `cur' is removed.
1123#
1124#
1125backup_file()
1126{
1127          _action=$1
1128          _file=$2
1129          _cur=$3
1130          _back=$4
1131
1132          if checkyesno backup_uses_rcs; then
1133                    _msg0="backup archive"
1134                    _msg1="update"
1135
1136                    # ensure that history file is not locked
1137                    if [ -f $_cur,v ]; then
1138                              rcs -q -u -U -M $_cur
1139                    fi
1140
1141                    # ensure after switching to rcs that the
1142                    # current backup is not lost
1143                    if [ -f $_cur ]; then
1144                              # no archive, or current newer than archive
1145                              if [ ! -f $_cur,v ] || [ $_cur -nt $_cur,v ]; then
1146                                        ci -q -f -u -t-"$_msg0" -m"$_msg1" $_cur
1147                                        rcs -q -kb -U $_cur
1148                                        co -q -f -u $_cur
1149                              fi
1150                    fi
1151
1152                    case $_action in
1153                    add|update)
1154                              cp -p $_file $_cur
1155                              ci -q -f -u -t-"$_msg0" -m"$_msg1" $_cur
1156                              rcs -q -kb -U $_cur
1157                              co -q -f -u $_cur
1158                              chown root:wheel $_cur $_cur,v
1159                              ;;
1160                    remove)
1161                              cp /dev/null $_cur
1162                              ci -q -f -u -t-"$_msg0" -m"$_msg1" $_cur
1163                              rcs -q -kb -U $_cur
1164                              chown root:wheel $_cur $_cur,v
1165                              rm $_cur
1166                              ;;
1167                    esac
1168          else
1169                    case $_action in
1170                    add|update)
1171                              if [ -f $_cur ]; then
1172                                        cp -p $_cur $_back
1173                              fi
1174                              cp -p $_file $_cur
1175                              chown root:wheel $_cur
1176                              ;;
1177                    remove)
1178                              mv -f $_cur $_back
1179                              ;;
1180                    esac
1181          fi
1182}
1183
1184#
1185# handle_fsck_error fsck_exit_code
1186#         Take action depending on the return code from fsck.
1187#
1188handle_fsck_error()
1189{
1190          case $1 in
1191          0)        # OK
1192                    return
1193                    ;;
1194          2)        # Needs re-run, still fs errors
1195                    echo "File system still has errors; re-run fsck manually!"
1196                    ;;
1197          4)        # Root modified
1198                    echo "Root file system was modified, rebooting ..."
1199                    reboot -n
1200                    echo "Reboot failed; help!"
1201                    ;;
1202          8)        # Check failed
1203                    echo "Automatic file system check failed; help!"
1204                    ;;
1205          12)       # Got signal
1206                    echo "Boot interrupted."
1207                    ;;
1208          *)
1209                    echo "Unknown error $1; help!"
1210                    ;;
1211          esac
1212          stop_boot
1213}
1214
1215#
1216# _has_rcorder_keyword word file
1217#         Check whether a file contains a "# KEYWORD:" comment with a
1218#         specified keyword in the style used by rcorder(8).
1219#
1220_has_rcorder_keyword()
1221{
1222          local word="$1"
1223          local file="$2"
1224          local line
1225
1226          [ -r "$file" ] || return 1
1227          while read line; do
1228                    case "${line} " in
1229                    "# KEYWORD:"*[\ \   ]"${word}"[\ \      ]*)
1230                              return 0
1231                              ;;
1232                    "#"*)
1233                              continue
1234                              ;;
1235                    *[A-Za-z0-9]*)
1236                              # give up at the first non-empty non-comment line
1237                              return 1
1238                              ;;
1239                    esac
1240          done <"$file"
1241          return 1
1242}
1243
1244#
1245# print_rc_metadata string
1246#         Print the specified string in such a way that the post-processor
1247#         inside /etc/rc will treat it as meta-data.
1248#
1249#         If we are not running inside /etc/rc, do nothing.
1250#
1251#         For public use by any rc.d script, the string must begin with
1252#         "note:", followed by arbitrary text.  The intent is that the text
1253#         will appear in a log file but not on the console.
1254#
1255#         For private use within /etc/rc, the string must contain a
1256#         keyword recognised by the rc_postprocess_metadata() function
1257#         defined in /etc/rc, followed by a colon, followed by one or more
1258#         colon-separated arguments associated with the keyword.
1259#
1260print_rc_metadata()
1261{
1262          # _rc_postprocessor fd, if defined, is the fd to which we must
1263          # print, prefixing the output with $_rc_metadata_prefix.
1264          #
1265          if _have_rc_postprocessor; then
1266                    command printf "%s%s\n" "$rc_metadata_prefix" "$1" \
1267                              >&${_rc_postprocessor_fd}
1268          fi
1269}
1270
1271#
1272# _flush_rc_output
1273#         Arrange for output to be flushed, if we are running
1274#         inside /etc/rc with postprocessing.
1275#
1276_flush_rc_output()
1277{
1278          print_rc_metadata "nop"
1279}
1280
1281#
1282# print_rc_normal [-n] string
1283#         Print the specified string in such way that it is treated as
1284#         normal output, regardless of whether or not we are running
1285#         inside /etc/rc with post-processing.
1286#
1287#         If "-n" is specified in $1, then the string in $2 is printed
1288#         without a newline; otherwise, the string in $1 is printed
1289#         with a newline.
1290#
1291#         Intended use cases include:
1292#
1293#         o   An rc.d script can use ``print_rc_normal -n'' to print a
1294#             partial line in such a way that it appears immediately
1295#             instead of being buffered by rc(8)'s post-processor.
1296#
1297#         o   An rc.d script that is run via the no_rc_postprocess
1298#             function (so most of its output is invisible to rc(8)'s
1299#             post-processor) can use print_rc_normal to force some of its
1300#             output to be seen by the post-processor.
1301#
1302#
1303print_rc_normal()
1304{
1305          # print to stdout or _rc_postprocessor_fd, depending on
1306          # whether not we have an rc postprocessor.
1307          #
1308          local fd=1
1309          _have_rc_postprocessor && fd="${_rc_postprocessor_fd}"
1310          case "$1" in
1311          "-n")
1312                    command printf "%s" "$2" >&${fd}
1313                    _flush_rc_output
1314                    ;;
1315          *)
1316                    command printf "%s\n" "$1" >&${fd}
1317                    ;;
1318          esac
1319}
1320
1321#
1322# no_rc_postprocess cmd...
1323#         Execute the specified command in such a way that its output
1324#         bypasses the post-processor that handles the output from
1325#         most commands that are run inside /etc/rc.  If we are not
1326#         inside /etc/rc, then just execute the command without special
1327#         treatment.
1328#
1329#         The intent is that interactive commands can be run via
1330#         no_rc_postprocess(), and their output will appear immediately
1331#         on the console instead of being hidden or delayed by the
1332#         post-processor.      An unfortunate consequence of the output
1333#         bypassing the post-processor is that the output will not be
1334#         logged.
1335#
1336no_rc_postprocess()
1337{
1338          if _have_rc_postprocessor; then
1339                    "$@" >&${_rc_original_stdout_fd} 2>&${_rc_original_stderr_fd}
1340          else
1341                    "$@"
1342          fi
1343}
1344
1345#
1346# twiddle
1347#         On each call, print a different one of "/", "-", "\\", "|",
1348#         followed by a backspace.  The most recently printed value is
1349#         saved in $_twiddle_state.
1350#
1351#         Output is to /dev/tty, so this function may be useful even inside
1352#         a script whose output is redirected.
1353#
1354twiddle()
1355{
1356          case "$_twiddle_state" in
1357          '/')      _next='-' ;;
1358          '-')      _next='\' ;;
1359          '\')      _next='|' ;;
1360          *)        _next='/' ;;
1361          esac
1362          command printf "%s\b" "$_next" >/dev/tty
1363          _twiddle_state="$_next"
1364}
1365
1366#
1367# human_exit_code
1368#         Print the a human version of the exit code.
1369#
1370human_exit_code()
1371{
1372          if [ "$1" -lt 127 ]
1373          then
1374                    echo "exited with code $1"
1375          elif [ "$(expr $1 % 256)" -eq 127 ]
1376          then
1377                    # This cannot really happen because the shell will not
1378                    # pass stopped job status out and the exit code is limited
1379                    # to 8 bits. This code is here just for completeness.
1380                    echo "stopped with signal $(expr $1 / 256)"
1381          else
1382                    echo "terminated with signal $(expr $1 - 128)"
1383          fi
1384}
1385
1386#
1387# collapse_backslash_newline
1388#         Copy input to output, collapsing <backslash><newline>
1389#         to nothing, but leaving other backslashes alone.
1390#
1391collapse_backslash_newline()
1392{
1393          local line
1394          while read -r line ; do
1395                    case "$line" in
1396                    *\\)
1397                              # print it, without the backslash or newline
1398                              command printf "%s" "${line%?}"
1399                              ;;
1400                    *)
1401                              # print it, with a newline
1402                              command printf "%s\n" "${line}"
1403                              ;;
1404                    esac
1405          done
1406}
1407
1408# Shell implementations of basename and dirname, usable before
1409# the /usr file system is mounted.
1410#
1411basename()
1412{
1413          local file="$1"
1414          local suffix="$2"
1415          local base
1416
1417          base="${file##*/}"            # remove up to and including last '/'
1418          base="${base%${suffix}}"      # remove suffix, if any
1419          command printf "%s\n" "${base}"
1420}
1421
1422dirname()
1423{
1424          local file="$1"
1425          local dir
1426
1427          case "$file" in
1428          /*/*)     dir="${file%/*}" ;; # common case: absolute path
1429          /*)       dir="/" ;;                    # special case: name in root dir
1430          */*)      dir="${file%/*}" ;; # common case: relative path with '/'
1431          *)        dir="." ;;                    # special case: name without '/'
1432          esac
1433          command printf "%s\n" "${dir}"
1434}
1435
1436# Override the normal "echo" and "printf" commands, so that
1437# partial lines printed by rc.d scripts appear immediately,
1438# instead of being buffered by rc(8)'s post-processor.
1439#
1440# Naive use of the echo or printf commands from rc.d scripts,
1441# elsewhere in rc.subr, or anything else that sources rc.subr,
1442# will call these functions.  To call the real echo and printf
1443# commands, use "command echo" or "command printf".
1444#
1445# Avoid use of echo altogether as much as possible, printf works better
1446#
1447echo()
1448{
1449          local IFS=' ' NL='\n'         # not a literal newline...
1450
1451          case "$1" in
1452          -n)       NL=; shift;;
1453          esac
1454
1455          command printf "%s${NL}" "$*"
1456
1457          if test -z "${NL}"
1458          then
1459                    _flush_rc_output
1460          fi
1461          return 0
1462}
1463
1464printf()
1465{
1466          command printf "$@"
1467          case "$1" in
1468          *'\n')    : ;;
1469          *)        _flush_rc_output ;;
1470          esac
1471          return 0
1472}
1473
1474kat() {
1475          local i
1476          local v
1477          for i; do
1478                    while read -r v; do
1479                              v="${v%%#*}"
1480                              if [ -z "$v" ]; then
1481                                        continue
1482                              fi
1483                              echo "$v"
1484                    done < "$i"
1485          done
1486}
1487
1488_rc_subr_loaded=:
1489