summaryrefslogtreecommitdiff
path: root/daemon.c
blob: 61de9f45395bc41c319be24e07fe3126f29852f0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
/* SPDX-License-Identifier: GPL-2.0 */

/** \file daemon.c Some helpers for programs that detach from the console. */

#include <pwd.h>
#include <sys/types.h> /* getgrnam() */
#include <grp.h>
#include <signal.h>
#include <sys/resource.h>

#include "para.h"
#include "daemon.h"
#include "string.h"
#include "color.h"

/** The internal state of the daemon. */
struct daemon {
	/** See \ref daemon_flags. */
	unsigned flags;
	/** Set by \ref daemon_set_logfile(). */
	char *logfile_name;
	/** Current loglevel, see \ref daemon_set_loglevel(). */
	int loglevel;
	/** Used by \ref server_uptime() and \ref uptime_str(). */
	time_t startuptime;
	/** The file pointer if the logfile is open. */
	FILE *logfile;
	/** Used for colored log messages. */
	char log_colors[NUM_LOGLEVELS][COLOR_MAXLEN];
	char *old_cwd;
	/*
	 * If these pointers are non-NULL, the functions are called from
	 * daemon_log() before and after writing each log message.
	 */
	void (*pre_log_hook)(void);
	void (*post_log_hook)(void);
};

static struct daemon the_daemon, *me = &the_daemon;

static void daemon_set_default_log_colors(void)
{
	int i;
	static const char *default_log_colors[NUM_LOGLEVELS] = {
		[LL_DEBUG] = "normal",
		[LL_INFO] = "normal",
		[LL_NOTICE] = "normal",
		[LL_WARNING] = "yellow",
		[LL_ERROR] = "red",
		[LL_CRIT] = "magenta bold",
		[LL_EMERG] = "red bold",
	};
	for (i = 0; i < NUM_LOGLEVELS; i++)
		color_parse_or_die(default_log_colors[i], me->log_colors[i]);
}

/**
 * Set the color for log messages of the given severity level.
 *
 * \param arg Must be of the form "severity:[fg [bg]] [attr]".
 */
void daemon_set_log_color_or_die(const char *arg)
{
	unsigned ll;
	const char * const sev[] = {SEVERITIES};
	const char *p = strchr(arg, ':');

	if (!p)
		goto err;
	for (ll = 0; ll < NUM_LOGLEVELS; ll++) {
		const char *name = sev[ll];
		/*
		 * Parse only the first part of the string so that, for
		 * example, the argument "info:something_else" is recognized.
		 * Note that the string comparison is performed
		 * case-insensitively.
		 */
		if (strncasecmp(arg, name, strlen(name)))
			continue;
		return color_parse_or_die(p + 1, me->log_colors[ll]);
	}
err:
	PARA_EMERG_LOG("%s: invalid color argument\n", arg);
	exit(EXIT_FAILURE);
}

/**
 * Initialize color mode if necessary.
 *
 * \param color_arg The argument given to --color.
 * \param color_arg_auto The value for automatic color detection.
 * \param color_arg_no The value to disable colored log messages.
 * \param logfile_given In auto mode colors are disabled if this value is true.
 *
 * If color_arg equals color_arg_no, color mode is disabled. That is, calls to
 * para_log() will not produce colored output. If color_arg equals
 * color_arg_auto, the function detects automatically whether to activate
 * colors. Otherwise color mode is enabled.
 *
 * If color mode is to be enabled, the default colors are set for each
 * loglevel. They can be overwritten by calling daemon_set_log_color_or_die().
 *
 * \return Whether colors have been enabled by the function.
 */
bool daemon_init_colors_or_die(int color_arg, int color_arg_auto,
		int color_arg_no, bool logfile_given)
{
	if (color_arg == color_arg_no)
		return false;
	if (color_arg == color_arg_auto) {
		if (logfile_given)
			return false;
		if (!isatty(STDERR_FILENO))
			return false;
	}
	daemon_set_flag(DF_COLOR_LOG);
	daemon_set_default_log_colors();
	return true;
}

