Esempio n. 1
0
void mp_parse_cfgfiles(struct MPContext *mpctx)
{
    struct MPOpts *opts = mpctx->opts;
    if (!opts->load_config)
        return;

    mp_mk_config_dir(mpctx->global, "");

    m_config_t *conf = mpctx->mconfig;
    char *section = NULL;
    bool encoding = opts->encode_opts &&
        opts->encode_opts->file && opts->encode_opts->file[0];
    // In encoding mode, we don't want to apply normal config options.
    // So we "divert" normal options into a separate section, and the diverted
    // section is never used - unless maybe it's explicitly referenced from an
    // encoding profile.
    if (encoding)
        section = "playback-default";

    // The #if is a stupid hack to avoid errors if libavfilter is not available.
#if HAVE_LIBAVFILTER && HAVE_ENCODING
    char *cf = mp_find_config_file(NULL, mpctx->global, "encoding-profiles.conf");
    if (cf)
        m_config_parse_config_file(mpctx->mconfig, cf, SECT_ENCODE, 0);
    talloc_free(cf);
#endif

    load_all_cfgfiles(mpctx, section, "config");
    load_all_cfgfiles(mpctx, section, "mpv.conf");

    if (encoding)
        m_config_set_profile(conf, m_config_add_profile(conf, SECT_ENCODE), 0);
}
Esempio n. 2
0
bool mp_parse_cfgfiles(struct MPContext *mpctx)
{
    struct MPOpts *opts = mpctx->opts;
    if (!opts->load_config)
        return true;

    m_config_t *conf = mpctx->mconfig;
    void *tmp = talloc_new(NULL);
    bool r = true;
    char *conffile;
    char *section = NULL;
    bool encoding = opts->encode_output.file && *opts->encode_output.file;
    // In encoding mode, we don't want to apply normal config options.
    // So we "divert" normal options into a separate section, and the diverted
    // section is never used - unless maybe it's explicitly referenced from an
    // encoding profile.
    if (encoding)
        section = "playback-default";

    // The #if is a stupid hack to avoid errors if libavfilter is not available.
#if HAVE_LIBAVFILTER && HAVE_ENCODING
    conffile = mp_find_config_file(tmp, mpctx->global, "encoding-profiles.conf");
    if (conffile && mp_path_exists(conffile))
        m_config_parse_config_file(mpctx->mconfig, conffile, SECT_ENCODE, 0);
#endif

    conffile = mp_find_global_config_file(tmp, mpctx->global, "mpv.conf");
    if (conffile && m_config_parse_config_file(conf, conffile, section, 0) < 0) {
        r = false;
        goto done;
    }
    mp_mk_config_dir(mpctx->global, NULL);
    if (!(conffile = mp_find_user_config_file(tmp, mpctx->global, "config")))
        MP_ERR(mpctx, "mp_find_user_config_file(\"config\") problem\n");
    else if (m_config_parse_config_file(conf, conffile, section, 0) < 0) {
        r = false;
        goto done;
    }

    if (encoding)
        m_config_set_profile(conf, m_config_add_profile(conf, SECT_ENCODE), 0);

done:
    talloc_free(tmp);
    return r;
}
Esempio n. 3
0
/** \param config The config object.
 *  \param conffile Path to the config file.
 *  \return 1 on sucess, -1 on error.
 */
