In the very start of the endeavor to the daemons' underground world I thought it would be inevitable to be acquainted with a few, perhaps new, essential concepts about processes in terms of how they are collected within the system. So I set out to knowing more about sessions and process groups. It was very instructive as a ground to better undrestanding the necessary steps to turn an ordinary process into a daemon in the traditional way. So, without further words, in essence, the main() function of a Solaris C++ program (or application) that is (or becomes) a daemon in the traditional way is:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <csignal>
#include <cstdlib>
#include <cstdio>
#include <cstdarg>
#include <string>
#include <iostream>
#include "toolbox.hxx"
int main()
{
// Some other log file or even syslog could be better.
// This is just for illustrational purposes.
// The full implementation is elsewhere.
toolbox::log log( "trace.log" );
if ( log.open() )
std::cout
<< "Log file: "
<< log.filename
<< std::endl;
else
{
std::cout
<< "FAILURE"
<< std::endl;
::exit( EXIT_FAILURE );
}
// This function does all the "magic"!
daemon( log );
log.write( "Finally, success! I'm a daemon!\n" );
const int seconds = 30;
log.write( "Resting for %d seconds...\n", seconds );
::sleep( seconds );
log.write( "Bye cruel world!\n\n" );
log.close();
return EXIT_SUCCESS;
}
Now let me show the magical :-) daemon() function:
// Little helper...
void error( const toolbox::log & log, const char * msg, ... )
{
int code = errno;
va_list ap;
va_start( ap, msg );
// MT-safe if setlocale(3C) not called
// Hence, possbily a serious limitation!
// More advanced C++ to the rescue!
char * buffer = 0;
::vasprintf( &buffer, msg, ap );
log.write( "ERROR: %s (%s)\n", buffer, ::strerror(code) );
::free( buffer );
va_end( ap );
::exit( EXIT_FAILURE );
}
void daemon( toolbox::log & log )
{
if ( ! log.write( "\n\nTrying to \"become\" daemon ...\n\n" ) )
::exit( EXIT_FAILURE );
pid_t pid;
// Creates a new process in the same process group.
// Sure is the new process is NOT a process group leader.
// The new process will be entitled to create a new session.
if ( ( pid = ::fork() ) < 0 )
error( log, "1st call to fork()" );
else if ( pid > 0 )
{
log.write( "Bye 1st generation descendant!\n" );
log.write( "PID %d to become a daemon!\n\n", pid );
::exit( EXIT_SUCCESS );
}
// Creates a new session, effectively
// disassociating from the controlling terminal.
if ( ::setsid() < 0 )
error( log, "setsid()" );
// The SIGHUP does not apply to me anymmore.
// There's no controlling terminal after all!
if ( ::sigignore( SIGHUP ) )
error( log, "sigignore()" );
// Creates another new process in the new process group.
// Sure is the new process is NOT a process group leader.
// The new process won't EVER get a controlling terminal.
if ( ( pid = ::fork() ) < 0 )
error( log, "2nd call to fork()" );
else if ( pid > 0 )
{
log.write( "Bye 2nd generation descendant!\n" );
log.write( "PID %d to become a daemon!\n\n", pid );
::exit( EXIT_SUCCESS );
}
//
// Finally! Almost there...
//
// Initially assures full access to daemon's own files.
// Later, should be diligently and temporarily set to 007,
// specially before library calls that involve files.
::umask( 0 );
// Changes to the selected working directory
// or at least to / so any unmounts should succeed.
// After this call don't forget to specify full paths!
if ( ::chdir( "/" ) < 0 )
error( log, "chdir()" );
{
// Makes sure there's no left over open files.
log.close();
::fcloseall();
// Mute all the standard files.
const int std_stream[] =
{
::open( "/dev/null", O_RDWR ), // stdin
::dup2( 0, 1 ), // stdout
::dup2( 0, 2 ) // stderr
};
if ( ! log.open() )
exit( EXIT_FAILURE );
// Double-check muted descriptors are 0, 1 and 2.
for ( auto i=0; i<3; i++ )
if ( std_stream[ i ] != i )
error( log, "Muting standard files." );
}
}
A log file produced by running this daemon could be:
$ cat trace.log
[2017-05-11 16:29:29]
Trying to "become" daemon ...
[2017-05-11 16:29:29] Bye 1st generation descendant!
[2017-05-11 16:29:29] PID 2508 to become a daemon!
[2017-05-11 16:29:29] Bye 2nd generation descendant!
[2017-05-11 16:29:29] PID 2509 to become a daemon!
[2017-05-11 16:29:29] Finally, success! I'm a daemon!
[2017-05-11 16:29:29] Resting for 30 seconds...
[2017-05-11 16:29:59] Bye cruel world!
The above approach, although traditional is not the recommended one. Nevertheless it's still possible to catch once in a while some daemons logs in SMF logs which reveal they could only have been programmed the old way...