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 : sqlite3_finalize(update_stmt);
119 : :
120 : 268 : provide(status);
121 : : }
122 : :
123 : : /**
124 : : * @brief Convert all files.stat blobs to v4 format.
125 : : *
126 : : * Corrupted row payloads do not stop migration; iteration stops only on SQLite
127 : : * errors or external interruption.
128 : : */
129 : 24 : static Return process_database(
130 : : sqlite3 *db,
131 : : bool *db_file_modified)
132 : : {
133 : : /* Status returned by this function through provide()
134 : : Default value assumes successful completion */
135 : 24 : Return status = SUCCESS;
136 : 24 : sqlite3_stmt *stmt = NULL;
137 : 24 : int rc = SQLITE_OK;
138 : :
139 : 24 : const char *select_sql = "SELECT ID, stat FROM files";
140 : :
141 : 24 : rc = sqlite3_prepare_v2(db,select_sql,-1,&stmt,NULL);
142 : :
143 [ - + ]: 24 : if(SQLITE_OK != rc)
144 : : {
145 : 0 : log_sqlite_error(db,rc,NULL,"Error preparing statement");
146 : 0 : status = FAILURE;
147 : : }
148 : :
149 [ + - ]: 24 : if(SUCCESS == status)
150 : : {
151 [ + + ]: 290 : while(SQLITE_ROW == (rc = sqlite3_step(stmt)))
152 : : {
153 [ - + ]: 268 : if(global_interrupt_flag == true)
154 : : {
155 : 0 : break;
156 : : }
157 : :
158 : 268 : status = process_row(stmt,db_file_modified);
159 : :
160 [ + + ]: 268 : if(SUCCESS != status)
161 : : {
162 : 2 : break;
163 : : }
164 : : }
165 : :
166 [ + + + - ]: 24 : if(SUCCESS == status && global_interrupt_flag == false)
167 : : {
168 [ - + ]: 22 : if(SQLITE_DONE != rc)
169 : : {
170 : 0 : log_sqlite_error(db,rc,NULL,"Error iterating over rows during migration");
171 : 0 : status = FAILURE;
172 : : }
173 : : }
174 : : }
175 : :
176 : 24 : sqlite3_finalize(stmt);
177 : :
178 : 24 : provide(status);
179 : : }
180 : :
181 : : /**
182 : : * @brief Normalize journaling and flush WAL artifacts before migration.
183 : : *
184 : : * Switches journal mode to DELETE, verifies returned mode value, and runs
185 : : * wal_checkpoint(TRUNCATE).
186 : : */
187 : 24 : static Return normalize_journal_mode(sqlite3 *db)
188 : : {
189 : : /* Status returned by this function through provide()
190 : : Default value assumes successful completion */
191 : 24 : Return status = SUCCESS;
192 : 24 : sqlite3_stmt *stmt = NULL;
193 : 24 : int rc = SQLITE_OK;
194 : :
195 : 24 : rc = sqlite3_prepare_v2(db,"PRAGMA journal_mode=DELETE;",-1,&stmt,NULL);
196 : :
197 [ - + ]: 24 : if(SQLITE_OK != rc)
198 : : {
199 : 0 : log_sqlite_error(db,rc,NULL,"Failed to prepare journal_mode switch");
200 : 0 : status = FAILURE;
201 : : }
202 : :
203 [ + - ]: 24 : if(SUCCESS == status)
204 : : {
205 : 24 : rc = sqlite3_step(stmt);
206 : :
207 [ + - ]: 24 : if(SQLITE_ROW == rc)
208 : : {
209 : 24 : const unsigned char *mode = sqlite3_column_text(stmt,0);
210 : :
211 [ + - - + ]: 24 : if(mode == NULL || strcmp((const char *)mode,"delete") != 0)
212 : : {
213 [ # # ]: 0 : slog(ERROR,"journal_mode switch failed, current mode: %s\n",mode ? (const char *)mode : "(null)");
214 : 0 : status = FAILURE;
215 : : }
216 : : } else {
217 : 0 : log_sqlite_error(db,rc,NULL,"journal_mode switch did not return a row");
218 : 0 : status = FAILURE;
219 : : }
220 : : }
221 : :
222 : 24 : sqlite3_finalize(stmt);
223 : :
224 [ + - ]: 24 : if(SUCCESS == status)
225 : : {
226 : 24 : rc = sqlite3_exec(db,"PRAGMA wal_checkpoint(TRUNCATE);",NULL,NULL,NULL);
227 : :
228 [ - + ]: 24 : if(SQLITE_OK != rc)
229 : : {
230 : 0 : log_sqlite_error(db,rc,NULL,"Failed to checkpoint WAL");
231 : 0 : status = FAILURE;
232 : : }
233 : : }
234 : :
235 : 24 : provide(status);
236 : : }
237 : :
238 : : /**
239 : : * @brief Migrate database schema/data to version 4.
240 : : *
241 : : * Migration runs inside an explicit transaction and issues ROLLBACK on any
242 : : * FAILURE after BEGIN TRANSACTION.
243 : : *
244 : : * @param[in] db_file_path Path to the SQLite database file.
245 : : * @return Return status code.
246 : : */
247 : 24 : Return db_migrate_to_version_4(const char *db_file_path)
248 : : {
249 : : /* Status returned by this function through provide()
250 : : Default value assumes successful completion */
251 : 24 : Return status = SUCCESS;
252 : 24 : sqlite3 *db = NULL;
253 : 24 : char *err_msg = NULL;
254 : 24 : bool db_file_modified = false;
255 : 24 : bool transaction_started = false;
256 : 24 : int rc = SQLITE_OK;
257 : :
258 [ - + ]: 24 : if(config->dry_run == true)
259 : : {
260 : 0 : slog(TRACE,"Dry Run mode is enabled. Database migration is not required\n");
261 : 0 : provide(status);
262 : : }
263 : :
264 : 24 : rc = sqlite3_open_v2(db_file_path,&db,SQLITE_OPEN_READWRITE|SQLITE_OPEN_FULLMUTEX,NULL);
265 : :
266 [ - + ]: 24 : if(SQLITE_OK != rc)
267 : : {
268 : 0 : log_sqlite_error(db,rc,NULL,"Failed to open database");
269 : 0 : status = FAILURE;
270 : : }
271 : :
272 [ + - ]: 24 : if(SUCCESS == status)
273 : : {
274 : 24 : const char *pragmas =
275 : : "PRAGMA strict=ON;"
276 : : "PRAGMA fsync=ON;"
277 : : "PRAGMA synchronous=EXTRA;"
278 : : "PRAGMA locking_mode=EXCLUSIVE;";
279 : :
280 : 24 : rc = sqlite3_exec(db,pragmas,NULL,NULL,&err_msg);
281 : :
282 [ - + ]: 24 : if(SQLITE_OK != rc)
283 : : {
284 : 0 : log_sqlite_error(db,rc,err_msg,"Failed to set pragmas");
285 : 0 : status = FAILURE;
286 : : }
287 : : }
288 : :
289 [ + - ]: 24 : if(SUCCESS == status)
290 : : {
291 : 24 : status = normalize_journal_mode(db);
292 : : }
293 : :
294 [ + - ]: 24 : if(SUCCESS == status)
295 : : {
296 : 24 : rc = sqlite3_exec(db,"BEGIN TRANSACTION",NULL,NULL,&err_msg);
297 : :
298 [ - + ]: 24 : if(SQLITE_OK != rc)
299 : : {
300 : 0 : log_sqlite_error(db,rc,err_msg,"Failed to begin transaction");
301 : 0 : status = FAILURE;
302 : : } else {
303 : 24 : transaction_started = true;
304 : : }
305 : : }
306 : :
307 [ + - ]: 24 : if(SUCCESS == status)
308 : : {
309 : 24 : status = process_database(db,&db_file_modified);
310 : :
311 [ + + ]: 24 : if(SUCCESS != status)
312 : : {
313 : 2 : slog(ERROR,"Database processing failed\n");
314 : : }
315 : : }
316 : :
317 [ + - ]: 24 : if(transaction_started == true)
318 : : {
319 [ - + ]: 24 : if(global_interrupt_flag == true)
320 : : {
321 : 0 : rc = sqlite3_exec(db,"ROLLBACK",NULL,NULL,NULL);
322 : :
323 [ # # ]: 0 : if(SQLITE_OK == rc)
324 : : {
325 : 0 : slog(TRACE,"The transaction has been rolled back\n");
326 : :
327 [ # # ]: 0 : if(SUCCESS == status)
328 : : {
329 : 0 : status = WARNING;
330 : : }
331 : : } else {
332 : 0 : log_sqlite_error(db,rc,NULL,"Failed to rollback transaction");
333 : 0 : status = FAILURE;
334 : : }
335 [ + + ]: 24 : } else if(SUCCESS != status){
336 : 2 : rc = sqlite3_exec(db,"ROLLBACK",NULL,NULL,NULL);
337 : :
338 [ + - ]: 2 : if(SQLITE_OK == rc)
339 : : {
340 : 2 : slog(TRACE,"The transaction has been rolled back\n");
341 : : } else {
342 : 0 : log_sqlite_error(db,rc,NULL,"Failed to rollback transaction");
343 : 0 : status = FAILURE;
344 : : }
345 : : } else {
346 : 22 : rc = sqlite3_exec(db,"COMMIT",NULL,NULL,&err_msg);
347 : :
348 [ - + ]: 22 : if(SQLITE_OK != rc)
349 : : {
350 : 0 : log_sqlite_error(db,rc,err_msg,"Failed to commit transaction");
351 : 0 : status = FAILURE;
352 : 0 : sqlite3_exec(db,"ROLLBACK",NULL,NULL,NULL);
353 : : }
354 : : }
355 : : }
356 : :
357 [ + + + - ]: 24 : if(SUCCESS == status && db_file_modified == true)
358 : : {
359 [ + + ]: 22 : if(strcmp(db_file_path,confstr(db_primary_file_path)) == 0)
360 : : {
361 : 10 : config->db_primary_file_modified = true;
362 : : }
363 : : }
364 : :
365 : 24 : call(db_close(db,&config->db_primary_file_modified));
366 : :
367 : 24 : provide(status);
368 : : }
|