summaryrefslogtreecommitdiffabout
path: root/src
Unidiff
Diffstat (limited to 'src') (more/less context) (ignore 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 @@
1.deps
2Makefile
3Makefile.in
4COPYING.cc
5dudki
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 @@
1sbin_PROGRAMS = dudki
2
3INCLUDES = ${DOTCONF_CFLAGS}
4LIBS += ${DOTCONF_LIBS}
5AM_CPPFLAGS = \
6 -DDEFAULT_CONF_FILE=\"${sysconfdir}/${PACKAGE}.conf\" \
7 -DDEFAULT_PID_FILE=\"/var/run/${PACKAGE}.pid\"
8
9dudki_SOURCES = dudki.cc \
10 process.cc process.h \
11 configuration.cc configuration.h \
12 util.cc util.h \
13 COPYING.cc
14
15COPYING.cc: ${top_srcdir}/COPYING
16 echo "const char * COPYING =" >$@ || (rm $@;exit 1)
17 sed 's/"/\\"/g' $< | sed 's/^/\"/' | sed 's/$$/\\n\"/' >>$@ || (rm $@;exit 1)
18 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 @@
1#include <stdexcept>
2using namespace std;
3#include <dotconf.h>
4#include "configuration.h"
5
6#ifndef DEFAULT_PID_FILE
7# define DEFAULT_PID_FILE "/var/run/dudki.pid"
8#endif
9
10configuration::configuration()
11 : check_interval(60), pidfile(DEFAULT_PID_FILE),
12 daemonize(true) {
13 }
14
15enum dc_ctx {
16 DCC_ROOT = 1,
17 DCC_PROCESS = 2
18};
19struct dc_context {
20 dc_ctx ctx;
21 configuration* cf;
22 process* ps;
23
24 dc_context()
25 : ctx(DCC_ROOT), cf(NULL), ps(NULL) { }
26};
27
28static DOTCONF_CB(dco_check_interval) { dc_context *dcc = (dc_context*)ctx;
29 dcc->cf->check_interval = cmd->data.value;
30 return NULL;
31}
32static DOTCONF_CB(dco_daemonize) { dc_context *dcc = (dc_context*)ctx;
33 dcc->cf->daemonize = cmd->data.value;
34 return NULL;
35}
36
37static DOTCONF_CB(dco_pid_file) { dc_context *dcc = (dc_context*)ctx;
38 switch(dcc->ctx) {
39 case DCC_ROOT:
40 dcc->cf->pidfile = cmd->data.str;
41 break;
42 case DCC_PROCESS:
43 dcc->ps->pidfile = cmd->data.str;
44 break;
45 default:
46 return "Unexpected PidFile";
47 }
48 return NULL;
49}
50static DOTCONF_CB(dco_mailto_header) { dc_context *dcc = (dc_context*)ctx;
51 if(cmd->arg_count!=2)
52 return "Invalid number of arguments";
53 string h = cmd->data.list[0];
54 string v = cmd->data.list[1];
55 switch(dcc->ctx) {
56 case DCC_ROOT:
57 dcc->cf->mailto_headers[h] = v;
58 break;
59 case DCC_PROCESS:
60 dcc->ps->mailto_headers[h] = v;
61 break;
62 default:
63 return "Unexpected MailtoHeader";
64 }
65 return NULL;
66}
67static DOTCONF_CB(dco_notify) { dc_context *dcc = (dc_context*)ctx;
68 switch(dcc->ctx) {
69 case DCC_ROOT:
70 dcc->cf->notify = cmd->data.str;
71 break;
72 case DCC_PROCESS:
73 dcc->ps->notify = cmd->data.str;
74 break;
75 default:
76 return "Unexpected Notify";
77 }
78 return NULL;
79}
80
81static DOTCONF_CB(dco_process) { dc_context *dcc = (dc_context*)ctx;
82 string id = cmd->data.str;
83 if(id[id.length()-1]=='>')
84 id.erase(id.length()-1);
85 dcc->ps = &(dcc->cf->processes[id]);
86 dcc->ctx = DCC_PROCESS;
87 return NULL;
88}
89static DOTCONF_CB(dco__process) { dc_context *dcc = (dc_context*)ctx;
90 dcc->ps = NULL;
91 dcc->ctx = DCC_ROOT;
92 return NULL;
93}
94
95static DOTCONF_CB(dco_restart_command) { dc_context *dcc = (dc_context*)ctx;
96 dcc->ps->restart_cmd = cmd->data.str;
97 return NULL;
98}
99static DOTCONF_CB(dco_user) { dc_context *dcc = (dc_context*)ctx;
100 dcc->ps->user = cmd->data.str;
101 return NULL;
102}
103static DOTCONF_CB(dco_group) { dc_context *dcc = (dc_context*)ctx;
104 dcc->ps->group = cmd->data.str;
105 return NULL;
106}
107static DOTCONF_CB(dco_chroot) { dc_context *dcc = (dc_context*)ctx;
108 dcc->ps->chroot = cmd->data.str;
109 return NULL;
110}
111
112static const configoption_t dc_options[] = {
113 { "CheckInterval", ARG_INT, dco_check_interval, NULL, DCC_ROOT },
114 { "Daemonize", ARG_TOGGLE, dco_daemonize, NULL, DCC_ROOT },
115 { "PidFile", ARG_STR, dco_pid_file, NULL, DCC_ROOT|DCC_PROCESS },
116 { "MailtoHeader", ARG_STR, dco_mailto_header, NULL, DCC_ROOT|DCC_PROCESS },
117 { "Notify", ARG_STR, dco_notify, NULL, DCC_ROOT|DCC_PROCESS },
118 { "<Process", ARG_STR, dco_process, NULL, DCC_ROOT },
119 { "RestartCommand", ARG_STR, dco_restart_command, NULL, DCC_PROCESS },
120 { "User", ARG_STR, dco_user, NULL, DCC_PROCESS },
121 { "Group", ARG_STR, dco_group, NULL, DCC_PROCESS },
122 { "Chroot", ARG_STR, dco_chroot, NULL, DCC_PROCESS },
123 { "</Process>", ARG_NONE, dco__process, NULL, DCC_PROCESS },
124 LAST_OPTION
125};
126
127static const char *dc_context_checker(command_t *cmd,unsigned long mask) {
128 dc_context *dcc = (dc_context*)cmd->context;
129 if( (mask==CTX_ALL) || ((mask&dcc->ctx)==dcc->ctx) )
130 return NULL;
131 return "misplaced option";
132}
133static FUNC_ERRORHANDLER(dc_error_handler) {
134 throw runtime_error(string("error parsing config file: ")+msg);
135}
136
137void configuration::parse(const string& cfile) {
138 struct dc_context dcc;
139 dcc.cf = this;
140 dcc.ctx = DCC_ROOT;
141 configfile_t *cf = dotconf_create((char*)cfile.c_str(),dc_options,(context_t*)&dcc,CASE_INSENSITIVE);
142 if(!cf)
143 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to dotconf_create()");
144 cf->errorhandler = (dotconf_errorhandler_t) dc_error_handler;
145 cf->contextchecker = (dotconf_contextchecker_t) dc_context_checker;
146 if(!dotconf_command_loop(cf))
147 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to dotconf_command_loop()");
148 dotconf_cleanup(cf);
149}
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 @@
1#ifndef __CONFIGURATION_H
2#define __CONFIGURATION_H
3
4#include <string>
5using namespace std;
6#include "process.h"
7
8class configuration {
9 public:
10 processes_t processes;
11
12 int check_interval;
13 string pidfile;
14 bool daemonize;
15 headers_t mailto_headers;
16 string notify;
17
18 configuration();
19
20 void parse(const string& cfile);
21};
22
23#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 @@
1#include <unistd.h>
2#include <signal.h>
3#include <syslog.h>
4#include <iostream>
5#include <fstream>
6#include <stdexcept>
7using namespace std;
8#include "configuration.h"
9#include "util.h"
10
11#include "config.h"
12#ifdef HAVE_GETOPT_H
13# include <getopt.h>
14#endif
15
16#ifndef DEFAULT_CONF_FILE
17# define DEFAULT_CONF_FILE "/etc/dudki.conf"
18#endif
19
20#define PHEADER PACKAGE " Version " VERSION
21#define PCOPY "Copyright (c) 2004 Klever Group"
22
23bool finishing = false;
24static char **_argv = NULL;
25
26static void lethal_signal_handler(int signum) {
27 syslog(LOG_NOTICE,"Lethal signal received. Terminating.");
28 finishing = true;
29}
30static void sighup_handler(int signum) {
31 syslog(LOG_NOTICE,"SUGHUP received, reloading.");
32 execvp(_argv[0],_argv);
33}
34
35void check_herd(configuration& config) {
36 for(processes_t::iterator i=config.processes.begin();i!=config.processes.end();++i)
37 i->second.check(i->first,config);
38}
39
40void signal_self(const configuration& config,int signum) {
41 ifstream pids(config.pidfile.c_str(),ios::in);
42 if(!pids)
43 throw runtime_error("Can't detect running instance");
44 pid_t pid = 0;
45 pids >> pid;
46 if(!pid)
47 throw runtime_error("Can't detect running instance");
48 if(pid==getpid())
49 throw 0;
50 if(kill(pid,signum))
51 throw runtime_error("Failed to signal running instance");
52}
53
54int main(int argc,char **argv) {
55 try {
56 _argv = new char*[argc+1];
57 if(!_argv)
58 throw runtime_error("memory allocation problem at the very start");
59 memmove(_argv,argv,sizeof(*_argv)*(argc+1));
60 string config_file = DEFAULT_CONF_FILE;
61 enum {
62 op_default,
63 op_work,
64 op_hup,
65 op_term,
66 op_check,
67 op_ensure,
68 op_test
69 } op = op_default;
70 while(true) {
71 #defineSHORTOPTSTRING "f:hVLrkcet"
72#ifdef HAVE_GETOPT_LONG
73 static struct option opts[] = {
74 { "help", no_argument, 0, 'h' },
75 { "usage", no_argument, 0, 'h' },
76 { "version", no_argument, 0, 'V' },
77 { "license", no_argument, 0, 'L' },
78 { "config", required_argument, 0, 'f' },
79 { "kill", no_argument, 0, 'k' },
80 { "reload", no_argument, 0, 'r' },
81 { "check", no_argument, 0, 'c' },
82 { "ensure", no_argument, 0, 'e' },
83 { "test", no_argument, 0, 't' },
84 { NULL, 0, 0, 0 }
85 };
86 int c = getopt_long(argc,argv,SHORTOPTSTRING,opts,NULL);
87#else /* !HAVE_GETOPT_LONG */
88 int c = getopt(argc,argv,SHORTOPTSTRING);
89#endif /* /HAVE_GETOPT_LONG */
90 if(c==-1)
91 break;
92 switch(c) {
93 case 'h':
94 cerr << PHEADER << endl
95 << PCOPY << endl << endl <<
96#ifdef HAVE_GETOPT_LONG
97 " -h, --help\n"
98 " --usage display this text\n"
99 " -V, --version display version number\n"
100 " -L, --license show license\n"
101 " -f filename, --config=filename\n"
102 " specify the configuration file to use\n"
103 "\n"
104 " -k, --kill stop running instance\n"
105 " -r, --reload reload running instance (send SIGHUP)\n"
106 " -c, --check check if dudki is running\n"
107 " -e, --ensure ensure that dudki is running\n"
108 " -t, --test test configuration file and exit"
109#else /* !HAVE_GETOPT_LONG */
110 " -h display this text\n"
111 " -V display version number\n"
112 " -L show license\n"
113 " -f filename specify the configuration file to use\n"
114 "\n"
115 " -k stop running instance\n"
116 " -r reload running instance (send SIGHUP)\n"
117 " -c check if dudki is running\n"
118 " -e ensure that dudki is running\n"
119 " -t test configuration file and exit"
120#endif /* /HAVE_GETOPT_LONG */
121 << endl;
122 exit(0);
123 break;
124 case 'V':
125 cerr << VERSION << endl;
126 exit(0);
127 break;
128 case 'L':
129 extern const char *COPYING;
130 cerr << COPYING << endl;
131 exit(0);
132 break;
133 case 'f':
134 config_file = optarg;
135 break;
136 case 'k':
137 if(op!=op_default) {
138 cerr << "Can't obey two or more orders at once" << endl;
139 exit(1);
140 }
141 op = op_term;
142 break;
143 case 'r':
144 if(op!=op_default) {
145 cerr << "Can't obey two or more orders at once" << endl;
146 exit(1);
147 }
148 op = op_hup;
149 break;
150 case 'c':
151 if(op!=op_default) {
152 cerr << "Can't obey two or more orders at once" << endl;
153 exit(1);
154 }
155 op = op_check;
156 break;
157 case 'e':
158 if(op!=op_default) {
159 cerr << "Can't obey two or more orders at once" << endl;
160 exit(1);
161 }
162 op = op_ensure;
163 break;
164 case 't':
165 if(op!=op_default) {
166 cerr << "Can't obey two or more orders at once" << endl;
167 exit(1);
168 }
169 op = op_test;
170 break;
171 default:
172 cerr << "Huh??" << endl;
173 exit(1);
174 break;
175 }
176 }
177 const char *sid = *argv;
178 const char *t;
179 while(t = index(sid,'/')) {
180 sid = t; sid++;
181 }
182 openlog(sid,LOG_CONS|LOG_PERROR|LOG_PID,LOG_DAEMON);
183 configuration config;
184 config.parse(config_file);
185 switch(op) {
186 case op_test:
187 cerr << "Configuration OK" << endl;
188 break;
189 case op_hup:
190 signal_self(config,SIGHUP);
191 break;
192 case op_term:
193 signal_self(config,SIGTERM);
194 break;
195 case op_check:
196 try{
197 signal_self(config,0);
198 exit(0);
199 }catch(exception& e) {
200 exit(1);
201 }
202 case op_ensure:
203 try {
204 signal_self(config,0);
205 break;
206 }catch(exception& e) {
207 syslog(LOG_NOTICE,"The dudki process is down, taking its place");
208 config.daemonize = true;
209 }catch(int zero) {
210 // we throw zero in case we're ensuring that this very process is running.
211 // we don't have to daemonize if we're daemonic.
212 config.daemonize = false;
213 }
214 case op_default:
215 case op_work:
216 {
217 if(config.daemonize) {
218 pid_t pf = fork();
219 if(pf<0)
220 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to fork()");
221 if(pf) {
222 _exit(0);
223 }
224 }
225 pid_file pidfile;
226 pidfile.set(config.pidfile);
227 signal(SIGINT,lethal_signal_handler);
228 signal(SIGABRT,lethal_signal_handler);
229 signal(SIGTERM,lethal_signal_handler);
230 signal(SIGHUP,sighup_handler);
231 while(!finishing) {
232 check_herd(config);
233 sleep(config.check_interval);
234 }
235 }
236 break;
237 default:
238 throw runtime_error(string(__PRETTY_FUNCTION__)+": internal error");
239 }
240 }catch(exception& e) {
241 cerr << "Oops: " << e.what() << endl;
242 return 1;
243 }
244}
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 @@
1#include <stdio.h>
2#include <sys/types.h>
3#include <unistd.h>
4#include <signal.h>
5#include <pwd.h>
6#include <grp.h>
7#include <sys/wait.h>
8#include <syslog.h>
9#include <errno.h>
10#include <iostream>
11#include <fstream>
12#include <stdexcept>
13using namespace std;
14#include "process.h"
15#include "configuration.h"
16
17void process::check(const string& id,configuration& config) {
18 bool running = false;
19 ifstream pids(pidfile.c_str(),ios::in);
20 if(pids) {
21 pid_t pid = 0;
22 pids >> pid;
23 pids.close();
24 if(pid) {
25 if(!kill(pid,0)) {
26 running = true;
27 }
28 }
29 }
30 if(running){
31 patience = 0;
32 }else{
33 if(patience>60) { // TODO: configurable
34 patience = 0;
35 }else{
36 if(patience<10) { // TODO: configurable
37 syslog(LOG_NOTICE,"The process '%s' is down, trying to launch.",id.c_str());
38 do_notify(id,"Starting up",
39 "The named process seems to be down. Dudki will try\n"
40 "to revive it by running the specified command.\n",
41 config);
42 try {
43 launch(id,config);
44 }catch(exception& e) {
45 syslog(LOG_ERR,"Error trying to launch process '%s': %s",id.c_str(),e.what());
46 }
47 }else if(patience==10){ // TODO: configurable like the above
48 syslog(LOG_NOTICE,"Giving up on process '%s' for a while",id.c_str());
49 do_notify(id,"Giving up",
50 "After a number of attempts to relaunch the named process\n"
51 "It still seems to be down. Dudki is giving up attempts\n"
52 "to revive the process for a while.\n",
53 config);
54 }
55 patience++;
56 }
57 }
58}
59
60void process::launch(const string& id,configuration& config) {
61 uid_t uid = 0;
62 if(!user.empty()) {
63 struct passwd *ptmp = getpwnam(user.c_str());
64 if(ptmp) {
65 uid = ptmp->pw_uid;
66 }else{
67 errno=0;
68 uid = strtol(user.c_str(),NULL,0);
69 if(errno)
70 throw runtime_error("Failed to resolve User value to uid");
71 }
72 }
73 gid_t gid = 0;
74 if(!group.empty()) {
75 struct group *gtmp = getgrnam(group.c_str());
76 if(gtmp) {
77 gid = gtmp->gr_gid;
78 }else{
79 errno = 0;
80 gid = strtol(group.c_str(),NULL,0);
81 if(errno)
82 throw runtime_error("Failed to reslove Group value to gid");
83 }
84 }
85 pid_t p = fork();
86 if(p<0)
87 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to fork()");
88 if(!p) {
89 // child
90 try {
91 setsid();
92 if(!chroot.empty()) {
93 if(::chroot(chroot.c_str()))
94 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to chroot()");
95 }
96 if(!group.empty()) {
97 // TODO: initgroups()?
98 if((getgid()!=gid) && setgid(gid))
99 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to setgid()");
100 }
101 if(!user.empty()) {
102 if((getuid()!=uid) && setuid(uid))
103 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to setuid()");
104 }
105 char *argv[] = { "/bin/sh", "-c", (char*)restart_cmd.c_str(), NULL };
106 close(0); close(1); close(2);
107 execv("/bin/sh",argv);
108 }catch(exception& e) {
109 syslog(LOG_ERR,"Error trying to launch process '%s': %s",id.c_str(),e.what());
110 }
111 _exit(-1);
112 }
113 // parent
114 int rv;
115 if(waitpid(p,&rv,0)<0)
116 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to waitpid()");
117}
118
119void process::do_notify(const string& id,const string& event,const string& description,configuration& config) {
120 string the_notify;
121 if(!notify.empty())
122 the_notify=notify;
123 else if(!config.notify.empty())
124 the_notify=config.notify;
125 else
126 return;
127 try {
128 string::size_type colon = the_notify.find(':');
129 if(colon==string::npos)
130 throw runtime_error("invalid notify action specification");
131 string nschema = the_notify.substr(0,colon);
132 string ntarget = the_notify.substr(colon+1);
133 if(nschema=="mailto") {
134 notify_mailto(ntarget,id,event,description,config);
135 }else
136 throw runtime_error("unrecognized notification schema");
137 }catch(exception& e) {
138 syslog(LOG_ERR,"Notification error: %s",e.what());
139 }
140}
141
142void process::notify_mailto(const string& email,const string& id,const string& event,const string& description,configuration& config) {
143 int files[2];
144 if(pipe(files))
145 throw runtime_error("Failed to pipe()");
146 pid_t pid = vfork();
147 if(pid==-1) {
148 close(files[0]);
149 close(files[1]);
150 throw runtime_error("Failed to vfork()");
151 }
152 if(!pid) {
153 // child
154 if(dup2(files[0],0)!=0)
155 _exit(-1);
156 close(1);
157 close(files[0]);
158 close(files[1]);
159 execl("/usr/sbin/sendmail","usr/sbin/sendmail","-i",email.c_str(),NULL);
160 _exit(-1);
161 }
162 // parent
163 close(files[0]);
164 FILE *mta = fdopen(files[1],"w");
165 for(headers_t::const_iterator i=mailto_headers.begin();i!=mailto_headers.end();++i) {
166 fprintf(mta,"%s: %s\n",i->first.c_str(),i->second.c_str());
167 }
168 for(headers_t::const_iterator i=config.mailto_headers.begin();i!=config.mailto_headers.end();++i) {
169 if(mailto_headers.find(i->first)!=mailto_headers.end())
170 continue;
171 fprintf(mta,"%s: %s\n",i->first.c_str(),i->second.c_str());
172 }
173 fprintf(mta,
174 "Subject: [%s] %s\n\n"
175 "%s\n"
176 "---\n"
177 "This message was sent automatically by the 'dudki' daemon\n",
178 id.c_str(), event.c_str(),
179 description.c_str() );
180 fclose(mta);
181 int status;
182 waitpid(pid,&status,0);
183 // TODO: check the return code
184}
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 @@
1#ifndef __PROCESS_H
2#define __PROCESS_H
3
4#include <string>
5#include <map>
6using namespace std;
7
8class configuration;
9
10typedef map<string,string> headers_t;
11
12class process {
13 public:
14 string pidfile;
15 string restart_cmd;
16 string notify;
17 string user;
18 string group;
19 string chroot;
20 headers_t mailto_headers;
21
22 int patience;
23
24 process()
25 : patience(0) { }
26
27 void check(const string& id,configuration& config);
28 void launch(const string& id,configuration& config);
29 void do_notify(const string& id,const string& event,const string& description,configuration& config);
30 void notify_mailto(const string& email,const string& id,const string& event,
31 const string& description,configuration& config);
32};
33
34typedef map<string,process> processes_t;
35
36#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 @@
1#include <unistd.h>
2#include <fstream>
3#include <stdexcept>
4using namespace std;
5#include "util.h"
6
7void pid_file::set(const string& f,bool u) {
8 ofstream of(f.c_str(),ios::trunc);
9 if(!of)
10 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to open file for writing pid");
11 of << getpid() << endl;
12 of.close();
13 file_name = f;
14 unlink_pid = u;
15}
16void pid_file::unlink() {
17 if(!unlink_pid)
18 return;
19 ::unlink(file_name.c_str());
20}
21
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 @@
1#ifndef __UTIL_H
2#define __UTIL_H
3
4#include <string>
5using namespace std;
6
7class pid_file {
8 public:
9 string file_name;
10 bool unlink_pid;
11
12 pid_file()
13 : unlink_pid(false) { }
14 ~pid_file() { unlink(); }
15
16 void set(const string& f,bool u=true);
17 void unlink();
18};
19
20#endif /* __UTIL_H */