]> git.sesse.net Git - vlc/blobdiff - src/config/file.c
SaveConfigFile: correctly an error on I/O failure
[vlc] / src / config / file.c
index 10a483e1f08a36df72e5a2c1990215f9e0f164a6..0d3f6b6afb06553ca3bea140be53db47127c36f3 100644 (file)
 #include <sys/stat.h>
 #ifdef __APPLE__
 #   include <xlocale.h>
-#else
+#elif defined(HAVE_USELOCALE)
 #include <locale.h>
 #endif
 
 #include <vlc_common.h>
 #include "../libvlc.h"
-#include "vlc_charset.h"
+#include <vlc_charset.h>
+#include <vlc_fs.h>
 #include "vlc_keys.h"
 
 #include "configuration.h"
 #include "modules/modules.h"
 
-static char *ConfigKeyToString( int );
-
 static inline char *strdupnull (const char *src)
 {
     return src ? strdup (src) : NULL;
@@ -56,10 +55,11 @@ static inline char *strdupnull (const char *src)
  */
 static char *config_GetConfigFile( vlc_object_t *obj )
 {
-    char *psz_file = config_GetPsz( obj, "config" );
+    char *psz_file = var_CreateGetNonEmptyString( obj, "config" );
+    var_Destroy( obj, "config" );
     if( psz_file == NULL )
     {
-        char *psz_dir = config_GetUserConfDir();
+        char *psz_dir = config_GetUserDir( VLC_CONFIG_DIR );
 
         if( asprintf( &psz_file, "%s" DIR_SEP CONFIG_FILE, psz_dir ) == -1 )
             psz_file = NULL;
@@ -76,7 +76,7 @@ static FILE *config_OpenConfigFile( vlc_object_t *p_obj )
 
     msg_Dbg( p_obj, "opening config file (%s)", psz_filename );
 
-    FILE *p_stream = utf8_fopen( psz_filename, "rt" );
+    FILE *p_stream = vlc_fopen( psz_filename, "rt" );
     if( p_stream == NULL && errno != ENOENT )
     {
         msg_Err( p_obj, "cannot open config file (%s): %m",
@@ -88,11 +88,14 @@ static FILE *config_OpenConfigFile( vlc_object_t *p_obj )
     {
         /* This is the fallback for pre XDG Base Directory
          * Specification configs */
+        char *home = config_GetUserDir(VLC_HOME_DIR);
         char *psz_old;
-        if( asprintf( &psz_old, "%s" DIR_SEP CONFIG_DIR DIR_SEP CONFIG_FILE,
-                      config_GetHomeDir() ) != -1 )
+
+        if( home != NULL
+         && asprintf( &psz_old, "%s/.vlc/" CONFIG_FILE,
+                      home ) != -1 )
         {
-            p_stream = utf8_fopen( psz_old, "rt" );
+            p_stream = vlc_fopen( psz_old, "rt" );
             if( p_stream )
             {
                 /* Old config file found. We want to write it at the
@@ -100,10 +103,10 @@ static FILE *config_OpenConfigFile( vlc_object_t *p_obj )
                 msg_Info( p_obj->p_libvlc, "Found old config file at %s. "
                           "VLC will now use %s.", psz_old, psz_filename );
                 char *psz_readme;
-                if( asprintf(&psz_readme,"%s"DIR_SEP CONFIG_DIR DIR_SEP"README",
-                              config_GetHomeDir() ) != -1 )
+                if( asprintf(&psz_readme,"%s/.vlc/README",
+                             home ) != -1 )
                 {
-                    FILE *p_readme = utf8_fopen( psz_readme, "wt" );
+                    FILE *p_readme = vlc_fopen( psz_readme, "wt" );
                     if( p_readme )
                     {
                         fprintf( p_readme, "The VLC media player "
@@ -117,9 +120,14 @@ static FILE *config_OpenConfigFile( vlc_object_t *p_obj )
                     }
                     free( psz_readme );
                 }
+                /* Remove the old configuration file so that --reset-config
+                 * can work properly. Fortunately, Linux allows removing
+                 * open files - with most filesystems. */
+                unlink( psz_old );
             }
             free( psz_old );
         }
+        free( home );
     }
 #endif
     free( psz_filename );
@@ -127,32 +135,35 @@ static FILE *config_OpenConfigFile( vlc_object_t *p_obj )
 }
 
 
-static int strtoi (const char *str)
+static int64_t strtoi (const char *str)
 {
     char *end;
-    long l;
+    long long l;
 
     errno = 0;
-    l = strtol (str, &end, 0);
+    l = strtoll (str, &end, 0);
 
     if (!errno)
     {
-        if ((l > INT_MAX) || (l < INT_MIN))
+#if (LLONG_MAX > 0x7fffffffffffffffLL)
+        if (l > 0x7fffffffffffffffLL
+         || l < -0x8000000000000000LL)
             errno = ERANGE;
+#endif
         if (*end)
             errno = EINVAL;
     }
-    return (int)l;
+    return l;
 }
 
-
+#undef config_LoadConfigFile
 /*****************************************************************************
  * config_LoadConfigFile: loads the configuration file.
  *****************************************************************************
  * This function is called to load the config options stored in the config
  * file.
  *****************************************************************************/
-int __config_LoadConfigFile( vlc_object_t *p_this, const char *psz_module_name )
+int config_LoadConfigFile( vlc_object_t *p_this, const char *psz_module_name )
 {
     FILE *file;
 
@@ -182,6 +193,7 @@ int __config_LoadConfigFile( vlc_object_t *p_this, const char *psz_module_name )
     locale_t loc = newlocale (LC_NUMERIC_MASK, "C", NULL);
     locale_t baseloc = uselocale (loc);
 
+    vlc_rwlock_wrlock (&config_lock);
     while (fgets (line, 1024, file) != NULL)
     {
         /* Ignore comments and empty lines */
@@ -256,18 +268,19 @@ int __config_LoadConfigFile( vlc_object_t *p_this, const char *psz_module_name )
             /* We found it */
             errno = 0;
 
-            vlc_mutex_lock( p_item->p_lock );
             switch( p_item->i_type )
             {
                 case CONFIG_ITEM_BOOL:
                 case CONFIG_ITEM_INTEGER:
                 {
-                    long l = strtoi (psz_option_value);
+                    int64_t l = strtoi (psz_option_value);
+                    if ((l > p_item->max.i) || (l < p_item->min.i))
+                        errno = ERANGE;
                     if (errno)
                         msg_Warn (p_this, "Integer value (%s) for %s: %m",
                                   psz_option_value, psz_option_name);
                     else
-                        p_item->saved.i = p_item->value.i = (int)l;
+                        p_item->saved.i = p_item->value.i = l;
                     break;
                 }
 
@@ -294,10 +307,10 @@ int __config_LoadConfigFile( vlc_object_t *p_this, const char *psz_module_name )
                     p_item->saved.psz = strdupnull (p_item->value.psz);
                     break;
             }
-            vlc_mutex_unlock( p_item->p_lock );
             break;
         }
     }
+    vlc_rwlock_unlock (&config_lock);
 
     if (ferror (file))
     {
@@ -322,7 +335,7 @@ int config_CreateDir( vlc_object_t *p_this, const char *psz_dirname )
 {
     if( !psz_dirname || !*psz_dirname ) return -1;
 
-    if( utf8_mkdir( psz_dirname, 0700 ) == 0 )
+    if( vlc_mkdir( psz_dirname, 0700 ) == 0 )
         return 0;
 
     switch( errno )
@@ -342,19 +355,19 @@ int config_CreateDir( vlc_object_t *p_this, const char *psz_dirname )
                 *psz_end = '\0';
                 if( config_CreateDir( p_this, psz_parent ) == 0 )
                 {
-                    if( !utf8_mkdir( psz_dirname, 0700 ) )
+                    if( !vlc_mkdir( psz_dirname, 0700 ) )
                         return 0;
                 }
             }
         }
     }
 
-    msg_Err( p_this, "could not create %s: %m", psz_dirname );
+    msg_Warn( p_this, "could not create %s: %m", psz_dirname );
     return -1;
 }
 
 static int
-config_Write (FILE *file, const char *type, const char *desc,
+config_Write (FILE *file, const char *desc, const char *type,
               bool comment, const char *name, const char *fmt, ...)
 {
     va_list ap;
@@ -381,8 +394,8 @@ config_Write (FILE *file, const char *type, const char *desc,
 
 static int config_PrepareDir (vlc_object_t *obj)
 {
-    char *psz_configdir = config_GetUserConfDir ();
-    if (psz_configdir == NULL) /* XXX: This should never happen */
+    char *psz_configdir = config_GetUserDir (VLC_CONFIG_DIR);
+    if (psz_configdir == NULL)
         return -1;
 
     int ret = config_CreateDir (obj, psz_configdir);
@@ -424,18 +437,24 @@ static int SaveConfigFile( vlc_object_t *p_this, const char *psz_module_name,
     if( config_PrepareDir( p_this ) )
     {
         msg_Err( p_this, "no configuration directory" );
-        goto error;
+        return -1;
     }
 
     file = config_OpenConfigFile( p_this );
     if( file != NULL )
     {
-        /* look for file size */
-        fseek( file, 0L, SEEK_END );
-        i_sizebuf = ftell( file );
-        fseek( file, 0L, SEEK_SET );
-        if( i_sizebuf >= LONG_MAX )
-            i_sizebuf = 0;
+        struct stat st;
+
+        /* Some users make vlcrc read-only to prevent changes.
+         * The atomic replacement scheme breaks this "feature",
+         * so we check for read-only by hand. */
+        if (fstat (fileno (file), &st)
+         || !(st.st_mode & S_IWUSR))
+        {
+            msg_Err (p_this, "configuration file is read-only");
+            goto error;
+        }
+        i_sizebuf = ( st.st_size < LONG_MAX ) ? st.st_size : 0;
     }
 
     p_bigbuffer = p_index = malloc( i_sizebuf+1 );
@@ -512,27 +531,25 @@ static int SaveConfigFile( vlc_object_t *p_this, const char *psz_module_name,
         goto error;
     }
 
-    if (asprintf (&temporary, "%s.%u", permanent,
-#ifdef UNDER_CE
-                  GetCurrentProcessId ()
-#else
-                  getpid ()
-#endif
-                 ) == -1)
+    if (asprintf (&temporary, "%s.%u", permanent, getpid ()) == -1)
     {
         temporary = NULL;
         module_list_free (list);
         goto error;
     }
 
+    /* Configuration lock must be taken before vlcrc serializer below. */
+    vlc_rwlock_rdlock (&config_lock);
+
     /* The temporary configuration file is per-PID. Therefore SaveConfigFile()
      * should be serialized against itself within a given process. */
     static vlc_mutex_t lock = VLC_STATIC_MUTEX;
     vlc_mutex_lock (&lock);
 
-    int fd = utf8_open (temporary, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR|S_IWUSR);
+    int fd = vlc_open (temporary, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR|S_IWUSR);
     if (fd == -1)
     {
+        vlc_rwlock_unlock (&config_lock);
         vlc_mutex_unlock (&lock);
         module_list_free (list);
         goto error;
@@ -540,19 +557,32 @@ static int SaveConfigFile( vlc_object_t *p_this, const char *psz_module_name,
     file = fdopen (fd, "wt");
     if (file == NULL)
     {
+        msg_Err (p_this, "cannot create configuration file: %m");
+        vlc_rwlock_unlock (&config_lock);
         close (fd);
         vlc_mutex_unlock (&lock);
         module_list_free (list);
         goto error;
     }
 
-    fprintf( file, "\xEF\xBB\xBF###\n###  " COPYRIGHT_MESSAGE "\n###\n\n"
-       "###\n### lines beginning with a '#' character are comments\n###\n\n" );
+    fprintf( file,
+        "\xEF\xBB\xBF###\n"
+        "###  "PACKAGE_NAME" "PACKAGE_VERSION"\n"
+        "###\n"
+        "\n"
+        "###\n"
+        "### lines beginning with a '#' character are comments\n"
+        "###\n"
+        "\n" );
 
     /* Ensure consistent number formatting... */
     locale_t loc = newlocale (LC_NUMERIC_MASK, "C", NULL);
     locale_t baseloc = uselocale (loc);
 
+    /* We would take the config lock here. But this would cause a lock
+     * inversion with the serializer above and config_AutoSaveConfigFile().
+    vlc_rwlock_rdlock (&config_lock);*/
+
     /* Look for the selected module, if NULL then save everything */
     for( i_index = 0; (p_parser = list[i_index]) != NULL; i_index++ )
     {
@@ -584,15 +614,13 @@ static int SaveConfigFile( vlc_object_t *p_this, const char *psz_module_name,
              || p_item->b_unsaveable)          /* ignore volatile option */
                 continue;
 
-            vlc_mutex_lock (p_item->p_lock);
-
             /* Do not save the new value in the configuration file
              * if doing an autosave, and the item is not an "autosaved" one. */
             bool b_retain = b_autosave && !p_item->b_autosave;
 
             if (IsConfigIntegerType (p_item->i_type))
             {
-                int val = b_retain ? p_item->saved.i : p_item->value.i;
+                int64_t val = b_retain ? p_item->saved.i : p_item->value.i;
                 if (p_item->i_type == CONFIG_ITEM_KEY)
                 {
                     char *psz_key = ConfigKeyToString (val);
@@ -607,7 +635,7 @@ static int SaveConfigFile( vlc_object_t *p_this, const char *psz_module_name,
                                   (p_item->i_type == CONFIG_ITEM_BOOL)
                                       ? N_("boolean") : N_("integer"),
                                   val == p_item->orig.i,
-                                  p_item->psz_name, "%d", val);
+                                  p_item->psz_name, "%"PRId64, val);
                 p_item->saved.i = val;
             }
             else
@@ -656,9 +684,9 @@ static int SaveConfigFile( vlc_object_t *p_this, const char *psz_module_name,
 
             if (!b_retain)
                 p_item->b_dirty = false;
-            vlc_mutex_unlock (p_item->p_lock);
         }
     }
+    vlc_rwlock_unlock (&config_lock);
 
     module_list_free (list);
     if (loc != (locale_t)0)
@@ -677,11 +705,23 @@ static int SaveConfigFile( vlc_object_t *p_this, const char *psz_module_name,
      * Flush to disk and replace atomically
      */
     fflush (file); /* Flush from run-time */
+    if (ferror (file))
+    {
+        vlc_unlink (temporary);
+        vlc_mutex_unlock (&lock);
+        msg_Err (p_this, "cannot write configuration file");
+        clearerr (file);
+        goto error;
+    }
 #ifndef WIN32
+#ifdef __APPLE__
+    fsync (fd); /* Flush from OS */
+#else
     fdatasync (fd); /* Flush from OS */
+#endif
     /* Atomically replace the file... */
-    if (utf8_rename (temporary, permanent))
-        utf8_unlink (temporary);
+    if (vlc_rename (temporary, permanent))
+        vlc_unlink (temporary);
     /* (...then synchronize the directory, err, TODO...) */
     /* ...and finally close the file */
     vlc_mutex_unlock (&lock);
@@ -689,9 +729,9 @@ static int SaveConfigFile( vlc_object_t *p_this, const char *psz_module_name,
     fclose (file);
 #ifdef WIN32
     /* Windows cannot remove open files nor overwrite existing ones */
-    utf8_unlink (permanent);
-    if (utf8_rename (temporary, permanent))
-        utf8_unlink (temporary);
+    vlc_unlink (permanent);
+    if (vlc_rename (temporary, permanent))
+        vlc_unlink (temporary);
     vlc_mutex_unlock (&lock);
 #endif
 
@@ -710,14 +750,15 @@ error:
 
 int config_AutoSaveConfigFile( vlc_object_t *p_this )
 {
-    size_t i_index;
+    int ret = VLC_SUCCESS;
     bool save = false;
 
     assert( p_this );
 
     /* Check if there's anything to save */
     module_t **list = module_list_get (NULL);
-    for( i_index = 0; list[i_index] && !save; i_index++ )
+    vlc_rwlock_rdlock (&config_lock);
+    for (size_t i_index = 0; list[i_index] && !save; i_index++)
     {
         module_t *p_parser = list[i_index];
         module_config_t *p_item, *p_end;
@@ -728,83 +769,21 @@ int config_AutoSaveConfigFile( vlc_object_t *p_this )
              p_item < p_end && !save;
              p_item++ )
         {
-            vlc_mutex_lock (p_item->p_lock);
             save = p_item->b_autosave && p_item->b_dirty;
-            vlc_mutex_unlock (p_item->p_lock);
         }
     }
-    module_list_free (list);
-
-    return save ? VLC_SUCCESS : SaveConfigFile( p_this, NULL, true );
-}
 
-int __config_SaveConfigFile( vlc_object_t *p_this, const char *psz_module_name )
-{
-    return SaveConfigFile( p_this, psz_module_name, false );
-}
+    if (save)
+        /* Note: this will get the read lock recursively. Ok. */
+        ret = SaveConfigFile (p_this, NULL, true);
+    vlc_rwlock_unlock (&config_lock);
 
-int ConfigStringToKey( const char *psz_key )
-{
-    int i_key = 0;
-    size_t i;
-    const char *psz_parser = strchr( psz_key, '-' );
-    while( psz_parser && psz_parser != psz_key )
-    {
-        for( i = 0; i < vlc_num_modifiers; ++i )
-        {
-            if( !strncasecmp( vlc_modifiers[i].psz_key_string, psz_key,
-                              strlen( vlc_modifiers[i].psz_key_string ) ) )
-            {
-                i_key |= vlc_modifiers[i].i_key_code;
-            }
-        }
-        psz_key = psz_parser + 1;
-        psz_parser = strchr( psz_key, '-' );
-    }
-    for( i = 0; i < vlc_num_keys; ++i )
-    {
-        if( !strcasecmp( vlc_keys[i].psz_key_string, psz_key ) )
-        {
-            i_key |= vlc_keys[i].i_key_code;
-            break;
-        }
-    }
-    return i_key;
+    module_list_free (list);
+    return ret;
 }
 
-char *ConfigKeyToString( int i_key )
+#undef config_SaveConfigFile
+int config_SaveConfigFile( vlc_object_t *p_this, const char *psz_module_name )
 {
-    // Worst case appears to be 45 characters:
-    // "Command-Meta-Ctrl-Shift-Alt-Browser Favorites"
-    enum { keylen=64 };
-    char *psz_key = malloc( keylen );
-    char *p;
-    size_t index;
-
-    if ( !psz_key )
-    {
-        return NULL;
-    }
-    *psz_key = '\0';
-    p = psz_key;
-
-    for( index = 0; index < vlc_num_modifiers; ++index )
-    {
-        if( i_key & vlc_modifiers[index].i_key_code )
-        {
-            p += snprintf( p, keylen-(psz_key-p), "%s-",
-                           vlc_modifiers[index].psz_key_string );
-        }
-    }
-    for( index = 0; index < vlc_num_keys; ++index )
-    {
-        if( (int)( i_key & ~KEY_MODIFIER ) == vlc_keys[index].i_key_code )
-        {
-            p += snprintf( p, keylen-(psz_key-p), "%s",
-                           vlc_keys[index].psz_key_string );
-            break;
-        }
-    }
-    return psz_key;
+    return SaveConfigFile( p_this, psz_module_name, false );
 }
-