1 /*
2  * MOC - music on console
3  * Copyright (C) 2004 Damian Pietras <daper@daper.net>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  */
11 
12 #ifdef HAVE_CONFIG_H
13 # include "config.h"
14 #endif
15 
16 #include <stdio.h>
17 #include <stdarg.h>
18 #include <stdint.h>
19 #include <pthread.h>
20 #include <sys/time.h>
21 #include <time.h>
22 #include <errno.h>
23 #include <signal.h>
24 
25 #include "common.h"
26 #include "lists.h"
27 #include "log.h"
28 
29 static FILE *logfp = NULL; /* logging file stream */
30 static enum {
31 	UNINITIALISED,
32 	BUFFERING,
33 	LOGGING
34 } logging_state = UNINITIALISED;
35 static lists_t_strs *buffered_log = NULL;
36 static int log_records_spilt = 0;
37 
38 static pthread_mutex_t logging_mutex = PTHREAD_MUTEX_INITIALIZER;
39 
40 #ifndef NDEBUG
41 static struct {
42 	int sig;
43 	const char *name;
44 	volatile uint64_t raised;
45 	uint64_t logged;
46 } sig_info[] = {
47 	{SIGINT, "SIGINT", 0, 0},
48 	{SIGHUP, "SIGHUP", 0, 0},
49 	{SIGQUIT, "SIGQUIT", 0, 0},
50 	{SIGTERM, "SIGTERM", 0, 0},
51 	{SIGCHLD, "SIGCHLD", 0, 0},
52 	{SIGWINCH, "SIGWINCH", 0, 0},
53 	{0, "SIG other", 0, 0}
54 };
55 #endif
56 
57 #ifndef NDEBUG
log_signal(int sig)58 void log_signal (int sig)
59 {
60 	int ix = 0;
61 
62 	while (sig_info[ix].sig && sig_info[ix].sig != sig)
63 		ix += 1;
64 
65 	sig_info[ix].raised += 1;
66 }
67 #endif
68 
flush_log(void)69 static inline void flush_log (void)
70 {
71 	int rc;
72 
73 	if (logfp) {
74 		do {
75 			rc = fflush (logfp);
76 		} while (rc != 0 && errno == EINTR);
77 	}
78 }
79 
locked_logit(const char * file,const int line,const char * function,const char * msg)80 static void locked_logit (const char *file, const int line,
81                           const char *function, const char *msg)
82 {
83 	char time_str[20];
84 	struct timeval utc_time;
85 	time_t tv_sec;
86 	struct tm tm_time;
87 	const char fmt[] = "%s.%06u: %s:%d %s(): %s\n";
88 
89 	gettimeofday (&utc_time, NULL);
90 	tv_sec = utc_time.tv_sec;
91 	localtime_r (&tv_sec, &tm_time);
92 	strftime (time_str, sizeof (time_str), "%b %e %T", &tm_time);
93 
94 	if (logfp) {
95 		fprintf (logfp, fmt, time_str, (unsigned)utc_time.tv_usec,
96 		                     file, line, function, msg);
97 	}
98 	else if (logging_state == BUFFERING) {
99 		int len;
100 		char *str;
101 
102 		len = snprintf (NULL, 0, fmt, time_str, (unsigned)utc_time.tv_usec,
103 		                              file, line, function, msg);
104 		str = xmalloc (len + 1);
105 		snprintf (str, len + 1, fmt, time_str, (unsigned)utc_time.tv_usec,
106 		                             file, line, function, msg);
107 
108 		lists_strs_push (buffered_log, str);
109 	}
110 }
111 
log_signals_raised(void)112 static void log_signals_raised (void)
113 {
114 #ifndef NDEBUG
115 	size_t ix;
116 
117     for (ix = 0; ix < ARRAY_SIZE(sig_info); ix += 1) {
118 		while (sig_info[ix].raised > sig_info[ix].logged) {
119 			locked_logit (__FILE__, __LINE__, __FUNCTION__, sig_info[ix].name);
120 			sig_info[ix].logged += 1;
121 		}
122 	}
123 #endif
124 }
125 
126 /* Put something into the log */
internal_logit(const char * file,const int line,const char * function,const char * format,...)127 void internal_logit (const char *file, const int line, const char *function,
128 		const char *format, ...)
129 {
130 	char *msg;
131 	va_list va;
132 
133 	LOCK(logging_mutex);
134 
135 	if (!logfp) {
136 		switch (logging_state) {
137 		case UNINITIALISED:
138 			buffered_log = lists_strs_new (128);
139 			logging_state = BUFFERING;
140 			break;
141 		case BUFFERING:
142 			/* Don't let storage run away on us. */
143 			if (lists_strs_size (buffered_log) < lists_strs_capacity (buffered_log))
144 				break;
145 			log_records_spilt += 1;
146 		case LOGGING:
147 			goto end;
148 		}
149 	}
150 
151 	log_signals_raised ();
152 
153 	va_start (va, format);
154 	msg = format_msg_va (format, va);
155 	va_end (va);
156 	locked_logit (file, line, function, msg);
157 	free (msg);
158 
159 	flush_log ();
160 
161 	log_signals_raised ();
162 
163 end:
164 	UNLOCK(logging_mutex);
165 }
166 
167 /* fake logit() function for NDEBUG */
fake_logit(const char * format ATTR_UNUSED,...)168 void fake_logit (const char *format ATTR_UNUSED, ...)
169 {
170 }
171 
172 /* Initialize logging stream */
log_init_stream(FILE * f,const char * fn)173 void log_init_stream (FILE *f, const char *fn)
174 {
175 	char *msg;
176 
177 	LOCK(logging_mutex);
178 
179 	logfp = f;
180 
181 	if (logging_state == BUFFERING) {
182 		if (logfp) {
183 			int ix;
184 
185 			for (ix = 0; ix < lists_strs_size (buffered_log); ix += 1)
186 				fprintf (logfp, "%s", lists_strs_at (buffered_log, ix));
187 		}
188 		lists_strs_free (buffered_log);
189 		buffered_log = NULL;
190 	}
191 
192 	logging_state = LOGGING;
193 
194 	msg = format_msg ("Writing log to: %s", fn);
195 	locked_logit (__FILE__, __LINE__, __FUNCTION__, msg);
196 	free (msg);
197 
198 	if (log_records_spilt > 0) {
199 		msg = format_msg ("%d log records spilt", log_records_spilt);
200 		locked_logit (__FILE__, __LINE__, __FUNCTION__, msg);
201 		free (msg);
202 	}
203 
204 	flush_log ();
205 
206 	UNLOCK(logging_mutex);
207 }
208 
log_close()209 void log_close ()
210 {
211 	LOCK(logging_mutex);
212 	if (!(logfp == stdout || logfp == stderr || logfp == NULL)) {
213 		fclose (logfp);
214 		logfp = NULL;
215 	}
216 	if (buffered_log) {
217 		lists_strs_free (buffered_log);
218 		buffered_log = NULL;
219 	}
220 	log_records_spilt = 0;
221 	UNLOCK(logging_mutex);
222 }
223