a chkconfig clone that works with upstart

Eric S. Raymond esr at thyrsus.com
Wed May 30 19:47:31 BST 2007


Enclosed please find code and man page of a bash+sed script that uses
update-rc.d to emulate the behavior of the IRIX/Red-Hat chkconfig tool
Try a few invocations with --dryrun to see exactly what it does --
that switch makes it print the generated commands rather than
executing them. 

Upconversion of the chkconfig.8 page to XML-DocBook is available on
request, if you prefer that format.

This will be useful because some third-party packages (especially in 
the Red Hat world) rely on chkconfig in their installation scripts.

I wrote this because as a Red Hat refugee I missed chkconfig.
I'm contributing this to upstart for the usual reason, which is that
I'd rather not have to maintain and separately distribute it myself.

Also, BTW, because I think upstart is pretty cool.  I've been 
administering boxes with System V runlevels for literally decades,
and they were OK, but I'm not sorry to see an elegant new approach
displace them.  Congrats on getting picked up by Debian and Ubuntu,
and best wishes for total world domination.
-- 
		<a href="http://www.catb.org/~esr/">Eric S. Raymond</a>
-------------- next part --------------
#!/bin/bash
#
# chkconfig -- list, enable, and disable system services by runlevel
#
# By Eric S. Raymond, May 2007.  Released under BSD license.
#
# This is an emulation of the IRIX chkconfig tool to run over Debian/Ubuntu
# (it calls update-rc.d, the native tool, to do the actual work).  Also
# requires sed(1) and runlevel(8).
#
# I wrote this because (a) a significant number of third-party packages
# depend on chkconfig, and (b) as an ex-Red-Hat user, I'm used to this
# interface and want it handy.

# Things one might want to configure
RUNLEVELS="1 2 3 4 5 6 S"
ONOFFDEFAULT=2345
START=20
STOP=80

currentlevel () {
   # Get the current run level
   set -- `runlevel`
   echo $2
}

explode () {
    # Explode a sequence of digits into a space-separated list
    echo "$1" | sed -e 's/[0-9A-Z]/ &/g'
}

checkroot(){
  # Privileged operation coming up, require caller to be superuser.
  if [ "$prefix" != echo -a "$EUID" -ne 0 ]
  then
    echo "chkconfig: you must be root to use this feature."
    exit 1
  fi
}

parse_init_header() {
    # Parse an LSB INIT INFO header for default start and stop levels.
    {
	state=0
	startlevels=''
	stoplevels=''
	status=1
	while read line
	do
	    #echo "Looking at" $line
	    if expr "$line" : ".*BEGIN INIT INFO" >/dev/null
	    then
		state=1
		status=0
		continue
	    fi
	    if expr "$line" : ".*END INIT INFO" >/dev/null
	    then
		state=0
		continue
	    fi
	    if [ $state = 1 ]
	    then
		t1=`expr "$line" : '.*Default-Start:[ \t]*\(.*\)'`
		if [ "$t1" != "" ]
		then
		    startlevels="$t1"
		fi
		t2=`expr "$line" : '.*Default-Stop:[ \t]*\(.*\)'`
		if [ "$t2" != "" ]
		then
		    stoplevels="$t2"
		fi
	    fi
	done
    } </etc/init.d/$1
    return $status
}

clearlevels () {
    # Clear init scripts for $name at runlevels $*
    name=$1
    shift
    for level in $*; do 
	$prefix rm -f /etc/rc[${level}].d/[SK][0-9][0-9]$name
    done
}