int m_config_parse_config_file(m_config_t *config, const char *conffile)
{
#define PRINT_LINENUM   mp_msg(MSGT_CFGPARSER, MSGL_V, "%s(%d): ", conffile, line_num)
#define MAX_LINE_LEN    10000
#define MAX_OPT_LEN     1000
#define MAX_PARAM_LEN   1500
    FILE *fp;
    char *line;
    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;
    int prev_mode = config->mode;
    m_profile_t *profile = NULL;

    mp_msg(MSGT_CFGPARSER, MSGL_V, "Reading config file %s", conffile);

    if (recursion_depth > MAX_RECURSION_DEPTH) {
        mp_msg(MSGT_CFGPARSER, MSGL_ERR,
               ": too deep 'include'. check your configfiles\n");
        ret = -1;
        goto out;
    } else

        config->mode = M_CONFIG_FILE;

    if ((line = malloc(MAX_LINE_LEN + 1)) == NULL) {
        mp_msg(MSGT_CFGPARSER, MSGL_FATAL,
               "\ncan't get memory for 'line': %s", strerror(errno));
        ret = -1;
        goto out;
    } else

        mp_msg(MSGT_CFGPARSER, MSGL_V, "\n");

    if ((fp = fopen(conffile, "r")) == NULL) {
        mp_msg(MSGT_CFGPARSER, MSGL_V, ": %s\n", strerror(errno));
        free(line);
        ret = 0;
        goto out;
    }

    while (fgets(line, MAX_LINE_LEN, fp)) {
        if (errors >= 16) {
            mp_msg(MSGT_CFGPARSER, MSGL_FATAL, "too many errors\n");
            goto out;
        }

        line_num++;
        line_pos = 0;

        /* skip whitespaces */
        while (isspace(line[line_pos]))
            ++line_pos;

        /* EOL / comment */
        if (line[line_pos] == '\0' || line[line_pos] == '#')
            continue;

        /* read option. */
        for (opt_pos = 0; 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_msg(MSGT_CFGPARSER, MSGL_ERR,
                       "too long option at line %d\n", line_num);
                errors++;
                ret = -1;
                goto nextline;
            }
        }
        if (opt_pos == 0) {
            PRINT_LINENUM;
            mp_msg(MSGT_CFGPARSER, MSGL_ERR, "parse error at line %d\n",
                   line_num);
            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';
            if (strcmp(opt + 1, "default"))
                profile = m_config_add_profile(config, opt + 1);
            else
                profile = NULL;
            continue;
        }

        /* skip whitespaces */
        while (isspace(line[line_pos]))
            ++line_pos;

        /* check '=' */
        if (line[line_pos++] != '=') {
            PRINT_LINENUM;
            mp_msg(MSGT_CFGPARSER, MSGL_ERR,
                   "Option %s needs a parameter at line %d\n", opt, line_num);
            ret = -1;
            errors++;
            continue;
        }

        /* whitespaces... */
        while (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 */) {
                param[param_pos++] = line[line_pos++];
                if (param_pos >= MAX_PARAM_LEN) {
                    PRINT_LINENUM;
                    mp_msg(MSGT_CFGPARSER, MSGL_ERR,
                           "Option %s has a too long parameter at line %d\n",
                           opt, line_num);
                    ret = -1;
                    errors++;
                    goto nextline;
                }
            }
            line_pos++;                 /* skip the closing " or ' */
        } else {
            for (param_pos = 0; isprint(line[line_pos])
                     && !isspace(line[line_pos])
                     && line[line_pos] != '#'; /* NOTHING */) {
                param[param_pos++] = line[line_pos++];
                if (param_pos >= MAX_PARAM_LEN) {
                    PRINT_LINENUM;
                    mp_msg(MSGT_CFGPARSER, MSGL_ERR, "too long parameter\n");
                    ret = -1;
                    errors++;
                    goto nextline;
                }
            }
        }
        param[param_pos] = '\0';

        /* did we read a parameter? */
        if (param_pos == 0) {
            PRINT_LINENUM;
            mp_msg(MSGT_CFGPARSER, MSGL_ERR,
                   "Option %s needs a parameter at line %d\n", opt, line_num);
            ret = -1;
            errors++;
            continue;
        }

        /* now, check if we have some more chars on the line */
        /* whitespace... */
        while (isspace(line[line_pos]))
            ++line_pos;

        /* EOL / comment */
        if (line[line_pos] != '\0' && line[line_pos] != '#') {
            PRINT_LINENUM;
            mp_msg(MSGT_CFGPARSER, MSGL_WARN,
                   "extra characters on line %d: %s\n",
                   line_num, line + line_pos);
            ret = -1;
        }

        if (profile) {
            if (!strcmp(opt, "profile-desc"))
                m_profile_set_desc(profile, param), tmp = 1;
            else
                tmp = m_config_set_profile_option(config, profile,
                                                  opt, param);
        } else
            tmp = m_config_set_option0(config, opt, param, false);
        if (tmp < 0) {
            PRINT_LINENUM;
            if (tmp == M_OPT_UNKNOWN) {
                mp_msg(MSGT_CFGPARSER, MSGL_WARN,
                       "Warning unknown option %s at line %d\n",
                       opt, line_num);
                continue;
            }
            mp_msg(MSGT_CFGPARSER, MSGL_ERR,
                   "Error parsing option %s=%s at line %d\n",
                   opt, param, line_num);
            ret = -1;
            errors++;
            continue;
            /* break */
        }
nextline:
        ;
    }

    free(line);
    fclose(fp);
out:
    config->mode = prev_mode;
    --recursion_depth;
    return ret;
}
Esempio n. 4
0
/** \param config The config object.
 *  \param conffile Path to the config file.
 *  \return 1 on sucess, -1 on error, 0 if file not accessible.
 */
