Add database snapshotting and recovery
authorKonrad Lipinski <k.lipinski2@partner.samsung.com>
Fri, 20 Jul 2018 13:29:00 +0000 (15:29 +0200)
committerTomasz Swierczek <t.swierczek@samsung.com>
Wed, 8 Aug 2018 10:00:39 +0000 (12:00 +0200)
A snapshot of a working database can be established by running
  security-manager-cmd --backup
This effectively copies "$TZ_SYS_DB/.security-manager.db" over
"$TZ_SYS_RO_SHARE/security-manager/.security-manager.db" (journal is not
being copied).

NOTE: backup does not check for concurrent access of the db file so the
user has to make sure no concurrent modification takes place in the
interim.

The manager performs an integrity check of the database at every startup
(see below). If the check fails, it truncates the database journal and
overwrites the database file with the latest snapshot, then reattempts
connection, migration and redoes the integrity check on the resulting
database.

As a first shot, integrity check uses the most aggressive possible form
achievable by sqlite pragmas by
* checking if the file exists (to prevent sqlite autovivifying it)
* checking 'pragma intergrity_check'
* checking 'pragma foreign_key_check'

TODO: for product acceptance, actual latency introduced by the integrity
check should be measured. If too high, the check can be made faster by
* dropping foreign_key_check
* replacing integrity_check with quick_check

To help make the decision, lax measurement were taken using
  time sqlite3 >/dev/null /opt/dbspace/.security-manager.db 'pragma..'
time[ms] foreign_key_check integrity_check quick_check
TM1                     17              20          18
emulator                 5               2           2

Change-Id: I01a4ed0879b10bdcadde78ab086776420850e13c

14 files changed:
packaging/security-manager.spec
src/cmd/security-manager-cmd.cpp
src/common/config.cpp
src/common/filesystem.cpp
src/common/include/config.h
src/common/include/filesystem.h
src/common/include/privilege_db.h
src/common/privilege_db.cpp
src/dpl/db/include/dpl/db/sql_connection.h
test/CMakeLists.txt
test/privilege_db_fixture.cpp
test/privilege_db_fixture.h
test/test_privilege_db_migration.cpp
test/test_privilege_db_transactions.cpp

index 73fe3ed..cb417c7 100644 (file)
@@ -145,6 +145,8 @@ install -m 0444 /dev/null %{buildroot}%{TZ_SYS_VAR}/security-manager/policy-vers
 mkdir -p %{buildroot}/%{db_test_dir}
 sqlite3 %{buildroot}/%{db_test_dir}/.security-manager-test.db  <  db/db.sql
 sqlite3 %{buildroot}/%{db_test_dir}/.security-manager-test-v0.db  <  db/db_test_v0.sql
+dd bs=1K count=$(($(stat -c%s %{buildroot}/%{db_test_dir}/.security-manager-test.db) / 1024 - 1)) if=%{buildroot}/%{db_test_dir}/.security-manager-test.db of=%{buildroot}/%{db_test_dir}/.security-manager-test-corrupted.db
+cp -a %{buildroot}/%{db_test_dir}/.security-manager-test.db-journal %{buildroot}/%{db_test_dir}/.security-manager-test-corrupted.db-journal
 echo -n > %{buildroot}/%{db_test_dir}/.security-manager-test-empty.db
 echo -n > %{buildroot}/%{db_test_dir}/.security-manager-test-empty.db-journal
 
@@ -240,6 +242,8 @@ chsmack -a System %{db_test_dir}/.security-manager-test.db
 chsmack -a System %{db_test_dir}/.security-manager-test.db-journal
 chsmack -a System %{db_test_dir}/.security-manager-test-v0.db
 chsmack -a System %{db_test_dir}/.security-manager-test-v0.db-journal
+chsmack -a System %{db_test_dir}/.security-manager-test-corrupted.db
+chsmack -a System %{db_test_dir}/.security-manager-test-corrupted.db-journal
 chsmack -a System %{db_test_dir}/.security-manager-test-empty.db
 chsmack -a System %{db_test_dir}/.security-manager-test-empty.db-journal
 
@@ -308,6 +312,8 @@ chsmack -a System %{db_test_dir}/.security-manager-test-empty.db-journal
 %attr(0600,root,root) %{db_test_dir}/.security-manager-test.db-journal
 %attr(0600,root,root) %{db_test_dir}/.security-manager-test-v0.db
 %attr(0600,root,root) %{db_test_dir}/.security-manager-test-v0.db-journal
