mpv/options/parse_configfile.c

179 lines
5.8 KiB
C

/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include "osdep/io.h"
#include "parse_configfile.h"
#include "common/common.h"
#include "common/msg.h"
#include "misc/ctype.h"
#include "m_option.h"
#include "m_config.h"
#include "stream/stream.h"
// Skip whitespace and comments (assuming there are no line breaks)
static bool skip_ws(bstr *s)
{
*s = bstr_lstrip(*s);
if (bstr_startswith0(*s, "#"))
s->len = 0;
return s->len;
}
int m_config_parse(m_config_t *config, const char *location, bstr data,
char *initial_section, int flags)
{
m_profile_t *profile = m_config_add_profile(config, initial_section);
void *tmp = talloc_new(NULL);
int line_no = 0;
int errors = 0;
bstr_eatstart0(&data, "\xEF\xBB\xBF"); // skip BOM
while (data.len) {
talloc_free_children(tmp);
bool ok = false;
line_no++;
char loc[512];
snprintf(loc, sizeof(loc), "%s:%d:", location, line_no);
bstr line = bstr_strip_linebreaks(bstr_getline(data, &data));
if (!skip_ws(&line))
continue;
// Profile declaration
if (bstr_eatstart0(&line, "[")) {
bstr profilename;
if (!bstr_split_tok(line, "]", &profilename, &line)) {
MP_ERR(config, "%s missing closing ]\n", loc);
goto error;
}
if (skip_ws(&line)) {
MP_ERR(config, "%s unparsable extra characters: '%.*s'\n",
loc, BSTR_P(line));
goto error;
}
profile = m_config_add_profile(config, bstrto0(tmp, profilename));
continue;
}
bstr_eatstart0(&line, "--");
bstr option = line;
while (line.len && (mp_isalnum(line.start[0]) || line.start[0] == '_' ||
line.start[0] == '-'))
line = bstr_cut(line, 1);
option.len = option.len - line.len;
skip_ws(&line);
bstr value = {0};
if (bstr_eatstart0(&line, "=")) {
skip_ws(&line);
if (line.len && (line.start[0] == '"' || line.start[0] == '\'')) {
// Simple quoting, like "value"
char term[2] = {line.start[0], 0};
line = bstr_cut(line, 1);
if (!bstr_split_tok(line, term, &value, &line)) {
MP_ERR(config, "%s unterminated quote\n", loc);
goto error;
}
} else if (bstr_eatstart0(&line, "%")) {
// Quoting with length, like %5%value
bstr rest;
long long len = bstrtoll(line, &rest, 10);
if (rest.len == line.len || !bstr_eatstart0(&rest, "%") ||
len > rest.len)
{
MP_ERR(config, "%s fixed-length quoting expected - put "
"\"quotes\" around the option value if you did not "
"intend to use this, but your option value starts "
"with '%%'\n", loc);
goto error;
}
value = bstr_splice(rest, 0, len);
line = bstr_cut(rest, len);
} else {
// No quoting; take everything until the comment or end of line
int end = bstrchr(line, '#');
value = bstr_strip(end < 0 ? line : bstr_splice(line, 0, end));
line.len = 0;
}
}
if (skip_ws(&line)) {
MP_ERR(config, "%s unparsable extra characters: '%.*s'\n",
loc, BSTR_P(line));
goto error;
}
int res = m_config_set_profile_option(config, profile, option, value);
if (res < 0) {
MP_ERR(config, "%s setting option %.*s='%.*s' failed.\n",
loc, BSTR_P(option), BSTR_P(value));
goto error;
}
ok = true;
error:
if (!ok)
errors++;
if (errors > 16) {
MP_ERR(config, "%s: too many errors, stopping.\n", location);
break;
}
}
if (config->recursion_depth == 0)
m_config_finish_default_profile(config, flags);
talloc_free(tmp);
return 1;
}
// Load options and profiles from a config file.
// conffile: path to the config file
// initial_section: default section where to add normal options
// flags: M_SETOPT_* bits
// returns: 1 on success, -1 on error, 0 if file not accessible.
int m_config_parse_config_file(m_config_t *config, struct mpv_global *global,
const char *conffile, char *initial_section,
int flags)
{
flags = flags | M_SETOPT_FROM_CONFIG_FILE;
MP_VERBOSE(config, "Reading config file %s\n", conffile);
struct stream *s = stream_create(conffile, STREAM_READ | STREAM_ORIGIN_DIRECT,
NULL, global);
if (!s)
return 0;
bstr data = stream_read_complete(s, s, 1000000000);
if (!data.start)
return 0;
int r = m_config_parse(config, conffile, data, initial_section, flags);
talloc_free(data.start);
free_stream(s);
return r;
}