/**
 * Init or change the name of the log file.
 *
 * \param logfile_name The full path of the logfile.
 */
void daemon_set_logfile(const char *logfile_name)
{
	free(me->logfile_name);
	me->logfile_name = NULL;
	if (!logfile_name)
		return;
	if (me->old_cwd && logfile_name[0] != '/')
		me->logfile_name = make_message("%s/%s", me->old_cwd,
			logfile_name);
	else
		me->logfile_name = para_strdup(logfile_name);
}

/**
 * Control the verbosity for logging.
 *
 * This instructs the daemon to not log subsequent messages whose severity is
 * lower than the given value.
 *
 * \param loglevel The new log level.
 */
void daemon_set_loglevel(int loglevel)
{
	assert(loglevel >= 0);
	assert(loglevel < NUM_LOGLEVELS);
	me->loglevel = loglevel;
}

/**
 * Get the current log level of the daemon.
 *
 * \return Greater or equal than zero and less than NUM_LOGLEVELS. This
 * function never fails.
 */
int daemon_get_loglevel(void)
{
	return me->loglevel;
}

/**
 * Register functions to be called before and after a message is logged.
 *
 * \param pre_log_hook Called before the message is logged.
 * \param post_log_hook Called after the message is logged.
 *
 * The purpose of this function is to provide a primitive for multi-threaded
 * applications to serialize the access to the log facility, preventing
 * interleaving log messages. This can be achieved by having the pre-log hook
 * acquire a lock which blocks the other threads on the attempt to log a
 * message at the same time.  The post-log hook is responsible for releasing
 * the lock.
 *
 * If these hooks are unnecessary, for example because the application is
 * single-threaded, this function does not need to be called.
 */
void daemon_set_hooks(void (*pre_log_hook)(void), void (*post_log_hook)(void))
{
	me->pre_log_hook = pre_log_hook;
	me->post_log_hook = post_log_hook;
}

/**
 * Set one of the daemon config flags.
 *
 * \param flag The flag to set.
 *
 * \sa \ref daemon_flags.
 */
void daemon_set_flag(unsigned flag)
{
	me->flags |= flag;
}

static bool daemon_test_flag(unsigned flag)
{
	return me->flags & flag;
}

/**
 * Do the usual stuff to become a daemon.
 *
 * \param parent_waits Whether the parent process should pause before exit.
 *
 * Fork, become session leader, cd to /, and dup fd 0, 1, 2 to /dev/null. If \a
 * parent_waits is false, the parent process terminates immediately.
 * Otherwise, a pipe is created prior to the fork() and the parent tries to
 * read a single byte from the reading end of the pipe. The child is supposed
 * to write to the writing end of the pipe after it completed its setup
 * procedure successfully. This behaviour is useful to let the parent process
 * die with an error if the child process aborts early, since in this case the
 * read() will return non-positive.
 *
 * \return This function either succeeds or calls exit(3). If parent_waits is
 * true, the return value is the file descriptor of the writing end of the
 * pipe. Otherwise the function returns zero.
 *
 * \sa fork(2), setsid(2), dup(2), pause(2).
 */
