/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 | } |