summaryrefslogtreecommitdiffstats
path: root/options/parse_configfile.c
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2015-04-04 00:02:34 +0200
committerwm4 <wm4@nowhere>2015-04-04 01:04:04 +0200
commit9ea0590371814f8ce1168056ee2fb228d9b30673 (patch)
tree95594b99ff113646ca01717cde0d2d5450ec46e0 /options/parse_configfile.c
parentbf3e0bc1dad8a51e60494388e3559c9bb5f1d7e5 (diff)
downloadmpv-9ea0590371814f8ce1168056ee2fb228d9b30673.tar.bz2
mpv-9ea0590371814f8ce1168056ee2fb228d9b30673.tar.xz
options: rewrite config file parser
The format doesn't change. Some details are different, though. For example, it will now accept option values with spaces even if they're not quoted. (I see no reason why the user should be forced to add quotes.) The code is now smaller and should be much easier to extend. It also can load config from in-memory buffers, which might be helpful in the future. read_file() should eventually be replaced with stream_read_complete(). But since the latter function may access options under various circumstances, and also needs access to the mpv_global struct, there is a separate implementation for now.
Diffstat (limited to 'options/parse_configfile.c')
-rw-r--r--options/parse_configfile.c352
1 files changed, 142 insertions, 210 deletions
diff --git a/options/parse_configfile.c b/options/parse_configfile.c
index 1f0834959c..b0bf814793 100644
--- a/options/parse_configfile.c
+++ b/options/parse_configfile.c
@@ -33,242 +33,174 @@
#include "m_option.h"
#include "m_config.h"
-/// Maximal include depth.
-#define MAX_RECURSION_DEPTH 8
+// 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;
+}
-// Load options and profiles from 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 sucess, -1 on error, 0 if file not accessible.
-int m_config_parse_config_file(m_config_t *config, const char *conffile,
- char *initial_section, int flags)
+static int m_config_parse(m_config_t *config, const char *location, bstr data,
+ char *initial_section, int flags)
{
-#define PRINT_LINENUM MP_ERR(config, "%s:%d: ", conffile, line_num)
-#define MAX_LINE_LEN 10000
-#define MAX_OPT_LEN 1000
-#define MAX_PARAM_LEN 1500
- FILE *fp = NULL;
- char *line = NULL;
- char opt[MAX_OPT_LEN + 1];
- char param[MAX_PARAM_LEN + 1];
- char c; /* for the "" and '' check */
- int tmp;
- int line_num = 0;
- int line_pos; /* line pos */
- int opt_pos; /* opt pos */
- int param_pos; /* param pos */
- int ret = 1;
- int errors = 0;
m_profile_t *profile = m_config_add_profile(config, initial_section);
+ void *tmp = talloc_new(NULL);
+ int line_no = 0;
+ int errors = 0;
- flags = flags | M_SETOPT_FROM_CONFIG_FILE;
-
- MP_VERBOSE(config, "Reading config file %s\n", conffile);
-
- if (config->recursion_depth > MAX_RECURSION_DEPTH) {
- MP_ERR(config, "Maximum 'include' nesting depth exceeded.\n");
- ret = -1;
- goto out;
- }
-
- if ((line = malloc(MAX_LINE_LEN + 1)) == NULL) {
- ret = -1;
- goto out;
- } else
-
- MP_VERBOSE(config, "\n");
-
- if ((fp = fopen(conffile, "r")) == NULL) {
- MP_VERBOSE(config, "Can't open config file: %s\n", mp_strerror(errno));
- ret = 0;
- goto out;
- }
-
- while (fgets(line, MAX_LINE_LEN, fp)) {
- if (errors >= 16) {
- MP_FATAL(config, "too many errors\n");
- goto out;
- }
-
- line_num++;
- line_pos = 0;
+ bstr_eatstart0(&data, "\xEF\xBB\xBF"); // skip BOM
- /* skip BOM */
- if (strncmp(line, "\xEF\xBB\xBF", 3) == 0)
- line_pos += 3;
+ while (data.len) {
+ talloc_free_children(tmp);
+ bool ok = false;
- /* skip whitespaces */
- while (mp_isspace(line[line_pos]))
- ++line_pos;
+ line_no++;
+ char loc[512];
+ snprintf(loc, sizeof(loc), "%s:%d:", location, line_no);
- /* EOL / comment */
- if (line[line_pos] == '\0' || line[line_pos] == '#')
+ bstr line = bstr_strip_linebreaks(bstr_getline(data, &data));
+ if (!skip_ws(&line))
continue;
- /* read option. */
- for (opt_pos = 0; mp_isprint(line[line_pos]) &&
- line[line_pos] != ' ' &&
- line[line_pos] != '#' &&
- line[line_pos] != '='; /* NOTHING */) {
- opt[opt_pos++] = line[line_pos++];
- if (opt_pos >= MAX_OPT_LEN) {
- PRINT_LINENUM;
- MP_ERR(config, "option name too long\n");
- errors++;
- ret = -1;
- goto nextline;
+ // 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 (opt_pos == 0) {
- PRINT_LINENUM;
- MP_ERR(config, "parse error\n");
- ret = -1;
- errors++;
- continue;
- }
- opt[opt_pos] = '\0';
-
- /* Profile declaration */
- if (opt_pos > 2 && opt[0] == '[' && opt[opt_pos - 1] == ']') {
- opt[opt_pos - 1] = '\0';
- profile = m_config_add_profile(config, opt + 1);
+ if (skip_ws(&line)) {
+ MP_ERR(config, "%s unparseable extra characters: '%.*s'\n",
+ loc, BSTR_P(line));
+ goto error;
+ }
+ profile = m_config_add_profile(config, bstrto0(tmp, profilename));
continue;
}
- /* skip whitespaces */
- while (mp_isspace(line[line_pos]))
- ++line_pos;
-
- param_pos = 0;
- bool param_set = false;
-
- /* check '=' */
- if (line[line_pos] == '=') {
- line_pos++;
- param_set = true;
-
- /* whitespaces... */
- while (mp_isspace(line[line_pos]))
- ++line_pos;
-
- /* read the parameter */
- if (line[line_pos] == '"' || line[line_pos] == '\'') {
- c = line[line_pos];
- ++line_pos;
- for (param_pos = 0; line[line_pos] != c; /* NOTHING */) {
- if (!line[line_pos]) {
- PRINT_LINENUM;
- MP_ERR(config, "unterminated quotes\n");
- ret = -1;
- errors++;
- goto nextline;
- }
- param[param_pos++] = line[line_pos++];
- if (param_pos >= MAX_PARAM_LEN) {
- PRINT_LINENUM;
- MP_ERR(config, "option %s has a too long parameter\n", opt);
- ret = -1;
- errors++;
- goto nextline;
- }
+ 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;
}
- line_pos++; /* skip the closing " or ' */
- goto param_done;
- }
-
- if (line[line_pos] == '%') {
- char *start = &line[line_pos + 1];
- char *end = start;
- unsigned long len = strtoul(start, &end, 10);
- if (start != end && end[0] == '%') {
- if (len >= MAX_PARAM_LEN - 1 ||
- strlen(end + 1) < len)
- {
- PRINT_LINENUM;
- MP_ERR(config, "bogus %% length\n");
- ret = -1;
- errors++;
- goto nextline;
- }
- param_pos = snprintf(param, sizeof(param), "%.*s",
- (int)len, end + 1);
- line_pos += 1 + (end - start) + 1 + len;
- goto param_done;
+ } 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 broken escaping with '%%'\n", loc);
+ goto error;
}
+ value = bstr_splice(line, 0, len);
+ line = bstr_cut(line, 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 unparseable extra characters: '%.*s'\n",
+ loc, BSTR_P(line));
+ goto error;
+ }
- for (param_pos = 0; mp_isprint(line[line_pos])
- && !mp_isspace(line[line_pos])
- && line[line_pos] != '#'; /* NOTHING */) {
- param[param_pos++] = line[line_pos++];
- if (param_pos >= MAX_PARAM_LEN) {
- PRINT_LINENUM;
- MP_ERR(config, "too long parameter\n");
- ret = -1;
- errors++;
- goto nextline;
- }
+ int res;
+ if (profile) {
+ if (bstr_equals0(option, "profile-desc")) {
+ m_profile_set_desc(profile, value);
+ res = 0;
+ } else {
+ res = m_config_set_profile_option(config, profile, option, value);
}
-
- param_done:
-
- while (mp_isspace(line[line_pos]))
- ++line_pos;
+ } else {
+ res = m_config_set_option_ext(config, option, value, flags);
}
- param[param_pos] = '\0';
-
- /* EOL / comment */
- if (line[line_pos] != '\0' && line[line_pos] != '#') {
- PRINT_LINENUM;
- MP_ERR(config, "extra characters: %s\n", line + line_pos);
- ret = -1;
+ if (res < 0) {
+ MP_ERR(config, "%s setting option %.*s='%.*s' failed.\n",
+ loc, BSTR_P(option), BSTR_P(value));
+ goto error;
}
- bstr bopt = bstr0(opt);
- bstr bparam = bstr0(param);
-
- if (bopt.len >= 3)
- bstr_eatstart0(&bopt, "--");
-
- if (profile && bstr_equals0(bopt, "profile-desc")) {
- m_profile_set_desc(profile, bparam);
- goto nextline;
+ ok = true;
+ error:
+ if (!ok)
+ errors++;
+ if (errors > 16) {
+ MP_ERR(config, "%s: too many errors, stopping.\n", location);
+ break;
}
+ }
- bool need_param = m_config_option_requires_param(config, bopt) > 0;
- if (need_param && !param_set) {
- PRINT_LINENUM;
- MP_ERR(config, "error parsing option %.*s=%.*s: %s\n",
- BSTR_P(bopt), BSTR_P(bparam),
- m_option_strerror(M_OPT_MISSING_PARAM));
- continue;
- }
+ talloc_free(tmp);
+ return 1;
+}
- if (profile) {
- tmp = m_config_set_profile_option(config, profile, bopt, bparam);
- } else {
- tmp = m_config_set_option_ext(config, bopt, bparam, flags);
- }
- if (tmp < 0) {
- PRINT_LINENUM;
- MP_ERR(config, "setting option %.*s='%.*s' failed.\n",
- BSTR_P(bopt), BSTR_P(bparam));
+static bstr read_file(struct mp_log *log, const char *filename)
+{
+ FILE *f = fopen(filename, "rb");
+ if (!f) {
+ mp_verbose(log, "Can't open config file: %s\n", mp_strerror(errno));
+ return (bstr){0};
+ }
+ char *data = talloc_array(NULL, char, 0);
+ size_t size = 0;
+ while (1) {
+ size_t left = talloc_get_size(data) - size;
+ if (!left) {
+ MP_TARRAY_GROW(NULL, data, size + 1);
continue;
- /* break */
}
-nextline:
- ;
+ size_t s = fread(data + size, 1, left, f);
+ if (!s) {
+ if (ferror(f))
+ mp_err(log, "Error reading config file.\n");
+ fclose(f);
+ MP_TARRAY_APPEND(NULL, data, size, 0);
+ return (bstr){data, size - 1};
+ }
+ size += s;
}
+ assert(0);
+}
-out:
- free(line);
- if (fp)
- fclose(fp);
- config->recursion_depth -= 1;
- if (ret < 0) {
- MP_FATAL(config, "Error loading config file %s.\n",
- conffile);
- }
- return ret;
+// Load options and profiles from 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, const char *conffile,
+ char *initial_section, int flags)
+{
+ flags = flags | M_SETOPT_FROM_CONFIG_FILE;
+
+ MP_VERBOSE(config, "Reading config file %s\n", conffile);
+
+ bstr data = read_file(config->log, conffile);
+ if (!data.start)
+ return 0;
+
+ int r = m_config_parse(config, conffile, data, initial_section, flags);
+ talloc_free(data.start);
+ return r;
}