+%attr(0600,root,root) %{db_test_dir}/.security-manager-test-corrupted.db
+%attr(0600,root,root) %{db_test_dir}/.security-manager-test-corrupted.db-journal
 %attr(0600,root,root) %{db_test_dir}/.security-manager-test-empty.db
 %attr(0600,root,root) %{db_test_dir}/.security-manager-test-empty.db-journal
 
index 18bde87..120bf05 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2000 - 2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *  Copyright (c) 2000 - 2018 Samsung Electronics Co., Ltd All Rights Reserved
  *
  *  Contact: Rafal Krypa <r.krypa@samsung.com>
  *
@@ -38,6 +38,9 @@
 #include <boost/program_options.hpp>
 #include <boost/exception/diagnostic_information.hpp>
 
+#include <config.h>
+#include <filesystem.h>
+
 namespace po = boost::program_options;
 
 static std::map <std::string, enum app_install_path_type> app_install_path_type_map = {
@@ -71,6 +74,7 @@ static po::options_description getGenericOptions()
          ("manage-apps,n", po::value<std::string>(), "add or remove app, parameter is either a/add or r/remove")
          ("manage-users,m", po::value<std::string>(), "add or remove user, parameter is either a/add or r/remove")
          ("manage-privilege,o", po::value<std::string>(), "allow or deny privilege, parameter is either a/allow or d/deny")
+         ("backup,b", "make a backup of the database file")
          ;
     return opts;
 }
@@ -475,6 +479,9 @@ int main(int argc, char *argv[])
             auto policy_ptr = makeUnique(policy_update, security_manager_policy_update_req_free);
             parsePrivilegeOptions(argc, argv, *req, vm);
             return managePrivilegeOperation(*req, policy_update, operation);
+        } else if (vm.count("backup")) {
+            if (SECURITY_MANAGER_SUCCESS == FS::overwriteFile(Config::privilegeDbPath, Config::privilegeDbFallbackPath))
+                return EXIT_SUCCESS;
         } else {
             std::cout << "No command argument was given." << std::endl;
             usage(std::string(argv[0]));
index d153b4f..b7411df 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2015-2016 Samsung Electronics Co., Ltd All Rights Reserved
+ *  Copyright (c) 2015 - 2018 Samsung Electronics Co., Ltd All Rights Reserved
  *
  *  Contact: Rafal Krypa <r.krypa@samsung.com>
  *
@@ -51,6 +51,13 @@ const bool IS_ASKUSER_ENABLED = true;
 #else
 const bool IS_ASKUSER_ENABLED = false;
 #endif
+
+const std::string privilegeDbPath = TizenPlatformConfig::makePath(TZ_SYS_DB, ".security-manager.db");
+const std::string privilegeDbFallbackPath = TizenPlatformConfig::makePath(TZ_SYS_RO_SHARE, "security-manager", ".security-manager.db");
+
+std::string dbBrokenFlagFileName(const std::string &dbPath) {
+    return dbPath + "-broken";
+}
 };
 
 } /* namespace SecurityManager */
index 36d7808..29c019b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2016 - 2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *  Copyright (c) 2016 - 2018 Samsung Electronics Co., Ltd All Rights Reserved
  *
  *  Contact: Rafal Krypa <r.krypa@samsung.com>
  *
@@ -217,5 +217,30 @@ int symLink(const std::string &src, const std::string &dst)
     return SECURITY_MANAGER_SUCCESS;
 }
 
+int overwriteFile(const std::string &srcPath, const std::string &dstPath) {
+    std::ifstream src(srcPath, std::ios::binary);
+    if (!src)
+        return SECURITY_MANAGER_ERROR_FILE_OPEN_FAILED;
+    std::ofstream dst(dstPath, std::ios::binary|std::ios::trunc);
+    if (src.peek() != std::ifstream::traits_type::eof()) // otherwise dst.fail()
+        dst << src.rdbuf();
+    src.close();
+    dst.close();
+    return src && dst ? SECURITY_MANAGER_SUCCESS : SECURITY_MANAGER_ERROR_FILE_CREATE_FAILED;
+}
+
+int truncateFile(const std::string &path) {
+    std::ofstream dst(path, std::ios::binary|std::ios::trunc);
+    dst.close();
+    return dst ? SECURITY_MANAGER_SUCCESS : SECURITY_MANAGER_ERROR_FILE_CREATE_FAILED;
+}
+
+off_t fileSize(const std::string &path) {
+    struct stat st;
+    static_assert(std::is_same<off_t, decltype(st.st_size)>::value);
+    static_assert(std::is_signed<off_t>::value);
+    return -1 == lstat(path.c_str(), &st) || !S_ISREG(st.st_mode) ? -1 : st.st_size;
+}
+
 } // namespace FS
 } // namespace SecurityManager
