Coverage Report

Created: 2026-11-11 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mpv/options/parse_configfile.c
Line
Count
Source
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 <stdio.h>
19
#include <stdlib.h>
20
#include <string.h>
21
#include <errno.h>
22
#include <assert.h>
23
24
#include "osdep/io.h"
25
26
#include "parse_configfile.h"
27
#include "common/common.h"
28
#include "common/msg.h"
29
#include "misc/ctype.h"
30
#include "m_option.h"
31
#include "m_config.h"
32
#include "stream/stream.h"
33
34
// Skip whitespace and comments (assuming there are no line breaks)
35
static bool skip_ws(bstr *s)
36
69.8M
{
37
69.8M
    *s = bstr_lstrip(*s);
38
69.8M
    if (bstr_startswith0(*s, "#"))
39
2.73M
        s->len = 0;
40
69.8M
    return s->len;
41
69.8M
}
42
43
int m_config_parse(m_config_t *config, const char *location, bstr data,
44
                   char *initial_section, int flags)
45
240k
{
46
240k
    m_profile_t *profile = m_config_add_profile(config, initial_section);
47
240k
    void *tmp = talloc_new(NULL);
48
240k
    int line_no = 0;
49
240k
    int errors = 0;
50
51
240k
    bstr_eatstart0(&data, "\xEF\xBB\xBF"); // skip BOM
52
53
23.3M
    while (data.len) {
54
23.1M
        talloc_free_children(tmp);
55
23.1M
        bool ok = false;
56
57
23.1M
        line_no++;
58
23.1M
        char loc[512];
59
23.1M
        snprintf(loc, sizeof(loc), "%s:%d:", location, line_no);
60
61
23.1M
        bstr line = bstr_strip_linebreaks(bstr_getline(data, &data));
62
23.1M
        if (!skip_ws(&line))
63
5.70M
            continue;
64
65
        // Profile declaration
66
17.4M
        if (bstr_eatstart0(&line, "[")) {
67
2.74M
            bstr profilename;
68
2.74M
            if (!bstr_split_tok(line, "]", &profilename, &line)) {
69
606
                MP_ERR(config, "%s missing closing ]\n", loc);
70
606
                goto error;
71
606
            }
72
2.74M
            if (skip_ws(&line)) {
73
185
                MP_ERR(config, "%s unparsable extra characters: '%.*s'\n",
74
185
                       loc, BSTR_P(line));
75
185
                goto error;
76
185
            }
77
2.74M
            profile = m_config_add_profile(config, bstrto0(tmp, profilename));
78
2.74M
            continue;
79
2.74M
        }
80
81
14.6M
        bstr_eatstart0(&line, "--");
82
83
14.6M
        bstr option = line;
84
197M
        while (line.len && (mp_isalnum(line.start[0]) || line.start[0] == '_' ||
85
30.3M
                            line.start[0] == '-'))
86
182M
            line = bstr_cut(line, 1);
87
14.6M
        option.len = option.len - line.len;
88
14.6M
        skip_ws(&line);
89
90
14.6M
        bstr value = {0};
91
14.6M
        if (bstr_eatstart0(&line, "=")) {
92
14.6M
            skip_ws(&line);
93
14.6M
            if (line.len && (line.start[0] == '"' || line.start[0] == '\'')) {
94
                // Simple quoting, like "value"
95
185
                char term[2] = {line.start[0], 0};
96
185
                line = bstr_cut(line, 1);
97
185
                if (!bstr_split_tok(line, term, &value, &line)) {
98
123
                    MP_ERR(config, "%s unterminated quote\n", loc);
99
123
                    goto error;
100
123
                }
101
14.6M
            } else if (bstr_eatstart0(&line, "%")) {
102
                // Quoting with length, like %5%value
103
965
                bstr rest;
104
965
                long long len = bstrtoll(line, &rest, 10);
105
965
                if (rest.len == line.len || !bstr_eatstart0(&rest, "%") ||
106
805
                    len > rest.len)
107
317
                {
108
317
                    MP_ERR(config, "%s fixed-length quoting expected - put "
109
317
                           "\"quotes\" around the option value if you did not "
110
317
                           "intend to use this, but your option value starts "
111
317
                           "with '%%'\n", loc);
112
317
                    goto error;
113
317
                }
114
648
                value = bstr_splice(rest, 0, len);
115
648
                line = bstr_cut(rest, len);
116
14.6M
            } else {
117
                // No quoting; take everything until the comment or end of line
118
14.6M
                int end = bstrchr(line, '#');
119
14.6M
                value = bstr_strip(end < 0 ? line : bstr_splice(line, 0, end));
120
14.6M
                line.len = 0;
121
14.6M
            }
122
14.6M
        }
123
14.6M
        if (skip_ws(&line)) {
124
10.0k
            MP_ERR(config, "%s unparsable extra characters: '%.*s'\n",
125
10.0k
                   loc, BSTR_P(line));
126
10.0k
            goto error;
127
10.0k
        }
128
129
14.6M
        int res = m_config_set_profile_option(config, profile, option, value);
130
14.6M
        if (res < 0) {
131
1.15M
            MP_ERR(config, "%s setting option %.*s='%.*s' failed.\n",
132
1.15M
                   loc, BSTR_P(option), BSTR_P(value));
133
1.15M
            goto error;
134
1.15M
        }
135
136
13.5M
        ok = true;
137
14.6M
    error:
138
14.6M
        if (!ok)
139
1.17M
            errors++;
140
14.6M
        if (errors > 16) {
141
308
            MP_ERR(config, "%s: too many errors, stopping.\n", location);
142
308
            break;
143
308
        }
144
14.6M
    }
145
146
240k
    if (config->recursion_depth == 0)
147
240k
        m_config_finish_default_profile(config, flags);
148
149
240k
    talloc_free(tmp);
150
240k
    return 1;
151
240k
}
152
153
// Load options and profiles from a config file.
154
//  conffile: path to the config file
155
//  initial_section: default section where to add normal options
156
//  flags: M_SETOPT_* bits
157
//  returns: 1 on success, -1 on error, 0 if file not accessible.
158
int m_config_parse_config_file(m_config_t *config, struct mpv_global *global,
159
                               const char *conffile, char *initial_section,
160
                               int flags)
161
13.0k
{
162
13.0k
    flags = flags | M_SETOPT_FROM_CONFIG_FILE;
163
164
13.0k
    MP_VERBOSE(config, "Reading config file %s\n", conffile);
165
166
13.0k
    int r = 0;
167
168
13.0k
    bstr data = stream_read_file2(conffile, NULL, STREAM_ORIGIN_DIRECT | STREAM_READ,
169
13.0k
                                  global, 1000000000);
170
13.0k
    if (data.start)
171
13.0k
        r = m_config_parse(config, conffile, data, initial_section, flags);
172
173
13.0k
    talloc_free(data.start);
174
13.0k
    return r;
175
13.0k
}