There are more steps involved in starting a daemon than you would probably imagine, though they can all be accomplished with a surprisingly small amount of code. The rest of this section presents a step-by-step guide to the more important aspects of the process.
The first task is to close all unnecessary file descriptors. If your daemon were to leave an ordinary file open, this would prevent the file from being deleted from the filesystem by any other process. It would also prevent a mounted file system containing the open file from being unmounted. In the case of terminal files (usually stdin, stdout and stderr), closing unnecessary connections is even more important, because here the daemon's access rights to the terminal will be revoked by the execution of the vhangup() system call after the user at the terminal logs out. This means that the daemon will have file descriptors which it thinks are in an open state, when in reality there is no longer any access to the terminal via these descriptors.
The simplest thing to do is just to close all file descriptors, which will isolate the daemon from these problems. There is no problem with using the close() system call on a file descriptor which is not open, and so the following code fragment can be used:
#include <sys/param.h> for (i = 0; i<NOFILE; ++i) close(i);
The symbolic constant NOFILE gives the maximum number of files that a process can have open at once.
If a process is started from within a login session then it will have a controlling terminal associated with it, inherited from the session. For a daemon, the consequence of this is that it can receive signals generated by the controlling terminal (such as SIGINT and SIGQUIT) which, if they are not caught, will terminate the process. This problem could be overcome by having the daemon ignore all the signals it can, but this would prevent the daemon from using signals for simple IPC purposes. A better solution is to have the daemon disassociate itself from the controlling terminal so that these signals are not propagated to the daemon in the first place.
One way to do this under Linux is to open the file /dev/tty and use ioctl() on the file with the command TIOCNOTTY. It is arranged so that every process which has a controlling terminal has access to that terminal via the file /dev/tty.
A short piece of code to do this could be:
if ((fd = open("/dev/tty", O_RDWR))>=0) { ioctl(fd, TIOCNOTTY, 0); close(fd) }
Under Linux, this is not the only way for a process to disassociate itself from its controlling terminal. Indeed, there is a simpler way to do this that we look at now in the next section.
A process gets its session and process group IDs from its parent process. Because it belongs to a session and a process group, a process will receive any signals that are sent to the session or process group as a whole. This is a similar problem to receiving signals from the controlling terminal, and the solution is essentially the same, i.e. to disassociate the process from this environmental influence.
In POSIX (and, therefore, in Linux) there is a single system call which will disassociate a process from its current session and process group, and set it up as a new session leader. The system call is set session ID:
setsid()
Because a controlling terminal can only be associated with a single session, the setsid() call has the side effect that it also disassociates a process from its controlling terminal (if it has one).
The only problem with setsid() is that it only works if the process which executes it is not already a session leader. In our case it is easy to suppose that the daemon won't be a session leader, but this cannot be guaranteed unless specific steps are taken to make it so. One thing you can say for sure is that if a particular process which is a session leader executes the fork() system call, then, by default, the child process won't be a session leader. This, then, provides the mechanism for making sure that the daemon process is not a session leader when it executes setsid(). All that the daemon needs to do is to execute a fork() call, then exit() in the parent process and execute setsid() in the child process, as follows:
/* After this, child process is not a session leader */ if (fork()) /* Terminate parent process whatever its status */ exit (0); /* Disassociate child from session, process group and tty */ setsid();
Even these measures do not solve all the problems surrounding disassociating a daemon from its controlling terminal. This is due to the fact that when a session leader without a controlling terminal (such as ours is now) opens a terminal device which itself is not already the control terminal for another session, the terminal automatically becomes the controlling terminal for the new session.
Notice, however, that acquiring a controlling terminal in this way can only be done by a session leader. The solution, therefore, is obviously to make sure that our process is not a session leader. This can be done by executing fork() for a second time and once again terminating the parent process. This leaves a child process which does not belong to its initial session or process group, does not have a controlling terminal and, now, cannot reacquire one. The required code fragment now appears as follows:
/* Start up new session, to lose old session and process group */ if (fork()) exit (0); /* Disassociate process group and controlling terminal */ setsid(); /* Become a NON-session leader so that a */ /* control terminal can't be reacquired */ if (fork()) exit (0);
The kernel holds open the current working directory of any process on the system for the life of the process. This does not normally present a problem, but if the process has a current directory in a mounted filesystem then that filesystem is flagged as 'in use' and, as such, it cannot be unmounted. To allow a system super-user to umount a filesystem, the daemon could execute:
chdir("/");
It is also possible that in order to operate properly a daemon has to chdir() to a particular directory. The example socket server is a case in point, where the files in the daemon's current working directory are the only files the server can supply. In cases like this you just have to accept the limitation, or arrange with a system administrator to provide for a place in the root filesystem from which the daemon can run. One possible problem can occur with changing the current working directory to the root. If there is any chance of the daemon terminating and dumping a core file, the kernel will try to open the file in the current directory and if this is the root directory then the operation will fail unless the daemon is running with super-user privileges. To overcome this problem, the following:
chdir("/tmp");
will make /tmp the current working directory, which is usually just a subdirectory of the root filesystem and also gives all processes write permission.
When a process is started off, its file creation mask is inherited from its parent. Typically, this will have a value of 022, meaning that any files created by the daemon will not give write permission to group or world users, whatever permission bits are specified by the daemon itself. Depending upon the nature of the daemon, this action may or may not present a problem. However, it is a simple matter to include the following line into your code to undo the effect of the file creation mask value set by the parent process, whatever it was:
unask(0);
Even now, there are still features in the environment, inherited from the parent process, which can cause a daemon some problems. For example, the nice() process priority setting, or the time remaining on an alarm clock signal, set up with alarm(). However, even though these things are possible, it would be a pretty malicious parent process that would set any of them up to trap an unwary daemon process.
Sometimes daemons are written that create child processes. Making the example socket server into a daemon will demonstrate this type of code. In this case, the parent process keeps looping back to accept more client connections, whilst the child processes service the client requests. Notice that the parent process does not execute a wait() system call to wait for the termination of its child processes. By default, child processes in this position become zombies until some process (probably init) waits for them in the future. The creation of zombies under these circumstances just wastes system resources. There are several ways round the problem under Linux, the simplest being to set the action of the SIGCHLD signal to SIG_IGN as follows:
signal (SIGCHLD, SIG_IGN);
This is a special feature of SIGCHLD which tells the kernel not to generate zombies from the children of the calling process.