Linux, where are the return codes stored of system daemons and other processes?
When a process terminates its parent process must acknowledge this using the wait
or waitpid
function. These functions also return the exit status. After the call to wait
or waitpid
the process table entry is removed, and the exit status is no longer stored anywhere in the operating system. You should check if the software you use to start the process saves the exit status somewhere.
If the parent process has not acknowledged that the child has terminated you can read its exit status from the /proc
file system: it is the last field in /proc/[pid]/stat
. It is stored in the same format that wait
returns it, so you have to divide by 256 to get the exit code. Also you probably have to be root.
Controlling a C daemon from another program
Instead of running the program via popen
why not use the good old POSIX fork
+ exec
? It gives you a bit more flexibility.
Now, to answer you question:
My problem is to detect that the daemon has been stopped.
To do this you have to listen to SIGCHLD
signal in your parent/controlling process. This is good enough since you directly invoked the process. But if you called a shell script which then forked your daemon, it would get difficult. This is why most daemons write something called a pid
file - A file written by the daemon early on with it's PID as the only content in that file. Normally, people have it put it /tmp/mydaemon.pid
or something like that.
On Linux, your controlling process can read the PID from this file, then every second you can test if /proc/<pid>/exe
file exists. If not, you know the daemon died. For example, if your child program's PID is 1234, then /proc/1234/exe
will be a soft link to the actual location of the executable of the child program.
Something like this:
FILE *f;
pid_t pid_child;
char proc_path[256];
f = fopen("/tmp/mydaemon.pid", "r");
fscanf(f, "%d", &pid_child);
fclose(f);
sprintf(proc_path, "/proc/%d/exe", pid_child);
while(1) {
if (access(proc_path, F_OK) == 0) {
printf("Program alive !\n");
sleep(1);
} else {
printf("Program dead!\n");
break;
}
}
In fact, this is roughly how many init systems are implemented. See rc, systemd, upstart etc. for a better understanding of how they implement this in more details.
How to get the correct exit code of a shell command executed via pipe using popen() and pclose() in a daemonized process?
The issue with my program was, that the signal SIGCHLD
was not handled by my program.
After evaluating errno
and checking the error given by pclose()
I was able to find a solution. errno
returned ECHILD
which means "No child processes" according to the errno man page. I caught this error by adding some more handling to my pclose_wrapper
lambda function:
...
auto pclose_wrapper = [&rc](FILE *cmd) {
rc = pclose(cmd);
if (rc < 0) {
/* Log the error if pclose returns "-1" signaling an error occured */
syslog(LOG_ERR, "rc is negativ - %s", strerror(errno));
}
};
...
After some more research and looking at the man page for signal I found that my program was ignoring the SIGCHLD
signal as mentioned before. This signal informs a process if a child process stopped or terminated.
The solution was to add sigaction(SIGCHLD, &newSigAction, NULL);
and throwing out the line signal(SIGCHLD, SIG_IGN);
which explicitly ignored the signal.
Here is the working code:
#include <array> // For std::array
#include <cstring>
#include <memory> // For std::unique_ptr
#include <string>
#include <sys/stat.h>
#include <sys/syslog.h> // For all syslog things
#include <sys/wait.h>
#include <unistd.h>
void signalHandler(int sig) {
switch (sig) {
case SIGINT:
case SIGTERM:
break;
case SIGCHLD:
/* Some child related action */
break;
}
}
int main(int argc, char *argv[]) {
/* Open log file to be able to use syslog */
setlogmask(LOG_UPTO(LOG_DEBUG));
openlog("MyDemoProg", LOG_PID, LOG_DAEMON);
#if 1 // Set to 0 to disable the daemonizing
pid_t pid = fork();
if (pid < 0)
exit(EXIT_FAILURE);
if (pid > 0)
exit(EXIT_SUCCESS);
if (setsid() < 0)
exit(EXIT_FAILURE);
struct sigaction newSigAction;
newSigAction.sa_handler = signalHandler;
sigemptyset(&newSigAction.sa_mask);
newSigAction.sa_flags = 0;
sigaction(SIGHUP, &newSigAction, NULL); /* catch hangup signal */
sigaction(SIGTERM, &newSigAction, NULL); /* catch term signal */
sigaction(SIGINT, &newSigAction, NULL); /* catch interrupt signal */
sigaction(SIGCHLD, &newSigAction,
NULL); /* catch child stopped or terminated signal */
pid = fork();
if (pid < 0)
exit(EXIT_FAILURE);
if (pid > 0)
exit(EXIT_SUCCESS);
umask(0);
chdir("/");
for (int x = sysconf(_SC_OPEN_MAX); x >= 0; x--) {
close(x);
}
syslog(LOG_DEBUG, "Daemonizing is enabled");
#else
syslog(LOG_DEBUG, "Daemonizing is disabled");
#endif
std::string command = "ls /var/bla/; sleep 2; echo test";
syslog(LOG_DEBUG, "Command is: %s", command.c_str());
int rc = -999;
std::array<char, 16> buffer;
std::string commandResult;
// A wrapper function to be able to get the return code while still using the
// automatic close function wizzardy of unique_ptr
auto pclose_wrapper = [&rc](FILE *cmd) {
rc = pclose(cmd);
if (rc < 0) {
/* Log the error if pclose returns "-1" signaling an error occured */
syslog(LOG_ERR, "rc is negativ - %s", strerror(errno));
}
};
{
const std::unique_ptr<FILE, decltype(pclose_wrapper)> pipe(
popen(command.c_str(), "r"), pclose_wrapper);
if (!pipe) {
syslog(LOG_ERR, "Could not open pipe! Exiting");
return EXIT_FAILURE;
}
/* Read in the pipe and save the content to a buffer */
while (::fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
commandResult += buffer.data();
}
}
syslog(LOG_DEBUG, "Command result is: %s", commandResult.c_str());
syslog(LOG_DEBUG, "Return code is: %d", rc);
return EXIT_SUCCESS;
}
And here is the output of both the non-daemonized and daemonized version:
(I added a syslog message to indicate if the daemonizing code was enabled or disabled.)
May 10 09:24:30 MY-EMBEDDED-DEVICE daemon.debug MyDemoProg[10872]: Daemonizing is disabled
May 10 09:24:30 MY-EMBEDDED-DEVICE daemon.debug MyDemoProg[10872]: Command is: ls /var/bla/; sleep 2; echo test
May 10 09:24:32 MY-EMBEDDED-DEVICE daemon.debug MyDemoProg[10872]: Command result is: test
May 10 09:24:32 MY-EMBEDDED-DEVICE daemon.debug MyDemoProg[10872]: Return code is: 0
---
May 10 09:24:49 MY-EMBEDDED-DEVICE daemon.debug MyDemoProg[10881]: Daemonizing is enabled
May 10 09:24:49 MY-EMBEDDED-DEVICE daemon.debug MyDemoProg[10881]: Command is: ls /var/bla/; sleep 2; echo test
May 10 09:24:51 MY-EMBEDDED-DEVICE daemon.debug MyDemoProg[10881]: Command result is: test
May 10 09:24:51 MY-EMBEDDED-DEVICE daemon.debug MyDemoProg[10881]: Return code is: 0
Now both versions give the expected return code of "0".
Does adding '&' makes it run as a daemon?
Yes the process will be ran as a daemon, or background process; they both do the same thing.
You can verify this by looking at the opt parser in the source code (if you really want to verify this):
. cmdoption:: --detach
Detach and run in the background as a daemon.
https://github.com/celery/celery/blob/d59518f5fb68957b2d179aa572af6f58cd02de40/celery/bin/beat.py#L12
https://github.com/celery/celery/blob/d59518f5fb68957b2d179aa572af6f58cd02de40/celery/platforms.py#L365
Ultimately, the code below is what detaches it in the DaemonContext
. Notice the fork and exit calls:
def _detach(self):
if os.fork() == 0: # first child
os.setsid() # create new session
if os.fork() > 0: # pragma: no cover
# second child
os._exit(0)
else:
os._exit(0)
return self
Creating a daemon in Linux
In Linux i want to add a daemon that cannot be stopped and which monitors filesystem changes. If any changes would be detected it should write the path to the console where it was started + a newline.
Daemons work in the background and (usually...) don't belong to a TTY that's why you can't use stdout/stderr in the way you probably want.
Usually a syslog daemon (syslogd) is used for logging messages to files (debug, error,...).
Besides that, there are a few required steps to daemonize a process.
If I remember correctly these steps are:
- fork off the parent process & let it terminate if forking was successful. -> Because the parent process has terminated, the child process now runs in the background.
- setsid - Create a new session. The calling process becomes the leader of the new session and the process group leader of the new process group. The process is now detached from its controlling terminal (CTTY).
- Catch signals - Ignore and/or handle signals.
- fork again & let the parent process terminate to ensure that you get rid of the session leading process. (Only session leaders may get a TTY again.)
- chdir - Change the working directory of the daemon.
- umask - Change the file mode mask according to the needs of the daemon.
- close - Close all open file descriptors that may be inherited from the parent process.
To give you a starting point: Look at this skeleton code that shows the basic steps. This code can now also be forked on GitHub: Basic skeleton of a linux daemon
/*
* daemonize.c
* This example daemonizes a process, writes a few log messages,
* sleeps 20 seconds and terminates afterwards.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <syslog.h>
static void skeleton_daemon()
{
pid_t pid;
/* Fork off the parent process */
pid = fork();
/* An error occurred */
if (pid < 0)
exit(EXIT_FAILURE);
/* Success: Let the parent terminate */
if (pid > 0)
exit(EXIT_SUCCESS);
/* On success: The child process becomes session leader */
if (setsid() < 0)
exit(EXIT_FAILURE);
/* Catch, ignore and handle signals */
//TODO: Implement a working signal handler */
signal(SIGCHLD, SIG_IGN);
signal(SIGHUP, SIG_IGN);
/* Fork off for the second time*/
pid = fork();
/* An error occurred */
if (pid < 0)
exit(EXIT_FAILURE);
/* Success: Let the parent terminate */
if (pid > 0)
exit(EXIT_SUCCESS);
/* Set new file permissions */
umask(0);
/* Change the working directory to the root directory */
/* or another appropriated directory */
chdir("/");
/* Close all open file descriptors */
int x;
for (x = sysconf(_SC_OPEN_MAX); x>=0; x--)
{
close (x);
}
/* Open the log file */
openlog ("firstdaemon", LOG_PID, LOG_DAEMON);
}
int main()
{
skeleton_daemon();
while (1)
{
//TODO: Insert daemon code here.
syslog (LOG_NOTICE, "First daemon started.");
sleep (20);
break;
}
syslog (LOG_NOTICE, "First daemon terminated.");
closelog();
return EXIT_SUCCESS;
}
- Compile the code:
gcc -o firstdaemon daemonize.c
- Start the daemon:
./firstdaemon
Check if everything is working properly:
ps -xj | grep firstdaemon
The output should be similar to this one:
+------+------+------+------+-----+-------+------+------+------+-----+
| PPID | PID | PGID | SID | TTY | TPGID | STAT | UID | TIME | CMD |
+------+------+------+------+-----+-------+------+------+------+-----+
| 1 | 3387 | 3386 | 3386 | ? | -1 | S | 1000 | 0:00 | ./ |
+------+------+------+------+-----+-------+------+------+------+-----+
What you should see here is:
- The daemon has no controlling terminal (TTY = ?)
- The parent process ID (PPID) is 1 (The init process)
- The PID != SID which means that our process is NOT the session leader
(because of the second fork()) - Because PID != SID our process can't take control of a TTY again
Reading the syslog:
- Locate your syslog file. Mine is here:
/var/log/syslog
Do a:
grep firstdaemon /var/log/syslog
The output should be similar to this one:
firstdaemon[3387]: First daemon started.
firstdaemon[3387]: First daemon terminated.
A note:
In reality you would also want to implement a signal handler and set up the logging properly (Files, log levels...).
Further reading:
- Linux-UNIX-Programmierung - German
- Unix Daemon Server Programming
Related Topics
Ffmpeg Split Avi into Frames with Known Frame Rate
How to Link to Shared Lib from Shared Lib with Relative Path
How to Automate the Installation of Eclipse Plugins with Command Line
How to Retain Docker Alpine Container After "Exit" Is Used
What Is the Recommended Way to Perform Source-Level Debugging of System Library Calls
Install Marklogic Centos Virtualbox Vm
Chrome Certificate Selection Appears Multiple Times
Redirecting Tcp-Traffic to a Unix Domain Socket Under Linux
Cut or Awk Command to Print First Field of First Row
Merge Multiple Jpgs into Single PDF in Linux
Systemd with Multiple Execstart
Delete Files with String Found in File - Linux Cli
How to Ssh Multiple Hops Without Putting the Local Rsa Key Everywhere
Need an Overview of Debugging Process from the Hardware Layer