[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

RFC: fix for daemon start (2)



Hello everyone,

Here's an updated version of the RFC text, as well as a new version of the
initscriptquery reference script. The fragments.sh script is included just
for completeness, and was not modified.

Changelog:
 * fixed typos, updated documentation to an assertive tone
 * addressed rcS.d issue (I received no comments on this one, BTW)
 * handle multiple links per runlevel nicely
 * detect and block attempt to run initscriptquery at runlevel 0 or 6
 * address pre-depends/depends issue

Another small RFC dealing with administrative policy enforcement on the
start of initscripts will be sent in a few days. Due to the design of
initscriptquery, such policy enforcement could be done without any
additional changes to other packages.

There are no technical issues left for initscriptquery that I know of. It
should be now in its nearly final state.


RFC: Fix for initscripts being run out of their intended runlevel by
     package scripts (during package installs and upgrades).


Interface proposal for /usr/sbin/initscriptquery script:
--------------------------------------------------------

Assumptions:
  Debian uses only sysvinit-compatible initscripts, stored in /etc/init.d/
  (currently true for debian, file-rc uses the same /etc/init.d/ scripts as
  sysvinit does).

  The unique identifier for an initscript is the initscript ID required by
  the update-rc.d command.

Documented Command Line Interface:
  /usr/sbin/initscriptquery [-q] <initscript ID>

  -q : Run initscriptquery in silent mode, errors are NOT reported to stderr
  initscript ID:  the update-rc.d identifier for the initscript

  Future versions to this script MUST be fully backwards compatible.

Documented behaviour of the initscriptquery script:

   stdin shall not be used (it is NOT an interactive script)
   stdout shall be used only to output usage information.
   stderr shall be used to output all error messages.
   
   Exit status codes:
    0 - the initscript is allowed to be started
    1 - the initscript is NOT allowed be started
    2 - initscript ID unknown
    3 - initscript ID known, but behaviour is undefined
    4 - syntax error
  >=5 - other error (usually means inconsistency in the underlining
        initscript subsystem)

Verbose description of the exit status codes:

    0 - the initscript is allowed to be started

	sysvinit meaning: There is a non-dangling, executable S?? link in
	the rc?.d directory for the given script ID and current runlevel.

        Also, no other administrative reasons for not starting the init
	scripts exist.

	NOTE: If an initscript would be started at the S runlevel, it is
	assumed that this should be true for all runlevels that don't
	actually stop the initscript as well.

	Desired behaviour: Call /etc/init.d/initscript as you'd have done
	before the advent of initscriptquery

    1 - the initscript is NOT allowed be started

        sysvinit meaning: There is a non-dangling, executable K?? link (and
	no S?? link) in the rc?.d directory for the given script ID and
	current runlevel.

	Or, an administrative reason prohibits starting the initscript.

	Desired behaviour: Do not run the initscript. If the daemon is
	running, assume it's because the user started it manually and would
	like it to be left alone (feel free to issue a warning, though).

    2 - initscript ID unknown

        sysvinit meaning: There isn't a normal file with the name matching
	<initscript ID> in /etc/init.d/ (note that the existence of a
	S??initscriptID link/file is NOT enough, as we are constrained to
	the same namespace used by update-rc.d).

	This might happen if update-rc.d fails, if you forget to run
	update-rc.d BEFORE initscriptquery in the package scripts, or if the
	user removed the /etc/init.d/ script.

        Desired behaviour: Do whatever is appropriate for your package.
	Unless -q was given, initscriptquery will have already printed a
	warning to stderr. The calling script might want to try to start the
	script anyway (which will probably fail, BTW). 

    3 - initscript ID known, but behaviour is undefined

        sysvinit meaning: No S?? or K?? link in the proper rc?.d directory
	for the given initscript ID was found, but a /etc/init.d/ file for
	the given initscript iD does exist.

	Desired behaviour: For non-daemon-starting initscripts, it is
	undefined. Do whatever is *safer* for your package (start it anyway,
	do nothing, or stop it).

	Never start a daemon which isn't already running if you receive this
	status code. You can either restart the daemon, stop the daemon, or
	do nothing.  WARNING: don't use "/etc/init.d/initscript restart" for
	this operation unless the initscript is under your control, and
	known not to start a daemon which was not running.

    4 - Syntax error
    
        There is an error in the command line arguments. Fix your script.
	  
    5 (and above) - something wicked happened (assume it was an error)

        sysvinit meaning: S??initscriptID/K??initscriptID exists but it is
	not a link (this is flagged as an error because postinst scripts
	call the /etc/init.d/ script directly), or a dangling link.

	Do whatever is safer for your package. Warning messages will have
	been printed to stderr describing the problem, unless the -q option
	was given to initscriptquery.

The S runlevel:

  Scripts for the S runlevel are given special treatment. If an initscript
  would be started in the S runlevel, and would not be killed in the current
  runlevel, initscriptquery will return a status code of 0: start daemon
  (unless an error is detected).

  Packages providing runlevel S scripts are NOT exempt of the requirement of
  calling initscriptquery to verify if they're allowed to start an
  initscript automatically.  They might very well issue severe warnings of
  impending doom if told not to run the initscript, however.

Runlevels 0 and 6:

  initscriptquery will return a status code of 1: do not start daemon if it
  detects it's being run while the system is in a shutdown runlevel
  (runlevel 0 or runlevel 6). This is considered an error condition,
  obviously, and a warning message will be sent to stderr.

Important notes:

  1.  All packages which use the initscript system and automatically attempt
      to start an initscript (by using a parameter of start, restart,
      reload, or the force- versions of these) are REQUIRED to call
      initscriptquery beforehand to verify if they're allowed to run the
      initscript.

  2.  Packages depending on any future version of the initscriptquery
      interface will probably need to add versioned dependencies on the
      initscript system base packages (currently sysvinit and file-rc) which
      implement the new version.

      Packages depending on the initial version of the initscriptquery
      interface should not change their dependencies to include the base
      initscript system. They should fallback to the previous behaviour if
      initscriptquery is not available.

  3.  Be warned that initscriptquery adds the following non-optional
      "features" to your package:

      a) The package will do a non-complete integrity check on the
	 underlining initscript system data for their daemons, and will be
	 notified of errors (initscript exit status codes 2 and 5 for the
	 sysvinit version).

      b) Local policy enforcement, if any, may cause the package not to
	 start its initscripts. There is no way to differentiate if
	 initscriptquery returned a "do not start" exit status code because
	 the daemon is not supposed to run in the current runlevel, or
	 because the local administrator disallows it. This is done on
	 purpose, and by design.

	 Using suidregister to force a /etc/init.d/ script to be
	 non-executable is considered a valid administrative measure by
	 initscriptquery; It will return a "do not start" exit status in
	 this case. This is in tune with current practice, and cleaner than
	 the error message it causes currently.

  4.  Any package providing a initscript is REQUIRED to register the
      initscripts using update-rc.d (as per current policy), and to do it
      before using initscriptquery.