int daemonize(bool parent_waits)
{
	pid_t pid;
	int null, pipe_fd[2];

	if (parent_waits && pipe(pipe_fd) < 0)
		goto err;
	PARA_INFO_LOG("subsequent log messages go to %s\n", me->logfile_name?
		 me->logfile_name : "/dev/null");
	pid = fork();
	if (pid < 0)
		goto err;
	if (pid) { /* parent exits */
		if (parent_waits) {
			char c;
			close(pipe_fd[1]);
			exit(read(pipe_fd[0], &c, 1) <= 0?
				EXIT_FAILURE : EXIT_SUCCESS);
		}
		exit(EXIT_SUCCESS);
	}
	if (parent_waits)
		close(pipe_fd[0]);
	/* become session leader */
	if (setsid() < 0)
		goto err;
	me->old_cwd = getcwd(NULL, 0);
	if (chdir("/") < 0)
		goto err;
	null = open("/dev/null", O_RDWR);
	if (null < 0)
		goto err;
	if (dup2(null, STDIN_FILENO) < 0)
		goto err;
	if (dup2(null, STDOUT_FILENO) < 0)
		goto err;
	if (dup2(null, STDERR_FILENO) < 0)
		goto err;
	close(null);
	return parent_waits? pipe_fd[1] : 0;
err:
	PARA_EMERG_LOG("fatal: %s\n", strerror(errno));
	exit(EXIT_FAILURE);
}

/**
 * Close the log file of the daemon.
 */
void daemon_close_log(void)
{
	if (!me->logfile)
		return;
	PARA_INFO_LOG("closing logfile\n");
	fclose(me->logfile);
	me->logfile = NULL;
}

/**
 * Open the logfile in append mode.
 *
 * This function either succeeds or exits.
 */
void daemon_open_log_or_die(void)
{
	FILE *new_log;

	if (!me->logfile_name)
		return;
	new_log = fopen(me->logfile_name, "a");
	if (!new_log) {
		PARA_EMERG_LOG("can not open %s: %s\n", me->logfile_name,
			strerror(errno));
		exit(EXIT_FAILURE);
	}
	daemon_close_log();
	me->logfile = new_log;
	/* equivalent to setlinebuf(), but portable */
	setvbuf(me->logfile, NULL, _IOLBF, 0);
}

/**
 * Log the startup message containing the paraslash version.
 *
 * \param name The name of the executable.
 *
 * First the given \a name is prefixed with the string "para_". Next the git
 * version is appended. The resulting string is logged with priority "INFO".
 */
void daemon_log_welcome(const char *name)
{
	PARA_INFO_LOG("welcome to para_%s-%s\n", name, paraslash_version());
}

/**
 * Renice the calling process.
 *
 * \param prio The priority value to set.
 *
 * Errors are not considered fatal, but a warning message is logged if the
 * underlying call to setpriority(2) fails.
 */
void daemon_set_priority(int prio)
{
	if (setpriority(PRIO_PROCESS, 0, prio) < 0)
		PARA_WARNING_LOG("could not set priority to %d: %s\n", prio,
			strerror(errno));
}

/**
 * Give up superuser privileges.
 *
 * \param username The user to switch to.
 * \param groupname The group to switch to.
 *
 * This function returns immediately if not invoked with EUID zero. Otherwise,
 * it tries to obtain the GID of \a groupname and the UID of \a username.  On
 * success, effective and real GID/UID and the saved set-group-ID/set-user-ID
 * are all set accordingly. On errors, an appropriate message is logged and
 * exit() is called to terminate the process.
 *
 * \sa getpwnam(3), getuid(2), setuid(2), getgrnam(2), setgid(2)
 */
void daemon_drop_privileges_or_die(const char *username, const char *groupname)
{
	struct passwd *p;
	char *tmp;

	if (geteuid())
		return;
	if (groupname) {
		struct group *g = getgrnam(groupname);
		if (!g) {
			PARA_EMERG_LOG("failed to get group %s: %s\n",
				groupname, strerror(errno));
			exit(EXIT_FAILURE);
		}
		if (setgid(g->gr_gid) < 0) {
			PARA_EMERG_LOG("failed to set group id %d: %s\n",
				(int)g->gr_gid, strerror(errno));
			exit(EXIT_FAILURE);
		}
	}
	if (!username) {
		PARA_EMERG_LOG("root privileges, but no user option given\n");
		exit(EXIT_FAILURE);
	}
	tmp = para_strdup(username);
	p = getpwnam(tmp);
	free(tmp);
	if (!p) {
		PARA_EMERG_LOG("%s: no such user\n", username);
		exit(EXIT_FAILURE);
	}
	PARA_INFO_LOG("dropping root privileges\n");
	if (setuid(p->pw_uid) < 0) {
		PARA_EMERG_LOG("failed to set effective user ID (%s)",
			strerror(errno));
		exit(EXIT_FAILURE);
	}
	PARA_DEBUG_LOG("uid: %d, euid: %d\n", (int)getuid(), (int)geteuid());
}

