Skip to content

Commit 2b18f3d

Browse files
committed
Initial ezd
1 parent 43406e8 commit 2b18f3d

File tree

3 files changed

+500
-3
lines changed

3 files changed

+500
-3
lines changed

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
ezd
2-
===
1+
ezd - easy daemon
2+
=================
33

4-
Making daemonizations easy - single .c+.h files providing the basic tools for daemons.
4+
Making daemonizations easy - single `.c+.h` files providing the
5+
basic tools for daemons.
6+
7+
For features and usage documentation, see comments in `ezd.h`

ezd.c

Lines changed: 383 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
1+
/**
2+
* ezd - eazy daemon - makes your life as a daemon conceiver easy.
3+
*
4+
* Provides the most basic tools for daemonizing a program, such as:
5+
* - configuration file parsing and reading (name=value.. pairs)
6+
* - pidfile handling with locking
7+
* - daemon()ization with wait-for-child-to-fully-start support to
8+
* allow full initialization in the child process.
9+
*
10+
* Simply add ezd.c and ezd.h to your project and use as you like.
11+
*/
12+
13+
/*
14+
* Copyright (c) 2013 Magnus Edenhill <[email protected]>
15+
*
16+
* All rights reserved.
17+
*
18+
* Redistribution and use in source and binary forms, with or without
19+
* modification, are permitted provided that the following conditions are met:
20+
*
21+
* 1. Redistributions of source code must retain the above copyright notice,
22+
* this list of conditions and the following disclaimer.
23+
* 2. Redistributions in binary form must reproduce the above copyright notice,
24+
* this list of conditions and the following disclaimer in the documentation
25+
* and/or other materials provided with the distribution.
26+
*
27+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
28+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
31+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37+
* POSSIBILITY OF SUCH DAMAGE.
38+
*/
39+
40+
41+
#define _ISOC99_SOURCE /* for strtoull() */
42+
#define _GNU_SOURCE /* for strdupa() */
43+
44+
#include <string.h>
45+
#include <strings.h>
46+
#include <errno.h>
47+
#include <fcntl.h>
48+
#include <stdio.h>
49+
#include <ctype.h>
50+
#include <stdlib.h>
51+
#include <time.h>
52+
#include <limits.h>
53+
#include <sys/file.h>
54+
#include <unistd.h>
55+
#include <sys/types.h>
56+
#include <sys/wait.h>
57+
#include <signal.h>
58+
#include <assert.h>
59+
60+
static char ezd_pidfile_path[PATH_MAX];
61+
static int ezd_pidfile_fd = -1;
62+
63+
64+
int ezd_str_tof (const char *val) {
65+
char *end;
66+
int i;
67+
68+
i = strtoul(val, &end, 0);
69+
if (end > val) /* treat as integer value */
70+
return !!i;
71+
72+
if (!strcasecmp(val, "yes") ||
73+
!strcasecmp(val, "true") ||
74+
!strcasecmp(val, "on"))
75+
return 1;
76+
else
77+
return 0;
78+
}
79+
80+
81+
82+
/*
83+
* Left and right trim string '*sp' of white spaces (incl newlines).
84+
*/
85+
static int ezd_trim (char **sp, char *end) {
86+
char *s = *sp;
87+
88+
while (s < end && isspace(*s))
89+
s++;
90+
91+
end--;
92+
93+
while (end > s && isspace(*end)) {
94+
*end = '\0';
95+
end--;
96+
}
97+
98+
*sp = s;
99+
100+
return (int)(end - *sp);
101+
}
102+
103+
104+
int ezd_csv2array (char ***arrp, const char *valstr) {
105+
char *val = strdupa(valstr);
106+
int size = 0;
107+
int cnt = 0;
108+
109+
*arrp = NULL;
110+
111+
while (*val) {
112+
int len;
113+
char *t = NULL;
114+
char *s = val;
115+
116+
do {
117+
if (!(t = strchr(s, ','))) {
118+
/* End of line */
119+
t = val + strlen(val);
120+
} else if (t == val)
121+
break;
122+
else if (*(t-1) == '\\') {
123+
/* Escaped: remove the escaper and keep going */
124+
memmove(t-1, t, strlen(t)+1);
125+
s = t;
126+
t = NULL;
127+
} else
128+
break;
129+
} while (!t);
130+
131+
ezd_trim(&val, t);
132+
133+
len = (int)(t-val);
134+
if (len > 0) {
135+
if (cnt == size) {
136+
size = (size + 4) * 2;
137+
*arrp = realloc(*arrp, sizeof(**arrp) * size);
138+
}
139+
140+
(*arrp)[cnt++] = strndup(val, len);
141+
} else
142+
len++;
143+
144+
val += len;
145+
}
146+
return cnt;
147+
}
148+
149+
150+
151+
152+
153+
int ezd_conf_file_read (const char *path,
154+
int (*conf_set_cb) (const char *name,
155+
const char *val,
156+
char *errstr,
157+
size_t errstr_size,
158+
const char *path,
159+
int line,
160+
void *opaque),
161+
char *errstr, size_t errstr_size,
162+
void *opaque) {
163+
FILE *fp;
164+
char buf[512];
165+
int line = 0;
166+
167+
if (!(fp = fopen(path, "r"))) {
168+
snprintf(errstr, errstr_size,
169+
"Failed to open configuration file %s: %s",
170+
path, strerror(errno));
171+
return -1;
172+
}
173+
174+
while (fgets(buf, sizeof(buf), fp)) {
175+
char *s = buf;
176+
char *t;
177+
178+
line++;
179+
180+
while (isspace(*s))
181+
s++;
182+
183+
if (!*s || *s == '#')
184+
continue;
185+
186+
/* "name=value"
187+
* find ^ */
188+
if (!(t = strchr(s, '='))) {
189+
snprintf(errstr, errstr_size,
190+
"%s:%i: warning: "
191+
"missing '=': line ignored\n",
192+
path, line);
193+
continue;
194+
}
195+
196+
/* ezd_trim "name"=.. */
197+
if (!ezd_trim(&s, t)) {
198+
snprintf(errstr, errstr_size,
199+
"%s:%i: warning: empty left-hand-side\n",
200+
path, line);
201+
continue;
202+
}
203+
204+
/* terminate "name"=.. */
205+
*t = '\0';
206+
t++;
207+
208+
/* ezd_trim ..="value" */
209+
ezd_trim(&t, t + strlen(t));
210+
211+
/* set the configuration value. */
212+
if (conf_set_cb(s, *t ? t : NULL, errstr, errstr_size,
213+
path, line, opaque) == -1) {
214+
fclose(fp);
215+
return -1;
216+
}
217+
}
218+
219+
220+
fclose(fp);
221+
return 0;
222+
}
223+
224+
225+
void ezd_pidfile_close (void) {
226+
if (!*ezd_pidfile_path)
227+
return;
228+
229+
flock(ezd_pidfile_fd, LOCK_UN|LOCK_NB);
230+
unlink(ezd_pidfile_path);
231+
close(ezd_pidfile_fd);
232+
}
233+
234+
235+
236+
int ezd_pidfile_open (const char *path, char *errstr, size_t errstr_size) {
237+
int fd;
238+
pid_t currpid = 0;
239+
char buf[64];
240+
int r;
241+
242+
fd = open(path, O_RDWR|O_CREAT, 0644);
243+
if (fd == -1) {
244+
snprintf(errstr, errstr_size,
245+
"Unable to open pidfile %s: %s",
246+
path, strerror(errno));
247+
return -1;
248+
}
249+
250+
/* Read current pid, if any. */
251+
if ((r = read(fd, buf, sizeof(buf)-1)) > 0) {
252+
char *end;
253+
buf[r] = '\0';
254+
currpid = strtoul(buf, &end, 10);
255+
if (end == buf)
256+
currpid = 0;
257+
}
258+
259+
if (flock(fd, LOCK_EX|LOCK_NB) == -1) {
260+
if (errno == EWOULDBLOCK)
261+
snprintf(errstr, errstr_size,
262+
"Pidfile %s locked by other process (%i)",
263+
path, (int)currpid);
264+
else
265+
snprintf(errstr, errstr_size,
266+
"Failed to lock pidfile %s: %s",
267+
path, strerror(errno));
268+
close(fd);
269+
return -1;
270+
}
271+
272+
(void)lseek(fd, 0, SEEK_SET);
273+
(void)ftruncate(fd, 0);
274+
275+
snprintf(buf, sizeof(buf), "%i\n", (int)getpid());
276+
r = write(fd, buf, strlen(buf));
277+
if (r == -1) {
278+
snprintf(errstr, errstr_size,
279+
"Failed to write pidfile %s: %s",
280+
path, strerror(errno));
281+
close(fd);
282+
return -1;
283+
} else if (r < strlen(buf)) {
284+
snprintf(errstr, errstr_size,
285+
"Partial pidfile write %s: %i/%i bytes written",
286+
path, r, (int)strlen(buf));
287+
close(fd);
288+
return -1;
289+
}
290+
291+
strncpy(ezd_pidfile_path, path, sizeof(ezd_pidfile_path)-1);
292+
ezd_pidfile_fd = fd;
293+
294+
return ezd_pidfile_fd;
295+
}
296+
297+
298+
299+
enum {
300+
EZD_DAEMON_WAIT,
301+
EZD_DAEMON_FAILED,
302+
EZD_DAEMON_DIED,
303+
EZD_DAEMON_STARTED,
304+
} ezd_daemon_status = EZD_DAEMON_WAIT;
305+
306+
static void ezd_daemon_sig_started_cb (int sig) {
307+
/* Child process is now fully started. */
308+
ezd_daemon_status = EZD_DAEMON_STARTED;
309+
}
310+
311+
static void ezd_daemon_sig_chld_cb (int sig) {
312+
int st;
313+
waitpid(-1, &st, 0);
314+
ezd_daemon_status = EZD_DAEMON_DIED;
315+
}
316+
317+
int ezd_daemon (int timeout_sec, char *errstr, size_t errstr_size) {
318+
pid_t pid;
319+
sighandler_t sh_usr2_orig, sh_chld_orig;
320+
time_t timeout_abs;
321+
322+
323+
/* Parent process will wait for signal or termination of the
324+
* child thread. */
325+
sh_usr2_orig = signal(SIGUSR2, ezd_daemon_sig_started_cb);
326+
sh_chld_orig = signal(SIGCHLD, ezd_daemon_sig_chld_cb);
327+
timeout_abs = time(NULL) + timeout_sec;
328+
329+
if ((pid = fork()) == -1) {
330+
snprintf(errstr, errstr_size,
331+
"Daemon fork failed: %s", strerror(errno));
332+
return -1;
333+
}
334+
335+
if (pid == 0) {
336+
/* Child process. */
337+
signal(SIGUSR2, sh_usr2_orig);
338+
signal(SIGCHLD, sh_chld_orig);
339+
return 0;
340+
}
341+
342+
343+
while (ezd_daemon_status == EZD_DAEMON_WAIT) {
344+
usleep(100000);
345+
if (time(NULL) >= timeout_abs) {
346+
snprintf(errstr, errstr_size,
347+
"Daemon child process (pid %i) did not "
348+
"start in %i seconds",
349+
(int)pid, timeout_sec);
350+
kill(pid, SIGTERM);
351+
ezd_daemon_status = EZD_DAEMON_FAILED;
352+
signal(SIGUSR2, sh_usr2_orig);
353+
signal(SIGCHLD, sh_chld_orig);
354+
return -1;
355+
}
356+
}
357+
358+
signal(SIGUSR2, sh_usr2_orig);
359+
signal(SIGCHLD, sh_chld_orig);
360+
361+
if (ezd_daemon_status == EZD_DAEMON_DIED) {
362+
snprintf(errstr, errstr_size,
363+
"Daemon child process (pid %i) terminated "
364+
"during startup", (int)pid);
365+
return -1;
366+
} else if (ezd_daemon_status == EZD_DAEMON_STARTED)
367+
exit(0);
368+
369+
assert(!*"notreached");
370+
exit(0);
371+
}
372+
373+
374+
void ezd_daemon_started (void) {
375+
int i;
376+
close(STDIN_FILENO);
377+
close(STDOUT_FILENO);
378+
close(STDERR_FILENO);
379+
for (i = 0 ; i < 3 ; i++)
380+
open("/dev/null", 0);
381+
kill(getppid(), SIGUSR2);
382+
setsid();
383+
}

0 commit comments

Comments
 (0)