==============================================================================
C-Scene Issue #2
Not All Daemons are Demons - July 20th 1997
Kerry D. Mathews II, aka Waterhose
==============================================================================

In this article, I'll attempt to explain some aspects of real world 
daemon applications. The example I will use to explain these concepts
is written in C and compiled using the gcc compiler of Linux.
It has been written for Linux, but the general functionality can be 
transposed to all Operating Systems. 

Be forewarned, I am no longer as academic as I am practical. I'll use
verbiage that may be over-specific, over-generalized but never
far from the reality. Just be thankful you can't hear me mumble.

One quick note on versions: gcc ver 2.7.2.1 and Linux ver 2.0.27 are used.


First a little Q and A.

Q: What's a daemon?
A: A constant running background process.
	In addition these processes are unattached from any specific
	user or device. Examples are lpd and kerneld.

Q: Why would I need a daemon?
A: I use them to fool friends and neighbors. You may need them as a
	architecture for programs that gathering data from a device, 
	or memory compression, or print queuing. All of which don't 
	need a UI or a user associated.

Q: Do I have to register the daemon with the kernel?
A: No. If your daemon is critical to your system, often there will
	be an entry in the init hierarchy. Specifically /etc/rc.d
	If your daemon is really important, then study how other 
	daemons are spawned. Example: /etc/rc.d/init.d/gpm

Q: Can daemons turn into zombies?
A: Yes. If the daemon is forked. It usually caused by a timing issue. 
	In my example, we will fork. 

Q: If my daemon has no UI how can i get data to it?
A: Be creative. In our example we will use the FIFO-pipe mechanism
	found in all Sys V IPC compliant Unices. Other ways may include
	IP protocols, data-file manipulation, or signals.

Q: Signals?
A: Signals are your friends. I'm not really sure of the exact mechanism,
	but i have never heard a Unix signal. I don't think my example
	'hears' them either. The great thing about signals, is that all
	processes have to respond to signals. SIGTERM, SIGALRM, and my
	favorites SIGUSR1 and SIGUSR2

Q: Semaphores?
A: Semaphores are cool. They are like system-level arrays where you can 
	lock a record. You can specify a key (like an index) and attribute 
	a value to it. The best part is that the inner workings are black 
	boxed. Ergo, little problematic conditions like race and corruption 
	don't exist. Honestly, I'm winging it, consult a book for specifics.
	:o



First... the Requirements.

Lets build some requirements for our daemon. We should always have a clear
view of what we want code.

I want this daemon to handle ASCII packages. Exactly what happens to the
data? For now, we will just dump it into a log file. Specific implementations
are out of the scope of this book. In other words, this program does good
stuff, it's up to you take it further.