/**
 * Set the startup time.
 *
 * This should be called once on startup. It sets the start time to the
 * current time. The stored time is used for retrieving the server uptime.
 *
 * \sa time(2), \ref daemon_get_uptime(), \ref daemon_get_uptime_str().
 */
void daemon_set_start_time(void)
{
	time(&me->startuptime);
}

/**
 * Get the uptime.
 *
 * \param current_time The current time.
 *
 * The \a current_time pointer may be \p NULL. In this case the function
 * obtains the current time from the system.
 *
 * \return This returns the server uptime in seconds, i.e. the difference
 * between the current time and the value stored previously via \ref
 * daemon_set_start_time().
 */
time_t daemon_get_uptime(const struct timeval *current_time)
{
	time_t t;

	if (current_time)
		return current_time->tv_sec - me->startuptime;
	time(&t);
	return difftime(t, me->startuptime);
}

/**
 * Construct a string containing the current uptime.
 *
 * \param current_time See a \ref daemon_get_uptime().
 *
 * \return A dynamically allocated string of the form "days:hours:minutes".
 */
__malloc char *daemon_get_uptime_str(const struct timeval *current_time)
{
	long t = daemon_get_uptime(current_time);
	return make_message("%li:%02li:%02li", t / 86400,
		(t / 3600) % 24, (t / 60) % 60);
}

/**
 * The log function for para_server and para_audiod.
 *
 * \param ll The log level.
 * \param fmt The format string describing the log message.
 */
__printf_2_3 void daemon_log(int ll, const char* fmt,...)
{
	va_list argp;
	FILE *fp;
	struct tm *tm;
	char *color;
	bool log_time = daemon_test_flag(DF_LOG_TIME), log_timing =
		daemon_test_flag(DF_LOG_TIMING);

	ll = PARA_MIN(ll, NUM_LOGLEVELS - 1);
	ll = PARA_MAX(ll, LL_DEBUG);
	if (ll < me->loglevel)
		return;

	fp = me->logfile? me->logfile : stderr;
	if (me->pre_log_hook)
		me->pre_log_hook();
	color = daemon_test_flag(DF_COLOR_LOG)? me->log_colors[ll] : NULL;
	if (color)
		fprintf(fp, "%s", color);
	if (log_time || log_timing) {
		struct timeval tv;
		clock_get_realtime(&tv);
		if (daemon_test_flag(DF_LOG_TIME)) { /* print date and time */
			time_t t1 = tv.tv_sec;
			char str[100];
			tm = localtime(&t1);
			strftime(str, sizeof(str), "%b %d %H:%M:%S", tm);
			fprintf(fp, "%s%s", str, log_timing? ":" : " ");
		}
		if (log_timing) /* print milliseconds */
			fprintf(fp, "%04lu ", (long unsigned)tv.tv_usec / 1000);
	}
	if (daemon_test_flag(DF_LOG_HOSTNAME))
		fprintf(fp, "%s ", para_hostname());
	if (daemon_test_flag(DF_LOG_LL)) /* log loglevel */
		fprintf(fp, "(%d) ", ll);
	if (daemon_test_flag(DF_LOG_PID)) { /* log pid */
		pid_t mypid = getpid();
		fprintf(fp, "(%d) ", (int)mypid);
	}
	va_start(argp, fmt);
	vfprintf(fp, fmt, argp);
	va_end(argp);
	if (color)
		fprintf(fp, "%s", COLOR_RESET);
	if (me->post_log_hook)
		me->post_log_hook();
}