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 73fe3edc80a280570e5ff0574011d467198384a3..cb417c7a3cfa6a232239801a432162a9ea19831d 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 18bde87e21202d7e71289184eceb4ed6f1582e81..120bf0593d6204f62856d9adbe9ba27c0d2d9723 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 d153b4fd8b67034cb13d37a6ce471d58c4277af8..b7411df8a308684b7d9e70473f582e4efbeb6534 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 36d780888913ef1ad9743133f7ab7af110ef85f9..29c019b4cf9b4759ff98b972d3cdde8540188546 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 1431446df299d75bcac77fdbc137bc7955af7e68..a04bfe15e15bb55515c4c84254d2f6c451b80694 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 13b1d0bc32c5d154388637c04bc968d08e5826bc..6bb97d3e5bba6a9a98835919e45a7d485bf6ede3 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 010c605ad42b3f4a38b095b409b75f61fa46bb8e..eac2d7d4c280416149628adf4da42d256e99f4af 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 408a9c91df69ede4cfdf97401b94070f75433c4f..f3e1f19868032619709216ac19785d15363e9095 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 d5ac7970fafca34ab9d2523f01f978047b018243..7bb5c3c9d316f63d7c9a5243d89d87b4055416eb 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)
@@ -443,6 +441,11 @@ class SqlConnection final
                  Flag::Type type = Flag::None,
                  Flag::Option flag = Flag::RO);
 
+    /**
+     * Disconnect SQL connection
+     */
+    void Disconnect();
+
     /**
      * Destructor
      */
index e1fcd2d8dd83c7104fd076dd247d8d966e2aa547..a7046df60006207a2cad7b059b31fc51fd716185 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 b470290dfc8cef863fa363153757347352473990..05d2d77baaa158a7543ee366ea6651f6f8c129ef 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 6b77483c615d8f70992020160267c63c21e34726..06c771bcd3eada114a5fc4aa1a13974a428e0fc6 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 10d23cbc50cad97f5fd9b2ac9eefec25453fb133..6f4065568c2d8bf7e94fed0cf44ff45a8d1bf386 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 7b65dbed5c1cc7bfe70a0d1ac0f93eda281aa641..68aed113ebe643de22498b72dbc7639e63060892 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());