LCOV - code coverage report
Current view: top level - src - db_migrate_to_version_4.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 74.5 % 157 117
Test Date: 2026-03-01 04:31:48 Functions: 100.0 % 5 5
Branches: 57.1 % 98 56

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

Generated by: LCOV version 2.0-1