summaryrefslogtreecommitdiffabout
path: root/src
Side-by-side diff
Diffstat (limited to 'src') (more/less context) (show whitespace changes)
-rw-r--r--src/.gitignore5
-rw-r--r--src/Makefile.am18
-rw-r--r--src/configuration.cc149
-rw-r--r--src/configuration.h23
-rw-r--r--src/dudki.cc244
-rw-r--r--src/process.cc184
-rw-r--r--src/process.h36
-rw-r--r--src/util.cc21
-rw-r--r--src/util.h20
9 files changed, 700 insertions, 0 deletions
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 0000000..e26d996
--- a/dev/null
+++ b/src/.gitignore
@@ -0,0 +1,5 @@
+.deps
+Makefile
+Makefile.in
+COPYING.cc
+dudki
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..3810272
--- a/dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,18 @@
+sbin_PROGRAMS = dudki
+
+INCLUDES = ${DOTCONF_CFLAGS}
+LIBS += ${DOTCONF_LIBS}
+AM_CPPFLAGS = \
+ -DDEFAULT_CONF_FILE=\"${sysconfdir}/${PACKAGE}.conf\" \
+ -DDEFAULT_PID_FILE=\"/var/run/${PACKAGE}.pid\"
+
+dudki_SOURCES = dudki.cc \
+ process.cc process.h \
+ configuration.cc configuration.h \
+ util.cc util.h \
+ COPYING.cc
+
+COPYING.cc: ${top_srcdir}/COPYING
+ echo "const char * COPYING =" >$@ || (rm $@;exit 1)
+ sed 's/"/\\"/g' $< | sed 's/^/\"/' | sed 's/$$/\\n\"/' >>$@ || (rm $@;exit 1)
+ echo ";" >>$@ || (rm $@;exit 1)
diff --git a/src/configuration.cc b/src/configuration.cc
new file mode 100644
index 0000000..eb010c1
--- a/dev/null
+++ b/src/configuration.cc
@@ -0,0 +1,149 @@
+#include <stdexcept>
+using namespace std;
+#include <dotconf.h>
+#include "configuration.h"
+
+#ifndef DEFAULT_PID_FILE
+# define DEFAULT_PID_FILE "/var/run/dudki.pid"
+#endif
+
+configuration::configuration()
+ : check_interval(60), pidfile(DEFAULT_PID_FILE),
+ daemonize(true) {
+ }
+
+enum dc_ctx {
+ DCC_ROOT = 1,
+ DCC_PROCESS = 2
+};
+struct dc_context {
+ dc_ctx ctx;
+ configuration* cf;
+ process* ps;
+
+ dc_context()
+ : ctx(DCC_ROOT), cf(NULL), ps(NULL) { }
+};
+
+static DOTCONF_CB(dco_check_interval) { dc_context *dcc = (dc_context*)ctx;
+ dcc->cf->check_interval = cmd->data.value;
+ return NULL;
+}
+static DOTCONF_CB(dco_daemonize) { dc_context *dcc = (dc_context*)ctx;
+ dcc->cf->daemonize = cmd->data.value;
+ return NULL;
+}
+
+static DOTCONF_CB(dco_pid_file) { dc_context *dcc = (dc_context*)ctx;
+ switch(dcc->ctx) {
+ case DCC_ROOT:
+ dcc->cf->pidfile = cmd->data.str;
+ break;
+ case DCC_PROCESS:
+ dcc->ps->pidfile = cmd->data.str;
+ break;
+ default:
+ return "Unexpected PidFile";
+ }
+ return NULL;
+}
+static DOTCONF_CB(dco_mailto_header) { dc_context *dcc = (dc_context*)ctx;
+ if(cmd->arg_count!=2)
+ return "Invalid number of arguments";
+ string h = cmd->data.list[0];
+ string v = cmd->data.list[1];
+ switch(dcc->ctx) {
+ case DCC_ROOT:
+ dcc->cf->mailto_headers[h] = v;
+ break;
+ case DCC_PROCESS:
+ dcc->ps->mailto_headers[h] = v;
+ break;
+ default:
+ return "Unexpected MailtoHeader";
+ }
+ return NULL;
+}
+static DOTCONF_CB(dco_notify) { dc_context *dcc = (dc_context*)ctx;
+ switch(dcc->ctx) {
+ case DCC_ROOT:
+ dcc->cf->notify = cmd->data.str;
+ break;
+ case DCC_PROCESS:
+ dcc->ps->notify = cmd->data.str;
+ break;
+ default:
+ return "Unexpected Notify";
+ }
+ return NULL;
+}
+
+static DOTCONF_CB(dco_process) { dc_context *dcc = (dc_context*)ctx;
+ string id = cmd->data.str;
+ if(id[id.length()-1]=='>')
+ id.erase(id.length()-1);
+ dcc->ps = &(dcc->cf->processes[id]);
+ dcc->ctx = DCC_PROCESS;
+ return NULL;
+}
+static DOTCONF_CB(dco__process) { dc_context *dcc = (dc_context*)ctx;
+ dcc->ps = NULL;
+ dcc->ctx = DCC_ROOT;
+ return NULL;
+}
+
+static DOTCONF_CB(dco_restart_command) { dc_context *dcc = (dc_context*)ctx;
+ dcc->ps->restart_cmd = cmd->data.str;
+ return NULL;
+}
+static DOTCONF_CB(dco_user) { dc_context *dcc = (dc_context*)ctx;
+ dcc->ps->user = cmd->data.str;
+ return NULL;
+}
+static DOTCONF_CB(dco_group) { dc_context *dcc = (dc_context*)ctx;
+ dcc->ps->group = cmd->data.str;
+ return NULL;
+}
+static DOTCONF_CB(dco_chroot) { dc_context *dcc = (dc_context*)ctx;
+ dcc->ps->chroot = cmd->data.str;
+ return NULL;
+}
+
+static const configoption_t dc_options[] = {
+ { "CheckInterval", ARG_INT, dco_check_interval, NULL, DCC_ROOT },
+ { "Daemonize", ARG_TOGGLE, dco_daemonize, NULL, DCC_ROOT },
+ { "PidFile", ARG_STR, dco_pid_file, NULL, DCC_ROOT|DCC_PROCESS },
+ { "MailtoHeader", ARG_STR, dco_mailto_header, NULL, DCC_ROOT|DCC_PROCESS },
+ { "Notify", ARG_STR, dco_notify, NULL, DCC_ROOT|DCC_PROCESS },
+ { "<Process", ARG_STR, dco_process, NULL, DCC_ROOT },
+ { "RestartCommand", ARG_STR, dco_restart_command, NULL, DCC_PROCESS },
+ { "User", ARG_STR, dco_user, NULL, DCC_PROCESS },
+ { "Group", ARG_STR, dco_group, NULL, DCC_PROCESS },
+ { "Chroot", ARG_STR, dco_chroot, NULL, DCC_PROCESS },
+ { "</Process>", ARG_NONE, dco__process, NULL, DCC_PROCESS },
+ LAST_OPTION
+};
+
+static const char *dc_context_checker(command_t *cmd,unsigned long mask) {
+ dc_context *dcc = (dc_context*)cmd->context;
+ if( (mask==CTX_ALL) || ((mask&dcc->ctx)==dcc->ctx) )
+ return NULL;
+ return "misplaced option";
+}
+static FUNC_ERRORHANDLER(dc_error_handler) {
+ throw runtime_error(string("error parsing config file: ")+msg);
+}
+
+void configuration::parse(const string& cfile) {
+ struct dc_context dcc;
+ dcc.cf = this;
+ dcc.ctx = DCC_ROOT;
+ configfile_t *cf = dotconf_create((char*)cfile.c_str(),dc_options,(context_t*)&dcc,CASE_INSENSITIVE);
+ if(!cf)
+ throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to dotconf_create()");
+ cf->errorhandler = (dotconf_errorhandler_t) dc_error_handler;
+ cf->contextchecker = (dotconf_contextchecker_t) dc_context_checker;
+ if(!dotconf_command_loop(cf))
+ throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to dotconf_command_loop()");
+ dotconf_cleanup(cf);
+}
diff --git a/src/configuration.h b/src/configuration.h
new file mode 100644
index 0000000..314af92
--- a/dev/null
+++ b/src/configuration.h
@@ -0,0 +1,23 @@
+#ifndef __CONFIGURATION_H
+#define __CONFIGURATION_H
+
+#include <string>
+using namespace std;
+#include "process.h"
+
+class configuration {
+ public:
+ processes_t processes;
+
+ int check_interval;
+ string pidfile;
+ bool daemonize;
+ headers_t mailto_headers;
+ string notify;
+
+ configuration();
+
+ void parse(const string& cfile);
+};
+
+#endif /* __CONFIGURATION_H */
diff --git a/src/dudki.cc b/src/dudki.cc
new file mode 100644
index 0000000..3c50e56
--- a/dev/null
+++ b/src/dudki.cc
@@ -0,0 +1,244 @@
+#include <unistd.h>
+#include <signal.h>
+#include <syslog.h>
+#include <iostream>
+#include <fstream>
+#include <stdexcept>
+using namespace std;
+#include "configuration.h"
+#include "util.h"
+
+#include "config.h"
+#ifdef HAVE_GETOPT_H
+# include <getopt.h>
+#endif
+
+#ifndef DEFAULT_CONF_FILE
+# define DEFAULT_CONF_FILE "/etc/dudki.conf"
+#endif
+
+#define PHEADER PACKAGE " Version " VERSION
+#define PCOPY "Copyright (c) 2004 Klever Group"
+
+bool finishing = false;
+static char **_argv = NULL;
+
+static void lethal_signal_handler(int signum) {
+ syslog(LOG_NOTICE,"Lethal signal received. Terminating.");
+ finishing = true;
+}
+static void sighup_handler(int signum) {
+ syslog(LOG_NOTICE,"SUGHUP received, reloading.");
+ execvp(_argv[0],_argv);
+}
+
+void check_herd(configuration& config) {
+ for(processes_t::iterator i=config.processes.begin();i!=config.processes.end();++i)
+ i->second.check(i->first,config);
+}
+
+void signal_self(const configuration& config,int signum) {
+ ifstream pids(config.pidfile.c_str(),ios::in);
+ if(!pids)
+ throw runtime_error("Can't detect running instance");
+ pid_t pid = 0;
+ pids >> pid;
+ if(!pid)
+ throw runtime_error("Can't detect running instance");
+ if(pid==getpid())
+ throw 0;
+ if(kill(pid,signum))
+ throw runtime_error("Failed to signal running instance");
+}
+
+int main(int argc,char **argv) {
+ try {
+ _argv = new char*[argc+1];
+ if(!_argv)
+ throw runtime_error("memory allocation problem at the very start");
+ memmove(_argv,argv,sizeof(*_argv)*(argc+1));
+ string config_file = DEFAULT_CONF_FILE;
+ enum {
+ op_default,
+ op_work,
+ op_hup,
+ op_term,
+ op_check,
+ op_ensure,
+ op_test
+ } op = op_default;
+ while(true) {
+#define SHORTOPTSTRING "f:hVLrkcet"
+#ifdef HAVE_GETOPT_LONG
+ static struct option opts[] = {
+ { "help", no_argument, 0, 'h' },
+ { "usage", no_argument, 0, 'h' },
+ { "version", no_argument, 0, 'V' },
+ { "license", no_argument, 0, 'L' },
+ { "config", required_argument, 0, 'f' },
+ { "kill", no_argument, 0, 'k' },
+ { "reload", no_argument, 0, 'r' },
+ { "check", no_argument, 0, 'c' },
+ { "ensure", no_argument, 0, 'e' },
+ { "test", no_argument, 0, 't' },
+ { NULL, 0, 0, 0 }
+ };
+ int c = getopt_long(argc,argv,SHORTOPTSTRING,opts,NULL);
+#else /* !HAVE_GETOPT_LONG */
+ int c = getopt(argc,argv,SHORTOPTSTRING);
+#endif /* /HAVE_GETOPT_LONG */
+ if(c==-1)
+ break;
+ switch(c) {
+ case 'h':
+ cerr << PHEADER << endl
+ << PCOPY << endl << endl <<
+#ifdef HAVE_GETOPT_LONG
+ " -h, --help\n"
+ " --usage display this text\n"
+ " -V, --version display version number\n"
+ " -L, --license show license\n"
+ " -f filename, --config=filename\n"
+ " specify the configuration file to use\n"
+ "\n"
+ " -k, --kill stop running instance\n"
+ " -r, --reload reload running instance (send SIGHUP)\n"
+ " -c, --check check if dudki is running\n"
+ " -e, --ensure ensure that dudki is running\n"
+ " -t, --test test configuration file and exit"
+#else /* !HAVE_GETOPT_LONG */
+ " -h display this text\n"
+ " -V display version number\n"
+ " -L show license\n"
+ " -f filename specify the configuration file to use\n"
+ "\n"
+ " -k stop running instance\n"
+ " -r reload running instance (send SIGHUP)\n"
+ " -c check if dudki is running\n"
+ " -e ensure that dudki is running\n"
+ " -t test configuration file and exit"
+#endif /* /HAVE_GETOPT_LONG */
+ << endl;
+ exit(0);
+ break;
+ case 'V':
+ cerr << VERSION << endl;
+ exit(0);
+ break;
+ case 'L':
+ extern const char *COPYING;
+ cerr << COPYING << endl;
+ exit(0);
+ break;
+ case 'f':
+ config_file = optarg;
+ break;
+ case 'k':
+ if(op!=op_default) {
+ cerr << "Can't obey two or more orders at once" << endl;
+ exit(1);
+ }
+ op = op_term;
+ break;
+ case 'r':
+ if(op!=op_default) {
+ cerr << "Can't obey two or more orders at once" << endl;
+ exit(1);
+ }
+ op = op_hup;
+ break;
+ case 'c':
+ if(op!=op_default) {
+ cerr << "Can't obey two or more orders at once" << endl;
+ exit(1);
+ }
+ op = op_check;
+ break;
+ case 'e':
+ if(op!=op_default) {
+ cerr << "Can't obey two or more orders at once" << endl;
+ exit(1);
+ }
+ op = op_ensure;
+ break;
+ case 't':
+ if(op!=op_default) {
+ cerr << "Can't obey two or more orders at once" << endl;
+ exit(1);
+ }
+ op = op_test;
+ break;
+ default:
+ cerr << "Huh??" << endl;
+ exit(1);
+ break;
+ }
+ }
+ const char *sid = *argv;
+ const char *t;
+ while(t = index(sid,'/')) {
+ sid = t; sid++;
+ }
+ openlog(sid,LOG_CONS|LOG_PERROR|LOG_PID,LOG_DAEMON);
+ configuration config;
+ config.parse(config_file);
+ switch(op) {
+ case op_test:
+ cerr << "Configuration OK" << endl;
+ break;
+ case op_hup:
+ signal_self(config,SIGHUP);
+ break;
+ case op_term:
+ signal_self(config,SIGTERM);
+ break;
+ case op_check:
+ try{
+ signal_self(config,0);
+ exit(0);
+ }catch(exception& e) {
+ exit(1);
+ }
+ case op_ensure:
+ try {
+ signal_self(config,0);
+ break;
+ }catch(exception& e) {
+ syslog(LOG_NOTICE,"The dudki process is down, taking its place");
+ config.daemonize = true;
+ }catch(int zero) {
+ // we throw zero in case we're ensuring that this very process is running.
+ // we don't have to daemonize if we're daemonic.
+ config.daemonize = false;
+ }
+ case op_default:
+ case op_work:
+ {
+ if(config.daemonize) {
+ pid_t pf = fork();
+ if(pf<0)
+ throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to fork()");
+ if(pf) {
+ _exit(0);
+ }
+ }
+ pid_file pidfile;
+ pidfile.set(config.pidfile);
+ signal(SIGINT,lethal_signal_handler);
+ signal(SIGABRT,lethal_signal_handler);
+ signal(SIGTERM,lethal_signal_handler);
+ signal(SIGHUP,sighup_handler);
+ while(!finishing) {
+ check_herd(config);
+ sleep(config.check_interval);
+ }
+ }
+ break;
+ default:
+ throw runtime_error(string(__PRETTY_FUNCTION__)+": internal error");
+ }
+ }catch(exception& e) {
+ cerr << "Oops: " << e.what() << endl;
+ return 1;
+ }
+}
diff --git a/src/process.cc b/src/process.cc
new file mode 100644
index 0000000..fda35e8
--- a/dev/null
+++ b/src/process.cc
@@ -0,0 +1,184 @@
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/wait.h>
+#include <syslog.h>
+#include <errno.h>
+#include <iostream>
+#include <fstream>
+#include <stdexcept>
+using namespace std;
+#include "process.h"
+#include "configuration.h"
+
+void process::check(const string& id,configuration& config) {
+ bool running = false;
+ ifstream pids(pidfile.c_str(),ios::in);
+ if(pids) {
+ pid_t pid = 0;
+ pids >> pid;
+ pids.close();
+ if(pid) {
+ if(!kill(pid,0)) {
+ running = true;
+ }
+ }
+ }
+ if(running){
+ patience = 0;
+ }else{
+ if(patience>60) { // TODO: configurable
+ patience = 0;
+ }else{
+ if(patience<10) { // TODO: configurable
+ syslog(LOG_NOTICE,"The process '%s' is down, trying to launch.",id.c_str());
+ do_notify(id,"Starting up",
+ "The named process seems to be down. Dudki will try\n"
+ "to revive it by running the specified command.\n",
+ config);
+ try {
+ launch(id,config);
+ }catch(exception& e) {
+ syslog(LOG_ERR,"Error trying to launch process '%s': %s",id.c_str(),e.what());
+ }
+ }else if(patience==10){ // TODO: configurable like the above
+ syslog(LOG_NOTICE,"Giving up on process '%s' for a while",id.c_str());
+ do_notify(id,"Giving up",
+ "After a number of attempts to relaunch the named process\n"
+ "It still seems to be down. Dudki is giving up attempts\n"
+ "to revive the process for a while.\n",
+ config);
+ }
+ patience++;
+ }
+ }
+}
+
+void process::launch(const string& id,configuration& config) {
+ uid_t uid = 0;
+ if(!user.empty()) {
+ struct passwd *ptmp = getpwnam(user.c_str());
+ if(ptmp) {
+ uid = ptmp->pw_uid;
+ }else{
+ errno=0;
+ uid = strtol(user.c_str(),NULL,0);
+ if(errno)
+ throw runtime_error("Failed to resolve User value to uid");
+ }
+ }
+ gid_t gid = 0;
+ if(!group.empty()) {
+ struct group *gtmp = getgrnam(group.c_str());
+ if(gtmp) {
+ gid = gtmp->gr_gid;
+ }else{
+ errno = 0;
+ gid = strtol(group.c_str(),NULL,0);
+ if(errno)
+ throw runtime_error("Failed to reslove Group value to gid");
+ }
+ }
+ pid_t p = fork();
+ if(p<0)
+ throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to fork()");
+ if(!p) {
+ // child
+ try {
+ setsid();
+ if(!chroot.empty()) {
+ if(::chroot(chroot.c_str()))
+ throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to chroot()");
+ }
+ if(!group.empty()) {
+ // TODO: initgroups()?
+ if((getgid()!=gid) && setgid(gid))
+ throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to setgid()");
+ }
+ if(!user.empty()) {
+ if((getuid()!=uid) && setuid(uid))
+ throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to setuid()");
+ }
+ char *argv[] = { "/bin/sh", "-c", (char*)restart_cmd.c_str(), NULL };
+ close(0); close(1); close(2);
+ execv("/bin/sh",argv);
+ }catch(exception& e) {
+ syslog(LOG_ERR,"Error trying to launch process '%s': %s",id.c_str(),e.what());
+ }
+ _exit(-1);
+ }
+ // parent
+ int rv;
+ if(waitpid(p,&rv,0)<0)
+ throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to waitpid()");
+}
+
+void process::do_notify(const string& id,const string& event,const string& description,configuration& config) {
+ string the_notify;
+ if(!notify.empty())
+ the_notify=notify;
+ else if(!config.notify.empty())
+ the_notify=config.notify;
+ else
+ return;
+ try {
+ string::size_type colon = the_notify.find(':');
+ if(colon==string::npos)
+ throw runtime_error("invalid notify action specification");
+ string nschema = the_notify.substr(0,colon);
+ string ntarget = the_notify.substr(colon+1);
+ if(nschema=="mailto") {
+ notify_mailto(ntarget,id,event,description,config);
+ }else
+ throw runtime_error("unrecognized notification schema");
+ }catch(exception& e) {
+ syslog(LOG_ERR,"Notification error: %s",e.what());
+ }
+}
+
+void process::notify_mailto(const string& email,const string& id,const string& event,const string& description,configuration& config) {
+ int files[2];
+ if(pipe(files))
+ throw runtime_error("Failed to pipe()");
+ pid_t pid = vfork();
+ if(pid==-1) {
+ close(files[0]);
+ close(files[1]);
+ throw runtime_error("Failed to vfork()");
+ }
+ if(!pid) {
+ // child
+ if(dup2(files[0],0)!=0)
+ _exit(-1);
+ close(1);
+ close(files[0]);
+ close(files[1]);
+ execl("/usr/sbin/sendmail","usr/sbin/sendmail","-i",email.c_str(),NULL);
+ _exit(-1);
+ }
+ // parent
+ close(files[0]);
+ FILE *mta = fdopen(files[1],"w");
+ for(headers_t::const_iterator i=mailto_headers.begin();i!=mailto_headers.end();++i) {
+ fprintf(mta,"%s: %s\n",i->first.c_str(),i->second.c_str());
+ }
+ for(headers_t::const_iterator i=config.mailto_headers.begin();i!=config.mailto_headers.end();++i) {
+ if(mailto_headers.find(i->first)!=mailto_headers.end())
+ continue;
+ fprintf(mta,"%s: %s\n",i->first.c_str(),i->second.c_str());
+ }
+ fprintf(mta,
+ "Subject: [%s] %s\n\n"
+ "%s\n"
+ "---\n"
+ "This message was sent automatically by the 'dudki' daemon\n",
+ id.c_str(), event.c_str(),
+ description.c_str() );
+ fclose(mta);
+ int status;
+ waitpid(pid,&status,0);
+ // TODO: check the return code
+}
diff --git a/src/process.h b/src/process.h
new file mode 100644
index 0000000..b6d7091
--- a/dev/null
+++ b/src/process.h
@@ -0,0 +1,36 @@
+#ifndef __PROCESS_H
+#define __PROCESS_H
+
+#include <string>
+#include <map>
+using namespace std;
+
+class configuration;
+
+typedef map<string,string> headers_t;
+
+class process {
+ public:
+ string pidfile;
+ string restart_cmd;
+ string notify;
+ string user;
+ string group;
+ string chroot;
+ headers_t mailto_headers;
+
+ int patience;
+
+ process()
+ : patience(0) { }
+
+ void check(const string& id,configuration& config);
+ void launch(const string& id,configuration& config);
+ void do_notify(const string& id,const string& event,const string& description,configuration& config);
+ void notify_mailto(const string& email,const string& id,const string& event,
+ const string& description,configuration& config);
+};
+
+typedef map<string,process> processes_t;
+
+#endif /* __PROCESS_H */
diff --git a/src/util.cc b/src/util.cc
new file mode 100644
index 0000000..afb2641
--- a/dev/null
+++ b/src/util.cc
@@ -0,0 +1,21 @@
+#include <unistd.h>
+#include <fstream>
+#include <stdexcept>
+using namespace std;
+#include "util.h"
+
+void pid_file::set(const string& f,bool u) {
+ ofstream of(f.c_str(),ios::trunc);
+ if(!of)
+ throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to open file for writing pid");
+ of << getpid() << endl;
+ of.close();
+ file_name = f;
+ unlink_pid = u;
+}
+void pid_file::unlink() {
+ if(!unlink_pid)
+ return;
+ ::unlink(file_name.c_str());
+}
+
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000..314d8e2
--- a/dev/null
+++ b/src/util.h
@@ -0,0 +1,20 @@
+#ifndef __UTIL_H
+#define __UTIL_H
+
+#include <string>
+using namespace std;
+
+class pid_file {
+ public:
+ string file_name;
+ bool unlink_pid;
+
+ pid_file()
+ : unlink_pid(false) { }
+ ~pid_file() { unlink(); }
+
+ void set(const string& f,bool u=true);
+ void unlink();
+};
+
+#endif /* __UTIL_H */