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

RFC: fix for daemon start on package install/upgrade out-of-runlevel



Hello debs,

This is a request for comments (and enhancements ;-) ) for a possible
solution to an annoying bug (for those it hits) we currently have: daemons
are started during package installs/upgrades regardless of the current
runlevel.

This behaviour can be fixed, and the fix is not overly complex. In this RFC,
I propose a solution including example code. However, the complete solution
requires policy that mandates the fix to be applied to all affected
packages.

I humbly request your input on this issue, and suggest a fix for this
problem (not necessarily the fix outlined in this RFC) to be considered a
goal for woody.

THE PROBLEM: During (non-first) installs and upgrades, init scripts are run
regardless of the current runlevel. Debian tries to respect the local
administrator's wishes through the update-rc.d script, but falls short when
it comes to start daemons only at the correct runlevels.

EXAMPLE: User sets up his runlevels 2 and 3 so as not to start the nfs
server in runlevel 2, and to start it in runlevel 3. The system is set to
runlevel 2, the nfs daemon isn't running. The user runs apt-get
dist-upgrade, and a new version of the nfs server is installed. The postinst
script starts the nfs server daemon, even though the current runlevel
clearly prohibits this. (BTW: this is an example, I didn't check if the nfs
server package has some sort of unusual postinst which does not start the
server if it's not runing on upgrades).

THE PROPOSED SOLUTION: A new program (/usr/sbin/initscriptquery) will be
provided by the sysvinit (base) and file-rc (optional) packages. This
program will be used by all packages which provide an init script (with the
possible exception of rcS.d scripts, see the Issues section) to verify if
they are supposed to run an init script in the current runlevel or not
(during both installs and upgrades).


The proposed /usr/sbin/initscriptquery script:
----------------------------------------------

Assumptions: 
  Debian uses only sysvinit-compatible init scripts, 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 init script is the init script ID required by
  the update-rc.d command.

Design goals: 
  1. Downgrade to the current (but broken) behaviour if the fix is not fully
     available in the host system.

  2. Default behaviour should match the current one for Debian as well as
     possible, with the exception that daemons are NOT started out of their
     intended runlevels.

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

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

  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 init script is allowed to be started
    1 - the init script is NOT allowed be started
    2 - init script ID unknown
    3 - init script ID known, but behaviour is undefined
    4 - syntax error
  >=5 - other error (usually means inconsistency in the underlining
        init script subsystem)

Verbose description of the exit status codes:

    0 - the init script 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. {currently not implemented, a separate RFC will
	address this, ignore it for now *please*}

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

    1 - the init script 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 init script.
	{currently not implemented, a separate RFC will address this, ignore
	it for now *please*}

	Desired behaviour: Do not run the init script. 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 - init script ID unknown

        sysvinit meaning: There isn't a normal file with the name matching
	<init script 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 - init script ID known, but behaviour is undefined

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

	Desired behaviour: For non-daemon-starting init scripts, 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 init script 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.

Issues:
-------

ISSUE: Is there a need for pre-depends?

    A package which needs a future version of the initsciptquery interface
    would need to pre-depends: sysvinit (>=someversion) | filerc
    (>=someversion). How is this done for update-rc.d ? 

    Fortunately, interface upgrades for initscriptquery are not very likely.
    
    There is no problem if the package uses the _first_ version of the
    initscriptquery interface, as the postinst is supposed to revert to the
    "start always" behaviour if /usr/sbin/initscriptquery cannot be run.

ISSUE: rcS.d scripts

    rcS.d scripts are a special case, and all initscriptquery could do for
    them is to always return status code 0 for them, unless the init script
    should be *stopped* in the current runlevel.

    For sysvinit that boils down to: if there's a valid rcS.d S?? link for
    the script, and no K?? link for the current runlevel (i.e. a valid
    rcS.d/S?? link is considered to be a valid S?? link for the other
    runlevels as well), the script should be started.

    Should this special behaviour be part of initscriptquery, or should rcS.d
    scripts be required NOT to make use of initscriptquery?

ISSUE: more sanity checks (sysvinit version)

    Should we pre-depends on debianutils (>=1.13.1) (also a base package),
    so as to have access to readlink, and use it to follow the S??/K?? links
    and make sure they point to the correct /etc/init.d/initscriptid script?
    Looks like unneded complexity for me, and I can think of a number of
    (good) reasons to explicitily allow (and support!) such a mess.
    

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.  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 init script system for the
#   current runlevel and verifies if a given script should be started 
#   in that runlevel.  
#
# $Id: initscriptquery,v 1.3.1.2 2000/09/09 03:39:46 hmh Exp $
#
# Usage: 
#   initscriptquery [--help] [-q] <init script ID>
#
#   init script 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 init script is allowed to be started [in this runlevel]
#    1 - the init script is NOT allowed be started [in this runlevel]
#    2 - init script ID unknown
#    3 - init script 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 init script 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 - Init script unique ID, as used for update-rd.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


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

#should I use find instead? the current code handles only the first link
#if there are two S??... or K??... links in /etc/rc?.d/
SLINK=`ls -d ${RCDPREFIX}${RL}.d/S[0-9][0-9]${INITSCRIPTID} 2>/dev/null`
KLINK=`ls -d ${RCDPREFIX}${RL}.d/K[0-9][0-9]${INITSCRIPTID} 2>/dev/null`

if test x${SLINK} != x ; then
    if test ! -L ${SLINK} ; then
	printerror "not a symlink: ${SLINK}" 
	exit 5
    fi
    if test ! -f ${SLINK} ; then
	printerror "dangling symlink: ${SLINK}"
	exit 5
    fi
fi
if test x${KLINK} != x ; then
    if test ! -L ${KLINK} ; then
        printerror "not a symlink: ${KLINK}"
        exit 5
    fi
    if test ! -f ${KLINK} ; then
        printerror "dangling symlink: ${KLINK}"
        exit 5
    fi
fi


# 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

if test x${SLINK} != x -a -x ${SLINK}; then
    exit 0
fi

if test x${KLINK} != x -a -x ${KLINK}; then
    exit 1
fi

# undefined behaviour, no start or stop link to executable found
# note that rcS.d scripts almost always end up here.
exit 3
#!/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: pgpSX3t1AyKWJ.pgp
Description: PGP signature


Reply to: