]> git.sesse.net Git - vlc/blob - src/config/file.c
Gather key functions and tables in a single file
[vlc] / src / config / file.c
1 /*****************************************************************************
2  * file.c: configuration file handling
3  *****************************************************************************
4  * Copyright (C) 2001-2007 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Gildas Bazin <gbazin@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 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 General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with this program; if not, write to the Free Software Foundation, Inc.,
21  * 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 #else
36 #include <locale.h>
37 #endif
38
39 #include <vlc_common.h>
40 #include "../libvlc.h"
41 #include "vlc_charset.h"
42 #include "vlc_keys.h"
43
44 #include "configuration.h"
45 #include "modules/modules.h"
46
47 static inline char *strdupnull (const char *src)
48 {
49     return src ? strdup (src) : NULL;
50 }
51
52 /**
53  * Get the user's configuration file
54  */
55 static char *config_GetConfigFile( vlc_object_t *obj )
56 {
57     char *psz_file = config_GetPsz( obj, "config" );
58     if( psz_file == NULL )
59     {
60         char *psz_dir = config_GetUserDir( VLC_CONFIG_DIR );
61
62         if( asprintf( &psz_file, "%s" DIR_SEP CONFIG_FILE, psz_dir ) == -1 )
63             psz_file = NULL;
64         free( psz_dir );
65     }
66     return psz_file;
67 }
68
69 static FILE *config_OpenConfigFile( vlc_object_t *p_obj )
70 {
71     char *psz_filename = config_GetConfigFile( p_obj );
72     if( psz_filename == NULL )
73         return NULL;
74
75     msg_Dbg( p_obj, "opening config file (%s)", psz_filename );
76
77     FILE *p_stream = utf8_fopen( psz_filename, "rt" );
78     if( p_stream == NULL && errno != ENOENT )
79     {
80         msg_Err( p_obj, "cannot open config file (%s): %m",
81                  psz_filename );
82
83     }
84 #if !( defined(WIN32) || defined(__APPLE__) || defined(SYS_BEOS) )
85     else if( p_stream == NULL && errno == ENOENT )
86     {
87         /* This is the fallback for pre XDG Base Directory
88          * Specification configs */
89         char *home = config_GetUserDir(VLC_HOME_DIR);
90         char *psz_old;
91
92         if( home != NULL
93          && asprintf( &psz_old, "%s/.vlc/" CONFIG_FILE,
94                       home ) != -1 )
95         {
96             p_stream = utf8_fopen( psz_old, "rt" );
97             if( p_stream )
98             {
99                 /* Old config file found. We want to write it at the
100                  * new location now. */
101                 msg_Info( p_obj->p_libvlc, "Found old config file at %s. "
102                           "VLC will now use %s.", psz_old, psz_filename );
103                 char *psz_readme;
104                 if( asprintf(&psz_readme,"%s/.vlc/README",
105                              home ) != -1 )
106                 {
107                     FILE *p_readme = utf8_fopen( psz_readme, "wt" );
108                     if( p_readme )
109                     {
110                         fprintf( p_readme, "The VLC media player "
111                                  "configuration folder has moved to comply\n"
112                                  "with the XDG Base Directory Specification "
113                                  "version 0.6. Your\nconfiguration has been "
114                                  "copied to the new location:\n%s\nYou can "
115                                  "delete this directory and all its contents.",
116                                   psz_filename);
117                         fclose( p_readme );
118                     }
119                     free( psz_readme );
120                 }
121             }
122             free( psz_old );
123         }
124         free( home );
125     }
126 #endif
127     free( psz_filename );
128     return p_stream;
129 }
130
131
132 static int strtoi (const char *str)
133 {
134     char *end;
135     long l;
136
137     errno = 0;
138     l = strtol (str, &end, 0);
139
140     if (!errno)
141     {
142         if ((l > INT_MAX) || (l < INT_MIN))
143             errno = ERANGE;
144         if (*end)
145             errno = EINVAL;
146     }
147     return (int)l;
148 }
149
150
151 /*****************************************************************************
152  * config_LoadConfigFile: loads the configuration file.
153  *****************************************************************************
154  * This function is called to load the config options stored in the config
155  * file.
156  *****************************************************************************/
157 int __config_LoadConfigFile( vlc_object_t *p_this, const char *psz_module_name )
158 {
159     FILE *file;
160
161     file = config_OpenConfigFile (p_this);
162     if (file == NULL)
163         return VLC_EGENERIC;
164
165     /* Look for the selected module, if NULL then save everything */
166     module_t **list = module_list_get (NULL);
167
168     /* Look for UTF-8 Byte Order Mark */
169     char * (*convert) (const char *) = strdupnull;
170     char bom[3];
171
172     if ((fread (bom, 1, 3, file) != 3)
173      || memcmp (bom, "\xEF\xBB\xBF", 3))
174     {
175         convert = FromLocaleDup;
176         rewind (file); /* no BOM, rewind */
177     }
178
179     module_t *module = NULL;
180     char line[1024], section[1022];
181     section[0] = '\0';
182
183     /* Ensure consistent number formatting... */
184     locale_t loc = newlocale (LC_NUMERIC_MASK, "C", NULL);
185     locale_t baseloc = uselocale (loc);
186
187     while (fgets (line, 1024, file) != NULL)
188     {
189         /* Ignore comments and empty lines */
190         switch (line[0])
191         {
192             case '#':
193             case '\n':
194             case '\0':
195                 continue;
196         }
197
198         if (line[0] == '[')
199         {
200             char *ptr = strchr (line, ']');
201             if (ptr == NULL)
202                 continue; /* syntax error; */
203             *ptr = '\0';
204
205             /* New section ( = a given module) */
206             strcpy (section, line + 1);
207             module = NULL;
208
209             if ((psz_module_name == NULL)
210              || (strcmp (psz_module_name, section) == 0))
211             {
212                 for (int i = 0; list[i]; i++)
213                 {
214                     module_t *m = list[i];
215
216                     if ((strcmp (section, m->psz_object_name) == 0)
217                      && (m->i_config_items > 0)) /* ignore config-less modules */
218                     {
219                         module = m;
220                         if (psz_module_name != NULL)
221                             msg_Dbg (p_this,
222                                      "loading config for module \"%s\"",
223                                      section);
224                         break;
225                     }
226                 }
227             }
228
229             continue;
230         }
231
232         if (module == NULL)
233             continue; /* no need to parse if there is no matching module */
234
235         char *ptr = strchr (line, '\n');
236         if (ptr != NULL)
237             *ptr = '\0';
238
239         /* look for option name */
240         const char *psz_option_name = line;
241
242         ptr = strchr (line, '=');
243         if (ptr == NULL)
244             continue; /* syntax error */
245
246         *ptr = '\0';
247         const char *psz_option_value = ptr + 1;
248
249         /* try to match this option with one of the module's options */
250         for (size_t i = 0; i < module->confsize; i++)
251         {
252             module_config_t *p_item = module->p_config + i;
253
254             if ((p_item->i_type & CONFIG_HINT)
255              || strcmp (p_item->psz_name, psz_option_name))
256                 continue;
257
258             /* We found it */
259             errno = 0;
260
261             vlc_mutex_lock( p_item->p_lock );
262             switch( p_item->i_type )
263             {
264                 case CONFIG_ITEM_BOOL:
265                 case CONFIG_ITEM_INTEGER:
266                 {
267                     long l = strtoi (psz_option_value);
268                     if (errno)
269                         msg_Warn (p_this, "Integer value (%s) for %s: %m",
270                                   psz_option_value, psz_option_name);
271                     else
272                         p_item->saved.i = p_item->value.i = (int)l;
273                     break;
274                 }
275
276                 case CONFIG_ITEM_FLOAT:
277                     if( !*psz_option_value )
278                         break;                    /* ignore empty option */
279                     p_item->value.f = (float)atof (psz_option_value);
280                     p_item->saved.f = p_item->value.f;
281                     break;
282
283                 case CONFIG_ITEM_KEY:
284                     if( !*psz_option_value )
285                         break;                    /* ignore empty option */
286                     p_item->value.i = ConfigStringToKey(psz_option_value);
287                     p_item->saved.i = p_item->value.i;
288                     break;
289
290                 default:
291                     /* free old string */
292                     free( (char*) p_item->value.psz );
293                     free( (char*) p_item->saved.psz );
294
295                     p_item->value.psz = convert (psz_option_value);
296                     p_item->saved.psz = strdupnull (p_item->value.psz);
297                     break;
298             }
299             vlc_mutex_unlock( p_item->p_lock );
300             break;
301         }
302     }
303
304     if (ferror (file))
305     {
306         msg_Err (p_this, "error reading configuration: %m");
307         clearerr (file);
308     }
309     fclose (file);
310
311     module_list_free (list);
312     if (loc != (locale_t)0)
313     {
314         uselocale (baseloc);
315         freelocale (loc);
316     }
317     return 0;
318 }
319
320 /*****************************************************************************
321  * config_CreateDir: Create configuration directory if it doesn't exist.
322  *****************************************************************************/
323 int config_CreateDir( vlc_object_t *p_this, const char *psz_dirname )
324 {
325     if( !psz_dirname || !*psz_dirname ) return -1;
326
327     if( utf8_mkdir( psz_dirname, 0700 ) == 0 )
328         return 0;
329
330     switch( errno )
331     {
332         case EEXIST:
333             return 0;
334
335         case ENOENT:
336         {
337             /* Let's try to create the parent directory */
338             char psz_parent[strlen( psz_dirname ) + 1], *psz_end;
339             strcpy( psz_parent, psz_dirname );
340
341             psz_end = strrchr( psz_parent, DIR_SEP_CHAR );
342             if( psz_end && psz_end != psz_parent )
343             {
344                 *psz_end = '\0';
345                 if( config_CreateDir( p_this, psz_parent ) == 0 )
346                 {
347                     if( !utf8_mkdir( psz_dirname, 0700 ) )
348                         return 0;
349                 }
350             }
351         }
352     }
353
354     msg_Err( p_this, "could not create %s: %m", psz_dirname );
355     return -1;
356 }
357
358 static int
359 config_Write (FILE *file, const char *desc, const char *type,
360               bool comment, const char *name, const char *fmt, ...)
361 {
362     va_list ap;
363     int ret;
364
365     if (desc == NULL)
366         desc = "?";
367
368     if (fprintf (file, "# %s (%s)\n%s%s=", desc, vlc_gettext (type),
369                  comment ? "#" : "", name) < 0)
370         return -1;
371
372     va_start (ap, fmt);
373     ret = vfprintf (file, fmt, ap);
374     va_end (ap);
375     if (ret < 0)
376         return -1;
377
378     if (fputs ("\n\n", file) == EOF)
379         return -1;
380     return 0;
381 }
382
383
384 static int config_PrepareDir (vlc_object_t *obj)
385 {
386     char *psz_configdir = config_GetUserDir (VLC_CONFIG_DIR);
387     if (psz_configdir == NULL)
388         return -1;
389
390     int ret = config_CreateDir (obj, psz_configdir);
391     free (psz_configdir);
392     return ret;
393 }
394
395 /*****************************************************************************
396  * config_SaveConfigFile: Save a module's config options.
397  *****************************************************************************
398  * This will save the specified module's config options to the config file.
399  * If psz_module_name is NULL then we save all the modules config options.
400  * It's no use to save the config options that kept their default values, so
401  * we'll try to be a bit clever here.
402  *
403  * When we save we mustn't delete the config options of the modules that
404  * haven't been loaded. So we cannot just create a new config file with the
405  * config structures we've got in memory.
406  * I don't really know how to deal with this nicely, so I will use a completly
407  * dumb method ;-)
408  * I will load the config file in memory, but skipping all the sections of the
409  * modules we want to save. Then I will create a brand new file, dump the file
410  * loaded in memory and then append the sections of the modules we want to
411  * save.
412  * Really stupid no ?
413  *****************************************************************************/
414 static int SaveConfigFile( vlc_object_t *p_this, const char *psz_module_name,
415                            bool b_autosave )
416 {
417     module_t *p_parser;
418     FILE *file = NULL;
419     char *permanent = NULL, *temporary = NULL;
420     char p_line[1024], *p_index2;
421     unsigned long i_sizebuf = 0;
422     char *p_bigbuffer = NULL, *p_index;
423     bool b_backup;
424     int i_index;
425
426     if( config_PrepareDir( p_this ) )
427     {
428         msg_Err( p_this, "no configuration directory" );
429         goto error;
430     }
431
432     file = config_OpenConfigFile( p_this );
433     if( file != NULL )
434     {
435         struct stat st;
436
437         /* Some users make vlcrc read-only to prevent changes.
438          * The atomic replacement scheme breaks this "feature",
439          * so we check for read-only by hand. */
440         if (fstat (fileno (file), &st)
441          || !(st.st_mode & S_IWUSR))
442         {
443             msg_Err (p_this, "configuration file is read-only");
444             goto error;
445         }
446         i_sizebuf = ( st.st_size < LONG_MAX ) ? st.st_size : 0;
447     }
448
449     p_bigbuffer = p_index = malloc( i_sizebuf+1 );
450     if( !p_bigbuffer )
451         goto error;
452     p_bigbuffer[0] = 0;
453
454     /* List all available modules */
455     module_t **list = module_list_get (NULL);
456
457     /* backup file into memory, we only need to backup the sections we won't
458      * save later on */
459     b_backup = false;
460     while( file && fgets( p_line, 1024, file ) )
461     {
462         if( (p_line[0] == '[') && (p_index2 = strchr(p_line,']')))
463         {
464
465             /* we found a section, check if we need to do a backup */
466             for( i_index = 0; (p_parser = list[i_index]) != NULL; i_index++ )
467             {
468                 if( ((p_index2 - &p_line[1])
469                        == (int)strlen(p_parser->psz_object_name) )
470                     && !memcmp( &p_line[1], p_parser->psz_object_name,
471                                 strlen(p_parser->psz_object_name) ) )
472                 {
473                     if( !psz_module_name )
474                         break;
475                     else if( !strcmp( psz_module_name,
476                                       p_parser->psz_object_name ) )
477                         break;
478                 }
479             }
480
481             if( list[i_index] == NULL )
482             {
483                 /* we don't have this section in our list so we need to back
484                  * it up */
485                 *p_index2 = 0;
486 #if 0
487                 msg_Dbg( p_this, "backing up config for unknown module \"%s\"",
488                                  &p_line[1] );
489 #endif
490                 *p_index2 = ']';
491
492                 b_backup = true;
493             }
494             else
495             {
496                 b_backup = false;
497             }
498         }
499
500         /* save line if requested and line is valid (doesn't begin with a
501          * space, tab, or eol) */
502         if( b_backup && (p_line[0] != '\n') && (p_line[0] != ' ')
503             && (p_line[0] != '\t') )
504         {
505             strcpy( p_index, p_line );
506             p_index += strlen( p_line );
507         }
508     }
509     if( file )
510         fclose( file );
511     file = NULL;
512
513     /*
514      * Save module config in file
515      */
516     permanent = config_GetConfigFile (p_this);
517     if (!permanent)
518     {
519         module_list_free (list);
520         goto error;
521     }
522
523     if (asprintf (&temporary, "%s.%u", permanent, getpid ()) == -1)
524     {
525         temporary = NULL;
526         module_list_free (list);
527         goto error;
528     }
529
530     /* The temporary configuration file is per-PID. Therefore SaveConfigFile()
531      * should be serialized against itself within a given process. */
532     static vlc_mutex_t lock = VLC_STATIC_MUTEX;
533     vlc_mutex_lock (&lock);
534
535     int fd = utf8_open (temporary, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR|S_IWUSR);
536     if (fd == -1)
537     {
538         vlc_mutex_unlock (&lock);
539         module_list_free (list);
540         goto error;
541     }
542     file = fdopen (fd, "wt");
543     if (file == NULL)
544     {
545         close (fd);
546         vlc_mutex_unlock (&lock);
547         module_list_free (list);
548         goto error;
549     }
550
551     fprintf( file, "\xEF\xBB\xBF###\n###  " COPYRIGHT_MESSAGE "\n###\n\n"
552        "###\n### lines beginning with a '#' character are comments\n###\n\n" );
553
554     /* Ensure consistent number formatting... */
555     locale_t loc = newlocale (LC_NUMERIC_MASK, "C", NULL);
556     locale_t baseloc = uselocale (loc);
557
558     /* Look for the selected module, if NULL then save everything */
559     for( i_index = 0; (p_parser = list[i_index]) != NULL; i_index++ )
560     {
561         module_config_t *p_item, *p_end;
562
563         if( psz_module_name && strcmp( psz_module_name,
564                                        p_parser->psz_object_name ) )
565             continue;
566
567         if( !p_parser->i_config_items )
568             continue;
569
570         if( psz_module_name )
571             msg_Dbg( p_this, "saving config for module \"%s\"",
572                      p_parser->psz_object_name );
573
574         fprintf( file, "[%s]", p_parser->psz_object_name );
575         if( p_parser->psz_longname )
576             fprintf( file, " # %s\n\n", p_parser->psz_longname );
577         else
578             fprintf( file, "\n\n" );
579
580         for( p_item = p_parser->p_config, p_end = p_item + p_parser->confsize;
581              p_item < p_end;
582              p_item++ )
583         {
584             if ((p_item->i_type & CONFIG_HINT) /* ignore hint */
585              || p_item->b_removed              /* ignore deprecated option */
586              || p_item->b_unsaveable)          /* ignore volatile option */
587                 continue;
588
589             vlc_mutex_lock (p_item->p_lock);
590
591             /* Do not save the new value in the configuration file
592              * if doing an autosave, and the item is not an "autosaved" one. */
593             bool b_retain = b_autosave && !p_item->b_autosave;
594
595             if (IsConfigIntegerType (p_item->i_type))
596             {
597                 int val = b_retain ? p_item->saved.i : p_item->value.i;
598                 if (p_item->i_type == CONFIG_ITEM_KEY)
599                 {
600                     char *psz_key = ConfigKeyToString (val);
601                     config_Write (file, p_item->psz_text, N_("key"),
602                                   val == p_item->orig.i,
603                                   p_item->psz_name, "%s",
604                                   psz_key ? psz_key : "");
605                     free (psz_key);
606                 }
607                 else
608                     config_Write (file, p_item->psz_text,
609                                   (p_item->i_type == CONFIG_ITEM_BOOL)
610                                       ? N_("boolean") : N_("integer"),
611                                   val == p_item->orig.i,
612                                   p_item->psz_name, "%d", val);
613                 p_item->saved.i = val;
614             }
615             else
616             if (IsConfigFloatType (p_item->i_type))
617             {
618                 float val = b_retain ? p_item->saved.f : p_item->value.f;
619                 config_Write (file, p_item->psz_text, N_("float"),
620                               val == p_item->orig.f,
621                               p_item->psz_name, "%f", val);
622                 p_item->saved.f = val;
623             }
624             else
625             {
626                 const char *psz_value = b_retain ? p_item->saved.psz
627                                                  : p_item->value.psz;
628                 bool modified;
629
630                 assert (IsConfigStringType (p_item->i_type));
631
632                 if (b_retain && (psz_value == NULL)) /* FIXME: hack */
633                     psz_value = p_item->orig.psz;
634
635                 modified =
636                     (psz_value != NULL)
637                         ? ((p_item->orig.psz != NULL)
638                             ? (strcmp (psz_value, p_item->orig.psz) != 0)
639                             : true)
640                         : (p_item->orig.psz != NULL);
641
642                 config_Write (file, p_item->psz_text, N_("string"),
643                               !modified, p_item->psz_name, "%s",
644                               psz_value ? psz_value : "");
645
646                 if ( !b_retain )
647                 {
648
649                     free ((char *)p_item->saved.psz);
650                     if( (psz_value && p_item->orig.psz &&
651                          strcmp( psz_value, p_item->orig.psz )) ||
652                         !psz_value || !p_item->orig.psz)
653                         p_item->saved.psz = strdupnull (psz_value);
654                     else
655                         p_item->saved.psz = NULL;
656                 }
657             }
658
659             if (!b_retain)
660                 p_item->b_dirty = false;
661             vlc_mutex_unlock (p_item->p_lock);
662         }
663     }
664
665     module_list_free (list);
666     if (loc != (locale_t)0)
667     {
668         uselocale (baseloc);
669         freelocale (loc);
670     }
671
672     /*
673      * Restore old settings from the config in file
674      */
675     fputs( p_bigbuffer, file );
676     free( p_bigbuffer );
677
678     /*
679      * Flush to disk and replace atomically
680      */
681     fflush (file); /* Flush from run-time */
682 #ifndef WIN32
683     fdatasync (fd); /* Flush from OS */
684     /* Atomically replace the file... */
685     if (utf8_rename (temporary, permanent))
686         utf8_unlink (temporary);
687     /* (...then synchronize the directory, err, TODO...) */
688     /* ...and finally close the file */
689     vlc_mutex_unlock (&lock);
690 #endif
691     fclose (file);
692 #ifdef WIN32
693     /* Windows cannot remove open files nor overwrite existing ones */
694     utf8_unlink (permanent);
695     if (utf8_rename (temporary, permanent))
696         utf8_unlink (temporary);
697     vlc_mutex_unlock (&lock);
698 #endif
699
700     free (temporary);
701     free (permanent);
702     return 0;
703
704 error:
705     if( file )
706         fclose( file );
707     free (temporary);
708     free (permanent);
709     free( p_bigbuffer );
710     return -1;
711 }
712
713 int config_AutoSaveConfigFile( vlc_object_t *p_this )
714 {
715     size_t i_index;
716     bool save = false;
717
718     assert( p_this );
719
720     /* Check if there's anything to save */
721     module_t **list = module_list_get (NULL);
722     for( i_index = 0; list[i_index] && !save; i_index++ )
723     {
724         module_t *p_parser = list[i_index];
725         module_config_t *p_item, *p_end;
726
727         if( !p_parser->i_config_items ) continue;
728
729         for( p_item = p_parser->p_config, p_end = p_item + p_parser->confsize;
730              p_item < p_end && !save;
731              p_item++ )
732         {
733             vlc_mutex_lock (p_item->p_lock);
734             save = p_item->b_autosave && p_item->b_dirty;
735             vlc_mutex_unlock (p_item->p_lock);
736         }
737     }
738     module_list_free (list);
739
740     return save ? VLC_SUCCESS : SaveConfigFile( p_this, NULL, true );
741 }
742
743 int __config_SaveConfigFile( vlc_object_t *p_this, const char *psz_module_name )
744 {
745     return SaveConfigFile( p_this, psz_module_name, false );
746 }