summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--options/m_config.c8
-rw-r--r--options/parse_configfile.c352
2 files changed, 150 insertions, 210 deletions
diff --git a/options/m_config.c b/options/m_config.c
index 5925b7a159..f4179e08cf 100644
--- a/options/m_config.c
+++ b/options/m_config.c
@@ -42,6 +42,8 @@ static const union m_option_value default_value;
// Profiles allow to predefine some sets of options that can then
// be applied later on with the internal -profile option.
#define MAX_PROFILE_DEPTH 20
+// Maximal include depth.
+#define MAX_RECURSION_DEPTH 8
struct m_profile {
struct m_profile *next;
@@ -66,8 +68,14 @@ static int parse_include(struct m_config *config, struct bstr param, bool set,
return M_OPT_MISSING_PARAM;
if (!set)
return 1;
+ if (config->recursion_depth >= MAX_RECURSION_DEPTH) {
+ MP_ERR(config, "Maximum 'include' nesting depth exceeded.\n");
+ return M_OPT_INVALID;
+ }
char *filename = bstrdup0(NULL, param);
+ config->recursion_depth += 1;
config->includefunc(config->includefunc_ctx, filename, flags);
+ config->recursion_depth -= 1;
talloc_free(filename);
return 1;
}
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;
}