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