Sample code:
------------

  Attached to this rfc, you'll find a reference (functional and somewhat
  tested, as well as written for easy-of-reading) shell script
  implementation of /usr/sbin/initscriptquery for sysvinit.  If someone else
  would like to rewrite it better, or in perl, or whatever... go ahead :-)

  Also attached to this rfc, you'll find a sample fragment of a postinst
  script which uses initscriptquery to run a daemon. It's a bit big because
  of the comments, but it's quite simple and very easy to read and
  understand.  Again, if anyone cares to write a better example, you're
  welcome.

-- 
  "One disk to rule them all, One disk to find them. One disk to bring
  them all and in the darkness grind them. In the Land of Redmond
  where the shadows lie." -- The Silicon Valley Tarot
  Henrique Holschuh
#!/bin/sh  
#
# initscriptquery - Queries the underlying initscript system for the
#   current runlevel and verifies if a given script should be started 
#   in that runlevel.  
#
# $Id: initscriptquery,v 1.3.1.4 2000/09/13 01:18:04 hmh Exp $
#
# Usage: 
#   initscriptquery [--help] [-q] <initscript ID>
#
#   initscript ID - The unique ID for the initscript, as used by
#     update-rc.d
#
#   if -q is given, the script runs silently (no error messages are
#   displayed and the calling script must do so for status codes 2 and
#   above).
#
# Exit status codes:
#    0 - the initscript is allowed to be started [in this runlevel]
#    1 - the initscript is NOT allowed be started [in this runlevel]
#    2 - initscript ID unknown
#    3 - initscript ID known, but behaviour is undefined
#    4 - syntax error or --help function called
#  >=5 - other error
#
# (SysVinit /etc/rc?.d version for Debian's sysvinit package)
#
# initscriptquery  -  verifies if a initscript should be run
# Copyright (C) 2000 Henrique de Moraes Holschuh <hmh@rcm.org.br>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., 59
# Temple Place, Suite 330, Boston, MA 02111-1307 USA

RUNLEVEL=/sbin/runlevel
INITDPREFIX=/etc/init.d/
RCDPREFIX=/etc/rc
BEQUIET=

dohelp () {
 # 
 # outputs help and usage
 #
cat <<EOF
initscriptquery, SysVinit /etc/rc?.d version.

Usage: initscriptquery [--help] [-q] <initscript ID>
  initscript ID - initscript unique ID, as used for update-rc.d
  --help        - this message
  -q            - no error messages
EOF
}

printerror () {
 #
 # prints an error message
 #  $* - error message
 #
if test x${BEQUIET} = x ; then
    echo `basename $0`: "$*" >&2
fi
}

##
##  main
##

## Verifies command line arguments

if test $# -eq 0 ; then
  printerror "syntax error: missing required parameter"
  dohelp
  exit 4
fi

state=I
for i in $* ; do
    case $i in
	--help) dohelp 
		exit 4
		;;
	-q)     BEQUIET=yes
		;;
	*)      case ${state} in
		I)  INITSCRIPTID=$i
		    ;;
		*)  printerror "syntax error: invalid parameter $i"
		    exit 4
		    ;;
		esac
		state=${state}I
		;;
    esac
