Branch data Line data Source code
1 : : /**
2 : : * @file db_migrate_to_version_4.c
3 : : * @brief Migration to database version 4
4 : : *
5 : : * This legacy can be removed in 2036 (10-year Long-Term Support)
6 : : */
7 : :
8 : : #include "precizer.h"
9 : : #include "db_upgrade.h"
10 : :
11 : : /**
12 : : * @brief Convert CmpctStat_v1 into CmpctStat (v4 layout).
13 : : *
14 : : * Caller may pass a zero-initialized source for corrupted rows; in that case
15 : : * the destination remains a valid zeroed v4 record.
16 : : */
17 : 268 : static void convert_blob_to_v4_stat(
18 : : const CmpctStat_v1 *source,
19 : : CmpctStat *destination)
20 : : {
21 : 268 : memset(destination,0,sizeof(*destination));
22 : :
23 : 268 : destination->st_size = source->st_size;
24 : 268 : destination->st_blocks = BLKCNT_UNKNOWN;
25 : 268 : destination->st_dev = 0;
26 : 268 : destination->st_ino = 0;
27 : 268 : destination->mtim_tv_sec = source->mtim_tv_sec;
28 : 268 : destination->mtim_tv_nsec = source->mtim_tv_nsec;
29 : 268 : destination->ctim_tv_sec = source->ctim_tv_sec;
30 : 268 : destination->ctim_tv_nsec = source->ctim_tv_nsec;
31 : 268 : }
32 : :
33 : : /**
34 : : * @brief Migrate one files row to v4 stat format.
35 : : *
36 : : * Row with invalid blob size/content is not fatal: a zeroed source is used and
37 : : * a zeroed v4 stat is written. FAILURE is returned only for SQLite errors.
38 : : */
39 : 268 : static Return process_row(
40 : : sqlite3_stmt *stmt,
41 : : bool *db_file_modified)
42 : : {
43 : : /* Status returned by this function through provide()
44 : : Default value assumes successful completion */
45 : 268 : Return status = SUCCESS;
46 : 268 : int rc = SQLITE_OK;
47 : 268 : int blob_size = 0;
48 : :
49 : 268 : sqlite3_int64 row_id = sqlite3_column_int64(stmt,0);
50 : 268 : CmpctStat_v1 zero_source = {0};
51 : 268 : const CmpctStat_v1 *source = &zero_source;
52 : :
53 : 268 : blob_size = sqlite3_column_bytes(stmt,1);
54 : :
55 [ + + ]: 268 : if(blob_size == (int)sizeof(CmpctStat_v1))
56 : : {
57 : 266 : const CmpctStat_v1 *blob = sqlite3_column_blob(stmt,1);
58 : :
59 [ + - ]: 266 : if(blob != NULL)
60 : : {
61 : 266 : source = blob;
62 : : } else {
63 : 0 : slog(ERROR,"Invalid v3 stat blob pointer for row id=%lld (size=%d). Zero stat will be stored\n",(long long)row_id,blob_size);
64 : : }
65 : : } else {
66 : 2 : slog(ERROR,"Invalid v3 stat blob size for row id=%lld (got=%d, expected=%zu). Zero stat will be stored\n",(long long)row_id,blob_size,sizeof(CmpctStat_v1));
67 : : }
68 : :
69 : 268 : CmpctStat destination = {0};
70 : 268 : convert_blob_to_v4_stat(source,&destination);
71 : :
72 : 268 : sqlite3_stmt *update_stmt = NULL;
73 : 268 : const char *update_sql = "UPDATE files SET stat = ?1 WHERE ID = ?2";
74 : :
75 : 268 : rc = sqlite3_prepare_v2(sqlite3_db_handle(stmt),update_sql,-1,&update_stmt,NULL);
76 : :
77 [ - + ]: 268 : if(SQLITE_OK != rc)
78 : : {
79 : 0 : log_sqlite_error(sqlite3_db_handle(stmt),rc,NULL,"Error preparing update statement");
80 : 0 : status = FAILURE;
81 : : }
82 : :
83 [ + - ]: 268 : if(SUCCESS == status)
84 : : {
85 : 268 : rc = sqlite3_bind_blob(update_stmt,1,&destination,sizeof(CmpctStat),SQLITE_STATIC);
86 : :
87 [ - + ]: 268 : if(SQLITE_OK != rc)
88 : : {
89 : 0 : log_sqlite_error(sqlite3_db_handle(stmt),rc,NULL,"Error binding stat blob");
90 : 0 : status = FAILURE;
91 : : }
92 : : }
93 : :
94 [ + - ]: 268 : if(SUCCESS == status)
95 : : {
96 : 268 : rc = sqlite3_bind_int64(update_stmt,2,row_id);
97 : :
98 [ - + ]: 268 : if(SQLITE_OK != rc)
99 : : {
100 : 0 : log_sqlite_error(sqlite3_db_handle(stmt),rc,NULL,"Error binding row id");
101 : 0 : status = FAILURE;
102 : : }
103 : : }
104 : :
105 [ + - ]: 268 : if(SUCCESS == status)
106 : : {
107 : 268 : rc = sqlite3_step(update_stmt);
108 : :
109 [ + + ]: 268 : if(SQLITE_DONE != rc)
110 : : {
111 : 2 : log_sqlite_error(sqlite3_db_handle(stmt),rc,NULL,"Error executing update statement");
112 : 2 : status = FAILURE;
113 : : } else {
114 : 266 : *db_file_modified = true;
115 : : }
116 : : }
117 : :
118 [ + - ]: 268 : if(update_stmt != NULL)
119 : : {
120 : 268 : sqlite3_finalize(update_stmt);
121 : : }
122 : :
123 : 268 : provide(status);
124 : : }
125 : :
126 : : /**
127 : : * @brief Convert all files.stat blobs to v4 format.
128 : : *
129 : : * Corrupted row payloads do not stop migration; iteration stops only on SQLite
130 : : * errors or external interruption.
131 : : */
132 : 24 : static Return process_database(
133 : : sqlite3 *db,
134 : : bool *db_file_modified)
135 : : {
136 : : /* Status returned by this function through provide()
137 : : Default value assumes successful completion */
138 : 24 : Return status = SUCCESS;
139 : 24 : sqlite3_stmt *stmt = NULL;
140 : 24 : int rc = SQLITE_OK;
141 : :
142 : 24 : const char *select_sql = "SELECT ID, stat FROM files";
143 : :
144 : 24 : rc = sqlite3_prepare_v2(db,select_sql,-1,&stmt,NULL);
145 : :
146 [ - + ]: 24 : if(SQLITE_OK != rc)
147 : : {
148 : 0 : log_sqlite_error(db,rc,NULL,"Error preparing statement");
149 : 0 : status = FAILURE;
150 : : }
151 : :
152 [ + - ]: 24 : if(SUCCESS == status)
153 : : {
154 [ + + ]: 290 : while(SQLITE_ROW == (rc = sqlite3_step(stmt)))
155 : : {
156 [ - + ]: 268 : if(global_interrupt_flag == true)
157 : : {
158 : 0 : break;
159 : : }
160 : :
161 : 268 : status = process_row(stmt,db_file_modified);
162 : :
163 [ + + ]: 268 : if(SUCCESS != status)
164 : : {
165 : 2 : break;
166 : : }
167 : : }
168 : :
169 [ + + + - ]: 24 : if(SUCCESS == status && global_interrupt_flag == false)
170 : : {
171 [ - + ]: 22 : if(SQLITE_DONE != rc)
172 : : {
173 : 0 : log_sqlite_error(db,rc,NULL,"Error iterating over rows during migration");
174 : 0 : status = FAILURE;
175 : : }
176 : : }
177 : : }
178 : :
179 [ + - ]: 24 : if(stmt != NULL)
180 : : {
181 : 24 : sqlite3_finalize(stmt);
182 : : }
183 : :
184 : 24 : provide(status);
185 : : }
186 : :
187 : : /**
188 : : * @brief Normalize journaling and flush WAL artifacts before migration.
189 : : *
190 : : * Switches journal mode to DELETE, verifies returned mode value, and runs
191 : : * wal_checkpoint(TRUNCATE).
192 : : */
193 : 24 : static Return normalize_journal_mode(sqlite3 *db)
194 : : {
195 : : /* Status returned by this function through provide()
196 : : Default value assumes successful completion */
197 : 24 : Return status = SUCCESS;
198 : 24 : sqlite3_stmt *stmt = NULL;
199 : 24 : int rc = SQLITE_OK;
200 : :
201 : 24 : rc = sqlite3_prepare_v2(db,"PRAGMA journal_mode=DELETE;",-1,&stmt,NULL);
202 : :
203 [ - + ]: 24 : if(SQLITE_OK != rc)
204 : : {
205 : 0 : log_sqlite_error(db,rc,NULL,"Failed to prepare journal_mode switch");
206 : 0 : status = FAILURE;
207 : : }
208 : :
209 [ + - ]: 24 : if(SUCCESS == status)
210 : : {
211 : 24 : rc = sqlite3_step(stmt);
212 : :
213 [ + - ]: 24 : if(SQLITE_ROW == rc)
214 : : {
215 : 24 : const unsigned char *mode = sqlite3_column_text(stmt,0);
216 : :
217 [ + - - + ]: 24 : if(mode == NULL || strcmp((const char *)mode,"delete") != 0)
218 : : {
219 [ # # ]: 0 : slog(ERROR,"journal_mode switch failed, current mode: %s\n",mode ? (const char *)mode : "(null)");
220 : 0 : status = FAILURE;
221 : : }
222 : : } else {
223 : 0 : log_sqlite_error(db,rc,NULL,"journal_mode switch did not return a row");
224 : 0 : status = FAILURE;
225 : : }
226 : : }
227 : :
228 [ + - ]: 24 : if(stmt != NULL)
229 : : {
230 : 24 : sqlite3_finalize(stmt);
231 : : }
232 : :
233 [ + - ]: 24 : if(SUCCESS == status)
234 : : {
235 : 24 : rc = sqlite3_exec(db,"PRAGMA wal_checkpoint(TRUNCATE);",NULL,NULL,NULL);
236 : :
237 [ - + ]: 24 : if(SQLITE_OK != rc)
238 : : {
239 : 0 : log_sqlite_error(db,rc,NULL,"Failed to checkpoint WAL");
240 : 0 : status = FAILURE;
241 : : }
242 : : }
243 : :
244 : 24 : provide(status);
245 : : }
246 : :
247 : : /**
248 : : * @brief Migrate database schema/data to version 4.
249 : : *
250 : : * Migration runs inside an explicit transaction and issues ROLLBACK on any
251 : : * FAILURE after BEGIN TRANSACTION.
252 : : *
253 : : * @param[in] db_file_path Path to the SQLite database file.
254 : : * @return Return status code.
255 : : */
256 : 24 : Return db_migrate_to_version_4(const char *db_file_path)
257 : : {
258 : : /* Status returned by this function through provide()
259 : : Default value assumes successful completion */
260 : 24 : Return status = SUCCESS;
261 : 24 : sqlite3 *db = NULL;
262 : 24 : char *err_msg = NULL;
263 : 24 : bool db_file_modified = false;
264 : 24 : bool transaction_started = false;
265 : 24 : int rc = SQLITE_OK;
266 : :
267 [ - + ]: 24 : if(config->dry_run == true)
268 : : {
269 : 0 : slog(TRACE,"Dry Run mode is enabled. Database migration is not required\n");
270 : 0 : provide(status);
271 : : }
272 : :
273 : 24 : rc = sqlite3_open_v2(db_file_path,&db,SQLITE_OPEN_READWRITE|SQLITE_OPEN_FULLMUTEX,NULL);
274 : :
275 [ - + ]: 24 : if(SQLITE_OK != rc)
276 : : {
277 : 0 : log_sqlite_error(db,rc,NULL,"Failed to open database");
278 : 0 : status = FAILURE;
279 : : }
280 : :
281 [ + - ]: 24 : if(SUCCESS == status)
282 : : {
283 : 24 : const char *pragmas =
284 : : "PRAGMA strict=ON;"
285 : : "PRAGMA fsync=ON;"
286 : : "PRAGMA synchronous=EXTRA;"
287 : : "PRAGMA locking_mode=EXCLUSIVE;";
288 : :
289 : 24 : rc = sqlite3_exec(db,pragmas,NULL,NULL,&err_msg);
290 : :
291 [ - + ]: 24 : if(SQLITE_OK != rc)
292 : : {
293 : 0 : log_sqlite_error(db,rc,err_msg,"Failed to set pragmas");
294 : 0 : status = FAILURE;
295 : : }
296 : : }
297 : :
298 [ + - ]: 24 : if(SUCCESS == status)
299 : : {
300 : 24 : status = normalize_journal_mode(db);
301 : : }
302 : :
303 [ + - ]: 24 : if(SUCCESS == status)
304 : : {
305 : 24 : rc = sqlite3_exec(db,"BEGIN TRANSACTION",NULL,NULL,&err_msg);
306 : :
307 [ - + ]: 24 : if(SQLITE_OK != rc)
308 : : {
309 : 0 : log_sqlite_error(db,rc,err_msg,"Failed to begin transaction");
310 : 0 : status = FAILURE;
311 : : } else {
312 : 24 : transaction_started = true;
313 : : }
314 : : }
315 : :
316 [ + - ]: 24 : if(SUCCESS == status)
317 : : {
318 : 24 : status = process_database(db,&db_file_modified);
319 : :
320 [ + + ]: 24 : if(SUCCESS != status)
321 : : {
322 : 2 : slog(ERROR,"Database processing failed\n");
323 : : }
324 : : }
325 : :
326 [ + - ]: 24 : if(transaction_started == true)
327 : : {
328 [ - + ]: 24 : if(global_interrupt_flag == true)
329 : : {
330 : 0 : rc = sqlite3_exec(db,"ROLLBACK",NULL,NULL,NULL);
331 : :
332 [ # # ]: 0 : if(SQLITE_OK == rc)
333 : : {
334 : 0 : slog(TRACE,"The transaction has been rolled back\n");
335 : :
336 [ # # ]: 0 : if(SUCCESS == status)
337 : : {
338 : 0 : status = WARNING;
339 : : }
340 : : } else {
341 : 0 : log_sqlite_error(db,rc,NULL,"Failed to rollback transaction");
342 : 0 : status = FAILURE;
343 : : }
344 [ + + ]: 24 : } else if(SUCCESS != status){
345 : 2 : rc = sqlite3_exec(db,"ROLLBACK",NULL,NULL,NULL);
346 : :
347 [ + - ]: 2 : if(SQLITE_OK == rc)
348 : : {
349 : 2 : slog(TRACE,"The transaction has been rolled back\n");
350 : : } else {
351 : 0 : log_sqlite_error(db,rc,NULL,"Failed to rollback transaction");
352 : 0 : status = FAILURE;
353 : : }
354 : : } else {
355 : 22 : rc = sqlite3_exec(db,"COMMIT",NULL,NULL,&err_msg);
356 : :
357 [ - + ]: 22 : if(SQLITE_OK != rc)
358 : : {
359 : 0 : log_sqlite_error(db,rc,err_msg,"Failed to commit transaction");
360 : 0 : status = FAILURE;
361 : 0 : sqlite3_exec(db,"ROLLBACK",NULL,NULL,NULL);
362 : : }
363 : : }
364 : : }
365 : :
366 [ + + + - ]: 24 : if(SUCCESS == status && db_file_modified == true)
367 : : {
368 [ + + ]: 22 : if(strcmp(db_file_path,confstr(db_primary_file_path)) == 0)
369 : : {
370 : 10 : config->db_primary_file_modified = true;
371 : : }
372 : : }
373 : :
374 [ - + + + ]: 24 : call(db_close(db,&config->db_primary_file_modified));
375 : :
376 : 24 : provide(status);
377 : : }
|