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