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 : sqlite3_finalize(update_stmt);
177 : :
178 : 96 : provide(status);
179 : : }
180 : :
181 : : /**
182 : : * @brief Process all rows of the files table for v0->v1 conversion.
183 : : *
184 : : * Row-level data corruption does not stop migration. The function fails only on
185 : : * SQLite iteration/update errors or external interruption.
186 : : *
187 : : * @param[in] db Open SQLite handle.
188 : : * @param[out] db_file_modified Set to true when at least one row is updated.
189 : : * @return Operation status.
190 : : */
191 : 8 : static Return process_database(
192 : : sqlite3 *db,
193 : : bool *db_file_modified)
194 : : {
195 : : /* Status returned by this function through provide()
196 : : Default value assumes successful completion */
197 : 8 : Return status = SUCCESS;
198 : 8 : sqlite3_stmt *stmt = NULL;
199 : 8 : int rc = SQLITE_OK;
200 : :
201 : 8 : const char *select_sql = "SELECT * FROM files";
202 : :
203 : 8 : rc = sqlite3_prepare_v2(db,select_sql,-1,&stmt,NULL);
204 : :
205 [ - + ]: 8 : if(SQLITE_OK != rc)
206 : : {
207 : 0 : log_sqlite_error(db,rc,NULL,"Error preparing statement");
208 : 0 : status = FAILURE;
209 : : }
210 : :
211 [ + - ]: 8 : if(SUCCESS == status)
212 : : {
213 : : /* Process each row */
214 [ + + ]: 104 : while(SQLITE_ROW == (rc = sqlite3_step(stmt)))
215 : : {
216 [ - + ]: 96 : if(global_interrupt_flag == true)
217 : : {
218 : 0 : break;
219 : : }
220 : :
221 : 96 : status = process_row(stmt,db_file_modified);
222 : :
223 [ - + ]: 96 : if(SUCCESS != status)
224 : : {
225 : 0 : break;
226 : : }
227 : : }
228 : :
229 [ + - + - ]: 8 : if(SUCCESS == status && global_interrupt_flag == false)
230 : : {
231 [ - + ]: 8 : if(SQLITE_DONE != rc)
232 : : {
233 : 0 : log_sqlite_error(db,rc,NULL,"Error iterating over rows during migration");
234 : 0 : status = FAILURE;
235 : : }
236 : : }
237 : : }
238 : :
239 : : /* Cleanup */
240 : 8 : sqlite3_finalize(stmt);
241 : :
242 : 8 : provide(status);
243 : : }
244 : :
245 : : /**
246 : : * @brief Migrate database schema/data from version 0 to version 1.
247 : : *
248 : : * Migration runs inside an explicit transaction and issues ROLLBACK on any
249 : : * FAILURE after BEGIN TRANSACTION.
250 : : *
251 : : * @param[in] db_file_path Path to the SQLite database file.
252 : : * @return Return status code.
253 : : */
254 : 8 : Return db_migrate_from_0_to_1(const char *db_file_path)
255 : : {
256 : : /* Status returned by this function through provide()
257 : : Default value assumes successful completion */
258 : 8 : Return status = SUCCESS;
259 : 8 : sqlite3 *db = NULL;
260 : 8 : char *err_msg = NULL;
261 : 8 : bool db_file_modified = false;
262 : 8 : bool transaction_started = false;
263 : 8 : int rc = SQLITE_OK;
264 : :
265 [ - + ]: 8 : if(config->dry_run == true)
266 : : {
267 : 0 : slog(TRACE,"Dry Run mode is enabled. Database migration is not required\n");
268 : 0 : provide(status);
269 : : }
270 : :
271 : : /* Open database in safe mode */
272 : 8 : rc = sqlite3_open_v2(db_file_path,&db,SQLITE_OPEN_READWRITE|SQLITE_OPEN_FULLMUTEX,NULL);
273 : :
274 [ - + ]: 8 : if(SQLITE_OK != rc)
275 : : {
276 : 0 : log_sqlite_error(db,rc,NULL,"Failed to open database");
277 : 0 : status = FAILURE;
278 : : }
279 : :
280 [ + - ]: 8 : if(SUCCESS == status)
281 : : {
282 : : /* Set safety pragmas */
283 : 8 : const char *pragmas =
284 : : "PRAGMA journal_mode=DELETE;"
285 : : "PRAGMA strict=ON;"
286 : : "PRAGMA fsync=ON;"
287 : : "PRAGMA synchronous=EXTRA;"
288 : : "PRAGMA locking_mode=EXCLUSIVE;";
289 : :
290 : 8 : rc = sqlite3_exec(db,pragmas,NULL,NULL,&err_msg);
291 : :
292 [ - + ]: 8 : if(SQLITE_OK != rc)
293 : : {
294 : 0 : log_sqlite_error(db,rc,err_msg,"Failed to set pragmas");
295 : 0 : status = FAILURE;
296 : : }
297 : : }
298 : :
299 [ + - ]: 8 : if(SUCCESS == status)
300 : : {
301 : : /* Begin transaction */
302 : 8 : rc = sqlite3_exec(db,"BEGIN TRANSACTION",NULL,NULL,&err_msg);
303 : :
304 [ - + ]: 8 : if(SQLITE_OK != rc)
305 : : {
306 : 0 : log_sqlite_error(db,rc,err_msg,"Failed to begin transaction");
307 : 0 : status = FAILURE;
308 : : } else {
309 : 8 : transaction_started = true;
310 : : }
311 : : }
312 : :
313 [ + - ]: 8 : if(SUCCESS == status)
314 : : {
315 : : /* Perform create table query */
316 : 8 : rc = sqlite3_exec(db,"CREATE TABLE IF NOT EXISTS metadata (db_version INTEGER NOT NULL UNIQUE)",NULL,NULL,&err_msg);
317 : :
318 [ - + ]: 8 : if(SQLITE_OK != rc)
319 : : {
320 : 0 : log_sqlite_error(db,rc,err_msg,"Failed to create table");
321 : 0 : status = FAILURE;
322 : : }
323 : : }
324 : :
325 [ + - ]: 8 : if(SUCCESS == status)
326 : : {
327 : : /* Perform version update query */
328 : 8 : status = process_database(db,&db_file_modified);
329 : :
330 [ - + ]: 8 : if(SUCCESS != status)
331 : : {
332 : 0 : slog(ERROR,"Database processing failed\n");
333 : : }
334 : : }
335 : :
336 [ + - ]: 8 : if(transaction_started == true)
337 : : {
338 [ - + ]: 8 : if(global_interrupt_flag == true)
339 : : {
340 : : /* Attempt rollback */
341 : 0 : rc = sqlite3_exec(db,"ROLLBACK",NULL,NULL,NULL);
342 : :
343 [ # # ]: 0 : if(SQLITE_OK == rc)
344 : : {
345 : 0 : slog(TRACE,"The transaction has been rolled back\n");
346 : :
347 [ # # ]: 0 : if(SUCCESS == status)
348 : : {
349 : 0 : status = WARNING;
350 : : }
351 : : } else {
352 : 0 : log_sqlite_error(db,rc,NULL,"Failed to rollback transaction");
353 : 0 : status = FAILURE;
354 : : }
355 [ - + ]: 8 : } else if(SUCCESS != status){
356 : : /* Attempt rollback */
357 : 0 : rc = sqlite3_exec(db,"ROLLBACK",NULL,NULL,NULL);
358 : :
359 [ # # ]: 0 : if(SQLITE_OK == rc)
360 : : {
361 : 0 : slog(TRACE,"The transaction has been rolled back\n");
362 : : } else {
363 : 0 : log_sqlite_error(db,rc,NULL,"Failed to rollback transaction");
364 : 0 : status = FAILURE;
365 : : }
366 : : } else {
367 : : /* Commit transaction */
368 : 8 : rc = sqlite3_exec(db,"COMMIT",NULL,NULL,&err_msg);
369 : :
370 [ - + ]: 8 : if(SQLITE_OK != rc)
371 : : {
372 : 0 : log_sqlite_error(db,rc,err_msg,"Failed to commit transaction");
373 : 0 : status = FAILURE;
374 : :
375 : : /* Attempt rollback */
376 : 0 : sqlite3_exec(db,"ROLLBACK",NULL,NULL,NULL);
377 : : }
378 : : }
379 : : }
380 : :
381 [ + - ]: 8 : if(SUCCESS == status)
382 : : {
383 : : /**
384 : : *
385 : : * If the database being updated is the primary one, adjust the global
386 : : * flag indicating that the main database file has been
387 : : * modified (this will consequently update the file's ctime, mtime, and size)
388 : : */
389 [ + - ]: 8 : if(db_file_modified == true)
390 : : {
391 [ + + ]: 8 : if(strcmp(db_file_path,confstr(db_primary_file_path)) == 0)
392 : : {
393 : : /* Changes have been made to the database. Update
394 : : this in the global variable value. */
395 : 4 : config->db_primary_file_modified = true;
396 : : }
397 : : }
398 : : }
399 : :
400 : : /* Cleanup */
401 : 8 : call(db_close(db,&config->db_primary_file_modified));
402 : :
403 : 8 : provide(status);
404 : : }
|