Aktualizr
C++ SOTA Client
sqlstorage_base.cc
1 #include "sqlstorage_base.h"
2 #include "storage_exception.h"
3 
4 #include <sys/stat.h>
5 
6 #include "utilities/utils.h"
7 
8 boost::filesystem::path SQLStorageBase::dbPath() const { return sqldb_path_; }
9 
10 StorageLock::StorageLock(boost::filesystem::path path) : lock_path(std::move(path)) {
11  {
12  std::fstream fs;
13  fs.open(lock_path.c_str(), std::fstream::in | std::fstream::out | std::fstream::app);
14  }
15  fl_ = lock_path.c_str();
16  if (!fl_.try_lock()) {
18  }
19 }
20 
21 StorageLock::~StorageLock() {
22  try {
23  if (!lock_path.empty()) {
24  fl_.unlock();
25  std::remove(lock_path.c_str());
26  }
27  } catch (std::exception& e) {
28  }
29 }
30 
31 SQLStorageBase::SQLStorageBase(boost::filesystem::path sqldb_path, bool readonly,
32  std::vector<std::string> schema_migrations,
33  std::vector<std::string> schema_rollback_migrations, std::string current_schema,
34  int current_schema_version)
35  : sqldb_path_(std::move(sqldb_path)),
36  readonly_(readonly),
37  mutex_(new std::mutex()),
38  schema_migrations_(std::move(schema_migrations)),
39  schema_rollback_migrations_(std::move(schema_rollback_migrations)),
40  current_schema_(std::move(current_schema)),
41  current_schema_version_(current_schema_version) {
42  boost::filesystem::path db_parent_path = dbPath().parent_path();
43  if (!boost::filesystem::is_directory(db_parent_path)) {
44  Utils::createDirectories(db_parent_path, S_IRWXU);
45  } else {
46  struct stat st {};
47  if (stat(db_parent_path.c_str(), &st) < 0) {
48  throw StorageException(std::string("Could not check storage directory permissions: ") + std::strerror(errno));
49  }
50  if ((st.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
51  throw StorageException("Storage directory has unsafe permissions");
52  }
53  if ((st.st_mode & (S_IRGRP | S_IROTH)) != 0) {
54  // Remove read permissions for group and others
55  if (chmod(db_parent_path.c_str(), S_IRWXU) < 0) {
56  throw StorageException("Storage directory has unsafe permissions");
57  }
58  }
59  }
60 
61  if (!readonly) {
62  try {
63  lock = StorageLock(db_parent_path / "storage.lock");
64  } catch (StorageLock::locked_exception& e) {
65  LOG_WARNING << "\033[31m"
66  << "Storage in " << db_parent_path
67  << " is already in use, running several instances concurrently may result in data corruption!"
68  << "\033[0m";
69  }
70  }
71 
72  if (!dbMigrate()) {
73  throw StorageException("SQLite database migration failed");
74  }
75 }
76 
77 SQLite3Guard SQLStorageBase::dbConnection() const {
78  SQLite3Guard db(dbPath(), readonly_, mutex_);
79  if (db.get_rc() != SQLITE_OK) {
80  throw SQLInternalException(std::string("Can't open database: ") + db.errmsg());
81  }
82  return db;
83 }
84 
85 std::string SQLStorageBase::getTableSchemaFromDb(const std::string& tablename) {
86  SQLite3Guard db = dbConnection();
87 
88  auto statement = db.prepareStatement<std::string>(
89  "SELECT sql FROM sqlite_master WHERE type='table' AND tbl_name=? LIMIT 1;", tablename);
90 
91  if (statement.step() != SQLITE_ROW) {
92  LOG_ERROR << "Can't get schema of " << tablename << ": " << db.errmsg();
93  return "";
94  }
95 
96  auto schema = statement.get_result_col_str(0);
97  if (schema == boost::none) {
98  return "";
99  }
100 
101  return schema.value() + ";";
102 }
103 
104 bool SQLStorageBase::dbInsertBackMigrations(SQLite3Guard& db, int version_latest) {
105  if (schema_rollback_migrations_.empty()) {
106  LOG_TRACE << "No backward migrations defined";
107  return true;
108  }
109 
110  if (schema_rollback_migrations_.size() < static_cast<size_t>(version_latest) + 1) {
111  LOG_ERROR << "Backward migrations from " << schema_rollback_migrations_.size() << " to " << version_latest
112  << " are missing";
113  return false;
114  }
115 
116  for (int k = 1; k <= version_latest; k++) {
117  if (schema_rollback_migrations_.at(static_cast<size_t>(k)).empty()) {
118  continue;
119  }
120  auto statement = db.prepareStatement("INSERT OR REPLACE INTO rollback_migrations VALUES (?,?);", k,
121  schema_rollback_migrations_.at(static_cast<uint32_t>(k)));
122  if (statement.step() != SQLITE_DONE) {
123  LOG_ERROR << "Can't insert rollback migration script: " << db.errmsg();
124  return false;
125  }
126  }
127 
128  return true;
129 }
130 
131 bool SQLStorageBase::dbMigrateForward(int version_from, int version_to) {
132  if (version_to <= 0) {
133  version_to = current_schema_version_;
134  }
135 
136  LOG_INFO << "Migrating DB from version " << version_from << " to version " << version_to;
137 
138  SQLite3Guard db = dbConnection();
139 
140  try {
141  db.beginTransaction();
142  } catch (const SQLException& e) {
143  return false;
144  }
145 
146  for (int32_t k = version_from + 1; k <= version_to; k++) {
147  auto result_code = db.exec(schema_migrations_.at(static_cast<size_t>(k)), nullptr, nullptr);
148  if (result_code != SQLITE_OK) {
149  LOG_ERROR << "Can't migrate DB from version " << (k - 1) << " to version " << k << ": " << db.errmsg();
150  return false;
151  }
152  }
153 
154  if (!dbInsertBackMigrations(db, version_to)) {
155  return false;
156  }
157 
158  db.commitTransaction();
159 
160  return true;
161 }
162 
163 bool SQLStorageBase::dbMigrateBackward(int version_from, int version_to) {
164  if (version_to <= 0) {
165  version_to = current_schema_version_;
166  }
167 
168  LOG_INFO << "Migrating DB backward from version " << version_from << " to version " << version_to;
169 
170  SQLite3Guard db = dbConnection();
171  for (int ver = version_from; ver > version_to; --ver) {
172  std::string migration;
173  {
174  // make sure the statement is destroyed before the next database operation
175  auto statement = db.prepareStatement("SELECT migration FROM rollback_migrations WHERE version_from=?;", ver);
176  if (statement.step() != SQLITE_ROW) {
177  LOG_ERROR << "Can't extract migration script: " << db.errmsg();
178  return false;
179  }
180  migration = *(statement.get_result_col_str(0));
181  }
182 
183  if (db.exec(migration, nullptr, nullptr) != SQLITE_OK) {
184  LOG_ERROR << "Can't migrate db from version " << (ver) << " to version " << ver - 1 << ": " << db.errmsg();
185  return false;
186  }
187  if (db.exec(std::string("DELETE FROM rollback_migrations WHERE version_from=") + std::to_string(ver) + ";", nullptr,
188  nullptr) != SQLITE_OK) {
189  LOG_ERROR << "Can't clear old migration script: " << db.errmsg();
190  return false;
191  }
192  }
193  return true;
194 }
195 
196 bool SQLStorageBase::dbMigrate() {
197  DbVersion schema_version = getVersion();
198 
199  if (schema_version == DbVersion::kInvalid) {
200  LOG_ERROR << "Sqlite database file is invalid.";
201  return false;
202  }
203 
204  auto schema_num_version = static_cast<int32_t>(schema_version);
205  if (schema_num_version == current_schema_version_) {
206  return true;
207  }
208 
209  if (readonly_) {
210  LOG_ERROR << "Database is opened in readonly mode and cannot be migrated to latest version";
211  return false;
212  }
213 
214  if (schema_version == DbVersion::kEmpty) {
215  LOG_INFO << "Bootstraping DB to version " << current_schema_version_;
216  SQLite3Guard db = dbConnection();
217 
218  try {
219  db.beginTransaction();
220  } catch (const SQLException& e) {
221  return false;
222  }
223 
224  auto result_code = db.exec(current_schema_, nullptr, nullptr);
225  if (result_code != SQLITE_OK) {
226  LOG_ERROR << "Can't bootstrap DB to version " << current_schema_version_ << ": " << db.errmsg();
227  return false;
228  }
229 
230  if (!dbInsertBackMigrations(db, current_schema_version_)) {
231  return false;
232  }
233 
234  db.commitTransaction();
235  } else if (schema_num_version > current_schema_version_) {
236  dbMigrateBackward(schema_num_version);
237  } else {
238  dbMigrateForward(schema_num_version);
239  }
240 
241  return true;
242 }
243 
244 DbVersion SQLStorageBase::getVersion() {
245  SQLite3Guard db = dbConnection();
246 
247  try {
248  auto statement = db.prepareStatement("SELECT count(*) FROM sqlite_master WHERE type='table';");
249  if (statement.step() != SQLITE_ROW) {
250  LOG_ERROR << "Can't get tables count: " << db.errmsg();
251  return DbVersion::kInvalid;
252  }
253 
254  if (statement.get_result_col_int(0) == 0) {
255  return DbVersion::kEmpty;
256  }
257 
258  statement = db.prepareStatement("SELECT version FROM version LIMIT 1;");
259 
260  if (statement.step() != SQLITE_ROW) {
261  LOG_ERROR << "Can't get database version: " << db.errmsg();
262  return DbVersion::kInvalid;
263  }
264 
265  try {
266  return DbVersion(statement.get_result_col_int(0));
267  } catch (const std::exception&) {
268  return DbVersion::kInvalid;
269  }
270  } catch (const SQLException&) {
271  return DbVersion::kInvalid;
272  }
273 }
StorageLock
Definition: sqlstorage_base.h:13
StorageException
Definition: storage_exception.h:4
SQLInternalException
Definition: sql_utils.h:28
StorageLock::locked_exception
Definition: sqlstorage_base.h:23
SQLite3Guard
Definition: sql_utils.h:131
SQLException
Definition: sql_utils.h:22