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