LCOV - code coverage report
Current view: top level - tests/src - helpers_db_utils.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 79.1 % 464 367
Test Date: 2026-07-12 01:01:34 Functions: 100.0 % 21 21
Branches: 50.9 % 454 231

             Branch data     Line data    Source code
       1                 :             : #include "sute.h"
       2                 :             : 
       3                 :             : /**
       4                 :             :  * @brief Open SQLite database from TMPDIR by relative filename
       5                 :             :  *
       6                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
       7                 :             :  * @param[in] open_flags Flags passed to sqlite3_open_v2
       8                 :             :  * @param[out] db_out Opened database handle
       9                 :             :  *
      10                 :             :  * @return Return status code:
      11                 :             :  *         - SUCCESS: Database opened successfully
      12                 :             :  *         - FAILURE: Validation, path construction, or open failed
      13                 :             :  */
      14                 :         340 : Return open_db_from_tmpdir(
      15                 :             :         const char *db_filename,
      16                 :             :         const int  open_flags,
      17                 :             :         sqlite3    **db_out)
      18                 :             : {
      19                 :             :         /* Status returned by this function through provide()
      20                 :             :            Default value assumes successful completion */
      21                 :         340 :         Return status = SUCCESS;
      22                 :             : 
      23                 :         340 :         m_create(char,db_path,MEMORY_STRING);
      24                 :             : 
      25   [ +  -  -  + ]:         340 :         if(db_filename == NULL || db_out == NULL)
      26                 :             :         {
      27                 :           0 :                 status = FAILURE;
      28                 :             :         }
      29                 :             : 
      30         [ +  - ]:         340 :         if(SUCCESS == status)
      31                 :             :         {
      32                 :         340 :                 status = construct_path(db_filename,db_path);
      33                 :             :         }
      34                 :             : 
      35         [ +  - ]:         340 :         if(SUCCESS == status)
      36                 :             :         {
      37                 :         340 :                 *db_out = NULL;
      38                 :             : 
      39         [ -  + ]:         340 :                 if(SQLITE_OK != sqlite3_open_v2(m_text(db_path),db_out,open_flags,NULL))
      40                 :             :                 {
      41                 :           0 :                         status = FAILURE;
      42                 :             : 
      43                 :           0 :                         (void)sqlite3_close(*db_out);
      44                 :           0 :                         *db_out = NULL;
      45                 :             :                 }
      46                 :             :         }
      47                 :             : 
      48                 :         340 :         m_del(db_path);
      49                 :             : 
      50                 :         340 :         return(status);
      51                 :             : }
      52                 :             : 
      53                 :             : /**
      54                 :             :  * @brief Verify that DB relative_path set matches expected list exactly
      55                 :             :  *
      56                 :             :  * @param[in] db_filename DB file name relative to TMPDIR
      57                 :             :  * @param[in] expected_paths Sorted expected relative_path values
      58                 :             :  * @param[in] expected_count Number of expected paths
      59                 :             :  *
      60                 :             :  * @return Return status code:
      61                 :             :  *         - SUCCESS: DB rows match expected paths exactly
      62                 :             :  *         - FAILURE: Mismatch or DB access error
      63                 :             :  */
      64                 :          10 : Return db_paths_match(
      65                 :             :         const char        *db_filename,
      66                 :             :         const char *const *expected_paths,
      67                 :             :         const int         expected_count)
      68                 :             : {
      69                 :             :         /* Status returned by this function through provide()
      70                 :             :            Default value assumes successful completion */
      71                 :          10 :         Return status = SUCCESS;
      72                 :          10 :         sqlite3 *db = NULL;
      73                 :          10 :         sqlite3_stmt *stmt = NULL;
      74                 :          10 :         const char *sql = "SELECT relative_path FROM files ORDER BY relative_path ASC;";
      75                 :          10 :         m_create(char,db_path,MEMORY_STRING);
      76                 :             : 
      77   [ +  -  +  -  :          10 :         if(SUCCESS == status && (db_filename == NULL || expected_paths == NULL || expected_count < 0))
             +  -  -  + ]
      78                 :             :         {
      79                 :           0 :                 status = FAILURE;
      80                 :             :         }
      81                 :             : 
      82         [ +  - ]:          10 :         if(SUCCESS == status)
      83                 :             :         {
      84                 :          10 :                 status = construct_path(db_filename,db_path);
      85                 :             :         }
      86                 :             : 
      87   [ +  -  -  + ]:          10 :         if(SUCCESS == status && SQLITE_OK != sqlite3_open_v2(m_text(db_path),&db,SQLITE_OPEN_READONLY,NULL))
      88                 :             :         {
      89                 :           0 :                 status = FAILURE;
      90                 :             :         }
      91                 :             : 
      92   [ +  -  -  + ]:          10 :         if(SUCCESS == status && SQLITE_OK != sqlite3_prepare_v2(db,sql,-1,&stmt,NULL))
      93                 :             :         {
      94                 :           0 :                 status = FAILURE;
      95                 :             :         }
      96                 :             : 
      97                 :          10 :         int index = 0;
      98                 :             : 
      99         [ +  - ]:          10 :         if(SUCCESS == status)
     100                 :             :         {
     101                 :          10 :                 int rc = sqlite3_step(stmt);
     102                 :             : 
     103         [ +  + ]:          68 :                 while(rc == SQLITE_ROW)
     104                 :             :                 {
     105         [ -  + ]:          58 :                         if(index >= expected_count)
     106                 :             :                         {
     107                 :           0 :                                 status = FAILURE;
     108                 :           0 :                                 break;
     109                 :             :                         }
     110                 :             : 
     111                 :          58 :                         const unsigned char *db_path_text = sqlite3_column_text(stmt,0);
     112                 :             : 
     113   [ +  -  -  + ]:          58 :                         if(db_path_text == NULL || strcmp((const char *)db_path_text,expected_paths[index]) != 0)
     114                 :             :                         {
     115                 :           0 :                                 status = FAILURE;
     116                 :           0 :                                 break;
     117                 :             :                         }
     118                 :             : 
     119                 :          58 :                         index++;
     120                 :          58 :                         rc = sqlite3_step(stmt);
     121                 :             :                 }
     122                 :             : 
     123   [ +  -  -  + ]:          10 :                 if(SUCCESS == status && rc != SQLITE_DONE)
     124                 :             :                 {
     125                 :           0 :                         status = FAILURE;
     126                 :             :                 }
     127                 :             :         }
     128                 :             : 
     129   [ +  -  -  + ]:          10 :         if(SUCCESS == status && index != expected_count)
     130                 :             :         {
     131                 :           0 :                 status = FAILURE;
     132                 :             :         }
     133                 :             : 
     134                 :          10 :         (void)sqlite3_finalize(stmt);
     135                 :             : 
     136                 :          10 :         (void)sqlite3_close(db);
     137                 :             : 
     138                 :          10 :         m_del(db_path);
     139                 :             : 
     140                 :          10 :         return(status);
     141                 :             : }
     142                 :             : 
     143                 :             : /**
     144                 :             :  * @brief Check whether one files-table row exists by relative path
     145                 :             :  *
     146                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
     147                 :             :  * @param[in] relative_path Relative path key in files table
     148                 :             :  * @param[out] exists_out Output existence flag
     149                 :             :  *
     150                 :             :  * @return Return status code:
     151                 :             :  *         - SUCCESS: Query completed and output was filled
     152                 :             :  *         - FAILURE: Validation, DB access, bind, or query execution failed
     153                 :             :  */
     154                 :          72 : Return db_relative_path_exists(
     155                 :             :         const char *db_filename,
     156                 :             :         const char *relative_path,
     157                 :             :         bool       *exists_out)
     158                 :             : {
     159                 :             :         /* Status returned by this function through provide()
     160                 :             :            Default value assumes successful completion */
     161                 :          72 :         Return status = SUCCESS;
     162                 :          72 :         sqlite3 *db = NULL;
     163                 :          72 :         sqlite3_stmt *stmt = NULL;
     164                 :          72 :         const char *sql = "SELECT 1 FROM files WHERE relative_path = ?1 LIMIT 1;";
     165                 :             : 
     166   [ +  -  +  -  :          72 :         if(db_filename == NULL || relative_path == NULL || exists_out == NULL)
                   -  + ]
     167                 :             :         {
     168                 :           0 :                 status = FAILURE;
     169                 :             :         }
     170                 :             : 
     171         [ +  - ]:          72 :         if(SUCCESS == status)
     172                 :             :         {
     173                 :          72 :                 status = open_db_from_tmpdir(db_filename,SQLITE_OPEN_READONLY,&db);
     174                 :             :         }
     175                 :             : 
     176   [ +  -  -  + ]:          72 :         if(SUCCESS == status && SQLITE_OK != sqlite3_prepare_v2(db,sql,-1,&stmt,NULL))
     177                 :             :         {
     178                 :           0 :                 status = FAILURE;
     179                 :             :         }
     180                 :             : 
     181   [ +  -  -  + ]:          72 :         if(SUCCESS == status && SQLITE_OK != sqlite3_bind_text(stmt,1,relative_path,(int)strlen(relative_path),SQLITE_TRANSIENT))
     182                 :             :         {
     183                 :           0 :                 status = FAILURE;
     184                 :             :         }
     185                 :             : 
     186         [ +  - ]:          72 :         if(SUCCESS == status)
     187                 :             :         {
     188                 :          72 :                 const int rc = sqlite3_step(stmt);
     189                 :             : 
     190         [ +  + ]:          72 :                 if(rc == SQLITE_ROW)
     191                 :             :                 {
     192                 :          66 :                         *exists_out = true;
     193                 :             : 
     194         [ -  + ]:          66 :                         if(SQLITE_DONE != sqlite3_step(stmt))
     195                 :             :                         {
     196                 :           0 :                                 status = FAILURE;
     197                 :             :                         }
     198                 :             : 
     199         [ +  - ]:           6 :                 } else if(rc == SQLITE_DONE){
     200                 :           6 :                         *exists_out = false;
     201                 :             : 
     202                 :             :                 } else {
     203                 :           0 :                         status = FAILURE;
     204                 :             :                 }
     205                 :             :         }
     206                 :             : 
     207                 :          72 :         (void)sqlite3_finalize(stmt);
     208                 :             : 
     209                 :          72 :         (void)sqlite3_close(db);
     210                 :             : 
     211                 :          72 :         return(status);
     212                 :             : }
     213                 :             : 
     214                 :             : /**
     215                 :             :  * @brief Read files.ID for one row selected by relative path
     216                 :             :  *
     217                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
     218                 :             :  * @param[in] relative_path Relative path key in files table
     219                 :             :  * @param[out] id_out Output primary key value
     220                 :             :  *
     221                 :             :  * @return Return status code:
     222                 :             :  *         - SUCCESS: Exactly one row was found and ID was read
     223                 :             :  *         - FAILURE: Validation, DB access, missing row, or duplicate row failed
     224                 :             :  */
     225                 :           8 : Return db_read_file_id(
     226                 :             :         const char    *db_filename,
     227                 :             :         const char    *relative_path,
     228                 :             :         sqlite3_int64 *id_out)
     229                 :             : {
     230                 :             :         /* Status returned by this function through provide()
     231                 :             :            Default value assumes successful completion */
     232                 :           8 :         Return status = SUCCESS;
     233                 :           8 :         sqlite3 *db = NULL;
     234                 :           8 :         sqlite3_stmt *stmt = NULL;
     235                 :           8 :         const char *sql = "SELECT ID FROM files WHERE relative_path = ?1;";
     236                 :             : 
     237   [ +  -  +  -  :           8 :         if(db_filename == NULL || relative_path == NULL || id_out == NULL)
                   -  + ]
     238                 :             :         {
     239                 :           0 :                 status = FAILURE;
     240                 :             :         }
     241                 :             : 
     242         [ +  - ]:           8 :         if(SUCCESS == status)
     243                 :             :         {
     244                 :           8 :                 status = open_db_from_tmpdir(db_filename,SQLITE_OPEN_READONLY,&db);
     245                 :             :         }
     246                 :             : 
     247   [ +  -  -  + ]:           8 :         if(SUCCESS == status && SQLITE_OK != sqlite3_prepare_v2(db,sql,-1,&stmt,NULL))
     248                 :             :         {
     249                 :           0 :                 status = FAILURE;
     250                 :             :         }
     251                 :             : 
     252   [ +  -  -  + ]:           8 :         if(SUCCESS == status && SQLITE_OK != sqlite3_bind_text(stmt,1,relative_path,(int)strlen(relative_path),SQLITE_TRANSIENT))
     253                 :             :         {
     254                 :           0 :                 status = FAILURE;
     255                 :             :         }
     256                 :             : 
     257         [ +  - ]:           8 :         if(SUCCESS == status)
     258                 :             :         {
     259                 :           8 :                 int rc = sqlite3_step(stmt);
     260                 :             : 
     261         [ +  - ]:           8 :                 if(rc == SQLITE_ROW)
     262                 :             :                 {
     263                 :           8 :                         *id_out = sqlite3_column_int64(stmt,0);
     264                 :           8 :                         rc = sqlite3_step(stmt);
     265                 :             : 
     266         [ -  + ]:           8 :                         if(rc != SQLITE_DONE)
     267                 :             :                         {
     268                 :           0 :                                 status = FAILURE;
     269                 :             :                         }
     270                 :             : 
     271                 :             :                 } else {
     272                 :           0 :                         status = FAILURE;
     273                 :             :                 }
     274                 :             :         }
     275                 :             : 
     276                 :           8 :         (void)sqlite3_finalize(stmt);
     277                 :             : 
     278                 :           8 :         (void)sqlite3_close(db);
     279                 :             : 
     280                 :           8 :         return(status);
     281                 :             : }
     282                 :             : 
     283                 :             : /**
     284                 :             :  * @brief Read first row ID from files table
     285                 :             :  *
     286                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
     287                 :             :  * @param[out] row_id_out Output row ID
     288                 :             :  *
     289                 :             :  * @return Return status code:
     290                 :             :  *         - SUCCESS: First row ID was read
     291                 :             :  *         - FAILURE: Validation, DB access, or query execution failed
     292                 :             :  */
     293                 :           6 : Return db_read_first_row_id(
     294                 :             :         const char    *db_filename,
     295                 :             :         sqlite3_int64 *row_id_out)
     296                 :             : {
     297                 :             :         /* Status returned by this function through provide()
     298                 :             :            Default value assumes successful completion */
     299                 :           6 :         Return status = SUCCESS;
     300                 :           6 :         sqlite3 *db = NULL;
     301                 :           6 :         sqlite3_stmt *stmt = NULL;
     302                 :           6 :         const char *sql = "SELECT ID FROM files ORDER BY ID ASC LIMIT 1;";
     303                 :             : 
     304   [ +  -  -  + ]:           6 :         if(db_filename == NULL || row_id_out == NULL)
     305                 :             :         {
     306                 :           0 :                 status = FAILURE;
     307                 :             :         }
     308                 :             : 
     309         [ +  - ]:           6 :         if(SUCCESS == status)
     310                 :             :         {
     311                 :           6 :                 status = open_db_from_tmpdir(db_filename,SQLITE_OPEN_READONLY,&db);
     312                 :             :         }
     313                 :             : 
     314   [ +  -  -  + ]:           6 :         if(SUCCESS == status && SQLITE_OK != sqlite3_prepare_v2(db,sql,-1,&stmt,NULL))
     315                 :             :         {
     316                 :           0 :                 status = FAILURE;
     317                 :             :         }
     318                 :             : 
     319         [ +  - ]:           6 :         if(SUCCESS == status)
     320                 :             :         {
     321                 :           6 :                 int rc = sqlite3_step(stmt);
     322                 :             : 
     323         [ +  - ]:           6 :                 if(SQLITE_ROW == rc)
     324                 :             :                 {
     325                 :           6 :                         *row_id_out = sqlite3_column_int64(stmt,0);
     326                 :           6 :                         rc = sqlite3_step(stmt);
     327                 :             : 
     328         [ -  + ]:           6 :                         if(SQLITE_DONE != rc)
     329                 :             :                         {
     330                 :           0 :                                 status = FAILURE;
     331                 :             :                         }
     332                 :             :                 } else {
     333                 :           0 :                         status = FAILURE;
     334                 :             :                 }
     335                 :             :         }
     336                 :             : 
     337                 :           6 :         (void)sqlite3_finalize(stmt);
     338                 :             : 
     339                 :           6 :         (void)sqlite3_close(db);
     340                 :             : 
     341                 :           6 :         return(status);
     342                 :             : }
     343                 :             : 
     344                 :             : /**
     345                 :             :  * @brief Overwrite stat blob for a specific files row ID
     346                 :             :  *
     347                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
     348                 :             :  * @param[in] row_id Row ID in files table
     349                 :             :  * @param[in] blob New blob bytes
     350                 :             :  * @param[in] blob_size Size of blob in bytes
     351                 :             :  *
     352                 :             :  * @return Return status code:
     353                 :             :  *         - SUCCESS: Blob was updated
     354                 :             :  *         - FAILURE: Validation, DB access, bind, step, or change check failed
     355                 :             :  */
     356                 :           4 : Return db_overwrite_stat_blob_by_row_id(
     357                 :             :         const char          *db_filename,
     358                 :             :         const sqlite3_int64 row_id,
     359                 :             :         const void          *blob,
     360                 :             :         const int           blob_size)
     361                 :             : {
     362                 :             :         /* Status returned by this function through provide()
     363                 :             :            Default value assumes successful completion */
     364                 :           4 :         Return status = SUCCESS;
     365                 :           4 :         sqlite3 *db = NULL;
     366                 :           4 :         sqlite3_stmt *stmt = NULL;
     367                 :           4 :         const char *sql = "UPDATE files SET stat = ?1 WHERE ID = ?2;";
     368                 :             : 
     369   [ +  -  +  -  :           4 :         if(db_filename == NULL || blob == NULL || blob_size < 0)
                   -  + ]
     370                 :             :         {
     371                 :           0 :                 status = FAILURE;
     372                 :             :         }
     373                 :             : 
     374         [ +  - ]:           4 :         if(SUCCESS == status)
     375                 :             :         {
     376                 :           4 :                 status = open_db_from_tmpdir(db_filename,SQLITE_OPEN_READWRITE,&db);
     377                 :             :         }
     378                 :             : 
     379   [ +  -  -  + ]:           4 :         if(SUCCESS == status && SQLITE_OK != sqlite3_prepare_v2(db,sql,-1,&stmt,NULL))
     380                 :             :         {
     381                 :           0 :                 status = FAILURE;
     382                 :             :         }
     383                 :             : 
     384   [ +  -  -  + ]:           4 :         if(SUCCESS == status && SQLITE_OK != sqlite3_bind_blob(stmt,1,blob,blob_size,SQLITE_TRANSIENT))
     385                 :             :         {
     386                 :           0 :                 status = FAILURE;
     387                 :             :         }
     388                 :             : 
     389   [ +  -  -  + ]:           4 :         if(SUCCESS == status && SQLITE_OK != sqlite3_bind_int64(stmt,2,row_id))
     390                 :             :         {
     391                 :           0 :                 status = FAILURE;
     392                 :             :         }
     393                 :             : 
     394   [ +  -  -  + ]:           4 :         if(SUCCESS == status && SQLITE_DONE != sqlite3_step(stmt))
     395                 :             :         {
     396                 :           0 :                 status = FAILURE;
     397                 :             :         }
     398                 :             : 
     399   [ +  -  -  + ]:           4 :         if(SUCCESS == status && sqlite3_changes(db) < 1)
     400                 :             :         {
     401                 :           0 :                 status = FAILURE;
     402                 :             :         }
     403                 :             : 
     404                 :           4 :         (void)sqlite3_finalize(stmt);
     405                 :             : 
     406                 :           4 :         (void)sqlite3_close(db);
     407                 :             : 
     408                 :           4 :         return(status);
     409                 :             : }
     410                 :             : 
     411                 :             : /**
     412                 :             :  * @brief Corrupt stat blob for first files row with one-byte payload
     413                 :             :  *
     414                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
     415                 :             :  * @param[out] row_id_out Optional output for affected row ID
     416                 :             :  *
     417                 :             :  * @return Return status code:
     418                 :             :  *         - SUCCESS: First row stat blob was corrupted
     419                 :             :  *         - FAILURE: Row lookup or blob overwrite failed
     420                 :             :  */
     421                 :           4 : Return db_corrupt_first_row_stat_blob(
     422                 :             :         const char    *db_filename,
     423                 :             :         sqlite3_int64 *row_id_out)
     424                 :             : {
     425                 :             :         /* Status returned by this function through provide()
     426                 :             :            Default value assumes successful completion */
     427                 :           4 :         Return status = SUCCESS;
     428                 :           4 :         sqlite3_int64 row_id = 0;
     429                 :           4 :         const unsigned char corrupt_blob[] = {0xA5};
     430                 :             : 
     431         [ +  - ]:           4 :         if(SUCCESS == status)
     432                 :             :         {
     433                 :           4 :                 status = db_read_first_row_id(db_filename,&row_id);
     434                 :             :         }
     435                 :             : 
     436         [ +  - ]:           4 :         if(SUCCESS == status)
     437                 :             :         {
     438                 :           4 :                 status = db_overwrite_stat_blob_by_row_id(db_filename,row_id,corrupt_blob,(int)sizeof(corrupt_blob));
     439                 :             :         }
     440                 :             : 
     441   [ +  -  +  - ]:           4 :         if(SUCCESS == status && row_id_out != NULL)
     442                 :             :         {
     443                 :           4 :                 *row_id_out = row_id;
     444                 :             :         }
     445                 :             : 
     446                 :           4 :         return(status);
     447                 :             : }
     448                 :             : 
     449                 :             : /**
     450                 :             :  * @brief Read number of rows from files table
     451                 :             :  *
     452                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
     453                 :             :  * @param[out] count_out Output row count
     454                 :             :  *
     455                 :             :  * @return Return status code:
     456                 :             :  *         - SUCCESS: Count value was read
     457                 :             :  *         - FAILURE: Validation, DB access, or query execution failed
     458                 :             :  */
     459                 :          16 : Return db_read_files_count(
     460                 :             :         const char *db_filename,
     461                 :             :         int        *count_out)
     462                 :             : {
     463                 :             :         /* Status returned by this function through provide()
     464                 :             :            Default value assumes successful completion */
     465                 :          16 :         Return status = SUCCESS;
     466                 :          16 :         sqlite3 *db = NULL;
     467                 :          16 :         sqlite3_stmt *stmt = NULL;
     468                 :          16 :         const char *sql = "SELECT COUNT(*) FROM files;";
     469                 :             : 
     470   [ +  -  -  + ]:          16 :         if(db_filename == NULL || count_out == NULL)
     471                 :             :         {
     472                 :           0 :                 status = FAILURE;
     473                 :             :         }
     474                 :             : 
     475         [ +  - ]:          16 :         if(SUCCESS == status)
     476                 :             :         {
     477                 :          16 :                 status = open_db_from_tmpdir(db_filename,SQLITE_OPEN_READONLY,&db);
     478                 :             :         }
     479                 :             : 
     480   [ +  -  -  + ]:          16 :         if(SUCCESS == status && SQLITE_OK != sqlite3_prepare_v2(db,sql,-1,&stmt,NULL))
     481                 :             :         {
     482                 :           0 :                 status = FAILURE;
     483                 :             :         }
     484                 :             : 
     485         [ +  - ]:          16 :         if(SUCCESS == status)
     486                 :             :         {
     487                 :          16 :                 int rc = sqlite3_step(stmt);
     488                 :             : 
     489         [ +  - ]:          16 :                 if(SQLITE_ROW == rc)
     490                 :             :                 {
     491                 :          16 :                         *count_out = sqlite3_column_int(stmt,0);
     492                 :          16 :                         rc = sqlite3_step(stmt);
     493                 :             : 
     494         [ -  + ]:          16 :                         if(SQLITE_DONE != rc)
     495                 :             :                         {
     496                 :           0 :                                 status = FAILURE;
     497                 :             :                         }
     498                 :             :                 } else {
     499                 :           0 :                         status = FAILURE;
     500                 :             :                 }
     501                 :             :         }
     502                 :             : 
     503                 :          16 :         (void)sqlite3_finalize(stmt);
     504                 :             : 
     505                 :          16 :         (void)sqlite3_close(db);
     506                 :             : 
     507                 :          16 :         return(status);
     508                 :             : }
     509                 :             : 
     510                 :             : /**
     511                 :             :  * @brief Read number of files rows whose stat blob has exact size
     512                 :             :  *
     513                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
     514                 :             :  * @param[in] blob_size Expected stat blob size
     515                 :             :  * @param[out] count_out Output row count
     516                 :             :  *
     517                 :             :  * @return Return status code:
     518                 :             :  *         - SUCCESS: Count value was read
     519                 :             :  *         - FAILURE: Validation, DB access, bind, or query execution failed
     520                 :             :  */
     521                 :           4 : Return db_read_files_count_with_blob_size(
     522                 :             :         const char *db_filename,
     523                 :             :         const int  blob_size,
     524                 :             :         int        *count_out)
     525                 :             : {
     526                 :             :         /* Status returned by this function through provide()
     527                 :             :            Default value assumes successful completion */
     528                 :           4 :         Return status = SUCCESS;
     529                 :           4 :         sqlite3 *db = NULL;
     530                 :           4 :         sqlite3_stmt *stmt = NULL;
     531                 :           4 :         const char *sql = "SELECT COUNT(*) FROM files WHERE length(stat) = ?1;";
     532                 :             : 
     533   [ +  -  +  -  :           4 :         if(db_filename == NULL || blob_size < 0 || count_out == NULL)
                   -  + ]
     534                 :             :         {
     535                 :           0 :                 status = FAILURE;
     536                 :             :         }
     537                 :             : 
     538         [ +  - ]:           4 :         if(SUCCESS == status)
     539                 :             :         {
     540                 :           4 :                 status = open_db_from_tmpdir(db_filename,SQLITE_OPEN_READONLY,&db);
     541                 :             :         }
     542                 :             : 
     543   [ +  -  -  + ]:           4 :         if(SUCCESS == status && SQLITE_OK != sqlite3_prepare_v2(db,sql,-1,&stmt,NULL))
     544                 :             :         {
     545                 :           0 :                 status = FAILURE;
     546                 :             :         }
     547                 :             : 
     548   [ +  -  -  + ]:           4 :         if(SUCCESS == status && SQLITE_OK != sqlite3_bind_int(stmt,1,blob_size))
     549                 :             :         {
     550                 :           0 :                 status = FAILURE;
     551                 :             :         }
     552                 :             : 
     553         [ +  - ]:           4 :         if(SUCCESS == status)
     554                 :             :         {
     555                 :           4 :                 int rc = sqlite3_step(stmt);
     556                 :             : 
     557         [ +  - ]:           4 :                 if(SQLITE_ROW == rc)
     558                 :             :                 {
     559                 :           4 :                         *count_out = sqlite3_column_int(stmt,0);
     560                 :           4 :                         rc = sqlite3_step(stmt);
     561                 :             : 
     562         [ -  + ]:           4 :                         if(SQLITE_DONE != rc)
     563                 :             :                         {
     564                 :           0 :                                 status = FAILURE;
     565                 :             :                         }
     566                 :             :                 } else {
     567                 :           0 :                         status = FAILURE;
     568                 :             :                 }
     569                 :             :         }
     570                 :             : 
     571                 :           4 :         (void)sqlite3_finalize(stmt);
     572                 :             : 
     573                 :           4 :         (void)sqlite3_close(db);
     574                 :             : 
     575                 :           4 :         return(status);
     576                 :             : }
     577                 :             : 
     578                 :             : /**
     579                 :             :  * @brief Read db_version value from metadata table
     580                 :             :  *
     581                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
     582                 :             :  * @param[out] db_version_out Output database version
     583                 :             :  *
     584                 :             :  * @return Return status code:
     585                 :             :  *         - SUCCESS: Version value was read
     586                 :             :  *         - FAILURE: Validation, DB access, or query execution failed
     587                 :             :  */
     588                 :          10 : Return read_db_version_from_metadata(
     589                 :             :         const char *db_filename,
     590                 :             :         int        *db_version_out)
     591                 :             : {
     592                 :             :         /* Status returned by this function through provide()
     593                 :             :            Default value assumes successful completion */
     594                 :          10 :         Return status = SUCCESS;
     595                 :          10 :         sqlite3 *db = NULL;
     596                 :          10 :         sqlite3_stmt *stmt = NULL;
     597                 :          10 :         const char *sql = "SELECT db_version FROM metadata LIMIT 1;";
     598                 :             : 
     599   [ +  -  -  + ]:          10 :         if(db_filename == NULL || db_version_out == NULL)
     600                 :             :         {
     601                 :           0 :                 status = FAILURE;
     602                 :             :         }
     603                 :             : 
     604         [ +  - ]:          10 :         if(SUCCESS == status)
     605                 :             :         {
     606                 :          10 :                 status = open_db_from_tmpdir(db_filename,SQLITE_OPEN_READONLY,&db);
     607                 :             :         }
     608                 :             : 
     609   [ +  -  -  + ]:          10 :         if(SUCCESS == status && SQLITE_OK != sqlite3_prepare_v2(db,sql,-1,&stmt,NULL))
     610                 :             :         {
     611                 :           0 :                 status = FAILURE;
     612                 :             :         }
     613                 :             : 
     614         [ +  - ]:          10 :         if(SUCCESS == status)
     615                 :             :         {
     616                 :          10 :                 int rc = sqlite3_step(stmt);
     617                 :             : 
     618         [ +  - ]:          10 :                 if(SQLITE_ROW == rc)
     619                 :             :                 {
     620                 :          10 :                         *db_version_out = sqlite3_column_int(stmt,0);
     621                 :          10 :                         rc = sqlite3_step(stmt);
     622                 :             : 
     623         [ -  + ]:          10 :                         if(SQLITE_DONE != rc)
     624                 :             :                         {
     625                 :           0 :                                 status = FAILURE;
     626                 :             :                         }
     627                 :             :                 } else {
     628                 :           0 :                         status = FAILURE;
     629                 :             :                 }
     630                 :             :         }
     631                 :             : 
     632                 :          10 :         (void)sqlite3_finalize(stmt);
     633                 :             : 
     634                 :          10 :         (void)sqlite3_close(db);
     635                 :             : 
     636                 :          10 :         return(status);
     637                 :             : }
     638                 :             : 
     639                 :             : /**
     640                 :             :  * @brief Update db_version value in metadata table
     641                 :             :  *
     642                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
     643                 :             :  * @param[in] db_version Version value to store
     644                 :             :  *
     645                 :             :  * @return Return status code:
     646                 :             :  *         - SUCCESS: Version value was updated
     647                 :             :  *         - FAILURE: Validation, DB access, bind, step, or change check failed
     648                 :             :  */
     649                 :           2 : Return set_db_version_in_metadata(
     650                 :             :         const char *db_filename,
     651                 :             :         const int  db_version)
     652                 :             : {
     653                 :             :         /* Status returned by this function through provide()
     654                 :             :            Default value assumes successful completion */
     655                 :           2 :         Return status = SUCCESS;
     656                 :           2 :         sqlite3 *db = NULL;
     657                 :           2 :         sqlite3_stmt *stmt = NULL;
     658                 :           2 :         const char *sql = "UPDATE metadata SET db_version = ?1;";
     659                 :             : 
     660   [ +  -  -  + ]:           2 :         if(db_filename == NULL || db_version < 0)
     661                 :             :         {
     662                 :           0 :                 status = FAILURE;
     663                 :             :         }
     664                 :             : 
     665         [ +  - ]:           2 :         if(SUCCESS == status)
     666                 :             :         {
     667                 :           2 :                 status = open_db_from_tmpdir(db_filename,SQLITE_OPEN_READWRITE,&db);
     668                 :             :         }
     669                 :             : 
     670   [ +  -  -  + ]:           2 :         if(SUCCESS == status && SQLITE_OK != sqlite3_prepare_v2(db,sql,-1,&stmt,NULL))
     671                 :             :         {
     672                 :           0 :                 status = FAILURE;
     673                 :             :         }
     674                 :             : 
     675   [ +  -  -  + ]:           2 :         if(SUCCESS == status && SQLITE_OK != sqlite3_bind_int(stmt,1,db_version))
     676                 :             :         {
     677                 :           0 :                 status = FAILURE;
     678                 :             :         }
     679                 :             : 
     680   [ +  -  -  + ]:           2 :         if(SUCCESS == status && SQLITE_DONE != sqlite3_step(stmt))
     681                 :             :         {
     682                 :           0 :                 status = FAILURE;
     683                 :             :         }
     684                 :             : 
     685   [ +  -  -  + ]:           2 :         if(SUCCESS == status && sqlite3_changes(db) < 1)
     686                 :             :         {
     687                 :           0 :                 status = FAILURE;
     688                 :             :         }
     689                 :             : 
     690                 :           2 :         (void)sqlite3_finalize(stmt);
     691                 :             : 
     692                 :           2 :         (void)sqlite3_close(db);
     693                 :             : 
     694                 :           2 :         return(status);
     695                 :             : }
     696                 :             : 
     697                 :             : /**
     698                 :             :  * @brief Read raw stat blob for a specific files row ID
     699                 :             :  *
     700                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
     701                 :             :  * @param[in] row_id Row ID in files table
     702                 :             :  * @param[out] blob_out Optional output buffer for blob bytes
     703                 :             :  * @param[in] blob_out_size Size of blob_out buffer in bytes
     704                 :             :  * @param[out] blob_size_out Output blob size from DB
     705                 :             :  *
     706                 :             :  * @return Return status code:
     707                 :             :  *         - SUCCESS: Blob size and optional bytes were read
     708                 :             :  *         - FAILURE: Validation, DB access, bind, or row parsing failed
     709                 :             :  */
     710                 :           8 : Return db_read_stat_blob_by_row_id(
     711                 :             :         const char          *db_filename,
     712                 :             :         const sqlite3_int64 row_id,
     713                 :             :         unsigned char       *blob_out,
     714                 :             :         const size_t        blob_out_size,
     715                 :             :         int                 *blob_size_out)
     716                 :             : {
     717                 :             :         /* Status returned by this function through provide()
     718                 :             :            Default value assumes successful completion */
     719                 :           8 :         Return status = SUCCESS;
     720                 :           8 :         sqlite3 *db = NULL;
     721                 :           8 :         sqlite3_stmt *stmt = NULL;
     722                 :           8 :         const char *sql = "SELECT stat FROM files WHERE ID = ?1;";
     723                 :             : 
     724   [ +  -  -  + ]:           8 :         if(db_filename == NULL || blob_size_out == NULL)
     725                 :             :         {
     726                 :           0 :                 status = FAILURE;
     727                 :             :         }
     728                 :             : 
     729         [ +  - ]:           8 :         if(SUCCESS == status)
     730                 :             :         {
     731                 :           8 :                 status = open_db_from_tmpdir(db_filename,SQLITE_OPEN_READONLY,&db);
     732                 :             :         }
     733                 :             : 
     734   [ +  -  -  + ]:           8 :         if(SUCCESS == status && SQLITE_OK != sqlite3_prepare_v2(db,sql,-1,&stmt,NULL))
     735                 :             :         {
     736                 :           0 :                 status = FAILURE;
     737                 :             :         }
     738                 :             : 
     739   [ +  -  -  + ]:           8 :         if(SUCCESS == status && SQLITE_OK != sqlite3_bind_int64(stmt,1,row_id))
     740                 :             :         {
     741                 :           0 :                 status = FAILURE;
     742                 :             :         }
     743                 :             : 
     744         [ +  - ]:           8 :         if(SUCCESS == status)
     745                 :             :         {
     746                 :           8 :                 int rc = sqlite3_step(stmt);
     747                 :             : 
     748         [ +  - ]:           8 :                 if(SQLITE_ROW == rc)
     749                 :             :                 {
     750                 :           8 :                         const void *blob = sqlite3_column_blob(stmt,0);
     751                 :           8 :                         const int blob_size = sqlite3_column_bytes(stmt,0);
     752                 :             : 
     753         [ -  + ]:           8 :                         if(blob_size < 0)
     754                 :             :                         {
     755                 :           0 :                                 status = FAILURE;
     756                 :             :                         }
     757                 :             : 
     758   [ +  -  +  -  :           8 :                         if(SUCCESS == status && blob_size > 0 && blob == NULL)
                   -  + ]
     759                 :             :                         {
     760                 :           0 :                                 status = FAILURE;
     761                 :             :                         }
     762                 :             : 
     763   [ +  -  +  - ]:           8 :                         if(SUCCESS == status && blob_out != NULL)
     764                 :             :                         {
     765         [ -  + ]:           8 :                                 if((size_t)blob_size > blob_out_size)
     766                 :             :                                 {
     767                 :           0 :                                         status = FAILURE;
     768         [ +  - ]:           8 :                                 } else if(blob_size > 0){
     769                 :           8 :                                         memcpy(blob_out,blob,(size_t)blob_size);
     770                 :             :                                 }
     771                 :             :                         }
     772                 :             : 
     773         [ +  - ]:           8 :                         if(SUCCESS == status)
     774                 :             :                         {
     775                 :           8 :                                 *blob_size_out = blob_size;
     776                 :             :                         }
     777                 :             : 
     778                 :           8 :                         rc = sqlite3_step(stmt);
     779                 :             : 
     780   [ +  -  -  + ]:           8 :                         if(SUCCESS == status && SQLITE_DONE != rc)
     781                 :             :                         {
     782                 :           0 :                                 status = FAILURE;
     783                 :             :                         }
     784                 :             :                 } else {
     785                 :           0 :                         status = FAILURE;
     786                 :             :                 }
     787                 :             :         }
     788                 :             : 
     789                 :           8 :         (void)sqlite3_finalize(stmt);
     790                 :             : 
     791                 :           8 :         (void)sqlite3_close(db);
     792                 :             : 
     793                 :           8 :         return(status);
     794                 :             : }
     795                 :             : 
     796                 :             : /**
     797                 :             :  * @brief Read CmpctStat struct from files row by ID
     798                 :             :  *
     799                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
     800                 :             :  * @param[in] row_id Row ID in files table
     801                 :             :  * @param[out] stat_out Output compact stat structure
     802                 :             :  *
     803                 :             :  * @return Return status code:
     804                 :             :  *         - SUCCESS: CmpctStat value was read
     805                 :             :  *         - FAILURE: Validation, blob read, size check, or conversion failed
     806                 :             :  */
     807                 :           4 : Return db_read_cmpctstat_by_row_id(
     808                 :             :         const char          *db_filename,
     809                 :             :         const sqlite3_int64 row_id,
     810                 :             :         CmpctStat           *stat_out)
     811                 :             : {
     812                 :             :         /* Status returned by this function through provide()
     813                 :             :            Default value assumes successful completion */
     814                 :           4 :         Return status = SUCCESS;
     815                 :             :         unsigned char raw[sizeof(CmpctStat)];
     816                 :           4 :         int blob_size = 0;
     817                 :             : 
     818         [ -  + ]:           4 :         if(stat_out == NULL)
     819                 :             :         {
     820                 :           0 :                 status = FAILURE;
     821                 :             :         }
     822                 :             : 
     823         [ +  - ]:           4 :         if(SUCCESS == status)
     824                 :             :         {
     825                 :           4 :                 status = db_read_stat_blob_by_row_id(db_filename,row_id,raw,sizeof(raw),&blob_size);
     826                 :             :         }
     827                 :             : 
     828   [ +  -  -  + ]:           4 :         if(SUCCESS == status && blob_size != (int)sizeof(CmpctStat))
     829                 :             :         {
     830                 :           0 :                 status = FAILURE;
     831                 :             :         }
     832                 :             : 
     833         [ +  - ]:           4 :         if(SUCCESS == status)
     834                 :             :         {
     835                 :           4 :                 memcpy(stat_out,raw,sizeof(CmpctStat));
     836                 :             :         }
     837                 :             : 
     838                 :           4 :         return(status);
     839                 :             : }
     840                 :             : 
     841                 :             : /**
     842                 :             :  * @brief Read CmpctStat blob for one file by relative path
     843                 :             :  *
     844                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
     845                 :             :  * @param[in] relative_path Relative path key in files table
     846                 :             :  * @param[out] stat_out Output compact stat structure
     847                 :             :  *
     848                 :             :  * @return Return status code:
     849                 :             :  *         - SUCCESS: CmpctStat value was read
     850                 :             :  *         - FAILURE: Validation, DB access, blob size check, or row parsing failed
     851                 :             :  */
     852                 :          96 : Return db_read_cmpctstat_by_relative_path(
     853                 :             :         const char *db_filename,
     854                 :             :         const char *relative_path,
     855                 :             :         CmpctStat  *stat_out)
     856                 :             : {
     857                 :             :         /* Status returned by this function through provide()
     858                 :             :            Default value assumes successful completion */
     859                 :          96 :         Return status = SUCCESS;
     860                 :          96 :         sqlite3 *db = NULL;
     861                 :          96 :         sqlite3_stmt *stmt = NULL;
     862                 :          96 :         const char *sql = "SELECT stat FROM files WHERE relative_path = ?1;";
     863                 :             : 
     864   [ +  -  +  -  :          96 :         if(db_filename == NULL || relative_path == NULL || stat_out == NULL)
                   -  + ]
     865                 :             :         {
     866                 :           0 :                 status = FAILURE;
     867                 :             :         }
     868                 :             : 
     869         [ +  - ]:          96 :         if(SUCCESS == status)
     870                 :             :         {
     871                 :          96 :                 status = open_db_from_tmpdir(db_filename,SQLITE_OPEN_READONLY,&db);
     872                 :             :         }
     873                 :             : 
     874   [ +  -  -  + ]:          96 :         if(SUCCESS == status && SQLITE_OK != sqlite3_prepare_v2(db,sql,-1,&stmt,NULL))
     875                 :             :         {
     876                 :           0 :                 status = FAILURE;
     877                 :             :         }
     878                 :             : 
     879   [ +  -  -  + ]:          96 :         if(SUCCESS == status && SQLITE_OK != sqlite3_bind_text(stmt,1,relative_path,(int)strlen(relative_path),SQLITE_TRANSIENT))
     880                 :             :         {
     881                 :           0 :                 status = FAILURE;
     882                 :             :         }
     883                 :             : 
     884         [ +  - ]:          96 :         if(SUCCESS == status)
     885                 :             :         {
     886                 :          96 :                 int rc = sqlite3_step(stmt);
     887                 :             : 
     888         [ +  - ]:          96 :                 if(rc == SQLITE_ROW)
     889                 :             :                 {
     890                 :          96 :                         const void *blob = sqlite3_column_blob(stmt,0);
     891                 :          96 :                         const int bytes = sqlite3_column_bytes(stmt,0);
     892                 :             : 
     893   [ +  -  -  + ]:          96 :                         if(blob == NULL || bytes != (int)sizeof(CmpctStat))
     894                 :             :                         {
     895                 :           0 :                                 status = FAILURE;
     896                 :             :                         } else {
     897                 :          96 :                                 memcpy(stat_out,blob,sizeof(CmpctStat));
     898                 :             :                         }
     899                 :             : 
     900                 :          96 :                         rc = sqlite3_step(stmt);
     901                 :             : 
     902   [ +  -  -  + ]:          96 :                         if(SUCCESS == status && rc != SQLITE_DONE)
     903                 :             :                         {
     904                 :           0 :                                 status = FAILURE;
     905                 :             :                         }
     906                 :             :                 } else {
     907                 :           0 :                         status = FAILURE;
     908                 :             :                 }
     909                 :             :         }
     910                 :             : 
     911                 :          96 :         (void)sqlite3_finalize(stmt);
     912                 :             : 
     913                 :          96 :         (void)sqlite3_close(db);
     914                 :             : 
     915                 :          96 :         return(status);
     916                 :             : }
     917                 :             : 
     918                 :             : /**
     919                 :             :  * @brief Check whether DB compact-stat timestamps match current file stat timestamps
     920                 :             :  *
     921                 :             :  * @param[in] db_stat Compact stat loaded from the database
     922                 :             :  * @param[in] file_stat Current filesystem stat structure
     923                 :             :  * @return `true` when both ctime and mtime fields match exactly, otherwise `false`
     924                 :             :  */
     925                 :          34 : bool cmpctstat_matches_stat_timestamps(
     926                 :             :         const CmpctStat   *db_stat,
     927                 :             :         const struct stat *file_stat)
     928                 :             : {
     929   [ +  -  -  + ]:          34 :         if(db_stat == NULL || file_stat == NULL)
     930                 :             :         {
     931                 :           0 :                 return(false);
     932                 :             :         }
     933                 :             : 
     934                 :          68 :         return(db_stat->mtim_tv_sec == file_stat->st_mtim.tv_sec &&
     935         [ +  + ]:          34 :                db_stat->mtim_tv_nsec == file_stat->st_mtim.tv_nsec &&
     936   [ +  -  +  - ]:          94 :                db_stat->ctim_tv_sec == file_stat->st_ctim.tv_sec &&
     937         [ +  - ]:          26 :                db_stat->ctim_tv_nsec == file_stat->st_ctim.tv_nsec);
     938                 :             : }
     939                 :             : 
     940                 :             : /**
     941                 :             :  * @brief Read final offset and SHA512 checksum for one file from files table
     942                 :             :  *
     943                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
     944                 :             :  * @param[in] relative_path Relative path key in files table
     945                 :             :  * @param[out] offset_out Output offset value, 0 when SQL value is NULL
     946                 :             :  * @param[out] sha512_out Output SHA512 bytes with SHA512_DIGEST_LENGTH size
     947                 :             :  *
     948                 :             :  * @return Return status code:
     949                 :             :  *         - SUCCESS: Row was found and outputs were filled
     950                 :             :  *         - FAILURE: Validation, DB access, missing row, or SHA512 blob size mismatch
     951                 :             :  */
     952                 :         108 : Return read_final_sha512_from_db(
     953                 :             :         const char    *db_filename,
     954                 :             :         const char    *relative_path,
     955                 :             :         sqlite3_int64 *offset_out,
     956                 :             :         unsigned char *sha512_out)
     957                 :             : {
     958                 :             :         /* Status returned by this function through provide()
     959                 :             :            Default value assumes successful completion */
     960                 :         108 :         Return status = SUCCESS;
     961                 :         108 :         sqlite3 *db = NULL;
     962                 :         108 :         sqlite3_stmt *stmt = NULL;
     963                 :         108 :         const char *sql = "SELECT offset, sha512 FROM files WHERE relative_path = ?1;";
     964                 :             : 
     965         [ +  - ]:         108 :         if(db_filename == NULL
     966         [ +  - ]:         108 :                 || relative_path == NULL
     967         [ +  - ]:         108 :                 || offset_out == NULL
     968         [ -  + ]:         108 :                 || sha512_out == NULL)
     969                 :             :         {
     970                 :           0 :                 status = FAILURE;
     971                 :             :         }
     972                 :             : 
     973         [ +  - ]:         108 :         if(SUCCESS == status)
     974                 :             :         {
     975                 :         108 :                 status = open_db_from_tmpdir(db_filename,SQLITE_OPEN_READONLY,&db);
     976                 :             :         }
     977                 :             : 
     978   [ +  -  -  + ]:         108 :         if((SUCCESS == status) && SQLITE_OK != sqlite3_prepare_v2(db,sql,-1,&stmt,NULL))
     979                 :             :         {
     980                 :           0 :                 status = FAILURE;
     981                 :             :         }
     982                 :             : 
     983   [ +  -  -  + ]:         108 :         if((SUCCESS == status) && SQLITE_OK != sqlite3_bind_text(stmt,1,relative_path,(int)strlen(relative_path),SQLITE_TRANSIENT))
     984                 :             :         {
     985                 :           0 :                 status = FAILURE;
     986                 :             :         }
     987                 :             : 
     988         [ +  - ]:         108 :         if(SUCCESS == status)
     989                 :             :         {
     990                 :         108 :                 const int step_rc = sqlite3_step(stmt);
     991                 :             : 
     992         [ -  + ]:         108 :                 if(step_rc != SQLITE_ROW)
     993                 :             :                 {
     994                 :           0 :                         status = FAILURE;
     995                 :             :                 }
     996                 :             :         }
     997                 :             : 
     998         [ +  - ]:         108 :         if(SUCCESS == status)
     999                 :             :         {
    1000         [ +  - ]:         108 :                 if(sqlite3_column_type(stmt,0) == SQLITE_NULL)
    1001                 :             :                 {
    1002                 :         108 :                         *offset_out = 0;
    1003                 :             :                 } else {
    1004                 :           0 :                         *offset_out = sqlite3_column_int64(stmt,0);
    1005                 :             :                 }
    1006                 :             : 
    1007                 :         108 :                 const void *sha512_blob = sqlite3_column_blob(stmt,1);
    1008                 :         108 :                 const int sha512_bytes = sqlite3_column_bytes(stmt,1);
    1009                 :             : 
    1010   [ +  -  -  + ]:         108 :                 if(sha512_blob == NULL || sha512_bytes != SHA512_DIGEST_LENGTH)
    1011                 :             :                 {
    1012                 :           0 :                         status = FAILURE;
    1013                 :             :                 } else {
    1014                 :         108 :                         memcpy(sha512_out,sha512_blob,(size_t)SHA512_DIGEST_LENGTH);
    1015                 :             :                 }
    1016                 :             : 
    1017                 :         108 :                 const int done_rc = sqlite3_step(stmt);
    1018                 :             : 
    1019   [ +  -  -  + ]:         108 :                 if((SUCCESS == status) && done_rc != SQLITE_DONE)
    1020                 :             :                 {
    1021                 :           0 :                         status = FAILURE;
    1022                 :             :                 }
    1023                 :             :         }
    1024                 :             : 
    1025                 :         108 :         (void)sqlite3_finalize(stmt);
    1026                 :             : 
    1027                 :         108 :         (void)sqlite3_close(db);
    1028                 :             : 
    1029                 :         108 :         return(status);
    1030                 :             : }
    1031                 :             : 
    1032                 :             : /**
    1033                 :             :  * @brief Verify that a final DB checksum matches the file on disk
    1034                 :             :  *
    1035                 :             :  * Reads the application's stored SHA512 digest, requires the final offset state
    1036                 :             :  * to be clear, computes the same file with the Monocypher oracle, and compares
    1037                 :             :  * the raw digest bytes
    1038                 :             :  *
    1039                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
    1040                 :             :  * @param[in] relative_path Relative path key in files table
    1041                 :             :  * @param[in] file_path Absolute path to the file being checked
    1042                 :             :  *
    1043                 :             :  * @return Return status code:
    1044                 :             :  *         - SUCCESS: Stored final checksum matches the file
    1045                 :             :  *         - FAILURE: Validation, DB read, non-final offset, hash, or compare failed
    1046                 :             :  */
    1047                 :           4 : Return db_final_sha512_matches_file(
    1048                 :             :         const char *db_filename,
    1049                 :             :         const char *relative_path,
    1050                 :             :         const char *file_path)
    1051                 :             : {
    1052                 :             :         /* Status returned by this function through provide()
    1053                 :             :            Default value assumes successful completion */
    1054                 :           4 :         Return status = SUCCESS;
    1055                 :           4 :         sqlite3_int64 final_offset = -1;
    1056                 :           4 :         unsigned char db_sha512[SHA512_DIGEST_LENGTH] = {0};
    1057                 :           4 :         unsigned char expected_sha512[SHA512_DIGEST_LENGTH] = {0};
    1058                 :             : 
    1059   [ +  -  +  -  :           4 :         if(db_filename == NULL || relative_path == NULL || file_path == NULL)
                   -  + ]
    1060                 :             :         {
    1061                 :           0 :                 status = FAILURE;
    1062                 :             :         }
    1063                 :             : 
    1064         [ +  - ]:           4 :         if(SUCCESS == status)
    1065                 :             :         {
    1066                 :           4 :                 status = read_final_sha512_from_db(db_filename,relative_path,&final_offset,db_sha512);
    1067                 :             :         }
    1068                 :             : 
    1069   [ +  -  -  + ]:           4 :         if(SUCCESS == status && final_offset != 0)
    1070                 :             :         {
    1071                 :           0 :                 status = FAILURE;
    1072                 :             :         }
    1073                 :             : 
    1074         [ +  - ]:           4 :         if(SUCCESS == status)
    1075                 :             :         {
    1076                 :           4 :                 status = compute_file_sha512_monocypher(file_path,expected_sha512);
    1077                 :             :         }
    1078                 :             : 
    1079   [ +  -  -  + ]:           4 :         if(SUCCESS == status && memcmp(db_sha512,expected_sha512,(size_t)SHA512_DIGEST_LENGTH) != 0)
    1080                 :             :         {
    1081                 :           0 :                 status = FAILURE;
    1082                 :             :         }
    1083                 :             : 
    1084                 :           4 :         return(status);
    1085                 :             : }
    1086                 :             : 
    1087                 :             : /**
    1088                 :             :  * @brief Set files.sha512 to NULL for one row in the database
    1089                 :             :  *
    1090                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
    1091                 :             :  * @param[in] relative_path Relative path key in files table
    1092                 :             :  *
    1093                 :             :  * @return Return status code:
    1094                 :             :  *         - SUCCESS: SHA512 value was set to NULL for at least one row
    1095                 :             :  *         - FAILURE: Validation, DB access, bind, step, or change check failed
    1096                 :             :  */
    1097                 :           2 : Return db_set_sha512_to_null(
    1098                 :             :         const char *db_filename,
    1099                 :             :         const char *relative_path)
    1100                 :             : {
    1101                 :             :         /* Status returned by this function through provide()
    1102                 :             :            Default value assumes successful completion */
    1103                 :           2 :         Return status = SUCCESS;
    1104                 :           2 :         sqlite3 *db = NULL;
    1105                 :           2 :         sqlite3_stmt *stmt = NULL;
    1106                 :           2 :         const char *sql = "UPDATE files SET sha512 = NULL WHERE relative_path = ?1;";
    1107                 :             : 
    1108   [ +  -  -  + ]:           2 :         if(db_filename == NULL || relative_path == NULL)
    1109                 :             :         {
    1110                 :           0 :                 status = FAILURE;
    1111                 :             :         }
    1112                 :             : 
    1113         [ +  - ]:           2 :         if(SUCCESS == status)
    1114                 :             :         {
    1115                 :           2 :                 status = open_db_from_tmpdir(db_filename,SQLITE_OPEN_READWRITE,&db);
    1116                 :             :         }
    1117                 :             : 
    1118   [ +  -  -  + ]:           2 :         if(SUCCESS == status && SQLITE_OK != sqlite3_prepare_v2(db,sql,-1,&stmt,NULL))
    1119                 :             :         {
    1120                 :           0 :                 status = FAILURE;
    1121                 :             :         }
    1122                 :             : 
    1123   [ +  -  -  + ]:           2 :         if(SUCCESS == status && SQLITE_OK != sqlite3_bind_text(stmt,1,relative_path,(int)strlen(relative_path),SQLITE_TRANSIENT))
    1124                 :             :         {
    1125                 :           0 :                 status = FAILURE;
    1126                 :             :         }
    1127                 :             : 
    1128   [ +  -  -  + ]:           2 :         if(SUCCESS == status && SQLITE_DONE != sqlite3_step(stmt))
    1129                 :             :         {
    1130                 :           0 :                 status = FAILURE;
    1131                 :             :         }
    1132                 :             : 
    1133   [ +  -  -  + ]:           2 :         if(SUCCESS == status && sqlite3_changes(db) < 1)
    1134                 :             :         {
    1135                 :           0 :                 status = FAILURE;
    1136                 :             :         }
    1137                 :             : 
    1138                 :           2 :         (void)sqlite3_finalize(stmt);
    1139                 :             : 
    1140                 :           2 :         (void)sqlite3_close(db);
    1141                 :             : 
    1142                 :           2 :         return(status);
    1143                 :             : }
    1144                 :             : 
    1145                 :             : /**
    1146                 :             :  * @brief Corrupt stored SHA512 bytes for one files-table row
    1147                 :             :  *
    1148                 :             :  * The row is selected by relative_path and the SHA512 blob is modified in a
    1149                 :             :  * deterministic way while keeping the SQL value non-NULL. This is useful for
    1150                 :             :  * tests that need a database checksum mismatch without changing fixture files
    1151                 :             :  *
    1152                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
    1153                 :             :  * @param[in] relative_path Relative path key in files table
    1154                 :             :  *
    1155                 :             :  * @return Return status code:
    1156                 :             :  *         - SUCCESS: SHA512 value was updated for at least one row
    1157                 :             :  *         - FAILURE: Validation, DB access, bind, step, or change check failed
    1158                 :             :  */
    1159                 :           2 : Return db_tamper_sha512(
    1160                 :             :         const char *db_filename,
    1161                 :             :         const char *relative_path)
    1162                 :             : {
    1163                 :             :         /* Status returned by this function through provide()
    1164                 :             :            Default value assumes successful completion */
    1165                 :           2 :         Return status = SUCCESS;
    1166                 :           2 :         sqlite3 *db = NULL;
    1167                 :           2 :         sqlite3_stmt *stmt = NULL;
    1168                 :           2 :         const char *sql = "UPDATE files SET sha512 = (substr(sha512,1,2) || X'BEEF' || substr(sha512,5)) "
    1169                 :             :                 "WHERE relative_path = ?1;";
    1170                 :             : 
    1171   [ +  -  -  + ]:           2 :         if(db_filename == NULL || relative_path == NULL)
    1172                 :             :         {
    1173                 :           0 :                 status = FAILURE;
    1174                 :             :         }
    1175                 :             : 
    1176         [ +  - ]:           2 :         if(SUCCESS == status)
    1177                 :             :         {
    1178                 :           2 :                 status = open_db_from_tmpdir(db_filename,SQLITE_OPEN_READWRITE,&db);
    1179                 :             :         }
    1180                 :             : 
    1181   [ +  -  -  + ]:           2 :         if(SUCCESS == status && SQLITE_OK != sqlite3_prepare_v2(db,sql,-1,&stmt,NULL))
    1182                 :             :         {
    1183                 :           0 :                 status = FAILURE;
    1184                 :             :         }
    1185                 :             : 
    1186   [ +  -  -  + ]:           2 :         if(SUCCESS == status && SQLITE_OK != sqlite3_bind_text(stmt,1,relative_path,(int)strlen(relative_path),SQLITE_TRANSIENT))
    1187                 :             :         {
    1188                 :           0 :                 status = FAILURE;
    1189                 :             :         }
    1190                 :             : 
    1191   [ +  -  -  + ]:           2 :         if(SUCCESS == status && SQLITE_DONE != sqlite3_step(stmt))
    1192                 :             :         {
    1193                 :           0 :                 status = FAILURE;
    1194                 :             :         }
    1195                 :             : 
    1196   [ +  -  -  + ]:           2 :         if(SUCCESS == status && sqlite3_changes(db) < 1)
    1197                 :             :         {
    1198                 :           0 :                 status = FAILURE;
    1199                 :             :         }
    1200                 :             : 
    1201                 :           2 :         (void)sqlite3_finalize(stmt);
    1202                 :             : 
    1203                 :           2 :         (void)sqlite3_close(db);
    1204                 :             : 
    1205                 :           2 :         return(status);
    1206                 :             : }
    1207                 :             : 
    1208                 :             : /**
    1209                 :             :  * @brief Read intermediate offset and mdContext size for one file from DB
    1210                 :             :  *
    1211                 :             :  * @param[in] db_filename DB file name relative to TMPDIR
    1212                 :             :  * @param[in] relative_path File path in DB relative_path column
    1213                 :             :  * @param[out] offset_out Output offset value from files table
    1214                 :             :  * @param[out] md_context_bytes_out Output byte size of mdContext column
    1215                 :             :  *
    1216                 :             :  * @return Return status code:
    1217                 :             :  *         - SUCCESS: Row was found and outputs were filled
    1218                 :             :  *         - FAILURE: Validation, DB access, or row parsing failed
    1219                 :             :  */
    1220                 :          14 : Return read_resume_state_from_db(
    1221                 :             :         const char    *db_filename,
    1222                 :             :         const char    *relative_path,
    1223                 :             :         sqlite3_int64 *offset_out,
    1224                 :             :         int           *md_context_bytes_out)
    1225                 :             : {
    1226                 :             :         /* Status returned by this function through provide()
    1227                 :             :            Default value assumes successful completion */
    1228                 :          14 :         Return status = SUCCESS;
    1229                 :          14 :         sqlite3 *db = NULL;
    1230                 :          14 :         sqlite3_stmt *stmt = NULL;
    1231                 :          14 :         const char *sql = "SELECT offset, mdContext FROM files WHERE relative_path = ?1;";
    1232                 :          14 :         m_create(char,db_path,MEMORY_STRING);
    1233                 :             : 
    1234   [ +  -  +  - ]:          14 :         if(SUCCESS == status && (db_filename == NULL
    1235         [ +  - ]:          14 :                 || relative_path == NULL
    1236         [ +  - ]:          14 :                 || offset_out == NULL
    1237         [ -  + ]:          14 :                 || md_context_bytes_out == NULL))
    1238                 :             :         {
    1239                 :           0 :                 status = FAILURE;
    1240                 :             :         }
    1241                 :             : 
    1242         [ +  - ]:          14 :         if(SUCCESS == status)
    1243                 :             :         {
    1244                 :          14 :                 status = construct_path(db_filename,db_path);
    1245                 :             :         }
    1246                 :             : 
    1247   [ +  -  -  + ]:          14 :         if(SUCCESS == status && SQLITE_OK != sqlite3_open_v2(m_text(db_path),&db,SQLITE_OPEN_READONLY,NULL))
    1248                 :             :         {
    1249                 :           0 :                 status = FAILURE;
    1250                 :             :         }
    1251                 :             : 
    1252   [ +  -  -  + ]:          14 :         if(SUCCESS == status && SQLITE_OK != sqlite3_prepare_v2(db,sql,-1,&stmt,NULL))
    1253                 :             :         {
    1254                 :           0 :                 status = FAILURE;
    1255                 :             :         }
    1256                 :             : 
    1257   [ +  -  -  + ]:          14 :         if(SUCCESS == status && SQLITE_OK != sqlite3_bind_text(stmt,1,relative_path,(int)strlen(relative_path),SQLITE_TRANSIENT))
    1258                 :             :         {
    1259                 :           0 :                 status = FAILURE;
    1260                 :             :         }
    1261                 :             : 
    1262         [ +  - ]:          14 :         if(SUCCESS == status)
    1263                 :             :         {
    1264                 :          14 :                 const int step_rc = sqlite3_step(stmt);
    1265                 :             : 
    1266         [ -  + ]:          14 :                 if(step_rc != SQLITE_ROW)
    1267                 :             :                 {
    1268                 :           0 :                         status = FAILURE;
    1269                 :             :                 }
    1270                 :             :         }
    1271                 :             : 
    1272         [ +  - ]:          14 :         if(SUCCESS == status)
    1273                 :             :         {
    1274         [ +  + ]:          14 :                 if(sqlite3_column_type(stmt,0) == SQLITE_NULL)
    1275                 :             :                 {
    1276                 :           7 :                         *offset_out = 0;
    1277                 :             :                 } else {
    1278                 :           7 :                         *offset_out = sqlite3_column_int64(stmt,0);
    1279                 :             :                 }
    1280                 :             : 
    1281                 :          14 :                 *md_context_bytes_out = sqlite3_column_bytes(stmt,1);
    1282                 :             : 
    1283                 :          14 :                 const int done_rc = sqlite3_step(stmt);
    1284                 :             : 
    1285         [ -  + ]:          14 :                 if(done_rc != SQLITE_DONE)
    1286                 :             :                 {
    1287                 :           0 :                         status = FAILURE;
    1288                 :             :                 }
    1289                 :             :         }
    1290                 :             : 
    1291                 :          14 :         (void)sqlite3_finalize(stmt);
    1292                 :             : 
    1293                 :          14 :         (void)sqlite3_close(db);
    1294                 :             : 
    1295                 :          14 :         m_del(db_path);
    1296                 :             : 
    1297                 :          14 :         return(status);
    1298                 :             : }
    1299                 :             : 
    1300                 :             : /**
    1301                 :             :  * @brief Verify that a files-table row has no partial resume state
    1302                 :             :  *
    1303                 :             :  * A completed hash must have a cleared offset and an empty mdContext blob.
    1304                 :             :  * This helper intentionally treats any non-empty value as a failure because it
    1305                 :             :  * would make a future update look resumable
    1306                 :             :  *
    1307                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
    1308                 :             :  * @param[in] relative_path Relative path key in files table
    1309                 :             :  *
    1310                 :             :  * @return Return status code:
    1311                 :             :  *         - SUCCESS: Offset and mdContext are both clear
    1312                 :             :  *         - FAILURE: Validation, DB read, non-zero offset, or non-empty context failed
    1313                 :             :  */
    1314                 :           7 : Return db_resume_state_is_empty(
    1315                 :             :         const char *db_filename,
    1316                 :             :         const char *relative_path)
    1317                 :             : {
    1318                 :             :         /* Status returned by this function through provide()
    1319                 :             :            Default value assumes successful completion */
    1320                 :           7 :         Return status = SUCCESS;
    1321                 :           7 :         sqlite3_int64 offset = -1;
    1322                 :           7 :         int md_context_bytes = -1;
    1323                 :             : 
    1324   [ +  -  -  + ]:           7 :         if(db_filename == NULL || relative_path == NULL)
    1325                 :             :         {
    1326                 :           0 :                 status = FAILURE;
    1327                 :             :         }
    1328                 :             : 
    1329         [ +  - ]:           7 :         if(SUCCESS == status)
    1330                 :             :         {
    1331                 :           7 :                 status = read_resume_state_from_db(db_filename,relative_path,&offset,&md_context_bytes);
    1332                 :             :         }
    1333                 :             : 
    1334   [ +  -  -  + ]:           7 :         if(SUCCESS == status && offset != 0)
    1335                 :             :         {
    1336                 :           0 :                 status = FAILURE;
    1337                 :             :         }
    1338                 :             : 
    1339   [ +  -  -  + ]:           7 :         if(SUCCESS == status && md_context_bytes != 0)
    1340                 :             :         {
    1341                 :           0 :                 status = FAILURE;
    1342                 :             :         }
    1343                 :             : 
    1344                 :           7 :         return(status);
    1345                 :             : }
        

Generated by: LCOV version 2.0-1