1 /*  RetroArch - A frontend for libretro.
2  *  Copyright (C) 2011-2017 - Daniel De Matteis
3  *  Copyright (C) 2016-2019 - Brad Parker
4  *
5  *  RetroArch is free software: you can redistribute it and/or modify it under the terms
6  *  of the GNU General Public License as published by the Free Software Found-
7  *  ation, either version 3 of the License, or (at your option) any later version.
8  *
9  *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
10  *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
11  *  PURPOSE.  See the GNU General Public License for more details.
12  *
13  *  You should have received a copy of the GNU General Public License along with RetroArch.
14  *  If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 #ifdef _XBOX1
18 #include <xtl.h>
19 #endif
20 
21 #if defined(__PSL1GHT__) || defined(__PS3__)
22 #include "defines/ps3_defines.h"
23 #endif
24 
25 #ifdef __MACH__
26 #include <TargetConditionals.h>
27 #if TARGET_IPHONE_SIMULATOR
28 #include <stdio.h>
29 #else
30 #include <asl.h>
31 #endif
32 #endif
33 
34 #include <stdio.h>
35 #include <stdarg.h>
36 #include <time.h>
37 
38 #ifdef _MSC_VER
39 #include <compat/msvc.h>
40 #endif
41 
42 #ifdef ANDROID
43 #include <android/log.h>
44 #endif
45 
46 #if defined(_WIN32)
47 
48 #if defined(_XBOX)
49 #include <Xtl.h>
50 #else
51 #ifndef WIN32_LEAN_AND_MEAN
52 #define WIN32_LEAN_AND_MEAN
53 #endif
54 #include <windows.h>
55 #endif
56 
57 #endif
58 
59 #include <file/file_path.h>
60 #include <string/stdstring.h>
61 #include <streams/file_stream.h>
62 #include <compat/fopen_utf8.h>
63 #include <time/rtime.h>
64 #include <retro_miscellaneous.h>
65 
66 #ifdef HAVE_CONFIG_H
67 #include "config.h"
68 #endif
69 
70 #ifdef RARCH_INTERNAL
71 #include "frontend/frontend_driver.h"
72 #endif
73 
74 #include "verbosity.h"
75 
76 #ifdef HAVE_QT
77 #include "ui/ui_companion_driver.h"
78 #endif
79 
80 #ifdef RARCH_INTERNAL
81 #include "config.def.h"
82 #else
83 #define DEFAULT_FRONTEND_LOG_LEVEL 1
84 #endif
85 
86 #if defined(IS_SALAMANDER)
87 #define FILE_PATH_PROGRAM_NAME "RetroArch Salamander"
88 #else
89 #define FILE_PATH_PROGRAM_NAME "RetroArch"
90 #endif
91 
92 typedef struct verbosity_state
93 {
94 #ifdef HAVE_LIBNX
95    Mutex mtx;
96 #endif
97    /* If this is non-NULL. RARCH_LOG and friends
98     * will write to this file. */
99    FILE *fp;
100    void *buf;
101 
102    char override_path[PATH_MAX_LENGTH];
103    bool verbosity;
104    bool initialized;
105    bool override_active;
106 } verbosity_state_t;
107 
108 /* TODO/FIXME - static public global variables */
109 static verbosity_state_t main_verbosity_st;
110 static unsigned verbosity_log_level           =
111 DEFAULT_FRONTEND_LOG_LEVEL;
112 
113 #ifdef HAVE_LIBNX
114 #ifdef NXLINK
115 /* TODO/FIXME - global referenced in platform_switch.c - not
116  * thread-safe */
117 bool nxlink_connected = false;
118 #endif /* NXLINK */
119 
120 #endif /* HAVE_LIBNX */
121 
verbosity_set_log_level(unsigned level)122 void verbosity_set_log_level(unsigned level)
123 {
124    verbosity_log_level = level;
125 }
126 
verbosity_enable(void)127 void verbosity_enable(void)
128 {
129    verbosity_state_t *g_verbosity = &main_verbosity_st;
130 
131    g_verbosity->verbosity         = true;
132 #ifdef RARCH_INTERNAL
133    if (!g_verbosity->initialized)
134       frontend_driver_attach_console();
135 #endif
136 }
137 
verbosity_disable(void)138 void verbosity_disable(void)
139 {
140    verbosity_state_t *g_verbosity = &main_verbosity_st;
141 
142    g_verbosity->verbosity         = false;
143 #ifdef RARCH_INTERNAL
144    if (!g_verbosity->initialized)
145       frontend_driver_detach_console();
146 #endif
147 }
148 
verbosity_is_enabled(void)149 bool verbosity_is_enabled(void)
150 {
151    verbosity_state_t *g_verbosity = &main_verbosity_st;
152    return g_verbosity->verbosity;
153 }
154 
is_logging_to_file(void)155 bool is_logging_to_file(void)
156 {
157    verbosity_state_t *g_verbosity = &main_verbosity_st;
158    return g_verbosity->initialized;
159 }
160 
verbosity_get_ptr(void)161 bool *verbosity_get_ptr(void)
162 {
163    verbosity_state_t *g_verbosity = &main_verbosity_st;
164    return &g_verbosity->verbosity;
165 }
166 
retro_main_log_file_init(const char * path,bool append)167 void retro_main_log_file_init(const char *path, bool append)
168 {
169    FILE *tmp                      = NULL;
170    verbosity_state_t *g_verbosity = &main_verbosity_st;
171    if (g_verbosity->initialized)
172       return;
173 
174 #ifdef HAVE_LIBNX
175    mutexInit(&g_verbosity->mtx);
176 #endif
177 
178    g_verbosity->fp      = stderr;
179    if (!path)
180       return;
181 
182    tmp                  = (FILE*)fopen_utf8(path, append ? "ab" : "wb");
183 
184    if (!tmp)
185    {
186       RARCH_ERR("Failed to open system event log file: %s\n", path);
187       return;
188    }
189 
190    g_verbosity->fp          = tmp;
191    g_verbosity->initialized = true;
192 
193    /* TODO: this is only useful for a few platforms, find which and add ifdef */
194    g_verbosity->buf         = calloc(1, 0x4000);
195    setvbuf(g_verbosity->fp, (char*)g_verbosity->buf, _IOFBF, 0x4000);
196 }
197 
retro_main_log_file_deinit(void)198 void retro_main_log_file_deinit(void)
199 {
200    verbosity_state_t *g_verbosity = &main_verbosity_st;
201 
202    if (g_verbosity->fp && g_verbosity->initialized)
203    {
204       fclose(g_verbosity->fp);
205       g_verbosity->fp       = NULL;
206    }
207    if (g_verbosity->buf)
208       free(g_verbosity->buf);
209    g_verbosity->buf         = NULL;
210    g_verbosity->initialized = false;
211 }
212 
213 #if !defined(HAVE_LOGGER)
RARCH_LOG_V(const char * tag,const char * fmt,va_list ap)214 void RARCH_LOG_V(const char *tag, const char *fmt, va_list ap)
215 {
216    verbosity_state_t *g_verbosity = &main_verbosity_st;
217    const char              *tag_v = tag ? tag : FILE_PATH_LOG_INFO;
218 
219 #if TARGET_OS_IPHONE
220 #if TARGET_IPHONE_SIMULATOR
221    vprintf(fmt, ap);
222 #else
223    static aslclient asl_client;
224    static int asl_initialized = 0;
225    if (!asl_initialized)
226    {
227       asl_client      = asl_open(
228             FILE_PATH_PROGRAM_NAME,
229             "com.apple.console",
230             ASL_OPT_STDERR | ASL_OPT_NO_DELAY);
231       asl_initialized = 1;
232    }
233    aslmsg msg = asl_new(ASL_TYPE_MSG);
234    asl_set(msg, ASL_KEY_READ_UID, "-1");
235    if (tag)
236       asl_log(asl_client, msg, ASL_LEVEL_NOTICE, "%s", tag);
237    asl_vlog(asl_client, msg, ASL_LEVEL_NOTICE, fmt, ap);
238    asl_free(msg);
239 #endif
240 #elif defined(_XBOX1)
241    /* FIXME: Using arbitrary string as fmt argument is unsafe. */
242    char msg_new[256];
243    char buffer[256];
244 
245    msg_new[0] = buffer[0] = '\0';
246    snprintf(msg_new, sizeof(msg_new), "%s: %s %s",
247          FILE_PATH_PROGRAM_NAME, tag_v, fmt);
248    wvsprintf(buffer, msg_new, ap);
249    OutputDebugStringA(buffer);
250 #elif defined(ANDROID)
251    int prio = ANDROID_LOG_INFO;
252    if (tag)
253    {
254       if (string_is_equal(FILE_PATH_LOG_WARN, tag))
255          prio = ANDROID_LOG_WARN;
256       else if (string_is_equal(FILE_PATH_LOG_ERROR, tag))
257          prio = ANDROID_LOG_ERROR;
258    }
259 
260    if (g_verbosity->initialized)
261    {
262       vfprintf(g_verbosity->fp, fmt, ap);
263       fflush(g_verbosity->fp);
264    }
265    else
266       __android_log_vprint(prio, FILE_PATH_PROGRAM_NAME, fmt, ap);
267 #else
268    FILE *fp = (FILE*)g_verbosity->fp;
269 #if defined(HAVE_QT) || defined(__WINRT__)
270    char buffer[256];
271    buffer[0] = '\0';
272 
273    /* Ensure null termination and line break in error case */
274    if (vsnprintf(buffer, sizeof(buffer), fmt, ap) < 0)
275    {
276       int end;
277       buffer[sizeof(buffer) - 1]  = '\0';
278       end = strlen(buffer) - 1;
279       if (end >= 0)
280          buffer[end] = '\n';
281       else
282       {
283          buffer[0]   = '\n';
284          buffer[1]   = '\0';
285       }
286    }
287 
288    if (fp)
289    {
290       fprintf(fp, "%s %s", tag_v, buffer);
291       fflush(fp);
292    }
293 
294 #if defined(HAVE_QT)
295    ui_companion_driver_log_msg(buffer);
296 #endif
297 
298 #if defined(__WINRT__)
299    OutputDebugStringA(buffer);
300 #endif
301 #else
302 #if defined(HAVE_LIBNX)
303    mutexLock(&g_verbosity->mtx);
304 #endif
305    if (fp)
306    {
307       fprintf(fp, "%s ", tag_v);
308       vfprintf(fp, fmt, ap);
309       fflush(fp);
310    }
311 #if defined(HAVE_LIBNX)
312    mutexUnlock(&g_verbosity->mtx);
313 #endif
314 
315 #endif
316 #endif
317 }
318 
RARCH_LOG_BUFFER(uint8_t * data,size_t size)319 void RARCH_LOG_BUFFER(uint8_t *data, size_t size)
320 {
321    unsigned i, offset;
322    int padding     = size % 16;
323    uint8_t buf[16] = {0};
324 
325    RARCH_LOG("== %d-byte buffer ==================\n", (int)size);
326 
327    for (i = 0, offset = 0; i < size; i++)
328    {
329       buf[offset] = data[i];
330       offset++;
331 
332       if (offset == 16)
333       {
334          offset = 0;
335          RARCH_LOG("%02x%02x%02x%02x%02x%02x%02x%02x  %02x%02x%02x%02x%02x%02x%02x%02x\n",
336             buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
337             buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15]);
338       }
339    }
340 
341    if (padding)
342    {
343       for (i = padding; i < 16; i++)
344          buf[i] = 0xff;
345       RARCH_LOG("%02x%02x%02x%02x%02x%02x%02x%02x  %02x%02x%02x%02x%02x%02x%02x%02x\n",
346          buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
347          buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15]);
348    }
349    RARCH_LOG("==================================\n");
350 }
351 
RARCH_DBG(const char * fmt,...)352 void RARCH_DBG(const char *fmt, ...)
353 {
354    va_list ap;
355    verbosity_state_t *g_verbosity = &main_verbosity_st;
356 
357    if (!g_verbosity->verbosity)
358       return;
359    if (verbosity_log_level > 0)
360       return;
361 
362    va_start(ap, fmt);
363    RARCH_LOG_V(FILE_PATH_LOG_INFO, fmt, ap);
364    va_end(ap);
365 }
366 
RARCH_LOG(const char * fmt,...)367 void RARCH_LOG(const char *fmt, ...)
368 {
369    va_list ap;
370    verbosity_state_t *g_verbosity = &main_verbosity_st;
371 
372    if (!g_verbosity->verbosity)
373       return;
374    if (verbosity_log_level > 1)
375       return;
376 
377    va_start(ap, fmt);
378    RARCH_LOG_V(FILE_PATH_LOG_INFO, fmt, ap);
379    va_end(ap);
380 }
381 
RARCH_LOG_OUTPUT(const char * msg,...)382 void RARCH_LOG_OUTPUT(const char *msg, ...)
383 {
384    va_list ap;
385    va_start(ap, msg);
386    RARCH_LOG_OUTPUT_V(FILE_PATH_LOG_INFO, msg, ap);
387    va_end(ap);
388 }
389 
RARCH_WARN(const char * fmt,...)390 void RARCH_WARN(const char *fmt, ...)
391 {
392    va_list ap;
393    verbosity_state_t *g_verbosity = &main_verbosity_st;
394 
395    if (!g_verbosity->verbosity)
396       return;
397    if (verbosity_log_level > 2)
398       return;
399 
400    va_start(ap, fmt);
401    RARCH_WARN_V(FILE_PATH_LOG_WARN, fmt, ap);
402    va_end(ap);
403 }
404 
RARCH_ERR(const char * fmt,...)405 void RARCH_ERR(const char *fmt, ...)
406 {
407    va_list ap;
408    verbosity_state_t *g_verbosity = &main_verbosity_st;
409 
410    if (!g_verbosity->verbosity)
411       return;
412 
413    va_start(ap, fmt);
414    RARCH_ERR_V(FILE_PATH_LOG_ERROR, fmt, ap);
415    va_end(ap);
416 }
417 #endif
418 
rarch_log_file_set_override(const char * path)419 void rarch_log_file_set_override(const char *path)
420 {
421    verbosity_state_t *g_verbosity = &main_verbosity_st;
422 
423    g_verbosity->override_active   = true;
424    strlcpy(g_verbosity->override_path, path,
425          sizeof(g_verbosity->override_path));
426 }
427 
rarch_log_file_init(bool log_to_file,bool log_to_file_timestamp,const char * log_dir)428 void rarch_log_file_init(
429       bool log_to_file,
430       bool log_to_file_timestamp,
431       const char *log_dir
432       )
433 {
434    char log_directory[PATH_MAX_LENGTH];
435    char log_file_path[PATH_MAX_LENGTH];
436    verbosity_state_t *g_verbosity            = &main_verbosity_st;
437    static bool log_file_created              = false;
438    static char timestamped_log_file_name[64] = {0};
439    bool logging_to_file                      = g_verbosity->initialized;
440 
441    log_directory[0]                          = '\0';
442    log_file_path[0]                          = '\0';
443 
444    /* If this is the first run, generate a timestamped log
445     * file name (do this even when not outputting timestamped
446     * log files, since user may decide to switch at any moment...) */
447    if (string_is_empty(timestamped_log_file_name))
448    {
449       char format[256];
450       struct tm tm_;
451       time_t cur_time = time(NULL);
452 
453       rtime_localtime(&cur_time, &tm_);
454 
455       format[0] = '\0';
456       strftime(format, sizeof(format), "retroarch__%Y_%m_%d__%H_%M_%S", &tm_);
457       fill_pathname_noext(timestamped_log_file_name, format,
458             ".log",
459             sizeof(timestamped_log_file_name));
460    }
461 
462    /* If nothing has changed, do nothing */
463    if ((!log_to_file && !logging_to_file) ||
464        (log_to_file && logging_to_file))
465       return;
466 
467    /* If we are currently logging to file and wish to stop,
468     * de-initialise existing logger... */
469    if (!log_to_file && logging_to_file)
470    {
471       retro_main_log_file_deinit();
472       /* ...and revert to console */
473       retro_main_log_file_init(NULL, false);
474       return;
475    }
476 
477    /* If we reach this point, then we are not currently
478     * logging to file, and wish to do so */
479 
480    /* > Check whether we are already logging to console */
481    /* De-initialise existing logger */
482    if (g_verbosity->fp)
483       retro_main_log_file_deinit();
484 
485    /* > Get directory/file paths */
486    if (g_verbosity->override_active)
487    {
488       /* Get log directory */
489       const char *override_path        = g_verbosity->override_path;
490       const char *last_slash           = find_last_slash(override_path);
491 
492       if (last_slash)
493       {
494          char tmp_buf[PATH_MAX_LENGTH] = {0};
495          size_t path_length            = last_slash + 1 - override_path;
496 
497          if ((path_length > 1) && (path_length < PATH_MAX_LENGTH))
498             strlcpy(tmp_buf, override_path, path_length * sizeof(char));
499          strlcpy(log_directory, tmp_buf, sizeof(log_directory));
500       }
501 
502       /* Get log file path */
503       strlcpy(log_file_path, override_path, sizeof(log_file_path));
504    }
505    else if (!string_is_empty(log_dir))
506    {
507       /* Get log directory */
508       strlcpy(log_directory, log_dir, sizeof(log_directory));
509 
510       /* Get log file path */
511       fill_pathname_join(log_file_path,
512             log_dir,
513             log_to_file_timestamp
514             ? timestamped_log_file_name
515             : "retroarch.log",
516             sizeof(log_file_path));
517    }
518 
519    /* > Attempt to initialise log file */
520    if (!string_is_empty(log_file_path))
521    {
522       /* Create log directory, if required */
523       if (!string_is_empty(log_directory))
524       {
525          if (!path_is_directory(log_directory))
526          {
527             if (!path_mkdir(log_directory))
528             {
529                /* Re-enable console logging and output error message */
530                retro_main_log_file_init(NULL, false);
531                RARCH_ERR("Failed to create system event log directory: %s\n", log_directory);
532                return;
533             }
534          }
535       }
536 
537       /* When RetroArch is launched, log file is overwritten.
538        * On subsequent calls within the same session, it is appended to. */
539       retro_main_log_file_init(log_file_path, log_file_created);
540       if (g_verbosity->initialized)
541          log_file_created = true;
542       return;
543    }
544 
545    /* If we reach this point, then something went wrong...
546     * Just fall back to console logging */
547    retro_main_log_file_init(NULL, false);
548    RARCH_ERR("Failed to initialise system event file logging...\n");
549 }
550 
rarch_log_file_deinit(void)551 void rarch_log_file_deinit(void)
552 {
553    verbosity_state_t *g_verbosity = &main_verbosity_st;
554 
555    /* De-initialise existing logger, if currently logging to file */
556    if (g_verbosity->initialized)
557       retro_main_log_file_deinit();
558 
559    /* If logging is currently disabled... */
560    if (!g_verbosity->fp) /* ...initialise logging to console */
561       retro_main_log_file_init(NULL, false);
562 }
563