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