Add CKM database versioning with DB migration mechanism.
authorMaciej J. Karpiuk <m.karpiuk2@samsung.com>
Wed, 7 Jan 2015 10:40:55 +0000 (11:40 +0100)
committerMaciej J. Karpiuk <m.karpiuk2@samsung.com>
Tue, 17 Feb 2015 11:09:37 +0000 (12:09 +0100)
Change-Id: I3d773b1b9ff4949a4ae98e25c778e6c010bc8a62

12 files changed:
data/scripts/create_schema.sql [new file with mode: 0644]
data/scripts/drop_all.sql [new file with mode: 0644]
data/scripts/migrate_1.sql [new file with mode: 0644]
data/scripts/migrate_2.sql [new file with mode: 0644]
packaging/key-manager.spec
src/manager/service/db-crypto.cpp
src/manager/service/db-crypto.h
tests/DBFixture.cpp
tests/DBFixture.h
tests/test_db_crypto.cpp
tests/testme_ver1.db [new file with mode: 0644]
tests/testme_ver2.db [new file with mode: 0644]

diff --git a/data/scripts/create_schema.sql b/data/scripts/create_schema.sql
new file mode 100644 (file)
index 0000000..1219ec1
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ *  Copyright (c) 2000 - 2015 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.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ *
+ *
+ * @file        create_schema.sql
+ * @author      Maciej Karpiuk (m.karpiuk2@samsung.com)
+ * @version     3.0
+ * @brief       DB script to create database schema.
+ */
+
+
+-- create the tables
+CREATE TABLE IF NOT EXISTS SCHEMA_INFO(name TEXT PRIMARY KEY NOT NULL,
+                                       value TEXT);
+
+CREATE TABLE IF NOT EXISTS NAME_TABLE(name TEXT NOT NULL,
+                                      label TEXT NOT NULL,
+                                      idx INTEGER PRIMARY KEY AUTOINCREMENT,
+                                      UNIQUE(name, label));
+
+CREATE TABLE IF NOT EXISTS OBJECT_TABLE(exportable INTEGER NOT NULL,
+                                        dataType INTEGER NOT NULL,
+                                        algorithmType INTEGER NOT NULL,
+                                        encryptionScheme INTEGER NOT NULL,
+                                        iv BLOB NOT NULL,
+                                        dataSize INTEGER NOT NULL,
+                                        data BLOB NOT NULL,
+                                        tag BLOB NOT NULL,
+                                        idx INTEGER NOT NULL,
+                                        FOREIGN KEY(idx) REFERENCES NAME_TABLE(idx) ON DELETE CASCADE,
+                                        PRIMARY KEY(idx, dataType));
+
+CREATE TABLE IF NOT EXISTS KEY_TABLE(label TEXT PRIMARY KEY,
+                                     key BLOB NOT NULL);
+
+CREATE TABLE IF NOT EXISTS PERMISSION_TABLE(permissionLabel TEXT NOT NULL,
+                                            permissionMask INTEGER NOT NULL,
+                                            idx INTEGER NOT NULL,
+                                            FOREIGN KEY(idx) REFERENCES NAME_TABLE(idx) ON DELETE CASCADE,
+                                            PRIMARY KEY(permissionLabel, idx));
+
+
+-- create views
+CREATE VIEW IF NOT EXISTS [join_name_object_tables] AS
+   SELECT N.name, N.label, O.* FROM
+       NAME_TABLE AS N
+       JOIN OBJECT_TABLE AS O ON O.idx=N.idx;
+
+CREATE VIEW IF NOT EXISTS [join_name_permission_tables] AS
+   SELECT N.name, N.label, P.permissionMask, P.permissionLabel FROM  NAME_TABLE AS N
+       JOIN PERMISSION_TABLE AS P ON P.idx=N.idx;
+
+CREATE VIEW IF NOT EXISTS [join_all_tables] AS
+   SELECT N.*, P.permissionLabel, P.permissionMask, O.dataType FROM  NAME_TABLE AS N
+       JOIN OBJECT_TABLE AS O ON O.idx=N.idx
+       JOIN PERMISSION_TABLE AS P ON P.idx=N.idx;
+
+
+-- create indexes
+CREATE INDEX IF NOT EXISTS perm_index_idx ON PERMISSION_TABLE(idx);
+CREATE INDEX IF NOT EXISTS name_index_idx ON NAME_TABLE(idx);
diff --git a/data/scripts/drop_all.sql b/data/scripts/drop_all.sql
new file mode 100644 (file)
index 0000000..5b7db33
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ *  Copyright (c) 2000 - 2015 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.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ *
+ *
+ * @file        drop_all.sql
+ * @author      Maciej Karpiuk (m.karpiuk2@samsung.com)
+ * @version     1.0
+ * @brief       DB script to drop all current and historical objects.
+ */
+
+
+-- drop tables
+-- SQLite does not provide DROP ALL TABLES construction.
+-- the SQLite-way is to remove the whole database file,
+-- which would require expensive changes to the service code
+-- (re-establishing the DB connection).
+DROP TABLE IF EXISTS SCHEMA_INFO;
+DROP TABLE IF EXISTS CKM_TABLE;
+DROP TABLE IF EXISTS NAME_TABLE;
+DROP TABLE IF EXISTS KEY_TABLE;
+DROP TABLE IF EXISTS OBJECT_TABLE;
+DROP TABLE IF EXISTS PERMISSION_TABLE;
+DROP TABLE IF EXISTS OLD_PERMISSION_TABLE;
+
+
+-- drop views
+DROP VIEW IF EXISTS [join_name_object_tables];
+DROP VIEW IF EXISTS [join_name_permission_tables];
+DROP VIEW IF EXISTS [join_all_tables];
+
diff --git a/data/scripts/migrate_1.sql b/data/scripts/migrate_1.sql
new file mode 100644 (file)
index 0000000..39e2d70
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ *  Copyright (c) 2000 - 2015 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.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ *
+ *
+ * @file        migrate_1.sql
+ * @author      Maciej Karpiuk (m.karpiuk2@samsung.com)
+ * @version     1.0
+ * @brief       DB migration script from schema version 1 to schema version 2.
+ */
+
+
+-- isolate old data
+ALTER TABLE PERMISSION_TABLE RENAME TO OLD_PERMISSION_TABLE;
+DROP INDEX perm_index_idx;
+
+
+-- create new structure
+CREATE TABLE NAME_TABLE(name TEXT NOT NULL,
+                        label TEXT NOT NULL,
+                        idx INTEGER PRIMARY KEY AUTOINCREMENT,
+                        UNIQUE(name, label));
+CREATE INDEX name_index_idx ON NAME_TABLE(idx);
+CREATE TABLE OBJECT_TABLE(exportable INTEGER NOT NULL,
+                          dataType INTEGER NOT NULL,
+                          algorithmType INTEGER NOT NULL,
+                          encryptionScheme INTEGER NOT NULL,
+                          iv BLOB NOT NULL,
+                          dataSize INTEGER NOT NULL,
+                          data BLOB NOT NULL,
+                          tag BLOB NOT NULL,
+                          idx INTEGER NOT NULL,
+                          FOREIGN KEY(idx) REFERENCES NAME_TABLE(idx) ON DELETE CASCADE,
+                          PRIMARY KEY(idx, dataType));
+CREATE TABLE PERMISSION_TABLE(label TEXT NOT NULL,
+                              accessFlags TEXT NOT NULL,
+                              idx INTEGER NOT NULL,
+                              FOREIGN KEY(idx) REFERENCES NAME_TABLE(idx) ON DELETE CASCADE,
+                              PRIMARY KEY(label, idx));
+CREATE INDEX perm_index_idx ON PERMISSION_TABLE(idx);
+
+
+-- move data
+INSERT INTO NAME_TABLE(name, label, idx) SELECT name, label, idx FROM CKM_TABLE;
+INSERT INTO OBJECT_TABLE(exportable, dataType, algorithmType, encryptionScheme,
+                         iv, dataSize, data, tag, idx)
+                SELECT exportable, dataType, algorithmType, encryptionScheme, iv,
+                       dataSize, data, tag, idx FROM CKM_TABLE;
+INSERT INTO PERMISSION_TABLE(label, accessFlags, idx) SELECT label, accessFlags, idx FROM OLD_PERMISSION_TABLE;
+
+
+-- cleanup
+DROP TABLE OLD_PERMISSION_TABLE;
+DROP TABLE CKM_TABLE;
diff --git a/data/scripts/migrate_2.sql b/data/scripts/migrate_2.sql
new file mode 100644 (file)
index 0000000..0abb271
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ *  Copyright (c) 2000 - 2015 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.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ *
+ *
+ * @file        migrate_2.sql
+ * @author      Maciej Karpiuk (m.karpiuk2@samsung.com)
+ * @version     1.0
+ * @brief       DB migration script from schema version 2 to schema version 3.
+ */
+
+
+-- isolate old data
+ALTER TABLE PERMISSION_TABLE RENAME TO OLD_PERMISSION_TABLE;
+DROP INDEX perm_index_idx;
+
+
+-- create new structure
+CREATE TABLE SCHEMA_INFO(name TEXT PRIMARY KEY NOT NULL,
+                         value TEXT);
+CREATE TABLE PERMISSION_TABLE(permissionLabel TEXT NOT NULL,
+                              permissionMask INTEGER NOT NULL,
+                              idx INTEGER NOT NULL,
+                              FOREIGN KEY(idx) REFERENCES NAME_TABLE(idx) ON DELETE CASCADE,
+                              PRIMARY KEY(permissionLabel, idx));
+CREATE INDEX perm_index_idx ON PERMISSION_TABLE(idx);
+CREATE VIEW [join_name_object_tables] AS
+        SELECT N.name, N.label, O.* FROM NAME_TABLE AS N
+            JOIN OBJECT_TABLE AS O ON O.idx=N.idx;
+CREATE VIEW [join_name_permission_tables] AS
+        SELECT N.name, N.label, P.permissionMask, P.permissionLabel FROM NAME_TABLE AS N
+            JOIN PERMISSION_TABLE AS P ON P.idx=N.idx;
+CREATE VIEW [join_all_tables] AS
+        SELECT N.*, P.permissionLabel, P.permissionMask, O.dataType FROM NAME_TABLE AS N
+            JOIN OBJECT_TABLE AS O ON O.idx=N.idx
+            JOIN PERMISSION_TABLE AS P ON P.idx=N.idx;
+
+
+-- move data
+INSERT INTO PERMISSION_TABLE(permissionLabel, permissionMask, idx) SELECT label, 1, idx FROM OLD_PERMISSION_TABLE WHERE accessFlags='R';
+INSERT INTO PERMISSION_TABLE(permissionLabel, permissionMask, idx) SELECT label, 3, idx FROM OLD_PERMISSION_TABLE WHERE accessFlags='RD';
+INSERT INTO PERMISSION_TABLE(permissionLabel, permissionMask, idx) SELECT label, 3, idx FROM NAME_TABLE;
+
+
+-- cleanup
+DROP TABLE OLD_PERMISSION_TABLE;
index 9059bdb..5db9860 100644 (file)
@@ -113,6 +113,11 @@ cp LICENSE %{buildroot}/usr/share/license/%{name}
 cp LICENSE %{buildroot}/usr/share/license/libkey-manager-client
 cp LICENSE %{buildroot}/usr/share/license/libkey-manager-control-client
 mkdir -p %{buildroot}/etc/security/
