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