Secondly I want this daemon to respond to signals. Specifically SIGTERM
and SIGUSR1. When it gets a SIGTERM [kill process#] I want the daemon
to gracefully shutdown. When the daemon gets a SIGUSR1 [kill -10 process#],
I want the daemon to re-initialize itself. 

I want the process to be visible by raising a semaphore. If the semaphore 
has already been raised, then it will be assumed that a previous instance
of the program is still running.

And lastly, I need the daemon to know if the last time it was running,
it was killed inappropriately (i.e. power outage, bad kill signal).
If the daemon did 'go down dirty', it would be good to have some kind
of recovery.


Easy enough.. just four requirements.   :)

OK. What tools do we have to use? Well, according to the last article
we've a tool called dbugger(). That will let us keep track of things
as they happen. 
**Note: There was a slight change in the dbugger.c file.
And the other tools available are from the great bag of tricks provided
by Linux. We'll use 

	ps	- Shows running processes and their parents IDús. 
	
	kill	- Sends signals to processes.

	ipcs	- Represents all semaphores being used.


There are man pages on these functions. I advise reading them.



Now.. The Code.

There are a total of 19 non-library functions used to make this particular
daemon. By group, The first seven handle general flow of logic. 

The next group of four functions handles the opening, verifying, reading
and closing of a FIFO-pipe. To re-state.. the FIFO-pipe allows the daemon 
to receive data without then encumbrance of a full blown User Interface.

The second group of four allows us to create, verify, read, and delete a
semaphore. With the raising of a semaphore, the rest of the system can know
that our daemon is 'alive'. This is not as important for this daemon, but
the need will be great in parallel synchronization.

And the last group of four functions collectively will be the mechanism
to keep a record of the program crashing, and recovery attempt.


void main             ( int, char** );
void sigusr           ( int );
void init_daemon      ( void );
void the_daemon       ( void );
void process_message  ( char* );
int  msg_handler_zero ( char* );
void shutdown_daemon  ( void );

void  open_pipe   ( char*, int );
int   verify_pipe ( char* );
int   read_pipe   ( char* );
void  close_pipe  ( void );

int   get_semaphore  ( int );
int   set_semaphore  ( int );
int   check_semaphore( int );
void  del_semaphore  ( int );

int  check_dirty_bit( void );
int  set_bit_clean  ( void );
int  set_bit_dirty  ( void );
int  system_dirty   ( void );


They look like well-named functions. Function names should always be
close in description to the functionality contained within.

So... Lets take a look at the meat of the code.
The functions: process_message() and msg_handler_zero() have little
function, so we will bypass their explanation. I will attempt
to state the core functionality of the functions, and the code will
follow.



The main() function distinguishes the names of files it want to use.
They all key off from the name of the executable. The DEBUG_FILE is
a file that logs messages. The INTEGRITY_FILE keeps a record that
is useful in knowing what state the last termination of this program
resulted in. The pipe_name is the name of the FIFO-pipe we use to read
data from. If the function check_dirty_bit() returns TRUE, then the
daemon will try to recover from what errors may have been cause from
the daemon terminating incorrectly.

The first function call 'del_semaphore( DAEMON_SEMA )' does not have to
be there. It was included because you may need it there in the development
stage of writing your own daemon. You do need it later on.

This daemon then closes all 'standard devices' because daemons have
no use for such things. The function setpgrp() is called so that
it will not be associated with any particular parent process id.

void main(int argc, char **argv)
{
struct sigaction sigact;

	if( ( program_name = strrchr( *argv, '/') ) == ( char * ) NULL )
		{ program_name = *argv; } else { program_name++; }

	sprintf( DEBUG_FILE,     "/tmp/%s.dbug", program_name );
	sprintf( INTEGRITY_FILE, "/tmp/%s.last", program_name );
	sprintf( pipe_name,      "/tmp/%s.fifo", program_name );
	argc = argc;

	del_semaphore( DAEMON_SEMA );

	if( check_semaphore( DAEMON_SEMA ) == GOOD )
	{
		fprintf(stderr,"Semaphore %d is already taken\n", DAEMON_SEMA);
		fprintf(stderr,"A copy of %s may already be running\n", program_name);
		fprintf(stderr,"Exiting...\n", program_name);
		exit(BAD);
		
	}
	fprintf(stderr,"Semaphore %d will be used\n", DAEMON_SEMA);

	if( check_dirty_bit() == TRUE) 
	{
		fprintf(stderr,
			"%s previously went down dirty, Trying to recover...\n",
			program_name);
		if( system_dirty() == TRUE )
		{
				fprintf(stderr,"%s Recovery Fails, Exiting...\n",program_name);
				exit(BAD);
		}
	}
	else
		fprintf(stderr,"Previous %s process terminated cleanly\n",program_name);

	fclose(stdin);
	fclose(stdout);
	fclose(stderr);
	setpgrp();

	sigact.sa_handler = SIG_IGN;
	sigemptyset(&sigact.sa_mask);
	sigact.sa_flags = 0;
	if (sigaction(SIGCHLD, &sigact, (void *) NULL))
	{
			dbugger(MANDATORY, "Can't get set the initial sigaction\n\n");
			exit(BAD);
	}

	switch(fork())
	{
		case 0:
			dbugger(MANDATORY, "Program: %s is forked in the background", program_name);
			the_daemon();
			break;
		case -1:
			dbugger(MANDATORY, "Program: %s is NOT forked the background (Error Num: %d)",
				program_name, errno);
			exit(BAD);
			break;
	}

	exit(GOOD);
} /* end main */



At this point in execution, we've just forked. So wait a few
seconds so the parent process dies... helps in the prevention of zombies.
Then we'll have the function sigusr() become the handler of a few signals.
We then check for a particular semaphore. If that semaphore was found,
that means it is in use.. more than likely a previous instance of this 
program. The daemon then opens the its read FIFO-pipe and sets the
state flag to dirty. If the program crashed or otherwise stopped, then
we would know that.

void init_daemon( void )
{
char *strptr;
struct sigaction sigact;
time_t lasttime = 0;

	sleep(3);
	ignore_all_signals(); 

	if( get_semaphore( DAEMON_SEMA ) == BAD )
	{
		dbugger(MANDATORY, "Can't get this programs semaphore\n\n");
		exit(BAD);
	}
	dbugger(MANDATORY, "Program: %s is INITIALIZING", program_name);

	sigact.sa_handler = sigusr;
	sigemptyset(&sigact.sa_mask);
	sigact.sa_flags = 0;

	if (sigaction(SIGTERM, &sigact, (void *) NULL))
		dbugger(MANDATORY, "Sigaction - SIGTERM failed");
	if (sigaction(SIGUSR1, &sigact, (void *) NULL))
		dbugger(MANDATORY, "Sigaction - SIGUSR1 failed");

	open_pipe(pipe_name, Pipe_Message_Length);
	set_bit_dirty();

	dbugger(MANDATORY, "Program: %s is running in the backround", program_name);

} /* end of init_daemon */



This next function is self-explanatory. The one note is that the daemon
calls set_clean_bit. Meaning this daemon has terminated cleanly.

void shutdown_daemon(void)
{
	close_pipe();
	dbugger(MANDATORY, "Pipe %s being deleted", pipe_name);
	del_semaphore(DAEMON_SEMA);
	dbugger(MANDATORY, "Semaphore %d being deleted", DAEMON_SEMA);
	set_bit_clean();
	dbugger(MANDATORY, "Program: %s is TERMINATING", program_name);

} /* end of shutdown_daemon */



This function is essential a signal catcher. If a particular signal is
sent to the process and we have decided to handle it.. then this would be
the place to do it. When a signal is received the execution flow jumps
to this function, does its business, and then jumps back. But for reasons 
not explained, the signals SIGTERM, SIGUSR1, SIGALRM only set flags. 
Later, when the execution jumps back to last execution position, and 
finally into the main loop, we will see the importance of those flags.

void sigusr(int sig)
{
	if (sig == SIGTERM)
	{
		dbugger(INFORMATION, "*** Terminate signal received ***");
		Ready_Flag = FALSE;
	}
	if (sig == SIGUSR1)
	{
		dbugger(INFORMATION, "*** Reset signal received ***");
		Reset_Flag = TRUE;
	}
	if (sig == SIGALRM)
		dbugger(INFORMATION, "** An alarm has been raised **");

	dbugger(LOW_LEVEL, "** Signal %d has been raised **", sig);

} /* end sigusr */



And now the function that all the other functions are trying their
best to support. Do notice the Ready_Flag. When it's value is TRUE,
the daemon calls shutdown_daemon() and then ends. However, if the
Reset_Flag is set to TRUE then shutdown_daemon() and init_daemon()
are called. The Reset scenerio is good for Óre-startingú the daemon 
without having to kill and re-invoke the process. If initialization
data was read from an init file and changes made to it, then issuing
a SIGUSR1 to invoke init_daemon() will enable the daemon to
run with the new changes.

void the_daemon(void)
{
char Message[Pipe_Message_Length];

	init_daemon();
	while (Ready_Flag)
	{
		if (read_pipe(Message))
			process_message(Message);

		if (Reset_Flag)
		{
			Reset_Flag = 0;
			shutdown_daemon();
			sleep(1);
			init_daemon();
		}
		usleep(100000);
	}
	shutdown_daemon();

} /*  end of: the_daemon  */




Well, that's enough of the code. Let's run this little doggie.
There is about a dozen things to do in order to test this daemon's
functionality. Here is the short list:

Compile the program:
	cc -o daemon2 daemon2.c dbugger.c sig_ign.c

Run the program:
	daemon2 or ./daemon2

Look at the immediate output:
	stderr: Semaphore 4142 will be used
	/tmp: daemon2.dbug, daemon2.fifo, daemon2.last

Run ps:
	PID TTY STAT TIME COMMAND
	311 1   S    0:20 ./daemon2

Run ipcs:
	semid   owner   perms   nsems   status
	0       root    666     1

Send some data to the daemon:
	echo "0 - This is a test" > /tmp/daemon2.fifo

**Note: Using echo to send data to a pipe is barbaric. Normally
data would be sent by a client program that has opened the same
pipe, and used a write() function. 


Look at the /tmp/daemon2.dbug:
   Jul 22 11:45:07.215|275|daemon2|Read 19 bytes from pipe
   Jul 22 11:45:07.218|275|daemon2|Received from pipe: 0 - This is a test
	

Now send a SIGUSR1 signal to that process:
	kill -10 process#

And finally issue a SIGTERM to the process:
	kill process#

When you view the file /tmp/daemon2.dbug you will see a very good 
interpretation of what had transpired. You may want to run 'ps' and
'ipcs' to assure yourself that termination progressed the right way.


There is good working code attached: daemon2.zip.

This is not the only way to create a daemon, but its an example of
field tested working code. For the small amount of coding effort
you need to modify this daemon, for your own purposes, you will
find the payback quite handsome.

Also note that not all of the headers are needed. 
How they got there? Who knows? It's you code from now on.   :)

		
					Kerry D. Mathews II


C Scene Official Web Site : http://cscene.oftheinter.net
C Scene Official Email : cscene@mindless.com
This page is Copyright © 1997 By C Scene. All Rights Reserved