c++ create daemonizeMe

发布时间 2023-06-12 16:05:41作者: DoubleLi

daemonizeMe

// Copyright (c) 2017 Deep Aggarwal
#include <getopt.h>
#include <grp.h>
#include <pwd.h>
#include <stdlib.h>
#include <syslog.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>
#include <thread>

static char *applicationName = NULL;
static char *pidFileName = NULL;
static int pidFd = -1;

/**
 * @brief Callback function for handling signals.
 *
 * @param sig Identifier of a signal
 */
void handleSignal(int sig) {
    if (sig == SIGINT) {
        syslog(LOG_INFO, "Stopping %s", applicationName);

        // Unlock and close lockfile
        if (pidFd != -1) {
            lockf(pidFd, F_ULOCK, 0);
            close(pidFd);
        }

        // Delete lockfile
        if (pidFileName != NULL) {
            unlink(pidFileName);
        }

        // Reset signal handling to default behavior
        signal(SIGINT, SIG_DFL);
    }
}

static uid_t getUserID(const char *name) {
    struct passwd pwentry;
    struct passwd *result;
    char *buf;
    size_t bufsize;
    int s;

    // Call to sysconf(_SC_GETPW_R_SIZE_MAX) returns either -1
    // without changing errno or an initial value suggested for
    // the size for buf
    bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
    if (bufsize == -1) {
        // Should be more than enough
        bufsize = 16384;
    }

    buf = new char[bufsize];
    if (buf == NULL) {
        exit(EXIT_FAILURE);
    }

    s = getpwnam_r(name, &pwentry, buf, bufsize, &result);
    if (result == NULL) {
        if (s == 0) {
            std::string sname = name;
            throw std::runtime_error("User \""+ sname +"\" is not found");
        } else {
            std::cerr << "Error in getpwnam_r(...)" << std::endl;
        }
        exit(EXIT_FAILURE);
    }

    return pwentry.pw_uid;
}

static gid_t getGroupID(const char *name) {
    struct group grentry;
    struct group *result;
    char *buf;
    size_t bufsize;
    int s;

    // Call to sysconf(_SC_GETGR_R_SIZE_MAX) returns either -1
    // without changing errno or an initial value suggested for
    // the size for buf
    bufsize = sysconf(_SC_GETGR_R_SIZE_MAX);
    if (bufsize == -1) {
        // Should be more than enough
        bufsize = 16384;
    }

    buf = new char[bufsize];
    if (buf == NULL) {
        exit(EXIT_FAILURE);
    }

    s = getgrnam_r(name, &grentry, buf, bufsize, &result);
    if (result == NULL) {
        if (s == 0) {
            std::string sname = name;
            throw std::runtime_error("User \""+ sname +"\" is not found");
        } else {
            std::cerr << "Error in getgrnam_r(...)" << std::endl;
        }
        exit(EXIT_FAILURE);
    }

    return grentry.gr_gid;
}

/**
 * @brief This function will daemonize this application
 */
