Fix for cache startup crash.
authorSteve McIntyre <steve@einval.com>
Sun, 7 Aug 2011 21:15:14 +0000 (22:15 +0100)
committerSteve McIntyre <steve@einval.com>
Sun, 7 Aug 2011 21:15:14 +0000 (22:15 +0100)
When syncing up the cache and DB at startup, be extra careful about
data safety. We haven't gone into normal code yet and the database
state might be interesting!

If we delete/modify an entry from the cache db, restart the entire
loop.

C/fmdb.c

index a4f0244..4bef898 100644 (file)
--- a/C/fmdb.c
+++ b/C/fmdb.c
@@ -757,6 +757,7 @@ int sync_cache_and_db(FMDB *dbp, char *cache_dir)
 #endif
     int startup_db_entries = 0;
     int removed_db_entries = 0;
+    int done = 0;
 
     /* First, check that all the entries in the DB exist on disk; any
      * that don't, or that look wrong: delete the DB entry */
@@ -769,74 +770,99 @@ int sync_cache_and_db(FMDB *dbp, char *cache_dir)
 #endif
     free_results();
 
-    sprintf(sql_command, "SELECT * FROM cache;");
-    error = sqlite3_exec(state->db, sql_command, results_callback, &result_type, &open_error);
-    if (error)
-    {
-        DBLOG0((logfile, "%s: Failed to lookup results, error %d (%s)\n",
-                __func__, error, open_error));
-        if (open_error)
-            sqlite3_free(open_error);
-        return error;
-    }
-
-    res_current = res_head;
-    while (res_current)
+    while (!done)
     {
-        db_cache_entry_t *entry = &res_current->data.cache;
-        char cache_file_name[PATH_MAX];
-
-        startup_db_entries++;
-
-        /* Remove entries that don't look complete */
-        if (entry->flac_path == NULL ||
-            entry->cache_path == NULL || 
-            entry->state == FMDB_CACHE_ENCODING ||
-            entry->state == FMDB_CACHE_DELETING)
+        int entry_changed = 0;
+        
+        sprintf(sql_command, "SELECT * FROM cache;");
+        error = sqlite3_exec(state->db, sql_command, results_callback, &result_type, &open_error);
+        if (error)
         {
-            db_remove_cache_entry(dbp, entry);
-            removed_db_entries++;
+            DBLOG0((logfile, "%s: Failed to lookup results, error %d (%s)\n",
+                    __func__, error, open_error));
+            if (open_error)
+                sqlite3_free(open_error);
+            return error;
         }
-        else
+
+        res_current = res_head;
+        /* Quick check - if we have no results (i.e. no cache entries,
+         * then break out */
+        if (!res_current)
+            done = 1;
+
+        while (res_current)
         {
-        
-            /* Fix up any entries that might be currently marked as in use */
-            if (entry->state == FMDB_CACHE_READING)
-            {
-                entry->state = FMDB_CACHE_FREE;
-                error = db_store_cache_entry(dbp, entry);
-                if (error)
-                {
-                    DBLOG0((logfile, "%s: can't update cache DB entry for %s; error %d\n",
-                            __func__, entry->cache_path, error));
-                    return error;
-                }
-            }
-        
-            /* Now look for entries that don't have a matching (and
-             * correct) cache file on disk */
-            snprintf(cache_file_name, PATH_MAX, "%s/%s", cache_dir, entry->cache_path);
-            error = stat(cache_file_name, &sb);
-            if (error)
+            db_cache_entry_t *entry = &res_current->data.cache;
+            char cache_file_name[PATH_MAX];
+            
+            startup_db_entries++;
+            
+            /* Remove entries that don't look complete */
+            if (entry->flac_path == NULL ||
+                entry->cache_path == NULL || 
+                entry->state == FMDB_CACHE_ENCODING ||
+                entry->state == FMDB_CACHE_DELETING)
             {
-                DBLOG1((logfile, "%s: can't stat cache file %s, error %d. Deleting DB entry\n",
-                        __func__, cache_file_name, errno));
                 db_remove_cache_entry(dbp, entry);
                 removed_db_entries++;
-            }
+                entry_changed++;            }
             else
             {
-                if (sb.st_size != entry->size)
+                
+                /* Fix up any entries that might be currently marked as in use */
+                if (entry->state == FMDB_CACHE_READING)
                 {
-                    DBLOG1((logfile, "%s: cache file %s is wrong size (%lld bytes, DB said %lld). Deleting DB entry\n",
-                            __func__, cache_file_name, (long long)sb.st_size, entry->size));
+                    entry->state = FMDB_CACHE_FREE;
+                    error = db_store_cache_entry(dbp, entry);
+                    if (error)
+                    {
+                        DBLOG0((logfile, "%s: can't update cache DB entry for %s; error %d\n",
+                                __func__, entry->cache_path, error));
+                        return error;
+                    }
+                    entry_changed++;
+                }
+        
+                /* Now look for entries that don't have a matching
+                 * (and correct) cache file on disk */
+                snprintf(cache_file_name, PATH_MAX, "%s/%s", cache_dir, entry->cache_path);
+                error = stat(cache_file_name, &sb);
+                if (error)
+                {
+                    DBLOG1((logfile, "%s: can't stat cache file %s, error %d. Deleting DB entry\n",
+                            __func__, cache_file_name, errno));
                     db_remove_cache_entry(dbp, entry);
                     removed_db_entries++;
+                    entry_changed++;
                 }
+                else
+                {
+                    if (sb.st_size != entry->size)
+                    {
+                        DBLOG1((logfile, "%s: cache file %s is wrong size (%lld bytes, DB said %lld). Deleting DB entry\n",
+                            __func__, cache_file_name, (long long)sb.st_size, entry->size));
+                        db_remove_cache_entry(dbp, entry);
+                        removed_db_entries++;
+                        entry_changed++;
+                    }
+                }
+            }
+            if (!entry_changed)
+            {
+                /* This DB entry looks OK, then. Next! */
+                res_current = res_current->next;
+                /* And if we've reached the end of the list without
+                 * changes, we're finished. */
+                if (!res_current)
+                    done = 1;
+            }
+            else
+            {
+                DBLOG0((logfile, "%s: modified a cache DB entry, restart loop\n", __func__));
+                break;
             }
         }
-        /* This DB entry looks OK, then. Next! */
-        res_current = res_current->next;
     }
     free_results();
     DBLOG1((logfile, "%s: statistics:\n", __func__));