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