]> git.ipfire.org Git - people/ms/pakfire.git/blob - src/libpakfire/db.c
libpakfire: db: Add files table
[people/ms/pakfire.git] / src / libpakfire / db.c
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
24 #include <sqlite3.h>
25
26 #include <pakfire/db.h>
27 #include <pakfire/logging.h>
28 #include <pakfire/pakfire.h>
29 #include <pakfire/private.h>
30 #include <pakfire/types.h>
31 #include <pakfire/util.h>
32
33 #define DATABASE_PATH PAKFIRE_PRIVATE_DIR "/packages.db"
34
35 #define CURRENT_SCHEMA 7
36 #define SCHEMA_MIN_SUP 7
37
38 struct pakfire_db {
39 Pakfire pakfire;
40 int nrefs;
41
42 int mode;
43
44 sqlite3* handle;
45 int schema;
46 };
47
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
55 static int pakfire_db_execute(struct pakfire_db* db, const char* stmt) {
56 int r;
57
58 DEBUG(db->pakfire, "Executing database query: %s\n", stmt);
59
60 do {
61 r = sqlite3_exec(db->handle, stmt, NULL, NULL, NULL);
62 } while (r == SQLITE_BUSY);
63
64 // Log any errors
65 if (r) {
66 ERROR(db->pakfire, "Database query failed: %s\n", sqlite3_errmsg(db->handle));
67 }
68
69 return r;
70 }
71
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
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");
91 }
92
93 static void pakfire_db_free(struct pakfire_db* db) {
94 DEBUG(db->pakfire, "Releasing database at %p\n", db);
95
96 if (db->handle) {
97 // Optimize the database before it is being closed
98 pakfire_db_optimize(db);
99
100 // Close database handle
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
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 // Create packages table
228 r = pakfire_db_execute(db,
229 "CREATE TABLE IF NOT EXISTS packages("
230 "id INTEGER PRIMARY KEY, "
231 "name TEXT, "
232 "epoch INTEGER, "
233 "version TEXT, "
234 "release TEXT, "
235 "arch TEXT, "
236 "groups TEXT, "
237 "filename TEXT, "
238 "size INTEGER, "
239 "inst_size INTEGER, "
240 "hash1 TEXT, "
241 "license TEXT, "
242 "summary TEXT, "
243 "description TEXT, "
244 "uuid TEXT, "
245 "vendor TEXT, "
246 "build_id TEXT, "
247 "build_host TEXT, "
248 "build_date TEXT, "
249 "build_time INTEGER, "
250 "installed INTEGER, "
251 "reason TEXT, "
252 "repository TEXT"
253 ")");
254 if (r)
255 return 1;
256
257 // packages: Create index to find package by name
258 r = pakfire_db_execute(db, "CREATE INDEX IF NOT EXISTS packages_name ON packages(name)");
259 if (r)
260 return 1;
261
262 // Create dependencies table
263 r = pakfire_db_execute(db,
264 "CREATE TABLE IF NOT EXISTS dependencies("
265 "pkg INTEGER, "
266 "type TEXT, "
267 "dependency TEXT"
268 ")");
269 if (r)
270 return r;
271
272 // dependencies: Add index over packages
273 r = pakfire_db_execute(db, "CREATE INDEX IF NOT EXISTS dependencies_pkg_index ON dependencies(pkg)");
274 if (r)
275 return r;
276
277 // Create files table
278 r = pakfire_db_execute(db,
279 "CREATE TABLE IF NOT EXISTS files("
280 "id INTEGER PRIMARY KEY, "
281 "name TEXT, "
282 "pkg INTEGER, "
283 "size INTEGER, "
284 "type INTEGER, "
285 "config INTEGER, "
286 "datafile INTEGER, "
287 "mode INTEGER, "
288 "user TEXT, "
289 "'group' TEXT, "
290 "hash1 TEXT, "
291 "mtime INTEGER, "
292 "capabilities TEXT"
293 ")");
294 if (r)
295 return 1;
296
297 // files: Add index over packages
298 r = pakfire_db_execute(db, "CREATE INDEX IF NOT EXISTS files_pkg_index ON files(pkg)");
299 if (r)
300 return 1;
301
302 return 0;
303 }
304
305 static int pakfire_db_migrate_schema(struct pakfire_db* db) {
306 int r;
307
308 while (db->schema < CURRENT_SCHEMA) {
309 // Begin a new transaction
310 r = pakfire_db_begin_transaction(db);
311 if (r)
312 goto ROLLBACK;
313
314 switch (db->schema) {
315 // No schema exists
316 case 0:
317 r = pakfire_db_create_schema(db);
318 if (r)
319 goto ROLLBACK;
320
321 db->schema = CURRENT_SCHEMA;
322 break;
323
324 default:
325 ERROR(db->pakfire, "Cannot migrate database from schema %d\n", db->schema);
326 goto ROLLBACK;
327 }
328
329 // Update the schema version
330 r = pakfire_db_set_int(db, "schema", CURRENT_SCHEMA);
331 if (r)
332 goto ROLLBACK;
333
334 // All done, commit!
335 r = pakfire_db_commit(db);
336 if (r)
337 goto ROLLBACK;
338 }
339
340 return 0;
341
342 ROLLBACK:
343 pakfire_db_rollback(db);
344
345 return 1;
346 }
347
348 static int pakfire_db_setup(struct pakfire_db* db) {
349 int r;
350
351 // Setup logging
352 sqlite3_config(SQLITE_CONFIG_LOG, logging_callback, db->pakfire);
353
354 // Make LIKE case-sensitive
355 pakfire_db_execute(db, "PRAGMA case_sensitive_like = ON");
356
357 // Fetch the current schema
358 db->schema = pakfire_db_get_schema(db);
359
360 // Check if the schema is recent enough
361 if (db->schema > 0 && db->schema < SCHEMA_MIN_SUP) {
362 ERROR(db->pakfire, "Database schema %d is not supported by this version of Pakfire\n",
363 db->schema);
364 return 1;
365 }
366
367 // Done when not in read-write mode
368 if (db->mode != PAKFIRE_DB_READWRITE)
369 return 0;
370
371 // Disable secure delete
372 pakfire_db_execute(db, "PRAGMA secure_delete = OFF");
373
374 // Set database journal to WAL
375 r = pakfire_db_execute(db, "PRAGMA journal_mode = WAL");
376 if (r != SQLITE_OK) {
377 ERROR(db->pakfire, "Could not set journal mode to WAL: %s\n",
378 sqlite3_errmsg(db->handle));
379 return 1;
380 }
381
382 // Disable autocheckpoint
383 r = sqlite3_wal_autocheckpoint(db->handle, 0);
384 if (r != SQLITE_OK) {
385 ERROR(db->pakfire, "Could not disable autocheckpoint: %s\n",
386 sqlite3_errmsg(db->handle));
387 return 1;
388 }
389
390 // Create or migrate schema
391 r = pakfire_db_migrate_schema(db);
392 if (r)
393 return r;
394
395 return 0;
396 }
397
398 PAKFIRE_EXPORT int pakfire_db_open(struct pakfire_db** db, Pakfire pakfire, int flags) {
399 int r = 1;
400
401 struct pakfire_db* o = pakfire_calloc(1, sizeof(*o));
402 if (!o)
403 return -ENOMEM;
404
405 DEBUG(pakfire, "Allocated database at %p\n", o);
406
407 o->pakfire = pakfire_ref(pakfire);
408 o->nrefs = 1;
409
410 int sqlite3_flags = 0;
411
412 // Store mode & forward it to sqlite3
413 if (flags & PAKFIRE_DB_READWRITE) {
414 o->mode = PAKFIRE_DB_READWRITE;
415 sqlite3_flags |= SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
416 } else {
417 o->mode = PAKFIRE_DB_READONLY;
418 sqlite3_flags |= SQLITE_OPEN_READONLY;
419 }
420
421 // Make the filename
422 char* path = pakfire_make_path(o->pakfire, DATABASE_PATH);
423 if (!path)
424 goto END;
425
426 // Try to open the sqlite3 database file
427 r = sqlite3_open_v2(path, &o->handle, sqlite3_flags, NULL);
428 if (r != SQLITE_OK) {
429 ERROR(pakfire, "Could not open database %s: %s\n",
430 path, sqlite3_errmsg(o->handle));
431
432 r = 1;
433 goto END;
434 }
435
436 // Setup the database
437 r = pakfire_db_setup(o);
438 if (r)
439 goto END;
440
441 *db = o;
442 r = 0;
443
444 END:
445 if (r)
446 pakfire_db_free(o);
447
448 if (path)
449 free(path);
450
451 return r;
452 }
453
454 PAKFIRE_EXPORT struct pakfire_db* pakfire_db_ref(struct pakfire_db* db) {
455 db->nrefs++;
456
457 return db;
458 }
459
460 PAKFIRE_EXPORT struct pakfire_db* pakfire_db_unref(struct pakfire_db* db) {
461 if (--db->nrefs > 0)
462 return db;
463
464 pakfire_db_free(db);
465
466 return NULL;
467 }
468
469 PAKFIRE_EXPORT int pakfire_db_add_package(struct pakfire_db* db, PakfirePackage pkg) {
470 return 0; // TODO
471 }
472
473 PAKFIRE_EXPORT int pakfire_db_remove_package(struct pakfire_db* db, PakfirePackage pkg) {
474 return 0; // TODO
475 }