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.2 % 151 106
Test Date: 2026-03-01 04:31:48 Functions: 100.0 % 5 5
Branches: 51.0 % 98 50

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

Generated by: LCOV version 2.0-1