FreeBSD and Python - Writing a Daemon Script Part 2

This article is a continuation of the previous article about how to write daemons in python on FreeBSD. The discussion in this article will discuss Init or in FreeBSD it is often called rc.d.


1. Init

Init scripts are used to control the daemon (starting and stopping it, telling it to reload configuration, etc.). There are various different init systems used in Unix-like operating systems. FreeBSD uses the standard BSD init system called rc.d. It works with a few shell scripts (or not so few if you need to manage very complex daemons).

Since most of the init system functions are the same across most of these scripts, rc.d handles all common cases in its own shell file which is then used in each script. In Python this can be done by importing a module, the term with shell scripts is looking for another shell script (or fragment).

For example, we will create a file called /usr/local/etc/rc.d/bdaemon with the following script contents.

root@ns1:~ # ee /usr/local/etc/rc.d/bdaemon
#!/bin/sh
. /etc/rc.subr

name=bdaemon
rcvar=bdaemon_enable

command="/usr/sbin/daemon"
command_args="-p /var/run/${name}.pid /path/to/script/bdaemon.py"

load_rc_config $name
run_rc_command "$1"

What is rcvar? Well, by entering “bdaemon_enable=YES” into /etc/rc.conf you can enable this daemon for automatic startup when the system starts up. If the line is not there, the daemon will not start. That's why we need to use "onestart" to start it (try it without "one" if you've never done it before and see what happens!).

Then the command to be executed as well as the arguments for the command are determined. And finally two helper functions from rc.subr are called that do all the complex magic that they fortunately hide from us.

OK, but what is /usr/sbin/daemon?. FreeBSD comes with a very useful little utility that handles the daemonization process for others. This means it can be helpful if we want to use something as a background service but we don't want to handle the actual daemonization ourselves.

The “-p” argument tells the daemon utility to handle the PID file for that process as well. This is necessary so that the init system can control the daemon. Even though our little example program is short-lived, we can still do something while it is running. Try the onestatus service and onestop service for example. If there is no PID file, the init system will claim that the daemon is not running, even though it is, and it will not be able to kill it.


2. Daemonization With Python

The result of part 1 is a program that actually requires external help to be daemonized. We will try to use the built-in FreeBSD daemon utility which is useful for putting programs in the background, to handle pidfiles. Now we make one step forward and try to achieve the same thing using only Python.

To do the job, we need a module that is not part of the Python standard library. So you may need to install the python-daemon package first. In the previous discussion we learned to install the python-daemon package on FreeBSD. Read part 1 of the article. Below is a small piece of code that can be applied to create Python daemonization.

root@ns1:~ # ee /tmp/gunungsemeru.py
#!/usr/local/bin/python

# Imports #
import daemon, daemon.pidfile
import logging
import signal
import time

# Fuctions #
def handler_sigterm(signum, frame):
logging.debug("Exiting on SIGTERM")
exit(0)

def main_program():
signal.signal(signal.SIGTERM, handler_sigterm)
try:
logging.basicConfig(filename='/var/log/bdaemon.log', format='%(levelname)s:%(message)s', level=logging.DEBUG)
except:
print("Error: Could not create log file! Exiting...")
exit(1)

logging.info("Started!")
while True:
time.sleep(1)

# Main #
with daemon.DaemonContext(pidfile=daemon.pidfile.TimeoutPIDLockFile("/var/run/bdaemon.pid"), umask=0o002):
main_program()

In the example script above there are two new imports for daemonization. As can be seen in the script above, it is possible to import multiple modules in one line. The first thing that is more interesting with this version is that the main program is moved to a function called “main_program”. We could have done that before if we really wanted, but we'll do it now so the code doesn't distract from the main point of this example. Look at the line that starts with the keyword with . Well, that's a mouthful, isn't it? Let's break this one down into parts to make it easier to understand.


3. Updated Init Script

Alright, the only thing to do here is to make the necessary changes to the init script. We no longer use daemon utilities, so we need to adapt them. The following is an example of a new init script.

root@ns1:~ # ee /usr/local/etc/rc.d/bdaemon
#!/bin/sh

. /etc/rc.subr

name=bdaemon
rcvar=bdaemon_enable

command="/root/bdaemon.py"
command_interpreter=/usr/local/bin/python
pidfile="/var/run/${name}.pid"

load_rc_config $name
run_rc_command "$1"

In the script above there are not too many changes, but let's discuss what has happened. The command definition is quite clear. The program can now daemonize itself, so we call it directly. No arguments are needed, which means we can remove command_args. But we need to add command_interpreter, because the program will look like this in the process list.

Without defining the interpreter, the init system will not recognize this process as a valid process. Then we also need to point it to pidfile , because in theory there could be some matching process otherwise. And that's it, now we have a daemon process running in FreeBSD, written in pure Python.
Iwan Setiawan

I Like Adventure: Mahameru Mount, Rinjani Mount I Like Writer FreeBSD

Post a Comment

Previous Post Next Post