+mkdir -p %{buildroot}/usr/share/ckm/scripts
+cp data/scripts/*.sql %{buildroot}/usr/share/ckm/scripts
+mkdir -p %{buildroot}/usr/share/ckm-db-test
+cp tests/testme_ver1.db %{buildroot}/usr/share/ckm-db-test/
+cp tests/testme_ver2.db %{buildroot}/usr/share/ckm-db-test/
 
 %make_install
 mkdir -p %{buildroot}%{_unitdir}/multi-user.target.wants
@@ -192,6 +197,8 @@ fi
 %{_unitdir}/sockets.target.wants/central-key-manager-api-ocsp.socket
 %{_unitdir}/central-key-manager-api-ocsp.socket
 %{_datadir}/license/%{name}
+%{_datadir}/ckm/scripts/*.sql
+%attr(444, root, root) %{_datadir}/ckm/scripts/*.sql
 
 %files -n key-manager-listener
 %manifest key-manager-listener.manifest
@@ -234,3 +241,5 @@ fi
 %files -n key-manager-tests
 %defattr(-,root,root,-)
 %{_bindir}/ckm-tests-internal
+%{_datadir}/ckm-db-test/testme_ver1.db
+%{_datadir}/ckm-db-test/testme_ver2.db
index 5912e85..4264064 100644 (file)
@@ -20,6 +20,7 @@
  * @brief       Implementation of encrypted db access layer
  */
 
