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 <errno.h>
19 #include <stddef.h>
20 #include <stdbool.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <fcntl.h>
24 #include <unistd.h>
25 #include <utime.h>
26 
27 #include <libavutil/md5.h>
28 
29 #include "config.h"
30 #include "mpv_talloc.h"
31 
32 #include "osdep/io.h"
33 
34 #include "common/global.h"
35 #include "common/encode.h"
36 #include "common/msg.h"
37 #include "misc/ctype.h"
38 #include "options/path.h"
39 #include "options/m_config.h"
40 #include "options/m_config_frontend.h"
41 #include "options/parse_configfile.h"
42 #include "common/playlist.h"
43 #include "options/options.h"
44 #include "options/m_property.h"
45 
46 #include "stream/stream.h"
47 
48 #include "core.h"
49 #include "command.h"
50 
load_all_cfgfiles(struct MPContext * mpctx,char * section,char * filename)51 static void load_all_cfgfiles(struct MPContext *mpctx, char *section,
52                               char *filename)
53 {
54     char **cf = mp_find_all_config_files(NULL, mpctx->global, filename);
55     for (int i = 0; cf && cf[i]; i++)
56         m_config_parse_config_file(mpctx->mconfig, cf[i], section, 0);
57     talloc_free(cf);
58 }
59 
60 // This name is used in builtin.conf to force encoding defaults (like ao/vo).
61 #define SECT_ENCODE "encoding"
62 
mp_parse_cfgfiles(struct MPContext * mpctx)63 void mp_parse_cfgfiles(struct MPContext *mpctx)
64 {
65     struct MPOpts *opts = mpctx->opts;
66 
67     mp_mk_config_dir(mpctx->global, "");
68 
69     char *p1 = mp_get_user_path(NULL, mpctx->global, "~~home/");
70     char *p2 = mp_get_user_path(NULL, mpctx->global, "~~old_home/");
71     if (strcmp(p1, p2) != 0 && mp_path_exists(p2)) {
72         MP_WARN(mpctx, "Warning, two config dirs found:\n   %s (main)\n"
73                 "   %s (bogus)\nYou should merge or delete the second one.\n",
74                 p1, p2);
75     }
76     talloc_free(p1);
77     talloc_free(p2);
78 
79     char *section = NULL;
80     bool encoding = opts->encode_opts &&
81         opts->encode_opts->file && opts->encode_opts->file[0];
82     // In encoding mode, we don't want to apply normal config options.
83     // So we "divert" normal options into a separate section, and the diverted
84     // section is never used - unless maybe it's explicitly referenced from an
85     // encoding profile.
86     if (encoding)
87         section = "playback-default";
88 
89     load_all_cfgfiles(mpctx, NULL, "encoding-profiles.conf");
90 
91     load_all_cfgfiles(mpctx, section, "mpv.conf|config");
92 
93     if (encoding)
94         m_config_set_profile(mpctx->mconfig, SECT_ENCODE, 0);
95 }
96 
try_load_config(struct MPContext * mpctx,const char * file,int flags,int msgl)97 static int try_load_config(struct MPContext *mpctx, const char *file, int flags,
98                            int msgl)
99 {
100     if (!mp_path_exists(file))
101         return 0;
102     MP_MSG(mpctx, msgl, "Loading config '%s'\n", file);
103     m_config_parse_config_file(mpctx->mconfig, file, NULL, flags);
104     return 1;
105 }
106 
107 // Set options file-local, and don't set them if the user set them via the
108 // command line.
109 #define FILE_LOCAL_FLAGS (M_SETOPT_BACKUP | M_SETOPT_PRESERVE_CMDLINE)
110 
mp_load_per_file_config(struct MPContext * mpctx)111 static void mp_load_per_file_config(struct MPContext *mpctx)
112 {
113     struct MPOpts *opts = mpctx->opts;
114     char *confpath;
115     char cfg[512];
116     const char *file = mpctx->filename;
117 
118     if (opts->use_filedir_conf) {
119         if (snprintf(cfg, sizeof(cfg), "%s.conf", file) >= sizeof(cfg)) {
120             MP_VERBOSE(mpctx, "Filename is too long, can not load file or "
121                               "directory specific config files\n");
122             return;
123         }
124 
125         char *name = mp_basename(cfg);
126 
127         bstr dir = mp_dirname(cfg);
128         char *dircfg = mp_path_join_bstr(NULL, dir, bstr0("mpv.conf"));
129         try_load_config(mpctx, dircfg, FILE_LOCAL_FLAGS, MSGL_INFO);
130         talloc_free(dircfg);
131 
132         if (try_load_config(mpctx, cfg, FILE_LOCAL_FLAGS, MSGL_INFO))
133             return;
134 
135         if ((confpath = mp_find_config_file(NULL, mpctx->global, name))) {
136             try_load_config(mpctx, confpath, FILE_LOCAL_FLAGS, MSGL_INFO);
137 
138             talloc_free(confpath);
139         }
140     }
141 }
142 
mp_auto_load_profile(struct MPContext * mpctx,char * category,bstr item)143 static void mp_auto_load_profile(struct MPContext *mpctx, char *category,
144                                  bstr item)
145 {
146     if (!item.len)
147         return;
148 
149     char t[512];
150     snprintf(t, sizeof(t), "%s.%.*s", category, BSTR_P(item));
151     m_profile_t *p = m_config_get_profile0(mpctx->mconfig, t);
152     if (p) {
153         MP_INFO(mpctx, "Auto-loading profile '%s'\n", t);
154         m_config_set_profile(mpctx->mconfig, t, FILE_LOCAL_FLAGS);
155     }
156 }
157 
mp_load_auto_profiles(struct MPContext * mpctx)158 void mp_load_auto_profiles(struct MPContext *mpctx)
159 {
160     mp_auto_load_profile(mpctx, "protocol",
161                          mp_split_proto(bstr0(mpctx->filename), NULL));
162     mp_auto_load_profile(mpctx, "extension",
163                          bstr0(mp_splitext(mpctx->filename, NULL)));
164 
165     mp_load_per_file_config(mpctx);
166 }
167 
168 #define MP_WATCH_LATER_CONF "watch_later"
169 
check_mtime(const char * f1,const char * f2)170 static bool check_mtime(const char *f1, const char *f2)
171 {
172     struct stat st1, st2;
173     if (stat(f1, &st1) != 0 || stat(f2, &st2) != 0)
174         return false;
175     return st1.st_mtime == st2.st_mtime;
176 }
177 
copy_mtime(const char * f1,const char * f2)178 static bool copy_mtime(const char *f1, const char *f2)
179 {
180     struct stat st1, st2;
181 
182     if (stat(f1, &st1) != 0 || stat(f2, &st2) != 0)
183         return false;
184 
185     struct utimbuf ut = {
186         .actime = st2.st_atime,  // we want to pass this through intact
187         .modtime = st1.st_mtime,
188     };
189 
190     if (utime(f2, &ut) != 0)
191         return false;
192 
193     return true;
194 }
195 
mp_get_playback_resume_config_filename(struct MPContext * mpctx,const char * fname)196 static char *mp_get_playback_resume_config_filename(struct MPContext *mpctx,
197                                                     const char *fname)
198 {
199     struct MPOpts *opts = mpctx->opts;
200     char *res = NULL;
201     void *tmp = talloc_new(NULL);
202     const char *realpath = fname;
203     bstr bfname = bstr0(fname);
204     if (!mp_is_url(bfname)) {
205         if (opts->ignore_path_in_watch_later_config) {
206             realpath = mp_basename(fname);
207         } else {
208             char *cwd = mp_getcwd(tmp);
209             if (!cwd)
210                 goto exit;
211             realpath = mp_path_join(tmp, cwd, fname);
212         }
213     }
214     uint8_t md5[16];
215     av_md5_sum(md5, realpath, strlen(realpath));
216     char *conf = talloc_strdup(tmp, "");
217     for (int i = 0; i < 16; i++)
218         conf = talloc_asprintf_append(conf, "%02X", md5[i]);
219 
220     if (!mpctx->cached_watch_later_configdir) {
221         char *wl_dir = mpctx->opts->watch_later_directory;
222         if (wl_dir && wl_dir[0]) {
223             mpctx->cached_watch_later_configdir =
224                 mp_get_user_path(mpctx, mpctx->global, wl_dir);
225         }
226     }
227 
228     if (!mpctx->cached_watch_later_configdir) {
229         mpctx->cached_watch_later_configdir =
230             mp_find_user_config_file(mpctx, mpctx->global, MP_WATCH_LATER_CONF);
231     }
232 
233     if (mpctx->cached_watch_later_configdir)
234         res = mp_path_join(NULL, mpctx->cached_watch_later_configdir, conf);
235 
236 exit:
237     talloc_free(tmp);
238     return res;
239 }
240 
241 // Should follow what parser-cfg.c does/needs
needs_config_quoting(const char * s)242 static bool needs_config_quoting(const char *s)
243 {
244     if (s[0] == '%')
245         return true;
246     for (int i = 0; s[i]; i++) {
247         unsigned char c = s[i];
248         if (!mp_isprint(c) || mp_isspace(c) || c == '#' || c == '\'' || c == '"')
249             return true;
250     }
251     return false;
252 }
253 
write_filename(struct MPContext * mpctx,FILE * file,char * filename)254 static void write_filename(struct MPContext *mpctx, FILE *file, char *filename)
255 {
256     if (mpctx->opts->write_filename_in_watch_later_config) {
257         char write_name[1024] = {0};
258         for (int n = 0; filename[n] && n < sizeof(write_name) - 1; n++)
259             write_name[n] = (unsigned char)filename[n] < 32 ? '_' : filename[n];
260         fprintf(file, "# %s\n", write_name);
261     }
262 }
263 
write_redirect(struct MPContext * mpctx,char * path)264 static void write_redirect(struct MPContext *mpctx, char *path)
265 {
266     char *conffile = mp_get_playback_resume_config_filename(mpctx, path);
267     if (conffile) {
268         FILE *file = fopen(conffile, "wb");
269         if (file) {
270             fprintf(file, "# redirect entry\n");
271             write_filename(mpctx, file, path);
272             fclose(file);
273         }
274 
275         if (mpctx->opts->position_check_mtime &&
276             !mp_is_url(bstr0(path)) && !copy_mtime(path, conffile))
277             MP_WARN(mpctx, "Can't copy mtime from %s to %s\n", path, conffile);
278 
279         talloc_free(conffile);
280     }
281 }
282 
mp_write_watch_later_conf(struct MPContext * mpctx)283 void mp_write_watch_later_conf(struct MPContext *mpctx)
284 {
285     struct playlist_entry *cur = mpctx->playing;
286     char *conffile = NULL;
287     if (!cur)
288         goto exit;
289 
290     struct demuxer *demux = mpctx->demuxer;
291 
292     conffile = mp_get_playback_resume_config_filename(mpctx, cur->filename);
293     if (!conffile)
294         goto exit;
295 
296     mp_mk_config_dir(mpctx->global, mpctx->cached_watch_later_configdir);
297 
298     MP_INFO(mpctx, "Saving state.\n");
299 
300     FILE *file = fopen(conffile, "wb");
301     if (!file)
302         goto exit;
303 
304     write_filename(mpctx, file, cur->filename);
305 
306     double pos = get_current_time(mpctx);
307 
308     if ((demux && (!demux->seekable || demux->partially_seekable)) ||
309         pos == MP_NOPTS_VALUE)
310     {
311         MP_INFO(mpctx, "Not seekable, or time unknown - not saving position.\n");
312     } else {
313         fprintf(file, "start=%f\n", pos);
314     }
315     char **watch_later_options = mpctx->opts->watch_later_options;
316     for (int i = 0; watch_later_options && watch_later_options[i]; i++) {
317         char *pname = watch_later_options[i];
318         // Only store it if it's different from the initial value.
319         if (m_config_watch_later_backup_opt_changed(mpctx->mconfig, pname)) {
320             char *val = NULL;
321             mp_property_do(pname, M_PROPERTY_GET_STRING, &val, mpctx);
322             if (needs_config_quoting(val)) {
323                 // e.g. '%6%STRING'
324                 fprintf(file, "%s=%%%d%%%s\n", pname, (int)strlen(val), val);
325             } else {
326                 fprintf(file, "%s=%s\n", pname, val);
327             }
328             talloc_free(val);
329         }
330     }
331     fclose(file);
332 
333     if (mpctx->opts->position_check_mtime &&
334         !mp_is_url(bstr0(cur->filename)) &&
335         !copy_mtime(cur->filename, conffile))
336     {
337         MP_WARN(mpctx, "Can't copy mtime from %s to %s\n", cur->filename,
338                 conffile);
339     }
340 
341     // This allows us to recursively resume directories etc., whose entries are
342     // expanded the first time it's "played". For example, if "/a/b/c.mkv" is
343     // the current entry, then we want to resume this file if the user does
344     // "mpv /a". This would expand to the directory entries in "/a", and if
345     // "/a/a.mkv" is not the first entry, this would be played.
346     // Here, we write resume entries for "/a" and "/a/b".
347     // (Unfortunately, this will leave stray resume files on resume, because
348     // obviously it resumes only from one of those paths.)
349     for (int n = 0; n < cur->num_redirects; n++)
350         write_redirect(mpctx, cur->redirects[n]);
351     // And at last, for local directories, we write an entry for each path
352     // prefix, so the user can resume from an arbitrary directory. This starts
353     // with the first redirect (all other redirects are further prefixes).
354     if (cur->num_redirects) {
355         char *path = cur->redirects[0];
356         char tmp[4096];
357         if (!mp_is_url(bstr0(path)) && strlen(path) < sizeof(tmp)) {
358             snprintf(tmp, sizeof(tmp), "%s", path);
359             for (;;) {
360                 bstr dir = mp_dirname(tmp);
361                 if (dir.len == strlen(tmp) || !dir.len || bstr_equals0(dir, "."))
362                     break;
363 
364                 tmp[dir.len] = '\0';
365                 if (strlen(tmp) >= 2) // keep "/"
366                     mp_path_strip_trailing_separator(tmp);
367                 write_redirect(mpctx, tmp);
368             }
369         }
370     }
371 
372 exit:
373     talloc_free(conffile);
374 }
375 
mp_delete_watch_later_conf(struct MPContext * mpctx,const char * file)376 void mp_delete_watch_later_conf(struct MPContext *mpctx, const char *file)
377 {
378     if (!file) {
379         struct playlist_entry *cur = mpctx->playing;
380         if (!cur)
381             return;
382         file = cur->filename;
383         if (!file)
384             return;
385     }
386 
387     char *fname = mp_get_playback_resume_config_filename(mpctx, file);
388     if (fname)
389         unlink(fname);
390     talloc_free(fname);
391 }
392 
mp_load_playback_resume(struct MPContext * mpctx,const char * file)393 void mp_load_playback_resume(struct MPContext *mpctx, const char *file)
394 {
395     if (!mpctx->opts->position_resume)
396         return;
397     char *fname = mp_get_playback_resume_config_filename(mpctx, file);
398     if (fname && mp_path_exists(fname)) {
399         if (mpctx->opts->position_check_mtime &&
400             !mp_is_url(bstr0(file)) && !check_mtime(file, fname))
401         {
402             talloc_free(fname);
403             return;
404         }
405 
406         // Never apply the saved start position to following files
407         m_config_backup_opt(mpctx->mconfig, "start");
408         MP_INFO(mpctx, "Resuming playback. This behavior can "
409                "be disabled with --no-resume-playback.\n");
410         try_load_config(mpctx, fname, M_SETOPT_PRESERVE_CMDLINE, MSGL_V);
411         unlink(fname);
412     }
413     talloc_free(fname);
414 }
415 
416 // Returns the first file that has a resume config.
417 // Compared to hashing the playlist file or contents and managing separate
418 // resume file for them, this is simpler, and also has the nice property
419 // that appending to a playlist doesn't interfere with resuming (especially
420 // if the playlist comes from the command line).
mp_check_playlist_resume(struct MPContext * mpctx,struct playlist * playlist)421 struct playlist_entry *mp_check_playlist_resume(struct MPContext *mpctx,
422                                                 struct playlist *playlist)
423 {
424     if (!mpctx->opts->position_resume)
425         return NULL;
426     for (int n = 0; n < playlist->num_entries; n++) {
427         struct playlist_entry *e = playlist->entries[n];
428         char *conf = mp_get_playback_resume_config_filename(mpctx, e->filename);
429         bool exists = conf && mp_path_exists(conf);
430         talloc_free(conf);
431         if (exists)
432             return e;
433     }
434     return NULL;
435 }
436 
437