Skip to content

Instantly share code, notes, and snippets.

@cypres
Created April 2, 2014 08:49
Show Gist options
  • Save cypres/9930379 to your computer and use it in GitHub Desktop.
Save cypres/9930379 to your computer and use it in GitHub Desktop.
C++ example daemon with fork and relaunch should child die. Required C++11, gflags and glog.
//
// Copyright (C) 2011-2012 Yaroslav Stavnichiy <yarosla@gmail.com>
// Copyright (C) 2014 OnlineCity Aps <hd@oc.dk>
//
// Inspired by: https://bitbucket.org/yarosla/nxweb/src/tip/src/lib/daemon.c
//
// Licensed under The MIT License:
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>
#include <grp.h>
#include <fcntl.h>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <cerrno>
#include <csignal>
#include "daemon/daemon.h"
DEFINE_bool(daemon, false, "Run as a daemon");
DEFINE_string(pidfile, "/var/run/ocmg-cpp-messagerouter.pid", "PID file");
DEFINE_string(workdir, "", "working directory");
DEFINE_string(user, "", "Run as user");
DEFINE_string(group, "", "Run as group");
namespace oc {
namespace daemon {
void ContinueAsDaemon(const std::string &work_dir, bool keep_stderr_open) {
// Our process ID and Session ID
pid_t pid, sid;
// Fork off the parent process
pid = fork();
if (pid < 0) {
exit(EXIT_FAILURE);
}
// If we got a good PID, then we can exit the parent process.
if (pid > 0) {
exit(EXIT_SUCCESS);
}
// Create a new SID for the child process
sid = setsid();
if (sid < 0) {
LOG(FATAL) << "setsid() failed";
}
// Change the file mode mask
umask(0);
// Change the current working directory
if (!work_dir.empty() && chdir(work_dir.c_str()) < 0) {
LOG(FATAL) << "chdir(work_dir) failed";
}
// Close the standard file descriptors
int zfd = open("/dev/null", O_RDONLY);
if (zfd == -1) {
LOG(FATAL) << "open(/dev/null) failed";
}
close(STDIN_FILENO);
close(STDOUT_FILENO);
if (dup2(zfd, STDIN_FILENO) == -1 || dup2(zfd, STDOUT_FILENO) == -1) {
LOG(FATAL) << "dup2(stdin/stdout) failed";
}
if (!keep_stderr_open) {
close(STDERR_FILENO);
if (dup2(zfd, STDERR_FILENO) == -1) {
LOG(FATAL) << "dup2(stderr) failed";
}
}
close(zfd);
}
void CreatePidFile(const std::string &pid_file, pid_t pid) {
auto pid_str = std::to_string(pid);
int fd = open(pid_file.c_str(), O_CREAT|O_TRUNC|O_WRONLY, 0666);
if (fd == -1) {
LOG(ERROR) << "can't create pid file " << pid_file << " [" << errno << "]";
return;
}
if (write(fd, pid_str.c_str(), pid_str.length())) {
close(fd);
}
}
int Relauncher(const std::function<void()> &main, const std::string &pid_file) {
pid_t pid = fork();
if (pid < 0) {
exit(EXIT_FAILURE);
} else if (pid > 0) { // we are the parent
int status;
if (!pid_file.empty()) {
CreatePidFile(pid_file, pid);
}
if (waitpid(pid, &status, 0) == -1) {
LOG(ERROR) << "waitpid failure";
exit(EXIT_FAILURE);
}
if (!pid_file.empty()) {
unlink(pid_file.c_str());
}
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) == EXIT_SUCCESS) {
LOG(INFO) << "Server closing";
} else {
LOG(ERROR) << "Server exited, status=" << WEXITSTATUS(status);
}
return WEXITSTATUS(status);
} else if (WIFSIGNALED(status)) {
LOG(ERROR) << "Server killed by signal " << WTERMSIG(status);
return 1;
}
} else { // we are the child
// Drop privileges if user and group is specified
if (FLAGS_user.length() > 0 && FLAGS_group.length() > 0) {
DropPrivileges(FLAGS_user, FLAGS_group);
}
LOG(INFO) << "Starting server loop";
main();
LOG(INFO) << "Server loop end";
exit(EXIT_SUCCESS);
}
return 0;
}
static uid_t get_uid_by_name(const std::string &user_name) {
auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
if (buflen == -1) {
buflen = 1024; // fallback on systems where it returns -1 (ie. FreeBSD)
}
char *buf = static_cast<char *>(malloc(buflen));
struct passwd pwbuf, *pwbufp;
getpwnam_r(user_name.c_str(), &pwbuf, buf, buflen, &pwbufp);
free(buf);
return pwbufp? pwbufp->pw_uid : -1;
}
static gid_t get_gid_by_name(const std::string &group_name) {
auto buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
if (buflen == -1) {
buflen = 1024; // fallback on systems where it returns -1 (ie. FreeBSD)
}
char *buf = static_cast<char *>(malloc(buflen));
struct group grbuf, *grbufp;
getgrnam_r(group_name.c_str(), &grbuf, buf, buflen, &grbufp);
free(buf);
return grbufp? grbufp->gr_gid : -1;
}
int RunDaemon(const std::string &work_dir, const std::string &pid_file, bool keep_stderr_open,
const std::function<void()> &main) {
ContinueAsDaemon(work_dir, keep_stderr_open);
while (Relauncher(main, pid_file) != EXIT_SUCCESS) {
sleep(2); // sleep 2 sec and launch again until child exits with EXIT_SUCCESS
}
return EXIT_SUCCESS;
}
int RunNormal(const std::string &work_dir, const std::string &pid_file, bool keep_stderr_open,
const std::function<void()> &main) {
if (!work_dir.empty() && chdir(work_dir.c_str()) < 0) {
LOG(FATAL) << "chdir(work_dir) failed";
}
if (!pid_file.empty()) {
CreatePidFile(pid_file, getpid());
}
// Drop privileges if user and group is specified
if (FLAGS_user.length() > 0 && FLAGS_group.length() > 0) {
DropPrivileges(FLAGS_user, FLAGS_group);
}
main();
if (!pid_file.empty()) {
unlink(pid_file.c_str()); // this might not always succeed since the privileges are dropped
}
return EXIT_SUCCESS;
}
int DropPrivileges(const std::string &group_name, const std::string &user_name) {
if (!user_name.empty() && !group_name.empty()) {
uid_t uid = get_uid_by_name(user_name);
gid_t gid = get_gid_by_name(group_name);
if (uid == -1 || gid == -1) {
LOG(ERROR) << "uid=" << uid << " gid=" << gid << " can't set uid&gid";
return -1;
} else {
// change them permanently
#if defined (__linux) || defined (linux)
if (setresgid(gid, gid, gid) == -1) {
LOG(ERROR) << "can't set gid=" << gid << " errno=" << errno;
return -1;
}
if (setresuid(uid, uid, uid) == -1) {
LOG(ERROR) << "can't set uid=" << uid << " errno=" << errno;
return -1;
}
#else
if (setgid(gid) == -1) {
LOG(ERROR) << "can't set gid=" << gid << " errno=" << errno;
return -1;
}
if (setuid(uid) == -1) {
LOG(ERROR) << "can't set uid=" << uid << " errno=" << errno;
return -1;
}
#endif
LOG(INFO) << "privileges dropped to " << group_name << "[" << gid << "] " << user_name << "[" << uid << "]";
}
}
return 0;
}
int MainStub(int argc, char **argv, const MainFunctionCallback &main) {
// Initialize gflags and glog
google::ParseCommandLineFlags(&argc, &argv, true);
google::InitGoogleLogging(argv[0]);
// Do we need to keep stderr open?
bool keep_stderr_open = FLAGS_logtostderr || FLAGS_alsologtostderr;
// Forking daemon with re-launch
if (FLAGS_daemon) {
RunDaemon(FLAGS_workdir, FLAGS_pidfile, keep_stderr_open, std::bind(main, argc, argv));
} else {
RunNormal(FLAGS_workdir, FLAGS_pidfile, keep_stderr_open, std::bind(main, argc, argv));
}
return EXIT_SUCCESS;
}
// There is no standard (non pthread specific) way of doing this, so
// use pthread_sigmask for this
int BlockSignals() {
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGTERM);
sigaddset(&set, SIGPIPE);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGQUIT);
sigaddset(&set, SIGHUP);
sigaddset(&set, SIGUSR1);
int res = pthread_sigmask(SIG_BLOCK, &set, NULL);
if (res) {
LOG(ERROR) << "can't set pthread_sigmask";
exit(EXIT_SUCCESS); // simulate normal exit so we don't respawn
}
return res;
}
int InstallStopCallback(void (*func)(int)) {
signal(SIGTERM, func);
signal(SIGINT, func);
// Unblock signals for the main thread;
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGTERM);
sigaddset(&set, SIGINT);
int res = pthread_sigmask(SIG_UNBLOCK, &set, NULL);
if (res) {
LOG(ERROR) << "can't unset pthread_sigmask";
exit(EXIT_SUCCESS); // simulate normal exit so we don't respawn
}
return res;
}
} // namespace daemon
} // namespace oc
//
// Copyright (C) 2011-2012 Yaroslav Stavnichiy <yarosla@gmail.com>
// Copyright (C) 2014 OnlineCity Aps <hd@oc.dk>
//
// Inspired by: https://bitbucket.org/yarosla/nxweb/src/tip/src/lib/daemon.c
//
// Licensed under The MIT License:
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#pragma once
#include <unistd.h>
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <functional>
#include <string>
DECLARE_bool(daemon);
DECLARE_string(pidfile);
DECLARE_string(workdir);
DECLARE_string(user);
DECLARE_string(group);
namespace oc {
namespace daemon {
typedef std::function<void(int, char **)> MainFunctionCallback;
// Initializes gflags, glog and starts either in daemon or normal mode as specified by flags
int MainStub(int argc, char **argv, const MainFunctionCallback &main);
// Performs all the steps required to become a daemon
void ContinueAsDaemon(const std::string &work_dir, bool keep_stderr_open);
// Create a PID file and writes pid to it
void CreatePidFile(const std::string &pid_file, pid_t pid);
// Fork and wait for something to happen
// Called in a loop until it returns EXIT_SUCCESS, thus the server is simply relaunched if it fails
int Relauncher(const std::function<void()> &main, const std::string &pid_file);
// Becomes a daemon and then calls relauncher in a loop
int RunDaemon(const std::string &work_dir, const std::string &pid_file, bool keep_stderr_open,
const std::function<void()> &main);
// Performs the same steps as Relauncher but without daemonizing to looping
int RunNormal(const std::string &work_dir, const std::string &pid_file, bool keep_stderr_open,
const std::function<void()> &main);
// Drop current process privileges to a group and user
int DropPrivileges(const std::string &group_name, const std::string &user_name);
// Block signals on all threads, otherwise a child thread may receive signal
// Without doing this SIGINT/SIGTERM handlers won't work as expected on Linux/FreeBSD
// Must be called before starting (child) threads
int BlockSignals();
// Install a callback for SIGINT and SIGTERM (stop signals)
// Call after starting threads
int InstallStopCallback(void (*func)(int));
} // namespace daemon
} // namespace oc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment