Coverage Report

Created: 2025-08-11 06:44

/src/mpv/common/msg.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * This file is part of mpv.
3
 *
4
 * mpv is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
6
 * License as published by the Free Software Foundation; either
7
 * version 2.1 of the License, or (at your option) any later version.
8
 *
9
 * mpv is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
15
 * License along with mpv.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17
18
#include <assert.h>
19
#include <stdarg.h>
20
#include <stdatomic.h>
21
#include <stdint.h>
22
#include <stdio.h>
23
#include <stdlib.h>
24
#include <string.h>
25
26
#include "mpv_talloc.h"
27
28
#include "common/common.h"
29
#include "common/global.h"
30
#include "misc/codepoint_width.h"
31
#include "options/options.h"
32
#include "options/path.h"
33
#include "osdep/io.h"
34
#include "osdep/terminal.h"
35
#include "osdep/threads.h"
36
#include "osdep/timer.h"
37
38
#include "mpv/client.h"
39
40
#include "msg.h"
41
#include "msg_control.h"
42
43
// log buffer size (lines) logfile level
44
0
#define FILE_BUF 100
45
46
// lines to accumulate before any client requests the terminal loglevel
47
303k
#define EARLY_TERM_BUF 100
48
49
// logfile lines to accumulate during init before we know the log file name.
50
// thousands of logfile lines during init can happen (especially with many
51
// scripts, big config, etc), so we set 5000. If it cycles and messages are
52
// overwritten, then the first (virtual) log line indicates how many were lost.
53
303k
#define EARLY_FILE_BUF 5000
54
55
struct mp_log_root {
56
    struct mpv_global *global;
57
    mp_mutex lock;
58
    mp_mutex log_file_lock;
59
    mp_cond log_file_wakeup;
60
    // --- protected by lock
61
    char **msg_levels;
62
    bool use_terminal;  // make accesses to stderr/stdout
63
    bool module;
64
    bool show_time;
65
    int blank_lines;    // number of lines usable by status
66
    int status_lines;   // number of current status lines
67
    bool color[STDERR_FILENO + 1];
68
    bool isatty[STDERR_FILENO + 1];
69
    int verbose;
70
    bool really_quiet;
71
    bool force_stderr;
72
    struct mp_log_buffer **buffers;
73
    int num_buffers;
74
    struct mp_log_buffer *early_buffer;
75
    struct mp_log_buffer *early_filebuffer;
76
    FILE *stats_file;
77
    bstr buffer;
78
    bstr term_msg;
79
    bstr term_msg_tmp;
80
    bstr status_line;
81
    struct mp_log *status_log;
82
    bstr term_status_msg;
83
    // --- must be accessed atomically
84
    /* This is incremented every time the msglevels must be reloaded.
85
     * (This is perhaps better than maintaining a globally accessible and
86
     * synchronized mp_log tree.) */
87
    atomic_ulong reload_counter;
88
    // --- owner thread only (caller of mp_msg_init() etc.)
89
    char *log_path;
90
    char *stats_path;
91
    mp_thread log_file_thread;
92
    // --- owner thread only, but frozen while log_file_thread is running
93
    FILE *log_file;
94
    struct mp_log_buffer *log_file_buffer;
95
    // --- protected by log_file_lock
96
    bool log_file_thread_active; // also termination signal for the thread
97
    int module_indent;
98
};
99
100
struct mp_log {
101
    struct mp_log_root *root;
102
    const char *prefix;
103
    const char *verbose_prefix;
104
    int max_level;              // minimum log level for this instance
105
    int level;                  // minimum log level for any outputs
106
    int terminal_level;         // minimum log level for terminal output
107
    atomic_ulong reload_counter;
108
    bstr partial[MSGL_MAX + 1];
109
};
110
111
struct mp_log_buffer {
112
    struct mp_log_root *root;
113
    mp_mutex lock;
114
    // --- protected by lock
115
    struct mp_log_buffer_entry **entries;   // ringbuffer
116
    int capacity;                           // total space in entries[]
117
    int entry0;                             // first (oldest) entry index
118
    int num_entries;                        // number of valid entries after entry0
119
    uint64_t dropped;                       // number of skipped entries
120
    bool silent;
121
    // --- immutable
122
    void (*wakeup_cb)(void *ctx);
123
    void *wakeup_cb_ctx;
124
    int level;
125
};
126
127
static const struct mp_log null_log = {0};
128
struct mp_log *const mp_null_log = (struct mp_log *)&null_log;
129
130
static bool match_mod(const char *name, const char *mod)
131
2.41M
{
132
2.41M
    if (!strcmp(mod, "all"))
133
2.41M
        return true;
134
    // Path prefix matches
135
692
    bstr b = bstr0(name);
136
692
    return bstr_eatstart0(&b, mod) && (bstr_eatstart0(&b, "/") || !b.len);
137
2.41M
}
138
139
static void update_loglevel(struct mp_log *log)
140
2.84M
{
141
2.84M
    struct mp_log_root *root = log->root;
142
2.84M
    mp_mutex_lock(&root->lock);
143
2.84M
    log->level = MSGL_STATUS + root->verbose; // default log level
144
2.84M
    if (root->really_quiet)
145
99
        log->level = -1;
146
5.26M
    for (int n = 0; root->msg_levels && root->msg_levels[n * 2 + 0]; n++) {
147
2.41M
        if (match_mod(log->verbose_prefix, root->msg_levels[n * 2 + 0]))
148
2.41M
            log->level = mp_msg_find_level(root->msg_levels[n * 2 + 1]);
149
2.41M
    }
150
2.84M
    log->terminal_level = log->level;
151
4.51M
    for (int n = 0; n < log->root->num_buffers; n++) {
152
1.66M
        int buffer_level = log->root->buffers[n]->level;
153
1.66M
        if (buffer_level == MP_LOG_BUFFER_MSGL_LOGFILE)
154
831k
            buffer_level = MSGL_DEBUG;
155
1.66M
        if (buffer_level != MP_LOG_BUFFER_MSGL_TERM)
156
831k
            log->level = MPMAX(log->level, buffer_level);
157
1.66M
    }
158
2.84M
    if (log->root->log_file)
159
0
        log->level = MPMAX(log->level, MSGL_DEBUG);
160
2.84M
    if (log->root->stats_file)
161
186
        log->level = MPMAX(log->level, MSGL_STATS);
162
2.84M
    log->level = MPMIN(log->level, log->max_level);
163
2.84M
    atomic_store(&log->reload_counter, atomic_load(&log->root->reload_counter));
164
2.84M
    mp_mutex_unlock(&root->lock);
165
2.84M
}
166
167
// Set (numerically) the maximum level that should still be output for this log
168
// instances. E.g. lev=MSGL_WARN => show only warnings and errors.
169
void mp_msg_set_max_level(struct mp_log *log, int lev)
170
0
{
171
0
    if (!log->root)
172
0
        return;
173
0
    mp_mutex_lock(&log->root->lock);
174
0
    log->max_level = MPCLAMP(lev, -1, MSGL_MAX);
175
0
    mp_mutex_unlock(&log->root->lock);
176
0
    update_loglevel(log);
177
0
}
178
179
// Get the current effective msg level.
180
// Thread-safety: see mp_msg().
181
int mp_msg_level(struct mp_log *log)
182
208M
{
183
208M
    struct mp_log_root *root = log->root;
184
208M
    if (!root)
185
4.47M
        return -1;
186
204M
    if (atomic_load_explicit(&log->reload_counter, memory_order_relaxed) !=
187
204M
        atomic_load_explicit(&root->reload_counter, memory_order_relaxed))
188
2.84M
    {
189
2.84M
        update_loglevel(log);
190
2.84M
    }
191
204M
    return log->level;
192
208M
}
193
194
static inline int term_msg_fileno(struct mp_log_root *root, int lev)
195
236M
{
196
236M
    return root->force_stderr ? STDERR_FILENO : STDOUT_FILENO;
197
236M
}
198
199
static inline FILE *term_msg_fp(struct mp_log_root *root, int lev)
200
117M
{
201
117M
    return term_msg_fileno(root, lev) == STDERR_FILENO ? stderr : stdout;
202
117M
}
203
204
static inline bool is_status_output(struct mp_log_root *root, int lev)
205
151k
{
206
151k
    if (lev == MSGL_STATUS)
207
2.28k
        return true;
208
149k
    int msg_out = term_msg_fileno(root, lev);
209
149k
    int status_out = term_msg_fileno(root, MSGL_STATUS);
210
149k
    if (msg_out != status_out && root->isatty[msg_out] != root->isatty[status_out])
211
0
        return false;
212
149k
    return true;
213
149k
}
214
215
// Reposition cursor and clear lines for outputting the status line. In certain
216
// cases, like term OSD and subtitle display, the status can consist of
217
// multiple lines.
218
static void prepare_prefix(struct mp_log_root *root, bstr *out, int lev, int term_lines)
219
151k
{
220
151k
    int new_lines = lev == MSGL_STATUS ? term_lines : 0;
221
151k
    out->len = 0;
222
223
151k
    if (!is_status_output(root, lev))
224
0
        return;
225
226
151k
    if (!root->isatty[term_msg_fileno(root, lev)]) {
227
151k
        if (root->status_lines)
228
96
            bstr_xappend(root, out, bstr0("\n"));
229
151k
        root->status_lines = new_lines;
230
151k
        return;
231
151k
    }
232
233
    // Set cursor state
234
0
    if (new_lines && !root->status_lines) {
235
0
        bstr_xappend(root, out, bstr0(TERM_ESC_HIDE_CURSOR));
236
0
    } else if (!new_lines && root->status_lines) {
237
0
        bstr_xappend(root, out, bstr0(TERM_ESC_RESTORE_CURSOR));
238
0
    }
239
240
0
    int line_skip = 0;
241
0
    if (root->status_lines) {
242
        // Clear previous status line
243
0
        bstr_xappend(root, out, bstr0("\033[1K\r"));
244
0
        bstr up_clear = bstr0("\033[A\033[K");
245
0
        for (int i = 1; i < root->status_lines; ++i)
246
0
            bstr_xappend(root, out, up_clear);
247
0
        mp_assert(root->status_lines > 0 && root->blank_lines >= root->status_lines);
248
0
        line_skip = root->blank_lines - root->status_lines;
249
0
    }
250
251
0
    if (new_lines)
252
0
        line_skip -= MPMAX(0, root->blank_lines - new_lines);
253
254
0
    if (line_skip)
255
0
        bstr_xappend_asprintf(root, out, line_skip > 0 ? "\033[%dA" : "\033[%dB", abs(line_skip));
256
257
0
    root->blank_lines = MPMAX(0, root->blank_lines - term_lines);
258
0
    root->status_lines = new_lines;
259
0
    root->blank_lines += root->status_lines;
260
0
}
261
262
static void msg_flush_status_line(struct mp_log_root *root, bool clear)
263
612k
{
264
612k
    if (!root->status_lines)
265
612k
        goto done;
266
267
4
    FILE *fp = term_msg_fp(root, MSGL_STATUS);
268
4
    if (!clear) {
269
4
        if (root->isatty[term_msg_fileno(root, MSGL_STATUS)])
270
0
            fprintf(fp, TERM_ESC_RESTORE_CURSOR);
271
4
        fprintf(fp, "\n");
272
4
        root->blank_lines = 0;
273
4
        root->status_lines = 0;
274
4
        goto done;
275
4
    }
276
277
0
    bstr term_msg = {0};
278
0
    prepare_prefix(root, &term_msg, MSGL_STATUS, 0);
279
0
    if (term_msg.len) {
280
0
        fprintf(fp, "%.*s", BSTR_P(term_msg));
281
0
        talloc_free(term_msg.start);
282
0
    }
283
284
612k
done:
285
612k
    root->status_line.len = 0;
286
612k
}
287
288
void mp_msg_flush_status_line(struct mp_log *log, bool clear)
289
612k
{
290
612k
    if (!log->root)
291
0
        return;
292
293
612k
    mp_mutex_lock(&log->root->lock);
294
612k
    msg_flush_status_line(log->root, clear);
295
612k
    mp_mutex_unlock(&log->root->lock);
296
612k
}
297
298
void mp_msg_set_term_title(struct mp_log *log, const char *title)
299
19
{
300
19
    if (log->root && title) {
301
        // Lock because printf to terminal is not necessarily atomic.
302
19
        mp_mutex_lock(&log->root->lock);
303
19
        fprintf(term_msg_fp(log->root, MSGL_STATUS), "\033]0;%s\007", title);
304
19
        mp_mutex_unlock(&log->root->lock);
305
19
    }
306
19
}
307
308
bool mp_msg_has_status_line(struct mpv_global *global)
309
2.19k
{
310
2.19k
    struct mp_log_root *root = global->log->root;
311
2.19k
    mp_mutex_lock(&root->lock);
312
2.19k
    bool r = root->status_lines > 0;
313
2.19k
    mp_mutex_unlock(&root->lock);
314
2.19k
    return r;
315
2.19k
}
316
317
static void set_term_color(void *talloc_ctx, bstr *text, int c)
318
0
{
319
0
    if (c == -1) {
320
0
        bstr_xappend(talloc_ctx, text, bstr0("\033[0m"));
321
0
        return;
322
0
    }
323
    // Pure black to gray
324
0
    if (c == 0)
325
0
        c += 8;
326
    // Pure white to light one
327
0
    if (c == 15)
328
0
        c -= 8;
329
0
    bstr_xappend_asprintf(talloc_ctx, text, "\033[%d%dm", c >= 8 ? 9 : 3, c & 7);
330
0
}
331
332
static void set_msg_color(void *talloc_ctx, bstr *text, int lev)
333
0
{
334
0
    static const int v_colors[] = {
335
0
        [MSGL_FATAL]  = 9,  // bright red
336
0
        [MSGL_ERR]    = 1,  // red
337
0
        [MSGL_WARN]   = 3,  // yellow
338
0
        [MSGL_INFO]   = -1, // default
339
0
        [MSGL_STATUS] = -1, // default
340
0
        [MSGL_V]      = 2,  // green
341
0
        [MSGL_DEBUG]  = 4,  // blue
342
0
        [MSGL_TRACE]  = 8,  // bright black aka. gray
343
0
    };
344
0
    set_term_color(talloc_ctx, text, v_colors[lev]);
345
0
}
346
347
static void pretty_print_module(struct mp_log_root *root, bstr *text,
348
                                const char *prefix, int lev)
349
2.33k
{
350
2.33k
    size_t prefix_len = strlen(prefix);
351
2.33k
    root->module_indent = MPMAX(10, MPMAX(root->module_indent, prefix_len));
352
2.33k
    bool color = root->color[term_msg_fileno(root, lev)];
353
354
    // Use random color based on the name of the module
355
2.33k
    if (color) {
356
0
        unsigned int mod = 0;
357
0
        for (int i = 0; i < prefix_len; ++i)
358
0
            mod = mod * 33 + prefix[i];
359
0
        set_term_color(root, text, (mod + 1) % 15 + 1);
360
0
    }
361
362
2.33k
    bstr_xappend_asprintf(root, text, "%*s", root->module_indent, prefix);
363
2.33k
    if (color)
364
0
        set_term_color(root, text, -1);
365
2.33k
    bstr_xappend(root, text, bstr0(": "));
366
2.33k
    if (color)
367
0
        set_msg_color(root, text, lev);
368
2.33k
}
369
370
static bool test_terminal_level(struct mp_log *log, int lev)
371
117M
{
372
117M
    return lev <= log->terminal_level && log->root->use_terminal &&
373
117M
           !(lev == MSGL_STATUS && terminal_in_background());
374
117M
}
375
376
static void append_terminal_line(struct mp_log *log, int lev,
377
                                 bstr text, bstr *term_msg, int *line_w, int term_w)
378
149k
{
379
149k
    struct mp_log_root *root = log->root;
380
381
149k
    size_t start = term_msg->len;
382
383
149k
    if (root->show_time)
384
3.19k
        bstr_xappend_asprintf(root, term_msg, "[%10.6f] ", mp_time_sec());
385
386
149k
    const char *log_prefix = (lev >= MSGL_V) || root->verbose || root->module
387
149k
                                ? log->verbose_prefix : log->prefix;
388
149k
    if (log_prefix) {
389
140k
        if (root->module) {
390
2.33k
            pretty_print_module(root, term_msg, log_prefix, lev);
391
138k
        } else {
392
138k
            bstr_xappend_asprintf(root, term_msg, "[%s] ", log_prefix);
393
138k
        }
394
140k
    }
395
396
149k
    bstr_xappend(root, term_msg, text);
397
398
149k
    const unsigned char *cut_pos = NULL;
399
149k
    int ellipsis_width = 2;
400
149k
    int width = term_disp_width(bstr_splice(*term_msg, start, term_msg->len),
401
149k
                                term_w - ellipsis_width, &cut_pos);
402
149k
    if (cut_pos) {
403
0
        int new_len = cut_pos - term_msg->start;
404
0
        bstr rem = bstrdup(NULL, (bstr){(unsigned char *)cut_pos, term_msg->len - new_len});
405
0
        void *ptr = rem.start;
406
0
        term_msg->len = new_len;
407
408
0
        bstr_xappend(root, term_msg, bstr0(".."));
409
410
0
        while (rem.len) {
411
0
            if (bstr_eatstart0(&rem, "\n")) {
412
0
                bstr_xappend(root, term_msg, bstr0("\n"));
413
0
                continue;
414
0
            }
415
0
            if (bstr_eatstart0(&rem, "\033[")) {
416
0
                bstr_xappend(root, term_msg, bstr0("\033["));
417
418
0
                while (rem.len && !((*rem.start >= '@' && *rem.start <= '~') || *rem.start == 'm')) {
419
0
                    bstr_xappend(root, term_msg, bstr_splice(rem, 0, 1));
420
0
                    rem = bstr_cut(rem, 1);
421
0
                }
422
0
                bstr_xappend(root, term_msg, bstr_splice(rem, 0, 1));
423
0
            }
424
0
            rem = bstr_cut(rem, 1);
425
0
        }
426
0
        talloc_free(ptr);
427
428
0
        width += ellipsis_width;
429
0
    }
430
149k
    *line_w = root->isatty[term_msg_fileno(root, lev)] ? width : 0;
431
149k
}
432
433
static struct mp_log_buffer_entry *log_buffer_read(struct mp_log_buffer *buffer)
434
66.8M
{
435
66.8M
    mp_assert(buffer->num_entries);
436
66.8M
    struct mp_log_buffer_entry *res = buffer->entries[buffer->entry0];
437
66.8M
    buffer->entry0 = (buffer->entry0 + 1) % buffer->capacity;
438
66.8M
    buffer->num_entries -= 1;
439
66.8M
    return res;
440
66.8M
}
441
442
static void write_msg_to_buffers(struct mp_log *log, int lev, bstr text)
443
135M
{
444
135M
    struct mp_log_root *root = log->root;
445
202M
    for (int n = 0; n < root->num_buffers; n++) {
446
67.3M
        struct mp_log_buffer *buffer = root->buffers[n];
447
67.3M
        bool wakeup = false;
448
67.3M
        mp_mutex_lock(&buffer->lock);
449
67.3M
        int buffer_level = buffer->level;
450
67.3M
        if (buffer_level == MP_LOG_BUFFER_MSGL_TERM)
451
33.6M
            buffer_level = log->terminal_level;
452
67.3M
        if (buffer_level == MP_LOG_BUFFER_MSGL_LOGFILE)
453
33.6M
            buffer_level = MPMAX(log->terminal_level, MSGL_DEBUG);
454
67.3M
        if (lev <= buffer_level && lev != MSGL_STATUS) {
455
66.8M
            if (buffer->level == MP_LOG_BUFFER_MSGL_LOGFILE) {
456
                // If the buffer is full, block until we can write again,
457
                // unless there's no write thread (died, or early filebuffer)
458
33.6M
                bool dead = false;
459
34.8M
                while (buffer->num_entries == buffer->capacity && !dead) {
460
                    // Temporary unlock is OK; buffer->level is immutable, and
461
                    // buffer can't go away because the global log lock is held.
462
1.14M
                    mp_mutex_unlock(&buffer->lock);
463
1.14M
                    mp_mutex_lock(&root->log_file_lock);
464
1.14M
                    if (root->log_file_thread_active) {
465
0
                        mp_cond_wait(&root->log_file_wakeup,
466
0
                                          &root->log_file_lock);
467
1.14M
                    } else {
468
1.14M
                        dead = true;
469
1.14M
                    }
470
1.14M
                    mp_mutex_unlock(&root->log_file_lock);
471
1.14M
                    mp_mutex_lock(&buffer->lock);
472
1.14M
                }
473
33.6M
            }
474
66.8M
            if (buffer->num_entries == buffer->capacity) {
475
20.2M
                struct mp_log_buffer_entry *skip = log_buffer_read(buffer);
476
20.2M
                talloc_free(skip);
477
20.2M
                buffer->dropped += 1;
478
20.2M
            }
479
66.8M
            struct mp_log_buffer_entry *entry = talloc_ptrtype(NULL, entry);
480
66.8M
            *entry = (struct mp_log_buffer_entry) {
481
66.8M
                .prefix = talloc_strdup(entry, log->verbose_prefix),
482
66.8M
                .level = lev,
483
66.8M
                .text = bstrdup0(entry, text),
484
66.8M
            };
485
66.8M
            int pos = (buffer->entry0 + buffer->num_entries) % buffer->capacity;
486
66.8M
            buffer->entries[pos] = entry;
487
66.8M
            buffer->num_entries += 1;
488
66.8M
            if (buffer->wakeup_cb && !buffer->silent)
489
0
                wakeup = true;
490
66.8M
        }
491
67.3M
        mp_mutex_unlock(&buffer->lock);
492
67.3M
        if (wakeup)
493
0
            buffer->wakeup_cb(buffer->wakeup_cb_ctx);
494
67.3M
    }
495
135M
}
496
497
static void dump_stats(struct mp_log *log, int lev, bstr text)
498
264
{
499
264
    struct mp_log_root *root = log->root;
500
264
    if (lev == MSGL_STATS && root->stats_file)
501
27
        fprintf(root->stats_file, "%"PRId64" %.*s\n", mp_time_ns(), BSTR_P(text));
502
264
}
503
504
static void write_term_msg(struct mp_log *log, int lev, bstr text, bstr *out)
505
117M
{
506
117M
    struct mp_log_root *root = log->root;
507
117M
    bool print_term = test_terminal_level(log, lev);
508
117M
    int fileno = term_msg_fileno(root, lev);
509
117M
    int term_w = 0, term_h = 0;
510
117M
    if (print_term && root->isatty[fileno])
511
0
        terminal_get_size(&term_w, &term_h);
512
513
117M
    out->len = 0;
514
515
    // Split away each line. Normally we require full lines; buffer partial
516
    // lines if they happen.
517
117M
    root->term_msg_tmp.len = 0;
518
117M
    int term_msg_lines = 0;
519
520
117M
    bstr str = text;
521
253M
    while (str.len) {
522
143M
        bstr line = bstr_getline(str, &str);
523
143M
        if (line.start[line.len - 1] != '\n') {
524
8.45M
            mp_assert(str.len == 0);
525
8.45M
            str = line;
526
8.45M
            break;
527
8.45M
        }
528
529
135M
        bool clip = bstr_eatstart0(&line, TERM_MSG_0);
530
135M
        if (print_term) {
531
149k
            int line_w;
532
149k
            append_terminal_line(log, lev, line, &root->term_msg_tmp, &line_w,
533
149k
                                 clip && term_w ? term_w : INT_MAX);
534
149k
            term_msg_lines += (!line_w || !term_w)
535
149k
                                ? 1 : (line_w + term_w - 1) / term_w;
536
149k
        }
537
135M
        write_msg_to_buffers(log, lev, line);
538
135M
    }
539
540
117M
    if (lev == MSGL_STATUS) {
541
2.28k
        int line_w = 0;
542
2.28k
        bool clip = bstr_eatstart0(&str, TERM_MSG_0);
543
2.28k
        if (str.len && print_term)
544
100
            append_terminal_line(log, lev, str, &root->term_msg_tmp, &line_w,
545
100
                                 clip && term_w ? term_w : INT_MAX);
546
2.28k
        term_msg_lines += !term_w ? (str.len ? 1 : 0)
547
2.28k
                                  : (line_w + term_w - 1) / term_w;
548
117M
    } else if (str.len) {
549
8.45M
        bstr_xappend(NULL, &log->partial[lev], str);
550
8.45M
    }
551
552
117M
    if (print_term && (root->term_msg_tmp.len || lev == MSGL_STATUS)) {
553
151k
        prepare_prefix(root, out, lev, term_msg_lines);
554
151k
        if (root->color[fileno] && root->term_msg_tmp.len) {
555
0
            set_msg_color(root, out, lev);
556
0
            set_term_color(root, &root->term_msg_tmp, -1);
557
0
        }
558
151k
        bstr_xappend(root, out, root->term_msg_tmp);
559
151k
    }
560
117M
}
561
562
void mp_msg_va(struct mp_log *log, int lev, const char *format, va_list va)
563
150M
{
564
150M
    if (!mp_msg_test(log, lev))
565
32.3M
        return; // do not display
566
567
117M
    struct mp_log_root *root = log->root;
568
569
117M
    mp_mutex_lock(&root->lock);
570
571
117M
    root->buffer.len = 0;
572
573
117M
    if (log->partial[lev].len)
574
8.45M
        bstr_xappend(root, &root->buffer, log->partial[lev]);
575
117M
    log->partial[lev].len = 0;
576
577
117M
    if (bstr_xappend_vasprintf(root, &root->buffer, format, va) < 0) {
578
0
        bstr_xappend(root, &root->buffer, bstr0("format error: "));
579
0
        bstr_xappend(root, &root->buffer, bstr0(format));
580
0
    }
581
582
    // Remember last status message and restore it to ensure that it is
583
    // always displayed
584
117M
    if (lev == MSGL_STATUS) {
585
2.20k
        root->status_log = log;
586
2.20k
        root->status_line.len = 0;
587
        // Use bstr_xappend instead bstrdup to reuse allocated memory
588
2.20k
        if (root->buffer.len)
589
8
            bstr_xappend(root, &root->status_line, root->buffer);
590
2.20k
    }
591
592
117M
    if (lev == MSGL_STATS) {
593
264
        dump_stats(log, lev, root->buffer);
594
117M
    } else if (lev == MSGL_STATUS && !test_terminal_level(log, lev)) {
595
        /* discard */
596
117M
    } else {
597
117M
        write_term_msg(log, lev, root->buffer, &root->term_msg);
598
599
117M
        FILE *stream = term_msg_fp(root, lev);
600
117M
        if (root->term_msg.len) {
601
149k
            root->term_status_msg.len = 0;
602
149k
            if (lev != MSGL_STATUS && root->status_line.len && root->status_log &&
603
149k
                is_status_output(root, lev) && test_terminal_level(root->status_log, MSGL_STATUS))
604
92
            {
605
92
                write_term_msg(root->status_log, MSGL_STATUS, root->status_line,
606
92
                               &root->term_status_msg);
607
92
            }
608
149k
            fwrite(root->term_msg.start, root->term_msg.len, 1, stream);
609
149k
            if (root->term_status_msg.len)
610
92
                fwrite(root->term_status_msg.start, root->term_status_msg.len, 1, stream);
611
149k
            fflush(stream);
612
149k
        }
613
117M
    }
614
615
117M
    mp_mutex_unlock(&root->lock);
616
117M
}
617
618
static void destroy_log(void *ptr)
619
7.00M
{
620
7.00M
    struct mp_log *log = ptr;
621
    // This is not managed via talloc itself, because mp_msg calls must be
622
    // thread-safe, while talloc is not thread-safe.
623
70.0M
    for (int lvl = 0; lvl <= MSGL_MAX; ++lvl)
624
63.0M
        talloc_free(log->partial[lvl].start);
625
7.00M
}
626
627
// Create a new log context, which uses talloc_ctx as talloc parent, and parent
628
// as logical parent.
629
// The name is the prefix put before the output. It's usually prefixed by the
630
// parent's name. If the name starts with "/", the parent's name is not
631
// prefixed (except in verbose mode), and if it starts with "!", the name is
632
// not printed at all (except in verbose mode).
633
// If name is NULL, the parent's name/prefix is used.
634
// Thread-safety: fully thread-safe, but keep in mind that talloc is not (so
635
//                talloc_ctx must be owned by the current thread).
636
struct mp_log *mp_log_new(void *talloc_ctx, struct mp_log *parent,
637
                          const char *name)
638
7.00M
{
639
7.00M
    mp_assert(parent);
640
7.00M
    struct mp_log *log = talloc_zero(talloc_ctx, struct mp_log);
641
7.00M
    if (!parent->root)
642
0
        return log; // same as null_log
643
7.00M
    talloc_set_destructor(log, destroy_log);
644
7.00M
    log->root = parent->root;
645
7.00M
    log->max_level = MSGL_MAX;
646
7.00M
    if (name) {
647
6.92M
        if (name[0] == '!') {
648
1.18M
            name = &name[1];
649
5.73M
        } else if (name[0] == '/') {
650
0
            name = &name[1];
651
0
            log->prefix = talloc_strdup(log, name);
652
5.73M
        } else {
653
5.73M
            log->prefix = parent->prefix
654
5.73M
                    ? talloc_asprintf(log, "%s/%s", parent->prefix, name)
655
5.73M
                    : talloc_strdup(log, name);
656
5.73M
        }
657
6.92M
        log->verbose_prefix = parent->prefix
658
6.92M
                ? talloc_asprintf(log, "%s/%s", parent->prefix, name)
659
6.92M
                : talloc_strdup(log, name);
660
6.92M
        if (log->prefix && !log->prefix[0])
661
164k
            log->prefix = NULL;
662
6.92M
        if (!log->verbose_prefix[0])
663
164k
            log->verbose_prefix = "global";
664
6.92M
    } else {
665
86.6k
        log->prefix = talloc_strdup(log, parent->prefix);
666
86.6k
        log->verbose_prefix = talloc_strdup(log, parent->verbose_prefix);
667
86.6k
    }
668
7.00M
    return log;
669
7.00M
}
670
671
void mp_msg_init(struct mpv_global *global)
672
164k
{
673
164k
    mp_assert(!global->log);
674
675
164k
    struct mp_log_root *root = talloc_zero(NULL, struct mp_log_root);
676
164k
    *root = (struct mp_log_root){
677
164k
        .global = global,
678
164k
        .reload_counter = 1,
679
164k
    };
680
681
164k
    mp_mutex_init(&root->lock);
682
164k
    mp_mutex_init(&root->log_file_lock);
683
164k
    mp_cond_init(&root->log_file_wakeup);
684
685
164k
    struct mp_log dummy = { .root = root };
686
164k
    struct mp_log *log = mp_log_new(root, &dummy, "");
687
688
164k
    global->log = log;
689
164k
}
690
691
static MP_THREAD_VOID log_file_thread(void *p)
692
0
{
693
0
    struct mp_log_root *root = p;
694
695
0
    mp_thread_set_name("log");
696
697
0
    mp_mutex_lock(&root->log_file_lock);
698
699
0
    while (root->log_file_thread_active) {
700
0
        struct mp_log_buffer_entry *e =
701
0
            mp_msg_log_buffer_read(root->log_file_buffer);
702
0
        if (e) {
703
0
            mp_mutex_unlock(&root->log_file_lock);
704
0
            fprintf(root->log_file, "[%8.3f][%c][%s] %s",
705
0
                    mp_time_sec(),
706
0
                    mp_log_levels[e->level][0], e->prefix, e->text);
707
0
            fflush(root->log_file);
708
0
            mp_mutex_lock(&root->log_file_lock);
709
0
            talloc_free(e);
710
            // Multiple threads might be blocked if the log buffer was full.
711
0
            mp_cond_broadcast(&root->log_file_wakeup);
712
0
        } else {
713
0
            mp_cond_wait(&root->log_file_wakeup, &root->log_file_lock);
714
0
        }
715
0
    }
716
717
0
    mp_mutex_unlock(&root->log_file_lock);
718
719
0
    MP_THREAD_RETURN();
720
0
}
721
722
static void wakeup_log_file(void *p)
723
0
{
724
0
    struct mp_log_root *root = p;
725
726
0
    mp_mutex_lock(&root->log_file_lock);
727
0
    mp_cond_broadcast(&root->log_file_wakeup);
728
0
    mp_mutex_unlock(&root->log_file_lock);
729
0
}
730
731
// Only to be called from the main thread.
732
static void terminate_log_file_thread(struct mp_log_root *root)
733
164k
{
734
164k
    bool wait_terminate = false;
735
736
164k
    mp_mutex_lock(&root->log_file_lock);
737
164k
    if (root->log_file_thread_active) {
738
0
        root->log_file_thread_active = false;
739
0
        mp_cond_broadcast(&root->log_file_wakeup);
740
0
        wait_terminate = true;
741
0
    }
742
164k
    mp_mutex_unlock(&root->log_file_lock);
743
744
164k
    if (wait_terminate)
745
0
        mp_thread_join(root->log_file_thread);
746
747
164k
    mp_msg_log_buffer_destroy(root->log_file_buffer);
748
164k
    root->log_file_buffer = NULL;
749
750
164k
    if (root->log_file)
751
0
        fclose(root->log_file);
752
164k
    root->log_file = NULL;
753
164k
}
754
755
// If opt is different from *current_path, update *current_path and return true.
756
// No lock must be held; passed values must be accessible without.
757
static bool check_new_path(struct mpv_global *global, char *opt,
758
                           char **current_path)
759
607k
{
760
607k
    void *tmp = talloc_new(NULL);
761
607k
    bool res = false;
762
763
607k
    char *new_path = mp_get_user_path(tmp, global, opt);
764
607k
    if (!new_path)
765
606k
        new_path = "";
766
767
607k
    char *old_path = *current_path ? *current_path : "";
768
607k
    if (strcmp(old_path, new_path) != 0) {
769
365
        talloc_free(*current_path);
770
365
        *current_path = NULL;
771
365
        if (new_path[0])
772
365
            *current_path = talloc_strdup(NULL, new_path);
773
365
        res = true;
774
365
    }
775
776
607k
    talloc_free(tmp);
777
778
607k
    return res;
779
607k
}
780
781
void mp_msg_update_msglevels(struct mpv_global *global, struct MPOpts *opts)
782
303k
{
783
303k
    struct mp_log_root *root = global->log->root;
784
785
303k
    mp_mutex_lock(&root->lock);
786
787
303k
    root->verbose = opts->verbose;
788
303k
    root->really_quiet = opts->msg_really_quiet;
789
303k
    root->module = opts->msg_module;
790
303k
    root->use_terminal = opts->use_terminal;
791
303k
    root->show_time = opts->msg_time;
792
793
303k
    if (root->really_quiet)
794
15
        root->status_lines = 0;
795
796
311k
    for (int i = STDOUT_FILENO; i <= STDERR_FILENO && root->use_terminal; ++i) {
797
7.48k
        root->isatty[i] = isatty(i);
798
7.48k
        root->color[i] = opts->msg_color && root->isatty[i];
799
7.48k
    }
800
801
303k
    m_option_type_msglevels.free(&root->msg_levels);
802
303k
    m_option_type_msglevels.copy(NULL, &root->msg_levels, &opts->msg_levels);
803
804
303k
    atomic_fetch_add(&root->reload_counter, 1);
805
303k
    mp_mutex_unlock(&root->lock);
806
807
303k
    if (check_new_path(global, opts->log_file, &root->log_path)) {
808
0
        terminate_log_file_thread(root);
809
0
        if (root->log_path) {
810
0
            root->log_file = fopen(root->log_path, "wb");
811
0
            if (root->log_file) {
812
813
                // if a logfile is created and the early filebuf still exists,
814
                // flush and destroy the early buffer
815
0
                mp_mutex_lock(&root->lock);
816
0
                struct mp_log_buffer *earlybuf = root->early_filebuffer;
817
0
                if (earlybuf)
818
0
                    root->early_filebuffer = NULL;  // but it still logs msgs
819
0
                mp_mutex_unlock(&root->lock);
820
821
0
                if (earlybuf) {
822
                    // flush, destroy before creating the normal logfile buf,
823
                    // as once the new one is created (specifically, its write
824
                    // thread), then MSGL_LOGFILE messages become blocking, but
825
                    // the early logfile buf is without dequeue - can deadlock.
826
                    // note: timestamp is unknown, we use 0.000 as indication.
827
                    // note: new messages while iterating are still flushed.
828
0
                    struct mp_log_buffer_entry *e;
829
0
                    while ((e = mp_msg_log_buffer_read(earlybuf))) {
830
0
                        fprintf(root->log_file, "[%8.3f][%c][%s] %s", 0.0,
831
0
                                mp_log_levels[e->level][0], e->prefix, e->text);
832
0
                        talloc_free(e);
833
0
                    }
834
0
                    mp_msg_log_buffer_destroy(earlybuf);  // + remove from root
835
0
                }
836
837
0
                root->log_file_buffer =
838
0
                    mp_msg_log_buffer_new(global, FILE_BUF, MP_LOG_BUFFER_MSGL_LOGFILE,
839
0
                                          wakeup_log_file, root);
840
0
                root->log_file_thread_active = true;
841
0
                if (mp_thread_create(&root->log_file_thread, log_file_thread,
842
0
                                   root))
843
0
                {
844
0
                    root->log_file_thread_active = false;
845
0
                    terminate_log_file_thread(root);
846
0
                }
847
0
            } else {
848
0
                mp_err(global->log, "Failed to open log file '%s'\n",
849
0
                       root->log_path);
850
0
            }
851
0
        }
852
0
    }
853
854
303k
    if (check_new_path(global, opts->dump_stats, &root->stats_path)) {
855
365
        bool open_error = false;
856
857
365
        mp_mutex_lock(&root->lock);
858
365
        if (root->stats_file)
859
0
            fclose(root->stats_file);
860
365
        root->stats_file = NULL;
861
365
        if (root->stats_path) {
862
365
            root->stats_file = fopen(root->stats_path, "wb");
863
365
            open_error = !root->stats_file;
864
365
        }
865
365
        mp_mutex_unlock(&root->lock);
866
867
365
        if (open_error) {
868
263
            mp_err(global->log, "Failed to open stats file '%s'\n",
869
263
                   root->stats_path);
870
263
        }
871
365
    }
872
303k
}
873
874
void mp_msg_force_stderr(struct mpv_global *global, bool force_stderr)
875
33
{
876
33
    struct mp_log_root *root = global->log->root;
877
878
33
    mp_mutex_lock(&root->lock);
879
33
    root->force_stderr = force_stderr;
880
33
    mp_mutex_unlock(&root->lock);
881
33
}
882
883
// Only to be called from the main thread.
884
bool mp_msg_has_log_file(struct mpv_global *global)
885
607k
{
886
607k
    struct mp_log_root *root = global->log->root;
887
888
607k
    return !!root->log_file;
889
607k
}
890
891
void mp_msg_uninit(struct mpv_global *global)
892
164k
{
893
164k
    struct mp_log_root *root = global->log->root;
894
164k
    mp_msg_flush_status_line(global->log, true);
895
164k
    if (root->really_quiet && root->isatty[term_msg_fileno(root, MSGL_STATUS)])
896
0
        fprintf(term_msg_fp(root, MSGL_STATUS), TERM_ESC_RESTORE_CURSOR);
897
164k
    terminate_log_file_thread(root);
898
164k
    mp_msg_log_buffer_destroy(root->early_buffer);
899
164k
    mp_msg_log_buffer_destroy(root->early_filebuffer);
900
164k
    mp_assert(root->num_buffers == 0);
901
164k
    if (root->stats_file)
902
102
        fclose(root->stats_file);
903
164k
    talloc_free(root->stats_path);
904
164k
    talloc_free(root->log_path);
905
164k
    m_option_type_msglevels.free(&root->msg_levels);
906
164k
    mp_mutex_destroy(&root->lock);
907
164k
    mp_mutex_destroy(&root->log_file_lock);
908
164k
    mp_cond_destroy(&root->log_file_wakeup);
909
164k
    talloc_free(root);
910
164k
    global->log = NULL;
911
164k
}
912
913
// early logging store log messages before they have a known destination.
914
// there are two early log buffers which are similar logically, and both cease
915
// function (if still exist, independently) once the log destination is known,
916
// or mpv init is complete (typically, after all clients/scripts init is done).
917
//
918
// - "normal" early_buffer, holds early terminal-level logs, and is handed over
919
//   to the first client which requests such log buffer, so that it sees older
920
//   messages too. further clients which request a log buffer get a new one
921
//   which accumulates messages starting at this point in time.
922
//
923
// - early_filebuffer - early log-file messages until a log file name is known.
924
//   main cases where meaningful messages are accumulated before the filename
925
//   is known are when log-file is set at mpv.conf, or from script/client init.
926
//   once a file name is known, the early buffer is flushed and destroyed.
927
//   unlike the "proper" log-file buffer, the early filebuffer is not backed by
928
//   a write thread, and hence non-blocking (can overwrite old messages).
929
//   it's also bigger than the actual file buffer (early: 5000, actual: 100).
930
931
static void mp_msg_set_early_logging_raw(struct mpv_global *global, bool enable,
932
                                         struct mp_log_buffer **root_logbuf,
933
                                         int size, int level)
