1 /*
2   Copyright 2020 Northern.tech AS
3 
4   This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5 
6   This program is free software; you can redistribute it and/or modify it
7   under the terms of the GNU General Public License as published by the
8   Free Software Foundation; version 3.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
18 
19   To the extent this program is licensed as part of the Enterprise
20   versions of CFEngine, the applicable Commercial Open Source License
21   (COSL) may apply to this file if you as a licensee so wish it. See
22   included file COSL.txt.
23 */
24 
25 #include <logging.h>
26 #include <alloc.h>
27 #include <string_lib.h>
28 #include <misc_lib.h>
29 #include <cleanup.h>
30 
31 #ifdef __MINGW32__
32 #include <definitions.h>        /* CF_BUFSIZE */
33 #endif
34 
35 char VPREFIX[1024] = ""; /* GLOBAL_C */
36 
37 static char AgentType[80] = "generic";
38 static bool TIMESTAMPS = false;
39 
40 static LogLevel global_level = LOG_LEVEL_NOTICE; /* GLOBAL_X */
41 
42 static pthread_once_t log_context_init_once = PTHREAD_ONCE_INIT; /* GLOBAL_T */
43 static pthread_key_t log_context_key; /* GLOBAL_T, initialized by pthread_key_create */
44 
LoggingInitializeOnce(void)45 static void LoggingInitializeOnce(void)
46 {
47     if (pthread_key_create(&log_context_key, &free) != 0)
48     {
49         /* There is no way to signal error out of pthread_once callback.
50          * However if pthread_key_create fails we are pretty much guaranteed
51          * that nothing else will work. */
52 
53         fprintf(stderr, "Unable to initialize logging subsystem\n");
54         DoCleanupAndExit(255);
55     }
56 }
57 
GetCurrentThreadContext(void)58 LoggingContext *GetCurrentThreadContext(void)
59 {
60     pthread_once(&log_context_init_once, &LoggingInitializeOnce);
61     LoggingContext *lctx = pthread_getspecific(log_context_key);
62     if (lctx == NULL)
63     {
64         lctx = xcalloc(1, sizeof(LoggingContext));
65         lctx->log_level = global_level;
66         lctx->report_level = global_level;
67         pthread_setspecific(log_context_key, lctx);
68     }
69     return lctx;
70 }
71 
LoggingFreeCurrentThreadContext(void)72 void LoggingFreeCurrentThreadContext(void)
73 {
74     pthread_once(&log_context_init_once, &LoggingInitializeOnce);
75     LoggingContext *lctx = pthread_getspecific(log_context_key);
76     if (lctx == NULL)
77     {
78         return;
79     }
80     // lctx->pctx is usually stack allocated and shouldn't be freed
81     FREE_AND_NULL(lctx);
82     pthread_setspecific(log_context_key, NULL);
83 }
84 
LoggingSetAgentType(const char * type)85 void LoggingSetAgentType(const char *type)
86 {
87     strlcpy(AgentType, type, sizeof(AgentType));
88 }
89 
LoggingEnableTimestamps(bool enable)90 void LoggingEnableTimestamps(bool enable)
91 {
92     TIMESTAMPS = enable;
93 }
94 
LoggingPrivSetContext(LoggingPrivContext * pctx)95 void LoggingPrivSetContext(LoggingPrivContext *pctx)
96 {
97     LoggingContext *lctx = GetCurrentThreadContext();
98     lctx->pctx = pctx;
99 }
100 
LoggingPrivGetContext(void)101 LoggingPrivContext *LoggingPrivGetContext(void)
102 {
103     LoggingContext *lctx = GetCurrentThreadContext();
104     return lctx->pctx;
105 }
106 
LoggingPrivSetLevels(LogLevel log_level,LogLevel report_level)107 void LoggingPrivSetLevels(LogLevel log_level, LogLevel report_level)
108 {
109     LoggingContext *lctx = GetCurrentThreadContext();
110     lctx->log_level = log_level;
111     lctx->report_level = report_level;
112 }
113 
LogLevelToString(LogLevel level)114 const char *LogLevelToString(LogLevel level)
115 {
116     switch (level)
117     {
118     case LOG_LEVEL_CRIT:
119         return "CRITICAL";
120     case LOG_LEVEL_ERR:
121         return "error";
122     case LOG_LEVEL_WARNING:
123         return "warning";
124     case LOG_LEVEL_NOTICE:
125         return "notice";
126     case LOG_LEVEL_INFO:
127         return "info";
128     case LOG_LEVEL_VERBOSE:
129         return "verbose";
130     case LOG_LEVEL_DEBUG:
131         return "debug";
132     default:
133         ProgrammingError("LogLevelToString: Unexpected log level %d", level);
134     }
135 }
136 
LogLevelFromString(const char * const level)137 LogLevel LogLevelFromString(const char *const level)
138 {
139     // Only compare the part the user typed
140     // i/info/inform/information will all result in LOG_LEVEL_INFO
141     size_t len = SafeStringLength(level);
142     if (len == 0)
143     {
144         return LOG_LEVEL_NOTHING;
145     }
146     if (StringEqualN_IgnoreCase(level, "CRITICAL", len))
147     {
148         return LOG_LEVEL_CRIT;
149     }
150     if (StringEqualN_IgnoreCase(level, "errors", len))
151     {
152         return LOG_LEVEL_ERR;
153     }
154     if (StringEqualN_IgnoreCase(level, "warnings", len))
155     {
156         return LOG_LEVEL_WARNING;
157     }
158     if (StringEqualN_IgnoreCase(level, "notices", len))
159     {
160         return LOG_LEVEL_NOTICE;
161     }
162     if (StringEqualN_IgnoreCase(level, "information", len))
163     {
164         return LOG_LEVEL_INFO;
165     }
166     if (StringEqualN_IgnoreCase(level, "verbose", len))
167     {
168         return LOG_LEVEL_VERBOSE;
169     }
170     if (StringEqualN_IgnoreCase(level, "debug", len))
171     {
172         return LOG_LEVEL_DEBUG;
173     }
174     return LOG_LEVEL_NOTHING;
175 }
176 
LogLevelToColor(LogLevel level)177 static const char *LogLevelToColor(LogLevel level)
178 {
179 
180     switch (level)
181     {
182     case LOG_LEVEL_CRIT:
183     case LOG_LEVEL_ERR:
184         return "\x1b[31m"; // red
185 
186     case LOG_LEVEL_WARNING:
187         return "\x1b[33m"; // yellow
188 
189     case LOG_LEVEL_NOTICE:
190     case LOG_LEVEL_INFO:
191         return "\x1b[32m"; // green
192 
193     case LOG_LEVEL_VERBOSE:
194     case LOG_LEVEL_DEBUG:
195         return "\x1b[34m"; // blue
196 
197     default:
198         ProgrammingError("LogLevelToColor: Unexpected log level %d", level);
199     }
200 }
201 
LoggingFormatTimestamp(char dest[64],size_t n,struct tm * timestamp)202 bool LoggingFormatTimestamp(char dest[64], size_t n, struct tm *timestamp)
203 {
204     if (strftime(dest, n, "%Y-%m-%dT%H:%M:%S%z", timestamp) == 0)
205     {
206         strlcpy(dest, "<unknown>", n);
207         return false;
208     }
209     return true;
210 }
211 
LogToConsole(const char * msg,LogLevel level,bool color)212 static void LogToConsole(const char *msg, LogLevel level, bool color)
213 {
214     struct tm now;
215     time_t now_seconds = time(NULL);
216     localtime_r(&now_seconds, &now);
217 
218     if (color)
219     {
220         fprintf(stdout, "%s", LogLevelToColor(level));
221     }
222     if (level >= LOG_LEVEL_INFO && VPREFIX[0])
223     {
224         fprintf(stdout, "%s ", VPREFIX);
225     }
226     if (TIMESTAMPS)
227     {
228         char formatted_timestamp[64];
229         LoggingFormatTimestamp(formatted_timestamp, 64, &now);
230         fprintf(stdout, "%s ", formatted_timestamp);
231     }
232 
233     fprintf(stdout, "%8s: %s\n", LogLevelToString(level), msg);
234 
235     if (color)
236     {
237         // Turn off the color again.
238         fprintf(stdout, "\x1b[0m");
239     }
240 
241     fflush(stdout);
242 }
243 
244 #if !defined(__MINGW32__)
LogLevelToSyslogPriority(LogLevel level)245 static int LogLevelToSyslogPriority(LogLevel level)
246 {
247     switch (level)
248     {
249     case LOG_LEVEL_CRIT: return LOG_CRIT;
250     case LOG_LEVEL_ERR: return LOG_ERR;
251     case LOG_LEVEL_WARNING: return LOG_WARNING;
252     case LOG_LEVEL_NOTICE: return LOG_NOTICE;
253     case LOG_LEVEL_INFO: return LOG_INFO;
254     case LOG_LEVEL_VERBOSE: return LOG_DEBUG; /* FIXME: Do we really want to conflate those levels? */
255     case LOG_LEVEL_DEBUG: return LOG_DEBUG;
256     default:
257         ProgrammingError("LogLevelToSyslogPriority: Unexpected log level %d",
258                          level);
259     }
260 
261 }
262 
LogToSystemLog(const char * msg,LogLevel level)263 void LogToSystemLog(const char *msg, LogLevel level)
264 {
265     char logmsg[4096];
266     snprintf(logmsg, sizeof(logmsg), "CFEngine(%s) %s %s\n", AgentType, VPREFIX, msg);
267     syslog(LogLevelToSyslogPriority(level), "%s", logmsg);
268 }
269 #endif  /* !__MINGW32__ */
270 
271 #ifndef __MINGW32__
GetErrorStrFromCode(int error_code)272 const char *GetErrorStrFromCode(int error_code)
273 {
274     return strerror(error_code);
275 }
276 
GetErrorStr(void)277 const char *GetErrorStr(void)
278 {
279     return strerror(errno);
280 }
281 
282 #else
283 
GetErrorStrFromCode(int error_code)284 const char *GetErrorStrFromCode(int error_code)
285 {
286     static char errbuf[CF_BUFSIZE];
287     int len;
288 
289     memset(errbuf, 0, sizeof(errbuf));
290 
291     if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, error_code,
292                   MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), errbuf, CF_BUFSIZE, NULL))
293     {
294         // remove CRLF from end
295         len = strlen(errbuf);
296         errbuf[len - 2] = errbuf[len - 1] = '\0';
297     } else {
298         strcpy(errbuf, "Unknown error");
299     }
300 
301     return errbuf;
302 }
303 
GetErrorStr(void)304 const char *GetErrorStr(void)
305 {
306     return GetErrorStrFromCode(GetLastError());
307 }
308 #endif  /* !__MINGW32__ */
309 
WouldLog(LogLevel level)310 bool WouldLog(LogLevel level)
311 {
312     LoggingContext *lctx = GetCurrentThreadContext();
313 
314     bool log_to_console = (level <= lctx->report_level);
315     bool log_to_syslog  = (level <= lctx->log_level &&
316                            level < LOG_LEVEL_VERBOSE);
317     bool force_hook     = (lctx->pctx &&
318                            lctx->pctx->log_hook &&
319                            lctx->pctx->force_hook_level >= level);
320 
321     return (log_to_console || log_to_syslog || force_hook);
322 }
323 
VLog(LogLevel level,const char * fmt,va_list ap)324 void VLog(LogLevel level, const char *fmt, va_list ap)
325 {
326     LoggingContext *lctx = GetCurrentThreadContext();
327 
328     bool log_to_console = ( level <= lctx->report_level );
329     bool log_to_syslog  = ( level <= lctx->log_level &&
330                             level < LOG_LEVEL_VERBOSE );
331     bool force_hook     = ( lctx->pctx &&
332                             lctx->pctx->log_hook &&
333                             lctx->pctx->force_hook_level >= level );
334 
335     /* NEEDS TO BE IN SYNC WITH THE CONDITION IN WouldLog() ABOVE! */
336     if (!log_to_console && !log_to_syslog && !force_hook)
337     {
338         return;                            /* early return - save resources */
339     }
340 
341     char *msg = StringVFormat(fmt, ap);
342     char *hooked_msg = NULL;
343 
344     /* Remove ending EOLN. */
345     for (char *sp = msg; *sp != '\0'; sp++)
346     {
347         if (*sp == '\n' && *(sp+1) == '\0')
348         {
349             *sp = '\0';
350             break;
351         }
352     }
353 
354     if (lctx->pctx && lctx->pctx->log_hook)
355     {
356         hooked_msg = lctx->pctx->log_hook(lctx->pctx, level, msg);
357     }
358     else
359     {
360         hooked_msg = msg;
361     }
362 
363     if (log_to_console)
364     {
365         LogToConsole(hooked_msg, level, lctx->color);
366     }
367     if (log_to_syslog)
368     {
369         LogToSystemLog(hooked_msg, level);
370     }
371 
372     if (hooked_msg != msg)
373     {
374         free(hooked_msg);
375     }
376     free(msg);
377 }
378 
379 /* TODO create libutils/defs.h. */
380 #define CF_MAX_BUFSIZE 16384
381 
382 /**
383  * @brief Logs binary data in #buf, with unprintable bytes translated to '.'.
384  *        Message is prefixed with #prefix.
385  * @param #buflen must be no more than CF_MAX_BUFSIZE
386  */
LogRaw(LogLevel level,const char * prefix,const void * buf,size_t buflen)387 void LogRaw(LogLevel level, const char *prefix, const void *buf, size_t buflen)
388 {
389     const unsigned char *src = buf;
390     unsigned char dst[buflen+1];
391     assert(sizeof(dst) <= CF_MAX_BUFSIZE);
392 
393     LoggingContext *lctx = GetCurrentThreadContext();
394     if (level <= lctx->report_level || level <= lctx->log_level)
395     {
396         size_t i;
397         for (i = 0; i < buflen; i++)
398         {
399             dst[i] = isprint(src[i]) ? src[i] : '.';
400         }
401         dst[i] = '\0';
402 
403         /* And Log the translated buffer, which is now a valid string. */
404         Log(level, "%s%s", prefix, dst);
405     }
406 }
407 
Log(LogLevel level,const char * fmt,...)408 void Log(LogLevel level, const char *fmt, ...)
409 {
410     va_list ap;
411     va_start(ap, fmt);
412     VLog(level, fmt, ap);
413     va_end(ap);
414 }
415 
416 
417 
418 static bool module_is_enabled[LOG_MOD_MAX];
419 static const char *log_modules[LOG_MOD_MAX] =
420 {
421     "",
422     "evalctx",
423     "expand",
424     "iterations",
425     "parser",
426     "vartable",
427     "vars",
428     "locks",
429     "ps",
430 };
431 
LogModuleFromString(const char * s)432 static enum LogModule LogModuleFromString(const char *s)
433 {
434     for (enum LogModule i = 0; i < LOG_MOD_MAX; i++)
435     {
436         if (strcmp(log_modules[i], s) == 0)
437         {
438             return i;
439         }
440     }
441 
442     return LOG_MOD_NONE;
443 }
444 
LogEnableModule(enum LogModule mod)445 void LogEnableModule(enum LogModule mod)
446 {
447     assert(mod < LOG_MOD_MAX);
448 
449     module_is_enabled[mod] = true;
450 }
451 
LogModuleHelp(void)452 void LogModuleHelp(void)
453 {
454     printf("\n--log-modules accepts a comma separated list of one or more of the following:\n\n");
455     printf("    help\n");
456     printf("    all\n");
457     for (enum LogModule i = LOG_MOD_NONE + 1;  i < LOG_MOD_MAX;  i++)
458     {
459         printf("    %s\n", log_modules[i]);
460     }
461     printf("\n");
462 }
463 
464 /**
465  * Parse a string of modules, and enable the relevant DEBUG logging modules.
466  * Example strings:
467  *
468  *   all         : enables all debug modules
469  *   help        : enables nothing, but prints a help message
470  *   iterctx     : enables the "iterctx" debug logging module
471  *   iterctx,vars: enables the 2 debug modules, "iterctx" and "vars"
472  *
473  * @NOTE modifies string #s but restores it before returning.
474  */
LogEnableModulesFromString(char * s)475 bool LogEnableModulesFromString(char *s)
476 {
477     bool retval = true;
478 
479     const char *token = s;
480     char saved_sep = ',';                     /* any non-NULL value will do */
481     while (saved_sep != '\0' && retval != false)
482     {
483         char *next_token = strchrnul(token, ',');
484         saved_sep        = *next_token;
485         *next_token      = '\0';                      /* modify parameter s */
486         size_t token_len = next_token - token;
487 
488         if (strcmp(token, "help") == 0)
489         {
490             LogModuleHelp();
491             retval = false;                                   /* early exit */
492         }
493         else if (strcmp(token, "all") == 0)
494         {
495             for (enum LogModule j = LOG_MOD_NONE + 1; j < LOG_MOD_MAX; j++)
496             {
497                 LogEnableModule(j);
498             }
499         }
500         else
501         {
502             enum LogModule mod = LogModuleFromString(token);
503 
504             assert(mod < LOG_MOD_MAX);
505             if (mod == LOG_MOD_NONE)
506             {
507                 Log(LOG_LEVEL_WARNING,
508                     "Unknown debug logging module '%*s'",
509                     (int) token_len, token);
510             }
511             else
512             {
513                 LogEnableModule(mod);
514             }
515         }
516 
517 
518         *next_token = saved_sep;            /* restore modified parameter s */
519         next_token++;                       /* bypass comma */
520         token = next_token;
521     }
522 
523     return retval;
524 }
525 
LogModuleEnabled(enum LogModule mod)526 bool LogModuleEnabled(enum LogModule mod)
527 {
528     assert(mod > LOG_MOD_NONE);
529     assert(mod < LOG_MOD_MAX);
530 
531     if (module_is_enabled[mod])
532     {
533         return true;
534     }
535     else
536     {
537         return false;
538     }
539 }
540 
LogDebug(enum LogModule mod,const char * fmt,...)541 void LogDebug(enum LogModule mod, const char *fmt, ...)
542 {
543     assert(mod < LOG_MOD_MAX);
544 
545     /* Did we forget any entry in log_modules? Should be a static assert. */
546     assert(sizeof(log_modules) / sizeof(log_modules[0]) == LOG_MOD_MAX);
547 
548     if (LogModuleEnabled(mod))
549     {
550         va_list ap;
551         va_start(ap, fmt);
552         VLog(LOG_LEVEL_DEBUG, fmt, ap);
553         va_end(ap);
554         /* VLog(LOG_LEVEL_DEBUG, "%s: ...", */
555         /*      debug_modules_description[mod_order], ...); */
556     }
557 }
558 
559 
LogSetGlobalLevel(LogLevel level)560 void LogSetGlobalLevel(LogLevel level)
561 {
562     global_level = level;
563     LoggingPrivSetLevels(level, level);
564 }
565 
LogSetGlobalLevelArgOrExit(const char * const arg)566 void LogSetGlobalLevelArgOrExit(const char *const arg)
567 {
568     LogLevel level = LogLevelFromString(arg);
569     if (level == LOG_LEVEL_NOTHING)
570     {
571         // This function is used as part of initializing the logging
572         // system. Using Log() can be considered incorrect, even though
573         // it may "work". Let's just print an error to stderr:
574         fprintf(stderr, "Invalid log level: '%s'\n", arg);
575         DoCleanupAndExit(1);
576     }
577     LogSetGlobalLevel(level);
578 }
579 
LogGetGlobalLevel(void)580 LogLevel LogGetGlobalLevel(void)
581 {
582     return global_level;
583 }
584 
LoggingSetColor(bool enabled)585 void LoggingSetColor(bool enabled)
586 {
587     LoggingContext *lctx = GetCurrentThreadContext();
588     lctx->color = enabled;
589 }
590 
591 // byte_magnitude and byte_unit are used to print human readable byte counts
byte_magnitude(long bytes)592 long byte_magnitude(long bytes)
593 {
594     const long Ki = 1024;
595     const long Mi = Ki * 1024;
596     const long Gi = Mi * 1024;
597 
598     if (bytes > 8 * Gi)
599     {
600         return bytes / Gi;
601     }
602     else if (bytes > 8 * Mi)
603     {
604         return bytes / Mi;
605     }
606     else if (bytes > 8 * Ki)
607     {
608         return bytes / Ki;
609     }
610     return bytes;
611 }
612 
613 // Use this with byte_magnitude
614 // Note that the cutoff is at 8x unit, because 3192 bytes is arguably more
615 // useful than 3KiB
byte_unit(long bytes)616 const char *byte_unit(long bytes)
617 {
618     const long Ki = 1024;
619     const long Mi = Ki * 1024;
620     const long Gi = Mi * 1024;
621 
622     if (bytes > 8 * Gi)
623     {
624         return "GiB";
625     }
626     else if (bytes > 8 * Mi)
627     {
628         return "MiB";
629     }
630     else if (bytes > 8 * Ki)
631     {
632         return "KiB";
633     }
634     return "bytes";
635 }
636