]>
Commit | Line | Data |
---|---|---|
33d55ab4 MT |
1 | /*############################################################################# |
2 | # # | |
3 | # Pakfire - The IPFire package management system # | |
4 | # Copyright (C) 2021 Pakfire development team # | |
5 | # # | |
6 | # This program is free software: you can redistribute it and/or modify # | |
7 | # it under the terms of the GNU General Public License as published by # | |
8 | # the Free Software Foundation, either version 3 of the License, or # | |
9 | # (at your option) any later version. # | |
10 | # # | |
11 | # This program is distributed in the hope that it will be useful, # | |
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # | |
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # | |
14 | # GNU General Public License for more details. # | |
15 | # # | |
16 | # You should have received a copy of the GNU General Public License # | |
17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. # | |
18 | # # | |
19 | #############################################################################*/ | |
20 | ||
21 | #include <errno.h> | |
22 | #include <stdlib.h> | |
23 | ||
26affd69 MT |
24 | #include <sqlite3.h> |
25 | ||
33d55ab4 | 26 | #include <pakfire/db.h> |
33d55ab4 | 27 | #include <pakfire/logging.h> |
18bf891d MT |
28 | #include <pakfire/pakfire.h> |
29 | #include <pakfire/private.h> | |
33d55ab4 MT |
30 | #include <pakfire/types.h> |
31 | #include <pakfire/util.h> | |
32 | ||
0cb487ff MT |
33 | #define DATABASE_PATH PAKFIRE_PRIVATE_DIR "/packages.db" |
34 | ||
c745fb2d MT |
35 | #define CURRENT_SCHEMA 7 |
36 | #define SCHEMA_MIN_SUP 7 | |
37 | ||
33d55ab4 MT |
38 | struct pakfire_db { |
39 | Pakfire pakfire; | |
40 | int nrefs; | |
77a4b3a3 MT |
41 | |
42 | int mode; | |
26affd69 MT |
43 | |
44 | sqlite3* handle; | |
c745fb2d | 45 | int schema; |
33d55ab4 MT |
46 | }; |
47 | ||
9c5938ea MT |
48 | static void logging_callback(void* data, int r, const char* msg) { |
49 | Pakfire pakfire = (Pakfire)data; | |
50 | ||
51 | ERROR(pakfire, "Database Error: %s: %s\n", | |
52 | sqlite3_errstr(r), msg); | |
53 | } | |
54 | ||
0076c50d MT |
55 | static int pakfire_db_execute(struct pakfire_db* db, const char* stmt) { |
56 | int r; | |
c745fb2d MT |
57 | |
58 | DEBUG(db->pakfire, "Executing database query: %s\n", stmt); | |
0076c50d MT |
59 | |
60 | do { | |
c745fb2d | 61 | r = sqlite3_exec(db->handle, stmt, NULL, NULL, NULL); |
0076c50d MT |
62 | } while (r == SQLITE_BUSY); |
63 | ||
c745fb2d MT |
64 | // Log any errors |
65 | if (r) { | |
66 | ERROR(db->pakfire, "Database query failed: %s\n", sqlite3_errmsg(db->handle)); | |
67 | } | |
68 | ||
25753290 MT |
69 | return r; |
70 | } | |
71 | ||
c745fb2d MT |
72 | static int pakfire_db_begin_transaction(struct pakfire_db* db) { |
73 | return pakfire_db_execute(db, "BEGIN TRANSACTION"); | |
74 | } | |
75 | ||
76 | static int pakfire_db_commit(struct pakfire_db* db) { | |
77 | return pakfire_db_execute(db, "COMMIT"); | |
78 | } | |
79 | ||
80 | static int pakfire_db_rollback(struct pakfire_db* db) { | |
81 | return pakfire_db_execute(db, "ROLLBACK"); | |
82 | } | |
83 | ||
25753290 MT |
84 | /* |
85 | This function performs any fast optimization and tries to truncate the WAL log file | |
86 | to keep the database as compact as possible on disk. | |
87 | */ | |
88 | static void pakfire_db_optimize(struct pakfire_db* db) { | |
89 | pakfire_db_execute(db, "PRAGMA optmize"); | |
90 | pakfire_db_execute(db, "PRAGMA wal_checkpoint = TRUNCATE"); | |
0076c50d MT |
91 | } |
92 | ||
26affd69 MT |
93 | static void pakfire_db_free(struct pakfire_db* db) { |
94 | DEBUG(db->pakfire, "Releasing database at %p\n", db); | |
95 | ||
26affd69 | 96 | if (db->handle) { |
25753290 MT |
97 | // Optimize the database before it is being closed |
98 | pakfire_db_optimize(db); | |
99 | ||
100 | // Close database handle | |
26affd69 MT |
101 | int r = sqlite3_close(db->handle); |
102 | if (r != SQLITE_OK) { | |
103 | ERROR(db->pakfire, "Could not close database handle: %s\n", | |
104 | sqlite3_errmsg(db->handle)); | |
105 | } | |
106 | } | |
107 | ||
108 | pakfire_unref(db->pakfire); | |
109 | ||
110 | pakfire_free(db); | |
111 | } | |
112 | ||
c745fb2d MT |
113 | static sqlite3_value* pakfire_db_get(struct pakfire_db* db, const char* key) { |
114 | sqlite3_stmt* stmt = NULL; | |
115 | sqlite3_value* val = NULL; | |
116 | int r; | |
117 | ||
118 | const char* sql = "SELECT val FROM settings WHERE key = ?"; | |
119 | ||
120 | // Prepare the statement | |
121 | r = sqlite3_prepare_v2(db->handle, sql, strlen(sql), &stmt, NULL); | |
122 | if (r != SQLITE_OK) { | |
123 | //ERROR(db->pakfire, "Could not prepare SQL statement: %s: %s\n", | |
124 | // sql, sqlite3_errmsg(db->handle)); | |
125 | return NULL; | |
126 | } | |
127 | ||
128 | // Bind key | |
129 | r = sqlite3_bind_text(stmt, 1, key, strlen(key), NULL); | |
130 | if (r != SQLITE_OK) { | |
131 | ERROR(db->pakfire, "Could not bind key: %s\n", sqlite3_errmsg(db->handle)); | |
132 | goto ERROR; | |
133 | } | |
134 | ||
135 | // Execute the statement | |
136 | do { | |
137 | r = sqlite3_step(stmt); | |
138 | } while (r == SQLITE_BUSY); | |
139 | ||
140 | // Read value | |
141 | val = sqlite3_column_value(stmt, 1); | |
142 | if (!val) { | |
143 | ERROR(db->pakfire, "Could not read value\n"); | |
144 | goto ERROR; | |
145 | } | |
146 | ||
147 | // Copy value onto the heap | |
148 | val = sqlite3_value_dup(val); | |
149 | ||
150 | ERROR: | |
151 | if (stmt) | |
152 | sqlite3_finalize(stmt); | |
153 | ||
154 | return val; | |
155 | } | |
156 | ||
157 | static int pakfire_db_set_int(struct pakfire_db* db, const char* key, int val) { | |
158 | sqlite3_stmt* stmt = NULL; | |
159 | int r; | |
160 | ||
161 | const char* sql = "INSERT INTO settings(key, val) VALUES(?, ?) \ | |
162 | ON CONFLICT (key) DO UPDATE SET val = excluded.val"; | |
163 | ||
164 | // Prepare statement | |
165 | r = sqlite3_prepare_v2(db->handle, sql, strlen(sql), &stmt, NULL); | |
166 | if (r != SQLITE_OK) { | |
167 | ERROR(db->pakfire, "Could not prepare SQL statement: %s: %s\n", | |
168 | sql, sqlite3_errmsg(db->handle)); | |
169 | return 1; | |
170 | } | |
171 | ||
172 | // Bind key | |
173 | r = sqlite3_bind_text(stmt, 1, key, strlen(key), NULL); | |
174 | if (r != SQLITE_OK) { | |
175 | ERROR(db->pakfire, "Could not bind key: %s\n", sqlite3_errmsg(db->handle)); | |
176 | goto ERROR; | |
177 | } | |
178 | ||
179 | // Bind val | |
180 | r = sqlite3_bind_int64(stmt, 2, val); | |
181 | if (r != SQLITE_OK) { | |
182 | ERROR(db->pakfire, "Could not bind val: %s\n", sqlite3_errmsg(db->handle)); | |
183 | goto ERROR; | |
184 | } | |
185 | ||
186 | // Execute the statement | |
187 | do { | |
188 | r = sqlite3_step(stmt); | |
189 | } while (r == SQLITE_BUSY); | |
190 | ||
191 | // Set return code | |
192 | r = (r == SQLITE_OK); | |
193 | ||
194 | ERROR: | |
195 | if (stmt) | |
196 | sqlite3_finalize(stmt); | |
197 | ||
198 | return r; | |
199 | } | |
200 | ||
201 | static int pakfire_db_get_schema(struct pakfire_db* db) { | |
202 | sqlite3_value* value = pakfire_db_get(db, "schema"); | |
203 | if (!value) | |
204 | return 0; | |
205 | ||
206 | int schema = sqlite3_value_int64(value); | |
207 | sqlite3_value_free(value); | |
208 | ||
209 | DEBUG(db->pakfire, "Database has schema version %d\n", schema); | |
210 | ||
211 | return schema; | |
212 | } | |
213 | ||
214 | static int pakfire_db_create_schema(struct pakfire_db* db) { | |
215 | int r; | |
216 | ||
217 | // Create settings table | |
218 | r = pakfire_db_execute(db, "CREATE TABLE IF NOT EXISTS settings(key TEXT, val TEXT)"); | |
219 | if (r) | |
220 | return 1; | |
221 | ||
222 | // settings: Add a unique index on key | |
223 | r = pakfire_db_execute(db, "CREATE UNIQUE INDEX IF NOT EXISTS settings_key ON settings(key)"); | |
224 | if (r) | |
225 | return 1; | |
226 | ||
227 | return 0; | |
228 | } | |
229 | ||
230 | static int pakfire_db_migrate_schema(struct pakfire_db* db) { | |
231 | int r; | |
232 | ||
233 | while (db->schema < CURRENT_SCHEMA) { | |
234 | // Begin a new transaction | |
235 | r = pakfire_db_begin_transaction(db); | |
236 | if (r) | |
237 | goto ROLLBACK; | |
238 | ||
239 | switch (db->schema) { | |
240 | // No schema exists | |
241 | case 0: | |
242 | r = pakfire_db_create_schema(db); | |
243 | if (r) | |
244 | goto ROLLBACK; | |
245 | ||
246 | db->schema = CURRENT_SCHEMA; | |
247 | break; | |
248 | ||
249 | default: | |
250 | ERROR(db->pakfire, "Cannot migrate database from schema %d\n", db->schema); | |
251 | goto ROLLBACK; | |
252 | } | |
253 | ||
254 | // Update the schema version | |
255 | r = pakfire_db_set_int(db, "schema", CURRENT_SCHEMA); | |
256 | if (r) | |
257 | goto ROLLBACK; | |
258 | ||
259 | // All done, commit! | |
260 | r = pakfire_db_commit(db); | |
261 | if (r) | |
262 | goto ROLLBACK; | |
263 | } | |
264 | ||
265 | return 0; | |
266 | ||
267 | ROLLBACK: | |
268 | pakfire_db_rollback(db); | |
269 | ||
270 | return 1; | |
271 | } | |
272 | ||
9c5938ea | 273 | static int pakfire_db_setup(struct pakfire_db* db) { |
25753290 MT |
274 | int r; |
275 | ||
9c5938ea MT |
276 | // Setup logging |
277 | sqlite3_config(SQLITE_CONFIG_LOG, logging_callback, db->pakfire); | |
278 | ||
0076c50d MT |
279 | // Make LIKE case-sensitive |
280 | pakfire_db_execute(db, "PRAGMA case_sensitive_like = ON"); | |
281 | ||
c745fb2d MT |
282 | // Fetch the current schema |
283 | db->schema = pakfire_db_get_schema(db); | |
284 | ||
285 | // Check if the schema is recent enough | |
286 | if (db->schema > 0 && db->schema < SCHEMA_MIN_SUP) { | |
287 | ERROR(db->pakfire, "Database schema %d is not supported by this version of Pakfire\n", | |
288 | db->schema); | |
289 | return 1; | |
290 | } | |
291 | ||
9c5938ea MT |
292 | // Done when not in read-write mode |
293 | if (db->mode != PAKFIRE_DB_READWRITE) | |
294 | return 0; | |
295 | ||
0076c50d MT |
296 | // Disable secure delete |
297 | pakfire_db_execute(db, "PRAGMA secure_delete = OFF"); | |
298 | ||
25753290 MT |
299 | // Set database journal to WAL |
300 | r = pakfire_db_execute(db, "PRAGMA journal_mode = WAL"); | |
301 | if (r != SQLITE_OK) { | |
302 | ERROR(db->pakfire, "Could not set journal mode to WAL: %s\n", | |
303 | sqlite3_errmsg(db->handle)); | |
304 | return 1; | |
305 | } | |
306 | ||
307 | // Disable autocheckpoint | |
308 | r = sqlite3_wal_autocheckpoint(db->handle, 0); | |
309 | if (r != SQLITE_OK) { | |
310 | ERROR(db->pakfire, "Could not disable autocheckpoint: %s\n", | |
311 | sqlite3_errmsg(db->handle)); | |
312 | return 1; | |
313 | } | |
314 | ||
c745fb2d MT |
315 | // Create or migrate schema |
316 | r = pakfire_db_migrate_schema(db); | |
317 | if (r) | |
318 | return r; | |
9c5938ea MT |
319 | |
320 | return 0; | |
321 | } | |
322 | ||
77a4b3a3 | 323 | PAKFIRE_EXPORT int pakfire_db_open(struct pakfire_db** db, Pakfire pakfire, int flags) { |
26affd69 MT |
324 | int r = 1; |
325 | ||
33d55ab4 MT |
326 | struct pakfire_db* o = pakfire_calloc(1, sizeof(*o)); |
327 | if (!o) | |
328 | return -ENOMEM; | |
329 | ||
330 | DEBUG(pakfire, "Allocated database at %p\n", o); | |
331 | ||
332 | o->pakfire = pakfire_ref(pakfire); | |
333 | o->nrefs = 1; | |
334 | ||
26affd69 MT |
335 | int sqlite3_flags = 0; |
336 | ||
337 | // Store mode & forward it to sqlite3 | |
338 | if (flags & PAKFIRE_DB_READWRITE) { | |
77a4b3a3 | 339 | o->mode = PAKFIRE_DB_READWRITE; |
26affd69 MT |
340 | sqlite3_flags |= SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; |
341 | } else { | |
77a4b3a3 | 342 | o->mode = PAKFIRE_DB_READONLY; |
26affd69 MT |
343 | sqlite3_flags |= SQLITE_OPEN_READONLY; |
344 | } | |
345 | ||
346 | // Make the filename | |
347 | char* path = pakfire_make_path(o->pakfire, DATABASE_PATH); | |
348 | if (!path) | |
349 | goto END; | |
350 | ||
351 | // Try to open the sqlite3 database file | |
352 | r = sqlite3_open_v2(path, &o->handle, sqlite3_flags, NULL); | |
353 | if (r != SQLITE_OK) { | |
354 | ERROR(pakfire, "Could not open database %s: %s\n", | |
355 | path, sqlite3_errmsg(o->handle)); | |
356 | ||
357 | r = 1; | |
358 | goto END; | |
359 | } | |
77a4b3a3 | 360 | |
9c5938ea MT |
361 | // Setup the database |
362 | r = pakfire_db_setup(o); | |
363 | if (r) | |
364 | goto END; | |
365 | ||
33d55ab4 | 366 | *db = o; |
26affd69 MT |
367 | r = 0; |
368 | ||
369 | END: | |
370 | if (r) | |
371 | pakfire_db_free(o); | |
33d55ab4 | 372 | |
26affd69 MT |
373 | if (path) |
374 | free(path); | |
375 | ||
376 | return r; | |
33d55ab4 MT |
377 | } |
378 | ||
18bf891d | 379 | PAKFIRE_EXPORT struct pakfire_db* pakfire_db_ref(struct pakfire_db* db) { |
33d55ab4 MT |
380 | db->nrefs++; |
381 | ||
382 | return db; | |
383 | } | |
384 | ||
18bf891d | 385 | PAKFIRE_EXPORT struct pakfire_db* pakfire_db_unref(struct pakfire_db* db) { |
33d55ab4 MT |
386 | if (--db->nrefs > 0) |
387 | return db; | |
388 | ||
389 | pakfire_db_free(db); | |
390 | ||
391 | return NULL; | |
392 | } | |
eafbe2ce | 393 | |
18bf891d | 394 | PAKFIRE_EXPORT int pakfire_db_add_package(struct pakfire_db* db, PakfirePackage pkg) { |
eafbe2ce MT |
395 | return 0; // TODO |
396 | } | |
397 | ||
18bf891d | 398 | PAKFIRE_EXPORT int pakfire_db_remove_package(struct pakfire_db* db, PakfirePackage pkg) { |
eafbe2ce MT |
399 | return 0; // TODO |
400 | } |