static void daemonizeMe() {
    pid_t pid = 0;
    int fd;

    // Fork off the parent process to ensures that
    //   the child process is not a process group leader, so
    //   that it is possible for that process tocreate
    //   a new session and become a session leader
    pid = fork();

    // If error occurred
    if (pid < 0) {
        exit(EXIT_FAILURE);
    }

    // On success, terminate the parent
    if (pid > 0) {
        exit(EXIT_SUCCESS);
    }

    // On success, the child process becomes processgroup
    // and session group leader. Since a controlling terminal
    // is associated with a session, and this new session
    // has not yet acquired a controlling terminal our
    // process now has no controlling terminal,
    // which is a good thing for daemons
    if (setsid() < 0) {
        exit(EXIT_FAILURE);
    }

    // Ignore signal sent from child to parent process
    signal(SIGCHLD, SIG_IGN);

    // Fork off for the second time to ensure that
    // the new child process is not a session leader,
    // so it won't be able to (accidentally) allocate
    // a controlling terminal, since daemons are not
    // supposed to ever have a controlling terminal
    pid = fork();

    // If error occurred
    if (pid < 0) {
        exit(EXIT_FAILURE);
    }

    // On success, terminate the parent
    if (pid > 0) {
        exit(EXIT_SUCCESS);
    }

    // Set new file permissions
    //
    //  "0" means that new files this daemon will
    //  create will have read and write permission for everyone
    //  (0666 or -rw-rw-rw-), and new directories that
    //  this daemon will create will have read, write and
    //  search permissions for everyone (0777 or drwxrwxrwx)
    //  This measn that we have complete control over the
    //  permissions of anything we write
    umask(0);

    // Change the working directory to the root directory
    //  This is to ensure that our process doesn't keep any directory in use.
    //  Failure to do this could make it so that an administrator couldn't unmount
    //  a filesystem, because it was our current directory
    if (chdir("/") != 0) {
        exit(EXIT_FAILURE);
    }

    // Drop priviledges since running network based daemons
    // with root permissions is considered to be a serious risk
    if (getuid() == 0) {
        // If here that means process is running as root,
        // so drop the root privileges
        uid_t userid = NULL;
        gid_t groupid = NULL;
        try {
            // FIXME: Hard-coded since this is the user that we will deal with
            userid = getUserID("mydaemon");
            groupid = getGroupID("mydaemon");
        } catch(const std::runtime_error& error) {
            syslog(LOG_ERR, "\"mydaemon\" couldn't be found");
            exit(EXIT_FAILURE);
        }

        if (setgid(groupid) != 0) {
            exit(EXIT_FAILURE);
        }
        if (setuid(userid) != 0) {
            exit(EXIT_FAILURE);
        }
    }

    // If we try to get root privileges back, it
    // should fail. If it doesn't fail, we exit with failure
    if (setuid(0) != -1) {
        exit(EXIT_FAILURE);
    }

    // Close all open file descriptors
    for (fd = sysconf(_SC_OPEN_MAX); fd > 0; fd--) {
        close(fd);
    }

    // Reopen stdin (fd = 0), stdout (fd = 1), stderr (fd = 2) as /dev/null
    stdin = fopen("/dev/null", "r");
    stdout = fopen("/dev/null", "w+");
    stderr = fopen("/dev/null", "w+");

    // Write PID of daemon to lockfile
    if (pidFileName != NULL) {
        char str[256];
        pidFd = open(pidFileName, O_RDWR|O_CREAT, 0640);
        if (pidFd < 0) {
            // Can't open lockfile
            exit(EXIT_FAILURE);
        }
        if (lockf(pidFd, F_TLOCK, 0) < 0) {
            // Can't lock file
            exit(EXIT_FAILURE);
        }

        // Get current PID
        sprintf(str, "%d\n", getpid());

        // Write PID to lockfile
        write(pidFd, str, strlen(str));
    }
}

/**
 * @brief This function starts the application
 */
static void startApplication() {
    syslog(LOG_NOTICE, "I am daemonizeMe and I am writing to my syslog");
    while (true) {
        // Run your server here
        // For example, a server running in background as a daemon process
        // listening for incoming requests from clients
    }
}

/**
 * @brief Free allocated memory
 */
static void cleanGlobalMemory() {
    if (pidFileName) {
        free(pidFileName);
    }

    return;
}

/**
 * @brief Print usage on the stdout
 */
void printHelp(void) {
    printf("\nUsage: %s [OPTIONS]\n\n", applicationName);
    printf("Options:\n");
    printf("    -h --help                 Print this help message\n");
    printf("    -p --pid_file  filename   PID file used by this application\n");
    printf("    -d --daemon               Daemonize this application\n");
    printf("\n");
}

int main(int argc, char **argv) {
    // Get the application name
    applicationName = argv[0];

    static struct option commandLineOptions[] = {
        {"pid_file",    required_argument,  0, 'p'},
        {"daemon",      no_argument,        0, 'd'},
        {"help",        no_argument,        0, 'h'},
        {NULL,          0,                  0,  0 }
    };

    // Process all the command line arguments
    int getopt_longRV;
    int optionIndex = 0;
    int daemonize = 0;  // 0 means do not daemonize
    while ((getopt_longRV = getopt_long(argc,
                    argv,
                    "p:dh",
                    commandLineOptions,
                    &optionIndex)) != -1) {
        switch (getopt_longRV) {
            case 'p':
                pidFileName = strdup(optarg);
                break;
            case 'd':
                daemonize = 1;
                break;
            case 'h':
                printHelp();
                return EXIT_SUCCESS;
            case '?':
                printHelp();
                return EXIT_FAILURE;
            default:
                break;
        }
    }

    // If daemonize flag is passed
    if (daemonize == 1) {
        daemonizeMe();
    }

    // Open system log to write message to it
    openlog(argv[0], LOG_PID|LOG_CONS, LOG_DAEMON);
    syslog(LOG_INFO, "Started %s", applicationName);

    // Handle SIGINT by this daemon
    signal(SIGINT, handleSignal);

    // Main function to start the application
    startApplication();

    // Write final system log and close the log
    syslog(LOG_INFO, "Stopped %s", applicationName);
    closelog();

    // Free memory
    cleanGlobalMemory();

    return 0;
}