Bug 1133763 - Part 3: Wipe out the cache directory when detecting a change in the DB schema; r=bkelly
authorEhsan Akhgari <ehsan@mozilla.com>
Wed, 04 Mar 2015 17:05:37 -0500
changeset 249780 1951eef47610d7737cd6fdd534aafd109eb41cb5
parent 249779 a1cce2cc24bea9c102197378c7c3df754fdeb45a
child 249781 7b88e237f24ced792974ba82adc2f7b8c6727f88
push idunknown
push userunknown
push dateunknown
reviewersbkelly
bugs1133763
milestone39.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1133763 - Part 3: Wipe out the cache directory when detecting a change in the DB schema; r=bkelly
dom/cache/DBAction.cpp
dom/cache/DBAction.h
dom/cache/DBSchema.cpp
dom/cache/DBSchema.h
dom/cache/FileUtils.cpp
dom/cache/FileUtils.h
--- a/dom/cache/DBAction.cpp
+++ b/dom/cache/DBAction.cpp
@@ -9,16 +9,18 @@
 #include "mozilla/dom/quota/PersistenceType.h"
 #include "mozilla/net/nsFileProtocolHandler.h"
 #include "mozIStorageConnection.h"
 #include "mozIStorageService.h"
 #include "mozStorageCID.h"
 #include "nsIFile.h"
 #include "nsIURI.h"
 #include "nsNetUtil.h"
+#include "DBSchema.h"
+#include "FileUtils.h"
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
 using mozilla::dom::quota::PersistenceType;
 
@@ -70,16 +72,18 @@ DBAction::RunOnTarget(Resolver* aResolve
 nsresult
 DBAction::OpenConnection(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
                          mozIStorageConnection** aConnOut)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aDBDir);
   MOZ_ASSERT(aConnOut);
 
+  nsCOMPtr<mozIStorageConnection> conn;
+
   bool exists;
   nsresult rv = aDBDir->Exists(&exists);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   if (!exists) {
     if (NS_WARN_IF(mMode != Create)) {  return NS_ERROR_FILE_NOT_FOUND; }
     rv = aDBDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
@@ -118,31 +122,58 @@ DBAction::OpenConnection(const QuotaInfo
     NS_LITERAL_CSTRING("&group=") + aQuotaInfo.mGroup +
     NS_LITERAL_CSTRING("&origin=") + aQuotaInfo.mOrigin);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   nsCOMPtr<mozIStorageService> ss =
     do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
   if (NS_WARN_IF(!ss)) { return NS_ERROR_UNEXPECTED; }
 
-  rv = ss->OpenDatabaseWithFileURL(dbFileUrl, aConnOut);
+  rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(conn));
   if (rv == NS_ERROR_FILE_CORRUPTED) {
     NS_WARNING("Cache database corrupted. Recreating empty database.");
 
+    conn = nullptr;
+
     // There is nothing else we can do to recover.  Also, this data can
     // be deleted by QuotaManager at any time anyways.
-    rv = dbFile->Remove(false);
+    rv = WipeDatabase(dbFile, aDBDir);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(conn));
+  }
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  // Check the schema to make sure it is not too old.
+  int32_t schemaVersion = 0;
+  rv = conn->GetSchemaVersion(&schemaVersion);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  if (schemaVersion > 0 && schemaVersion < DBSchema::kMaxWipeSchemaVersion) {
+    conn = nullptr;
+    rv = WipeDatabase(dbFile, aDBDir);
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-    // TODO: clean up any orphaned body files (bug 1110446)
+    rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(conn));
+  }
+
+  conn.forget(aConnOut);
+
+  return rv;
+}
 
-    rv = ss->OpenDatabaseWithFileURL(dbFileUrl, aConnOut);
-  }
+nsresult
+DBAction::WipeDatabase(nsIFile* aDBFile, nsIFile* aDBDir)
+{
+  nsresult rv = aDBFile->Remove(false);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
-  MOZ_ASSERT(*aConnOut);
+
+  // Delete the morgue as well.
+  rv = FileUtils::BodyDeleteDir(aDBDir);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
   return rv;
 }
 
 SyncDBAction::SyncDBAction(Mode aMode)
   : DBAction(aMode)
 {
 }
 