+#include <fstream>
 #include <db-crypto.h>
 #include <dpl/db/sql_connection.h>
 #include <dpl/log/log.h>
 #pragma GCC diagnostic warning "-Wdeprecated-declarations"
 
 namespace {
-    const char *TABLE_NAME                          = "NAME_TABLE";
-    const char *TABLE_OBJECT                        = "OBJECT_TABLE";
-    const char *TABLE_KEY                           = "KEY_TABLE";
-    const char *TABLE_PERMISSION                    = "PERMISSION_TABLE";
-    const CKM::PermissionMask DEFAULT_PERMISSIONS   = static_cast<CKM::PermissionMask>(CKM::Permission::READ | CKM::Permission::REMOVE);
-
-    const char *DB_CMD_NAME_CREATE =
-            "CREATE TABLE IF NOT EXISTS NAME_TABLE("
-            "   name TEXT NOT NULL,"
-            "   label TEXT NOT NULL,"
-            "   idx INTEGER PRIMARY KEY AUTOINCREMENT,"
-            "   UNIQUE(name, label)"
-            "); CREATE INDEX IF NOT EXISTS name_index_idx ON NAME_TABLE(idx);";
+    const CKM::PermissionMask DEFAULT_PERMISSIONS =
+                        static_cast<CKM::PermissionMask>(CKM::Permission::READ | CKM::Permission::REMOVE);
+
+    const char *SCRIPTS_PATH = "/usr/share/ckm/scripts/";
+
+    enum DBVersion : int {
+        DB_VERSION_1                   = 1,
+        DB_VERSION_2                   = 2,
+        /* ... since version 3, there is no need to manually
+         * recognize database version.
+         * Remember only that if doing changes to the database,
+         * increment and update DB_VERSION_CURRENT,
+         * then provide migration mechanism!
+         */
+        DB_VERSION_CURRENT             = 3
+    };
+
+    const char *SCRIPT_CREATE_SCHEMA                = "create_schema";
+    const char *SCRIPT_DROP_ALL_ITEMS               = "drop_all";
+    const char *SCRIPT_MIGRATE                      = "migrate_";
+
+    // common substitutions:
+    // 100 - idx
+    // 101 - name
+    // 102 - label
+    // 103 - value
+    // 104 - permissionLabel
+    // 105 - permissionMask
+    const char *DB_CMD_SCHEMA_SET =
+            "REPLACE INTO SCHEMA_INFO(name, value) "
+            "   VALUES(?101, ?103);";
+
+    const char *DB_CMD_SCHEMA_GET =
+            "SELECT * FROM SCHEMA_INFO WHERE name=?101;";
+
+    const char *DB_SCHEMA_VERSION_FIELD = "schema_version";
+
 
     const char *DB_CMD_NAME_INSERT =
             "INSERT INTO NAME_TABLE("
@@ -57,20 +82,6 @@ namespace {
     const char *DB_CMD_NAME_DELETE_BY_LABEL =
             "DELETE FROM NAME_TABLE WHERE label=?102;";
 
-    const char *DB_CMD_OBJECT_CREATE =
-            "CREATE TABLE IF NOT EXISTS OBJECT_TABLE("
-            "   exportable INTEGER NOT NULL,"
-            "   dataType INTEGER NOT NULL,"
-            "   algorithmType INTEGER NOT NULL,"
-            "   encryptionScheme INTEGER NOT NULL,"
-            "   iv BLOB NOT NULL,"
-            "   dataSize INTEGER NOT NULL,"
-            "   data BLOB NOT NULL,"
-            "   tag BLOB NOT NULL,"
-            "   idx INTEGER NOT NULL,"
-            "   FOREIGN KEY(idx) REFERENCES NAME_TABLE(idx) ON DELETE CASCADE,"
-            "   PRIMARY KEY(idx, dataType)"
-            ");"; // TODO: index and performance tests
 
     const char *DB_CMD_OBJECT_INSERT =
             "INSERT INTO OBJECT_TABLE("
@@ -87,11 +98,6 @@ namespace {
             " WHERE (dataType BETWEEN ?001 AND ?002) "
             " AND name=?101 and label=?102;";
 
-    const char *DB_CMD_KEY_CREATE =
-            "CREATE TABLE IF NOT EXISTS KEY_TABLE("
-            "   label TEXT PRIMARY KEY,"
-            "   key BLOB NOT NULL"
-            ");";
 
     const char *DB_CMD_KEY_INSERT =
             "INSERT INTO KEY_TABLE(label, key) VALUES (?, ?);";
@@ -101,26 +107,17 @@ namespace {
             "DELETE FROM KEY_TABLE WHERE label=?";
 
 
-    const char *DB_CMD_PERMISSION_CREATE =
-            "CREATE TABLE IF NOT EXISTS PERMISSION_TABLE("
-            "   permissionLabel TEXT NOT NULL,"
-            "   permissionMask INTEGER NOT NULL,"
-            "   idx INTEGER NOT NULL,"
-            "   FOREIGN KEY(idx) REFERENCES NAME_TABLE(idx) ON DELETE CASCADE,"
-            "   PRIMARY KEY(permissionLabel, idx)"
-            "); CREATE INDEX IF NOT EXISTS perm_index_idx ON PERMISSION_TABLE(idx);"; // based on ANALYZE and performance test result
-
     const char *DB_CMD_PERMISSION_SET = // SQLite does not support updating views
             "REPLACE INTO PERMISSION_TABLE(permissionLabel, permissionMask, idx) "
-            " VALUES (?001, ?002, (SELECT idx FROM NAME_TABLE WHERE name=?101 and label=?102));";
+            " VALUES (?104, ?105, (SELECT idx FROM NAME_TABLE WHERE name=?101 and label=?102));";
 
     const char *DB_CMD_PERMISSION_SELECT =
             "SELECT permissionMask FROM [join_name_permission_tables] "
-            " WHERE permissionLabel=?001 "
+            " WHERE permissionLabel=?104 "
             " AND name=?101 and label=?102;";
 
     const char *DB_CMD_PERMISSION_DELETE = // SQLite does not support updating views
-            "DELETE FROM PERMISSION_TABLE WHERE permissionLabel=?001 AND "
+            "DELETE FROM PERMISSION_TABLE WHERE permissionLabel=?104 AND "
             " idx=(SELECT idx FROM NAME_TABLE WHERE name=?101 and label=?102);";
 
 
@@ -133,26 +130,7 @@ namespace {
     const char *DB_CMD_NAME_SELECT_BY_TYPE_AND_PERMISSION =
             "SELECT label, name FROM [join_all_tables] "
             " WHERE dataType>=?001 AND dataType<=?002 "
-            " AND permissionLabel=?003 AND permissionMask&?004!=0 GROUP BY idx;";
-
-    const char *DB_CMD_CREATE_JOIN_NAME_OBJECT_VIEW =
-            "CREATE VIEW IF NOT EXISTS [join_name_object_tables] AS"
-            "   SELECT N.name, N.label, O.* FROM "
-            "       NAME_TABLE AS N "
-            "       JOIN OBJECT_TABLE AS O ON O.idx=N.idx;";
-
-    const char *DB_CMD_CREATE_JOIN_NAME_PERMISSION_VIEW =
-            "CREATE VIEW IF NOT EXISTS [join_name_permission_tables] AS"
-            "   SELECT N.name, N.label, P.permissionMask, P.permissionLabel FROM "
-            "       NAME_TABLE AS N "
-            "       JOIN PERMISSION_TABLE AS P ON P.idx=N.idx;";
-
-    const char *DB_CMD_CREATE_ALL_JOIN_VIEW =
-            "CREATE VIEW IF NOT EXISTS [join_all_tables] AS"
-            "   SELECT N.*, P.permissionLabel, P.permissionMask, O.dataType FROM "
-            "       NAME_TABLE AS N "
-            "       JOIN OBJECT_TABLE AS O ON O.idx=N.idx "
-            "       JOIN PERMISSION_TABLE AS P ON P.idx=N.idx;";
+            " AND permissionLabel=?104 AND permissionMask&?004!=0 GROUP BY idx;";
 }
 
 namespace CKM {
@@ -164,8 +142,8 @@ using namespace DB;
         Try {
             m_connection = new SqlConnection(path, SqlConnection::Flag::Option::CRW);
             m_connection->SetKey(rawPass);
-            m_connection->ExecCommand("VACUUM;");
             initDatabase();
+            m_connection->ExecCommand("VACUUM;");
         } Catch(SqlConnection::Exception::ConnectionBroken) {
             LogError("Couldn't connect to database: " << path);
             ReThrow(DBCrypto::Exception::InternalError);
@@ -176,7 +154,7 @@ using namespace DB;
             LogError("Couldn't initiate the database");
             ReThrow(DBCrypto::Exception::InternalError);
         } Catch(SqlConnection::Exception::InternalError) {
-            LogError("Couldn't create the database");
+            LogError("Couldn't intialize the database");
             ReThrow(DBCrypto::Exception::InternalError);
         }
     }
@@ -236,15 +214,116 @@ using namespace DB;
         }
     }
 
-    void DBCrypto::initDatabase() {
+    bool DBCrypto::getDBVersion(int & schemaVersion)
+    {
+        SchemaInfo SchemaInfo(this);
+        if(SchemaInfo.getVersionInfo(schemaVersion)) {
+            LogDebug("Current DB version: " << schemaVersion);
+            return true;
+        }
+        else
+        {
+            LogDebug("No DB version known or DB not present");
+
+            // special case: old CKM_TABLE exists
+            if(m_connection->CheckTableExist("CKM_TABLE")) {
+                schemaVersion = DB_VERSION_1;
+                return true;
+            }
+
+            // special case: new scheme exists, but no SCHEMA_INFO table present
+            else if(m_connection->CheckTableExist("NAME_TABLE")) {
+                schemaVersion = DB_VERSION_2;
+                return true;
+            }
+        }
+        // not recognized - proceed with an empty DBs
+        return false;
+    }
+
+    void DBCrypto::initDatabase()
+    {
+        // run migration if old database is present
+        int schemaVersion;
+        if( getDBVersion(schemaVersion)==false ||       // DB empty or corrupted
+            schemaVersion > DB_VERSION_CURRENT)         // or too new scheme
+        {
+            LogDebug("no database or database corrupted, initializing the DB");
+            resetDB();
+        }
+        else
+        {
+            // migration needed
+            LogDebug("DB migration from version " << schemaVersion << " to version " << DB_VERSION_CURRENT << " started.");
+            Transaction transaction(this);
+            for(int vi=schemaVersion; vi<DB_VERSION_CURRENT; vi++)
+            {
+                ScriptOptional script = getMigrationScript(vi);
+                if(!script)
+                {
+                    LogError("Error, script to migrate database from version: " << vi <<
+                             " to version: " << vi+1 << " not available, resetting the DB");
+                    resetDB();
+                    break;
+                }
+
+                LogInfo("migrating from version " << vi << " to version " << vi+1);
+                m_connection->ExecCommand((*script).c_str());
+            }
+            // update DB version info
+            SchemaInfo SchemaInfo(this);
+            SchemaInfo.setVersionInfo();
+            transaction.commit();
+        }
+    }
+
+    DBCrypto::ScriptOptional DBCrypto::getScript(const std::string &scriptName) const
+    {
+        std::string scriptPath = SCRIPTS_PATH + scriptName + std::string(".sql");
+        std::ifstream is(scriptPath);
+        if(is.fail()) {
+            LogError("Script " << scriptPath << " not found!");
+            return ScriptOptional();
+        }
+
+        std::istreambuf_iterator<char> begin(is),end;
+        return ScriptOptional(std::string(begin, end));
+    }
+    DBCrypto::ScriptOptional DBCrypto::getMigrationScript(int db_version) const
+    {
+        std::string scriptPath = std::string(SCRIPT_MIGRATE) + std::to_string(db_version);
+        return getScript(scriptPath);
+    }
+
+    void DBCrypto::createDBSchema() {
         Transaction transaction(this);
-        createTable(DB_CMD_NAME_CREATE, TABLE_NAME);
-        createTable(DB_CMD_OBJECT_CREATE, TABLE_OBJECT);
-        createTable(DB_CMD_KEY_CREATE, TABLE_KEY);
-        createTable(DB_CMD_PERMISSION_CREATE, TABLE_PERMISSION);
-        createView(DB_CMD_CREATE_ALL_JOIN_VIEW);
-        createView(DB_CMD_CREATE_JOIN_NAME_OBJECT_VIEW);
-        createView(DB_CMD_CREATE_JOIN_NAME_PERMISSION_VIEW);
+
+        ScriptOptional script = getScript(SCRIPT_CREATE_SCHEMA);
+        if(!script)
+        {
+            std::string errmsg = "Can not create the database schema: no initialization script";
+            LogError(errmsg);
+            ThrowMsg(Exception::InternalError, errmsg);
+        }
+
+        m_connection->ExecCommand((*script).c_str());
+        SchemaInfo SchemaInfo(this);
+        SchemaInfo.setVersionInfo();
+        transaction.commit();
+    }
+
+    void DBCrypto::resetDB() {
+        Transaction transaction(this);
+        ScriptOptional script = getScript(SCRIPT_DROP_ALL_ITEMS);
+        if(!script)
+        {
+            std::string errmsg = "Can not clear the database: no clearing script";
+            LogError(errmsg);
+            ThrowMsg(Exception::InternalError, errmsg);
+        }
+
+        m_connection->ExecCommand((*script).c_str());
+        createDBSchema();
         transaction.commit();
     }
 
@@ -477,7 +556,7 @@ using namespace DB;
                             m_connection->PrepareDataCommand(DB_CMD_NAME_SELECT_BY_TYPE_AND_PERMISSION);
             selectCommand->BindInteger(1, static_cast<int>(typeRangeStart));
             selectCommand->BindInteger(2, static_cast<int>(typeRangeStop));
-            selectCommand->BindString(3, smackLabel.c_str());
+            selectCommand->BindString(104, smackLabel.c_str());
             selectCommand->BindInteger(4, static_cast<int>(Permission::READ | Permission::REMOVE));
 
             while(selectCommand->Step()) {
@@ -589,6 +668,38 @@ using namespace DB;
                 "Couldn't set permissions for name " << name );
     }
 
+
+    void DBCrypto::SchemaInfo::setVersionInfo() {
+        SqlConnection::DataCommandUniquePtr insertContextCommand =
+                m_db->m_connection->PrepareDataCommand(DB_CMD_SCHEMA_SET);
+        insertContextCommand->BindString(101, DB_SCHEMA_VERSION_FIELD);
+        insertContextCommand->BindString(103, std::to_string(DB_VERSION_CURRENT).c_str());
+        insertContextCommand->Step();
+    }
+
+    bool DBCrypto::SchemaInfo::getVersionInfo(int & version) const
+    {
+        // Try..Catch mandatory here - we don't need to escalate the error
+        // if it happens - we just won't return the version, allowing CKM to work
+        Try {
+            SqlConnection::DataCommandUniquePtr selectCommand =
+                    m_db->m_connection->PrepareDataCommand(DB_CMD_SCHEMA_GET);
+            selectCommand->BindString(101, DB_SCHEMA_VERSION_FIELD);
+
+            if(selectCommand->Step()) {
+                version = static_cast<int>(atoi(selectCommand->GetColumnString(1).c_str()));
+                return true;
+            }
+        } Catch (SqlConnection::Exception::InvalidColumn) {
+            LogError("Select statement invalid column error");
+        } Catch (SqlConnection::Exception::SyntaxError) {
+            LogError("Couldn't prepare select statement");
+        } Catch (SqlConnection::Exception::InternalError) {
+            LogError("Couldn't execute select statement");
+        }
+        return false;
+    }
+
     void DBCrypto::PermissionTable::setPermission(
             const Name &name,
             const Label& ownerLabel,
@@ -600,7 +711,7 @@ using namespace DB;
             // clear permissions
             SqlConnection::DataCommandUniquePtr deletePermissionCommand =
                 m_connection->PrepareDataCommand(DB_CMD_PERMISSION_DELETE);
-            deletePermissionCommand->BindString(1, accessorLabel.c_str());
+            deletePermissionCommand->BindString(104, accessorLabel.c_str());
             deletePermissionCommand->BindString(101, name.c_str());
             deletePermissionCommand->BindString(102, ownerLabel.c_str());
             deletePermissionCommand->Step();
@@ -610,8 +721,8 @@ using namespace DB;
             // add new permissions
             SqlConnection::DataCommandUniquePtr setPermissionCommand =
                 m_connection->PrepareDataCommand(DB_CMD_PERMISSION_SET);
-            setPermissionCommand->BindString(1, accessorLabel.c_str());
-            setPermissionCommand->BindInteger(2, static_cast<int>(permissionMask));
+            setPermissionCommand->BindString(104, accessorLabel.c_str());
+            setPermissionCommand->BindInteger(105, static_cast<int>(permissionMask));
             setPermissionCommand->BindString(101, name.c_str());
             setPermissionCommand->BindString(102, ownerLabel.c_str());
             setPermissionCommand->Step();
@@ -625,7 +736,7 @@ using namespace DB;
     {
         SqlConnection::DataCommandUniquePtr selectCommand =
                 m_connection->PrepareDataCommand(DB_CMD_PERMISSION_SELECT);
-        selectCommand->BindString(1, accessorLabel.c_str());
+        selectCommand->BindString(104, accessorLabel.c_str());
 
         // name table reference
         selectCommand->BindString(101, name.c_str());
index d5f75ae..9bd4a99 100644 (file)
@@ -53,7 +53,7 @@ namespace CKM {
                 m_connection(NULL),
                 m_inUserTransaction(false)
               {};
-            //user name instead of path?
+            // user name instead of path?
             DBCrypto(const std::string &path, const RawBuffer &rawPass);
             DBCrypto(const DBCrypto &other) = delete;
             DBCrypto(DBCrypto &&other);
@@ -209,7 +209,21 @@ namespace CKM {
             DB::SqlConnection* m_connection;
             bool m_inUserTransaction;
 
+            void resetDB();
             void initDatabase();
+            void createDBSchema();
+            /**
+             * return current database version
+             *
+             * @param[out] schemaVersion    if success, will contain DB schema version code
+             *
+             * @return false on DB empty or corrupted, true if information read
+             */
+            bool getDBVersion(int & schemaVersion);
+            typedef boost::optional<std::string> ScriptOptional;
+            ScriptOptional getScript(const std::string &scriptName) const;
+            ScriptOptional getMigrationScript(int db_version) const;
+
             DBRow getRow(
                     const DB::SqlConnection::DataCommandUniquePtr &selectCommand) const;
 
@@ -220,6 +234,18 @@ namespace CKM {
             void createView(
                     const char* create_cmd);
 
+            class SchemaInfo {
+            public:
+                explicit SchemaInfo(const DBCrypto *db) : m_db(db) {}
+
+                void        setVersionInfo();
+                bool        getVersionInfo(int & version) const;
+
+            private:
+                const DBCrypto *m_db;
+            };
+
+        public:
             class NameTable {
             public:
                 explicit NameTable(DB::SqlConnection* connection) : m_connection(connection) {}
index d82c434..20e1a43 100644 (file)
@@ -2,6 +2,7 @@
 #include <db-crypto.h>
 #include <ckm/ckm-error.h>
 #include <DBFixture.h>
+#include <fstream>
 
 using namespace CKM;
 using namespace std::chrono;
@@ -9,10 +10,28 @@ using namespace std::chrono;
 
 DBFixture::DBFixture()
 {
+    BOOST_CHECK(unlink(m_crypto_db_fname) == 0 || errno == ENOENT);
+    init();
+}
+DBFixture::DBFixture(const char *db_fname)
+{
+    BOOST_CHECK(unlink(m_crypto_db_fname) == 0 || errno == ENOENT);
+
+    // copy file
+    std::ifstream f1(db_fname, std::fstream::binary);
+    std::ofstream f2(m_crypto_db_fname, std::fstream::trunc|std::fstream::binary);
+    f2 << f1.rdbuf();
+    f2.close();
+    f1.close();
+
+    init();
+}
+
+void DBFixture::init()
+{
     high_resolution_clock::time_point srand_feed = high_resolution_clock::now();
     srand(srand_feed.time_since_epoch().count());
 
-    BOOST_CHECK(unlink(m_crypto_db_fname) == 0 || errno == ENOENT);
     BOOST_REQUIRE_NO_THROW(m_db = DBCrypto(m_crypto_db_fname, defaultPass));
 }
 
@@ -51,7 +70,7 @@ void DBFixture::generate_label(unsigned int id, Label & output)
     output = ss.str();
 }
 
-void DBFixture::generate_perf_DB(unsigned int num_name, unsigned int num_label)
+void DBFixture::generate_perf_DB(unsigned int num_name, unsigned int num_elements)
 {
     // to speed up data creation - cache the row
     DBRow rowPattern = create_default_row(DBDataType::BINARY_DATA);
@@ -62,7 +81,7 @@ void DBFixture::generate_perf_DB(unsigned int num_name, unsigned int num_label)
     for(unsigned int i=0; i<num_name; i++)
     {
         generate_name(i, rowPattern.name);
-        generate_label(i/num_label, rowPattern.ownerLabel);
+        generate_label(i/num_elements, rowPattern.ownerLabel);
 
         BOOST_REQUIRE_NO_THROW(m_db.saveDBRow(rowPattern));
     }
index deacabf..76c8fe5 100644 (file)
@@ -9,6 +9,7 @@ class DBFixture
 {
     public:
         DBFixture();
+        DBFixture(const char *db_fname);
 
         constexpr static const char* m_default_name = "name";
         constexpr static const char* m_default_label = "label";
@@ -38,6 +39,7 @@ class DBFixture
 
         CKM::DBCrypto    m_db;
     private:
+        void    init();
         double  performance_get_time_elapsed_ms();
 
         constexpr static const char* m_crypto_db_fname = "/tmp/testme.db";
index 69f697b..1256720 100644 (file)
@@ -199,5 +199,112 @@ BOOST_AUTO_TEST_CASE(DBperfGetAliasList)
     }
     performance_stop(c_test_retries/num_labels);
 }
+BOOST_AUTO_TEST_SUITE_END()
+
+
+BOOST_AUTO_TEST_SUITE(DBCRYPTO_MIGRATION_TEST)
+namespace
+{
+const unsigned migration_names = 16107;
+const unsigned migration_labels = 273;
+const unsigned migration_reference_label_idx = 0;
+const unsigned migration_accessed_element_idx = 7;
+
+void verifyDBisValid(DBFixture & fixture)
+{
+    /**
+     * there are (migration_labels), each having (migration_names)/(migration_labels) entries.
+     * reference label (migration_reference_label_idx) exists such that it has access to
+     * all others' label element with index (migration_accessed_element_idx).
+     *
+     * Example:
+     * - migration_label_63 has access to all items owned by migration_label_63,
+     *   which gives (migration_names)/(migration_labels) entries.
+     *
+     * - migration_label_0 (0 is the reference label) has access to all items
+     *   owned by migration_label_0 and all others' label element index 7,
+     *   which gives (migration_names)/(migration_labels)  + (migration_labels-1) entries.
+     *
+     */
+    Label reference_label;
+    fixture.generate_label(migration_reference_label_idx, reference_label);
+
+    // check number of elements accessible to the reference label
+    LabelNameVector ret_list;
+    BOOST_REQUIRE_NO_THROW(fixture.m_db.listNames(reference_label, ret_list, DBDataType::BINARY_DATA));
+    BOOST_REQUIRE((migration_names/migration_labels)/*own items*/ + (migration_labels-1)/*other labels'*/ == ret_list.size());
+    ret_list.clear();
+
+    // check number of elements accessible to the other labels
+    for(unsigned int l=0; l<migration_labels; l++)
+    {
+        // bypass the reference owner label
+        if(l == migration_reference_label_idx)
+            continue;
+
+        Label current_label;
+        fixture.generate_label(l, current_label);
+        BOOST_REQUIRE_NO_THROW(fixture.m_db.listNames(current_label, ret_list, DBDataType::BINARY_DATA));
+        BOOST_REQUIRE((migration_names/migration_labels) == ret_list.size());
+        for(auto it: ret_list)
+            BOOST_REQUIRE(it.first == current_label);
+        ret_list.clear();
+    }
+}
+struct DBVer1Migration : public DBFixture
+{
+    DBVer1Migration() : DBFixture("/usr/share/ckm-db-test/testme_ver1.db")
+    {}
+};
+
+struct DBVer2Migration : public DBFixture
+{
+    DBVer2Migration() : DBFixture("/usr/share/ckm-db-test/testme_ver2.db")
+    {}
+};
+}
+
+BOOST_AUTO_TEST_CASE(DBMigrationDBVer1)
+{
+    DBVer1Migration DBver1;
+    verifyDBisValid(DBver1);
+}
+
+BOOST_AUTO_TEST_CASE(DBMigrationDBVer2)
+{
+    DBVer2Migration DBver2;
+    verifyDBisValid(DBver2);
+}
+
+BOOST_AUTO_TEST_CASE(DBMigrationDBCurrent)
+{
+    DBFixture currentDB;
+
+    // prepare data using current DB mechanism
+    Label reference_label;
+    currentDB.generate_label(migration_reference_label_idx, reference_label);
+    {
+        currentDB.generate_perf_DB(migration_names, migration_names/migration_labels);
+
+        // only the reference label has access to the other labels element <migration_accessed_element_idx>
+        for(unsigned int l=0; l<migration_labels; l++)
+        {
+            // bypass the reference owner label
+            if(l == migration_reference_label_idx)
+                continue;
+
+            unsigned element_index = migration_accessed_element_idx + l*migration_names/migration_labels;
+
+            // add permission
+            Name accessed_name;
+            currentDB.generate_name(element_index, accessed_name);
+            Label current_label;
+            currentDB.generate_label(l, current_label);
+            currentDB.add_permission(accessed_name, current_label, reference_label);
+        }
+    }
+
+    verifyDBisValid(currentDB);
+}
 
 BOOST_AUTO_TEST_SUITE_END()
diff --git a/tests/testme_ver1.db b/tests/testme_ver1.db
new file mode 100644 (file)
index 0000000..96ebac1
Binary files /dev/null and b/tests/testme_ver1.db differ
diff --git a/tests/testme_ver2.db b/tests/testme_ver2.db
new file mode 100644 (file)
index 0000000..3cfa39d
Binary files /dev/null and b/tests/testme_ver2.db differ