config: assume UTF-8 vlcrc
[vlc.git] / src / config / file.c
1 /*****************************************************************************
2  * file.c: configuration file handling
3  *****************************************************************************
4  * Copyright (C) 2001-2007 VLC authors and VideoLAN
5  * $Id$
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_keys.h>
45 #include <vlc_modules.h>
46 #include <vlc_plugin.h>
47
48 #include "configuration.h"
49 #include "modules/modules.h"
50
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  */
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
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->p_libvlc, "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
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  *****************************************************************************/
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 (p_this, 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 ((char *)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  *****************************************************************************/
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
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
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  */
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         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     size_t count;
425     module_t **list = module_list_get (&count);
426     for (size_t i = 0; i < count; i++)
427     {
428         module_t *p_parser = list[i];
429         module_config_t *p_item, *p_end;
430
431         if( !p_parser->i_config_items )
432             continue;
433
434         fprintf( file, "[%s]", module_get_object (p_parser) );
435         if( p_parser->psz_longname )
436             fprintf( file, " # %s\n\n", p_parser->psz_longname );
437         else
438             fprintf( file, "\n\n" );
439
440         for( p_item = p_parser->p_config, p_end = p_item + p_parser->confsize;
441              p_item < p_end;
442              p_item++ )
443         {
444             if (!CONFIG_ITEM(p_item->i_type)   /* ignore hint */
445              || p_item->b_removed              /* ignore deprecated option */
446              || p_item->b_unsaveable)          /* ignore volatile option */
447                 continue;
448
449             if (IsConfigIntegerType (p_item->i_type))
450             {
451                 int64_t val = p_item->value.i;
452                 config_Write (file, p_item->psz_text,
453                              (CONFIG_CLASS(p_item->i_type) == CONFIG_ITEM_BOOL)
454                                   ? N_("boolean") : N_("integer"),
455                               val == p_item->orig.i,
456                               p_item->psz_name, "%"PRId64, val);
457             }
458             else
459             if (IsConfigFloatType (p_item->i_type))
460             {
461                 float val = p_item->value.f;
462                 config_Write (file, p_item->psz_text, N_("float"),
463                               val == p_item->orig.f,
464                               p_item->psz_name, "%f", val);
465             }
466             else
467             {
468                 const char *psz_value = p_item->value.psz;
469                 bool modified;
470
471                 assert (IsConfigStringType (p_item->i_type));
472
473                 modified = !!strcmp (psz_value ? psz_value : "",
474                                      p_item->orig.psz ? p_item->orig.psz : "");
475                 config_Write (file, p_item->psz_text, N_("string"),
476                               !modified, p_item->psz_name, "%s",
477                               psz_value ? psz_value : "");
478             }
479         }
480     }
481     vlc_rwlock_unlock (&config_lock);
482
483     module_list_free (list);
484     if (loc != (locale_t)0)
485     {
486         uselocale (baseloc);
487         freelocale (loc);
488     }
489
490     /*
491      * Flush to disk and replace atomically
492      */
493     fflush (file); /* Flush from run-time */
494     if (ferror (file))
495     {
496         vlc_unlink (temporary);
497         vlc_mutex_unlock (&lock);
498         msg_Err (p_this, "cannot write configuration file");
499         fclose (file);
500         goto error;
501     }
502 #if defined(__APPLE__) || defined(__ANDROID__)
503     fsync (fd); /* Flush from OS */
504 #else
505     fdatasync (fd); /* Flush from OS */
506 #endif
507 #if defined (_WIN32) || defined (__OS2__)
508     /* Windows cannot (re)move open files nor overwrite existing ones */
509     fclose (file);
510     vlc_unlink (permanent);
511 #endif
512     /* Atomically replace the file... */
513     if (vlc_rename (temporary, permanent))
514         vlc_unlink (temporary);
515     /* (...then synchronize the directory, err, TODO...) */
516     /* ...and finally close the file */
517     vlc_mutex_unlock (&lock);
518 #if !defined (_WIN32) && !defined (__OS2__)
519     fclose (file);
520 #endif
521
522     free (temporary);
523     free (permanent);
524     return 0;
525
526 error:
527     free (temporary);
528     free (permanent);
529     return -1;
530 }
531
532 int config_AutoSaveConfigFile( vlc_object_t *p_this )
533 {
534     int ret = 0;
535
536     assert( p_this );
537
538     vlc_rwlock_rdlock (&config_lock);
539     if (config_dirty)
540     {
541         /* Note: this will get the read lock recursively. Ok. */
542         ret = config_SaveConfigFile (p_this);
543         config_dirty = (ret != 0);
544     }
545     vlc_rwlock_unlock (&config_lock);
546
547     return ret;
548 }