- daemonize :: IO () -> IO ()
- serviced :: CreateDaemon a -> IO ()
- data CreateDaemon a = CreateDaemon {}
- simpleDaemon :: CreateDaemon ()
- fatalError :: MonadIO m => String -> m a
- exitCleanly :: MonadIO m => m a
Simple daemonization
daemonize :: IO () -> IO ()Source
Turning a process into a daemon involves a fixed set of
operations on unix systems, described in section 13.3 of Stevens
and Rago, Advanced Programming in the Unix Environment. Since
they are fixed, they can be written as a single function,
daemonize
taking an IO
action which represents the daemon's
actual activity.
Briefly, daemonize
sets the file creation mask to 0, forks twice,
changed the working directory to /
, closes stdin, stdout, and
stderr, blocks sigHUP
, and runs its argument. Strictly, it
should close all open file descriptors, but this is not possible in
a sensible way in Haskell.
The most trivial daemon would be
daemonize (forever $ return ())
which does nothing until killed.
Building system services
serviced :: CreateDaemon a -> IO ()Source
serviced
turns a program into a UNIX daemon (system service)
ready to be deployed to etcrc.d or similar startup folder. It
is meant to be used in the main
function of a program, such as
serviced simpleDaemon
The resulting program takes one of three arguments: start
,
stop
, and restart
. All control the status of a daemon by
looking for a file containing a text string holding the PID of
any running instance. Conventionally, this file is in
varrun/$name.pid
, where $name is the executable's name. For
obvious reasons, this file is known as a PID file.
start
makes the program write a PID file. If the file already
exists, it refuses to start, guaranteeing there is only one
instance of the daemon at any time.
stop
read the PID file, and terminates the process whose pid is
written therein. First it does a soft kill, SIGTERM, giving the
daemon a chance to shut down cleanly, then three seconds later a
hard kill which the daemon cannot catch or escape.
restart
is simple stop
followed by start
.
serviced
also tries to drop privileges. If you don't specify a
user the daemon should run as, it will try to switch to a user
with the same name as the daemon, and otherwise to user daemon
.
It goes through the same sequence for group. Just to complicate
matters, the name of the daemon is by default the name of the
executable file, but can again be set to something else in the
CreateDaemon
record.
Finally, exceptions in the program are caught, logged to syslog, and the program restarted.
data CreateDaemon a Source
The details of any given daemon are fixed by the CreateDaemon
record passed to serviced
. You can also take a predefined form
of CreateDaemon
, such as simpleDaemon
below, and set what
options you want, rather than defining the whole record yourself.
CreateDaemon | |
|
simpleDaemon :: CreateDaemon ()Source
The simplest possible instance of CreateDaemon
is
CreateDaemon { privilegedAction = return () program = const $ forever $ return () name = Nothing, user = Nothing, group = Nothing, syslogOptions = [], pidfileDirectory = Nothing, }
which does nothing forever with all default settings. We give it a
name, simpleDaemon
, since you may want to use it as a template
and modify only the fields that you need.
Intradaemon utilities
fatalError :: MonadIO m => String -> m aSource
When you encounter an error where the only sane way to handle it is to write an error to the log and die messily, use fatalError. This is a good candidate for things like not being able to find configuration files on startup.
exitCleanly :: MonadIO m => m aSource
Use this function when the daemon should terminate normally. It logs a message, and exits with status 0.
An example
Here is an example of a full program which writes a message to
syslog once a second proclaiming its continued existance, and
which installs its own SIGHUP handler. Note that you won't
actually see the message once a second in the log on most
systems. syslogd
detects repeated messages and prints the
first one, then delays for the rest and eventually writes a line
about how many times it has seen it.
module Main where import System.Posix.Daemonize (CreateDaemon(..), serviced, simpleDaemon) import System.Posix.Signals (installHandler, Handler(Catch), sigHUP, fullSignalSet) import System.Posix.Syslog (syslog, Priority(Notice)) import Control.Concurrent (threadDelay) import Control.Monad (forever) main :: IO () main = serviced stillAlive stillAlive :: CreateDaemon () stillAlive = simpleDaemon { program = stillAliveMain } stillAliveMain :: () -> IO () stillAliveMain _ = do installHandler sigHUP (Catch taunt) (Just fullSignalSet) forever $ do threadDelay (10^6) syslog Notice "I'm still alive!" taunt :: IO () taunt = syslog Notice "I sneeze in your general direction, you and your SIGHUP."