1 /*****************************************************************************
2  * file.c: configuration file handling
3  *****************************************************************************
4  * Copyright (C) 2001-2007 VLC authors and VideoLAN
5  * $Id: 6270a6bc9d621ae6a0d7a23da5dbb6e7a176487a $
6  *
7  * Authors: Gildas Bazin <gbazin@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23 
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27 
28 #include <errno.h>                                                  /* errno */
29 #include <assert.h>
30 #include <limits.h>
31 #include <fcntl.h>
32 #include <sys/stat.h>
33 #ifdef __APPLE__
34 #   include <xlocale.h>
35 #elif defined(HAVE_USELOCALE)
36 #include <locale.h>
37 #endif
38 #include <unistd.h>
39 
40 #include <vlc_common.h>
41 #include "../libvlc.h"
42 #include <vlc_charset.h>
43 #include <vlc_fs.h>
44 #include <vlc_actions.h>
45 #include <vlc_modules.h>
46 #include <vlc_plugin.h>
47 
48 #include "configuration.h"
49 #include "modules/modules.h"
50 
strdupnull(const char * src)51 static inline char *strdupnull (const char *src)
52 {
53     return src ? strdup (src) : NULL;
54 }
55 
56 /**
57  * Get the user's configuration file
58  */
config_GetConfigFile(vlc_object_t * obj)59 static char *config_GetConfigFile( vlc_object_t *obj )
60 {
61     char *psz_file = var_CreateGetNonEmptyString( obj, "config" );
62     var_Destroy( obj, "config" );
63     if( psz_file == NULL )
64     {
65         char *psz_dir = config_GetUserDir( VLC_CONFIG_DIR );
66 
67         if( asprintf( &psz_file, "%s" DIR_SEP CONFIG_FILE, psz_dir ) == -1 )
68             psz_file = NULL;
69         free( psz_dir );
70     }
71     return psz_file;
72 }
73 
config_OpenConfigFile(vlc_object_t * p_obj)74 static FILE *config_OpenConfigFile( vlc_object_t *p_obj )
75 {
76     char *psz_filename = config_GetConfigFile( p_obj );
77     if( psz_filename == NULL )
78         return NULL;
79 
80     msg_Dbg( p_obj, "opening config file (%s)", psz_filename );
81 
82     FILE *p_stream = vlc_fopen( psz_filename, "rt" );
83     if( p_stream == NULL && errno != ENOENT )
84     {
85         msg_Err( p_obj, "cannot open config file (%s): %s",
86                  psz_filename, vlc_strerror_c(errno) );
87 
88     }
89 #if !( defined(_WIN32) || defined(__APPLE__) || defined(__OS2__) )
90     else if( p_stream == NULL && errno == ENOENT )
91     {
92         /* This is the fallback for pre XDG Base Directory
93          * Specification configs */
94         char *home = config_GetUserDir(VLC_HOME_DIR);
95         char *psz_old;
96 
97         if( home != NULL
98          && asprintf( &psz_old, "%s/.vlc/" CONFIG_FILE,
99                       home ) != -1 )
100         {
101             p_stream = vlc_fopen( psz_old, "rt" );
102             if( p_stream )
103             {
104                 /* Old config file found. We want to write it at the
105                  * new location now. */
106                 msg_Info( p_obj, "Found old config file at %s. "
107                           "VLC will now use %s.", psz_old, psz_filename );
108                 char *psz_readme;
109                 if( asprintf(&psz_readme,"%s/.vlc/README",
110                              home ) != -1 )
111                 {
112                     FILE *p_readme = vlc_fopen( psz_readme, "wt" );
113                     if( p_readme )
114                     {
115                         fprintf( p_readme, "The VLC media player "
116                                  "configuration folder has moved to comply\n"
117                                  "with the XDG Base Directory Specification "
118                                  "version 0.6. Your\nconfiguration has been "
119                                  "copied to the new location:\n%s\nYou can "
120                                  "delete this directory and all its contents.",
121                                   psz_filename);
122                         fclose( p_readme );
123                     }
124                     free( psz_readme );
125                 }
126                 /* Remove the old configuration file so that --reset-config
127                  * can work properly. Fortunately, Linux allows removing
128                  * open files - with most filesystems. */
129                 unlink( psz_old );
130             }
131             free( psz_old );
132         }
133         free( home );
134     }
135 #endif
136     free( psz_filename );
137     return p_stream;
138 }
139 
140 
vlc_strtoi(const char * str)141 static int64_t vlc_strtoi (const char *str)
142 {
143     char *end;
144     long long l;
145 
146     errno = 0;
147     l = strtoll (str, &end, 0);
148 
149     if (!errno)
150     {
151 #if (LLONG_MAX > 0x7fffffffffffffffLL)
152         if (l > 0x7fffffffffffffffLL
153          || l < -0x8000000000000000LL)
154             errno = ERANGE;
155 #endif
156         if (*end)
157             errno = EINVAL;
158     }
159     return l;
160 }
161 
162 #undef config_LoadConfigFile
163 /*****************************************************************************
164  * config_LoadConfigFile: loads the configuration file.
165  *****************************************************************************
166  * This function is called to load the config options stored in the config
167  * file.
168  *****************************************************************************/
config_LoadConfigFile(vlc_object_t * p_this)169 int config_LoadConfigFile( vlc_object_t *p_this )
170 {
171     FILE *file;
172 
173     file = config_OpenConfigFile (p_this);
174     if (file == NULL)
175         return VLC_EGENERIC;
176 
177     /* Skip UTF-8 Byte Order Mark if present */
178     char bom[3];
179     if (fread (bom, 1, 3, file) != 3 || memcmp (bom, "\xEF\xBB\xBF", 3))
180         rewind (file); /* no BOM, rewind */
181 
182     char *line = NULL;
183     size_t bufsize;
184     ssize_t linelen;
185 
186     /* Ensure consistent number formatting... */
187     locale_t loc = newlocale (LC_NUMERIC_MASK, "C", NULL);
188     locale_t baseloc = uselocale (loc);
189 
190     vlc_rwlock_wrlock (&config_lock);
191     while ((linelen = getline (&line, &bufsize, file)) != -1)
192     {
193         line[linelen - 1] = '\0'; /* trim newline */
194 
195         /* Ignore comments, section and empty lines */
196         if (memchr ("#[", line[0], 3) != NULL)
197             continue;
198 
199         /* look for option name */
200         const char *psz_option_name = line;
201 
202         char *ptr = strchr (line, '=');
203         if (ptr == NULL)
204             continue; /* syntax error */
205         *ptr = '\0';
206 
207         module_config_t *item = config_FindConfig(psz_option_name);
208         if (item == NULL)
209             continue;
210 
211         const char *psz_option_value = ptr + 1;
212         switch (CONFIG_CLASS(item->i_type))
213         {
214             case CONFIG_ITEM_BOOL:
215             case CONFIG_ITEM_INTEGER:
216             {
217                 int64_t l;
218 
219                 errno = 0;
220                 l = vlc_strtoi (psz_option_value);
221                 if ((l > item->max.i) || (l < item->min.i))
222                     errno = ERANGE;
223                 if (errno)
224                     msg_Warn (p_this, "Integer value (%s) for %s: %s",
225                               psz_option_value, psz_option_name,
226                               vlc_strerror_c(errno));
227                 else
228                     item->value.i = l;
229                 break;
230             }
231 
232             case CONFIG_ITEM_FLOAT:
233                 if (!*psz_option_value)
234                     break;                    /* ignore empty option */
235                 item->value.f = (float)atof (psz_option_value);
236                 break;
237 
238             default:
239                 free (item->value.psz);
240                 item->value.psz = strdupnull (psz_option_value);
241                 break;
242         }
243     }
244     vlc_rwlock_unlock (&config_lock);
245     free (line);
246 
247     if (ferror (file))
248     {
249         msg_Err (p_this, "error reading configuration: %s",
250                  vlc_strerror_c(errno));
251         clearerr (file);
252     }
253     fclose (file);
254 
255     if (loc != (locale_t)0)
256     {
257         uselocale (baseloc);
258         freelocale (loc);
259     }
260     return 0;
261 }
262 
263 /*****************************************************************************
264  * config_CreateDir: Create configuration directory if it doesn't exist.
265  *****************************************************************************/
config_CreateDir(vlc_object_t * p_this,const char * psz_dirname)266 int config_CreateDir( vlc_object_t *p_this, const char *psz_dirname )
267 {
268     if( !psz_dirname || !*psz_dirname ) return -1;
269 
270     if( vlc_mkdir( psz_dirname, 0700 ) == 0 )
271         return 0;
272 
273     switch( errno )
274     {
275         case EEXIST:
276             return 0;
277 
278         case ENOENT:
279         {
280             /* Let's try to create the parent directory */
281             char psz_parent[strlen( psz_dirname ) + 1], *psz_end;
282             strcpy( psz_parent, psz_dirname );
283 
284             psz_end = strrchr( psz_parent, DIR_SEP_CHAR );
285             if( psz_end && psz_end != psz_parent )
286             {
287                 *psz_end = '\0';
288                 if( config_CreateDir( p_this, psz_parent ) == 0 )
289                 {
290                     if( !vlc_mkdir( psz_dirname, 0700 ) )
291                         return 0;
292                 }
293             }
294         }
295     }
296 
297     msg_Warn( p_this, "could not create %s: %s", psz_dirname,
298               vlc_strerror_c(errno) );
299     return -1;
300 }
301 
302 static int
config_Write(FILE * file,const char * desc,const char * type,bool comment,const char * name,const char * fmt,...)303 config_Write (FILE *file, const char *desc, const char *type,
304               bool comment, const char *name, const char *fmt, ...)
305 {
306     va_list ap;
307     int ret;
308 
309     if (desc == NULL)
310         desc = "?";
311 
312     if (fprintf (file, "# %s (%s)\n%s%s=", desc, vlc_gettext (type),
313                  comment ? "#" : "", name) < 0)
314         return -1;
315 
316     va_start (ap, fmt);
317     ret = vfprintf (file, fmt, ap);
318     va_end (ap);
319     if (ret < 0)
320         return -1;
321 
322     if (fputs ("\n\n", file) == EOF)
323         return -1;
324     return 0;
325 }
326 
327 
config_PrepareDir(vlc_object_t * obj)328 static int config_PrepareDir (vlc_object_t *obj)
329 {
330     char *psz_configdir = config_GetUserDir (VLC_CONFIG_DIR);
331     if (psz_configdir == NULL)
332         return -1;
333 
334     int ret = config_CreateDir (obj, psz_configdir);
335     free (psz_configdir);
336     return ret;
337 }
338 
339 #undef config_SaveConfigFile
340 /**
341  * Saves the in-memory configuration into a file.
342  * @return 0 on success, -1 on error.
343  */
config_SaveConfigFile(vlc_object_t * p_this)344 int config_SaveConfigFile (vlc_object_t *p_this)
345 {
346 
347     if( config_PrepareDir( p_this ) )
348     {
349         msg_Err( p_this, "no configuration directory" );
350         return -1;
351     }
352 
353     /*
354      * Save module config in file
355      */
356     char *temporary;
357     char *permanent = config_GetConfigFile (p_this);
358     if (permanent == NULL)
359         return -1;
360     if (asprintf (&temporary, "%s.%u", permanent, getpid ()) == -1)
361     {
362         free (permanent);
363         return -1;
364     }
365     else
366     {
367         struct stat st;
368 
369         /* Some users make vlcrc read-only to prevent changes.
370          * The atomic replacement scheme breaks this "feature",
371          * so we check for read-only by hand. */
372         if (stat (permanent, &st) == 0 && !(st.st_mode & S_IWUSR))
373         {
374             msg_Err (p_this, "configuration file is read-only");
375             goto error;
376         }
377     }
378 
379     /* Configuration lock must be taken before vlcrc serializer below. */
380     vlc_rwlock_rdlock (&config_lock);
381 
382     /* The temporary configuration file is per-PID. Therefore this function
383      * should be serialized against itself within a given process. */
384     static vlc_mutex_t lock = VLC_STATIC_MUTEX;
385     vlc_mutex_lock (&lock);
386 
387     int fd = vlc_open (temporary, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR|S_IWUSR);
388     if (fd == -1)
389     {
390         vlc_rwlock_unlock (&config_lock);
391         vlc_mutex_unlock (&lock);
392         goto error;
393     }
394     FILE *file = fdopen (fd, "wt");
395     if (file == NULL)
396     {
397         msg_Err (p_this, "cannot create configuration file: %s",
398                  vlc_strerror_c(errno));
399         vlc_rwlock_unlock (&config_lock);
400         vlc_close (fd);
401         vlc_mutex_unlock (&lock);
402         goto error;
403     }
404 
405     fprintf( file,
406         "\xEF\xBB\xBF###\n"
407         "###  "PACKAGE_NAME" "PACKAGE_VERSION"\n"
408         "###\n"
409         "\n"
410         "###\n"
411         "### lines beginning with a '#' character are comments\n"
412         "###\n"
413         "\n" );
414 
415     /* Ensure consistent number formatting... */
416     locale_t loc = newlocale (LC_NUMERIC_MASK, "C", NULL);
417     locale_t baseloc = uselocale (loc);
418 
419     /* We would take the config lock here. But this would cause a lock
420      * inversion with the serializer above and config_AutoSaveConfigFile().
421     vlc_rwlock_rdlock (&config_lock);*/
422 
423     /* Look for the selected module, if NULL then save everything */
424     for (vlc_plugin_t *p = vlc_plugins; p != NULL; p = p->next)
425     {
426         module_t *p_parser = p->module;
427         module_config_t *p_item, *p_end;
428 
429         if (p->conf.count == 0)
430             continue;
431 
432         fprintf( file, "[%s]", module_get_object (p_parser) );
433         if( p_parser->psz_longname )
434             fprintf( file, " # %s\n\n", p_parser->psz_longname );
435         else
436             fprintf( file, "\n\n" );
437 
438         for (p_item = p->conf.items, p_end = p_item + p->conf.size;
439              p_item < p_end;
440              p_item++)
441         {
442             if (!CONFIG_ITEM(p_item->i_type)   /* ignore hint */
443              || p_item->b_removed              /* ignore deprecated option */
444              || p_item->b_unsaveable)          /* ignore volatile option */
445                 continue;
446 
447             if (IsConfigIntegerType (p_item->i_type))
448             {
449                 int64_t val = p_item->value.i;
450                 config_Write (file, p_item->psz_text,
451                              (CONFIG_CLASS(p_item->i_type) == CONFIG_ITEM_BOOL)
452                                   ? N_("boolean") : N_("integer"),
453                               val == p_item->orig.i,
454                               p_item->psz_name, "%"PRId64, val);
455             }
456             else
457             if (IsConfigFloatType (p_item->i_type))
458             {
459                 float val = p_item->value.f;
460                 config_Write (file, p_item->psz_text, N_("float"),
461                               val == p_item->orig.f,
462                               p_item->psz_name, "%f", val);
463             }
464             else
465             {
466                 const char *psz_value = p_item->value.psz;
467                 bool modified;
468 
469                 assert (IsConfigStringType (p_item->i_type));
470 
471                 modified = !!strcmp (psz_value ? psz_value : "",
472                                      p_item->orig.psz ? p_item->orig.psz : "");
473                 config_Write (file, p_item->psz_text, N_("string"),
474                               !modified, p_item->psz_name, "%s",
475                               psz_value ? psz_value : "");
476             }
477         }
478     }
479     vlc_rwlock_unlock (&config_lock);
480 
481     if (loc != (locale_t)0)
482     {
483         uselocale (baseloc);
484         freelocale (loc);
485     }
486 
487     /*
488      * Flush to disk and replace atomically
489      */
490     fflush (file); /* Flush from run-time */
491     if (ferror (file))
492     {
493         vlc_unlink (temporary);
494         vlc_mutex_unlock (&lock);
495         msg_Err (p_this, "cannot write configuration file");
496         fclose (file);
497         goto error;
498     }
499     fdatasync (fd); /* Flush from OS */
500 #if defined (_WIN32) || defined (__OS2__)
501     /* Windows cannot (re)move open files nor overwrite existing ones */
502     fclose (file);
503     vlc_unlink (permanent);
504 #endif
505     /* Atomically replace the file... */
506     if (vlc_rename (temporary, permanent))
507         vlc_unlink (temporary);
508     /* (...then synchronize the directory, err, TODO...) */
509     /* ...and finally close the file */
510     vlc_mutex_unlock (&lock);
511 #if !defined (_WIN32) && !defined (__OS2__)
512     fclose (file);
513 #endif
514 
515     free (temporary);
516     free (permanent);
517     return 0;
518 
519 error:
520     free (temporary);
521     free (permanent);
522     return -1;
523 }
524 
config_AutoSaveConfigFile(vlc_object_t * p_this)525 int config_AutoSaveConfigFile( vlc_object_t *p_this )
526 {
527     int ret = 0;
528 
529     assert( p_this );
530 
531     vlc_rwlock_rdlock (&config_lock);
532     if (config_dirty)
533     {
534         /* Note: this will get the read lock recursively. Ok. */
535         ret = config_SaveConfigFile (p_this);
536         config_dirty = (ret != 0);
537     }
538     vlc_rwlock_unlock (&config_lock);
539 
540     return ret;
541 }
542