1 /*
2  *  This program is free software; you can redistribute it and/or modify
3  *  it under the terms of the GNU General Public License as published by
4  *  the Free Software Foundation; either version 2 of the License, or
5  *  (at your option) any later version.
6  *
7  *  This program is distributed in the hope that it will be useful,
8  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *  GNU General Public License for more details.
11  *
12  *  You should have received a copy of the GNU General Public License
13  *  along with this program; if not, write to the Free Software
14  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
15  */
16 
17 #include "uqmlog.h"
18 #include "loginternal.h"
19 #include "msgbox.h"
20 #include <string.h>
21 #include <stdio.h>
22 #include <stdarg.h>
23 #include <stdlib.h>
24 #include <signal.h>
25 #include <errno.h>
26 #include "libs/threadlib.h"
27 
28 #ifndef MAX_LOG_ENTRY_SIZE
29 #	define MAX_LOG_ENTRY_SIZE 256
30 #endif
31 
32 #ifndef MAX_LOG_ENTRIES
33 #	define MAX_LOG_ENTRIES 128
34 #endif
35 
36 typedef char log_Entry[MAX_LOG_ENTRY_SIZE];
37 
38 // static buffers in case we run out of memory
39 static log_Entry queue[MAX_LOG_ENTRIES];
40 static log_Entry msgNoThread;
41 static char msgBuf[16384];
42 
43 static int maxLevel = log_Error;
44 static int maxStreamLevel = log_Debug;
45 static int maxDisp = 10;
46 static int qtotal = 0;
47 static int qhead = 0;
48 static int qtail = 0;
49 static volatile bool noThreadReady = false;
50 static bool showBox = true;
51 static bool errorBox = true;
52 
53 FILE *streamOut;
54 
55 static volatile int qlock = 0;
56 static Mutex qmutex;
57 
58 static void exitCallback (void);
59 static void displayLog (bool isError);
60 
61 static void
lockQueue(void)62 lockQueue (void)
63 {
64 	if (!qlock)
65 		return;
66 
67 	LockMutex (qmutex);
68 }
69 
70 static void
unlockQueue(void)71 unlockQueue (void)
72 {
73 	if (!qlock)
74 		return;
75 
76 	UnlockMutex (qmutex);
77 }
78 
79 static void
removeExcess(int room)80 removeExcess (int room)
81 {
82 	room = maxDisp - room;
83 	if (room < 0)
84 		room = 0;
85 
86 	for ( ; qtotal > room; --qtotal, ++qtail)
87 		;
88 	qtail %= MAX_LOG_ENTRIES;
89 }
90 
91 static int
acquireSlot(void)92 acquireSlot (void)
93 {
94 	int slot;
95 
96 	lockQueue ();
97 
98 	removeExcess (1);
99 	slot = qhead;
100 	qhead = (qhead + 1) % MAX_LOG_ENTRIES;
101 	++qtotal;
102 
103 	unlockQueue ();
104 
105 	return slot;
106 }
107 
108 // queues the non-threaded message when present
109 static void
queueNonThreaded(void)110 queueNonThreaded (void)
111 {
112 	int slot;
113 
114 	// This is not perfect. A race condition still exists
115 	// between buffering the no-thread message and setting
116 	// the noThreadReady flag. Neither does this prevent
117 	// the fully or partially overwritten message (by
118 	// another competing thread). But it is 'good enough'
119 	if (!noThreadReady)
120 		return;
121 	noThreadReady = false;
122 
123 	slot = acquireSlot ();
124 	memcpy (queue[slot], msgNoThread, sizeof (msgNoThread));
125 }
126 
127 void
log_init(int max_lines)128 log_init (int max_lines)
129 {
130 	int i;
131 
132 	maxDisp = max_lines;
133 	streamOut = stderr;
134 
135 	// pre-term queue strings
136 	for (i = 0; i < MAX_LOG_ENTRIES; ++i)
137 		queue[i][MAX_LOG_ENTRY_SIZE - 1] = '\0';
138 
139 	msgBuf[sizeof (msgBuf) - 1] = '\0';
140 	msgNoThread[sizeof (msgNoThread) - 1] = '\0';
141 
142 	// install exit handlers
143 	atexit (exitCallback);
144 }
145 
146 void
log_initThreads(void)147 log_initThreads (void)
148 {
149 	qmutex = CreateMutex ("Logging Lock", SYNC_CLASS_RESOURCE);
150 	qlock = 1;
151 }
152 
153 int
log_exit(int code)154 log_exit (int code)
155 {
156 	showBox = false;
157 
158 	if (qlock)
159 	{
160 		qlock = 0;
161 		DestroyMutex (qmutex);
162 		qmutex = 0;
163 	}
164 
165 	return code;
166 }
167 
168 void
log_setLevel(int level)169 log_setLevel (int level)
170 {
171 	maxLevel = level;
172 	//maxStreamLevel = level;
173 }
174 
175 FILE *
log_setOutput(FILE * out)176 log_setOutput (FILE *out)
177 {
178 	FILE *old = streamOut;
179 	streamOut = out;
180 
181 	return old;
182 }
183 
184 void
log_addV(log_Level level,const char * fmt,va_list list)185 log_addV (log_Level level, const char *fmt, va_list list)
186 {
187 	log_Entry full_msg;
188 	vsnprintf (full_msg, sizeof (full_msg) - 1, fmt, list);
189 	full_msg[sizeof (full_msg) - 1] = '\0';
190 
191 	if ((int)level <= maxStreamLevel)
192 	{
193 		fprintf (streamOut, "%s\n", full_msg);
194 	}
195 
196 	if ((int)level <= maxLevel)
197 	{
198 		int slot;
199 
200 		queueNonThreaded ();
201 
202 		slot = acquireSlot ();
203 		memcpy (queue[slot], full_msg, sizeof (queue[0]));
204 	}
205 }
206 
207 void
log_add(log_Level level,const char * fmt,...)208 log_add (log_Level level, const char *fmt, ...)
209 {
210 	va_list list;
211 
212 	va_start (list, fmt);
213 	log_addV (level, fmt, list);
214 	va_end (list);
215 }
216 
217 // non-threaded version of 'add'
218 // uses single-instance static storage with entry into the
219 // queue delayed until the next threaded 'add' or 'exit'
220 void
log_add_nothreadV(log_Level level,const char * fmt,va_list list)221 log_add_nothreadV (log_Level level, const char *fmt, va_list list)
222 {
223 	log_Entry full_msg;
224 	vsnprintf (full_msg, sizeof (full_msg) - 1, fmt, list);
225 	full_msg[sizeof (full_msg) - 1] = '\0';
226 
227 	if ((int)level <= maxStreamLevel)
228 	{
229 		fprintf (streamOut, "%s\n", full_msg);
230 	}
231 
232 	if ((int)level <= maxLevel)
233 	{
234 		memcpy (msgNoThread, full_msg, sizeof (msgNoThread));
235 		noThreadReady = true;
236 	}
237 }
238 
239 void
log_add_nothread(log_Level level,const char * fmt,...)240 log_add_nothread (log_Level level, const char *fmt, ...)
241 {
242 	va_list list;
243 
244 	va_start (list, fmt);
245 	log_add_nothreadV (level, fmt, list);
246 	va_end (list);
247 }
248 
249 void
log_showBox(bool show,bool err)250 log_showBox (bool show, bool err)
251 {
252 	showBox = show;
253 	errorBox = err;
254 }
255 
256 // sets the maximum log lines captured for the final
257 // display to the user on failure exit
258 void
log_captureLines(int num)259 log_captureLines (int num)
260 {
261 	if (num > MAX_LOG_ENTRIES)
262 		num = MAX_LOG_ENTRIES;
263 	if (num < 1)
264 		num = 1;
265 	maxDisp = num;
266 
267 	// remove any extra lines already on queue
268 	lockQueue ();
269 	removeExcess (0);
270 	unlockQueue ();
271 }
272 
273 static void
exitCallback(void)274 exitCallback (void)
275 {
276 	if (showBox)
277 		displayLog (errorBox);
278 
279 	log_exit (0);
280 }
281 
282 static void
displayLog(bool isError)283 displayLog (bool isError)
284 {
285 	char *p = msgBuf;
286 	int left = sizeof (msgBuf) - 1;
287 	int len;
288 	int ptr;
289 
290 	if (isError)
291 	{
292 		strcpy (p, "The Ur-Quan Masters encountered a fatal error.\n"
293 				"Part of the log follows:\n\n");
294 		len = strlen (p);
295 		p += len;
296 		left -= len;
297 	}
298 
299 	// Glue the log entries together
300 	// Locking is not a good idea at this point and we do not
301 	// really need it -- the worst that can happen is we get
302 	// an extra or an incomplete message
303 	for (ptr = qtail; ptr != qhead && left > 0;
304 			ptr = (ptr + 1) % MAX_LOG_ENTRIES)
305 	{
306 		len = strlen (queue[ptr]) + 1;
307 		if (len > left)
308 			len = left;
309 		memcpy (p, queue[ptr], len);
310 		p[len - 1] = '\n';
311 		p += len;
312 		left -= len;
313 	}
314 
315 	// Glue the non-threaded message if present
316 	if (noThreadReady)
317 	{
318 		noThreadReady = false;
319 		len = strlen (msgNoThread);
320 		if (len > left)
321 			len = left;
322 		memcpy (p, msgNoThread, len);
323 		p += len;
324 		left -= len;
325 	}
326 
327 	*p = '\0';
328 
329 	log_displayBox ("The Ur-Quan Masters", isError, msgBuf);
330 }
331 
332