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