LCOV - code coverage report
Current view: top level - src - file_list.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 85.0 % 286 243
Test Date: 2026-07-12 01:01:34 Functions: 100.0 % 2 2
Branches: 80.6 % 283 228

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

Generated by: LCOV version 2.0-1