LCOV - code coverage report
Current view: top level - src - db_migrate_from_0_to_1.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 70.0 % 140 98
Test Date: 2026-01-12 05:34:38 Functions: 100.0 % 5 5

            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              : }
        

Generated by: LCOV version 2.0-1