Branch data Line data Source code
1 : : /**
2 : : * @file db_migrate_from_0_to_1.c
3 : : * @brief Migration to database version 1
4 : : *
5 : : * This legacy can be removed in 2034 (10-year Long-Term Support)
6 : : */
7 : :
8 : : #include "precizer.h"
9 : : #include "db_upgrade.h"
10 : :
11 : : #define STAT64_SIZE 144
12 : : #define STAT64_ST_SIZE_OFF 48
13 : : #define STAT64_ST_MTIM_OFF 88
14 : : #define STAT64_ST_CTIM_OFF 104
15 : :
16 : : /**
17 : : * @brief Validate sanity of a compact stat structure.
18 : : *
19 : : * Checks timestamp ranges (0..999999999 for nsec, reasonable sec).
20 : : *
21 : : * @param stat Pointer to compact stat to validate.
22 : : * @return SUCCESS if fields look sane, FAILURE otherwise.
23 : : */
24 : 94 : static Return cmpct_stat_is_sane(const CmpctStat_v1 *stat)
25 : : {
26 [ - + ]: 94 : if(NULL == stat)
27 : : {
28 : 0 : return(FAILURE);
29 : : }
30 : :
31 [ + - - + ]: 94 : if(stat->mtim_tv_nsec < 0 || stat->mtim_tv_nsec > 999999999)
32 : : {
33 : 0 : return(FAILURE);
34 : : }
35 : :
36 [ + - - + ]: 94 : if(stat->ctim_tv_nsec < 0 || stat->ctim_tv_nsec > 999999999)
37 : : {
38 : 0 : return(FAILURE);
39 : : }
40 : :
41 : 94 : const time_t max_ts = (time_t)32503680000; // Year 3000 upper bound
42 : :
43 [ + - - + ]: 94 : if(stat->mtim_tv_sec < 0 || stat->mtim_tv_sec > max_ts)
44 : : {
45 : 0 : return(FAILURE);
46 : : }
47 : :
48 [ + - - + ]: 94 : if(stat->ctim_tv_sec < 0 || stat->ctim_tv_sec > max_ts)
49 : : {
50 : 0 : return(FAILURE);
51 : : }
52 : :
53 : 94 : return(SUCCESS);
54 : : }
55 : :
56 : : /**
57 : : * @brief Convert a glibc/Linux stat blob into a compact stat.
58 : : *
59 : : * The blob format corresponds to 64-bit glibc stat layout (144 bytes) used in legacy DB v0.
60 : : * Extracts st_size, st_mtim, st_ctim by fixed offsets and fills CmpctStat_v1.
61 : : *
62 : : * @param blob Pointer to raw blob data.
63 : : * @param blob_size Size of the blob in bytes.
64 : : * @param new_stat Output compact stat structure.
65 : : * @return SUCCESS on successful conversion and sanity check, FAILURE otherwise.
66 : : */
67 : 96 : static Return populate_from_glibc_stat_blob(
68 : : const void *blob,
69 : : const int blob_size,
70 : : CmpctStat_v1 *new_stat)
71 : : {
72 [ + + ]: 96 : if(blob_size < STAT64_SIZE)
73 : : {
74 : 2 : return(FAILURE);
75 : : }
76 : :
77 : 94 : const unsigned char *b = (const unsigned char *)blob;
78 : 94 : int64_t st_size = 0;
79 : 94 : int64_t mtim_sec = 0;
80 : 94 : int64_t mtim_nsec = 0;
81 : 94 : int64_t ctim_sec = 0;
82 : 94 : int64_t ctim_nsec = 0;
83 : :
84 : 94 : memcpy(&st_size,b + STAT64_ST_SIZE_OFF,sizeof(st_size));
85 : 94 : memcpy(&mtim_sec,b + STAT64_ST_MTIM_OFF,sizeof(mtim_sec));
86 : 94 : memcpy(&mtim_nsec,b + STAT64_ST_MTIM_OFF + sizeof(int64_t),sizeof(mtim_nsec));
87 : 94 : memcpy(&ctim_sec,b + STAT64_ST_CTIM_OFF,sizeof(ctim_sec));
88 : 94 : memcpy(&ctim_nsec,b + STAT64_ST_CTIM_OFF + sizeof(int64_t),sizeof(ctim_nsec));
89 : :
90 : 94 : new_stat->st_size = (off_t)st_size;
91 : 94 : new_stat->mtim_tv_sec = (time_t)mtim_sec;
92 : 94 : new_stat->mtim_tv_nsec = (long)mtim_nsec;
93 : 94 : new_stat->ctim_tv_sec = (time_t)ctim_sec;
94 : 94 : new_stat->ctim_tv_nsec = (long)ctim_nsec;
95 : :
96 : 94 : return(cmpct_stat_is_sane(new_stat));
97 : : }
98 : :
99 : : /**
100 : : * @brief Convert and rewrite one row of the files table.
101 : : *
102 : : * Invalid legacy blobs are replaced with a zeroed CmpctStat_v1 and migration
103 : : * continues; FAILURE is returned only for SQLite errors.
104 : : *
105 : : * @param[in] stmt Prepared statement positioned at the current row.
106 : : * @param[out] db_file_modified Set to true when row update succeeds.
107 : : * @return Operation status.
108 : : */
109 : 96 : static Return process_row(
110 : : sqlite3_stmt *stmt,
111 : : bool *db_file_modified)
112 : : {
113 : : /* Status returned by this function through provide()
114 : : Default value assumes successful completion */
115 : 96 : Return status = SUCCESS;
116 : :
117 : 96 : int rc = SQLITE_OK;
118 : :
119 : 96 : sqlite3_int64 row_id = sqlite3_column_int64(stmt,0);
120 : :
121 : : /* Allocate memory for new blob data */
122 : 96 : CmpctStat_v1 new_stat = {0};
123 : :
124 : : /* Get blob data from the 'stat' column (column index 4) */
125 : 96 : const void *blob = sqlite3_column_blob(stmt,4);
126 : :
127 : 96 : int blob_size = sqlite3_column_bytes(stmt,4);
128 : :
129 : 96 : Return conversion_status = FAILURE;
130 : :
131 [ + - ]: 96 : if(blob != NULL)
132 : : {
133 : 96 : conversion_status = populate_from_glibc_stat_blob(blob,blob_size,&new_stat);
134 : : }
135 : :
136 [ + + ]: 96 : if(SUCCESS != conversion_status)
137 : : {
138 : 2 : memset(&new_stat,0,sizeof(new_stat));
139 : 2 : slog(ERROR,"Invalid legacy stat blob for row id=%lld (len=%d). Zero v1 stat will be stored\n",(long long)row_id,blob_size);
140 : : }
141 : :
142 : : /* Prepare update statement */
143 : 96 : sqlite3_stmt *update_stmt = NULL;
144 : 96 : const char *update_sql = "UPDATE files SET stat = ? WHERE ID = ?";
145 : :
146 : 96 : rc = sqlite3_prepare_v2(sqlite3_db_handle(stmt),update_sql,-1,&update_stmt,NULL);
147 : :
148 [ - + ]: 96 : if(SQLITE_OK != rc)
149 : : {
150 : 0 : log_sqlite_error(sqlite3_db_handle(stmt),rc,NULL,"Error preparing update statement");
151 : 0 : status = FAILURE;
152 : : }
153 : :
154 [ + - ]: 96 : if(SUCCESS == status)
155 : : {
156 : : /* Bind parameters */
157 : 96 : rc = sqlite3_bind_blob(update_stmt,1,&new_stat,sizeof(CmpctStat_v1),SQLITE_STATIC);
158 : :
159 [ - + ]: 96 : if(SQLITE_OK != rc)
160 : : {
161 : 0 : log_sqlite_error(sqlite3_db_handle(stmt),rc,NULL,"Error binding blob parameter");
162 : 0 : status = FAILURE;
163 [ - + ]: 96 : } else if(SQLITE_OK != (rc = sqlite3_bind_int64(update_stmt,2,row_id))){
164 : 0 : log_sqlite_error(sqlite3_db_handle(stmt),rc,NULL,"Error binding row ID parameter");
165 : 0 : status = FAILURE;
166 [ - + ]: 96 : } else if(SQLITE_DONE != (rc = sqlite3_step(update_stmt))){
167 : 0 : log_sqlite_error(sqlite3_db_handle(stmt),rc,NULL,"Error executing update statement");
168 : 0 : status = FAILURE;
169 : : } else {
170 : : /* Changes have been made to the database.
171 : : Reflect this in the caller-provided flag. */
172 : 96 : *db_file_modified = true;
173 : : }
174 : : }
175 : :
176 [ + - ]: 96 : if(update_stmt != NULL)
177 : : {
178 : 96 : sqlite3_finalize(update_stmt);
179 : : }
180 : :
181 : 96 : provide(status);
182 : : }
183 : :
184 : : /**
185 : : * @brief Process all rows of the files table for v0->v1 conversion.
186 : : *
187 : : * Row-level data corruption does not stop migration. The function fails only on
188 : : * SQLite iteration/update errors or external interruption.
189 : : *
190 : : * @param[in] db Open SQLite handle.
191 : : * @param[out] db_file_modified Set to true when at least one row is updated.
192 : : * @return Operation status.
193 : : */
194 : 8 : static Return process_database(
195 : : sqlite3 *db,
196 : : bool *db_file_modified)
197 : : {
198 : : /* Status returned by this function through provide()
199 : : Default value assumes successful completion */
200 : 8 : Return status = SUCCESS;
201 : 8 : sqlite3_stmt *stmt = NULL;
202 : 8 : int rc = SQLITE_OK;
203 : :
204 : 8 : const char *select_sql = "SELECT * FROM files";
205 : :
206 : 8 : rc = sqlite3_prepare_v2(db,select_sql,-1,&stmt,NULL);
207 : :
208 [ - + ]: 8 : if(SQLITE_OK != rc)
209 : : {
210 : 0 : log_sqlite_error(db,rc,NULL,"Error preparing statement");
211 : 0 : status = FAILURE;
212 : : }
213 : :
214 [ + - ]: 8 : if(SUCCESS == status)
215 : : {
216 : : /* Process each row */
217 [ + + ]: 104 : while(SQLITE_ROW == (rc = sqlite3_step(stmt)))
218 : : {
219 [ - + ]: 96 : if(global_interrupt_flag == true)
220 : : {
221 : 0 : break;
222 : : }
223 : :
224 : 96 : status = process_row(stmt,db_file_modified);
225 : :
226 [ - + ]: 96 : if(SUCCESS != status)
227 : : {
228 : 0 : break;
229 : : }
230 : : }
231 : :
232 [ + - + - ]: 8 : if(SUCCESS == status && global_interrupt_flag == false)
233 : : {
234 [ - + ]: 8 : if(SQLITE_DONE != rc)
235 : : {
236 : 0 : log_sqlite_error(db,rc,NULL,"Error iterating over rows during migration");
237 : 0 : status = FAILURE;
238 : : }
239 : : }
240 : : }
241 : :
242 : : /* Cleanup */
243 [ + - ]: 8 : if(NULL != stmt)
244 : : {
245 : 8 : sqlite3_finalize(stmt);
246 : : }
247 : :
248 : 8 : provide(status);
249 : : }
250 : :
251 : : /**
252 : : * @brief Migrate database schema/data from version 0 to version 1.
253 : : *
254 : : * Migration runs inside an explicit transaction and issues ROLLBACK on any
255 : : * FAILURE after BEGIN TRANSACTION.
256 : : *
257 : : * @param[in] db_file_path Path to the SQLite database file.
258 : : * @return Return status code.
259 : : */
260 : 8 : Return db_migrate_from_0_to_1(const char *db_file_path)
261 : : {
262 : : /* Status returned by this function through provide()
263 : : Default value assumes successful completion */
264 : 8 : Return status = SUCCESS;
265 : 8 : sqlite3 *db = NULL;
266 : 8 : char *err_msg = NULL;
267 : 8 : bool db_file_modified = false;
268 : 8 : bool transaction_started = false;
269 : 8 : int rc = SQLITE_OK;
270 : :
271 [ - + ]: 8 : if(config->dry_run == true)
272 : : {
273 : 0 : slog(TRACE,"Dry Run mode is enabled. Database migration is not required\n");
274 : 0 : provide(status);
275 : : }
276 : :
277 : : /* Open database in safe mode */
278 : 8 : rc = sqlite3_open_v2(db_file_path,&db,SQLITE_OPEN_READWRITE|SQLITE_OPEN_FULLMUTEX,NULL);
279 : :
280 [ - + ]: 8 : if(SQLITE_OK != rc)
281 : : {
282 : 0 : log_sqlite_error(db,rc,NULL,"Failed to open database");
283 : 0 : status = FAILURE;
284 : : }
285 : :
286 [ + - ]: 8 : if(SUCCESS == status)
287 : : {
288 : : /* Set safety pragmas */
289 : 8 : const char *pragmas =
290 : : "PRAGMA journal_mode=DELETE;"
291 : : "PRAGMA strict=ON;"
292 : : "PRAGMA fsync=ON;"
293 : : "PRAGMA synchronous=EXTRA;"
294 : : "PRAGMA locking_mode=EXCLUSIVE;";
295 : :
296 : 8 : rc = sqlite3_exec(db,pragmas,NULL,NULL,&err_msg);
297 : :
298 [ - + ]: 8 : if(SQLITE_OK != rc)
299 : : {
300 : 0 : log_sqlite_error(db,rc,err_msg,"Failed to set pragmas");
301 : 0 : status = FAILURE;
302 : : }
303 : : }
304 : :
305 [ + - ]: 8 : if(SUCCESS == status)
306 : : {
307 : : /* Begin transaction */
308 : 8 : rc = sqlite3_exec(db,"BEGIN TRANSACTION",NULL,NULL,&err_msg);
309 : :
310 [ - + ]: 8 : if(SQLITE_OK != rc)
311 : : {
312 : 0 : log_sqlite_error(db,rc,err_msg,"Failed to begin transaction");
313 : 0 : status = FAILURE;
314 : : } else {
315 : 8 : transaction_started = true;
316 : : }
317 : : }
318 : :
319 [ + - ]: 8 : if(SUCCESS == status)
320 : : {
321 : : /* Perform create table query */
322 : 8 : rc = sqlite3_exec(db,"CREATE TABLE IF NOT EXISTS metadata (db_version INTEGER NOT NULL UNIQUE)",NULL,NULL,&err_msg);
323 : :
324 [ - + ]: 8 : if(SQLITE_OK != rc)
325 : : {
326 : 0 : log_sqlite_error(db,rc,err_msg,"Failed to create table");
327 : 0 : status = FAILURE;
328 : : }
329 : : }
330 : :
331 [ + - ]: 8 : if(SUCCESS == status)
332 : : {
333 : : /* Perform version update query */
334 : 8 : status = process_database(db,&db_file_modified);
335 : :
336 [ - + ]: 8 : if(SUCCESS != status)
337 : : {
338 : 0 : slog(ERROR,"Database processing failed\n");
339 : : }
340 : : }
341 : :
342 [ + - ]: 8 : if(transaction_started == true)
343 : : {
344 [ - + ]: 8 : if(global_interrupt_flag == true)
345 : : {
346 : : /* Attempt rollback */
347 : 0 : rc = sqlite3_exec(db,"ROLLBACK",NULL,NULL,NULL);
348 : :
349 [ # # ]: 0 : if(SQLITE_OK == rc)
350 : : {
351 : 0 : slog(TRACE,"The transaction has been rolled back\n");
352 : :
353 [ # # ]: 0 : if(SUCCESS == status)
354 : : {
355 : 0 : status = WARNING;
356 : : }
357 : : } else {
358 : 0 : log_sqlite_error(db,rc,NULL,"Failed to rollback transaction");
359 : 0 : status = FAILURE;
360 : : }
361 [ - + ]: 8 : } else if(SUCCESS != status){
362 : : /* Attempt rollback */
363 : 0 : rc = sqlite3_exec(db,"ROLLBACK",NULL,NULL,NULL);
364 : :
365 [ # # ]: 0 : if(SQLITE_OK == rc)
366 : : {
367 : 0 : slog(TRACE,"The transaction has been rolled back\n");
368 : : } else {
369 : 0 : log_sqlite_error(db,rc,NULL,"Failed to rollback transaction");
370 : 0 : status = FAILURE;
371 : : }
372 : : } else {
373 : : /* Commit transaction */
374 : 8 : rc = sqlite3_exec(db,"COMMIT",NULL,NULL,&err_msg);
375 : :
376 [ - + ]: 8 : if(SQLITE_OK != rc)
377 : : {
378 : 0 : log_sqlite_error(db,rc,err_msg,"Failed to commit transaction");
379 : 0 : status = FAILURE;
380 : :
381 : : /* Attempt rollback */
382 : 0 : sqlite3_exec(db,"ROLLBACK",NULL,NULL,NULL);
383 : : }
384 : : }
385 : : }
386 : :
387 [ + - ]: 8 : if(SUCCESS == status)
388 : : {
389 : : /**
390 : : *
391 : : * If the database being updated is the primary one, adjust the global
392 : : * flag indicating that the main database file has been
393 : : * modified (this will consequently update the file's ctime, mtime, and size)
394 : : */
395 [ + - ]: 8 : if(db_file_modified == true)
396 : : {
397 [ + + ]: 8 : if(strcmp(db_file_path,confstr(db_primary_file_path)) == 0)
398 : : {
399 : : /* Changes have been made to the database. Update
400 : : this in the global variable value. */
401 : 4 : config->db_primary_file_modified = true;
402 : : }
403 : : }
404 : : }
405 : :
406 : : /* Cleanup */
407 [ - + - + ]: 8 : call(db_close(db,&config->db_primary_file_modified));
408 : :
409 : 8 : provide(status);
410 : : }
|