934
606k
{
935
606k
    struct mp_log_root *root = global->log->root;
936
606k
    mp_mutex_lock(&root->lock);
937
938
606k
    if (enable != !!*root_logbuf) {
939
606k
        if (enable) {
940
307k
            mp_mutex_unlock(&root->lock);
941
307k
            struct mp_log_buffer *buf =
942
307k
                mp_msg_log_buffer_new(global, size, level, NULL, NULL);
943
307k
            mp_mutex_lock(&root->lock);
944
307k
            mp_assert(!*root_logbuf); // no concurrent calls to this function
945
307k
            *root_logbuf = buf;
946
307k
        } else {
947
298k
            struct mp_log_buffer *buf = *root_logbuf;
948
298k
            *root_logbuf = NULL;
949
298k
            mp_mutex_unlock(&root->lock);
950
298k
            mp_msg_log_buffer_destroy(buf);
951
298k
            return;
952
298k
        }
953
606k
    }
954
955
307k
    mp_mutex_unlock(&root->lock);
956
307k
}
957
958
void mp_msg_set_early_logging(struct mpv_global *global, bool enable)
959
303k
{
960
303k
    struct mp_log_root *root = global->log->root;
961
962
303k
    mp_msg_set_early_logging_raw(global, enable, &root->early_buffer,
963
303k
                                 EARLY_TERM_BUF, MP_LOG_BUFFER_MSGL_TERM);
964
965
    // normally MSGL_LOGFILE buffer gets a write thread, but not the early buf
966
303k
    mp_msg_set_early_logging_raw(global, enable, &root->early_filebuffer,
967
303k
                                 EARLY_FILE_BUF, MP_LOG_BUFFER_MSGL_LOGFILE);
968
303k
}
969
970
struct mp_log_buffer *mp_msg_log_buffer_new(struct mpv_global *global,
971
                                            int size, int level,
972
                                            void (*wakeup_cb)(void *ctx),
973
                                            void *wakeup_cb_ctx)