done

if test ${state} != II ; then
    printerror "syntax error: missing required parameter"
    exit 4
fi

## Verifies if the given initscript ID is known

if test ! -f "${INITDPREFIX}${INITSCRIPTID}" ; then
    printerror "WARNING: Unknown initscript ${INITDPREFIX}${INITSCRIPTID}"
    exit 2
fi


## Queries sysvinit for the current runlevel

RL=`${RUNLEVEL} | sed 's/.*\ //'`
if test ! $? ; then
    printerror "Could not determine current runlevel"
    exit 5
fi

if test x${RL} = x0 -o x${RL} = x6 ; then
    printerror "WARNING: script called during system shutdown sequence"
    # return a sane status; do not risk aborting a shutdown sequence
    exit 1
fi

## Verifies the existance of proper S??initscriptID and K??initscriptID 
## *links* in the proper /etc/rc?.d/ directory

verifyrclink () {
  #
  # verifies if parameters are non-dangling symlinks
  # all parameters are verified
  #
  doexit=
  while test $# -gt 0 ; do
    if test ! -L "$1" ; then
        printerror "not a symlink: $1"
        doexit=5
    fi
    if test ! -f "$1" ; then
        printerror "dangling symlink: $1"
        doexit=5
    fi
    shift
  done
  if test x${doexit} != x ; then
     exit ${doexit}
  fi
  return 0
}

# we do handle multiple links per runlevel
SLINK=`ls -d ${RCDPREFIX}${RL}.d/S[0-9][0-9]${INITSCRIPTID} 2>/dev/null | xargs`
KLINK=`ls -d ${RCDPREFIX}${RL}.d/K[0-9][0-9]${INITSCRIPTID} 2>/dev/null | xargs`
SSLINK=`ls -d ${RCDPREFIX}S.d/S[0-9][0-9]${INITSCRIPTID} 2>/dev/null | xargs`

verifyrclink ${SLINK} ${KLINK} ${SSLINK}

## Now, we decide if the daemon should be started or not. We play nice
## with people who make the /etc/init.d/initscript file non-executable
##
## For the multiple links per runlevel case, one success is enough
##
## Runlevel S start links are considered the same as a start link in
## every runlevel.

testexec () {
  #
  # returns true if any of the parameters is
  # executable (after following links)
  #
  while test $# -gt 0 ; do
    if test -x "$1" ; then
       return 0
    fi
    shift
  done
  return 1
}

if testexec ${SLINK} ; then
    exit 0
elif testexec ${KLINK} ; then
    exit 1
elif testexec ${SSLINK} ; then
    exit 0
fi

# is /etc/init.d/initscript actually executable?
if testexec "${INITDPREFIX}${INITSCRIPTID}" ; then
    # executable: return "undefined behaviour"
    exit 3
fi

# /etc/init.d/initscript is not executable. 
# we assume this is a non-subtle hint ;-)
exit 1
#!/bin/sh
#
# ...
#

# Replace all ocourrences of /etc/init.d/initscript start with
# a call to runinitscript initscript (or unroll it in place)
#
# eg: /etc/init.d/daemonname start
#          becomes 
# runinitscript daemonname

runinitscript () {
  #
  # parameters: $1 = initscript ID
  #
  initscriptid=$1
  initscriptquery=/usr/sbin/initscriptquery

  if test -x ${initscriptquery} ; then
    case `${initscriptquery} ${initscriptid} ; echo $?` in
    	0) /etc/init.d/${initscriptid} start
	   return 0
	   ;;
	1) return 0
	   ;;
	2) 
           # Something very very bad happened to the update-rc.d call,
           # or the user has deleted our /etc/init.d/ script.
           #
           # DO WHATEVER IS SAFER FOR YOUR PACKAGE HERE.
	   # default: this is probably NOT what you want, as it will
	   # probably fail. But that's what would happen without 
	   # initscriptquery
	   /etc/init.d/${initscriptid} start
	   return $?
	   ;;
	3)
	   # Undefined behaviour problem, we don't know if the initscript
	   # should be run or not in this runlevel.
	   #
	   # DO WHATEVER IS SAFER FOR YOUR PACKAGE HERE, but don't start
	   # a daemon which is not already running.
	   # default: we hope that restart will not start a daemon
           # if it is not running. You might want to change this.
           /etc/init.d/${initscriptid} restart
	   return 0
	   ;;
	4) 
	   # Should never happen, unless the script is broken
	   exit 1
	   ;;
	*)
	   # Something wicked happened to the initscript system.
	   # 
	   # DO WHATEVER IS SAFER FOR YOUR PACKAGE HERE
	   # default: we just try start the daemon anyway.
	   /etc/init.d/${initscriptid} start
	   return 0
	   ;;
    esac
  else
    /etc/init.d/${initscriptid} start
  fi
}

Attachment: pgpMbueLnfBgn.pgp
Description: PGP signature


Reply to: