Branch data Line data Source code
1 : : #include "precizer.h"
2 : :
3 : : /**
4 : : * @brief Compare two FTS entries by filename
5 : : * @param first Pointer to first FTSENT structure
6 : : * @param second Pointer to second FTSENT structure
7 : : * @return Integer less than, equal to, or greater than zero if first is found,
8 : : * respectively, to be less than, to match, or be greater than second
9 : : */
10 : 6158 : static int compare_by_name(
11 : : const FTSENT **first,
12 : : const FTSENT **second)
13 : : {
14 : 6158 : return strcmp((*first)->fts_name,(*second)->fts_name);
15 : : }
16 : :
17 : : /**
18 : : * @brief Traverse configured paths and process files for one pass.
19 : : *
20 : : * Supports two modes controlled by summary->stats_only_pass:
21 : : * - true: collect counters and allocated size only;
22 : : * - false: hash files, update DB rows, and collect timing/hash metrics.
23 : : *
24 : : * @param summary Traversal state that is reset and populated by this call.
25 : : * @return SUCCESS, WARNING, or FAILURE.
26 : : */
27 : 758 : Return file_list(TraversalSummary *summary)
28 : : {
29 : : /* Status returned by this function through provide()
30 : : Default value assumes successful completion */
31 : 758 : Return status = SUCCESS;
32 : :
33 : : // Don't do anything
34 [ + + ]: 758 : if(config->compare == true)
35 : : {
36 : 224 : provide(status);
37 : : }
38 : :
39 [ + + + + ]: 534 : if(config->progress == false && summary->stats_only_pass == true)
40 : : {
41 : : // Don't do anything
42 : 202 : provide(status);
43 : : }
44 : :
45 : : // Flags that reflect the presence of any changes
46 : : // since the last research
47 : :
48 : : // Print traversal/update banners only once
49 : 332 : bool first_iteration = true;
50 : :
51 : : // Signals integrity issues for locked files
52 : 332 : bool lock_checksum_violation_detected = false;
53 : :
54 : 332 : FTS *file_systems = NULL;
55 : 332 : FTSENT *p = NULL;
56 : :
57 : 332 : int fts_options = FTS_PHYSICAL;
58 : :
59 [ + + ]: 332 : if(config->start_device_only == true)
60 : : {
61 : 2 : fts_options |= FTS_XDEV;
62 : : }
63 : :
64 : : // Reset traversal counters and timing for this pass.
65 : 332 : summary->count_dirs = 0;
66 : :
67 : : // Number of regular files seen in this pass.
68 : 332 : summary->count_files = 0;
69 : :
70 : : // Number of symlinks seen in this pass.
71 : 332 : summary->count_symlnks = 0;
72 : :
73 : : // Sum of allocated bytes for encountered files.
74 : 332 : summary->total_allocated_bytes = 0;
75 : :
76 : : // Sum of bytes hashed by SHA512 during this pass.
77 : 332 : summary->total_hashed_bytes = 0;
78 : :
79 : : // Track whether any output was produced
80 : 332 : summary->at_least_one_file_was_shown = false;
81 : :
82 : : // Sum of per-file hashing elapsed time in nanoseconds.
83 : 332 : summary->total_hashing_elapsed_ns = 0LL;
84 : :
85 [ - + ]: 332 : if((file_systems = fts_open(config->paths,fts_options,compare_by_name)) == NULL)
86 : : {
87 : 0 : slog(ERROR,"fts_open() error\n");
88 : 0 : provide(FAILURE);
89 : : }
90 : :
91 : : /*
92 : : * Determine the absolute path prefix.
93 : : * We are only interested in relative paths in the database.
94 : : * To obtain a relative path, trim the prefix from the absolute path.
95 : : */
96 : 332 : char *runtime_root = NULL;
97 : : #if 0 // Old multiPATH solution
98 : : /**
99 : : * Index of the path prefix
100 : : * All full runtime paths are stored in the table "paths".
101 : : * A real path can be retrieved due to its index ID
102 : : */
103 : : sqlite3_int64 runtime_root_index = -1;
104 : : #endif
105 : :
106 : : // Limit recursion to the depth determined in config->maxdepth
107 [ + + ]: 332 : if(config->maxdepth > -1)
108 : : {
109 : 4 : slog(EVERY,"Recursion depth limited to: %d\n",config->maxdepth);
110 : : }
111 : :
112 [ + + + - ]: 332 : if(summary->stats_only_pass == true && config->progress == true)
113 : : {
114 : 65 : slog(EVERY,"File system traversal initiated to calculate file count and storage usage\n");
115 : : }
116 : :
117 : 332 : bool continue_the_loop = true;
118 : :
119 : : // Allocate space for a memory structure
120 : 332 : create(unsigned char,file_buffer);
121 : :
122 [ + + ]: 332 : if(summary->stats_only_pass == false)
123 : : {
124 : 267 : status = resize(file_buffer,file_buffer_memory());
125 : :
126 [ - + ]: 267 : if(SUCCESS != status)
127 : : {
128 : 0 : provide(status);
129 : : }
130 : : #ifdef TESTITALL_TEST_HOOKS
131 : 267 : signal_wait_at_point(1U);
132 : : #endif
133 : : }
134 : :
135 [ + + + - ]: 34614 : while((p = fts_read(file_systems)) != NULL && continue_the_loop == true)
136 : : {
137 : : /* Interrupt the loop smoothly */
138 : : /* Interrupt when Ctrl+C */
139 [ + + ]: 34286 : if(global_interrupt_flag == true)
140 : : {
141 : 4 : break;
142 : : }
143 : :
144 : : /* Get absolute path prefix from FTSENT structure and current runtime path */
145 [ + + ]: 34282 : if(p->fts_level == FTS_ROOTLEVEL)
146 : : {
147 : 658 : size_t new_size = (size_t)(p->fts_pathlen + 1) * sizeof(char);
148 : :
149 : : // All below run once per new path prefix
150 : 658 : char *tmp = (char *)realloc(runtime_root,new_size);
151 : :
152 [ - + ]: 658 : if(NULL == tmp)
153 : : {
154 : 0 : report("Memory allocation failed, requested size: %zu bytes",new_size);
155 : 0 : status = FAILURE;
156 : 0 : break;
157 : : } else {
158 : 658 : runtime_root = tmp;
159 : : }
160 : :
161 : : // Remember temporary string in long-lasting variable
162 : 658 : memcpy(runtime_root,p->fts_path,(size_t)p->fts_pathlen);
163 : :
164 [ + - ]: 658 : if(p->fts_pathlen > 0)
165 : : {
166 : 658 : runtime_root[p->fts_pathlen] = '\0';
167 : : }
168 : :
169 : : // Remove unnecessary trailing slash at the end of the directory path
170 : 658 : remove_trailing_slash(runtime_root);
171 : :
172 : : #if 0 // Old multiPATH solution
173 : : // If several paths were passed as arguments,
174 : : // then the counting of the path prefix index
175 : : // will start from zero
176 : : if(SUCCESS != (status = db_get_runtime_root_index(config,
177 : : runtime_root,
178 : : &runtime_root_index)))
179 : : {
180 : : continue_the_loop = false;
181 : : break;
182 : : }
183 : : #endif
184 : : }
185 : :
186 [ + + + + ]: 34282 : if(config->maxdepth > -1 && p->fts_level > config->maxdepth + 1)
187 : : {
188 [ + + ]: 42 : if(p->fts_info == FTS_D)
189 : : {
190 : 14 : (void)fts_set(file_systems,p,FTS_SKIP);
191 : : }
192 : :
193 : 42 : continue;
194 : : }
195 : :
196 [ + + + - : 34240 : switch(p->fts_info)
+ ]
197 : : {
198 : 14886 : case FTS_D:
199 : : {
200 : 14886 : const char *relative_path = extract_relative_path(p->fts_path,runtime_root);
201 : :
202 : : // Captures files explicitly skipped or forced by regexp filters
203 : : // Ignored with --ignore= or admitted with --include=
204 : 14886 : bool ignore = false;
205 : :
206 : : // Included with --include=
207 : 14886 : bool include = false;
208 : :
209 : 14886 : status = match_include_ignore(relative_path,&include,&ignore);
210 : :
211 [ - + ]: 14886 : if(SUCCESS != status)
212 : : {
213 : 0 : continue_the_loop = false;
214 : 0 : break;
215 : : }
216 : :
217 [ + + ]: 14886 : if(summary->stats_only_pass == false)
218 : : {
219 : 11754 : directory_show(relative_path,
220 : : &first_iteration,
221 : : summary,
222 : : ignore,
223 : : include);
224 : : }
225 : :
226 [ + + ]: 14886 : if(ignore == true)
227 : : {
228 : : /*
229 : : * Use FTS_SKIP for an ignored directory only when no --include patterns were provided
230 : : * If any --include is present, keep traversing even ignored directories
231 : : * At this point only the directory path itself was checked, not paths under it
232 : : * Otherwise FTS_SKIP would cut off child files that should be brought back by --include
233 : : */
234 [ + + ]: 208 : if(config->include_specified == false)
235 : : {
236 : 22 : (void)fts_set(file_systems,p,FTS_SKIP);
237 : : }
238 : 208 : break;
239 : : }
240 : :
241 [ + + ]: 14678 : if(summary->stats_only_pass == false)
242 : : {
243 : : // Check access and skip subtrees that are not readable.
244 : 11638 : status = verify_directory_access(file_systems,
245 : : p,
246 : : runtime_root,
247 : : &first_iteration,
248 : : summary);
249 : : }
250 : :
251 [ - + ]: 14678 : if(SUCCESS != status)
252 : : {
253 : 0 : continue_the_loop = false;
254 : 0 : break;
255 : : }
256 : :
257 : 14678 : summary->count_dirs++;
258 : 14678 : break;
259 : : }
260 : 4192 : case FTS_F:
261 : : {
262 : 4192 : summary->total_allocated_bytes += blocks_to_bytes(p->fts_statp->st_blocks);
263 : 4192 : summary->count_files++;
264 : :
265 [ + + ]: 4192 : if(summary->stats_only_pass == true)
266 : : {
267 : 844 : continue;
268 : : }
269 : :
270 : : // Keep per-file processing state on the stack and attach
271 : : // the DB row that will be filled for this path
272 : 3348 : File _file = {0};
273 : 3348 : File *file = &_file;
274 : 3348 : DBrow dbrow = {0};
275 : 3348 : file->db = &dbrow;
276 : :
277 : 3348 : const char *relative_path = extract_relative_path(p->fts_path,runtime_root);
278 : :
279 : : /* Get all file's metadata from the database */
280 : : #if 0 // Old multiPATH solution
281 : : run(db_read_file_data_from(file,&runtime_root_index,relative_path));
282 : : #else
283 [ + - - + ]: 3348 : run(db_read_file_data_from(file,relative_path));
284 : : #endif
285 : :
286 [ - + ]: 3348 : if(SUCCESS != status)
287 : : {
288 : 0 : continue_the_loop = false;
289 : 3348 : break;
290 : : }
291 : :
292 : : // Tracks whether the current relative path existed in DB before current file processing
293 : 3348 : const bool path_known = file->db->relative_path_was_in_db_before_processing == true;
294 : :
295 : 3348 : LockChecksum lock_checksum_response = match_checksum_lock_pattern(relative_path);
296 : :
297 [ - + ]: 3348 : if(FAIL_REGEXP_LOCK_CHECKSUM == lock_checksum_response)
298 : : {
299 : 0 : slog(ERROR,"Fail lock-checksum REGEXP for a string: %s\n",relative_path);
300 : 0 : status = FAILURE;
301 : 0 : continue_the_loop = false;
302 : 0 : break;
303 [ + + ]: 3348 : } else if(LOCK_CHECKSUM == lock_checksum_response){
304 : : // Mark this file as checksum-locked
305 : 164 : file->locked_checksum_file = true;
306 : : }
307 : :
308 : : // Store the final --include/--ignore decision in the per-file state
309 : 3348 : status = match_include_ignore(relative_path,&file->include,&file->ignore);
310 : :
311 [ - + ]: 3348 : if(SUCCESS != status)
312 : : {
313 : 0 : continue_the_loop = false;
314 : 0 : break;
315 : : }
316 : :
317 : : // Ensure checksum-locked files are tracked even if matched by ignore pattern
318 [ + + - + : 3348 : if(file->ignore == true && file->locked_checksum_file == true && path_known == false)
- - ]
319 : : {
320 : 0 : file->ignore = false;
321 : : }
322 : :
323 : : // Determine read access for non-ignored paths
324 [ + + ]: 3348 : if(file->ignore == false)
325 : : {
326 : 3258 : FileAccessStatus access_status = file_check_access(p->fts_path,(size_t)p->fts_pathlen,R_OK);
327 : :
328 [ - + ]: 3258 : if(access_status == FILE_ACCESS_ERROR)
329 : : {
330 : 0 : status = FAILURE;
331 : 0 : continue_the_loop = false;
332 : 0 : break;
333 : : }
334 : :
335 : 3258 : file->is_readable = (access_status == FILE_ACCESS_ALLOWED);
336 : : }
337 : :
338 : : // Copy the current metadata once for comparisons, hashing, logging,
339 : : // and database writes
340 [ + - - + ]: 3348 : run(stat_copy(p->fts_statp,&file->stat));
341 : :
342 : : // Validate whether logical size, allocated blocks, and ctime/mtime
343 : : // changed since the previous scan.
344 : : // Default value is:
345 : 3348 : file->db_record_vs_file_metadata_changes = NOT_EQUAL;
346 : :
347 [ + + ]: 3348 : if(path_known == true)
348 : : {
349 : : // Validate whether logical size, allocated blocks, and ctime/mtime
350 : : // changed since the previous scan.
351 : 1304 : file->db_record_vs_file_metadata_changes = file_compare_metadata_equivalence(&file->db->saved_stat,&file->stat);
352 : : }
353 : :
354 : 3348 : bool file_metadata_identical = file->db_record_vs_file_metadata_changes == IDENTICAL;
355 : 3348 : const bool has_saved_offset = file->db->saved_offset > 0;
356 : : // Indicates that the checksum-locked file has already been fully hashed and recorded
357 : 6696 : bool lock_checksum_ready = file->locked_checksum_file == true
358 [ + + ]: 164 : && path_known == true
359 [ + + + - ]: 3512 : && has_saved_offset == false;
360 : :
361 : : // Used to skip files whose metadata and checksum are already up to date
362 : 3348 : bool unchanged_and_complete = path_known == true
363 [ + + ]: 1304 : && file_metadata_identical == true
364 [ + + + + ]: 4652 : && has_saved_offset == false;
365 : :
366 [ + + + + : 3348 : if(unchanged_and_complete == true && !(config->rehash_locked == true && lock_checksum_ready == true))
+ + ]
367 : : {
368 : : // Relative path already in DB and doesn't require any change
369 : : break;
370 : : }
371 : :
372 : : // Derived flags to qualify the type of metadata change
373 : 2294 : bool size_changed = (file->db_record_vs_file_metadata_changes & SIZE_CHANGED) != 0;
374 : :
375 : 2294 : bool timestamps_changed = (file->db_record_vs_file_metadata_changes & (STATUS_CHANGED_TIME | MODIFICATION_TIME_CHANGED)) != 0;
376 : :
377 : 2294 : bool timestamps_only_changed = path_known == true
378 [ + + ]: 250 : && file_metadata_identical == false
379 [ + + ]: 224 : && config->watch_timestamps == false
380 [ + + ]: 170 : && size_changed == false
381 [ + + + - ]: 2544 : && has_saved_offset == false;
382 : :
383 : : // Decision whether to rehash the file contents using
384 : : // the SHA512 algorithm. Defaults to rehash.
385 : 2294 : file->rehash = true;
386 : :
387 [ + + ]: 2294 : if(timestamps_only_changed == true)
388 : : {
389 : : // ctime/mtime changed only: update DB without rehash
390 : 126 : file->rehash = false;
391 : : }
392 : :
393 [ + + + + ]: 2294 : if(lock_checksum_ready == true && config->rehash_locked == true)
394 : : {
395 : 36 : file->rehash = true;
396 : : }
397 : :
398 : : /* For a file which had been changed before creation
399 : : of its checksum has been already finished */
400 : :
401 : : // Can we resume hashing from a previous partial state?
402 : 2294 : bool can_resume_partial_hash = has_saved_offset == true
403 [ + + + + ]: 2294 : && file_metadata_identical == true;
404 : :
405 : : // Indicates that a previous partial hash is now invalid and must restart
406 : 2294 : bool partial_hash_invalidated = has_saved_offset == true
407 [ + + + + ]: 2294 : && file_metadata_identical == false;
408 : :
409 [ + + ]: 2294 : if(can_resume_partial_hash == true)
410 : : {
411 : : // Continue hashing from the saved offset and SHA512 context
412 : : // Restore byte offset from where the previous pass stopped
413 : 2 : file->checksum_offset = file->db->saved_offset;
414 : : // Restore SHA512 state to continue from that offset
415 : 2 : memcpy(&file->mdContext,&file->db->saved_mdContext,sizeof(SHA512_Context));
416 : :
417 [ + + ]: 2292 : } else if(partial_hash_invalidated == true){
418 : : /* The SHA512 hashing of the file had not been
419 : : finished previously and the file has been changed */
420 : : // Signal that hashing must restart from byte zero
421 : 2 : file->rehashing_from_the_beginning = true;
422 : : }
423 : :
424 : : // Marks zero-length files to avoid unnecessary hashing
425 [ + + ]: 2294 : if(file->stat.st_size == 0)
426 : : {
427 : 6 : file->zero_size_file = true;
428 : 6 : file->rehash = false;
429 : : }
430 : :
431 : : // Locked checksum files must not diverge once sealed
432 : 2294 : file->lock_checksum_violation = lock_checksum_ready == true
433 [ + + + + ]: 2332 : && (size_changed == true
434 [ + + ]: 38 : || (config->watch_timestamps == true
435 [ + + ]: 20 : && config->rehash_locked == false
436 [ + - ]: 2 : && timestamps_changed == true));
437 : :
438 : : // Timestamps drift on a locked file may be ignored depending on config
439 : 2294 : bool locked_timestamp_drift_only = lock_checksum_ready == true
440 [ + + ]: 48 : && config->watch_timestamps == false
441 [ + + ]: 28 : && config->rehash_locked == false
442 [ + - ]: 10 : && timestamps_changed == true
443 [ + + + + ]: 2342 : && size_changed == false;
444 : :
445 [ + + ]: 2294 : if(locked_timestamp_drift_only == true)
446 : : {
447 : 2 : break;
448 : : }
449 : :
450 : : // Hard hashing failure that should stop traversal for this pass
451 : 2292 : bool hash_failed = false;
452 : : // Controls whether the final outcome for this file should be shown
453 : 2292 : bool show_log = false;
454 : :
455 : 4584 : bool should_process = file->is_readable == true
456 [ + - ]: 2220 : && file->ignore == false
457 [ + + + + ]: 4512 : && file->lock_checksum_violation == false;
458 : :
459 [ + + ]: 2292 : if(should_process == true)
460 : : {
461 [ + + ]: 2208 : if(file->rehash == true)
462 : : {
463 [ + - ]: 2086 : if(SUCCESS == status)
464 : : {
465 : 2086 : status = sha512sum(p->fts_path,
466 : 2086 : (size_t)p->fts_pathlen,
467 : : file_buffer,
468 : : summary,
469 : : file);
470 : : }
471 : :
472 [ + - ]: 2086 : if(TRIUMPH & status)
473 : : {
474 : : /* If the sha512sum has been interrupted smoothly when Ctrl+C */
475 [ + + + - ]: 2086 : if(file->checksum_offset > 0 && global_interrupt_flag == true)
476 : : {
477 : 2 : file->hash_interrupted = true;
478 : : }
479 : :
480 : : } else {
481 : 0 : continue_the_loop = false;
482 : 0 : hash_failed = true;
483 : : }
484 : :
485 : : } else {
486 : : // No rehash: reuse the digest stored in the DB
487 : 122 : memcpy(file->sha512,file->db->sha512,sizeof(file->sha512));
488 : : }
489 : :
490 [ + - ]: 2208 : if(hash_failed == false
491 [ + + ]: 2208 : && file->read_error == false
492 [ + + ]: 2207 : && config->rehash_locked == true
493 [ + - ]: 34 : && lock_checksum_ready == true
494 [ + - ]: 34 : && file->rehash == true
495 [ + - ]: 34 : && (TRIUMPH & status)
496 [ + - ]: 34 : && file->wrong_file_type == false
497 [ + - ]: 34 : && file->zero_size_file == false
498 [ + - ]: 34 : && file->checksum_offset == 0)
499 : : {
500 [ + + ]: 34 : if(memcmp(file->sha512,file->db->sha512,SHA512_DIGEST_LENGTH) != 0)
501 : : {
502 : : // Detects corruption when rehashing locked files
503 : 6 : file->locked_checksum_mismatch = true;
504 : : }
505 : : }
506 : : }
507 : :
508 [ + + ]: 2292 : if(file->is_readable != true
509 [ + - ]: 2220 : || file->ignore == true
510 [ + + ]: 2220 : || file->lock_checksum_violation == true
511 [ + - ]: 2208 : || hash_failed == true
512 [ + + ]: 2208 : || file->read_error == true
513 [ + + ]: 2207 : || file->locked_checksum_mismatch == true)
514 : : {
515 : 91 : show_log = true;
516 : :
517 : : /* When a checksum-locked file changed;
518 : : blocks rehash/DB update and flags corruption */
519 [ + + + + : 91 : if((file->lock_checksum_violation == true || file->locked_checksum_mismatch == true) && file->read_error == false)
+ - ]
520 : : {
521 : 18 : lock_checksum_violation_detected = true;
522 : 18 : config->show_remembered_messages_at_exit = true;
523 : : }
524 : :
525 [ + + ]: 2201 : } else if(path_known == true){
526 : : /* Update in DB */
527 : 222 : show_log = true;
528 : :
529 : 444 : bool allow_locked_update = file->lock_checksum_violation == false
530 [ + - + + ]: 250 : && (file->locked_checksum_file == false
531 [ - + ]: 28 : || config->rehash_locked == true
532 [ # # ]: 0 : || has_saved_offset == true);
533 : :
534 : 222 : bool should_update_db = path_known == true
535 [ + - ]: 222 : && allow_locked_update == true
536 [ + - + - ]: 666 : && (file->checksum_offset > file->db->saved_offset
537 [ + + - + ]: 222 : || (has_saved_offset == true && file->checksum_offset == 0)
538 [ + + ]: 218 : || file_metadata_identical == false);
539 : :
540 [ + + ]: 222 : if(should_update_db == true)
541 : : {
542 : : /* Update record in DB */
543 [ + - ]: 200 : if(TRIUMPH & status)
544 : : {
545 : 200 : status = db_update_the_record_by_id(file);
546 : :
547 [ - + ]: 200 : if((TRIUMPH & status) == 0)
548 : : {
549 : 0 : continue_the_loop = false;
550 : 0 : break;
551 : : }
552 : : // Record that the DB row was updated
553 : 200 : file->db_record_updated = true;
554 : : }
555 : : }
556 : : } else {
557 : 1979 : show_log = true;
558 : :
559 : : /* Insert into DB */
560 [ + - ]: 1979 : if(TRIUMPH & status)
561 : : {
562 : : #if 0 // Old multiPATH solution
563 : : status = db_insert_the_record(&runtime_root_index,
564 : : relative_path,
565 : : file);
566 : : #else
567 : 1979 : status = db_insert_the_record(relative_path,file);
568 : : #endif
569 : :
570 [ - + ]: 1979 : if((TRIUMPH & status) == 0)
571 : : {
572 : 0 : continue_the_loop = false;
573 : 0 : break;
574 : : }
575 : : // Record that a new DB row was inserted
576 : 1979 : file->new_db_record_inserted = true;
577 : : }
578 : : }
579 : :
580 [ + - ]: 2292 : if(show_log == true)
581 : : {
582 : : // Print out of a file name and its changes
583 : 2292 : show_file(relative_path,
584 : : &first_iteration,
585 : : summary,
586 : : file);
587 : : }
588 : :
589 : 2292 : break;
590 : : }
591 : 278 : case FTS_SL:
592 : 278 : summary->count_symlnks++;
593 : 278 : break;
594 : 0 : case FTS_DNR:
595 : : case FTS_ERR:
596 : : case FTS_NS:
597 : : {
598 [ # # ]: 0 : if(summary->stats_only_pass == true)
599 : : {
600 : 0 : break;
601 : : }
602 : :
603 : 0 : const char *relative_path = extract_relative_path(p->fts_path,runtime_root);
604 : :
605 [ # # ]: 0 : if(p->fts_info == FTS_DNR)
606 : : {
607 : 0 : slog_show(EVERY|UNDECOR|REMEMBER,false,&first_iteration,summary,"inaccessible directory %s\n",relative_path);
608 : :
609 [ # # ]: 0 : } else if(p->fts_info == FTS_NS){
610 : :
611 : 0 : slog_show(EVERY|UNDECOR|REMEMBER,false,&first_iteration,summary,"cannot stat \"%s\" when reading %s\n",strerror(p->fts_errno),relative_path);
612 : :
613 : : } else {
614 : :
615 : 0 : slog_show(EVERY|UNDECOR|REMEMBER,false,&first_iteration,summary,"fts error \"%s\" when reading %s\n",strerror(p->fts_errno),relative_path);
616 : : }
617 : :
618 : 0 : break;
619 : : }
620 : 14884 : default:
621 : 14884 : break;
622 : : }
623 : : }
624 : :
625 : 332 : del(file_buffer);
626 : :
627 : 332 : free(runtime_root);
628 : :
629 : 332 : fts_close(file_systems);
630 : :
631 : : // Print completion banner only when traversal emitted visible path-level lines.
632 : : // Print preflight totals only for the stats-only pass from main().
633 [ + + ]: 332 : if(SUCCESS == status)
634 : : {
635 [ + + ]: 330 : if(summary->at_least_one_file_was_shown == true)
636 : : {
637 : :
638 : 183 : slog(EVERY,"File traversal complete\n");
639 : : }
640 : :
641 [ + + ]: 330 : if(summary->stats_only_pass == true)
642 : : {
643 : 65 : show_statistics(summary);
644 : : }
645 : : }
646 : :
647 [ + + ]: 332 : if(lock_checksum_violation_detected == true)
648 : : {
649 : 14 : slog(EVERY,BOLD "Warning! Data corruption detected for checksum-locked file!" RESET "\n");
650 : :
651 [ + - ]: 14 : if(SUCCESS == status)
652 : : {
653 : 14 : status = WARNING;
654 : : }
655 : : }
656 : :
657 : 332 : provide(status);
658 : : }
|