--- a/dom/cache/DBAction.h
+++ b/dom/cache/DBAction.h
@@ -44,16 +44,18 @@ protected:
 
 private:
   virtual void
   RunOnTarget(Resolver* aResolver, const QuotaInfo& aQuotaInfo) MOZ_OVERRIDE;
 
   nsresult OpenConnection(const QuotaInfo& aQuotaInfo, nsIFile* aQuotaDir,
                           mozIStorageConnection** aConnOut);
 
+  nsresult WipeDatabase(nsIFile* aDBFile, nsIFile* aDBDir);
+
   const Mode mMode;
 };
 
 class SyncDBAction : public DBAction
 {
 protected:
   explicit SyncDBAction(Mode aMode);
 
--- a/dom/cache/DBSchema.cpp
+++ b/dom/cache/DBSchema.cpp
@@ -15,16 +15,17 @@
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 
+const int32_t DBSchema::kMaxWipeSchemaVersion = 1;
 const int32_t DBSchema::kLatestSchemaVersion = 1;
 const int32_t DBSchema::kMaxEntriesPerStatement = 255;
 
 using mozilla::void_t;
 
 // static
 nsresult
 DBSchema::CreateSchema(mozIStorageConnection* aConn)
--- a/dom/cache/DBSchema.h
+++ b/dom/cache/DBSchema.h
@@ -83,16 +83,19 @@ public:
                                   CacheId aCacheId);
   static nsresult StorageForgetCache(mozIStorageConnection* aConn,
                                      Namespace aNamespace,
                                      const nsAString& aKey);
   static nsresult StorageGetKeys(mozIStorageConnection* aConn,
                                  Namespace aNamespace,
                                  nsTArray<nsString>& aKeysOut);
 
+  // We will wipe out databases with a schema versions less than this.
+  static const int32_t kMaxWipeSchemaVersion;
+
 private:
   typedef int32_t EntryId;
 
   static nsresult QueryAll(mozIStorageConnection* aConn, CacheId aCacheId,
                            nsTArray<EntryId>& aEntryIdListOut);
   static nsresult QueryCache(mozIStorageConnection* aConn, CacheId aCacheId,
                              const PCacheRequest& aRequest,
                              const PCacheQueryParams& aParams,
--- a/dom/cache/FileUtils.cpp
+++ b/dom/cache/FileUtils.cpp
@@ -44,16 +44,39 @@ FileUtils::BodyCreateDir(nsIFile* aBaseD
   }
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   return rv;
 }
 
 // static
 nsresult
+FileUtils::BodyDeleteDir(nsIFile* aBaseDir)
+{
+  MOZ_ASSERT(aBaseDir);
+
+  nsCOMPtr<nsIFile> aBodyDir;
+  nsresult rv = aBaseDir->Clone(getter_AddRefs(aBodyDir));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = aBodyDir->Append(NS_LITERAL_STRING("morgue"));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = aBodyDir->Remove(/* recursive = */ true);
+  if (rv == NS_ERROR_FILE_NOT_FOUND ||
+      rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
+    rv = NS_OK;
+  }
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  return rv;
+}
+
+// static
+nsresult
 FileUtils::BodyGetCacheDir(nsIFile* aBaseDir, const nsID& aId,
                            nsIFile** aCacheDirOut)
 {
   MOZ_ASSERT(aBaseDir);
   MOZ_ASSERT(aCacheDirOut);
 
   *aCacheDirOut = nullptr;
 
--- a/dom/cache/FileUtils.h
+++ b/dom/cache/FileUtils.h
@@ -25,16 +25,20 @@ class FileUtils MOZ_FINAL
 public:
   enum BodyFileType
   {
     BODY_FILE_FINAL,
     BODY_FILE_TMP
   };
 
   static nsresult BodyCreateDir(nsIFile* aBaseDir);
+  // Note that this function can only be used during the initialization of the
+  // database.  We're unlikely to be able to delete the DB successfully past
+  // that point due to the file being in use.
+  static nsresult BodyDeleteDir(nsIFile* aBaseDir);
   static nsresult BodyGetCacheDir(nsIFile* aBaseDir, const nsID& aId,
                                   nsIFile** aCacheDirOut);
 
   static nsresult
   BodyStartWriteStream(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir,
                        nsIInputStream* aSource, void* aClosure,
                        nsAsyncCopyCallbackFun aCallback, nsID* aIdOut,
                        nsISupports** aCopyContextOut);