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.0 % 154 114
Test Date: 2026-07-12 01:01:34 Functions: 100.0 % 5 5
Branches: 56.8 % 88 50

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

Generated by: LCOV version 2.0-1