int m_config_parse_config_file(m_config_t *config, const char *conffile)
{
#define PRINT_LINENUM   mp_msg(MSGT_CFGPARSER, MSGL_ERR, "%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 = NULL;

    mp_msg(MSGT_CFGPARSER, MSGL_V, "Reading config file %s", conffile);

    if (recursion_depth > MAX_RECURSION_DEPTH) {
        mp_msg(MSGT_CFGPARSER, MSGL_ERR,
               ": too deep 'include'. check your configfiles\n");
        ret = -1;
        goto out;
    }

    if ((line = malloc(MAX_LINE_LEN + 1)) == NULL) {
        mp_msg(MSGT_CFGPARSER, MSGL_FATAL,
               "\ncan't get memory for 'line': %s", strerror(errno));
        ret = -1;
        goto out;
    } else

        mp_msg(MSGT_CFGPARSER, MSGL_V, "\n");

    if ((fp = fopen(conffile, "r")) == NULL) {
        mp_msg(MSGT_CFGPARSER, MSGL_V, ": %s\n", strerror(errno));
        ret = 0;
        goto out;
    }

    while (fgets(line, MAX_LINE_LEN, fp)) {
        if (errors >= 16) {
            mp_msg(MSGT_CFGPARSER, MSGL_FATAL, "too many errors\n");
            goto out;
        }

        line_num++;
        line_pos = 0;

        /* skip whitespaces */
        while (isspace(line[line_pos]))
            ++line_pos;

        /* EOL / comment */
        if (line[line_pos] == '\0' || line[line_pos] == '#')
            continue;

        /* read option. */
        for (opt_pos = 0; 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_msg(MSGT_CFGPARSER, MSGL_ERR, "too long option\n");
                errors++;
                ret = -1;
                goto nextline;
            }
        }
        if (opt_pos == 0) {
            PRINT_LINENUM;
            mp_msg(MSGT_CFGPARSER, MSGL_ERR, "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';
            if (strcmp(opt + 1, "default"))
                profile = m_config_add_profile(config, opt + 1);
            else
                profile = NULL;
            continue;
        }

        /* skip whitespaces */
        while (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 (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 */) {
                    param[param_pos++] = line[line_pos++];
                    if (param_pos >= MAX_PARAM_LEN) {
                        PRINT_LINENUM;
                        mp_msg(MSGT_CFGPARSER, MSGL_ERR,
                               "option %s has a too long parameter\n", opt);
                        ret = -1;
                        errors++;
                        goto nextline;
                    }
                }
                line_pos++;                 /* skip the closing " or ' */
            } else {
                for (param_pos = 0; isprint(line[line_pos])
                        && !isspace(line[line_pos])
                        && line[line_pos] != '#'; /* NOTHING */) {
                    param[param_pos++] = line[line_pos++];
                    if (param_pos >= MAX_PARAM_LEN) {
                        PRINT_LINENUM;
                        mp_msg(MSGT_CFGPARSER, MSGL_ERR, "too long parameter\n");
                        ret = -1;
                        errors++;
                        goto nextline;
                    }
                }
            }

            while (isspace(line[line_pos]))
                ++line_pos;
        }
        param[param_pos] = '\0';

        /* EOL / comment */
        if (line[line_pos] != '\0' && line[line_pos] != '#') {
            PRINT_LINENUM;
            mp_msg(MSGT_CFGPARSER, MSGL_ERR,
                   "extra characters: %s\n", line + line_pos);
            ret = -1;
        }

        bstr bopt = bstr0(opt);
        bstr bparam = bstr0(param);

        if (profile && bstr_equals0(bopt, "profile-desc")) {
            m_profile_set_desc(profile, param);
            goto nextline;
        }

        tmp = m_config_option_requires_param(config, bopt);
        if (tmp > 0 && !param_set)
            tmp = M_OPT_MISSING_PARAM;
        if (tmp < 0) {
            PRINT_LINENUM;
            mp_msg(MSGT_CFGPARSER, MSGL_ERR,
                   "error parsing option %s=%s: %s\n",
                   opt, param, m_option_strerror(tmp));
            continue;
        }

        if (profile) {
            tmp = m_config_set_profile_option(config, profile, bopt, bparam);
        } else {
            tmp = m_config_set_option_ext(config, bopt, bparam,
                                          M_SETOPT_FROM_CONFIG_FILE);
        }
        if (tmp < 0) {
            PRINT_LINENUM;
            mp_msg(MSGT_CFGPARSER, MSGL_ERR,
                   "setting option %s='%s' failed.\n", opt, param);
            continue;
            /* break */
        }
nextline:
        ;
    }

out:
    free(line);
    if (fp)
        fclose(fp);
    --recursion_depth;
    if (ret < 0) {
        mp_msg(MSGT_CFGPARSER, MSGL_FATAL, "Error loading config file %s.\n",
               conffile);
    }
    return ret;
}
Esempio n. 5
0
// 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)
{
#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);

    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", 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;

        /* skip BOM */
        if (strncmp(line, "\xEF\xBB\xBF", 3) == 0)
            line_pos += 3;

        /* skip whitespaces */
        while (mp_isspace(line[line_pos]))
            ++line_pos;

        /* EOL / comment */
        if (line[line_pos] == '\0' || line[line_pos] == '#')
            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;
            }
        }
        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);
            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 */) {
                    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;
                    }
                }
                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;
                }
            }

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

        param_done:

            while (mp_isspace(line[line_pos]))
                ++line_pos;
        }
        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;
        }

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

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

        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));
            continue;
            /* break */
        }
nextline:
        ;
    }

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;
}
Esempio n. 6
0
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 unparseable 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 unparseable extra characters: '%.*s'\n",
                   loc, BSTR_P(line));
            goto error;
        }

        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);
            }
        } else {
            res = m_config_set_option_ext(config, option, value, flags);
        }
        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;
        }
    }

    talloc_free(tmp);
    return 1;
}