Coverage Report

Created: 2026-03-27 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}