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 <float.h>
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <errno.h>
22 #include <string.h>
23 #include <strings.h>
24 #include <assert.h>
25 #include <stdbool.h>
26 #include <pthread.h>
27 
28 #include "libmpv/client.h"
29 
30 #include "m_config.h"
31 #include "m_config_frontend.h"
32 #include "options/m_option.h"
33 #include "common/common.h"
34 #include "common/global.h"
35 #include "common/msg.h"
36 #include "common/msg_control.h"
37 #include "misc/dispatch.h"
38 #include "misc/node.h"
39 #include "osdep/atomic.h"
40 
41 extern const char mp_help_text[];
42 
43 // Profiles allow to predefine some sets of options that can then
44 // be applied later on with the internal -profile option.
45 #define MAX_PROFILE_DEPTH 20
46 // Maximal include depth.
47 #define MAX_RECURSION_DEPTH 8
48 
49 struct m_profile {
50     struct m_profile *next;
51     char *name;
52     char *desc;
53     char *cond;
54     int restore_mode;
55     int num_opts;
56     // Option/value pair array.
57     // name,value = opts[n*2+0],opts[n*2+1]
58     char **opts;
59     // For profile restoring.
60     struct m_opt_backup *backups;
61 };
62 
63 // In the file local case, this contains the old global value.
64 // It's also used for profile restoring.
65 struct m_opt_backup {
66     struct m_opt_backup *next;
67     struct m_config_option *co;
68     int flags;
69     void *backup, *nval;
70 };
71 
72 static const struct m_option profile_restore_mode_opt = {
73     .name = "profile-restore",
74     .type = &m_option_type_choice,
75     M_CHOICES({"default", 0}, {"copy", 1}, {"copy-equal", 2}),
76 };
77 
list_profiles(struct m_config * config)78 static void list_profiles(struct m_config *config)
79 {
80     MP_INFO(config, "Available profiles:\n");
81     for (struct m_profile *p = config->profiles; p; p = p->next)
82         MP_INFO(config, "\t%s\t%s\n", p->name, p->desc ? p->desc : "");
83     MP_INFO(config, "\n");
84 }
85 
show_profile(struct m_config * config,bstr param)86 static int show_profile(struct m_config *config, bstr param)
87 {
88     struct m_profile *p;
89     if (!param.len) {
90         list_profiles(config);
91         return M_OPT_EXIT;
92     }
93     if (!(p = m_config_get_profile(config, param))) {
94         MP_ERR(config, "Unknown profile '%.*s'.\n", BSTR_P(param));
95         return M_OPT_EXIT;
96     }
97     if (!config->profile_depth)
98         MP_INFO(config, "Profile %s: %s\n", p->name,
99                 p->desc ? p->desc : "");
100     config->profile_depth++;
101     if (p->cond) {
102         MP_INFO(config, "%*sprofile-cond=%s\n", config->profile_depth, "",
103                 p->cond);
104     }
105     for (int i = 0; i < p->num_opts; i++) {
106         MP_INFO(config, "%*s%s=%s\n", config->profile_depth, "",
107                 p->opts[2 * i], p->opts[2 * i + 1]);
108 
109         if (config->profile_depth < MAX_PROFILE_DEPTH
110             && !strcmp(p->opts[2*i], "profile")) {
111             char *e, *list = p->opts[2 * i + 1];
112             while ((e = strchr(list, ','))) {
113                 int l = e - list;
114                 if (!l)
115                     continue;
116                 show_profile(config, (bstr){list, e - list});
117                 list = e + 1;
118             }
119             if (list[0] != '\0')
120                 show_profile(config, bstr0(list));
121         }
122     }
123     config->profile_depth--;
124     if (!config->profile_depth)
125         MP_INFO(config, "\n");
126     return M_OPT_EXIT;
127 }
128 
m_config_from_obj_desc(void * talloc_ctx,struct mp_log * log,struct mpv_global * global,struct m_obj_desc * desc)129 static struct m_config *m_config_from_obj_desc(void *talloc_ctx,
130                                                struct mp_log *log,
131                                                struct mpv_global *global,
132                                                struct m_obj_desc *desc)
133 {
134     struct m_sub_options *root = talloc_ptrtype(NULL, root);
135     *root = (struct m_sub_options){
136         .opts = desc->options,
137         // (global == NULL got repurposed to mean "no alloc")
138         .size = global ? desc->priv_size : 0,
139         .defaults = desc->priv_defaults,
140     };
141 
142     struct m_config *c = m_config_new(talloc_ctx, log, root);
143     talloc_steal(c, root);
144     c->global = global;
145     return c;
146 }
147 
m_config_from_obj_desc_noalloc(void * talloc_ctx,struct mp_log * log,struct m_obj_desc * desc)148 struct m_config *m_config_from_obj_desc_noalloc(void *talloc_ctx,
149                                                 struct mp_log *log,
150                                                 struct m_obj_desc *desc)
151 {
152     return m_config_from_obj_desc(talloc_ctx, log, NULL, desc);
153 }
154 
m_config_set_obj_params(struct m_config * config,struct mp_log * log,struct mpv_global * global,struct m_obj_desc * desc,char ** args)155 static int m_config_set_obj_params(struct m_config *config, struct mp_log *log,
156                                    struct mpv_global *global,
157                                    struct m_obj_desc *desc, char **args)
158 {
159     for (int n = 0; args && args[n * 2 + 0]; n++) {
160         bstr opt = bstr0(args[n * 2 + 0]);
161         bstr val = bstr0(args[n * 2 + 1]);
162         if (m_config_set_option_cli(config, opt, val, 0) < 0)
163             return -1;
164     }
165 
166     return 0;
167 }
168 
m_config_from_obj_desc_and_args(void * ta_parent,struct mp_log * log,struct mpv_global * global,struct m_obj_desc * desc,const char * name,struct m_obj_settings * defaults,char ** args)169 struct m_config *m_config_from_obj_desc_and_args(void *ta_parent,
170     struct mp_log *log, struct mpv_global *global, struct m_obj_desc *desc,
171     const char *name, struct m_obj_settings *defaults, char **args)
172 {
173     struct m_config *config = m_config_from_obj_desc(ta_parent, log, global, desc);
174 
175     for (int n = 0; defaults && defaults[n].name; n++) {
176         struct m_obj_settings *entry = &defaults[n];
177         if (name && strcmp(entry->name, name) == 0) {
178             if (m_config_set_obj_params(config, log, global, desc, entry->attribs) < 0)
179                 goto error;
180         }
181     }
182 
183     if (m_config_set_obj_params(config, log, global, desc, args) < 0)
184         goto error;
185 
186     return config;
187 error:
188     talloc_free(config);
189     return NULL;
190 }
191 
backup_dtor(void * p)192 static void backup_dtor(void *p)
193 {
194     struct m_opt_backup *bc = p;
195     m_option_free(bc->co->opt, bc->backup);
196     if (bc->nval)
197         m_option_free(bc->co->opt, bc->nval);
198 }
199 
200 #define BACKUP_LOCAL 1
201 #define BACKUP_NVAL 2
ensure_backup(struct m_opt_backup ** list,int flags,struct m_config_option * co)202 static void ensure_backup(struct m_opt_backup **list, int flags,
203                           struct m_config_option *co)
204 {
205     if (!co->data)
206         return;
207     for (struct m_opt_backup *cur = *list; cur; cur = cur->next) {
208         if (cur->co->data == co->data) // comparing data ptr catches aliases
209             return;
210     }
211     struct m_opt_backup *bc = talloc_ptrtype(NULL, bc);
212     talloc_set_destructor(bc, backup_dtor);
213     *bc = (struct m_opt_backup) {
214         .co = co,
215         .backup = talloc_zero_size(bc, co->opt->type->size),
216         .nval = flags & BACKUP_NVAL
217             ? talloc_zero_size(bc, co->opt->type->size) : NULL,
218         .flags = flags,
219     };
220     m_option_copy(co->opt, bc->backup, co->data);
221     bc->next = *list;
222     *list = bc;
223     if (bc->flags & BACKUP_LOCAL)
224         co->is_set_locally = true;
225 }
226 
restore_backups(struct m_opt_backup ** list,struct m_config * config)227 static void restore_backups(struct m_opt_backup **list, struct m_config *config)
228 {
229     while (*list) {
230         struct m_opt_backup *bc = *list;
231         *list = bc->next;
232 
233         if (!bc->nval || m_option_equal(bc->co->opt, bc->co->data, bc->nval))
234             m_config_set_option_raw(config, bc->co, bc->backup, 0);
235 
236         if (bc->flags & BACKUP_LOCAL)
237             bc->co->is_set_locally = false;
238         talloc_free(bc);
239     }
240 }
241 
m_config_restore_backups(struct m_config * config)242 void m_config_restore_backups(struct m_config *config)
243 {
244     restore_backups(&config->backup_opts, config);
245 }
246 
m_config_watch_later_backup_opt_changed(struct m_config * config,char * opt_name)247 bool m_config_watch_later_backup_opt_changed(struct m_config *config,
248                                              char *opt_name)
249 {
250     struct m_config_option *co = m_config_get_co(config, bstr0(opt_name));
251     if (!co) {
252         MP_ERR(config, "Option %s not found.\n", opt_name);
253         return false;
254     }
255 
256     for (struct m_opt_backup *bc = config->watch_later_backup_opts; bc;
257          bc = bc->next) {
258         if (strcmp(bc->co->name, co->name) == 0) {
259             struct m_config_option *bc_co = (struct m_config_option *)bc->backup;
260             return !m_option_equal(co->opt, co->data, bc_co);
261         }
262     }
263 
264     return false;
265 }
266 
m_config_backup_opt(struct m_config * config,const char * opt)267 void m_config_backup_opt(struct m_config *config, const char *opt)
268 {
269     struct m_config_option *co = m_config_get_co(config, bstr0(opt));
270     if (co) {
271         ensure_backup(&config->backup_opts, BACKUP_LOCAL, co);
272     } else {
273         MP_ERR(config, "Option %s not found.\n", opt);
274     }
275 }
276 
m_config_backup_all_opts(struct m_config * config)277 void m_config_backup_all_opts(struct m_config *config)
278 {
279     for (int n = 0; n < config->num_opts; n++)
280         ensure_backup(&config->backup_opts, BACKUP_LOCAL, &config->opts[n]);
281 }
282 
m_config_backup_watch_later_opts(struct m_config * config)283 void m_config_backup_watch_later_opts(struct m_config *config)
284 {
285     for (int n = 0; n < config->num_opts; n++)
286         ensure_backup(&config->watch_later_backup_opts, 0, &config->opts[n]);
287 }
288 
m_config_get_co_raw(const struct m_config * config,struct bstr name)289 struct m_config_option *m_config_get_co_raw(const struct m_config *config,
290                                             struct bstr name)
291 {
292     if (!name.len)
293         return NULL;
294 
295     for (int n = 0; n < config->num_opts; n++) {
296         struct m_config_option *co = &config->opts[n];
297         struct bstr coname = bstr0(co->name);
298         if (bstrcmp(coname, name) == 0)
299             return co;
300     }
301 
302     return NULL;
303 }
304 
305 // Like m_config_get_co_raw(), but resolve aliases.
m_config_get_co_any(const struct m_config * config,struct bstr name)306 static struct m_config_option *m_config_get_co_any(const struct m_config *config,
307                                                    struct bstr name)
308 {
309     struct m_config_option *co = m_config_get_co_raw(config, name);
310     if (!co)
311         return NULL;
312 
313     const char *prefix = config->is_toplevel ? "--" : "";
314     if (co->opt->type == &m_option_type_alias) {
315         const char *alias = (const char *)co->opt->priv;
316         if (co->opt->deprecation_message && !co->warning_was_printed) {
317             if (co->opt->deprecation_message[0]) {
318                 MP_WARN(config, "Warning: option %s%s was replaced with "
319                         "%s%s: %s\n", prefix, co->name, prefix, alias,
320                         co->opt->deprecation_message);
321             } else {
322                 MP_WARN(config, "Warning: option %s%s was replaced with "
323                         "%s%s and might be removed in the future.\n",
324                         prefix, co->name, prefix, alias);
325             }
326             co->warning_was_printed = true;
327         }
328         return m_config_get_co_any(config, bstr0(alias));
329     } else if (co->opt->type == &m_option_type_removed) {
330         if (!co->warning_was_printed) {
331             char *msg = co->opt->priv;
332             if (msg) {
333                 MP_FATAL(config, "Option %s%s was removed: %s\n",
334                          prefix, co->name, msg);
335             } else {
336                 MP_FATAL(config, "Option %s%s was removed.\n",
337                          prefix, co->name);
338             }
339             co->warning_was_printed = true;
340         }
341         return NULL;
342     } else if (co->opt->deprecation_message) {
343         if (!co->warning_was_printed) {
344             MP_WARN(config, "Warning: option %s%s is deprecated "
345                     "and might be removed in the future (%s).\n",
346                     prefix, co->name, co->opt->deprecation_message);
347             co->warning_was_printed = true;
348         }
349     }
350     return co;
351 }
352 
m_config_get_co(const struct m_config * config,struct bstr name)353 struct m_config_option *m_config_get_co(const struct m_config *config,
354                                         struct bstr name)
355 {
356     struct m_config_option *co = m_config_get_co_any(config, name);
357     // CLI aliases should not be real options, and are explicitly handled by
358     // m_config_set_option_cli(). So pretend it does not exist.
359     if (co && co->opt->type == &m_option_type_cli_alias)
360         co = NULL;
361     return co;
362 }
363 
m_config_get_co_count(struct m_config * config)364 int m_config_get_co_count(struct m_config *config)
365 {
366     return config->num_opts;
367 }
368 
m_config_get_co_index(struct m_config * config,int index)369 struct m_config_option *m_config_get_co_index(struct m_config *config, int index)
370 {
371     return &config->opts[index];
372 }
373 
m_config_get_co_default(const struct m_config * config,struct m_config_option * co)374 const void *m_config_get_co_default(const struct m_config *config,
375                                     struct m_config_option *co)
376 {
377     return m_config_shadow_get_opt_default(config->shadow, co->opt_id);
378 }
379 
m_config_get_positional_option(const struct m_config * config,int p)380 const char *m_config_get_positional_option(const struct m_config *config, int p)
381 {
382     int pos = 0;
383     for (int n = 0; n < config->num_opts; n++) {
384         struct m_config_option *co = &config->opts[n];
385         if (!co->opt->deprecation_message) {
386             if (pos == p)
387                 return co->name;
388             pos++;
389         }
390     }
391     return NULL;
392 }
393 
394 // return: <0: M_OPT_ error, 0: skip, 1: check, 2: set
handle_set_opt_flags(struct m_config * config,struct m_config_option * co,int flags)395 static int handle_set_opt_flags(struct m_config *config,
396                                 struct m_config_option *co, int flags)
397 {
398     int optflags = co->opt->flags;
399     bool set = !(flags & M_SETOPT_CHECK_ONLY);
400 
401     if ((flags & M_SETOPT_PRE_PARSE_ONLY) && !(optflags & M_OPT_PRE_PARSE))
402         return 0;
403 
404     if ((flags & M_SETOPT_PRESERVE_CMDLINE) && co->is_set_from_cmdline)
405         set = false;
406 
407     if ((flags & M_SETOPT_NO_OVERWRITE) &&
408         (co->is_set_from_cmdline || co->is_set_from_config))
409         set = false;
410 
411     if ((flags & M_SETOPT_NO_PRE_PARSE) && (optflags & M_OPT_PRE_PARSE))
412         return M_OPT_INVALID;
413 
414     // Check if this option isn't forbidden in the current mode
415     if ((flags & M_SETOPT_FROM_CONFIG_FILE) && (optflags & M_OPT_NOCFG)) {
416         MP_ERR(config, "The %s option can't be used in a config file.\n",
417                co->name);
418         return M_OPT_INVALID;
419     }
420     if ((flags & M_SETOPT_BACKUP) && set)
421         ensure_backup(&config->backup_opts, BACKUP_LOCAL, co);
422 
423     return set ? 2 : 1;
424 }
425 
m_config_mark_co_flags(struct m_config_option * co,int flags)426 void m_config_mark_co_flags(struct m_config_option *co, int flags)
427 {
428     if (flags & M_SETOPT_FROM_CMDLINE)
429         co->is_set_from_cmdline = true;
430 
431     if (flags & M_SETOPT_FROM_CONFIG_FILE)
432         co->is_set_from_config = true;
433 }
434 
435 // Special options that don't really fit into the option handling model. They
436 // usually store no data, but trigger actions. Caller is assumed to have called
437 // handle_set_opt_flags() to make sure the option can be set.
438 // Returns M_OPT_UNKNOWN if the option is not a special option.
m_config_handle_special_options(struct m_config * config,struct m_config_option * co,void * data,int flags)439 static int m_config_handle_special_options(struct m_config *config,
440                                            struct m_config_option *co,
441                                            void *data, int flags)
442 {
443     if (config->use_profiles && strcmp(co->name, "profile") == 0) {
444         char **list = *(char ***)data;
445 
446         if (list && list[0] && !list[1] && strcmp(list[0], "help") == 0) {
447             if (!config->profiles) {
448                 MP_INFO(config, "No profiles have been defined.\n");
449                 return M_OPT_EXIT;
450             }
451             list_profiles(config);
452             return M_OPT_EXIT;
453         }
454 
455         for (int n = 0; list && list[n]; n++) {
456             int r = m_config_set_profile(config, list[n], flags);
457             if (r < 0)
458                 return r;
459         }
460         return 0;
461     }
462 
463     if (config->includefunc && strcmp(co->name, "include") == 0) {
464         char *param = *(char **)data;
465         if (!param || !param[0])
466             return M_OPT_MISSING_PARAM;
467         if (config->recursion_depth >= MAX_RECURSION_DEPTH) {
468             MP_ERR(config, "Maximum 'include' nesting depth exceeded.\n");
469             return M_OPT_INVALID;
470         }
471         config->recursion_depth += 1;
472         config->includefunc(config->includefunc_ctx, param, flags);
473         config->recursion_depth -= 1;
474         if (config->recursion_depth == 0 && config->profile_depth == 0)
475             m_config_finish_default_profile(config, flags);
476         return 1;
477     }
478 
479     if (config->use_profiles && strcmp(co->name, "show-profile") == 0)
480         return show_profile(config, bstr0(*(char **)data));
481 
482     if (config->is_toplevel && (strcmp(co->name, "h") == 0 ||
483                                 strcmp(co->name, "help") == 0))
484     {
485         char *h = *(char **)data;
486         mp_info(config->log, "%s", mp_help_text);
487         if (h && h[0])
488             m_config_print_option_list(config, h);
489         return M_OPT_EXIT;
490     }
491 
492     if (strcmp(co->name, "list-options") == 0) {
493         m_config_print_option_list(config, "*");
494         return M_OPT_EXIT;
495     }
496 
497     return M_OPT_UNKNOWN;
498 }
499 
500 // This notification happens when anyone other than m_config->cache (i.e. not
501 // through m_config_set_option_raw() or related) changes any options.
async_change_cb(void * p)502 static void async_change_cb(void *p)
503 {
504     struct m_config *config = p;
505 
506     void *ptr;
507     while (m_config_cache_get_next_changed(config->cache, &ptr)) {
508         // Regrettable linear search, might degenerate to quadratic.
509         for (int n = 0; n < config->num_opts; n++) {
510             struct m_config_option *co = &config->opts[n];
511             if (co->data == ptr) {
512                 if (config->option_change_callback) {
513                     config->option_change_callback(
514                         config->option_change_callback_ctx, co,
515                         config->cache->change_flags, false);
516                 }
517                 break;
518             }
519         }
520         config->cache->change_flags = 0;
521     }
522 }
523 
m_config_set_update_dispatch_queue(struct m_config * config,struct mp_dispatch_queue * dispatch)524 void m_config_set_update_dispatch_queue(struct m_config *config,
525                                         struct mp_dispatch_queue *dispatch)
526 {
527     m_config_cache_set_dispatch_change_cb(config->cache, dispatch,
528                                           async_change_cb, config);
529 }
530 
config_destroy(void * p)531 static void config_destroy(void *p)
532 {
533     struct m_config *config = p;
534     config->option_change_callback = NULL;
535     m_config_restore_backups(config);
536 
537     struct m_opt_backup **list = &config->watch_later_backup_opts;
538     while (*list) {
539         struct m_opt_backup *bc = *list;
540         *list = bc->next;
541         talloc_free(bc);
542     }
543 
544     talloc_free(config->cache);
545     talloc_free(config->shadow);
546 }
547 
m_config_new(void * talloc_ctx,struct mp_log * log,const struct m_sub_options * root)548 struct m_config *m_config_new(void *talloc_ctx, struct mp_log *log,
549                               const struct m_sub_options *root)
550 {
551     struct m_config *config = talloc(talloc_ctx, struct m_config);
552     talloc_set_destructor(config, config_destroy);
553     *config = (struct m_config){.log = log,};
554 
555     config->shadow = m_config_shadow_new(root);
556 
557     if (root->size) {
558         config->cache = m_config_cache_from_shadow(config, config->shadow, root);
559         config->optstruct = config->cache->opts;
560     }
561 
562     int32_t optid = -1;
563     while (m_config_shadow_get_next_opt(config->shadow, &optid)) {
564         char buf[M_CONFIG_MAX_OPT_NAME_LEN];
565         const char *opt_name =
566             m_config_shadow_get_opt_name(config->shadow, optid, buf, sizeof(buf));
567 
568         struct m_config_option co = {
569             .name = talloc_strdup(config, opt_name),
570             .opt = m_config_shadow_get_opt(config->shadow, optid),
571             .opt_id = optid,
572         };
573 
574         if (config->cache)
575             co.data = m_config_cache_get_opt_data(config->cache, optid);
576 
577         MP_TARRAY_APPEND(config, config->opts, config->num_opts, co);
578     }
579 
580     return config;
581 }
582 
583 // Normally m_config_cache will not send notifications when _we_ change our
584 // own stuff. For whatever funny reasons, we need that, though.
force_self_notify_change_opt(struct m_config * config,struct m_config_option * co,bool self_notification)585 static void force_self_notify_change_opt(struct m_config *config,
586                                          struct m_config_option *co,
587                                          bool self_notification)
588 {
589     int changed =
590         m_config_cache_get_option_change_mask(config->cache, co->opt_id);
591 
592     if (config->option_change_callback) {
593         config->option_change_callback(config->option_change_callback_ctx, co,
594                                        changed, self_notification);
595     }
596 }
597 
notify_opt(struct m_config * config,void * ptr,bool self_notification)598 static void notify_opt(struct m_config *config, void *ptr, bool self_notification)
599 {
600     for (int n = 0; n < config->num_opts; n++) {
601         struct m_config_option *co = &config->opts[n];
602         if (co->data == ptr) {
603             if (m_config_cache_write_opt(config->cache, co->data))
604                 force_self_notify_change_opt(config, co, self_notification);
605             return;
606         }
607     }
608     // ptr doesn't point to any config->optstruct field declared in the
609     // option list?
610     assert(false);
611 }
612 
m_config_notify_change_opt_ptr(struct m_config * config,void * ptr)613 void m_config_notify_change_opt_ptr(struct m_config *config, void *ptr)
614 {
615     notify_opt(config, ptr, true);
616 }
617 
m_config_notify_change_opt_ptr_notify(struct m_config * config,void * ptr)618 void m_config_notify_change_opt_ptr_notify(struct m_config *config, void *ptr)
619 {
620     // (the notify bool is inverted: by not marking it as self-notification,
621     // the mpctx option change handler actually applies it)
622     notify_opt(config, ptr, false);
623 }
624 
m_config_set_option_raw(struct m_config * config,struct m_config_option * co,void * data,int flags)625 int m_config_set_option_raw(struct m_config *config,
626                             struct m_config_option *co,
627                             void *data, int flags)
628 {
629     if (!co)
630         return M_OPT_UNKNOWN;
631 
632     int r = handle_set_opt_flags(config, co, flags);
633     if (r <= 1)
634         return r;
635 
636     r = m_config_handle_special_options(config, co, data, flags);
637     if (r != M_OPT_UNKNOWN)
638         return r;
639 
640     // This affects some special options like "playlist", "v". Maybe these
641     // should work, or maybe not. For now they would require special code.
642     if (!co->data)
643         return flags & M_SETOPT_FROM_CMDLINE ? 0 : M_OPT_UNKNOWN;
644 
645     if (config->profile_backup_tmp)
646         ensure_backup(config->profile_backup_tmp, config->profile_backup_flags, co);
647 
648     m_config_mark_co_flags(co, flags);
649 
650     m_option_copy(co->opt, co->data, data);
651     if (m_config_cache_write_opt(config->cache, co->data))
652         force_self_notify_change_opt(config, co, false);
653 
654     return 0;
655 }
656 
657 // Handle CLI exceptions to option handling.
658 // Used to turn "--no-foo" into "--foo=no".
659 // It also handles looking up "--vf-add" as "--vf".
m_config_mogrify_cli_opt(struct m_config * config,struct bstr * name,bool * out_negate,int * out_add_flags)660 static struct m_config_option *m_config_mogrify_cli_opt(struct m_config *config,
661                                                         struct bstr *name,
662                                                         bool *out_negate,
663                                                         int *out_add_flags)
664 {
665     *out_negate = false;
666     *out_add_flags = 0;
667 
668     struct m_config_option *co = m_config_get_co(config, *name);
669     if (co)
670         return co;
671 
672     // Turn "--no-foo" into "foo" + set *out_negate.
673     bstr no_name = *name;
674     if (!co && bstr_eatstart0(&no_name, "no-")) {
675         co = m_config_get_co(config, no_name);
676 
677         // Not all choice types have this value - if they don't, then parsing
678         // them will simply result in an error. Good enough.
679         if (!co || !(co->opt->type->flags & M_OPT_TYPE_CHOICE))
680             return NULL;
681 
682         *name = no_name;
683         *out_negate = true;
684         return co;
685     }
686 
687     // Resolve CLI alias. (We don't allow you to combine them with "--no-".)
688     co = m_config_get_co_any(config, *name);
689     if (co && co->opt->type == &m_option_type_cli_alias)
690         *name = bstr0((char *)co->opt->priv);
691 
692     // Might be a suffix "action", like "--vf-add". Expensively check for
693     // matches. (We don't allow you to combine them with "--no-".)
694     for (int n = 0; n < config->num_opts; n++) {
695         co = &config->opts[n];
696         struct bstr basename = bstr0(co->name);
697 
698         if (!bstr_startswith(*name, basename))
699             continue;
700 
701         // Aliased option + a suffix action, e.g. --opengl-shaders-append
702         if (co->opt->type == &m_option_type_alias)
703             co = m_config_get_co_any(config, basename);
704         if (!co)
705             continue;
706 
707         const struct m_option_type *type = co->opt->type;
708         for (int i = 0; type->actions && type->actions[i].name; i++) {
709             const struct m_option_action *action = &type->actions[i];
710             bstr suffix = bstr0(action->name);
711 
712             if (bstr_endswith(*name, suffix) &&
713                 (name->len == basename.len + 1 + suffix.len) &&
714                 name->start[basename.len] == '-')
715             {
716                 *out_add_flags = action->flags;
717                 return co;
718             }
719         }
720     }
721 
722     return NULL;
723 }
724 
m_config_set_option_cli(struct m_config * config,struct bstr name,struct bstr param,int flags)725 int m_config_set_option_cli(struct m_config *config, struct bstr name,
726                             struct bstr param, int flags)
727 {
728     int r;
729     assert(config != NULL);
730 
731     bool negate;
732     struct m_config_option *co =
733         m_config_mogrify_cli_opt(config, &name, &negate, &(int){0});
734 
735     if (!co) {
736         r = M_OPT_UNKNOWN;
737         goto done;
738     }
739 
740     if (negate) {
741         if (param.len) {
742             r = M_OPT_DISALLOW_PARAM;
743             goto done;
744         }
745 
746         param = bstr0("no");
747     }
748 
749     // This is the only mandatory function
750     assert(co->opt->type->parse);
751 
752     r = handle_set_opt_flags(config, co, flags);
753     if (r <= 0)
754         goto done;
755 
756     if (r == 2) {
757         MP_VERBOSE(config, "Setting option '%.*s' = '%.*s' (flags = %d)\n",
758                    BSTR_P(name), BSTR_P(param), flags);
759     }
760 
761     union m_option_value val = {0};
762 
763     // Some option types are "impure" and work on the existing data.
764     // (Prime examples: --vf-add, --sub-file)
765     if (co->data)
766         m_option_copy(co->opt, &val, co->data);
767 
768     r = m_option_parse(config->log, co->opt, name, param, &val);
769 
770     if (r >= 0)
771         r = m_config_set_option_raw(config, co, &val, flags);
772 
773     m_option_free(co->opt, &val);
774 
775 done:
776     if (r < 0 && r != M_OPT_EXIT) {
777         MP_ERR(config, "Error parsing option %.*s (%s)\n",
778                BSTR_P(name), m_option_strerror(r));
779         r = M_OPT_INVALID;
780     }
781     return r;
782 }
783 
m_config_set_option_node(struct m_config * config,bstr name,struct mpv_node * data,int flags)784 int m_config_set_option_node(struct m_config *config, bstr name,
785                              struct mpv_node *data, int flags)
786 {
787     int r;
788 
789     struct m_config_option *co = m_config_get_co(config, name);
790     if (!co)
791         return M_OPT_UNKNOWN;
792 
793     // Do this on an "empty" type to make setting the option strictly overwrite
794     // the old value, as opposed to e.g. appending to lists.
795     union m_option_value val = {0};
796 
797     if (data->format == MPV_FORMAT_STRING) {
798         bstr param = bstr0(data->u.string);
799         r = m_option_parse(mp_null_log, co->opt, name, param, &val);
800     } else {
801         r = m_option_set_node(co->opt, &val, data);
802     }
803 
804     if (r >= 0)
805         r = m_config_set_option_raw(config, co, &val, flags);
806 
807     if (mp_msg_test(config->log, MSGL_V)) {
808         char *s = m_option_type_node.print(NULL, data);
809         MP_DBG(config, "Setting option '%.*s' = %s (flags = %d) -> %d\n",
810                BSTR_P(name), s ? s : "?", flags, r);
811         talloc_free(s);
812     }
813 
814     m_option_free(co->opt, &val);
815     return r;
816 }
817 
m_config_option_requires_param(struct m_config * config,bstr name)818 int m_config_option_requires_param(struct m_config *config, bstr name)
819 {
820     bool negate;
821     int flags;
822     struct m_config_option *co =
823         m_config_mogrify_cli_opt(config, &name, &negate, &flags);
824 
825     if (!co)
826         return M_OPT_UNKNOWN;
827 
828     if (negate || (flags & M_OPT_TYPE_OPTIONAL_PARAM))
829         return 0;
830 
831     return m_option_required_params(co->opt);
832 }
833 
sort_opt_compare(const void * pa,const void * pb)834 static int sort_opt_compare(const void *pa, const void *pb)
835 {
836     const struct m_config_option *a = pa;
837     const struct m_config_option *b = pb;
838     return strcasecmp(a->name, b->name);
839 }
840 
m_config_print_option_list(const struct m_config * config,const char * name)841 void m_config_print_option_list(const struct m_config *config, const char *name)
842 {
843     char min[50], max[50];
844     int count = 0;
845     const char *prefix = config->is_toplevel ? "--" : "";
846 
847     struct m_config_option *sorted =
848         talloc_memdup(NULL, config->opts, config->num_opts * sizeof(sorted[0]));
849     if (config->is_toplevel)
850         qsort(sorted, config->num_opts, sizeof(sorted[0]), sort_opt_compare);
851 
852     MP_INFO(config, "Options:\n\n");
853     for (int i = 0; i < config->num_opts; i++) {
854         struct m_config_option *co = &sorted[i];
855         const struct m_option *opt = co->opt;
856         if (strcmp(name, "*") != 0 && !strstr(co->name, name))
857             continue;
858         MP_INFO(config, " %s%-30s", prefix, co->name);
859         if (opt->type == &m_option_type_choice) {
860             MP_INFO(config, " Choices:");
861             struct m_opt_choice_alternatives *alt = opt->priv;
862             for (int n = 0; alt[n].name; n++)
863                 MP_INFO(config, " %s", alt[n].name);
864             if (opt->min < opt->max)
865                 MP_INFO(config, " (or an integer)");
866         } else {
867             MP_INFO(config, " %s", opt->type->name);
868         }
869         if ((opt->type->flags & M_OPT_TYPE_USES_RANGE) && opt->min < opt->max) {
870             snprintf(min, sizeof(min), "any");
871             snprintf(max, sizeof(max), "any");
872             if (opt->min != DBL_MIN)
873                 snprintf(min, sizeof(min), "%.14g", opt->min);
874             if (opt->max != DBL_MAX)
875                 snprintf(max, sizeof(max), "%.14g", opt->max);
876             MP_INFO(config, " (%s to %s)", min, max);
877         }
878         char *def = NULL;
879         const void *defptr = m_config_get_co_default(config, co);
880         const union m_option_value default_value = {0};
881         if (!defptr)
882             defptr = &default_value;
883         if (defptr)
884             def = m_option_pretty_print(opt, defptr);
885         if (def) {
886             MP_INFO(config, " (default: %s)", def);
887             talloc_free(def);
888         }
889         if (opt->flags & M_OPT_NOCFG)
890             MP_INFO(config, " [not in config files]");
891         if (opt->flags & M_OPT_FILE)
892             MP_INFO(config, " [file]");
893         if (opt->deprecation_message)
894             MP_INFO(config, " [deprecated]");
895         if (opt->type == &m_option_type_alias)
896             MP_INFO(config, " for %s", (char *)opt->priv);
897         if (opt->type == &m_option_type_cli_alias)
898             MP_INFO(config, " for --%s (CLI/config files only)", (char *)opt->priv);
899         MP_INFO(config, "\n");
900         for (int n = 0; opt->type->actions && opt->type->actions[n].name; n++) {
901             const struct m_option_action *action = &opt->type->actions[n];
902             MP_INFO(config, "    %s%s-%s\n", prefix, co->name, action->name);
903             count++;
904         }
905         count++;
906     }
907     MP_INFO(config, "\nTotal: %d options\n", count);
908     talloc_free(sorted);
909 }
910 
m_config_list_options(void * ta_parent,const struct m_config * config)911 char **m_config_list_options(void *ta_parent, const struct m_config *config)
912 {
913     char **list = talloc_new(ta_parent);
914     int count = 0;
915     for (int i = 0; i < config->num_opts; i++) {
916         struct m_config_option *co = &config->opts[i];
917         // For use with CONF_TYPE_STRING_LIST, it's important not to set list
918         // as allocation parent.
919         char *s = talloc_strdup(ta_parent, co->name);
920         MP_TARRAY_APPEND(ta_parent, list, count, s);
921     }
922     MP_TARRAY_APPEND(ta_parent, list, count, NULL);
923     return list;
924 }
925 
m_config_get_profile(const struct m_config * config,bstr name)926 struct m_profile *m_config_get_profile(const struct m_config *config, bstr name)
927 {
928     for (struct m_profile *p = config->profiles; p; p = p->next) {
929         if (bstr_equals0(name, p->name))
930             return p;
931     }
932     return NULL;
933 }
934 
m_config_get_profile0(const struct m_config * config,char * name)935 struct m_profile *m_config_get_profile0(const struct m_config *config,
936                                         char *name)
937 {
938     return m_config_get_profile(config, bstr0(name));
939 }
940 
m_config_add_profile(struct m_config * config,char * name)941 struct m_profile *m_config_add_profile(struct m_config *config, char *name)
942 {
943     if (!name || !name[0])
944         name = "default";
945     struct m_profile *p = m_config_get_profile0(config, name);
946     if (p)
947         return p;
948     p = talloc_zero(config, struct m_profile);
949     p->name = talloc_strdup(p, name);
950     p->next = config->profiles;
951     config->profiles = p;
952     return p;
953 }
954 
m_config_set_profile_option(struct m_config * config,struct m_profile * p,bstr name,bstr val)955 int m_config_set_profile_option(struct m_config *config, struct m_profile *p,
956                                 bstr name, bstr val)
957 {
958     if (bstr_equals0(name, "profile-desc")) {
959         talloc_free(p->desc);
960         p->desc = bstrto0(p, val);
961         return 0;
962     }
963     if (bstr_equals0(name, "profile-cond")) {
964         TA_FREEP(&p->cond);
965         val = bstr_strip(val);
966         if (val.len)
967             p->cond = bstrto0(p, val);
968         return 0;
969     }
970     if (bstr_equals0(name, profile_restore_mode_opt.name)) {
971         return m_option_parse(config->log, &profile_restore_mode_opt, name, val,
972                               &p->restore_mode);
973     }
974 
975     int i = m_config_set_option_cli(config, name, val,
976                                     M_SETOPT_CHECK_ONLY |
977                                     M_SETOPT_FROM_CONFIG_FILE);
978     if (i < 0)
979         return i;
980     p->opts = talloc_realloc(p, p->opts, char *, 2 * (p->num_opts + 2));
981     p->opts[p->num_opts * 2] = bstrto0(p, name);
982     p->opts[p->num_opts * 2 + 1] = bstrto0(p, val);
983     p->num_opts++;
984     p->opts[p->num_opts * 2] = p->opts[p->num_opts * 2 + 1] = NULL;
985     return 1;
986 }
987 
find_check_profile(struct m_config * config,char * name)988 static struct m_profile *find_check_profile(struct m_config *config, char *name)
989 {
990     struct m_profile *p = m_config_get_profile0(config, name);
991     if (!p) {
992         MP_WARN(config, "Unknown profile '%s'.\n", name);
993         return NULL;
994     }
995     if (config->profile_depth > MAX_PROFILE_DEPTH) {
996         MP_WARN(config, "WARNING: Profile inclusion too deep.\n");
997         return NULL;
998     }
999     return p;
1000 }
1001 
m_config_set_profile(struct m_config * config,char * name,int flags)1002 int m_config_set_profile(struct m_config *config, char *name, int flags)
1003 {
1004     MP_VERBOSE(config, "Applying profile '%s'...\n", name);
1005     struct m_profile *p = find_check_profile(config, name);
1006     if (!p)
1007         return M_OPT_INVALID;
1008 
1009     if (!config->profile_backup_tmp && p->restore_mode) {
1010         config->profile_backup_tmp = &p->backups;
1011         config->profile_backup_flags = p->restore_mode == 2 ? BACKUP_NVAL : 0;
1012     }
1013 
1014     config->profile_depth++;
1015     for (int i = 0; i < p->num_opts; i++) {
1016         m_config_set_option_cli(config,
1017                                 bstr0(p->opts[2 * i]),
1018                                 bstr0(p->opts[2 * i + 1]),
1019                                 flags | M_SETOPT_FROM_CONFIG_FILE);
1020     }
1021     config->profile_depth--;
1022 
1023     if (config->profile_backup_tmp == &p->backups) {
1024         config->profile_backup_tmp = NULL;
1025 
1026         for (struct m_opt_backup *bc = p->backups; bc; bc = bc->next) {
1027             if (bc && bc->nval)
1028                 m_option_copy(bc->co->opt, bc->nval, bc->co->data);
1029             talloc_steal(p, bc);
1030         }
1031     }
1032 
1033     return 0;
1034 }
1035 
m_config_restore_profile(struct m_config * config,char * name)1036 int m_config_restore_profile(struct m_config *config, char *name)
1037 {
1038     MP_VERBOSE(config, "Restoring from profile '%s'...\n", name);
1039     struct m_profile *p = find_check_profile(config, name);
1040     if (!p)
1041         return M_OPT_INVALID;
1042 
1043     if (!p->backups)
1044         MP_WARN(config, "Profile contains no restore data.\n");
1045 
1046     restore_backups(&p->backups, config);
1047 
1048     return 0;
1049 }
1050 
m_config_finish_default_profile(struct m_config * config,int flags)1051 void m_config_finish_default_profile(struct m_config *config, int flags)
1052 {
1053     struct m_profile *p = m_config_add_profile(config, NULL);
1054     m_config_set_profile(config, p->name, flags);
1055     p->num_opts = 0;
1056 }
1057 
m_config_get_profiles(struct m_config * config)1058 struct mpv_node m_config_get_profiles(struct m_config *config)
1059 {
1060     struct mpv_node root;
1061     node_init(&root, MPV_FORMAT_NODE_ARRAY, NULL);
1062 
1063     for (m_profile_t *profile = config->profiles; profile; profile = profile->next)
1064     {
1065         struct mpv_node *entry = node_array_add(&root, MPV_FORMAT_NODE_MAP);
1066 
1067         node_map_add_string(entry, "name", profile->name);
1068         if (profile->desc)
1069             node_map_add_string(entry, "profile-desc", profile->desc);
1070         if (profile->cond)
1071             node_map_add_string(entry, "profile-cond", profile->cond);
1072         if (profile->restore_mode) {
1073             char *s =
1074                 m_option_print(&profile_restore_mode_opt, &profile->restore_mode);
1075             node_map_add_string(entry, profile_restore_mode_opt.name, s);
1076             talloc_free(s);
1077         }
1078 
1079         struct mpv_node *opts =
1080             node_map_add(entry, "options", MPV_FORMAT_NODE_ARRAY);
1081 
1082         for (int n = 0; n < profile->num_opts; n++) {
1083             struct mpv_node *opt_entry = node_array_add(opts, MPV_FORMAT_NODE_MAP);
1084             node_map_add_string(opt_entry, "key", profile->opts[n * 2 + 0]);
1085             node_map_add_string(opt_entry, "value", profile->opts[n * 2 + 1]);
1086         }
1087     }
1088 
1089     return root;
1090 }
1091