chkconfig_list() {
    if [ $# == 0 ]
    then
	for service in /etc/init.d/*
	do
	    # Ignoring files that aren't executable is a cheap way
	    # to skip README and other similar cruft like .dpkg-dist.
	    if [ -x $service ]
	    then
		chkconfig_list `basename $service`
	    fi
	done
    else
	printf "%-16s\t" $1
	for i in $RUNLEVELS
	do
		status="none"	# Other chkconfigs report 'off' here
		if [ -f /etc/rc$i.d/K[0-9][0-9]$1 ]
		then
			status=off
		fi
		if [ -f /etc/rc$i.d/S[0-9][0-9]$1 ]
		then
			status=on
		fi
		printf "$i:$status\t"
	done
	echo ""
    fi
}

chkconfig_add() {
    if parse_init_header $1
    then
	exec $prefix update-rc.d $1 start $START $startlevels . stop $STOP $stoplevels .
    else
	echo "chkconfig: can't --add a service with no INIT INFO header."
	exit 1
    fi
}

chkconfig_del() {
    exec $prefix update-rc.d -f $1 remove
}

chkconfig_level() {
    levels=$1
    name=$2
    directive=$3
    priority=$4
    if [ "$name" = "" ]
    then
	echo "chkconfig: requires a service name"
	exit 1
    fi
    # Query the state of a service (doesn't require root).
    if [ "$directive" = "" ]
    then
	levels=${levels:-`currentlevel`}
	if [ -f /etc/rc${level}.d/S[0-9][0-9]${1} ]
	then
	    exit 0
	else
	    exit 1
	fi
    fi
    # Turn on, turn off, or reset a service.
    checkroot
    case $directive in
    on)    levels=${levels:-$ONOFFDEFAULT}
	   clearlevels $name $levels
	   $prefix update-rc.d $name start ${priority:-$START} `explode $levels` . 
	   ;;
    off)   levels=${levels:-$ONOFFDEFAULT}
	   clearlevels $name $levels
	   $prefix update-rc.d $name stop  ${priority:-$STOP} `explode $levels` . 
	   ;;
    reset) if parse_init_header $name
	   then
	       startlevels=${startlevels:-$RESETDEFAULT} 
	       stoplevels=${stoplevels:-$RESETDEFAULT}
	       clearlevels $name ${startlevels}${stoplevels}
	       $prefix update-rc.d $name \
		    start ${priority:-$START} `explode $startlevels` . \ 
		    stop  ${priority:-$STOP}  `explode $stoplevels` . 
	   else
	       echo "chkconfig: no INIT INFO header found"
	       exit 1
	   fi 
	   ;;
    *)  echo "chkconfig: unknown operation '$directive'"; exit 1 ;;
    esac
}

if [ "$#" = 0 ]
then
    echo -en "usage:   chkconfig --list [name]\n\
         chkconfig [--dryrun] --add <name>\n\
         chkconfig [--dryrun] --del <name>\n\
         chkconfig [--dryrun] [--level <levels>] <name> [<on|off|reset>] [priority]\n"
    exit 0
fi

prefix=""
if [ "$1" = "--dryrun" ]
then
    prefix=echo
    shift
fi

case $1 in
    --list) shift; chkconfig_list $1 ;;
    --add) checkroot; shift; chkconfig_add $1 ;;
    --del) checkroot; shift; chkconfig_del $1 ;;
    --level) shift; chkconfig_level $* ;;
    *) chkconfig_level "" $* ;;
esac
-------------- next part --------------
.TH CHKCONFIG 8 "Wed Oct 8 1997"
.UC 4
.SH NAME
chkconfig \- updates and queries runlevel information for system services

.SH SYNOPSIS
\fBchkconfig\fR \fB--list\fR [\fIname\fR]
.br
\fBchkconfig\fR [\fB--dryrun\fR] \fB--add\fR \fIname\fR
.br
\fBchkconfig\fR [\fB--dryrun\fR] \fB--del\fR \fIname\fR
.br
\fBchkconfig\fR [\fB--dryrun\fR] [\fB--level\fR \fIlevels\fR]
\fIname\fR {\fBon\fR|\fBoff\fR||\fBreset\fR\fR} [priority]
.br
\fBchkconfig\fR [\fB--level\fR \fIlevels\fR] \fIname\fR 
.br

.SH DESCRIPTION
\fBchkconfig\fR provides a simple command-line tool for maintaining the
/etc/rc[0-6].d directory hierarchy by relieving system administrators of
the task of directly manipulating the numerous symbolic links in those
directories.

This implementation of \fBchkconfig\fR was inspired by the
\fBchkconfig\fR command present in the IRIX operating system and Red
Hat Linux. Rather than maintaining configuration information outside
of the /etc/rc[0-6].d hierarchy, however, this version uses
update-rc.d to directly manages the symlinks in /etc/rc[0-6].d. This
leaves all of the configuration information regarding what services
\fIinit\fR starts in a single location.

\fBchkconfig\fR has five distinct functions: adding new services for
management, removing services from management, listing the current
startup information for services, changing the startup information
for services, and checking the startup state of a particular service.

When \fBchkconfig\fR is run without any options, it displays usage
information.  If only a service name is given, it checks to see if the
service is configured to be started in the current runlevel. If it is,
\fBchkconfig\fR returns true (0); otherwise it returns false (1). The
\fB--level\fR option may be used to have \fBchkconfig\fR query an
alternative runlevel rather than the current one.

If one of \fBon\fR, \fBoff\fR, or \fRreset\fR is specified after the
service name, \fBchkconfig\fR changes the startup information for the
specified service.  The \fBon\fR and \fBoff\fR flags cause the service
to be started or stopped, respectively, in the runlevels being changed.
The \fBreset\fR flag resets the startup information for the service to
whatever is specified in the init script in question.

The optional priority argument, used only if the operation is \fBon\fR
or\fBoff\fR, sets the start or stop priority of the link.

By default, the \fBon\fR and \fBoff\fR options affect only runlevels 2, 3, 4,
and 5, while \fBreset\fR affects all of the runlevels.  The \fB--level\fR
option may be used to specify which runlevels are affected.

Note that for every service, each runlevel has either a start script
or a stop script.  When switching runlevels, init will not re-start
an already-started service, and will not re-stop a service that is
not running.

.SH OPTIONS
.TP
\fB--dryrun\fR
Show what the rest of the command line would do, but don't actually do it.

.TP
\fB-\-level \fIlevels\fR
Specifies the run levels an operation should pertain to. It is given as
a string of numbers from 0 to 7. For example, \fB--level 35\fR specifies
runlevels 3 and 5.

.TP
\fB-\-add\fR \fIname\fR
This option adds a new service for management by \fBchkconfig\fR.
When a new service is added, \fBchkconfig\fR ensures that the service
has either a start or a kill entry in every runlevel. If any runlevel
is missing such an entry, \fBchkconfig\fR creates the appropriate entry
as specified by the default values in the init script. Note that
default entries in LSB-delimited 'INIT INFO' sections take precedence
over the default runlevels in the initscript.

.TP
\fB-\-del\fR \fIname\fR
The service is removed from \fBchkconfig\fR management, and any symbolic
links in /etc/rc[0-6].d which pertain to it are removed.

Note that future package installs for this service may run
\fBchkconfig \-\-add\fR, which will re-add such links. To disable a
service, run \fBchkconfig \fIname\fR \fBoff\fR.

.TP
\fB-\-list\fR \fIname\fR
This option lists all of the services which \fBchkconfig\fR knows about,
and whether they are stopped or started in each runlevel. If \fIname\fR
is specified, information in only display about service \fIname\fR.

.SH LIMITATIONS
Red Hat \fBchkconfig\fR could manage xinetd scripts by manipulating
xinetd.d configuration files (only the \fBon\fR, \fBoff\fR, and
\fB-\-list\fR commands were supported for xinetd.d services).  This
Debian/Ubuntu port doesn't do that.

Red Hat \fBchkconfig\fR had a special syntax for embedding 
metainformation in init-script headers which this script doesn't 
support.  Instead, this script interprets LSB INIT INFO blocks, which are
functionally equivalent except that they don't specify priority
numbers.  To compensate, this implementation adds the priority argument;
if that is not given, the script uses a fixed startup priority
of 20 and a fixed stop priority of 80, relying on processing of 
the dependencies in the LSIB INIT INFO blocks to get ordering right.

On the other hand, the IRIX and Red Hat version didn't have --dryrun.

.SH SEE ALSO
.IR init (8).
.IR runlevel (8).

.SH AUTHOR
.nf
Eric S. Raymond.  Portions of the manual page are from the Red Hat
implementation by Erik Troan <ewt at redhat.com>.
.fi


More information about the upstart-devel mailing list