hdaemonize-0.5.1: Library to handle the details of writing daemons for UNIX

Safe HaskellNone
LanguageHaskell98

System.Posix.Daemonize

Contents

Synopsis

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.

Constructors

CreateDaemon 

Fields

  • privilegedAction :: IO a

    An action to be run as root, before permissions are dropped, e.g., binding a trusted port.

  • program :: a -> IO ()

    The actual guts of the daemon, more or less the main function. Its argument is the result of running privilegedAction before dropping privileges.

  • name :: Maybe String

    The name of the daemon, which is used as the name for the PID file, as the name that appears in the system logs, and as the user and group the daemon tries to run as if none are explicitly specified. In general, this should be Nothing, in which case the system defaults to the name of the executable file containing the daemon.

  • user :: Maybe String

    Most daemons are initially run as root, and try to change to another user so they have fewer privileges and represent less of a security threat. This field specifies which user it should try to run as. If it is Nothing, or if the user does not exist on the system, it next tries to become a user with the same name as the daemon, and if that fails, the user daemon.

  • group :: Maybe String

    group is the group the daemon should try to run as, and works the same way as the user field.

  • syslogOptions :: [Option]

    The options the daemon should set on syslog. You can safely leave this as [].

  • pidfileDirectory :: Maybe FilePath

    The directory where the daemon should write and look for the PID file. Nothing means varrun. Unless you have a good reason to do otherwise, leave this as Nothing.

  • killWait :: Maybe Int

    How many seconds to wait between sending sigTERM and sending sigKILL. If Nothing wait forever. Default 4.

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 a Source #

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 a Source #

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.

{-# LANGUAGE OverloadedStrings #-}
module Main where

import System.Posix.Daemonize (CreateDaemon(..), serviced, simpleDaemon)
import System.Posix.Signals (installHandler, Handler(Catch), sigHUP, fullSignalSet)
import System.Posix.Syslog (syslogUnsafe, Facility(DAEMON), 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 DAEMON Notice "I'm still alive!"

taunt :: IO ()
taunt = syslogUnsafe DAEMON Notice "I sneeze in your general direction, you and your SIGHUP."