1 /*
2  * Copyright © 2017 Google, Inc.
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice (including the next
12  * paragraph) shall be included in all copies or substantial portions of the
13  * Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21  * IN THE SOFTWARE.
22  */
23 
24 #include <stdarg.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include "c11/threads.h"
29 #include "util/detect_os.h"
30 #include "util/log.h"
31 #include "util/ralloc.h"
32 #include "util/u_debug.h"
33 
34 #if DETECT_OS_UNIX
35 #include <syslog.h>
36 #include "util/u_process.h"
37 #endif
38 
39 #if DETECT_OS_ANDROID
40 #include <android/log.h>
41 #endif
42 
43 #if DETECT_OS_WINDOWS
44 #include <windows.h>
45 #endif
46 
47 enum mesa_log_control {
48    MESA_LOG_CONTROL_NULL = 1 << 0,
49    MESA_LOG_CONTROL_FILE = 1 << 1,
50    MESA_LOG_CONTROL_SYSLOG = 1 << 2,
51    MESA_LOG_CONTROL_ANDROID = 1 << 3,
52    MESA_LOG_CONTROL_WINDBG = 1 << 4,
53    MESA_LOG_CONTROL_LOGGER_MASK = 0xff,
54 
55    MESA_LOG_CONTROL_WAIT = 1 << 8,
56 };
57 
58 static const struct debug_control mesa_log_control_options[] = {
59    /* loggers */
60    { "null", MESA_LOG_CONTROL_NULL },
61    { "file", MESA_LOG_CONTROL_FILE },
62    { "syslog", MESA_LOG_CONTROL_SYSLOG },
63    { "android", MESA_LOG_CONTROL_ANDROID },
64    { "windbg", MESA_LOG_CONTROL_WINDBG },
65    /* flags */
66    { "wait", MESA_LOG_CONTROL_WAIT },
67    { NULL, 0 },
68 };
69 
70 static uint32_t mesa_log_control;
71 static FILE *mesa_log_file;
72 
73 static void
mesa_log_init_once(void)74 mesa_log_init_once(void)
75 {
76    mesa_log_control = parse_debug_string(os_get_option("MESA_LOG"),
77          mesa_log_control_options);
78 
79    if (!(mesa_log_control & MESA_LOG_CONTROL_LOGGER_MASK)) {
80       /* pick the default loggers */
81 #if DETECT_OS_ANDROID
82       mesa_log_control |= MESA_LOG_CONTROL_ANDROID;
83 #else
84       mesa_log_control |= MESA_LOG_CONTROL_FILE;
85 #endif
86 
87 #if DETECT_OS_WINDOWS
88       /* stderr from windows applications without console is not usually
89        * visible, so communicate with the debugger instead */
90       mesa_log_control |= MESA_LOG_CONTROL_WINDBG;
91 #endif
92    }
93 
94    mesa_log_file = stderr;
95 
96 #if !DETECT_OS_WINDOWS
97    if (geteuid() == getuid()) {
98       const char *log_file = os_get_option("MESA_LOG_FILE");
99       if (log_file) {
100          FILE *fp = fopen(log_file, "w");
101          if (fp) {
102             mesa_log_file = fp;
103             mesa_log_control |= MESA_LOG_CONTROL_FILE;
104          }
105       }
106    }
107 #endif
108 
109 #if DETECT_OS_UNIX
110    if (mesa_log_control & MESA_LOG_CONTROL_SYSLOG)
111       openlog(util_get_process_name(), LOG_NDELAY | LOG_PID, LOG_USER);
112 #endif
113 }
114 
115 static void
mesa_log_init(void)116 mesa_log_init(void)
117 {
118    static once_flag once = ONCE_FLAG_INIT;
119    call_once(&once, mesa_log_init_once);
120 }
121 
122 static inline const char *
level_to_str(enum mesa_log_level l)123 level_to_str(enum mesa_log_level l)
124 {
125    switch (l) {
126    case MESA_LOG_ERROR: return "error";
127    case MESA_LOG_WARN: return "warning";
128    case MESA_LOG_INFO: return "info";
129    case MESA_LOG_DEBUG: return "debug";
130    }
131 
132    unreachable("bad mesa_log_level");
133 }
134 
135 enum logger_vasnprintf_affix {
136    LOGGER_VASNPRINTF_AFFIX_TAG = 1 << 0,
137    LOGGER_VASNPRINTF_AFFIX_LEVEL = 1 << 1,
138    LOGGER_VASNPRINTF_AFFIX_NEWLINE = 1 << 2,
139 };
140 
141 /* Try vsnprintf first and fall back to vasprintf if buf is too small.  This
142  * function handles all errors and never fails.
143  */
144 static char *
logger_vasnprintf(char * buf,int size,int affixes,enum mesa_log_level level,const char * tag,const char * format,va_list in_va)145 logger_vasnprintf(char *buf,
146                   int size,
147                   int affixes,
148                   enum mesa_log_level level,
149                   const char *tag,
150                   const char *format,
151                   va_list in_va)
152 {
153    struct {
154       char *cur;
155       int rem;
156       int total;
157       bool invalid;
158    } state = {
159       .cur = buf,
160       .rem = size,
161    };
162 
163    va_list va;
164    va_copy(va, in_va);
165 
166 #define APPEND(state, func, ...)                                     \
167    do {                                                              \
168       int ret = func(state.cur, state.rem, __VA_ARGS__);             \
169       if (ret < 0) {                                                 \
170          state.invalid = true;                                       \
171       }  else {                                                      \
172          state.total += ret;                                         \
173          if (ret >= state.rem)                                       \
174             ret = state.rem;                                         \
175          state.cur += ret;                                           \
176          state.rem -= ret;                                           \
177       }                                                              \
178    } while (false)
179 
180    if (affixes & LOGGER_VASNPRINTF_AFFIX_TAG)
181       APPEND(state, snprintf, "%s: ", tag);
182    if (affixes & LOGGER_VASNPRINTF_AFFIX_LEVEL)
183       APPEND(state, snprintf, "%s: ", level_to_str(level));
184 
185    APPEND(state, vsnprintf, format, va);
186 
187    if (affixes & LOGGER_VASNPRINTF_AFFIX_NEWLINE) {
188       if (state.cur == buf || state.cur[-1] != '\n')
189          APPEND(state, snprintf, "\n");
190    }
191 #undef APPEND
192 
193    assert(size >= 64);
194    if (state.invalid) {
195       strncpy(buf, "invalid message format", size);
196    } else if (state.total >= size) {
197       /* print again into alloc to avoid truncation */
198       void *alloc = malloc(state.total + 1);
199       if (alloc) {
200          buf = logger_vasnprintf(alloc, state.total + 1, affixes, level, tag,
201                format, in_va);
202          assert(buf == alloc);
203       } else {
204          /* pretty-truncate the message */
205          strncpy(buf + size - 4, "...", 4);
206       }
207    }
208 
209    va_end(va);
210 
211    return buf;
212 }
213 
214 static void
logger_file(enum mesa_log_level level,const char * tag,const char * format,va_list va)215 logger_file(enum mesa_log_level level,
216             const char *tag,
217             const char *format,
218             va_list va)
219 {
220    FILE *fp = mesa_log_file;
221    char local_msg[1024];
222    char *msg = logger_vasnprintf(local_msg, sizeof(local_msg),
223          LOGGER_VASNPRINTF_AFFIX_TAG |
224          LOGGER_VASNPRINTF_AFFIX_LEVEL |
225          LOGGER_VASNPRINTF_AFFIX_NEWLINE,
226          level, tag, format, va);
227 
228    fprintf(fp, "%s", msg);
229    fflush(fp);
230 
231    if (msg != local_msg)
232       free(msg);
233 }
234 
235 #if DETECT_OS_UNIX
236 
237 static inline int
level_to_syslog(enum mesa_log_level l)238 level_to_syslog(enum mesa_log_level l)
239 {
240    switch (l) {
241    case MESA_LOG_ERROR: return LOG_ERR;
242    case MESA_LOG_WARN: return LOG_WARNING;
243    case MESA_LOG_INFO: return LOG_INFO;
244    case MESA_LOG_DEBUG: return LOG_DEBUG;
245    }
246 
247    unreachable("bad mesa_log_level");
248 }
249 
250 static void
logger_syslog(enum mesa_log_level level,const char * tag,const char * format,va_list va)251 logger_syslog(enum mesa_log_level level,
252               const char *tag,
253               const char *format,
254               va_list va)
255 {
256    char local_msg[1024];
257    char *msg = logger_vasnprintf(local_msg, sizeof(local_msg),
258          LOGGER_VASNPRINTF_AFFIX_TAG, level, tag, format, va);
259 
260    syslog(level_to_syslog(level), "%s", msg);
261 
262    if (msg != local_msg)
263       free(msg);
264 }
265 
266 #endif /* DETECT_OS_UNIX */
267 
268 #if DETECT_OS_ANDROID
269 
270 static inline android_LogPriority
level_to_android(enum mesa_log_level l)271 level_to_android(enum mesa_log_level l)
272 {
273    switch (l) {
274    case MESA_LOG_ERROR: return ANDROID_LOG_ERROR;
275    case MESA_LOG_WARN: return ANDROID_LOG_WARN;
276    case MESA_LOG_INFO: return ANDROID_LOG_INFO;
277    case MESA_LOG_DEBUG: return ANDROID_LOG_DEBUG;
278    }
279 
280    unreachable("bad mesa_log_level");
281 }
282 
283 static void
logger_android(enum mesa_log_level level,const char * tag,const char * format,va_list va)284 logger_android(enum mesa_log_level level,
285                const char *tag,
286                const char *format,
287                va_list va)
288 {
289    /* Android can truncate/drop messages
290     *
291     *  - the internal buffer for vsnprintf has a fixed size (usually 1024)
292     *  - the socket to logd is non-blocking
293     *
294     * and provides no way to detect.  Try our best.
295     */
296    char local_msg[1024];
297    char *msg = logger_vasnprintf(local_msg, sizeof(local_msg), 0, level, tag,
298          format, va);
299 
300    __android_log_write(level_to_android(level), tag, msg);
301 
302    if (msg != local_msg)
303       free(msg);
304 
305    /* increase the chance of logd doing its part */
306    if (mesa_log_control & MESA_LOG_CONTROL_WAIT)
307       thrd_yield();
308 }
309 
310 #endif /* DETECT_OS_ANDROID */
311 
312 #if DETECT_OS_WINDOWS
313 
314 static void
logger_windbg(enum mesa_log_level level,const char * tag,const char * format,va_list va)315 logger_windbg(enum mesa_log_level level,
316               const char *tag,
317               const char *format,
318               va_list va)
319 {
320    char local_msg[1024];
321    char *msg = logger_vasnprintf(local_msg, sizeof(local_msg),
322          LOGGER_VASNPRINTF_AFFIX_TAG |
323          LOGGER_VASNPRINTF_AFFIX_LEVEL |
324          LOGGER_VASNPRINTF_AFFIX_NEWLINE,
325          level, tag, format, va);
326 
327    OutputDebugStringA(msg);
328 
329    if (msg != local_msg)
330       free(msg);
331 }
332 
333 #endif /* DETECT_OS_WINDOWS */
334 
335 /* This is for use with debug functions that take a FILE, such as
336  * nir_print_shader, although switching to nir_log_shader* is preferred.
337  */
338 FILE *
mesa_log_get_file(void)339 mesa_log_get_file(void)
340 {
341    mesa_log_init();
342    return mesa_log_file;
343 }
344 
345 void
mesa_log(enum mesa_log_level level,const char * tag,const char * format,...)346 mesa_log(enum mesa_log_level level, const char *tag, const char *format, ...)
347 {
348    va_list va;
349 
350    va_start(va, format);
351    mesa_log_v(level, tag, format, va);
352    va_end(va);
353 }
354 
355 void
mesa_log_v(enum mesa_log_level level,const char * tag,const char * format,va_list va)356 mesa_log_v(enum mesa_log_level level, const char *tag, const char *format,
357             va_list va)
358 {
359    static const struct {
360       enum mesa_log_control bit;
361       void (*log)(enum mesa_log_level level,
362                   const char *tag,
363                   const char *format,
364                   va_list va);
365    } loggers[] = {
366       { MESA_LOG_CONTROL_FILE, logger_file },
367 #if DETECT_OS_UNIX
368       { MESA_LOG_CONTROL_SYSLOG, logger_syslog },
369 #endif
370 #if DETECT_OS_ANDROID
371       { MESA_LOG_CONTROL_ANDROID, logger_android },
372 #endif
373 #if DETECT_OS_WINDOWS
374       { MESA_LOG_CONTROL_WINDBG, logger_windbg },
375 #endif
376    };
377 
378    mesa_log_init();
379 
380    for (uint32_t i = 0; i < ARRAY_SIZE(loggers); i++) {
381       if (mesa_log_control & loggers[i].bit) {
382          va_list copy;
383          va_copy(copy, va);
384          loggers[i].log(level, tag, format, copy);
385          va_end(copy);
386       }
387    }
388 }
389 
390 struct log_stream *
_mesa_log_stream_create(enum mesa_log_level level,const char * tag)391 _mesa_log_stream_create(enum mesa_log_level level, const char *tag)
392 {
393    struct log_stream *stream = ralloc(NULL, struct log_stream);
394    stream->level = level;
395    stream->tag = tag;
396    stream->msg = ralloc_strdup(stream, "");
397    stream->pos = 0;
398    return stream;
399 }
400 
401 void
mesa_log_stream_destroy(struct log_stream * stream)402 mesa_log_stream_destroy(struct log_stream *stream)
403 {
404    /* If you left trailing stuff in the log stream, flush it out as a line. */
405    if (stream->pos != 0)
406       mesa_log(stream->level, stream->tag, "%s", stream->msg);
407 
408    ralloc_free(stream);
409 }
410 
411 static void
mesa_log_stream_flush(struct log_stream * stream,size_t scan_offset)412 mesa_log_stream_flush(struct log_stream *stream, size_t scan_offset)
413 {
414    char *end;
415    char *next = stream->msg;
416    while ((end = strchr(stream->msg + scan_offset, '\n'))) {
417       *end = 0;
418       mesa_log(stream->level, stream->tag, "%s", next);
419       next = end + 1;
420       scan_offset = next - stream->msg;
421    }
422    if (next != stream->msg) {
423       /* Clear out the lines we printed and move any trailing chars to the start. */
424       size_t remaining = stream->msg + stream->pos - next;
425       memmove(stream->msg, next, remaining);
426       stream->pos = remaining;
427    }
428 }
429 
mesa_log_stream_printf(struct log_stream * stream,const char * format,...)430 void mesa_log_stream_printf(struct log_stream *stream, const char *format, ...)
431 {
432    size_t old_pos = stream->pos;
433 
434    va_list va;
435    va_start(va, format);
436    ralloc_vasprintf_rewrite_tail(&stream->msg, &stream->pos, format, va);
437    va_end(va);
438 
439    mesa_log_stream_flush(stream, old_pos);
440 }
441 
442 void
_mesa_log_multiline(enum mesa_log_level level,const char * tag,const char * lines)443 _mesa_log_multiline(enum mesa_log_level level, const char *tag, const char *lines)
444 {
445    struct log_stream tmp = {
446       .level = level,
447       .tag = tag,
448       .msg = strdup(lines),
449       .pos = strlen(lines),
450    };
451    mesa_log_stream_flush(&tmp, 0);
452    free(tmp.msg);
453 }
454