1 /* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2
3 This program is free software: you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation, either version 3 of the License, or
6 (at your option) any later version.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program. If not, see <https://www.gnu.org/licenses/>.
15 */
16
17 #include <stdarg.h>
18 #include <stdio.h>
19 #include <string.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <sys/time.h>
23 #include <time.h>
24 #include <urcu.h>
25
26 #ifdef ENABLE_SYSTEMD
27 #define SD_JOURNAL_SUPPRESS_LOCATION 1
28 #include <systemd/sd-journal.h>
29 #include <systemd/sd-daemon.h>
30 #endif
31
32 #include "knot/common/log.h"
33 #include "libknot/libknot.h"
34 #include "contrib/ucw/lists.h"
35
36 /*! Single log message buffer length (one line). */
37 #define LOG_BUFLEN 512
38 #define NULL_ZONE_STR "?"
39
40 #ifdef ENABLE_SYSTEMD
41 int use_journal = 0;
42 #endif
43
44 /*! Log context. */
45 typedef struct {
46 size_t target_count; /*!< Log target count. */
47 int *target; /*!< Log targets. */
48 size_t file_count; /*!< Open files count. */
49 FILE **file; /*!< Open files. */
50 log_flag_t flags; /*!< Formatting flags. */
51 } log_t;
52
53 /*! Log singleton. */
54 log_t *s_log = NULL;
55
log_isopen(void)56 static bool log_isopen(void)
57 {
58 return s_log != NULL;
59 }
60
sink_free(log_t * log)61 static void sink_free(log_t *log)
62 {
63 if (log == NULL) {
64 return;
65 }
66
67 // Close open log files.
68 for (int i = 0; i < log->file_count; ++i) {
69 fclose(log->file[i]);
70 }
71 free(log->target);
72 free(log->file);
73 free(log);
74 }
75
76 /*!
77 * \brief Create logging targets respecting their canonical order.
78 *
79 * Facilities ordering: Syslog, Stderr, Stdout, File0...
80 */
sink_setup(size_t file_count)81 static log_t *sink_setup(size_t file_count)
82 {
83 log_t *log = malloc(sizeof(*log));
84 if (log == NULL) {
85 return NULL;
86 }
87 memset(log, 0, sizeof(*log));
88
89 // Reserve space for targets.
90 log->target_count = LOG_TARGET_FILE + file_count;
91 log->target = malloc(LOG_SOURCE_ANY * sizeof(int) * log->target_count);
92 if (!log->target) {
93 free(log);
94 return NULL;
95 }
96 memset(log->target, 0, LOG_SOURCE_ANY * sizeof(int) * log->target_count);
97
98 // Reserve space for log files.
99 if (file_count > 0) {
100 log->file = malloc(sizeof(FILE *) * file_count);
101 if (!log->file) {
102 free(log->target);
103 free(log);
104 return NULL;
105 }
106 memset(log->file, 0, sizeof(FILE *) * file_count);
107 }
108
109 return log;
110 }
111
sink_publish(log_t * log)112 static void sink_publish(log_t *log)
113 {
114 log_t **current_log = &s_log;
115 log_t *old_log = rcu_xchg_pointer(current_log, log);
116 synchronize_rcu();
117 sink_free(old_log);
118 }
119
src_levels(log_t * log,log_target_t target,log_source_t src)120 static int *src_levels(log_t *log, log_target_t target, log_source_t src)
121 {
122 assert(src < LOG_SOURCE_ANY);
123 return &log->target[LOG_SOURCE_ANY * target + src];
124 }
125
sink_levels_set(log_t * log,log_target_t target,log_source_t src,int levels)126 static void sink_levels_set(log_t *log, log_target_t target, log_source_t src, int levels)
127 {
128 // Assign levels to the specified source.
129 if (src != LOG_SOURCE_ANY) {
130 *src_levels(log, target, src) = levels;
131 } else {
132 // ANY ~ set levels to all sources.
133 for (int i = 0; i < LOG_SOURCE_ANY; ++i) {
134 *src_levels(log, target, i) = levels;
135 }
136 }
137 }
138
sink_levels_add(log_t * log,log_target_t target,log_source_t src,int levels)139 static void sink_levels_add(log_t *log, log_target_t target, log_source_t src, int levels)
140 {
141 // Add levels to the specified source.
142 if (src != LOG_SOURCE_ANY) {
143 *src_levels(log, target, src) |= levels;
144 } else {
145 // ANY ~ add levels to all sources.
146 for (int i = 0; i < LOG_SOURCE_ANY; ++i) {
147 *src_levels(log, target, i) |= levels;
148 }
149 }
150 }
151
log_init(void)152 void log_init(void)
153 {
154 // Setup initial state.
155 int emask = LOG_MASK(LOG_CRIT) | LOG_MASK(LOG_ERR) | LOG_MASK(LOG_WARNING);
156 int imask = LOG_MASK(LOG_NOTICE) | LOG_MASK(LOG_INFO);
157
158 // Publish base log sink.
159 log_t *log = sink_setup(0);
160 if (log == NULL) {
161 fprintf(stderr, "Failed to setup logging\n");
162 return;
163 }
164
165 #ifdef ENABLE_SYSTEMD
166 // Should only use the journal if system was booted with systemd.
167 use_journal = sd_booted();
168 #endif
169
170 sink_levels_set(log, LOG_TARGET_SYSLOG, LOG_SOURCE_ANY, emask);
171 sink_levels_set(log, LOG_TARGET_STDERR, LOG_SOURCE_ANY, emask);
172 sink_levels_set(log, LOG_TARGET_STDOUT, LOG_SOURCE_ANY, imask);
173 sink_publish(log);
174
175 setlogmask(LOG_UPTO(LOG_DEBUG));
176 openlog(PACKAGE_NAME, LOG_PID, LOG_DAEMON);
177 }
178
log_close(void)179 void log_close(void)
180 {
181 sink_publish(NULL);
182
183 fflush(stdout);
184 fflush(stderr);
185
186 closelog();
187 }
188
log_flag_set(log_flag_t flag)189 void log_flag_set(log_flag_t flag)
190 {
191 if (log_isopen()) {
192 s_log->flags |= flag;
193 }
194 }
195
log_levels_set(log_target_t target,log_source_t src,int levels)196 void log_levels_set(log_target_t target, log_source_t src, int levels)
197 {
198 if (log_isopen()) {
199 sink_levels_set(s_log, target, src, levels);
200 }
201 }
202
log_levels_add(log_target_t target,log_source_t src,int levels)203 void log_levels_add(log_target_t target, log_source_t src, int levels)
204 {
205 if (log_isopen()) {
206 sink_levels_add(s_log, target, src, levels);
207 }
208 }
209
emit_log_msg(int level,log_source_t src,const char * zone,size_t zone_len,const char * msg,const char * param)210 static void emit_log_msg(int level, log_source_t src, const char *zone,
211 size_t zone_len, const char *msg, const char *param)
212 {
213 log_t *log = s_log;
214
215 // Syslog target.
216 if (*src_levels(log, LOG_TARGET_SYSLOG, src) & LOG_MASK(level)) {
217 #ifdef ENABLE_SYSTEMD
218 if (use_journal) {
219 char *zone_fmt = zone ? "ZONE=%.*s." : NULL;
220 sd_journal_send("PRIORITY=%d", level,
221 "MESSAGE=%s", msg,
222 zone_fmt, zone_len, zone,
223 param, NULL);
224 } else
225 #endif
226 {
227 syslog(level, "%s", msg);
228 }
229 }
230
231 // Prefix date and time.
232 char tstr[LOG_BUFLEN] = { 0 };
233 if (!(s_log->flags & LOG_FLAG_NOTIMESTAMP)) {
234 struct tm lt;
235 struct timeval tv;
236 gettimeofday(&tv, NULL);
237 time_t sec = tv.tv_sec;
238 if (localtime_r(&sec, <) != NULL) {
239 strftime(tstr, sizeof(tstr), KNOT_LOG_TIME_FORMAT " ", <);
240 }
241 }
242
243 // Other log targets.
244 for (int i = LOG_TARGET_STDERR; i < LOG_TARGET_FILE + log->file_count; ++i) {
245 if (*src_levels(log, i, src) & LOG_MASK(level)) {
246 FILE *stream;
247 switch (i) {
248 case LOG_TARGET_STDERR: stream = stderr; break;
249 case LOG_TARGET_STDOUT: stream = stdout; break;
250 default: stream = log->file[i - LOG_TARGET_FILE]; break;
251 }
252
253 // Print the message.
254 fprintf(stream, "%s%s\n", tstr, msg);
255 if (stream == stdout) {
256 fflush(stream);
257 }
258 }
259 }
260 }
261
level_prefix(int level)262 static const char *level_prefix(int level)
263 {
264 switch (level) {
265 case LOG_DEBUG: return "debug";
266 case LOG_INFO: return "info";
267 case LOG_NOTICE: return "notice";
268 case LOG_WARNING: return "warning";
269 case LOG_ERR: return "error";
270 case LOG_CRIT: return "critical";
271 default: return NULL;
272 };
273 }
274
log_msg_add(char ** write,size_t * capacity,const char * fmt,...)275 static int log_msg_add(char **write, size_t *capacity, const char *fmt, ...)
276 {
277 va_list args;
278 va_start(args, fmt);
279 int written = vsnprintf(*write, *capacity, fmt, args);
280 va_end(args);
281
282 if (written < 0 || written >= *capacity) {
283 return KNOT_ESPACE;
284 }
285
286 *write += written;
287 *capacity -= written;
288
289 return KNOT_EOK;
290 }
291
log_msg_text(int level,log_source_t src,const char * zone,const char * fmt,va_list args,const char * param)292 static void log_msg_text(int level, log_source_t src, const char *zone,
293 const char *fmt, va_list args, const char *param)
294 {
295 if (!log_isopen() || src == LOG_SOURCE_ANY) {
296 return;
297 }
298
299 // Buffer for log message.
300 char buff[LOG_BUFLEN];
301 char *write = buff;
302 size_t capacity = sizeof(buff);
303
304 rcu_read_lock();
305
306 // Prefix error level.
307 if (level != LOG_INFO || !(s_log->flags & LOG_FLAG_NOINFO)) {
308 const char *prefix = level_prefix(level);
309 int ret = log_msg_add(&write, &capacity, "%s: ", prefix);
310 if (ret != KNOT_EOK) {
311 rcu_read_unlock();
312 return;
313 }
314 }
315
316 // Prefix zone name.
317 size_t zone_len = 0;
318 if (zone != NULL) {
319 zone_len = strlen(zone);
320 if (zone_len > 0 && zone[zone_len - 1] == '.') {
321 zone_len--;
322 }
323
324 int ret = log_msg_add(&write, &capacity, "[%.*s.] ", (int)zone_len, zone);
325 if (ret != KNOT_EOK) {
326 rcu_read_unlock();
327 return;
328 }
329 }
330
331 // Compile log message.
332 int ret = vsnprintf(write, capacity, fmt, args);
333 if (ret >= 0) {
334 // Send to logging targets.
335 emit_log_msg(level, src, zone, zone_len, buff, param);
336 }
337
338 rcu_read_unlock();
339 }
340
log_fmt(int priority,log_source_t src,const char * fmt,...)341 void log_fmt(int priority, log_source_t src, const char *fmt, ...)
342 {
343 va_list args;
344 va_start(args, fmt);
345 log_msg_text(priority, src, NULL, fmt, args, NULL);
346 va_end(args);
347 }
348
log_fmt_zone(int priority,log_source_t src,const knot_dname_t * zone,const char * param,const char * fmt,...)349 void log_fmt_zone(int priority, log_source_t src, const knot_dname_t *zone,
350 const char *param, const char *fmt, ...)
351 {
352 knot_dname_txt_storage_t buff;
353 char *zone_str = knot_dname_to_str(buff, zone, sizeof(buff));
354 if (zone_str == NULL) {
355 zone_str = NULL_ZONE_STR;
356 }
357
358 va_list args;
359 va_start(args, fmt);
360 log_msg_text(priority, src, zone_str, fmt, args, param);
361 va_end(args);
362 }
363
log_fmt_zone_str(int priority,log_source_t src,const char * zone,const char * fmt,...)364 void log_fmt_zone_str(int priority, log_source_t src, const char *zone,
365 const char *fmt, ...)
366 {
367 if (zone == NULL) {
368 zone = NULL_ZONE_STR;
369 }
370
371 va_list args;
372 va_start(args, fmt);
373 log_msg_text(priority, src, zone, fmt, args, NULL);
374 va_end(args);
375 }
376
log_update_privileges(int uid,int gid)377 int log_update_privileges(int uid, int gid)
378 {
379 if (!log_isopen()) {
380 return KNOT_EOK;
381 }
382
383 for (int i = 0; i < s_log->file_count; ++i) {
384 if (fchown(fileno(s_log->file[i]), uid, gid) < 0) {
385 log_error("failed to change log file owner");
386 }
387 }
388
389 return KNOT_EOK;
390 }
391
get_logtype(const char * logname)392 static log_target_t get_logtype(const char *logname)
393 {
394 assert(logname);
395
396 if (strcasecmp(logname, "syslog") == 0) {
397 return LOG_TARGET_SYSLOG;
398 } else if (strcasecmp(logname, "stderr") == 0) {
399 return LOG_TARGET_STDERR;
400 } else if (strcasecmp(logname, "stdout") == 0) {
401 return LOG_TARGET_STDOUT;
402 } else {
403 return LOG_TARGET_FILE;
404 }
405 }
406
log_open_file(log_t * log,const char * filename)407 static int log_open_file(log_t *log, const char *filename)
408 {
409 assert(LOG_TARGET_FILE + log->file_count < log->target_count);
410
411 // Open the file.
412 log->file[log->file_count] = fopen(filename, "a");
413 if (log->file[log->file_count] == NULL) {
414 return knot_map_errno();
415 }
416
417 // Disable buffering.
418 setvbuf(log->file[log->file_count], NULL, _IONBF, 0);
419
420 return LOG_TARGET_FILE + log->file_count++;
421 }
422
log_reconfigure(conf_t * conf)423 void log_reconfigure(conf_t *conf)
424 {
425 // Use defaults if no 'log' section is configured.
426 if (conf_id_count(conf, C_LOG) == 0) {
427 log_close();
428 log_init();
429 return;
430 }
431
432 // Find maximum log target id.
433 unsigned files = 0;
434 for (conf_iter_t iter = conf_iter(conf, C_LOG); iter.code == KNOT_EOK;
435 conf_iter_next(conf, &iter)) {
436 conf_val_t id = conf_iter_id(conf, &iter);
437 if (get_logtype(conf_str(&id)) == LOG_TARGET_FILE) {
438 ++files;
439 }
440 }
441
442 // Initialize logsystem.
443 log_t *log = sink_setup(files);
444 if (log == NULL) {
445 fprintf(stderr, "Failed to setup logging\n");
446 return;
447 }
448
449 // Setup logs.
450 for (conf_iter_t iter = conf_iter(conf, C_LOG); iter.code == KNOT_EOK;
451 conf_iter_next(conf, &iter)) {
452 conf_val_t id = conf_iter_id(conf, &iter);
453 const char *logname = conf_str(&id);
454
455 // Get target.
456 int target = get_logtype(logname);
457 if (target == LOG_TARGET_FILE) {
458 target = log_open_file(log, logname);
459 if (target < 0) {
460 log_error("failed to open log, file '%s' (%s)",
461 logname, knot_strerror(target));
462 continue;
463 }
464 }
465
466 conf_val_t levels_val;
467 unsigned levels;
468
469 // Set SERVER logging.
470 levels_val = conf_id_get(conf, C_LOG, C_SERVER, &id);
471 levels = conf_opt(&levels_val);
472 sink_levels_add(log, target, LOG_SOURCE_SERVER, levels);
473
474 // Set CONTROL logging.
475 levels_val = conf_id_get(conf, C_LOG, C_CTL, &id);
476 levels = conf_opt(&levels_val);
477 sink_levels_add(log, target, LOG_SOURCE_CONTROL, levels);
478
479 // Set ZONE logging.
480 levels_val = conf_id_get(conf, C_LOG, C_ZONE, &id);
481 levels = conf_opt(&levels_val);
482 sink_levels_add(log, target, LOG_SOURCE_ZONE, levels);
483
484 // Set ANY logging.
485 levels_val = conf_id_get(conf, C_LOG, C_ANY, &id);
486 levels = conf_opt(&levels_val);
487 sink_levels_add(log, target, LOG_SOURCE_ANY, levels);
488 }
489
490 sink_publish(log);
491 }
492