1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
4 
5   This software is provided 'as-is', without any express or implied
6   warranty.  In no event will the authors be held liable for any damages
7   arising from the use of this software.
8 
9   Permission is granted to anyone to use this software for any purpose,
10   including commercial applications, and to alter it and redistribute it
11   freely, subject to the following restrictions:
12 
13   1. The origin of this software must not be misrepresented; you must not
14      claim that you wrote the original software. If you use this software
15      in a product, an acknowledgment in the product documentation would be
16      appreciated but is not required.
17   2. Altered source versions must be plainly marked as such, and must not be
18      misrepresented as being the original software.
19   3. This notice may not be removed or altered from any source distribution.
20 */
21 #include "./SDL_internal.h"
22 
23 #if defined(__WIN32__) || defined(__WINRT__)
24 #include "core/windows/SDL_windows.h"
25 #endif
26 
27 /* Simple log messages in SDL */
28 
29 #include "SDL_error.h"
30 #include "SDL_log.h"
31 
32 #if HAVE_STDIO_H
33 #include <stdio.h>
34 #endif
35 
36 #if defined(__ANDROID__)
37 #include <android/log.h>
38 #endif
39 
40 #define DEFAULT_PRIORITY                SDL_LOG_PRIORITY_CRITICAL
41 #define DEFAULT_ASSERT_PRIORITY         SDL_LOG_PRIORITY_WARN
42 #define DEFAULT_APPLICATION_PRIORITY    SDL_LOG_PRIORITY_INFO
43 #define DEFAULT_TEST_PRIORITY           SDL_LOG_PRIORITY_VERBOSE
44 
45 typedef struct SDL_LogLevel
46 {
47     int category;
48     SDL_LogPriority priority;
49     struct SDL_LogLevel *next;
50 } SDL_LogLevel;
51 
52 /* The default log output function */
53 static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority priority, const char *message);
54 
55 static SDL_LogLevel *SDL_loglevels;
56 static SDL_LogPriority SDL_default_priority = DEFAULT_PRIORITY;
57 static SDL_LogPriority SDL_assert_priority = DEFAULT_ASSERT_PRIORITY;
58 static SDL_LogPriority SDL_application_priority = DEFAULT_APPLICATION_PRIORITY;
59 static SDL_LogPriority SDL_test_priority = DEFAULT_TEST_PRIORITY;
60 static SDL_LogOutputFunction SDL_log_function = SDL_LogOutput;
61 static void *SDL_log_userdata = NULL;
62 
63 static const char *SDL_priority_prefixes[SDL_NUM_LOG_PRIORITIES] = {
64     NULL,
65     "VERBOSE",
66     "DEBUG",
67     "INFO",
68     "WARN",
69     "ERROR",
70     "CRITICAL"
71 };
72 
73 #ifdef __ANDROID__
74 static const char *SDL_category_prefixes[SDL_LOG_CATEGORY_RESERVED1] = {
75     "APP",
76     "ERROR",
77     "SYSTEM",
78     "AUDIO",
79     "VIDEO",
80     "RENDER",
81     "INPUT"
82 };
83 
84 static int SDL_android_priority[SDL_NUM_LOG_PRIORITIES] = {
85     ANDROID_LOG_UNKNOWN,
86     ANDROID_LOG_VERBOSE,
87     ANDROID_LOG_DEBUG,
88     ANDROID_LOG_INFO,
89     ANDROID_LOG_WARN,
90     ANDROID_LOG_ERROR,
91     ANDROID_LOG_FATAL
92 };
93 #endif /* __ANDROID__ */
94 
95 
96 void
SDL_LogSetAllPriority(SDL_LogPriority priority)97 SDL_LogSetAllPriority(SDL_LogPriority priority)
98 {
99     SDL_LogLevel *entry;
100 
101     for (entry = SDL_loglevels; entry; entry = entry->next) {
102         entry->priority = priority;
103     }
104     SDL_default_priority = priority;
105     SDL_assert_priority = priority;
106     SDL_application_priority = priority;
107 }
108 
109 void
SDL_LogSetPriority(int category,SDL_LogPriority priority)110 SDL_LogSetPriority(int category, SDL_LogPriority priority)
111 {
112     SDL_LogLevel *entry;
113 
114     for (entry = SDL_loglevels; entry; entry = entry->next) {
115         if (entry->category == category) {
116             entry->priority = priority;
117             return;
118         }
119     }
120 
121     /* Create a new entry */
122     entry = (SDL_LogLevel *)SDL_malloc(sizeof(*entry));
123     if (entry) {
124         entry->category = category;
125         entry->priority = priority;
126         entry->next = SDL_loglevels;
127         SDL_loglevels = entry;
128     }
129 }
130 
131 SDL_LogPriority
SDL_LogGetPriority(int category)132 SDL_LogGetPriority(int category)
133 {
134     SDL_LogLevel *entry;
135 
136     for (entry = SDL_loglevels; entry; entry = entry->next) {
137         if (entry->category == category) {
138             return entry->priority;
139         }
140     }
141 
142     if (category == SDL_LOG_CATEGORY_TEST) {
143         return SDL_test_priority;
144     } else if (category == SDL_LOG_CATEGORY_APPLICATION) {
145         return SDL_application_priority;
146     } else if (category == SDL_LOG_CATEGORY_ASSERT) {
147         return SDL_assert_priority;
148     } else {
149         return SDL_default_priority;
150     }
151 }
152 
153 void
SDL_LogResetPriorities(void)154 SDL_LogResetPriorities(void)
155 {
156     SDL_LogLevel *entry;
157 
158     while (SDL_loglevels) {
159         entry = SDL_loglevels;
160         SDL_loglevels = entry->next;
161         SDL_free(entry);
162     }
163 
164     SDL_default_priority = DEFAULT_PRIORITY;
165     SDL_assert_priority = DEFAULT_ASSERT_PRIORITY;
166     SDL_application_priority = DEFAULT_APPLICATION_PRIORITY;
167     SDL_test_priority = DEFAULT_TEST_PRIORITY;
168 }
169 
170 void
SDL_Log(SDL_PRINTF_FORMAT_STRING const char * fmt,...)171 SDL_Log(SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
172 {
173     va_list ap;
174 
175     va_start(ap, fmt);
176     SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, fmt, ap);
177     va_end(ap);
178 }
179 
180 void
SDL_LogVerbose(int category,SDL_PRINTF_FORMAT_STRING const char * fmt,...)181 SDL_LogVerbose(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
182 {
183     va_list ap;
184 
185     va_start(ap, fmt);
186     SDL_LogMessageV(category, SDL_LOG_PRIORITY_VERBOSE, fmt, ap);
187     va_end(ap);
188 }
189 
190 void
SDL_LogDebug(int category,SDL_PRINTF_FORMAT_STRING const char * fmt,...)191 SDL_LogDebug(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
192 {
193     va_list ap;
194 
195     va_start(ap, fmt);
196     SDL_LogMessageV(category, SDL_LOG_PRIORITY_DEBUG, fmt, ap);
197     va_end(ap);
198 }
199 
200 void
SDL_LogInfo(int category,SDL_PRINTF_FORMAT_STRING const char * fmt,...)201 SDL_LogInfo(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
202 {
203     va_list ap;
204 
205     va_start(ap, fmt);
206     SDL_LogMessageV(category, SDL_LOG_PRIORITY_INFO, fmt, ap);
207     va_end(ap);
208 }
209 
210 void
SDL_LogWarn(int category,SDL_PRINTF_FORMAT_STRING const char * fmt,...)211 SDL_LogWarn(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
212 {
213     va_list ap;
214 
215     va_start(ap, fmt);
216     SDL_LogMessageV(category, SDL_LOG_PRIORITY_WARN, fmt, ap);
217     va_end(ap);
218 }
219 
220 void
SDL_LogError(int category,SDL_PRINTF_FORMAT_STRING const char * fmt,...)221 SDL_LogError(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
222 {
223     va_list ap;
224 
225     va_start(ap, fmt);
226     SDL_LogMessageV(category, SDL_LOG_PRIORITY_ERROR, fmt, ap);
227     va_end(ap);
228 }
229 
230 void
SDL_LogCritical(int category,SDL_PRINTF_FORMAT_STRING const char * fmt,...)231 SDL_LogCritical(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
232 {
233     va_list ap;
234 
235     va_start(ap, fmt);
236     SDL_LogMessageV(category, SDL_LOG_PRIORITY_CRITICAL, fmt, ap);
237     va_end(ap);
238 }
239 
240 void
SDL_LogMessage(int category,SDL_LogPriority priority,SDL_PRINTF_FORMAT_STRING const char * fmt,...)241 SDL_LogMessage(int category, SDL_LogPriority priority, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
242 {
243     va_list ap;
244 
245     va_start(ap, fmt);
246     SDL_LogMessageV(category, priority, fmt, ap);
247     va_end(ap);
248 }
249 
250 #ifdef __ANDROID__
251 static const char *
GetCategoryPrefix(int category)252 GetCategoryPrefix(int category)
253 {
254     if (category < SDL_LOG_CATEGORY_RESERVED1) {
255         return SDL_category_prefixes[category];
256     }
257     if (category < SDL_LOG_CATEGORY_CUSTOM) {
258         return "RESERVED";
259     }
260     return "CUSTOM";
261 }
262 #endif /* __ANDROID__ */
263 
264 void
SDL_LogMessageV(int category,SDL_LogPriority priority,const char * fmt,va_list ap)265 SDL_LogMessageV(int category, SDL_LogPriority priority, const char *fmt, va_list ap)
266 {
267     char *message;
268     size_t len;
269 
270     /* Nothing to do if we don't have an output function */
271     if (!SDL_log_function) {
272         return;
273     }
274 
275     /* Make sure we don't exceed array bounds */
276     if ((int)priority < 0 || priority >= SDL_NUM_LOG_PRIORITIES) {
277         return;
278     }
279 
280     /* See if we want to do anything with this message */
281     if (priority < SDL_LogGetPriority(category)) {
282         return;
283     }
284 
285     /* !!! FIXME: why not just "char message[SDL_MAX_LOG_MESSAGE];" ? */
286     message = SDL_stack_alloc(char, SDL_MAX_LOG_MESSAGE);
287     if (!message) {
288         return;
289     }
290 
291     SDL_vsnprintf(message, SDL_MAX_LOG_MESSAGE, fmt, ap);
292 
293     /* Chop off final endline. */
294     len = SDL_strlen(message);
295     if ((len > 0) && (message[len-1] == '\n')) {
296         message[--len] = '\0';
297         if ((len > 0) && (message[len-1] == '\r')) {  /* catch "\r\n", too. */
298             message[--len] = '\0';
299         }
300     }
301 
302     SDL_log_function(SDL_log_userdata, category, priority, message);
303     SDL_stack_free(message);
304 }
305 
306 #if defined(__WIN32__) && !defined(HAVE_STDIO_H) && !defined(__WINRT__)
307 /* Flag tracking the attachment of the console: 0=unattached, 1=attached to a console, 2=attached to a file, -1=error */
308 static int consoleAttached = 0;
309 
310 /* Handle to stderr output of console. */
311 static HANDLE stderrHandle = NULL;
312 #endif
313 
314 static void SDLCALL
SDL_LogOutput(void * userdata,int category,SDL_LogPriority priority,const char * message)315 SDL_LogOutput(void *userdata, int category, SDL_LogPriority priority,
316               const char *message)
317 {
318 #if defined(__WIN32__) || defined(__WINRT__)
319     /* Way too many allocations here, urgh */
320     /* Note: One can't call SDL_SetError here, since that function itself logs. */
321     {
322         char *output;
323         size_t length;
324         LPTSTR tstr;
325         SDL_bool isstack;
326 
327 #if !defined(HAVE_STDIO_H) && !defined(__WINRT__)
328         BOOL attachResult;
329         DWORD attachError;
330         DWORD charsWritten;
331         DWORD consoleMode;
332 
333         /* Maybe attach console and get stderr handle */
334         if (consoleAttached == 0) {
335             attachResult = AttachConsole(ATTACH_PARENT_PROCESS);
336             if (!attachResult) {
337                     attachError = GetLastError();
338                     if (attachError == ERROR_INVALID_HANDLE) {
339                         /* This is expected when running from Visual Studio */
340                         /*OutputDebugString(TEXT("Parent process has no console\r\n"));*/
341                         consoleAttached = -1;
342                     } else if (attachError == ERROR_GEN_FAILURE) {
343                          OutputDebugString(TEXT("Could not attach to console of parent process\r\n"));
344                          consoleAttached = -1;
345                     } else if (attachError == ERROR_ACCESS_DENIED) {
346                          /* Already attached */
347                         consoleAttached = 1;
348                     } else {
349                         OutputDebugString(TEXT("Error attaching console\r\n"));
350                         consoleAttached = -1;
351                     }
352                 } else {
353                     /* Newly attached */
354                     consoleAttached = 1;
355                 }
356 
357                 if (consoleAttached == 1) {
358                         stderrHandle = GetStdHandle(STD_ERROR_HANDLE);
359 
360                         if (GetConsoleMode(stderrHandle, &consoleMode) == 0) {
361                             /* WriteConsole fails if the output is redirected to a file. Must use WriteFile instead. */
362                             consoleAttached = 2;
363                         }
364                 }
365         }
366 #endif /* !defined(HAVE_STDIO_H) && !defined(__WINRT__) */
367 
368         length = SDL_strlen(SDL_priority_prefixes[priority]) + 2 + SDL_strlen(message) + 1 + 1 + 1;
369         output = SDL_small_alloc(char, length, &isstack);
370         SDL_snprintf(output, length, "%s: %s\r\n", SDL_priority_prefixes[priority], message);
371         tstr = WIN_UTF8ToString(output);
372 
373         /* Output to debugger */
374         OutputDebugString(tstr);
375 
376 #if !defined(HAVE_STDIO_H) && !defined(__WINRT__)
377         /* Screen output to stderr, if console was attached. */
378         if (consoleAttached == 1) {
379                 if (!WriteConsole(stderrHandle, tstr, (DWORD) SDL_tcslen(tstr), &charsWritten, NULL)) {
380                     OutputDebugString(TEXT("Error calling WriteConsole\r\n"));
381                     if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY) {
382                         OutputDebugString(TEXT("Insufficient heap memory to write message\r\n"));
383                     }
384                 }
385 
386         } else if (consoleAttached == 2) {
387             if (!WriteFile(stderrHandle, output, (DWORD) SDL_strlen(output), &charsWritten, NULL)) {
388                 OutputDebugString(TEXT("Error calling WriteFile\r\n"));
389             }
390         }
391 #endif /* !defined(HAVE_STDIO_H) && !defined(__WINRT__) */
392 
393         SDL_free(tstr);
394         SDL_small_free(output, isstack);
395     }
396 #elif defined(__ANDROID__)
397     {
398         char tag[32];
399 
400         SDL_snprintf(tag, SDL_arraysize(tag), "SDL/%s", GetCategoryPrefix(category));
401         __android_log_write(SDL_android_priority[priority], tag, message);
402     }
403 #elif defined(__APPLE__) && (defined(SDL_VIDEO_DRIVER_COCOA) || defined(SDL_VIDEO_DRIVER_UIKIT))
404     /* Technically we don't need Cocoa/UIKit, but that's where this function is defined for now.
405     */
406     extern void SDL_NSLog(const char *text);
407     {
408         char *text;
409         /* !!! FIXME: why not just "char text[SDL_MAX_LOG_MESSAGE];" ? */
410         text = SDL_stack_alloc(char, SDL_MAX_LOG_MESSAGE);
411         if (text) {
412             SDL_snprintf(text, SDL_MAX_LOG_MESSAGE, "%s: %s", SDL_priority_prefixes[priority], message);
413             SDL_NSLog(text);
414             SDL_stack_free(text);
415             return;
416         }
417     }
418 #elif defined(__PSP__)
419     {
420         FILE*        pFile;
421         pFile = fopen ("SDL_Log.txt", "a");
422         fprintf(pFile, "%s: %s\n", SDL_priority_prefixes[priority], message);
423         fclose (pFile);
424     }
425 #elif defined(__VITA__)
426     {
427         FILE*        pFile;
428         pFile = fopen ("ux0:/data/SDL_Log.txt", "a");
429         fprintf(pFile, "%s: %s\n", SDL_priority_prefixes[priority], message);
430         fclose (pFile);
431     }
432 #endif
433 #if HAVE_STDIO_H
434     fprintf(stderr, "%s: %s\n", SDL_priority_prefixes[priority], message);
435 #if __NACL__
436     fflush(stderr);
437 #endif
438 #endif
439 }
440 
441 void
SDL_LogGetOutputFunction(SDL_LogOutputFunction * callback,void ** userdata)442 SDL_LogGetOutputFunction(SDL_LogOutputFunction *callback, void **userdata)
443 {
444     if (callback) {
445         *callback = SDL_log_function;
446     }
447     if (userdata) {
448         *userdata = SDL_log_userdata;
449     }
450 }
451 
452 void
SDL_LogSetOutputFunction(SDL_LogOutputFunction callback,void * userdata)453 SDL_LogSetOutputFunction(SDL_LogOutputFunction callback, void *userdata)
454 {
455     SDL_log_function = callback;
456     SDL_log_userdata = userdata;
457 }
458 
459 /* vi: set ts=4 sw=4 expandtab: */
460