index 1431446..a04bfe1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2015-2016 Samsung Electronics Co., Ltd All Rights Reserved
+ *  Copyright (c) 2015 - 2018 Samsung Electronics Co., Ltd All Rights Reserved
  *
  *  Contact: Rafal Krypa <r.krypa@samsung.com>
  *
@@ -25,6 +25,7 @@
 #pragma once
 
 #include <string>
+#include <tzplatform-config.h>
 
 namespace SecurityManager {
 
@@ -57,6 +58,11 @@ extern const std::string PRIVACY_POLICY_DESC;
 
 /* true if privacy-related privileges should result in UI-popup question*/
 extern const bool IS_ASKUSER_ENABLED;
+
+extern const std::string privilegeDbPath;
+extern const std::string privilegeDbFallbackPath;
+
+std::string dbBrokenFlagFileName(const std::string &dbPath);
 };
 
 } /* namespace SecurityManager */
index 13b1d0b..6bb97d3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2016 - 2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *  Copyright (c) 2016 - 2018 Samsung Electronics Co., Ltd All Rights Reserved
  *
  *  Contact: Rafal Krypa <r.krypa@samsung.com>
  *
@@ -48,6 +48,9 @@ int fileStatus(const std::string &path);
 int createFile(const std::string &path);
 int removeFile(const std::string &path);
 int symLink(const std::string &src, const std::string &dst);
+int overwriteFile(const std::string &src, const std::string &dst);
+int truncateFile(const std::string &path);
+off_t fileSize(const std::string &path); // < 0 on error
 
 } // namespace FS
 } // namespace SecurityManager
