9
\$\begingroup\$

I am aware that the malloc is a potential memory leak, but with an execvp coming, and that never returning.

The purpose is to do something like:

daemon my-blocking-program arg1

The idea is to daemonize ourselves, null stdin/stdout, and then execute whatever program was specified in the arguments. This is very similar to nohup, but without logging to a file, or requiring an &.

/*
 * daemon.c
 *
 *  Created on: Aug 1, 2015
 *      Author: javaprophet
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>

int main(int argc, char** argv) {
    if (argc < 2) {
        printf("No program to execute!\n");
        return 1;
    }
    pid_t pid = fork();
    if (pid < 0) {
        exit(1);
    } else if (pid > 0) {
        exit(0);
    }
    if (setsid() < 0) {
        exit(1);
    }
    freopen("/dev/null", "r", stdin);
    freopen("/dev/null", "w", stdout);
    freopen("/dev/null", "w", stderr);
    char** args = malloc(argc * sizeof(char*));
    if (args == NULL) {
        exit(1);
    }
    for (int i = 1; i < argc; i++) {
        args[i - 1] = argv[i];
    }
    args[argc - 1] = NULL;
    execvp(argv[1], args);
    return 1; // will only ever return if execvp failed
}
\$\endgroup\$
0

3 Answers 3

12
\$\begingroup\$

The points jacwah made in his answer are basically valid.

You don't seem to be using any feature from <sys/wait.h> so that could be dropped from your list of headers. Similarly, you aren't using <errno.h> directly; you aren't using anything from <sys/stat.h>; you aren't using anything from <string.h> — so those could be dropped from your list of headers. The header <sys/types.h> isn't needed since POSIX 2001 — officially.

Nominally, you should check that freopen() works. There isn't going to be much of anything useful happening on the system if /dev/null is AWOL, but maybe reporting the problem would be a useful heads up for the user. This is a very minor point.

If you were going to copy the arguments (which you're not), then it would be more appropriate to do that before modifying standard error; in fact, modifying standard error should be the last thing you do before the execvp(), precisely so that you can report errors until the last possible moment.


There are some other points to consider when daemonizing. These do not all necessarily need to be acted on — but they are points to consider.

  1. Should the daemon change directory to /?

    • Pro: it means that the daemon isn't blocking unmounting the file system its on, etc. This used to be an issue in the days of multiple file systems because of multiple disk drives. These days, it is far less of a concern, in general. But beware of running the daemon on a network-mounted file system (NFS or equivalent).
    • Con: it means that the daemon doesn't ensure that the file system it needs is still mounted. Yes, the converse of the first problem. I have a daemonize program which was used to daemonize a DBMS, and it consciously has an option to specify which directory the daemonized program should have as its current directory (the default is not to change directory — like yours), and I used it to change directory to where the DBMS software was installed, precisely so that if it was a network-mounted file system, it wouldn't be unmounted while the daemon was running because the file system would be busy.
  2. Should the daemon close open files?

    • The usual answer is yes; you probably don't want any files other than than the standard ones open.
    • My code provides for keeping selected file descriptors open.
  3. Should the daemon be able to write to a log file?

    • My program has command line options for each of the standard descriptors separately. The default is /dev/null for all three, though.
  4. If you're opening a log file, which options are used with the open() call.

    • My code goes over the top; you can control separately O_CREAT, O_APPEND, O_EXCL, O_TRUNC. It also ensures O_NOCTTY.
  5. Should the daemon ignore any signals?

  6. Should the daemon set umask?

  7. Should the PID of the final daemon process be reported on standard output?

I used my daemonize in conjunction with env to set the exact environment and in conjunction with a program similar to sudo or su to set the user ID and the active groups for the daemon, which gave me very precise (but not concise) control the environment of the DBMS daemon.


What you've got can easily be fixed into a workmanlike usable program by simply dropping the argument copying. The extensive list of other issues to consider doesn't prevent it being usable.

\$\endgroup\$
0
8
\$\begingroup\$

I don't think malloc before exec is a problem. As you've said it's freed by the OS anyway.

Error messages

A user wants to know if a command has failed! Even if they check the exit status of your program, they want to know why it failed. You can save stderr to another file descriptor before redirecting to /dev/null. Use fprintf to give users information when a standard library or system call fails. Affected calls: fork, setsid, execvp, malloc.

int oldstderrfd = -1;
FILE *oldstderr = NULL;

oldstderrfd = dup(STDERR_FILENO);
oldstderr = fdopen(oldstderrfd);

fprintf(oldstderr, "%s: %s\n", argv[0], strerror(errno);

Copying arguments

You don't need to copy the pointers in argv. Use &argv[1] instead.

\$\endgroup\$
0
2
\$\begingroup\$

In unistd.h is a declaration for the daemon(3) method, which you can use to implement your program without reinventing the wheel.

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char **argv) {
  //Read command line from arguments
  assert(argc >= 1 + 1);
  //read daemon parameters from the env
  int nochdir = getenv("nochdir") ? 1 : 0;
  int noclose = getenv("noclose") ? 1 : 0;

  //Daemonize
  daemon(nochdir, noclose);
  //No we're continuing a different process

  //This will only be visible with noclose set
  fprintf(stderr, "daemon pid: %d\n", getpid());

  //Exec the command line -- notice how this avoids the malloc call you're making
  execvp(argv[1], argv+1);

  //Won't get here unless exec fails
  fprintf(stderr, "Failed to exec: %s : %s\n", argv[1], strerror(errno));
  return 1;
}
\$\endgroup\$
1
  • 4
    \$\begingroup\$ Note that daemon(3) is not part of POSIX; it is not available everywhere. The daemon() function is declared in <stdlib.h> on Mac OS X. \$\endgroup\$ Aug 1, 2015 at 19:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.