Introductory Quickie
Welcome to the first of what I’m hoping will be a fairly regular programming column. I’m not going to stick with one subject, I’m going to try and cover a variety of topics, some will be more detailed than others but they will all be based on my personal experiences.
So who am I and what qualifies me to write about software engineering? I guess a lot of that is answered by the interview Eriken did with my partner in crime and I. In terms of employment, at the moment, by day my official job title is ‘Research and Development Engineer’… sounds good, but translated… I write software to control and manage cash handling/ticketing machines (Tools- Delphi 7, MySQL and others), I assist with the development of prototype machines (Tools- soldering iron, screwdrivers, nut runners, crimp guns and a bag full of other bits and pieces) and most enjoyable of all, I get to go out on site and repair/upgrade machines (Tools- patience, mobile phone, and a nice fast company car). By night however, I work on The Outer Reaches. A multiplayer browser based space strategy game that runs entirely on our own web server platform.
I’ve been a software engineer full time since 1995, prior to that I was doing contract work whilst studying Computer Science. I don’t consider myself to be a guru, so I may make mistakes. If you spot one, please let me know and lets have a chat about it. Only by realising your weak points can you fully understand what you need to learn.
If you have any questions or comments (or, heaven forbid, you find an error) you can drop me a mail at athena at outer hyphen reaches dot com.
So, introductions over, I’m going to start off with a short item covering the basics of daemonising a process with Kylix.
Daemonising A Process With Kylix
For anyone who has never encountered Linux (or another Unix derivative), a daemon is the equivalent of a Windows service (and just in case… a service is a background application that is normally loaded and started during operating system startup).
Delphi provides ready made support for creating services, although the way it handles it implies that you should put all your code in the service application. This is fine and dandy if you can guarantee you are not going to want to do too much interactive debugging, but generally, it ends up being a nightmare. I’ll cover creating services and daemons in a later article as there is quite a lot you can do to make your life easier.
So, for now, lets look at the process of daemonising a process with Kylix. A daemonised process is one that is running outside the confines of a user session. i.e. the process can be started and the user can log off without ending the process. Normally if you log off, the applications you started will be stopped, but a daemonised process is immune from this cull as it is no longer associated with you.
Sounds slightly complex. In actual fact, creating daemons with Kylix is in many respects easier than creating services with Delphi as it takes a single call to achieve. Time for a quick example.
libc.daemon(0,0);
And there you have it. After executing that command, your application is seperated from your user session and will continue to run indefinitely (providing it doesn’t terminate, crash and the machine is not restarted). Seems simple enough, but there is just a little more to it. The only way to terminate this would be shut down the machine… hardly ideal for debugging, so we need to address this.
With Windows services, Delphi provides you with events that are hooked up to catch service messages, the Stop message for example, allowing you to stop the service. With Kylix you have to create a set of signal handlers. The following code segment illustrates how to do this.
libc.signal(libc.SIGINT,@killDaemon); libc.signal(libc.SIGTERM,@killDaemon); libc.signal(libc.SIGKILL,@killDaemon);
The procedure ‘killDaemon’ should take no parameters and should be used to signal to the rest of your code that the daemon should terminate. It will be called when the process receives the signals SIGINT, SIGTERM and SIGKILL.
That’s the basics of daemonising a process. The next page provides a full set of example code that can be used to daemonize a process.
Example Daemon
The following code example is real code from one of our projects, so I’m fairly confident that it works. The only part I haven’t fully tested is the user switching code, you have been warned. You may reuse this code, providing the copyright notices remain intact. Neither I nor Outer Reaches Studios accepts any responsibility if this code goes boom and causes problems. You use it at your own risk.
program sampledaemond;
(*
Sample Daemon
Replace the routines 'initialise' and 'deinitialise' to create and cleanup
your actual process object. If you need to periodically call a routine in
your object to make it operate, then you should do this in 'main' at the
point indicated.
The paths used by this example are fairly standard on RedHat systems, but will
undoubtedly need tweaking for other distributions.
Original Code - Copyright (C) 2001-2005 Outer Reaches Studios
(http://www.outer-reaches.co.uk)
*)
{$APPTYPE CONSOLE}
uses
SysUtils,
Libc,
IniFiles;
const
// This constant is used to provide the name that will be used for the
// PID file. Ideally it should be the same as the compiled executable
_appName = 'sampledaemond';
// Specify the name of the file that will be used to hold our basic
// configuration
_configFileName = 'sampledaemon.ini';
var
// Daemon variables
// These are used to store base config information and also to handle state
terminateDaemon : boolean;
becomeDaemon : boolean;
becameDaemon : boolean;
abortingDaemon : boolean;
changeUID : boolean;
newUID : integer;
newGID : integer;
oldUID : integer;
oldGID : integer;
iniFile : TIniFile;
(*---Main Routines-----------------------------------------------------------*)
function initialise:boolean;
begin
// Initialise your process here.
// You should return TRUE if this completes successfully
end;
procedure deinitialise;
begin
// De-initialise your process and do any cleanup here
end;
procedure main;
begin
// Wait here in the main loop until terminated
repeat
// If you need to periodically call a method of your process
// object to make it work, you should do so here. A better way
// to handle that however would be to create a timer object
// inside your process object and use that to make the call
// You may need to adjust the sleep period if the process is
// chewing clock cycles
sleep(100);
until (terminateDaemon);
end;
(*---Daemon Support Routines-------------------------------------------------*)
(*---Pid File Routine--------------------------------------------------------*)
procedure pidFile(writeit:boolean);
var
pFile : textFile;
pFileName : string;
begin
pFileName:='/var/run/'+_appName+'.pid';
assignFile(pFile,pFileName);
if writeIt then
begin
try
rewrite(pFile);
write(pFile,format('%d',[libc.getPid]));
closeFile(pFile);
except
end;
end
else
begin
try
if fileExists(pFileName) then
erase(pFile);
except
end;
end;
end;
(*---SIGTERM/SIGINT/SIGKILL Handler------------------------------------------*)
procedure killDaemon;
begin
terminateDaemon:=true;
end;
(*---Daemon Start Up Code----------------------------------------------------*)
begin
// Initialise our globals
terminateDaemon:=false;
abortingDaemon:=false;
becameDaemon:=false;
becomeDaemon:=false;
changeUID:=false;
// Initialise any variables you need to here. Do NOT create any
// process specific objects here. That should be done using the
// 'Initialise' function above.
// Initialise these to keep the compiler happy, even though they are
// initialised then they are used
newUID:=99;
newGID:=99;
oldUID:=0;
oldGID:=0;
// Read a little about our configuration now (the stuff we need to know
// about switching to demon mode)
try
iniFile:=TIniFile.create('/etc/'+_configFileName);
// Are we to become a damone?
becomeDaemon:=(iniFile.readInteger('DAEMON','RUNASDAEMON',0)=1);
// Are we to run as someone else?
changeUID:=(iniFile.readInteger('DAEMON','DAEMONSWITCHUID',0)=1);
// If we are to run as someone else...
if changeUID then
begin
// Get our current UID and GID
oldUID:=libc.getUID;
oldGID:=libc.getGID;
// Read the UID and GID that we are to become (Default is
// 99... nobody)
newUID:=iniFile.readInteger('DAEMON','NEWDAEMONUID',99);
newGID:=iniFile.readInteger('DAEMON','NEWDAEMONGID',99);
end;
iniFile.free;
except
on e:exception do
begin
writeln('Exception occured accessing /etc/'+
_configFileName+'. The message was '+e.message);
abortingDaemon:=true;
end;
end;
// If we are to run as a daemon and we have managed to read our
if (becomeDaemon) and (not abortingDaemon) then
begin
// Call libc.daemon to fork the process and become
// a daemon
libc.daemon(0,0);
// Create our PID file
pidFile(true);
// Hook the signals we want to listen to
// to terminate
libc.signal(libc.SIGINT,@killDaemon);
libc.signal(libc.SIGTERM,@killDaemon);
libc.signal(libc.SIGKILL,@killDaemon);
// If we are to run as someone else, switch ID's
if changeUID then
begin
libc.setuid(newUID);
libc.setgid(newGID);
end;
becameDaemon:=true;
end;
// If all is well...
if (not abortingDaemon) then
begin
// If we initialise...
if initialise then
begin
// Enter the main loop until we are tol to stop
main;
// Deinitialise and cleanup
deinitialise;
end;
end;
// If we became a daemon...
if becameDaemon then
begin
// If we switched ID's, then switch back to our originals
if changeUID then
begin
libc.setgid(oldGID);
libc.setuid(oldUID);
end;
// Get rid of the PID file
pidFile(false);
end;
end.
Conclusion
That about wraps it up. If you have any questions, comments or you find an error or oversight, please drop me a message.
I have a few topic ideas lined up, including ‘Creating and debugging services and daemons’ and ‘Bug tracking and project management with Open Source tools’. But, if theres anything you guys want me to look into or write about, let me know and I’ll see what I can do.
And on that note, take care all, until next time.
AthenaOfDelphi
PS. If anyone wants to chat, I can normally be found on the NetGamers IRC network (www.netgamers.org) lurking in our game channel #OuterReaches. Feel free to drop in and have a chinwag.


