LCOV - code coverage report
Current view: top level - src - file_list.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 85.0 % 301 256
Test Date: 2026-10-20 14:27:43 Functions: 100.0 % 2 2
Branches: 80.5 % 293 236

             Branch data     Line data    Source code
       1                 :             : #include "precizer.h"
       2                 :             : #include <errno.h>
       3                 :             : 
       4                 :             : /**
       5                 :             :  * @brief Compare two FTS entries by filename
       6                 :             :  * @param first Pointer to first FTSENT structure
       7                 :             :  * @param second Pointer to second FTSENT structure
       8                 :             :  * @return Integer less than, equal to, or greater than zero if first is found,
       9                 :             :  *         respectively, to be less than, to match, or be greater than second
      10                 :             :  */
      11                 :        9894 : static int compare_by_name(
      12                 :             : #ifdef __MSYS__
      13                 :             :         const FTSENT * const *first,
      14                 :             :         const FTSENT * const *second)
      15                 :             : #else
      16                 :             :         const FTSENT **first,
      17                 :             :         const FTSENT **second)
      18                 :             : #endif
      19                 :             : {
      20                 :        9894 :         return strcmp((*first)->fts_name,(*second)->fts_name);
      21                 :             : }
      22                 :             : 
      23                 :             : /**
      24                 :             :  * @brief Traverse configured roots and process files for one pass
      25                 :             :  *
      26                 :             :  * The function is a no-op in compare mode. During normal scanning it walks each
      27                 :             :  * root in `config->roots` with a separate FTS stream. Paths written to the
      28                 :             :  * database are kept relative to the current root, so the same traversal logic
      29                 :             :  * works for one root or for several positional directory arguments
      30                 :             :  *
      31                 :             :  * `summary->stats_only_pass` controls how much work is done. When it is `true`,
      32                 :             :  * traversal only counts directories, files, symlinks, and allocated size, and
      33                 :             :  * that pass is skipped unless progress output needs those counters. When it is
      34                 :             :  * `false`, regular files can be hashed, compared with existing database rows,
      35                 :             :  * inserted, updated, or reported according to ignore/include and checksum
      36                 :             :  * locking settings. File writes are committed by the shared checkpoint interval
      37                 :             :  * and before this function returns. A graceful interruption commits pending
      38                 :             :  * work, while a technical failure rolls back only the current batch.
      39                 :             :  * Main-pass timing includes traversal setup, file processing, and the final
      40                 :             :  * commit or rollback, but excludes the preliminary counting pass
      41                 :             :  *
      42                 :             :  * For example, after `precizer src tests`, `config->roots` contains two roots.
      43                 :             :  * This function traverses `src` first, closes that FTS stream, then traverses
      44                 :             :  * `tests`
      45                 :             :  *
      46                 :             :  * @param summary Traversal state that is reset and populated by this call
      47                 :             :  * @return `SUCCESS` when traversal completes, `WARNING` when a non-fatal
      48                 :             :  *         checksum-lock violation is reported, or `FAILURE` for hard traversal,
      49                 :             :  *         memory, hashing, or database errors
      50                 :             :  */
      51                 :        1324 : Return file_list(TraversalSummary *summary)
      52                 :             : {
      53                 :             :         /* Status returned by this function through provide()
      54                 :             :            Default value assumes successful completion */
      55                 :        1324 :         Return status = SUCCESS;
      56                 :             : 
      57                 :             :         // Don't do anything
      58         [ +  + ]:        1324 :         if(config->compare == true)
      59                 :             :         {
      60                 :         284 :                 provide(status);
      61                 :             :         }
      62                 :             : 
      63                 :             : #ifdef TESTITALL_TEST_HOOKS
      64                 :             :         {
      65                 :        1040 :                 const char *skip = getenv("TESTITALL_TEST_ENV_SKIP_FILE_LIST");
      66                 :             : 
      67   [ +  +  +  + ]:        1040 :                 if(skip != NULL && strcmp(skip,"1") == 0)
      68                 :             :                 {
      69                 :          24 :                         provide(status);
      70                 :             :                 }
      71                 :             :         }
      72                 :             : #endif
      73                 :             : 
      74   [ +  +  +  + ]:        1016 :         if(config->progress == false && summary->stats_only_pass == true)
      75                 :             :         {
      76                 :             :                 // Don't do anything
      77                 :         406 :                 provide(status);
      78                 :             :         }
      79                 :             : 
      80                 :             :         // Measure the whole pass, including setup and final transaction cleanup.
      81                 :             :         // This field holds the start time until transaction cleanup finishes
      82                 :         610 :         summary->scan_elapsed_ns = cur_time_monotonic_ns();
      83                 :             : 
      84                 :             :         // Per-pass state used to control visible output and checksum-lock warnings
      85                 :             : 
      86                 :             :         // Print traversal/update banners only once
      87                 :         610 :         bool first_iteration = true;
      88                 :             : 
      89                 :             :         // Signals integrity issues for locked files
      90                 :         610 :         bool lock_checksum_violation_detected = false;
      91                 :             : 
      92                 :         610 :         FTS *file_systems = NULL;
      93                 :         610 :         FTSENT *p = NULL;
      94                 :             : 
      95                 :         610 :         int fts_options = FTS_PHYSICAL;
      96                 :             : 
      97                 :             : #ifdef FTS_NOCHDIR
      98                 :             :         /*
      99                 :             :          * Keep FTS from changing the process working directory during traversal.
     100                 :             :          * File operations use the opened root descriptor and root-relative paths,
     101                 :             :          * so traversal does not need process-wide working directory changes
     102                 :             :          */
     103                 :         610 :         fts_options |= FTS_NOCHDIR;
     104                 :             : #endif
     105                 :             : 
     106         [ +  + ]:         610 :         if(config->one_file_system == true)
     107                 :             :         {
     108                 :           8 :                 fts_options |= FTS_XDEV;
     109                 :             :         }
     110                 :             : 
     111                 :             :         // Reset traversal counters and timing for this pass.
     112                 :         610 :         summary->count_dirs = 0;
     113                 :             : 
     114                 :             :         // Number of regular files seen in this pass.
     115                 :         610 :         summary->count_files = 0;
     116                 :             : 
     117                 :             :         // Number of symlinks seen in this pass.
     118                 :         610 :         summary->count_symlnks = 0;
     119                 :             : 
     120                 :             :         // Sum of allocated bytes for encountered files.
     121                 :         610 :         summary->total_allocated_bytes = 0;
     122                 :             : 
     123                 :             :         // Sum of bytes hashed by SHA512 during this pass.
     124                 :         610 :         summary->total_hashed_bytes = 0;
     125                 :             : 
     126                 :             :         // Track whether any output was produced
     127                 :         610 :         summary->at_least_one_file_was_shown = false;
     128                 :             : 
     129                 :             :         // Root path currently being traversed by this pass.
     130                 :         610 :         summary->root = NULL;
     131                 :             : 
     132                 :             :         // Sum of per-file hashing elapsed time in nanoseconds.
     133                 :         610 :         summary->total_hashing_elapsed_ns = 0LL;
     134                 :             : 
     135                 :             : #if 0 // Disabled multi-root path index implementation
     136                 :             :         /**
     137                 :             :          * Path-prefix index reserved for the disabled per-file root mapping.
     138                 :             :          * Active traversal code currently builds each file path relative to the
     139                 :             :          * root being scanned and does not use this value
     140                 :             :          */
     141                 :             :         sqlite3_int64 runtime_root_path_index = -1;
     142                 :             : #endif
     143                 :             : 
     144                 :             :         // Limit recursion to the depth determined in config->maxdepth
     145         [ +  + ]:         610 :         if(config->maxdepth > -1)
     146                 :             :         {
     147                 :           8 :                 slog(EVERY,"Recursion depth limited to: %d\n",config->maxdepth);
     148                 :             :         }
     149                 :             : 
     150   [ +  +  +  - ]:         610 :         if(summary->stats_only_pass == true && config->progress == true)
     151                 :             :         {
     152                 :         102 :                 slog(EVERY,"File system traversal initiated to calculate file count and storage usage\n");
     153                 :             :         }
     154                 :             : 
     155                 :         610 :         bool continue_the_loop = true;
     156                 :             : 
     157                 :             :         // Allocate reusable buffers shared by entries in this traversal pass
     158                 :         610 :         m_create(unsigned char,file_buffer);
     159                 :         610 :         m_create(char,relative_path,MEMORY_STRING);
     160                 :             : 
     161         [ +  + ]:         610 :         if(summary->stats_only_pass == false)
     162                 :             :         {
     163                 :         508 :                 status = m_resize(file_buffer,FILE_READ_BUFFER_SIZE);
     164                 :             : 
     165         [ -  + ]:         508 :                 if(SUCCESS != status)
     166                 :             :                 {
     167                 :           0 :                         provide(status);
     168                 :             :                 }
     169                 :             : #ifdef TESTITALL_TEST_HOOKS
     170                 :         508 :                 signal_wait_at_point(1U);
     171                 :             : #endif
     172                 :             :         }
     173                 :             : 
     174                 :             :         // Open one FTS stream per traversal root
     175   [ +  +  +  + ]:        1840 :         m_string_array_foreach(conf(roots),root)
     176                 :             :         {
     177                 :         630 :                 summary->root = root;
     178                 :             : 
     179                 :         630 :                 char *root_path_text = m_data(char,root);
     180                 :             : 
     181         [ -  + ]:         630 :                 if(root_path_text == NULL)
     182                 :             :                 {
     183                 :           0 :                         slog(ERROR,"Unable to access root path descriptor\n");
     184                 :           0 :                         status = FAILURE;
     185                 :           4 :                         break;
     186                 :             :                 }
     187                 :             : 
     188                 :             :                 /*
     189                 :             :                  * fts_open() expects a NULL-terminated array of root path strings, even
     190                 :             :                  * when only one root is opened. file_list() processes configured roots
     191                 :             :                  * one by one, so this short array keeps the current FTS stream scoped to
     192                 :             :                  * the current root only
     193                 :             :                  */
     194                 :         630 :                 char *root_argv[] = {
     195                 :             :                         root_path_text,
     196                 :             :                         NULL
     197                 :             :                 };
     198                 :             : 
     199                 :             :                 /*
     200                 :             :                  * Descriptor for the current traversal root. Content-pass access checks
     201                 :             :                  * use it as the base directory for relative paths
     202                 :             :                  */
     203                 :         630 :                 int root_directory_fd = -1;
     204                 :             : 
     205                 :             :                 /*
     206                 :             :                  * The stats-only pass does not perform directory-relative access checks,
     207                 :             :                  * so it can traverse the root path directly through FTS. The content pass
     208                 :             :                  * needs a stable root descriptor because file and directory access checks
     209                 :             :                  * are made with paths relative to this root. If the root cannot be
     210                 :             :                  * opened, directory_open_root() reports and remembers the
     211                 :             :                  * warning, and this loop skips only the unavailable root before
     212                 :             :                  * continuing with the next configured traversal root
     213                 :             :                  */
     214         [ +  + ]:         630 :                 if(summary->stats_only_pass == false)
     215                 :             :                 {
     216                 :         528 :                         const FileAccessStatus root_access_status = directory_open_root(root,&root_directory_fd);
     217                 :             : 
     218         [ +  + ]:         528 :                         if(root_access_status != FILE_ACCESS_ALLOWED)
     219                 :             :                         {
     220                 :           4 :                                 continue;
     221                 :             :                         }
     222                 :             :                 }
     223                 :             : 
     224         [ -  + ]:         626 :                 if((file_systems = fts_open(root_argv,fts_options,compare_by_name)) == NULL)
     225                 :             :                 {
     226                 :           0 :                         slog(ERROR,"fts_open() error\n");
     227                 :           0 :                         status = FAILURE;
     228                 :           0 :                         break;
     229                 :             :                 }
     230                 :             : 
     231   [ +  +  +  - ]:       56590 :                 while((p = fts_read(file_systems)) != NULL && continue_the_loop == true)
     232                 :             :                 {
     233                 :             :                         /* Interrupt the loop smoothly */
     234                 :             :                         /* Interrupt when Ctrl+C */
     235         [ +  + ]:       55971 :                         if(global_interrupt_flag == true)
     236                 :             :                         {
     237                 :           4 :                                 break;
     238                 :             :                         }
     239                 :             : 
     240                 :             :                         /*
     241                 :             :                          * Check the active batch before processing the next entry. Commit when
     242                 :             :                          * the shared checkpoint interval has elapsed and stop traversal on failure
     243                 :             :                          */
     244         [ +  + ]:       55967 :                         if(config->file_transaction_active == true)
     245                 :             :                         {
     246                 :       24521 :                                 call(db_file_transaction_check(cur_time_monotonic_ns()));
     247                 :             : 
     248         [ -  + ]:       24521 :                                 if(FAILURE & status)
     249                 :             :                                 {
     250                 :           0 :                                         break;
     251                 :             :                                 }
     252                 :             :                         }
     253                 :             : 
     254                 :             :         #if 0 // Disabled multi-root path index implementation
     255                 :             :                         if(p->fts_level == FTS_ROOTLEVEL)
     256                 :             :                         {
     257                 :             :                                 // If several paths were passed as arguments,
     258                 :             :                                 // then the counting of the path prefix index
     259                 :             :                                 // will start from zero
     260                 :             :                                 if(SUCCESS != (status = db_get_runtime_root_path_index(config,
     261                 :             :                                         root,
     262                 :             :                                         &runtime_root_path_index)))
     263                 :             :                                 {
     264                 :             :                                         continue_the_loop = false;
     265                 :             :                                         break;
     266                 :             :                                 }
     267                 :             :                         }
     268                 :             : #endif
     269                 :             : 
     270   [ +  +  +  + ]:       55967 :                         if(config->maxdepth > -1 && p->fts_level > config->maxdepth + 1)
     271                 :             :                         {
     272         [ +  + ]:          84 :                                 if(p->fts_info == FTS_D)
     273                 :             :                                 {
     274                 :          28 :                                         (void)fts_set(file_systems,p,FTS_SKIP);
     275                 :             :                                 }
     276                 :             : 
     277                 :          84 :                                 continue;
     278                 :             :                         }
     279                 :             : 
     280   [ +  +  +  -  :       55883 :                         switch(p->fts_info)
                      + ]
     281                 :             :                         {
     282                 :       24302 :                                 case FTS_D:
     283                 :             :                                 {
     284                 :       24302 :                                         run(path_build_relative(relative_path,p));
     285                 :             : 
     286         [ -  + ]:       24302 :                                         if((TRIUMPH & status) == 0)
     287                 :             :                                         {
     288                 :           0 :                                                 continue_the_loop = false;
     289                 :           0 :                                                 break;
     290                 :             :                                         }
     291                 :             : 
     292                 :             :                                         // Captures files explicitly skipped or forced by regexp filters
     293                 :             :                                         // Ignored with --ignore= or admitted with --include=
     294                 :       24302 :                                         bool ignore = false;
     295                 :             : 
     296                 :             :                                         // Included with --include=
     297                 :       24302 :                                         bool include = false;
     298                 :             : 
     299                 :       24302 :                                         status = match_include_ignore(relative_path,&include,&ignore);
     300                 :             : 
     301         [ -  + ]:       24302 :                                         if((TRIUMPH & status) == 0)
     302                 :             :                                         {
     303                 :           0 :                                                 continue_the_loop = false;
     304                 :           0 :                                                 break;
     305                 :             :                                         }
     306                 :             : 
     307         [ +  + ]:       24302 :                                         if(summary->stats_only_pass == false)
     308                 :             :                                         {
     309                 :       19204 :                                                 directory_show(relative_path,
     310                 :             :                                                         &first_iteration,
     311                 :             :                                                         summary,
     312                 :             :                                                         ignore,
     313                 :             :                                                         include);
     314                 :             :                                         }
     315                 :             : 
     316         [ +  + ]:       24302 :                                         if(ignore == true)
     317                 :             :                                         {
     318                 :             :                                                 /*
     319                 :             :                                                  * Use FTS_SKIP for an ignored directory only when no --include
     320                 :             :                                                  * or --lock-checksum patterns were provided
     321                 :             :                                                  * When checksum locking is active, descendants of an ignored
     322                 :             :                                                  * directory may still need integrity checks or rehashing
     323                 :             :                                                  */
     324         [ +  + ]:         532 :                                                 if(config->include_specified == false
     325         [ +  + ]:         110 :                                                         && config->lock_checksum == NULL)
     326                 :             :                                                 {
     327                 :          30 :                                                         (void)fts_set(file_systems,p,FTS_SKIP);
     328                 :             :                                                 }
     329                 :         532 :                                                 break;
     330                 :             :                                         }
     331                 :             : 
     332         [ +  + ]:       23770 :                                         if(summary->stats_only_pass == false)
     333                 :             :                                         {
     334                 :             :                                                 // Check directory access and skip subtrees that cannot be entered
     335                 :       18856 :                                                 status = directory_access_verify(
     336                 :             :                                                         file_systems,
     337                 :             :                                                         p,
     338                 :             :                                                         root_directory_fd,
     339                 :             :                                                         relative_path,
     340                 :             :                                                         &first_iteration,
     341                 :             :                                                         summary);
     342                 :             :                                         }
     343                 :             : 
     344         [ -  + ]:       23770 :                                         if((TRIUMPH & status) == 0)
     345                 :             :                                         {
     346                 :           0 :                                                 continue_the_loop = false;
     347                 :           0 :                                                 break;
     348                 :             :                                         }
     349                 :             : 
     350                 :       23770 :                                         summary->count_dirs++;
     351                 :       23770 :                                         break;
     352                 :             :                                 }
     353                 :        6818 :                                 case FTS_F:
     354                 :             :                                 {
     355                 :        6818 :                                         summary->total_allocated_bytes += blocks_to_bytes(p->fts_statp->st_blocks);
     356                 :        6818 :                                         summary->count_files++;
     357                 :             : 
     358         [ +  + ]:        6818 :                                         if(summary->stats_only_pass == true)
     359                 :             :                                         {
     360                 :        1364 :                                                 continue;
     361                 :             :                                         }
     362                 :             : 
     363                 :             :                                         // Keep per-file processing state on the stack and attach
     364                 :             :                                         // the DB row that will be filled for this path
     365                 :        5454 :                                         File _file = {0};
     366                 :        5454 :                                         File *file = &_file;
     367                 :        5454 :                                         DBrow dbrow = {0};
     368                 :        5454 :                                         file->db = &dbrow;
     369                 :             : 
     370                 :        5454 :                                         run(path_build_relative(relative_path,p));
     371                 :             : 
     372         [ -  + ]:        5454 :                                         if((TRIUMPH & status) == 0)
     373                 :             :                                         {
     374                 :           0 :                                                 continue_the_loop = false;
     375                 :        5451 :                                                 break;
     376                 :             :                                         }
     377                 :             : 
     378                 :             :                                         /* Load the database row, if any, for this root-relative file path */
     379                 :             : #if 0 // Disabled multi-root path index implementation
     380                 :             :                                         run(db_read_file_data_from(file,&runtime_root_path_index,relative_path));
     381                 :             : #else
     382                 :        5454 :                                         run(db_read_file_data_from(file,relative_path));
     383                 :             : #endif
     384                 :             : 
     385         [ -  + ]:        5454 :                                         if(SUCCESS != status)
     386                 :             :                                         {
     387                 :           0 :                                                 continue_the_loop = false;
     388                 :           0 :                                                 break;
     389                 :             :                                         }
     390                 :             : 
     391                 :             :                                         /*
     392                 :             :                                          * Mutable row-existence flag for the current relative path.
     393                 :             :                                          * It starts with the state loaded before file processing and may become true after sha512sum() writes the first periodic checkpoint for a new file
     394                 :             :                                          */
     395                 :        5454 :                                         bool path_known = file->db->relative_path_was_in_db_before_processing == true;
     396                 :             : 
     397                 :             :                                         /*
     398                 :             :                                          * YES means the current relative path is checksum-locked and the file must be marked as protected.
     399                 :             :                                          * NO means the path is not checksum-locked, so the file remains a regular tracked file
     400                 :             :                                          */
     401                 :        5454 :                                         if(ask(path_check_locked_checksum(relative_path)))
     402                 :             :                                         {
     403                 :             :                                                 // Mark this file as checksum-locked
     404                 :         312 :                                                 file->locked_checksum_file = true;
     405                 :             :                                         }
     406                 :             : 
     407         [ -  + ]:        5454 :                                         if(SUCCESS != status)
     408                 :             :                                         {
     409                 :           0 :                                                 continue_the_loop = false;
     410                 :           0 :                                                 break;
     411                 :             :                                         }
     412                 :             : 
     413                 :             :                                         // Store the final --include/--ignore decision in the per-file state
     414                 :        5454 :                                         status = match_include_ignore(relative_path,&file->include,&file->ignore);
     415                 :             : 
     416         [ -  + ]:        5454 :                                         if(SUCCESS != status)
     417                 :             :                                         {
     418                 :           0 :                                                 continue_the_loop = false;
     419                 :           0 :                                                 break;
     420                 :             :                                         }
     421                 :             : 
     422                 :             :                                         // Ensure checksum-locked files stay visible even if matched by ignore
     423   [ +  +  +  + ]:        5454 :                                         if(file->ignore == true && file->locked_checksum_file == true)
     424                 :             :                                         {
     425                 :          28 :                                                 file->ignore = false;
     426                 :             :                                         }
     427                 :             : 
     428                 :             :                                         // Determine read access for non-ignored paths
     429         [ +  + ]:        5454 :                                         if(file->ignore == false)
     430                 :             :                                         {
     431                 :        5288 :                                                 FileAccessStatus access_status = file_check_access(root_directory_fd,relative_path,R_OK);
     432                 :        5288 :                                                 bool locked_unavailable_violation = false;
     433                 :             : 
     434         [ +  + ]:        5288 :                                                 if(path_known == true
     435         [ +  + ]:        1957 :                                                         && file->locked_checksum_file == true
     436         [ +  + ]:         171 :                                                         && access_status != FILE_ACCESS_ALLOWED)
     437                 :             :                                                 {
     438                 :           8 :                                                         status = show_locked_checksum_unavailable_violation(relative_path,
     439                 :             :                                                                 access_status,
     440                 :             :                                                                 &first_iteration,
     441                 :             :                                                                 summary);
     442                 :             : 
     443         [ -  + ]:           8 :                                                         if(SUCCESS != status)
     444                 :             :                                                         {
     445                 :           0 :                                                                 continue_the_loop = false;
     446                 :           0 :                                                                 break;
     447                 :             :                                                         }
     448                 :             : 
     449                 :           8 :                                                         file->is_readable = false;
     450                 :           8 :                                                         locked_unavailable_violation = true;
     451                 :             : 
     452                 :             :                                                 } else {
     453                 :        5280 :                                                         file->is_readable = (access_status == FILE_ACCESS_ALLOWED);
     454                 :             :                                                 }
     455                 :             : 
     456         [ +  + ]:        5288 :                                                 if(locked_unavailable_violation == true)
     457                 :             :                                                 {
     458                 :           8 :                                                         lock_checksum_violation_detected = true;
     459                 :           8 :                                                         break;
     460                 :             :                                                 }
     461                 :             :                                         }
     462                 :             : 
     463                 :             :                                         // Copy the current metadata once for comparisons, hashing, logging,
     464                 :             :                                         // and database writes
     465                 :        5446 :                                         run(stat_copy(p->fts_statp,&file->stat));
     466                 :             : 
     467                 :             :                                         // Validate whether logical size, allocated blocks, and ctime/mtime
     468                 :             :                                         // changed since the previous scan.
     469                 :             :                                         // Default value is:
     470                 :        5446 :                                         file->db_record_vs_file_metadata_changes = NOT_EQUAL;
     471                 :             : 
     472         [ +  + ]:        5446 :                                         if(path_known == true)
     473                 :             :                                         {
     474                 :             :                                                 // Validate whether logical size, allocated blocks, and ctime/mtime
     475                 :             :                                                 // changed since the previous scan.
     476                 :        2005 :                                                 file->db_record_vs_file_metadata_changes = file_compare_metadata_equivalence(&file->db->saved_stat,&file->stat);
     477                 :             :                                         }
     478                 :             : 
     479                 :        5446 :                                         bool file_metadata_identical = file->db_record_vs_file_metadata_changes == IDENTICAL;
     480                 :        5446 :                                         const bool has_saved_offset = file->db->saved_offset > 0;
     481                 :             :                                         // Indicates that the checksum-locked file has already been fully hashed and recorded
     482                 :       10892 :                                         bool lock_checksum_ready = file->locked_checksum_file == true
     483         [ +  + ]:         304 :                                                 && path_known == true
     484   [ +  +  +  - ]:        5750 :                                                 && has_saved_offset == false;
     485                 :             : 
     486                 :             :                                         // Used to skip files whose metadata and checksum are already up to date
     487                 :       10892 :                                         bool unchanged_and_complete = path_known == true
     488         [ +  + ]:        2005 :                                                 && file_metadata_identical == true
     489   [ +  +  +  + ]:        7451 :                                                 && has_saved_offset == false;
     490                 :             : 
     491   [ +  +  +  +  :        5446 :                                         if(unchanged_and_complete == true && !(config->rehash_locked == true && lock_checksum_ready == true))
                   +  + ]
     492                 :             :                                         {
     493                 :             :                                                 // Relative path already in DB and doesn't require any change
     494                 :             :                                                 break;
     495                 :             :                                         }
     496                 :             : 
     497                 :             :                                         // Derived flags to qualify the type of metadata change
     498                 :        3791 :                                         bool size_changed = (file->db_record_vs_file_metadata_changes & SIZE_CHANGED) != 0;
     499                 :             : 
     500                 :        3791 :                                         bool modification_time_changed = (file->db_record_vs_file_metadata_changes & MODIFICATION_TIME_CHANGED) != 0;
     501                 :             : 
     502                 :        3791 :                                         bool timestamps_changed = (file->db_record_vs_file_metadata_changes & (STATUS_CHANGED_TIME | MODIFICATION_TIME_CHANGED)) != 0;
     503                 :             : 
     504                 :        7582 :                                         bool unwatched_metadata_only_changed = path_known == true
     505         [ +  + ]:         350 :                                                 && file_metadata_identical == false
     506         [ +  + ]:         304 :                                                 && config->watch_timestamps == false
     507         [ +  + ]:         246 :                                                 && size_changed == false
     508         [ +  + ]:         184 :                                                 && modification_time_changed == false
     509   [ +  +  +  - ]:        4141 :                                                 && has_saved_offset == false;
     510                 :             : 
     511                 :             :                                         // Decision whether to rehash the file contents using
     512                 :             :                                         // the SHA512 algorithm. Defaults to rehash.
     513                 :        3791 :                                         file->rehash = true;
     514                 :             : 
     515         [ +  + ]:        3791 :                                         if(unwatched_metadata_only_changed == true)
     516                 :             :                                         {
     517                 :             :                                                 // Only ctime or allocated blocks changed: update DB without rehash
     518                 :          48 :                                                 file->rehash = false;
     519                 :             :                                         }
     520                 :             : 
     521   [ +  +  +  + ]:        3791 :                                         if(lock_checksum_ready == true && config->rehash_locked == true)
     522                 :             :                                         {
     523                 :          61 :                                                 file->rehash = true;
     524                 :             :                                         }
     525                 :             : 
     526                 :             :                                         /* Handle files whose previous checksum pass stopped before completion */
     527                 :             : 
     528                 :             :                                         // Can we resume hashing from a previous partial state?
     529                 :        3791 :                                         bool can_resume_partial_hash = has_saved_offset == true
     530   [ +  +  +  + ]:        3791 :                                                 && file_metadata_identical == true;
     531                 :             : 
     532                 :             :                                         // Indicates that a previous partial hash is now invalid and must restart
     533                 :        3791 :                                         bool partial_hash_invalidated = has_saved_offset == true
     534   [ +  +  +  + ]:        3791 :                                                 && file_metadata_identical == false;
     535                 :             : 
     536         [ +  + ]:        3791 :                                         if(can_resume_partial_hash == true)
     537                 :             :                                         {
     538                 :             :                                                 // Continue hashing from the saved offset and SHA512 context
     539                 :             :                                                 // Restore byte offset from where the previous pass stopped
     540                 :           5 :                                                 file->checksum_offset = file->db->saved_offset;
     541                 :             :                                                 // Restore SHA512 state to continue from that offset
     542                 :           5 :                                                 memcpy(&file->mdContext,&file->db->saved_mdContext,sizeof(SHA512_Context));
     543                 :             : 
     544         [ +  + ]:        3786 :                                         } else if(partial_hash_invalidated == true){
     545                 :             :                                                 /* The previous partial hash is no longer valid because file metadata changed */
     546                 :             :                                                 // Signal that hashing must restart from byte zero
     547                 :           2 :                                                 file->rehashing_from_the_beginning = true;
     548                 :             :                                         }
     549                 :             : 
     550                 :             :                                         // Marks zero-length files to avoid unnecessary hashing
     551         [ +  + ]:        3791 :                                         if(file->stat.st_size == 0)
     552                 :             :                                         {
     553                 :           6 :                                                 file->zero_size_file = true;
     554                 :           6 :                                                 file->rehash = false;
     555                 :             :                                         }
     556                 :             : 
     557                 :             :                                         // Locked checksum files must not diverge once sealed
     558                 :        3791 :                                         file->lock_checksum_violation = lock_checksum_ready == true
     559   [ +  +  +  + ]:        3854 :                                                 && (size_changed == true
     560         [ +  + ]:          63 :                                                 || (config->watch_timestamps == true
     561         [ +  + ]:          20 :                                                 && config->rehash_locked == false
     562         [ +  - ]:           2 :                                                 && timestamps_changed == true));
     563                 :             : 
     564                 :             :                                         // Timestamps drift on a locked file may be ignored depending on config
     565                 :        3791 :                                         bool locked_timestamp_drift_only = lock_checksum_ready == true
     566         [ +  + ]:          77 :                                                 && config->watch_timestamps == false
     567         [ +  + ]:          57 :                                                 && config->rehash_locked == false
     568         [ +  - ]:          14 :                                                 && timestamps_changed == true
     569   [ +  +  +  + ]:        3868 :                                                 && size_changed == false;
     570                 :             : 
     571         [ +  + ]:        3791 :                                         if(locked_timestamp_drift_only == true)
     572                 :             :                                         {
     573                 :           2 :                                                 break;
     574                 :             :                                         }
     575                 :             : 
     576                 :             :                                         // Hard hashing failure that should stop traversal for this pass
     577                 :        3789 :                                         bool hash_failed = false;
     578                 :             :                                         // Controls whether the final outcome for this file should be shown
     579                 :        3789 :                                         bool show_log = false;
     580                 :             : 
     581                 :        7578 :                                         bool should_process = file->is_readable == true
     582         [ +  - ]:        3665 :                                                 && file->ignore == false
     583   [ +  +  +  + ]:        7454 :                                                 && file->lock_checksum_violation == false;
     584                 :             : 
     585         [ +  + ]:        3789 :                                         if(should_process == true)
     586                 :             :                                         {
     587         [ +  + ]:        3649 :                                                 if(file->rehash == true)
     588                 :             :                                                 {
     589         [ +  - ]:        3613 :                                                         if(SUCCESS == status)
     590                 :             :                                                         {
     591                 :        3613 :                                                                 status = sha512sum(root_directory_fd,
     592                 :             :                                                                         relative_path,
     593                 :             :                                                                         file_buffer,
     594                 :             :                                                                         summary,
     595                 :             :                                                                         file,
     596                 :             :                                                                         &path_known);
     597                 :             :                                                         }
     598                 :             : 
     599         [ +  - ]:        3610 :                                                         if(SUCCESS & status)
     600                 :             :                                                         {
     601                 :             :                                                                 /* If the sha512sum has been interrupted smoothly when Ctrl+C */
     602         [ +  + ]:        3610 :                                                                 if(global_interrupt_flag == true)
     603                 :             :                                                                 {
     604                 :             :                                                                         /* Skip the file when interruption leaves no resumable hash state */
     605         [ -  + ]:           2 :                                                                         if(file->checksum_offset == 0)
     606                 :             :                                                                         {
     607                 :           0 :                                                                                 break;
     608                 :             :                                                                         }
     609                 :             : 
     610                 :           2 :                                                                         file->hash_interrupted = true;
     611                 :             :                                                                 }
     612                 :             : 
     613                 :             :                                                         } else {
     614                 :           0 :                                                                 continue_the_loop = false;
     615                 :           0 :                                                                 hash_failed = true;
     616                 :             :                                                         }
     617                 :             : 
     618                 :             :                                                 } else {
     619                 :             :                                                         // No rehash: reuse the digest stored in the DB
     620                 :          36 :                                                         memcpy(file->sha512,file->db->sha512,sizeof(file->sha512));
     621                 :             :                                                 }
     622                 :             : 
     623         [ +  - ]:        3646 :                                                 if(hash_failed == false
     624         [ +  + ]:        3646 :                                                         && file->read_error == false
     625         [ +  + ]:        3643 :                                                         && config->rehash_locked == true
     626         [ +  - ]:          59 :                                                         && lock_checksum_ready == true
     627         [ +  - ]:          59 :                                                         && file->rehash == true
     628         [ +  - ]:          59 :                                                         && (TRIUMPH & status)
     629         [ +  - ]:          59 :                                                         && file->wrong_file_type == false
     630         [ +  - ]:          59 :                                                         && file->zero_size_file == false
     631         [ +  - ]:          59 :                                                         && file->checksum_offset == 0)
     632                 :             :                                                 {
     633         [ +  + ]:          59 :                                                         if(memcmp(file->sha512,file->db->sha512,SHA512_DIGEST_LENGTH) != 0)
     634                 :             :                                                         {
     635                 :             :                                                                 // Detects corruption when rehashing locked files
     636                 :          12 :                                                                 file->locked_checksum_mismatch = true;
     637                 :             :                                                         }
     638                 :             :                                                 }
     639                 :             :                                         }
     640                 :             : 
     641         [ +  + ]:        3786 :                                         if(file->is_readable != true
     642         [ +  - ]:        3662 :                                                 || file->ignore == true
     643         [ +  + ]:        3662 :                                                 || file->lock_checksum_violation == true
     644         [ +  - ]:        3646 :                                                 || hash_failed == true
     645         [ +  + ]:        3646 :                                                 || file->read_error == true
     646         [ +  + ]:        3643 :                                                 || file->locked_checksum_mismatch == true)
     647                 :             :                                         {
     648                 :         155 :                                                 show_log = true;
     649                 :             : 
     650                 :             :                                                 /* When a checksum-locked file changed;
     651                 :             :                                                    blocks rehash/DB update and flags corruption */
     652   [ +  +  +  +  :         155 :                                                 if((file->lock_checksum_violation == true || file->locked_checksum_mismatch == true) && file->read_error == false)
                   +  - ]
     653                 :             :                                                 {
     654                 :          28 :                                                         lock_checksum_violation_detected = true;
     655                 :             :                                                         /*
     656                 :             :                                                          * A checksum-locked content or metadata mismatch means protected data may be corrupted.
     657                 :             :                                                          * Replay remembered warnings at exit even without --progress so this critical result remains visible after normal traversal output
     658                 :             :                                                          */
     659                 :          28 :                                                         config->show_remembered_messages_at_exit = true;
     660                 :             :                                                 }
     661                 :             : 
     662         [ +  + ]:        3631 :                                         } else if(path_known == true){
     663                 :             :                                                 /* Update in DB */
     664                 :         306 :                                                 show_log = true;
     665                 :             : 
     666                 :             :                                                 /*
     667                 :             :                                                  * True when this file was absent at the start of processing.
     668                 :             :                                                  * A periodic checkpoint may already have inserted its row, so the final save must still be allowed and reported as a new file
     669                 :             :                                                  */
     670                 :         306 :                                                 bool path_inserted_during_processing = file->db->relative_path_was_in_db_before_processing == false;
     671                 :             : 
     672                 :             :                                                 /*
     673                 :             :                                                  * Controls whether the final save may update an existing row.
     674                 :             :                                                  * Regular files, forced locked-file rehashes, resumed partial hashes, and rows inserted during this pass are allowed to be saved
     675                 :             :                                                  */
     676                 :         306 :                                                 bool allow_locked_update = path_inserted_during_processing == true
     677         [ +  + ]:         305 :                                                         || file->locked_checksum_file == false
     678         [ -  + ]:          47 :                                                         || config->rehash_locked == true
     679   [ +  +  -  - ]:         611 :                                                         || has_saved_offset == true;
     680                 :             : 
     681                 :             :                                                 /*
     682                 :             :                                                  * Final database-save decision for a known row.
     683                 :             :                                                  * It is true when the row was created by a checkpoint, the hash advanced, a resumed hash completed, or the file metadata changed
     684                 :             :                                                  */
     685                 :         306 :                                                 bool should_update_db = allow_locked_update == true
     686   [ +  -  +  + ]:         611 :                                                         && (path_inserted_during_processing == true
     687         [ +  - ]:         305 :                                                         || file->checksum_offset > file->db->saved_offset
     688   [ +  +  -  + ]:         305 :                                                         || (has_saved_offset == true && file->checksum_offset == 0)
     689         [ +  + ]:         299 :                                                         || file_metadata_identical == false);
     690                 :             : 
     691         [ +  + ]:         306 :                                                 if(should_update_db == true)
     692                 :             :                                                 {
     693                 :             :                                                         /* Save record in DB */
     694         [ +  - ]:         267 :                                                         if(SUCCESS & status)
     695                 :             :                                                         {
     696                 :         267 :                                                                 status = db_save_file_record(relative_path,file,&path_known,true);
     697                 :             : 
     698         [ -  + ]:         267 :                                                                 if((SUCCESS & status) == 0)
     699                 :             :                                                                 {
     700                 :           0 :                                                                         continue_the_loop = false;
     701                 :           0 :                                                                         break;
     702                 :             :                                                                 }
     703                 :             :                                                         }
     704                 :             :                                                 }
     705                 :             :                                         } else {
     706                 :        3325 :                                                 show_log = true;
     707                 :             : 
     708                 :             :                                                 /* Save record in DB */
     709         [ +  - ]:        3325 :                                                 if(SUCCESS & status)
     710                 :             :                                                 {
     711                 :        3325 :                                                         status = db_save_file_record(relative_path,file,&path_known,true);
     712                 :             : 
     713         [ -  + ]:        3325 :                                                         if((SUCCESS & status) == 0)
     714                 :             :                                                         {
     715                 :           0 :                                                                 continue_the_loop = false;
     716                 :           0 :                                                                 break;
     717                 :             :                                                         }
     718                 :             :                                                 }
     719                 :             :                                         }
     720                 :             : 
     721         [ +  - ]:        3786 :                                         if(show_log == true)
     722                 :             :                                         {
     723                 :             :                                                 // Print out of a file name and its changes
     724                 :        3786 :                                                 show_file(relative_path,
     725                 :             :                                                         &first_iteration,
     726                 :             :                                                         summary,
     727                 :             :                                                         file);
     728                 :             :                                         }
     729                 :             : 
     730                 :        3786 :                                         break;
     731                 :             :                                 }
     732                 :         466 :                                 case FTS_SL:
     733                 :         466 :                                         summary->count_symlnks++;
     734                 :         466 :                                         break;
     735                 :           0 :                                 case FTS_DNR:
     736                 :             :                                 case FTS_ERR:
     737                 :             :                                 case FTS_NS:
     738                 :             :                                 {
     739         [ #  # ]:           0 :                                         if(summary->stats_only_pass == true)
     740                 :             :                                         {
     741                 :           0 :                                                 break;
     742                 :             :                                         }
     743                 :             : 
     744                 :           0 :                                         run(path_build_relative(relative_path,p));
     745                 :             : 
     746         [ #  # ]:           0 :                                         if((TRIUMPH & status) == 0)
     747                 :             :                                         {
     748                 :           0 :                                                 continue_the_loop = false;
     749                 :           0 :                                                 break;
     750                 :             :                                         }
     751                 :             : 
     752                 :           0 :                                         const char *runtime_relative_path = m_text(relative_path);
     753                 :             : 
     754         [ #  # ]:           0 :                                         if(p->fts_info == FTS_DNR)
     755                 :             :                                         {
     756                 :           0 :                                                 slog_show(EVERY|UNDECOR|REMEMBER,false,&first_iteration,summary,"inaccessible directory %s\n",runtime_relative_path);
     757                 :             : 
     758         [ #  # ]:           0 :                                         } else if(p->fts_info == FTS_NS){
     759                 :             : 
     760                 :           0 :                                                 slog_show(EVERY|UNDECOR|REMEMBER,false,&first_iteration,summary,"cannot stat \"%s\" when reading %s\n",strerror(p->fts_errno),runtime_relative_path);
     761                 :             : 
     762                 :             :                                         } else {
     763                 :             : 
     764                 :           0 :                                                 slog_show(EVERY|UNDECOR|REMEMBER,false,&first_iteration,summary,"fts error \"%s\" when reading %s\n",strerror(p->fts_errno),runtime_relative_path);
     765                 :             :                                         }
     766                 :             : 
     767                 :           0 :                                         break;
     768                 :             :                                 }
     769                 :       24297 :                                 default:
     770                 :       24297 :                                         break;
     771                 :             :                         }
     772                 :             :                 }
     773                 :             : 
     774         [ +  - ]:         623 :                 if(file_systems != NULL)
     775                 :             :                 {
     776                 :         623 :                         fts_close(file_systems);
     777                 :         623 :                         file_systems = NULL;
     778                 :             :                 }
     779                 :             : 
     780         [ +  + ]:         623 :                 if(root_directory_fd >= 0)
     781                 :             :                 {
     782         [ -  + ]:         521 :                         if(close(root_directory_fd) != 0)
     783                 :             :                         {
     784                 :           0 :                                 slog(ERROR,"Failed to close traversal root directory descriptor: %s\n",strerror(errno));
     785                 :           0 :                                 status = FAILURE;
     786                 :             :                         }
     787                 :             :                 }
     788                 :             : 
     789   [ +  +  +  - ]:         623 :                 if(global_interrupt_flag == true || (SUCCESS & status) == 0)
     790                 :             :                 {
     791                 :             :                         break;
     792                 :             :                 }
     793                 :             :         }
     794                 :             : 
     795                 :         607 :         m_del(relative_path);
     796                 :         607 :         m_del(file_buffer);
     797                 :             : 
     798                 :             :         // Clear the traversal root pointer before returning from this pass.
     799                 :             :         // The root descriptors belong to config->roots and must not be treated as active after the loop finishes
     800                 :         607 :         summary->root = NULL;
     801                 :             : 
     802                 :             :         /*
     803                 :             :          * Commit the pending batch when traversal ends without a technical failure,
     804                 :             :          * including a graceful Ctrl+C interruption. call() ensures that HALTED does
     805                 :             :          * not skip this final commit
     806                 :             :          */
     807         [ +  - ]:         607 :         if((FAILURE & status) == 0)
     808                 :             :         {
     809                 :         607 :                 call(db_file_transaction_commit());
     810                 :             :         }
     811                 :             : 
     812                 :             :         /*
     813                 :             :          * Roll back any batch left active after a traversal or commit failure.
     814                 :             :          * This call does nothing when the transaction has already been committed
     815                 :             :          */
     816                 :         607 :         call(db_file_transaction_rollback());
     817                 :             : 
     818                 :             :         // Replace the start time with elapsed time after an interruption or normal completion
     819         [ +  + ]:         607 :         if(summary->stats_only_pass == false)
     820                 :             :         {
     821                 :         505 :                 summary->scan_elapsed_ns = cur_time_monotonic_ns() - summary->scan_elapsed_ns;
     822                 :             :         } else {
     823                 :             :                 // Exclude the preliminary counting pass from the reported scan time
     824                 :         102 :                 summary->scan_elapsed_ns = 0LL;
     825                 :             :         }
     826                 :             : 
     827                 :             :         // Print completion banner only when traversal emitted visible path-level lines.
     828                 :             :         // Print preflight totals only for the stats-only pass from main().
     829         [ +  + ]:         607 :         if(SUCCESS == status)
     830                 :             :         {
     831         [ +  + ]:         603 :                 if(summary->at_least_one_file_was_shown == true)
     832                 :             :                 {
     833                 :             : 
     834                 :         400 :                         slog(EVERY,"File traversal complete\n");
     835                 :             :                 }
     836                 :             : 
     837         [ +  + ]:         603 :                 if(summary->stats_only_pass == true)
     838                 :             :                 {
     839                 :         102 :                         show_statistics(summary);
     840                 :             :                 }
     841                 :             :         }
     842                 :             : 
     843         [ +  + ]:         607 :         if(lock_checksum_violation_detected == true)
     844                 :             :         {
     845                 :          30 :                 slog(EVERY,"@{bold}Warning! Data corruption detected for checksum-locked file!@{reset}\n");
     846                 :             : 
     847         [ +  - ]:          30 :                 if(SUCCESS == status)
     848                 :             :                 {
     849                 :          30 :                         status = WARNING;
     850                 :             :                 }
     851                 :             :         }
     852                 :             : 
     853                 :         607 :         provide(status);
     854                 :             : }
        

Generated by: LCOV version 2.0-1