/src/mpv/options/parse_commandline.c
Line | Count | Source |
1 | | /* |
2 | | * This file is part of mpv. |
3 | | * |
4 | | * mpv is free software; you can redistribute it and/or |
5 | | * modify it under the terms of the GNU Lesser General Public |
6 | | * License as published by the Free Software Foundation; either |
7 | | * version 2.1 of the License, or (at your option) any later version. |
8 | | * |
9 | | * mpv is distributed in the hope that it will be useful, |
10 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | * GNU Lesser General Public License for more details. |
13 | | * |
14 | | * You should have received a copy of the GNU Lesser General Public |
15 | | * License along with mpv. If not, see <http://www.gnu.org/licenses/>. |
16 | | */ |
17 | | |
18 | | #include <stdio.h> |
19 | | #include <stdlib.h> |
20 | | #include <string.h> |
21 | | #include <errno.h> |
22 | | #include <assert.h> |
23 | | #include <stdbool.h> |
24 | | |
25 | | #include "osdep/io.h" |
26 | | #include "common/msg.h" |
27 | | #include "common/msg_control.h" |
28 | | #include "m_option.h" |
29 | | #include "m_config_frontend.h" |
30 | | #include "options.h" |
31 | | #include "common/playlist.h" |
32 | | #include "parse_commandline.h" |
33 | | |
34 | 31.7k | #define GLOBAL 0 |
35 | 187k | #define LOCAL 1 |
36 | | |
37 | | struct parse_state { |
38 | | struct m_config *config; |
39 | | char **argv; |
40 | | struct mp_log *log; // silent if NULL |
41 | | |
42 | | bool no_more_opts; |
43 | | bool error; |
44 | | |
45 | | bool is_opt; |
46 | | struct bstr arg; |
47 | | struct bstr param; |
48 | | }; |
49 | | |
50 | | // Returns true if more args, false if all parsed or an error occurred. |
51 | | static bool split_opt(struct parse_state *p) |
52 | 389k | { |
53 | 389k | mp_assert(!p->error); |
54 | | |
55 | 389k | if (!p->argv || !p->argv[0]) |
56 | 29.5k | return false; |
57 | | |
58 | 360k | p->is_opt = false; |
59 | 360k | p->arg = bstr0(p->argv[0]); |
60 | 360k | p->param = bstr0(NULL); |
61 | | |
62 | 360k | p->argv++; |
63 | | |
64 | 360k | if (p->no_more_opts || !bstr_startswith0(p->arg, "-") || p->arg.len == 1) |
65 | 157k | return true; |
66 | | |
67 | 202k | if (bstrcmp0(p->arg, "--") == 0) { |
68 | 56 | p->no_more_opts = true; |
69 | 56 | return split_opt(p); |
70 | 56 | } |
71 | | |
72 | 202k | p->is_opt = true; |
73 | | |
74 | 202k | bool new_opt = bstr_eatstart0(&p->arg, "--"); |
75 | 202k | if (!new_opt) |
76 | 201k | bstr_eatstart0(&p->arg, "-"); |
77 | | |
78 | 202k | bool ambiguous = !bstr_split_tok(p->arg, "=", &p->arg, &p->param); |
79 | | |
80 | 202k | bool need_param = m_config_option_requires_param(p->config, p->arg) > 0; |
81 | | |
82 | 202k | if (ambiguous && need_param) { |
83 | 128k | if (!p->argv[0] || new_opt) { |
84 | 167 | p->error = true; |
85 | 167 | MP_FATAL(p, "Error parsing commandline option %.*s: %s\n", |
86 | 167 | BSTR_P(p->arg), m_option_strerror(M_OPT_MISSING_PARAM)); |
87 | 167 | MP_WARN(p, "Make sure you're using e.g. '--%.*s=value' instead " |
88 | 167 | "of '--%.*s value'.\n", BSTR_P(p->arg), BSTR_P(p->arg)); |
89 | 167 | return false; |
90 | 167 | } |
91 | 128k | p->param = bstr0(p->argv[0]); |
92 | 128k | p->argv++; |
93 | 128k | } |
94 | | |
95 | 202k | return true; |
96 | 202k | } |
97 | | |
98 | | #ifdef _WIN32 |
99 | | static void process_non_option(struct playlist *files, const char *arg) |
100 | | { |
101 | | glob_t gg; |
102 | | |
103 | | // Glob filenames on Windows (cmd.exe doesn't do this automatically) |
104 | | if (glob(arg, 0, NULL, &gg)) { |
105 | | playlist_append_file(files, arg); |
106 | | } else { |
107 | | for (int i = 0; i < gg.gl_pathc; i++) |
108 | | playlist_append_file(files, gg.gl_pathv[i]); |
109 | | |
110 | | globfree(&gg); |
111 | | } |
112 | | } |
113 | | #else |
114 | | static void process_non_option(struct playlist *files, const char *arg) |
115 | 77.7k | { |
116 | 77.7k | playlist_append_file(files, arg); |
117 | 77.7k | } |
118 | | #endif |
119 | | |
120 | | // returns M_OPT_... error code |
121 | | int m_config_parse_mp_command_line(m_config_t *config, struct playlist *files, |
122 | | struct mpv_global *global, char **argv) |
123 | 17.1k | { |
124 | 17.1k | int ret = M_OPT_UNKNOWN; |
125 | 17.1k | int mode = 0; |
126 | 17.1k | struct playlist_entry *local_start = NULL; |
127 | | |
128 | 17.1k | int local_params_count = 0; |
129 | 17.1k | struct playlist_param *local_params = 0; |
130 | | |
131 | 17.1k | mp_assert(config != NULL); |
132 | | |
133 | 17.1k | mode = GLOBAL; |
134 | | |
135 | 17.1k | struct parse_state p = {config, argv, config->log}; |
136 | 186k | while (split_opt(&p)) { |
137 | 173k | if (p.is_opt) { |
138 | 96.0k | int flags = M_SETOPT_FROM_CMDLINE; |
139 | 96.0k | if (mode == LOCAL) |
140 | 5.15k | flags |= M_SETOPT_BACKUP | M_SETOPT_CHECK_ONLY; |
141 | 96.0k | int r = m_config_set_option_cli(config, p.arg, p.param, flags); |
142 | 96.0k | if (r == M_OPT_EXIT) { |
143 | 373 | ret = r; |
144 | 373 | goto err_out; |
145 | 95.6k | } else if (r < 0) { |
146 | 4.11k | MP_FATAL(config, "Setting commandline option --%.*s=%.*s failed.\n", |
147 | 4.11k | BSTR_P(p.arg), BSTR_P(p.param)); |
148 | 4.11k | goto err_out; |
149 | 4.11k | } |
150 | | |
151 | | // Handle some special arguments outside option parser. |
152 | | |
153 | 91.5k | if (!bstrcmp0(p.arg, "{")) { |
154 | 1.10k | if (mode != GLOBAL) { |
155 | 1 | MP_ERR(config, "'--{' can not be nested.\n"); |
156 | 1 | goto err_out; |
157 | 1 | } |
158 | 1.09k | mode = LOCAL; |
159 | 1.09k | mp_assert(!local_start); |
160 | 1.09k | local_start = playlist_get_last(files); |
161 | 1.09k | continue; |
162 | 1.09k | } |
163 | | |
164 | 90.4k | if (!bstrcmp0(p.arg, "}")) { |
165 | 1.01k | if (mode != LOCAL) { |
166 | 12 | MP_ERR(config, "Too many closing '--}'.\n"); |
167 | 12 | goto err_out; |
168 | 12 | } |
169 | 998 | if (local_params_count) { |
170 | | // The files added between '{' and '}' are the entries from |
171 | | // the entry _after_ local_start, until the end of the list. |
172 | | // If local_start is NULL, the list was empty on '{', and we |
173 | | // want all files in the list. |
174 | 419 | struct playlist_entry *cur = local_start |
175 | 419 | ? playlist_entry_get_rel(local_start, 1) |
176 | 419 | : playlist_get_first(files); |
177 | 419 | if (!cur) |
178 | 419 | MP_WARN(config, "Ignored options!\n"); |
179 | 2.24k | while (cur) { |
180 | 1.82k | playlist_entry_add_params(cur, local_params, |
181 | 1.82k | local_params_count); |
182 | 1.82k | cur = playlist_entry_get_rel(cur, 1); |
183 | 1.82k | } |
184 | 419 | } |
185 | 998 | local_params_count = 0; |
186 | 998 | mode = GLOBAL; |
187 | 998 | m_config_restore_backups(config); |
188 | 998 | local_start = NULL; |
189 | 998 | continue; |
190 | 1.01k | } |
191 | | |
192 | 89.4k | if (bstrcmp0(p.arg, "playlist") == 0) { |
193 | | // append the playlist to the local args |
194 | 0 | char *param0 = bstrdup0(NULL, p.param); |
195 | 0 | struct playlist *pl = playlist_parse_file(param0, NULL, global); |
196 | 0 | if (!pl) { |
197 | 0 | MP_FATAL(config, "Error reading playlist '%.*s'\n", |
198 | 0 | BSTR_P(p.param)); |
199 | 0 | talloc_free(param0); |
200 | 0 | goto err_out; |
201 | 0 | } |
202 | 0 | playlist_transfer_entries(files, pl); |
203 | 0 | talloc_free(param0); |
204 | 0 | talloc_free(pl); |
205 | 0 | continue; |
206 | 0 | } |
207 | | |
208 | 89.4k | if (mode == LOCAL) { |
209 | 4.14k | MP_TARRAY_APPEND(NULL, local_params, local_params_count, |
210 | 4.14k | (struct playlist_param) {p.arg, p.param}); |
211 | 4.14k | } |
212 | 89.4k | } else { |
213 | | // filename |
214 | 77.7k | void *tmp = talloc_new(NULL); |
215 | 77.7k | char *file0 = bstrdup0(tmp, p.arg); |
216 | 77.7k | process_non_option(files, file0); |
217 | 77.7k | talloc_free(tmp); |
218 | 77.7k | } |
219 | 173k | } |
220 | | |
221 | 12.6k | if (p.error) |
222 | 61 | goto err_out; |
223 | | |
224 | 12.5k | if (mode != GLOBAL) { |
225 | 82 | MP_ERR(config, "Missing closing --} on command line.\n"); |
226 | 82 | goto err_out; |
227 | 82 | } |
228 | | |
229 | 12.4k | ret = 0; // success |
230 | | |
231 | 17.1k | err_out: |
232 | 17.1k | talloc_free(local_params); |
233 | 17.1k | m_config_restore_backups(config); |
234 | 17.1k | return ret; |
235 | 12.4k | } |
236 | | |
237 | | /* Parse some command line options early before main parsing. |
238 | | * --no-config prevents reading configuration files (otherwise done before |
239 | | * command line parsing), and --really-quiet suppresses messages printed |
240 | | * during normal options parsing. |
241 | | */ |
242 | | void m_config_preparse_command_line(m_config_t *config, struct mpv_global *global, |
243 | | int *verbose, char **argv) |
244 | 17.1k | { |
245 | 17.1k | struct parse_state p = {config, argv, mp_null_log}; |
246 | 203k | while (split_opt(&p)) { |
247 | 186k | if (p.is_opt) { |
248 | | // Ignore non-pre-parse options. They will be set later. |
249 | | // Option parsing errors will be handled later as well. |
250 | 106k | int flags = M_SETOPT_FROM_CMDLINE | M_SETOPT_PRE_PARSE_ONLY; |
251 | 106k | m_config_set_option_cli(config, p.arg, p.param, flags); |
252 | 106k | if (bstrcmp0(p.arg, "v") == 0) |
253 | 10.0k | (*verbose)++; |
254 | 106k | } |
255 | 186k | } |
256 | | |
257 | 13.0M | for (int n = 0; n < config->num_opts; n++) |
258 | 13.0M | config->opts[n].warning_was_printed = false; |
259 | 17.1k | } |