1
0
mirror of https://github.com/mpv-player/mpv synced 2024-12-23 15:22:09 +00:00
mpv/parser-mpcmd.c
wm4 b28c6d0ba5 options: fix "--"
Handling this was accidentally forgotten when command line parsing was
refactored. The added recursive call should be a tail recursion with
all reasonable compilers, and it shouldn't be possible to provoke a
stack overflow.
2012-09-18 21:07:29 +02:00

364 lines
11 KiB
C

/*
* This file is part of MPlayer.
*
* MPlayer is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* MPlayer 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with MPlayer; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <stdbool.h>
#include "mp_msg.h"
#include "m_option.h"
#include "m_config.h"
#include "playlist.h"
#include "playlist_parser.h"
#include "parser-mpcmd.h"
#include "osdep/macosx_finder_args.h"
#define GLOBAL 0
#define LOCAL 1
#define dvd_range(a) (a > 0 && a < 256)
struct parse_state {
struct m_config *config;
int argc;
char **argv;
bool no_more_opts;
bool error;
const struct m_option *mp_opt; // NULL <=> it's a file arg
struct bstr arg;
struct bstr param;
};
// Returns 0 if a valid option/file is available, <0 on error, 1 on end of args.
static int split_opt_silent(struct parse_state *p)
{
assert(!p->error);
if (p->argc < 1)
return 1;
p->mp_opt = NULL;
p->arg = bstr0(p->argv[0]);
p->param = bstr0(NULL);
p->argc--;
p->argv++;
if (p->no_more_opts || !bstr_startswith0(p->arg, "-") || p->arg.len == 1)
return 0;
if (bstrcmp0(p->arg, "--") == 0) {
p->no_more_opts = true;
return split_opt_silent(p);
}
bool old_syntax = !bstr_startswith0(p->arg, "--");
if (old_syntax) {
p->arg = bstr_cut(p->arg, 1);
} else {
p->arg = bstr_cut(p->arg, 2);
int idx = bstrchr(p->arg, '=');
if (idx > 0) {
p->param = bstr_cut(p->arg, idx + 1);
p->arg = bstr_splice(p->arg, 0, idx);
}
}
p->mp_opt = m_config_get_option(p->config, p->arg);
if (!p->mp_opt) {
// Automagic "no-" arguments: "--no-bla" turns into "--bla=no".
if (!bstr_startswith0(p->arg, "no-"))
return -1;
struct bstr s = bstr_cut(p->arg, 3);
p->mp_opt = m_config_get_option(p->config, s);
if (!p->mp_opt || p->mp_opt->type != &m_option_type_flag)
return -1;
// Avoid allowing "--no-no-bla".
if (bstr_startswith(bstr0(p->mp_opt->name), bstr0("no-")))
return -1;
// Flag options never have parameters.
old_syntax = false;
if (p->param.len)
return -2;
p->arg = s;
p->param = bstr0("no");
}
if (bstr_endswith0(p->arg, "-clr"))
old_syntax = false;
if (old_syntax && !(p->mp_opt->type->flags & M_OPT_TYPE_OLD_SYNTAX_NO_PARAM))
{
if (p->argc < 1)
return -3;
p->param = bstr0(p->argv[0]);
p->argc--;
p->argv++;
}
return 0;
}
// Returns true if more args, false if all parsed or an error occurred.
static bool split_opt(struct parse_state *p)
{
int r = split_opt_silent(p);
if (r >= 0)
return r == 0;
p->error = true;
if (r == -2)
mp_tmsg(MSGT_CFGPARSER, MSGL_ERR,
"A no-* option can't take parameters: --%.*s=%.*s\n",
BSTR_P(p->arg), BSTR_P(p->param));
else if (r == -3)
mp_tmsg(MSGT_CFGPARSER, MSGL_ERR,
"Option %.*s needs a parameter.\n", BSTR_P(p->arg));
else
mp_tmsg(MSGT_CFGPARSER, MSGL_ERR,
"Unknown option on the command line: %.*s\n",
BSTR_P(p->arg));
return false;
}
static bool parse_flag(bstr name, bstr f)
{
struct m_option opt = {NULL, NULL, CONF_TYPE_FLAG, 0, 0, 1, NULL};
int val = 0;
m_option_parse(&opt, name, f, &val);
return !!val;
}
bool m_config_parse_mp_command_line(m_config_t *config, struct playlist *files,
int argc, char **argv)
{
int mode = 0;
bool opt_exit = false; // exit immediately after parsing (help options)
struct playlist_entry *local_start = NULL;
bool shuffle = false;
int local_params_count = 0;
struct playlist_param *local_params = 0;
assert(config != NULL);
assert(!config->file_local_mode);
config->mode = M_COMMAND_LINE;
mode = GLOBAL;
#ifdef CONFIG_MACOSX_FINDER
if (macosx_finder_args(config, files, argc, argv))
return true;
#endif
struct parse_state p = {config, argc, argv};
while (split_opt(&p)) {
if (p.mp_opt) {
int r;
if (mode == GLOBAL && !(p.mp_opt->flags & M_OPT_PRE_PARSE)) {
r = m_config_set_option(config, p.arg, p.param);
} else {
r = m_config_check_option(config, p.arg, p.param);
}
if (r <= M_OPT_EXIT) {
opt_exit = true;
r = M_OPT_EXIT - r;
} else if (r < 0) {
char *msg = m_option_strerror(r);
if (!msg)
goto print_err;
mp_tmsg(MSGT_CFGPARSER, MSGL_FATAL,
"Error parsing commandline option %.*s: %s\n",
BSTR_P(p.arg), msg);
goto err_out;
}
// Handle some special arguments outside option parser.
if (!bstrcmp0(p.arg, "{")) {
if (mode != GLOBAL) {
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
"'--{' can not be nested.\n");
goto err_out;
}
mode = LOCAL;
// Needed for option checking.
m_config_enter_file_local(config);
assert(!local_start);
local_start = files->last;
continue;
}
if (!bstrcmp0(p.arg, "}")) {
if (mode != LOCAL) {
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
"Too many closing '--}'.\n");
goto err_out;
}
if (local_params_count) {
// The files added between '{' and '}' are the entries from
// the entry _after_ local_start, until the end of the list.
// If local_start is NULL, the list was empty on '{', and we
// want all files in the list.
struct playlist_entry *cur
= local_start ? local_start->next : files->first;
if (!cur)
mp_msg(MSGT_CFGPARSER, MSGL_WARN, "Ignored options!\n");
while (cur) {
playlist_entry_add_params(cur, local_params,
local_params_count);
cur = cur->next;
}
}
local_params_count = 0;
mode = GLOBAL;
m_config_leave_file_local(config);
local_start = NULL;
shuffle = false;
continue;
}
if (bstrcmp0(p.arg, "shuffle") == 0) {
shuffle = parse_flag(p.arg, p.param);
continue;
}
if (bstrcmp0(p.arg, "playlist") == 0) {
// append the playlist to the local args
char *param0 = bstrdup0(NULL, p.param);
struct playlist *pl = playlist_parse_file(param0);
talloc_free(param0);
if (!pl)
goto print_err;
playlist_transfer_entries(files, pl);
talloc_free(pl);
continue;
}
if (bstrcmp0(p.arg, "v") == 0) {
verbose++;
continue;
}
if (mode == LOCAL) {
MP_TARRAY_APPEND(NULL, local_params, local_params_count,
(struct playlist_param) {p.arg, p.param});
}
} else {
// filename
bstr file = p.arg;
char *file0 = bstrdup0(NULL, p.arg);
// expand DVD filename entries like dvd://1-3 into component titles
if (bstr_startswith0(file, "dvd://")) {
int offset = 6;
char *splitpos = strstr(file0 + offset, "-");
if (splitpos != NULL) {
int start_title = strtol(file0 + offset, NULL, 10);
int end_title;
//entries like dvd://-2 imply start at title 1
if (start_title < 0) {
end_title = abs(start_title);
start_title = 1;
} else
end_title = strtol(splitpos + 1, NULL, 10);
if (dvd_range(start_title) && dvd_range(end_title)
&& (start_title < end_title)) {
for (int j = start_title; j <= end_title; j++) {
char entbuf[15];
snprintf(entbuf, sizeof(entbuf), "dvd://%d", j);
playlist_add_file(files, entbuf);
}
} else
mp_tmsg(MSGT_CFGPARSER, MSGL_ERR,
"Invalid play entry %s\n", file0);
} else // dvd:// or dvd://x entry
playlist_add_file(files, file0);
} else
playlist_add_file(files, file0);
talloc_free(file0);
// Lock stdin if it will be used as input
if (bstrcmp0(file, "-") == 0)
m_config_set_option0(config, "consolecontrols", "no");
}
}
if (p.error)
goto err_out;
if (mode != GLOBAL) {
mp_tmsg(MSGT_CFGPARSER, MSGL_ERR,
"Missing closing --} on command line.\n");
goto err_out;
}
if (opt_exit)
goto err_out;
if (shuffle)
playlist_shuffle(files);
talloc_free(local_params);
assert(!config->file_local_mode);
return true;
print_err:
mp_tmsg(MSGT_CFGPARSER, MSGL_FATAL,
"Error parsing option on the command line: %.*s\n", BSTR_P(p.arg));
err_out:
talloc_free(local_params);
if (config->file_local_mode)
m_config_leave_file_local(config);
return false;
}
extern int mp_msg_levels[];
/* Parse some command line options early before main parsing.
* --noconfig prevents reading configuration files (otherwise done before
* command line parsing), and --really-quiet suppresses messages printed
* during normal options parsing.
*/
void m_config_preparse_command_line(m_config_t *config, int argc, char **argv)
{
// Hack to shut up parser error messages
int msg_lvl_backup = mp_msg_levels[MSGT_CFGPARSER];
mp_msg_levels[MSGT_CFGPARSER] = -11;
struct parse_state p = {config, argc, argv};
while (split_opt_silent(&p) == 0) {
if (p.mp_opt) {
// Ignore non-pre-parse options. They will be set later.
// Option parsing errors will be handled later as well.
if (p.mp_opt->flags & M_OPT_PRE_PARSE)
m_config_set_option(config, p.arg, p.param);
}
}
mp_msg_levels[MSGT_CFGPARSER] = msg_lvl_backup;
}