974
307k
{
975
307k
    struct mp_log_root *root = global->log->root;
976
977
307k
    mp_mutex_lock(&root->lock);
978
979
307k
    if (level == MP_LOG_BUFFER_MSGL_TERM) {
980
        // The first thing which creates a terminal-level log buffer gets the
981
        // early log buffer, if it exists. This is supposed to enable a script
982
        // to grab log messages from before it was initialized. It's OK that
983
        // this works only for 1 script and only once.
984
153k
        if (root->early_buffer) {
985
0
            struct mp_log_buffer *buffer = root->early_buffer;
986
0
            root->early_buffer = NULL;
987
0
            mp_msg_log_buffer_resize(buffer, size);
988
0
            buffer->wakeup_cb = wakeup_cb;
989
0
            buffer->wakeup_cb_ctx = wakeup_cb_ctx;
990
0
            mp_mutex_unlock(&root->lock);
991
0
            return buffer;
992
0
        }
993
153k
    }
994
995
307k
    mp_assert(size > 0);
996
997
307k
    struct mp_log_buffer *buffer = talloc_ptrtype(NULL, buffer);
998
307k
    *buffer = (struct mp_log_buffer) {
999
307k
        .root = root,
1000
307k
        .level = level,
1001
307k
        .entries = talloc_array(buffer, struct mp_log_buffer_entry *, size),
1002
307k
        .capacity = size,
1003
307k
        .wakeup_cb = wakeup_cb,
1004
307k
        .wakeup_cb_ctx = wakeup_cb_ctx,
1005
307k
    };
1006
1007
307k
    mp_mutex_init(&buffer->lock);
1008
1009
307k
    MP_TARRAY_APPEND(root, root->buffers, root->num_buffers, buffer);
1010
1011
307k
    atomic_fetch_add(&root->reload_counter, 1);
1012
307k
    mp_mutex_unlock(&root->lock);
1013
1014
307k
    return buffer;
1015
307k
}
1016
1017
void mp_msg_log_buffer_resize(struct mp_log_buffer *buffer, int size)
1018
0
{
1019
0
    mp_mutex_lock(&buffer->lock);
1020
1021
0
    mp_assert(size > 0);
1022
0
    if (buffer->capacity < size &&
1023
0
        buffer->entry0 + buffer->num_entries <= buffer->capacity) {
1024
        // shortcut if buffer doesn't wrap
1025
0
        buffer->entries = talloc_realloc(buffer, buffer->entries,
1026
0
                                         struct mp_log_buffer_entry *, size);
1027
0
    } else if (buffer->capacity != size) {
1028
0
        struct mp_log_buffer_entry **entries =
1029
0
            talloc_array(buffer, struct mp_log_buffer_entry *, size);
1030
0
        int num_entries = 0;
1031
0
        for (int i = buffer->num_entries - 1; i >= 0; i--) {
1032
0
            int entry = (buffer->entry0 + i) % buffer->num_entries;
1033
0
            struct mp_log_buffer_entry *res = buffer->entries[entry];
1034
0
            if (num_entries < size) {
1035
0
                entries[num_entries++] = res;
1036
0
            } else {
1037
0
                talloc_free(res);
1038
0
                buffer->dropped += 1;
1039
0
            }
1040
0
        }
1041
0
        talloc_free(buffer->entries);
1042
0
        buffer->entries = entries;
1043
0
        buffer->entry0 = 0;
1044
0
        buffer->num_entries = num_entries;
1045
0
    }
1046
0
    buffer->capacity = size;
1047
1048
0
    mp_mutex_unlock(&buffer->lock);
1049
0
}
1050
1051
void mp_msg_log_buffer_set_silent(struct mp_log_buffer *buffer, bool silent)
1052
0
{
1053
0
    mp_mutex_lock(&buffer->lock);
1054
0
    buffer->silent = silent;
1055
0
    mp_mutex_unlock(&buffer->lock);
1056
0
}
1057
1058
void mp_msg_log_buffer_destroy(struct mp_log_buffer *buffer)
1059
961k
{
1060
961k
    if (!buffer)
1061
653k
        return;
1062
1063
307k
    struct mp_log_root *root = buffer->root;
1064
1065
307k
    mp_mutex_lock(&root->lock);
1066
1067
307k
    for (int n = 0; n < root->num_buffers; n++) {
1068
307k
        if (root->buffers[n] == buffer) {
1069
307k
            MP_TARRAY_REMOVE_AT(root->buffers, root->num_buffers, n);
1070
307k
            goto found;
1071
307k
        }
1072
307k
    }
1073
1074
0
    MP_ASSERT_UNREACHABLE();
1075
1076
307k
found:
1077
1078
46.9M
    while (buffer->num_entries)
1079
46.6M
        talloc_free(log_buffer_read(buffer));
1080
1081
307k
    mp_mutex_destroy(&buffer->lock);
1082
307k
    talloc_free(buffer);
1083
1084
307k
    atomic_fetch_add(&root->reload_counter, 1);
1085
307k
    mp_mutex_unlock(&root->lock);
1086
307k
}
1087
1088
// Return a queued message, or if the buffer is empty, NULL.
1089
// Thread-safety: one buffer can be read by a single thread only.
1090
struct mp_log_buffer_entry *mp_msg_log_buffer_read(struct mp_log_buffer *buffer)
1091
0
{
1092
0
    struct mp_log_buffer_entry *res = NULL;
1093
1094
0
    mp_mutex_lock(&buffer->lock);
1095
1096
0
    if (!buffer->silent && buffer->num_entries) {
1097
0
        if (buffer->dropped) {
1098
0
            res = talloc_ptrtype(NULL, res);
1099
0
            *res = (struct mp_log_buffer_entry) {
1100
0
                .prefix = "overflow",
1101
0
                .level = MSGL_FATAL,
1102
0
                .text = talloc_asprintf(res,
1103
0
                    "log message buffer overflow: %"PRId64" messages skipped\n",
1104
0
                    buffer->dropped),
1105
0
            };
1106
0
            buffer->dropped = 0;
1107
0
        } else {
1108
0
            res = log_buffer_read(buffer);
1109
0
        }
1110
0
    }
1111
1112
0
    mp_mutex_unlock(&buffer->lock);
1113
1114
0
    return res;
1115
0
}
1116
1117
// Thread-safety: fully thread-safe, but keep in mind that the lifetime of
1118
//                log must be guaranteed during the call.
1119
//                Never call this from signal handlers.
1120
void mp_msg(struct mp_log *log, int lev, const char *format, ...)
1121
150M
{
1122
150M
    va_list va;
1123
150M
    va_start(va, format);
1124
150M
    mp_msg_va(log, lev, format, va);
1125
150M
    va_end(va);
1126
150M
}
1127
1128
const char *const mp_log_levels[MSGL_MAX + 1] = {
1129
    [MSGL_FATAL]        = "fatal",
1130
    [MSGL_ERR]          = "error",
1131
    [MSGL_WARN]         = "warn",
1132
    [MSGL_INFO]         = "info",
1133
    [MSGL_STATUS]       = "status",
1134
    [MSGL_V]            = "v",
1135
    [MSGL_DEBUG]        = "debug",
1136
    [MSGL_TRACE]        = "trace",
1137
    [MSGL_STATS]        = "stats",
1138
};
1139
1140
const int mp_mpv_log_levels[MSGL_MAX + 1] = {
1141
    [MSGL_FATAL]        = MPV_LOG_LEVEL_FATAL,
1142
    [MSGL_ERR]          = MPV_LOG_LEVEL_ERROR,
1143
    [MSGL_WARN]         = MPV_LOG_LEVEL_WARN,
1144
    [MSGL_INFO]         = MPV_LOG_LEVEL_INFO,
1145
    [MSGL_STATUS]       = 0, // never used
1146
    [MSGL_V]            = MPV_LOG_LEVEL_V,
1147
    [MSGL_DEBUG]        = MPV_LOG_LEVEL_DEBUG,
1148
    [MSGL_TRACE]        = MPV_LOG_LEVEL_TRACE,
1149
    [MSGL_STATS]        = 0, // never used
1150
};
1151
1152
int mp_msg_find_level(const char *s)
1153
2.56M
{
1154
20.5M
    for (int n = 0; n < MP_ARRAY_SIZE(mp_log_levels); n++) {
1155
20.5M
        if (mp_log_levels[n] && !strcasecmp(s, mp_log_levels[n]))
1156
2.56M
            return n;
1157
20.5M
    }
1158
2.87k
    return -1;
1159
2.56M
}