index 010c605..eac2d7d 100644 (file)
@@ -47,6 +47,8 @@
 
 namespace SecurityManager {
 
+std::string genJournalPath(const std::string &dbPath);
+
 enum class StmtType : uint8_t {
     EAddApplication,
     ERemoveApplication,
@@ -162,7 +164,7 @@ public:
      * @exception PrivilegeDb::Exception::IOError on problems with database access
      *
      */
-    explicit PrivilegeDb(const std::string &path);
+    explicit PrivilegeDb(const std::string &path, const std::string &roFallbackPath);
 
     static PrivilegeDb &getInstance();
 
index 408a9c9..f3e1f19 100644 (file)
@@ -36,6 +36,7 @@
 #include <sys/stat.h>
 
 #include <dpl/log/log.h>
+#include <config.h>
 #include "../gen/db.h"
 #include "privilege_db.h"
 #include "tzplatform-config.h"
@@ -129,20 +130,15 @@ auto prepare(DB::SqlConnection &db, const char *fmt) {
     return cmd;
 }
 
-std::string getPrivilegeDbPath()
-{
-    static std::string path = TizenPlatformConfig::makePath(TZ_SYS_DB, ".security-manager.db");
-    return path;
-}
-
 bool dbEmpty(const std::string &dbPath) {
-    struct stat buf;
-    if (-1 == stat(dbPath.c_str(), &buf))
+    auto s = FS::fileSize(dbPath);
+    if (s < 0)
         ThrowMsg(DB::SqlConnection::Exception::Base, "Failed to stat db file");
-    return !buf.st_size;
+    return !s;
 }
 
-void migrate(DB::SqlConnection &db, const std::string &path) {
+void connectMigrateVerify(DB::SqlConnection &db, const std::string &path) {
+    db.Connect(path, DB::SqlConnection::Flag::None, DB::SqlConnection::Flag::RW);
     using Ex = DB::SqlConnection::Exception::Base;
     int32_t version;
     {
@@ -160,8 +156,19 @@ void migrate(DB::SqlConnection &db, const std::string &path) {
             do db.ExecCommand(dbUpdateScript[version]); while (++version < dbVersion);
         db.ExecCommand(dbSchema);
     }
+
+    {
+        auto cmd = prepare(db, "PRAGMA integrity_check");
+        if (!cmd->Step())
+            ThrowMsg(Ex, "Integrity check returned no result");
+        if (cmd->GetColumnString(0) != "ok")
+            ThrowMsg(Ex, "Integrity check failed");
+    }
+
+    auto cmd = prepare(db, "PRAGMA foreign_key_check");
+    if (cmd->Step())
+        ThrowMsg(Ex, "Foreign key check failed");
 }
-} //namespace
 
 /* Common code for handling SqlConnection exceptions */
 template <typename T>
@@ -184,30 +191,59 @@ T try_catch(const std::function<T()> &func)
     }
 }
 
-std::string getPrivilegeDbFailFlagPath()
-{
-    static std::string path = TizenPlatformConfig::makePath(TZ_SYS_DB, ".security-manager.db-broken");
-    return path;
+void throwDbInitEx(const std::string &errDesc) {
+    auto s = "Database initialization error: " + errDesc;
+    LogError(s);
+    ThrowMsg(PrivilegeDb::Exception::IOError, s);
+}
+
+void createBrokenFlagFile(const std::string &dbPath) {
+    if (SECURITY_MANAGER_SUCCESS != FS::createFile(Config::dbBrokenFlagFileName(dbPath)))
+        throwDbInitEx("Error creating db broken flag file");
 }
 
-PrivilegeDb::PrivilegeDb() : PrivilegeDb(getPrivilegeDbPath())
+template <class F>
+void tryCatchDbInit(F &&f) {
+    try {
+        f();
+    } catch (DB::SqlConnection::Exception::Base &e) {
+        throwDbInitEx(e.DumpToString());
+    }
+}
+
+void applyFallbackDb(DB::SqlConnection &conn, const std::string &dbPath, const std::string &roFallbackPath) {
+    if (SECURITY_MANAGER_SUCCESS != FS::overwriteFile(roFallbackPath, dbPath))
+        throwDbInitEx("Error overwriting database with fallback: " + roFallbackPath);
+    if (SECURITY_MANAGER_SUCCESS != FS::truncateFile(genJournalPath(dbPath)))
+        throwDbInitEx("Error truncating journal");
+    tryCatchDbInit([&]{ connectMigrateVerify(conn, dbPath); });
+}
+} //namespace
+
+std::string genJournalPath(const std::string &dbPath) {
+    return dbPath + "-journal";
+}
+
+PrivilegeDb::PrivilegeDb()
+    : PrivilegeDb(Config::privilegeDbPath, Config::privilegeDbFallbackPath)
 {
 }
 
-PrivilegeDb::PrivilegeDb(const std::string &path)
+PrivilegeDb::PrivilegeDb(const std::string &path, const std::string &roFallbackPath)
 {
-    try {
-        mSqlConnection.Connect(path,
-                DB::SqlConnection::Flag::None,
-                DB::SqlConnection::Flag::RW);
-        migrate(mSqlConnection, path);
-        initDataCommands();
+    if (!FS::fileStatus(path)) {
+        createBrokenFlagFile(path);
+        LogError("Database file missing, attempting fallback");
+        applyFallbackDb(mSqlConnection, path, roFallbackPath);
+    } else try {
+        connectMigrateVerify(mSqlConnection, path);
     } catch (DB::SqlConnection::Exception::Base &e) {
-        LogError("Database initialization error: " << e.DumpToString());
-        FS::createFile(getPrivilegeDbFailFlagPath());
-        ThrowMsg(PrivilegeDb::Exception::IOError,
-                "Database initialization error:" << e.DumpToString());
-    };
+        createBrokenFlagFile(path);
+        LogError("Database initialization error (" << e.DumpToString() << "), attempting fallback");
+        tryCatchDbInit([&]{ mSqlConnection.Disconnect(); });
+        applyFallbackDb(mSqlConnection, path, roFallbackPath);
+    }
+    tryCatchDbInit([&]{ initDataCommands(); });
 }
 
 void PrivilegeDb::initDataCommands()
index d5ac797..7bb5c3c 100644 (file)
@@ -410,8 +410,6 @@ class SqlConnection final
     // Synchronization object
     std::unique_ptr<SynchronizationObject> m_synchronizationObject;
 
-    void Disconnect();
-
     void TurnOnForeignKeys();
 
     static SynchronizationObject *AllocDefaultSynchronizationObject();
@@ -433,7 +431,7 @@ class SqlConnection final
     /**
      * Open SQL connection
      *
-     * Called exactly once on a newly constructed object before using it.
+     * Called exactly once on a disconnected object before using it.
      *
      * @param address Database file name
      * @param type Open options (ie. use Lucene index or not)
@@ -444,6 +442,11 @@ class SqlConnection final
                  Flag::Option flag = Flag::RO);
 
     /**
+     * Disconnect SQL connection
+     */
+    void Disconnect();
+
+    /**
      * Destructor
      */
     ~SqlConnection();
index e1fcd2d..a7046df 100644 (file)
@@ -72,6 +72,7 @@ SET(SM_TESTS_SOURCES
     ${DPL_PATH}/log/src/abstract_log_provider.cpp
     ${DPL_PATH}/log/src/log.cpp
     ${DPL_PATH}/log/src/old_style_log_provider.cpp
+    ${PROJECT_SOURCE_DIR}/src/common/config.cpp
     ${PROJECT_SOURCE_DIR}/src/common/config-file.cpp
     ${PROJECT_SOURCE_DIR}/src/common/file-lock.cpp
     ${PROJECT_SOURCE_DIR}/src/common/privilege_db.cpp
@@ -98,6 +99,7 @@ SET(SM_PERFORMANCE_TESTS_SOURCES
     ${DPL_PATH}/log/src/abstract_log_provider.cpp
     ${DPL_PATH}/log/src/log.cpp
     ${DPL_PATH}/log/src/old_style_log_provider.cpp
+    ${PROJECT_SOURCE_DIR}/src/common/config.cpp
     ${PROJECT_SOURCE_DIR}/src/common/config-file.cpp
     #${PROJECT_SOURCE_DIR}/src/common/file-lock.cpp
     ${PROJECT_SOURCE_DIR}/src/common/privilege_db.cpp
index b470290..05d2d77 100644 (file)
@@ -29,6 +29,9 @@
 #include <boost/test/unit_test.hpp>
 #include <boost/test/results_reporter.hpp>
 
+#include <config.h>
+#include <filesystem.h>
+
 #include "privilege_db.h"
 #include "privilege_db_fixture.h"
 
@@ -38,11 +41,7 @@ namespace {
 constexpr uid_t FirstUidForTests = 9900;
 
 void putFile(const std::string &srcPath, const std::string &dstPath) {
-    std::ifstream src(srcPath, std::ios::binary);
-    std::ofstream dst(dstPath, std::ios::binary|std::ios::trunc);
-    if (src.peek() != std::ifstream::traits_type::eof()) // otherwise dst.fail()
-        dst << src.rdbuf();
-    BOOST_REQUIRE(src && dst);
+    BOOST_REQUIRE(SECURITY_MANAGER_SUCCESS == FS::overwriteFile(srcPath, dstPath));
 }
 
 std::string genName(const std::string &prefix, int i)
@@ -52,30 +51,38 @@ std::string genName(const std::string &prefix, int i)
 }
 } //namespace
 
-std::string genJournalPath(const char *dbPath) {
-    Assert(dbPath);
-    return dbPath + std::string("-journal");
+void requireNoDb(const std::string &dbPath) {
+    BOOST_REQUIRE(!FS::fileStatus(dbPath));
+    BOOST_REQUIRE(!FS::fileStatus(genJournalPath(dbPath)));
+    BOOST_REQUIRE(!FS::fileStatus(Config::dbBrokenFlagFileName(dbPath)));
 }
 
-PrivilegeDBFixture::PrivilegeDBFixture(char const *src)
+PrivilegeDBFixture::PrivilegeDBFixture(const std::string &src, const std::string &fallback,
+        HaveBrokenFlagFile haveBrokenFlagFile, const std::string &dst)
+    : dbPath(dst)
 {
-    Assert(src);
-    putFile(src, TEST_PRIVILEGE_DB_PATH);
-    putFile(genJournalPath(src), genJournalPath(TEST_PRIVILEGE_DB_PATH));
-
-    testPrivDb = new PrivilegeDb(TEST_PRIVILEGE_DB_PATH);
-};
+    requireNoDb(dst);
+    putFile(src, dst);
+    putFile(genJournalPath(src), genJournalPath(dst));
+
+    testPrivDb = new PrivilegeDb(dst, fallback);
+    const auto brokenFlagFileName = Config::dbBrokenFlagFileName(dst);
+    const auto flagFileStatus = FS::fileStatus(brokenFlagFileName);
+    if (haveBrokenFlagFile == HaveBrokenFlagFile::no) {
+        BOOST_REQUIRE(!flagFileStatus);
+    } else {
+        BOOST_REQUIRE(flagFileStatus > 0);
+        BOOST_REQUIRE(!FS::fileSize(brokenFlagFileName));
+        BOOST_REQUIRE(!remove(brokenFlagFileName.c_str()));
+    }
+}
 
 PrivilegeDBFixture::~PrivilegeDBFixture()
 {
-    auto journalPath = genJournalPath(TEST_PRIVILEGE_DB_PATH);
-    if (std::ifstream(TEST_PRIVILEGE_DB_PATH))
-        BOOST_WARN_MESSAGE(remove(TEST_PRIVILEGE_DB_PATH) == 0,
-            "Could not delete test database file: " << TEST_PRIVILEGE_DB_PATH);
-    if (std::ifstream(journalPath))
-        BOOST_WARN_MESSAGE(remove(journalPath.c_str()) == 0,
-             "Could not delete test database file: " << journalPath);
-
+    BOOST_REQUIRE_MESSAGE(remove(dbPath.c_str()) == 0, "Could not delete test database file: " << dbPath);
+    auto journalPath = genJournalPath(dbPath);
+    BOOST_REQUIRE_MESSAGE(remove(journalPath.c_str()) == 0, "Could not delete test database journal file: " << journalPath);
+    BOOST_REQUIRE(!FS::fileStatus(Config::dbBrokenFlagFileName(dbPath)));
     delete testPrivDb;
 }
 
index 6b77483..06c771b 100644 (file)
 
 #define PRIVILEGE_DB_TEMPLATE DB_TEST_DIR"/.security-manager-test.db"
 #define PRIVILEGE_DB_EXAMPLE_V0 DB_TEST_DIR"/.security-manager-test-v0.db"
+#define PRIVILEGE_DB_CORRUPTED DB_TEST_DIR"/.security-manager-test-corrupted.db"
 #define PRIVILEGE_DB_EMPTY DB_TEST_DIR"/.security-manager-test-empty.db"
 
 #define TEST_PRIVILEGE_DB_PATH "/tmp/.security-manager-test.db"
+#define TEST_PRIVILEGE_DB_PATH_2 "/tmp/.security-manager-test-2.db"
 
 using namespace SecurityManager;
 
-std::string genJournalPath(const char *dbPath);
+void requireNoDb(const std::string &dbPath);
 
 struct PrivilegeDBFixture {
 public:
-    explicit PrivilegeDBFixture(char const *src = PRIVILEGE_DB_TEMPLATE);
+    enum class HaveBrokenFlagFile : bool { no, yes };
+    explicit PrivilegeDBFixture(const std::string &src = PRIVILEGE_DB_TEMPLATE,
+        const std::string &fallback = PRIVILEGE_DB_TEMPLATE,
+        HaveBrokenFlagFile = HaveBrokenFlagFile::no, const std::string &dst = TEST_PRIVILEGE_DB_PATH);
     ~PrivilegeDBFixture();
 
     PrivilegeDb* getPrivDb();
@@ -68,4 +73,7 @@ public:
 
 protected:
     PrivilegeDb *testPrivDb;
+
+private:
+    std::string dbPath;
 };
index 10d23cb..6f40655 100644 (file)
  *  limitations under the License
  */
 
+#include <stdio.h>
 #include <stdlib.h>
 
 #include <boost/iostreams/device/mapped_file.hpp>
 #include <boost/test/unit_test.hpp>
 #include <boost/test/unit_test_monitor.hpp>
 
+#include <config.h>
+#include <filesystem.h>
 #include "privilege_db.h"
 #include "privilege_db_fixture.h"
 
@@ -30,13 +33,22 @@ bool fileContentsSame(const std::string &aPath, const std::string &bPath) {
     return s == b.size() && !memcmp(a.data(), b.data(), s);
 }
 
+void requireTestDbContents(const std::string &db) {
+    BOOST_REQUIRE(fileContentsSame(TEST_PRIVILEGE_DB_PATH, db));
+}
+
+void requireTestDbAndJournalContents(const std::string &db) {
+    requireTestDbContents(db);
+    BOOST_REQUIRE(fileContentsSame(genJournalPath(TEST_PRIVILEGE_DB_PATH), genJournalPath(db)));
+}
+
 void translateIOError(PrivilegeDb::Exception::IOError e) {
     BOOST_FAIL("IOError: " + e.DumpToString());
 }
 
-class Config {
+class TestConfig {
 public:
-    Config() {
+    TestConfig() {
         boost::unit_test::unit_test_monitor.register_exception_translator<PrivilegeDb::Exception::IOError>(&translateIOError);
     }
 };
@@ -47,16 +59,57 @@ struct PrivilegeV0DBFixture : PrivilegeDBFixture {
 struct PrivilegeEmptyDBFixture : PrivilegeDBFixture {
     PrivilegeEmptyDBFixture() : PrivilegeDBFixture(PRIVILEGE_DB_EMPTY) {}
 };
+struct PrivilegeFallbackDBFixture : PrivilegeDBFixture {
+    PrivilegeFallbackDBFixture() : PrivilegeDBFixture(PRIVILEGE_DB_CORRUPTED, PRIVILEGE_DB_TEMPLATE, HaveBrokenFlagFile::yes) {}
+};
+struct PrivilegeFallbackV0DBFixture : PrivilegeDBFixture {
+    PrivilegeFallbackV0DBFixture() : PrivilegeDBFixture(PRIVILEGE_DB_CORRUPTED, PRIVILEGE_DB_EXAMPLE_V0, HaveBrokenFlagFile::yes) {}
+};
+struct PrivilegeFallbackEmptyDBFixture : PrivilegeDBFixture {
+    PrivilegeFallbackEmptyDBFixture() : PrivilegeDBFixture(PRIVILEGE_DB_CORRUPTED, PRIVILEGE_DB_EMPTY, HaveBrokenFlagFile::yes) {}
+};
 } //namespace
 
-BOOST_GLOBAL_FIXTURE(Config)
+BOOST_GLOBAL_FIXTURE(TestConfig)
 
 BOOST_FIXTURE_TEST_SUITE(PRIVILEGE_DB_TEST_EMPTY, PrivilegeEmptyDBFixture)
+BOOST_AUTO_TEST_CASE(T1500_schema_application) {
+    requireTestDbAndJournalContents(PRIVILEGE_DB_TEMPLATE);
+}
+BOOST_AUTO_TEST_SUITE_END()
 
-BOOST_AUTO_TEST_CASE(T1500_schema_application)
-{
-    BOOST_REQUIRE(fileContentsSame(PRIVILEGE_DB_TEMPLATE, TEST_PRIVILEGE_DB_PATH));
-    BOOST_REQUIRE(fileContentsSame(genJournalPath(PRIVILEGE_DB_TEMPLATE), genJournalPath(TEST_PRIVILEGE_DB_PATH)));
+BOOST_FIXTURE_TEST_SUITE(PRIVILEGE_DB_TEST_FALLBACK, PrivilegeFallbackDBFixture)
+BOOST_AUTO_TEST_CASE(T1510_fallback) {
+    requireTestDbContents(PRIVILEGE_DB_TEMPLATE);
+    BOOST_REQUIRE(!FS::fileSize(genJournalPath(TEST_PRIVILEGE_DB_PATH)));
+}
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_FIXTURE_TEST_SUITE(PRIVILEGE_DB_TEST_FALLBACK_EMPTY, PrivilegeFallbackEmptyDBFixture)
+BOOST_AUTO_TEST_CASE(T1520_fallback_schema_application) {
+    requireTestDbAndJournalContents(PRIVILEGE_DB_TEMPLATE);
+}
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_FIXTURE_TEST_SUITE(PRIVILEGE_DB_TEST_FALLBACK_V0, PrivilegeFallbackV0DBFixture)
+
+BOOST_AUTO_TEST_CASE(T1530_fallback_migration) {
+    PrivilegeDBFixture v0(PRIVILEGE_DB_EXAMPLE_V0, PRIVILEGE_DB_TEMPLATE,
+            PrivilegeDBFixture::HaveBrokenFlagFile::no, TEST_PRIVILEGE_DB_PATH_2);
+    requireTestDbContents(TEST_PRIVILEGE_DB_PATH_2);
+}
+
+BOOST_AUTO_TEST_CASE(T1540_db_missing_fallback_migration) {
+    const std::string missingDbPath("/tmp/thisNotExists.db");
+    requireNoDb(missingDbPath);
+    const auto missingDbPathJournal = genJournalPath(missingDbPath);
+    const auto missingDbPathFlag = Config::dbBrokenFlagFileName(missingDbPath);
+    BOOST_REQUIRE_NO_THROW(PrivilegeDb(missingDbPath, PRIVILEGE_DB_EXAMPLE_V0));
+    requireTestDbContents(missingDbPath);
+    BOOST_REQUIRE(!remove(missingDbPath.c_str()));
+    BOOST_REQUIRE(!remove(missingDbPathJournal.c_str()));
+    BOOST_REQUIRE(!FS::fileSize(missingDbPathFlag));
+    BOOST_REQUIRE(!remove(missingDbPathFlag.c_str()));
 }
 
 BOOST_AUTO_TEST_SUITE_END()
index 7b65dbe..68aed11 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2016 - 2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *  Copyright (c) 2016 - 2018 Samsung Electronics Co., Ltd All Rights Reserved
  *
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -27,6 +27,9 @@
 #include <boost/test/results_reporter.hpp>
 #include <boost/test/utils/wrap_stringstream.hpp>
 
+#include <config.h>
+#include <filesystem.h>
+
 #include "privilege_db.h"
 #include "privilege_db_fixture.h"
 
@@ -39,14 +42,26 @@ BOOST_FIXTURE_TEST_SUITE(PRIVILEGE_DB_TEST_TRANSACTIONS, PrivilegeDBFixture)
 
 BOOST_FIXTURE_TEST_CASE(T100_privilegedb_constructor, Empty)
 {
-    PrivilegeDb *testPrivDb = nullptr;
+    std::unique_ptr<PrivilegeDb> testPrivDb;
+
+    BOOST_REQUIRE_NO_THROW(testPrivDb.reset(new PrivilegeDb()));
 
-    BOOST_REQUIRE_NO_THROW(testPrivDb = new PrivilegeDb());
-    delete testPrivDb;
-    testPrivDb = nullptr;
-    BOOST_REQUIRE_THROW(testPrivDb = new PrivilegeDb("/this/not/exists"),
+    std::string nExist("/tmp/thisNotExists"), nExist2("/tmp/neitherDoesThis");
+    requireNoDb(nExist);
+    requireNoDb(nExist2);
+    BOOST_REQUIRE_THROW(testPrivDb.reset(new PrivilegeDb(nExist, nExist2)),
         PrivilegeDb::Exception::IOError);
-    delete testPrivDb;
+    const auto flagFile = Config::dbBrokenFlagFileName(nExist);
+    BOOST_REQUIRE(!FS::fileSize(flagFile));
+    BOOST_REQUIRE(!remove(flagFile.c_str()));
+    requireNoDb(nExist);
+
+    // fallback existent but db can't be created w/out mkdir -p
+    std::string nExistDeep("/this/not/exists");
+    requireNoDb(nExistDeep);
+    BOOST_REQUIRE_THROW(testPrivDb.reset(new PrivilegeDb(nExistDeep, PRIVILEGE_DB_TEMPLATE)),
+        PrivilegeDb::Exception::IOError);
+    requireNoDb(nExistDeep);
 }
 
 // Transactions
@@ -86,7 +101,7 @@ BOOST_AUTO_TEST_CASE(T230_rollback_without_begin)
 BOOST_AUTO_TEST_CASE(T240_transaction)
 {
     BOOST_REQUIRE_NO_THROW(getPrivDb()->BeginTransaction());
-    addAppSuccess(app(1),pkg(1), uid(1), tizenVer(1), author(1), NotHybrid);
+    addAppSuccess(app(1), pkg(1), uid(1), tizenVer(1), author(1), NotHybrid);
 
     BOOST_REQUIRE_NO_THROW(getPrivDb()->CommitTransaction());
     BOOST_REQUIRE_NO_THROW(getPrivDb()->BeginTransaction());