LCOV - code coverage report
Current view: top level - cmdline - scan.c (source / functions) Hit Total Coverage
Test: lcov.info Lines: 639 711 89.9 %
Date: 2026-04-29 15:04:44 Functions: 29 29 100.0 %

          Line data    Source code
       1             : // SPDX-License-Identifier: GPL-3.0-or-later
       2             : // Copyright (C) 2011 Andrea Mazzoleni
       3             : 
       4             : #include "portable.h"
       5             : 
       6             : #include "support.h"
       7             : #include "elem.h"
       8             : #include "state.h"
       9             : #include "parity.h"
      10             : 
      11             : struct snapraid_scan {
      12             :         struct snapraid_state* state; /**< State used. */
      13             :         struct snapraid_disk* disk; /**< Disk used. */
      14             :         thread_id_t thread; /**< Thread used for scanning the disk */
      15             : 
      16             :         int is_diff; /**< If it's a diff command or a scanning */
      17             :         int need_write; /**< If a state write is required */
      18             : 
      19             :         /**
      20             :          * Counters of changes.
      21             :          */
      22             :         unsigned count_equal; /**< Files equal. */
      23             :         unsigned count_move; /**< Files with a different name, but equal inode, size and timestamp in the same disk. */
      24             :         unsigned count_restore; /**< Files with equal name, size and timestamp, but different inode. */
      25             :         unsigned count_change; /**< Files with same name, but different size and/or timestamp. */
      26             :         unsigned count_copy; /**< Files new, with same name size and timestamp of a file in a different disk. */
      27             :         unsigned count_relocate; /**< Like copy, but with the original disappeared. */
      28             :         unsigned count_insert; /**< Files new. */
      29             :         unsigned count_remove; /**< Files removed. */
      30             : 
      31             :         tommy_list file_insert_list; /**< Files to insert. */
      32             :         tommy_list link_insert_list; /**< Links to insert. */
      33             :         tommy_list dir_insert_list; /**< Dirs to insert. */
      34             :         tommy_list local_filter_list; /**< Filter list specific for the disk. */
      35             : 
      36             :         /* nodes for data structures */
      37             :         tommy_node node;
      38             : };
      39             : 
      40         659 : static struct snapraid_scan* scan_alloc(struct snapraid_state* state, struct snapraid_disk* disk, int is_diff)
      41             : {
      42             :         struct snapraid_scan* scan;
      43             : 
      44         659 :         scan = malloc_nofail(sizeof(struct snapraid_scan));
      45         659 :         scan->state = state;
      46         659 :         scan->disk = disk;
      47         659 :         scan->count_equal = 0;
      48         659 :         scan->count_move = 0;
      49         659 :         scan->count_copy = 0;
      50         659 :         scan->count_relocate = 0;
      51         659 :         scan->count_restore = 0;
      52         659 :         scan->count_change = 0;
      53         659 :         scan->count_remove = 0;
      54         659 :         scan->count_insert = 0;
      55         659 :         tommy_list_init(&scan->file_insert_list);
      56         659 :         tommy_list_init(&scan->link_insert_list);
      57         659 :         tommy_list_init(&scan->dir_insert_list);
      58         659 :         tommy_list_init(&scan->local_filter_list);
      59         659 :         scan->is_diff = is_diff;
      60         659 :         scan->need_write = 0;
      61             : 
      62             : #if HAVE_THREAD
      63         659 :         thread_mutex_init(&disk->stamp_mutex);
      64             : #endif
      65             : 
      66         659 :         return scan;
      67             : }
      68             : 
      69         653 : static void scan_free(struct snapraid_scan* scan)
      70             : {
      71             : #if HAVE_THREAD
      72         653 :         thread_mutex_destroy(&scan->disk->stamp_mutex);
      73             : #endif
      74         653 :         tommy_list_foreach(&scan->local_filter_list, filter_free);
      75         653 :         free(scan);
      76         653 : }
      77             : 
      78      558309 : static void stamp_lock(struct snapraid_disk* disk)
      79             : {
      80             : #if HAVE_THREAD
      81      558309 :         thread_mutex_lock(&disk->stamp_mutex);
      82             : #else
      83             :         (void)disk;
      84             : #endif
      85      558309 : }
      86             : 
      87      558309 : static void stamp_unlock(struct snapraid_disk* disk)
      88             : {
      89             : #if HAVE_THREAD
      90      558309 :         thread_mutex_unlock(&disk->stamp_mutex);
      91             : #else
      92             :         (void)disk;
      93             : #endif
      94      558309 : }
      95             : 
      96             : /**
      97             :  * Remove the specified link from the data set.
      98             :  */
      99         993 : static void scan_link_remove(struct snapraid_scan* scan, struct snapraid_link* slink)
     100             : {
     101         993 :         struct snapraid_disk* disk = scan->disk;
     102             : 
     103             :         /* state changed */
     104         993 :         scan->need_write = 1;
     105             : 
     106             :         /* remove the file from the link containers */
     107         993 :         tommy_hashdyn_remove_existing(&disk->linkset, &slink->nodeset);
     108         993 :         tommy_list_remove_existing(&disk->linklist, &slink->nodelist);
     109             : 
     110             :         /* deallocate */
     111         993 :         link_free(slink);
     112         993 : }
     113             : 
     114             : /**
     115             :  * Insert the specified link in the data set.
     116             :  */
     117        2566 : static void scan_link_insert(struct snapraid_scan* scan, struct snapraid_link* slink)
     118             : {
     119        2566 :         struct snapraid_disk* disk = scan->disk;
     120             : 
     121             :         /* state changed */
     122        2566 :         scan->need_write = 1;
     123             : 
     124             :         /* insert the link in the link containers */
     125        2566 :         tommy_hashdyn_insert(&disk->linkset, &slink->nodeset, slink, link_name_hash(slink->sub));
     126        2566 :         tommy_list_insert_tail(&disk->linklist, &slink->nodelist, slink);
     127        2566 : }
     128             : 
     129             : /**
     130             :  * Process a symbolic link.
     131             :  */
     132       40712 : static void scan_link(struct snapraid_scan* scan, int is_diff, const char* sub, const char* linkto, unsigned link_flag)
     133             : {
     134       40712 :         struct snapraid_state* state = scan->state;
     135       40712 :         struct snapraid_disk* disk = scan->disk;
     136             :         struct snapraid_link* slink;
     137             :         char esc_buffer[ESC_MAX];
     138             : 
     139             :         /* check if the link already exists */
     140       40712 :         slink = tommy_hashdyn_search(&disk->linkset, link_name_compare_to_arg, sub, link_name_hash(sub));
     141       40712 :         if (slink) {
     142             :                 /* check if multiple files have the same name */
     143       38146 :                 if (link_flag_has(slink, FILE_IS_PRESENT)) {
     144             :                         /* LCOV_EXCL_START */
     145             :                         log_fatal(EINTERNAL, "Internal inconsistency for link '%s%s'\n", disk->dir, sub);
     146             :                         os_abort();
     147             :                         /* LCOV_EXCL_STOP */
     148             :                 }
     149             : 
     150             :                 /* mark as present */
     151       38146 :                 link_flag_set(slink, FILE_IS_PRESENT);
     152             : 
     153             :                 /* check if the link is not changed and it's of the same kind */
     154       38146 :                 if (strcmp(slink->linkto, linkto) == 0 && link_flag == link_flag_get(slink, FILE_IS_LINK_MASK)) {
     155             :                         /* it's equal */
     156       38088 :                         ++scan->count_equal;
     157             : 
     158       38088 :                         if (state->opt.gui_verbose) {
     159           0 :                                 log_tag("scan:equal:%s:%s\n", disk->name, esc_tag(slink->sub, esc_buffer));
     160             :                         }
     161             :                 } else {
     162             :                         /* it's an update */
     163             : 
     164             :                         /* we have to save the linkto/type */
     165          58 :                         scan->need_write = 1;
     166             : 
     167          58 :                         ++scan->count_change;
     168             : 
     169          58 :                         log_tag("scan:update:%s:%s\n", disk->name, esc_tag(slink->sub, esc_buffer));
     170          58 :                         if (is_diff) {
     171           3 :                                 msg_info("update %s\n", fmt_term(disk, slink->sub, esc_buffer));
     172             :                         }
     173             : 
     174             :                         /* update it */
     175          58 :                         free(slink->linkto);
     176          58 :                         slink->linkto = strdup_nofail(linkto);
     177          58 :                         link_flag_let(slink, link_flag, FILE_IS_LINK_MASK);
     178             :                 }
     179             : 
     180             :                 /* nothing more to do */
     181       38146 :                 return;
     182             :         } else {
     183             :                 /* create the new link */
     184        2566 :                 ++scan->count_insert;
     185             : 
     186        2566 :                 log_tag("scan:add:%s:%s\n", disk->name, esc_tag(sub, esc_buffer));
     187        2566 :                 if (is_diff) {
     188         599 :                         msg_info("add %s\n", fmt_term(disk, sub, esc_buffer));
     189             :                 }
     190             : 
     191             :                 /* and continue to insert it */
     192             :         }
     193             : 
     194             :         /* insert it */
     195        2566 :         slink = link_alloc(sub, linkto, link_flag);
     196             : 
     197             :         /* mark it as present */
     198        2566 :         link_flag_set(slink, FILE_IS_PRESENT);
     199             : 
     200             :         /* insert it in the delayed insert list */
     201        2566 :         tommy_list_insert_tail(&scan->link_insert_list, &slink->nodelist, slink);
     202             : }
     203             : 
     204             : /**
     205             :  * Insert the specified file in the parity.
     206             :  */
     207      105934 : static void scan_file_allocate(struct snapraid_scan* scan, struct snapraid_file* file)
     208             : {
     209      105934 :         struct snapraid_state* state = scan->state;
     210      105934 :         struct snapraid_disk* disk = scan->disk;
     211             :         block_off_t i;
     212             :         block_off_t parity_pos;
     213             : 
     214             :         /* state changed */
     215      105934 :         scan->need_write = 1;
     216             : 
     217             :         /* allocate the blocks of the file */
     218      105934 :         parity_pos = disk->first_free_block;
     219      360546 :         for (i = 0; i < file->blockmax; ++i) {
     220             :                 struct snapraid_block* block;
     221             :                 struct snapraid_block* over_block;
     222             :                 snapraid_info info;
     223             : 
     224             :                 /* increment the position until the first really free block */
     225      786215 :                 while (block_has_file(fs_par2block_find(disk, parity_pos)))
     226      531603 :                         ++parity_pos;
     227             : 
     228             :                 /* get block we are going to overwrite, if any */
     229      254612 :                 over_block = fs_par2block_find(disk, parity_pos);
     230             : 
     231             :                 /* deallocate it */
     232      254612 :                 if (over_block != BLOCK_NULL)
     233       93731 :                         fs_deallocate(disk, parity_pos);
     234             : 
     235             :                 /* get block specific info */
     236      254612 :                 info = info_get(&state->infoarr, parity_pos);
     237             : 
     238             :                 /* get the new block we are going to write */
     239      254612 :                 block = fs_file2block_get(file, i);
     240             : 
     241             :                 /* if the file block already has an updated hash without rehash */
     242      254612 :                 if (block_has_updated_hash(block) && !info_get_rehash(info)) {
     243             :                         /* the only possible case is for REP blocks */
     244       85548 :                         assert(block_state_get(block) == BLOCK_STATE_REP);
     245             : 
     246             :                         /* convert to a REP block */
     247       85548 :                         block_state_set(block, BLOCK_STATE_REP);
     248             : 
     249             :                         /* and keep the hash as it's */
     250             :                 } else {
     251             :                         unsigned over_state;
     252             : 
     253             :                         /* convert to a CHG block */
     254      169064 :                         block_state_set(block, BLOCK_STATE_CHG);
     255             : 
     256             :                         /* state of the block we are going to overwrite */
     257      169064 :                         over_state = block_state_get(over_block);
     258             : 
     259             :                         /* if the block is an empty one */
     260      169064 :                         if (over_state == BLOCK_STATE_EMPTY) {
     261             :                                 /*
     262             :                                  * The block was empty and filled with zeros
     263             :                                  * set the hash to the special ZERO value
     264             :                                  */
     265      120437 :                                 hash_zero_set(block->hash);
     266             :                         } else {
     267             :                                 /* otherwise it's a DELETED one */
     268       48627 :                                 assert(over_state == BLOCK_STATE_DELETED);
     269             : 
     270             :                                 /*
     271             :                                  * In this cases we don't know if the old state is still the one
     272             :                                  * stored inside the parity, because after an aborted sync, the parity
     273             :                                  * may be or may be not have been updated with the data that it's now
     274             :                                  * deleted.
     275             :                                  *
     276             :                                  * We anyway keep the hash as the new sync will not assume it correct,
     277             :                                  * but the check/fix may be using it.
     278             :                                  *
     279             :                                  * For example:
     280             :                                  * - One file is deleted
     281             :                                  * - Sync aborted after updating the parity to the new state,
     282             :                                  *   but without saving the content file representing this new state.
     283             :                                  * - Another file is added again (exactly here)
     284             :                                  *   with the hash of DELETED block not representing the real parity state
     285             :                                  */
     286             : 
     287             :                                 /* copy the past hash of the block */
     288       48627 :                                 memcpy(block->hash, over_block->hash, BLOCK_HASH_SIZE);
     289             :                         }
     290             :                 }
     291             : 
     292             :                 /* store in the disk map, after invalidating all the other blocks */
     293      254612 :                 fs_allocate(disk, parity_pos, file, i);
     294             : 
     295             :                 /* set the new free position */
     296      254612 :                 disk->first_free_block = parity_pos + 1;
     297             :         }
     298             : 
     299             :         /* insert in the list of contained files */
     300      105934 :         tommy_list_insert_tail(&disk->filelist, &file->nodelist, file);
     301      105934 : }
     302             : 
     303             : /**
     304             :  * Delete the specified file from the parity.
     305             :  *
     306             :  * Note that the parity remains allocated, but the blocks and the file are marked as DELETED.
     307             :  * The file is then inserted in the deleted set, and it should not be deallocated,
     308             :  * as the parity still references it.
     309             :  */
     310       58857 : static void scan_file_deallocate(struct snapraid_scan* scan, struct snapraid_file* file)
     311             : {
     312       58857 :         struct snapraid_disk* disk = scan->disk;
     313             :         block_off_t i;
     314             : 
     315             :         /* remove from the list of contained files */
     316       58857 :         tommy_list_remove_existing(&disk->filelist, &file->nodelist);
     317             : 
     318             :         /* state changed */
     319       58857 :         scan->need_write = 1;
     320             : 
     321             :         /*
     322             :          * Here we are supposed to adjust the ::first_free_block position
     323             :          * with the parity position we are deleting
     324             :          * but we also know that we do only delayed insert, after all the deletion,
     325             :          * so at this point ::first_free_block is always at 0, and we don't need to update it
     326             :          */
     327       58857 :         if (disk->first_free_block != 0) {
     328             :                 /* LCOV_EXCL_START */
     329             :                 log_fatal(EINTERNAL, "Internal inconsistency for first free position at '%u' deallocating file '%s'\n", disk->first_free_block, file->sub);
     330             :                 os_abort();
     331             :                 /* LCOV_EXCL_STOP */
     332             :         }
     333             : 
     334             :         /* free all the blocks of the file */
     335      200683 :         for (i = 0; i < file->blockmax; ++i) {
     336      141826 :                 struct snapraid_block* block = fs_file2block_get(file, i);
     337             :                 unsigned block_state;
     338             : 
     339             :                 /*
     340             :                  * In case we scan after an aborted sync,
     341             :                  * we could get also intermediate states
     342             :                  */
     343      141826 :                 block_state = block_state_get(block);
     344      141826 :                 switch (block_state) {
     345       35354 :                 case BLOCK_STATE_BLK :
     346             :                         /* we keep the hash making it an "old" hash, because the parity is still containing data for it */
     347       35354 :                         break;
     348       29813 :                 case BLOCK_STATE_CHG :
     349             :                         /*
     350             :                          * In this cases we don't know if the old state is still the one
     351             :                          * stored inside the parity, because after an aborted sync, the parity
     352             :                          * may be or may be not have been updated with the data that it's now
     353             :                          * deleted.
     354             :                          *
     355             :                          * We anyway keep the hash as the new sync will not assume it correct,
     356             :                          * but the check/fix may be using it.
     357             :                          *
     358             :                          * For example:
     359             :                          * - One file is added
     360             :                          * - Sync aborted after updating the parity to the new state,
     361             :                          *   but without saving the content file representing this new state.
     362             :                          * - File is now deleted after the aborted sync
     363             :                          * - Sync again, deleting the blocks (exactly here)
     364             :                          *   with the hash of CHG block not representing the real parity state
     365             :                          */
     366       29813 :                         break;
     367       76659 :                 case BLOCK_STATE_REP :
     368             :                         /* we just don't know the old hash, and then we set it to invalid */
     369       76659 :                         hash_invalid_set(block->hash);
     370       76659 :                         break;
     371           0 :                 default :
     372             :                         /* LCOV_EXCL_START */
     373             :                         log_fatal(EINTERNAL, "Internal inconsistency in file '%s' deallocating block '%u:%u' state %u\n", file->sub, i, file->blockmax, block_state);
     374             :                         os_abort();
     375             :                         /* LCOV_EXCL_STOP */
     376             :                 }
     377             : 
     378             :                 /* set the block as deleted */
     379      141826 :                 block_state_set(block, BLOCK_STATE_DELETED);
     380             :         }
     381             : 
     382             :         /* mark the file as deleted */
     383       58857 :         file_flag_set(file, FILE_IS_DELETED);
     384             : 
     385             :         /* insert it in the list of deleted blocks */
     386       58857 :         tommy_list_insert_tail(&disk->deletedlist, &file->nodelist, file);
     387       58857 : }
     388             : 
     389      105934 : static void scan_file_delayed_allocate(struct snapraid_scan* scan, struct snapraid_file* file)
     390             : {
     391      105934 :         struct snapraid_state* state = scan->state;
     392      105934 :         struct snapraid_disk* disk = scan->disk;
     393             : 
     394             :         /* if we sort for physical offsets we have to read them for new files */
     395      105934 :         if (state->opt.force_order == SORT_PHYSICAL
     396       11432 :                 && file->physical == FILEPHY_UNREAD_OFFSET
     397             :         ) {
     398             :                 char path_next[PATH_MAX];
     399             : 
     400       11432 :                 pathprint(path_next, sizeof(path_next), "%s%s", disk->dir, file->sub);
     401             : 
     402       11432 :                 if (filephy(path_next, file->size, &file->physical) != 0) {
     403             :                         /* LCOV_EXCL_START */
     404             :                         log_fatal(errno, "Error in getting the physical offset of file '%s'. %s.\n", path_next, strerror(errno));
     405             :                         exit(EXIT_FAILURE);
     406             :                         /* LCOV_EXCL_STOP */
     407             :                 }
     408             :         }
     409             : 
     410             :         /* insert in the delayed list */
     411      105934 :         tommy_list_insert_tail(&scan->file_insert_list, &file->nodelist, file);
     412      105934 : }
     413             : 
     414             : /**
     415             :  * Check if a file is completely formed of blocks with invalid parity,
     416             :  * and no rehash is tagged, and if it has at least one block.
     417             :  */
     418     1158929 : static int file_is_full_invalid_parity_and_stable(struct snapraid_state* state, struct snapraid_disk* disk, struct snapraid_file* file)
     419             : {
     420             :         block_off_t i;
     421             : 
     422             :         /* with no block, it never has an invalid parity */
     423     1158929 :         if (file->blockmax == 0)
     424        1622 :                 return 0;
     425             : 
     426             :         /* check all blocks */
     427     1216071 :         for (i = 0; i < file->blockmax; ++i) {
     428             :                 snapraid_info info;
     429     1191589 :                 struct snapraid_block* block = fs_file2block_get(file, i);
     430             :                 block_off_t parity_pos;
     431             : 
     432             :                 /* exclude blocks with parity */
     433     1191589 :                 if (!block_has_invalid_parity(block))
     434     1132825 :                         return 0;
     435             : 
     436             :                 /*
     437             :                  * Get the parity position.
     438             :                  *
     439             :                  * Note that here we expect to always have mapped
     440             :                  * parity, because kept files always have it.
     441             :                  *
     442             :                  * Anyway, checking for POS_NULL doesn't hurt.
     443             :                  */
     444       58764 :                 parity_pos = fs_file2par_find(disk, file, i);
     445             : 
     446             :                 /* if it's not mapped, it cannot have rehash */
     447       58764 :                 if (parity_pos != POS_NULL) {
     448             :                         /* get block specific info */
     449       58764 :                         info = info_get(&state->infoarr, parity_pos);
     450             : 
     451             :                         /* if rehash fails */
     452       58764 :                         if (info_get_rehash(info))
     453           0 :                                 return 0;
     454             :                 }
     455             :         }
     456             : 
     457       24482 :         return 1;
     458             : }
     459             : 
     460             : /**
     461             :  * Check if a file is completely formed of blocks with an updated hash,
     462             :  * and no rehash is tagged, and if it has at least one block.
     463             :  */
     464       23020 : static int file_is_full_hashed_and_stable(struct snapraid_state* state, struct snapraid_disk* disk, struct snapraid_file* file)
     465             : {
     466             :         block_off_t i;
     467             : 
     468             :         /* with no block, it never has a hash */
     469       23020 :         if (file->blockmax == 0)
     470          25 :                 return 0;
     471             : 
     472             :         /* check all blocks */
     473       79548 :         for (i = 0; i < file->blockmax; ++i) {
     474             :                 snapraid_info info;
     475       56559 :                 struct snapraid_block* block = fs_file2block_get(file, i);
     476             :                 block_off_t parity_pos;
     477             : 
     478             :                 /* exclude blocks without hash */
     479       56559 :                 if (!block_has_updated_hash(block))
     480           6 :                         return 0;
     481             : 
     482             :                 /*
     483             :                  * Get the parity position.
     484             :                  *
     485             :                  * Note that it's possible to have files
     486             :                  * not mapped into the parity, even if they
     487             :                  * have a valid hash.
     488             :                  *
     489             :                  * This happens for example, for 'copied' files
     490             :                  * that have REP blocks, but not yet mapped.
     491             :                  *
     492             :                  * If there are multiple copies, it's also possible
     493             :                  * that such files are used as 'source' to copy
     494             :                  * hashes, and then to get them inside this function.
     495             :                  */
     496       56553 :                 parity_pos = fs_file2par_find(disk, file, i);
     497             : 
     498             :                 /* if it's not mapped, it cannot have rehash */
     499       56553 :                 if (parity_pos != POS_NULL) {
     500             :                         /* get block specific info */
     501       56545 :                         info = info_get(&state->infoarr, parity_pos);
     502             : 
     503             :                         /* exclude blocks needing a rehash */
     504       56545 :                         if (info_get_rehash(info))
     505           0 :                                 return 0;
     506             :                 }
     507             :         }
     508             : 
     509       22989 :         return 1;
     510             : }
     511             : 
     512             : /**
     513             :  * Refresh the file info.
     514             :  *
     515             :  * This is needed by Windows as the normal way to list directories may report not
     516             :  * updated info. Only the GetFileInformationByHandle() func, called file-by-file,
     517             :  * really ensures to return synced info.
     518             :  *
     519             :  * If this happens, we read also the physical offset, to avoid to read it later.
     520             :  */
     521       81452 : static void scan_file_refresh(struct snapraid_scan* scan, const char* sub, struct stat* st, uint64_t* physical)
     522             : {
     523             : #if HAVE_LSTAT_SYNC
     524             :         struct snapraid_state* state = scan->state;
     525             :         struct snapraid_disk* disk = scan->disk;
     526             : 
     527             :         /* if the st_sync is not set, ensure to get synced info */
     528             :         if (st->st_sync == 0) {
     529             :                 char path_next[PATH_MAX];
     530             :                 struct stat synced_st;
     531             : 
     532             :                 pathprint(path_next, sizeof(path_next), "%s%s", disk->dir, sub);
     533             : 
     534             :                 /* if we sort for physical offsets we have to read them for new files */
     535             :                 if (state->opt.force_order == SORT_PHYSICAL
     536             :                         && *physical == FILEPHY_UNREAD_OFFSET
     537             :                 ) {
     538             :                         /* do nothing, leave the pointer to read the physical offset */
     539             :                 } else {
     540             :                         physical = 0; /* set the pointer to 0 to read nothing */
     541             :                 }
     542             : 
     543             :                 if (lstat_sync(path_next, &synced_st, physical) != 0) {
     544             :                         /* LCOV_EXCL_START */
     545             :                         log_fatal(errno, "Error in stat file '%s'. %s.\n", path_next, strerror(errno));
     546             :                         exit(EXIT_FAILURE);
     547             :                         /* LCOV_EXCL_STOP */
     548             :                 }
     549             : 
     550             :                 if (st->st_mtime != synced_st.st_mtime
     551             :                         || st->st_mtimensec != synced_st.st_mtimensec
     552             :                 ) {
     553             : #ifndef _WIN32
     554             :                         /*
     555             :                          * In Windows having different metadata is expected with open files
     556             :                          * because the metadata in the directory is updated only when the file
     557             :                          * is closed.
     558             :                          *
     559             :                          * The same happens for hardlinks that duplicate metadata.
     560             :                          * The link metadata is updated only when the link is opened.
     561             :                          * This extends also to st_size and st_nlink.
     562             :                          *
     563             :                          * See also:
     564             :                          * Why is the file size reported incorrectly for files that are still being written to?
     565             :                          * http://blogs.msdn.com/b/oldnewthing/archive/2011/12/26/10251026.aspx
     566             :                          */
     567             :                         log_fatal(ESOFT, "WARNING! Detected uncached time change from %" PRIu64 ".%09u to %" PRIu64 ".%09u for file '%s'\n",
     568             :                                 (uint64_t)st->st_mtime, (uint32_t)st->st_mtimensec, (uint64_t)synced_st.st_mtime, (uint32_t)synced_st.st_mtimensec, sub);
     569             :                         log_fatal(ESOFT, "It's better if you run SnapRAID without other processes running.\n");
     570             : #endif
     571             :                         st->st_mtime = synced_st.st_mtime;
     572             :                         st->st_mtimensec = synced_st.st_mtimensec;
     573             :                 }
     574             : 
     575             :                 if (st->st_size != synced_st.st_size) {
     576             : #ifndef _WIN32
     577             :                         log_fatal(ESOFT, "WARNING! Detected uncached size change from %" PRIu64 " to %" PRIu64 " for file '%s'\n",
     578             :                                 (uint64_t)st->st_size, (uint64_t)synced_st.st_size, sub);
     579             :                         log_fatal(ESOFT, "It's better if you run SnapRAID without other processes running.\n");
     580             : #endif
     581             :                         st->st_size = synced_st.st_size;
     582             :                 }
     583             : 
     584             :                 if (st->st_nlink != synced_st.st_nlink) {
     585             : #ifndef _WIN32
     586             :                         log_fatal(ESOFT, "WARNING! Detected uncached nlink change from %u to %u for file '%s'\n",
     587             :                                 (uint32_t)st->st_nlink, (uint32_t)synced_st.st_nlink, sub);
     588             :                         log_fatal(ESOFT, "It's better if you run SnapRAID without other processes running.\n");
     589             : #endif
     590             :                         st->st_nlink = synced_st.st_nlink;
     591             :                 }
     592             : 
     593             :                 if (st->st_ino != synced_st.st_ino) {
     594             :                         log_fatal(ESOFT, "DANGER! Detected uncached inode change from %" PRIu64 " to %" PRIu64 " for file '%s'\n",
     595             :                                 (uint64_t)st->st_ino, (uint64_t)synced_st.st_ino, sub);
     596             :                         log_fatal(ESOFT, "It's better if you run SnapRAID without other processes running.\n");
     597             :                         /*
     598             :                          * At this point, it's too late to change inode
     599             :                          * and having inconsistent inodes may result to internal failures
     600             :                          * so, it's better to abort
     601             :                          */
     602             :                         exit(EXIT_FAILURE);
     603             :                 }
     604             :         }
     605             : #else
     606             :         (void)scan;
     607             :         (void)sub;
     608             :         (void)st;
     609             :         (void)physical;
     610             : #endif
     611       81452 : }
     612             : 
     613             : /**
     614             :  * Insert the file in the data set.
     615             :  */
     616      105934 : static void scan_file_insert(struct snapraid_scan* scan, struct snapraid_file* file)
     617             : {
     618      105934 :         struct snapraid_disk* disk = scan->disk;
     619             : 
     620             :         /* insert the file in the containers */
     621      105934 :         if (!file_flag_has(file, FILE_IS_WITHOUT_INODE))
     622      105934 :                 tommy_hashdyn_insert(&disk->inodeset, &file->nodeset, file, file_inode_hash(file->inode));
     623             : 
     624      105934 :         stamp_lock(disk);
     625      105934 :         tommy_hashdyn_insert(&disk->pathset, &file->pathset, file, file_path_hash(file->sub));
     626      105934 :         tommy_hashdyn_insert(&disk->stampset, &file->stampset, file, file_stamp_hash(file->size, file->mtime_sec, file->mtime_nsec));
     627      105934 :         stamp_unlock(disk);
     628             : 
     629             :         /* delayed allocation of the parity */
     630      105934 :         scan_file_delayed_allocate(scan, file);
     631      105934 : }
     632             : 
     633             : /**
     634             :  * Remove the file from the data set.
     635             :  *
     636             :  * File is then deleted.
     637             :  */
     638       58857 : static void scan_file_remove(struct snapraid_scan* scan, struct snapraid_file* file, int keep_track)
     639             : {
     640       58857 :         struct snapraid_disk* disk = scan->disk;
     641             : 
     642             :         /* remove the file from the containers */
     643       58857 :         if (!file_flag_has(file, FILE_IS_WITHOUT_INODE))
     644       58827 :                 tommy_hashdyn_remove_existing(&disk->inodeset, &file->nodeset);
     645       58857 :         tommy_hashdyn_remove_existing(&disk->pathset, &file->pathset);
     646             : 
     647             :         /*
     648             :          * Keep track of the removed file (that won't be added later)
     649             :          *
     650             :          * This allows to record from where the parity was computed.
     651             :          */
     652       58857 :         if (keep_track) {
     653       34375 :                 struct snapraid_dealloc* dealloc = dealloc_alloc(scan->state->block_size, file->sub, file->size, file->mtime_sec, file->mtime_nsec);
     654             : 
     655       34375 :                 dealloc_import(dealloc, file);
     656             : 
     657             :                 /* insert the dealloc in the dealloc containers */
     658       34375 :                 tommy_list_insert_tail(&disk->dealloclist, &dealloc->nodelist, dealloc);
     659             :         }
     660             : 
     661       58857 :         stamp_lock(disk);
     662       58857 :         tommy_hashdyn_remove_existing(&disk->stampset, &file->stampset);
     663       58857 :         stamp_unlock(disk);
     664             : 
     665             :         /* deallocate the file from the parity */
     666       58857 :         scan_file_deallocate(scan, file);
     667       58857 : }
     668             : 
     669             : /**
     670             :  * Keep the file as it's (or with only a name/inode modification).
     671             :  *
     672             :  * If the file is kept, nothing has to be done.
     673             :  *
     674             :  * But if a file contains only blocks with invalid parity, it's reallocated to ensure
     675             :  * to always minimize the space used in the parity.
     676             :  *
     677             :  * This could happen after a failed sync, when some other files are deleted,
     678             :  * and then new ones can be moved backward to fill the hole created.
     679             :  */
     680     1158929 : static void scan_file_keep(struct snapraid_scan* scan, struct snapraid_file* file)
     681             : {
     682     1158929 :         struct snapraid_disk* disk = scan->disk;
     683             : 
     684             :         /* if the file is full invalid, schedule a reinsert at later stage */
     685     1158929 :         if (file_is_full_invalid_parity_and_stable(scan->state, disk, file)) {
     686       24482 :                 struct snapraid_file* copy = file_dup(file);
     687             : 
     688             :                 /* remove the file */
     689       24482 :                 scan_file_remove(scan, file, 0);
     690             : 
     691             :                 /* reinsert the copy in the delayed list */
     692       24482 :                 scan_file_insert(scan, copy);
     693             :         }
     694     1158929 : }
     695             : 
     696             : /**
     697             :  * Process a file.
     698             :  */
     699     1240603 : static void scan_file(struct snapraid_scan* scan, int is_diff, const char* sub, struct stat* st, uint64_t physical)
     700             : {
     701     1240603 :         struct snapraid_state* state = scan->state;
     702     1240603 :         struct snapraid_disk* disk = scan->disk;
     703             :         struct snapraid_file* file;
     704             :         tommy_node* i;
     705             :         int is_original_file_size_different_than_zero;
     706             :         int is_file_already_present;
     707             :         data_off_t file_already_present_size;
     708             :         int64_t file_already_present_mtime_sec;
     709             :         int file_already_present_mtime_nsec;
     710             :         int is_file_reported;
     711             :         char esc_buffer[ESC_MAX];
     712             :         char esc_buffer_alt[ESC_MAX];
     713             : 
     714             :         /*
     715             :          * If the disk has persistent inodes and UUID, try a search on the past inodes,
     716             :          * to detect moved files.
     717             :          *
     718             :          * For persistent inodes we mean inodes that keep their values when the file-system
     719             :          * is unmounted and remounted. This don't always happen.
     720             :          *
     721             :          * Cases found are:
     722             :          * - Linux FUSE with exFAT driver from https://code.google.com/p/exfat/.
     723             :          *   Inodes are reassigned at every mount restarting from 1 and incrementing.
     724             :          *   As worse, the exFAT support in FUSE doesn't use sub-second precision in timestamps
     725             :          *   making inode collision more easy (exFAT by design supports 10ms precision).
     726             :          * - Linux VFAT kernel (3.2) driver. Inodes are fully reassigned at every mount.
     727             :          *
     728             :          * In such cases, to avoid possible random collisions, it's better to disable the moved
     729             :          * file recognition.
     730             :          *
     731             :          * For persistent UUID we mean that it has the same UUID as before.
     732             :          * Otherwise, if the UUID is changed, likely it's a new recreated file-system,
     733             :          * and then the inode have no meaning.
     734             :          *
     735             :          * Note that to disable the search by past inode, we do this implicitly
     736             :          * removing all the past inode before searching for files.
     737             :          * This ensures that no file is found with a past inode, but at the same time,
     738             :          * it allows to find new files with the same inode, to identify them as hardlinks.
     739             :          */
     740     1240603 :         int has_past_inodes = !disk->has_volatile_inodes && !disk->has_different_uuid && !disk->has_unsupported_uuid;
     741             : 
     742             :         /*
     743             :          * Always search with the new inode, in the all new inodes found until now,
     744             :          * with the eventual presence of also the past inodes
     745             :          */
     746     1240603 :         uint64_t inode = st->st_ino;
     747             : 
     748     1240603 :         file = tommy_hashdyn_search(&disk->inodeset, file_inode_compare_to_arg, &inode, file_inode_hash(inode));
     749             : 
     750             :         /* identify moved files with past inodes and hardlinks with the new inodes */
     751     1240603 :         if (file) {
     752             :                 /* check if the file is not changed */
     753     1070742 :                 if (file->size == st->st_size
     754     1056029 :                         && file->mtime_sec == st->st_mtime
     755     1054197 :                         && (file->mtime_nsec == STAT_NSEC(st)
     756             :                         /*
     757             :                          * Always accept the stored value if it's STAT_NSEC_INVALID
     758             :                          * it happens when upgrading from an old version of SnapRAID
     759             :                          * not yet supporting the nanosecond field
     760             :                          */
     761           6 :                         || file->mtime_nsec == STAT_NSEC_INVALID
     762             :                         )
     763             :                 ) {
     764             :                         /* check if multiple files have the same inode */
     765     1054191 :                         if (file_flag_has(file, FILE_IS_PRESENT)) {
     766             :                                 /* if has_volatile_hardlinks is true, the nlink value is not reliable */
     767         222 :                                 if (!disk->has_volatile_hardlinks && st->st_nlink == 1) {
     768             :                                         /* LCOV_EXCL_START */
     769             :                                         log_fatal(EINTERNAL, "Internal inode '%" PRIu64 "' inconsistency for file '%s%s' already present\n", (uint64_t)st->st_ino, disk->dir, sub);
     770             :                                         os_abort();
     771             :                                         /* LCOV_EXCL_STOP */
     772             :                                 }
     773             : 
     774             :                                 /* it's a hardlink */
     775         222 :                                 scan_link(scan, is_diff, sub, file->sub, FILE_IS_HARDLINK);
     776     1159151 :                                 return;
     777             :                         }
     778             : 
     779             :                         /* mark as present */
     780     1053969 :                         file_flag_set(file, FILE_IS_PRESENT);
     781             : 
     782             :                         /*
     783             :                          * Update the nanoseconds mtime only if different
     784             :                          * to avoid unneeded updates
     785             :                          */
     786     1053969 :                         if (file->mtime_nsec == STAT_NSEC_INVALID
     787           0 :                                 && STAT_NSEC(st) != file->mtime_nsec
     788             :                         ) {
     789           0 :                                 file->mtime_nsec = STAT_NSEC(st);
     790             : 
     791             :                                 /* we have to save the new mtime */
     792           0 :                                 scan->need_write = 1;
     793             :                         }
     794             : 
     795     1053969 :                         if (strcmp(file->sub, sub) != 0) {
     796             :                                 /* if the path is different, it means a moved file with the same inode */
     797         155 :                                 ++scan->count_move;
     798             : 
     799         155 :                                 log_tag("scan:move:%s:%s:%s\n", disk->name, esc_tag(file->sub, esc_buffer), esc_tag(sub, esc_buffer_alt));
     800         155 :                                 if (is_diff) {
     801           0 :                                         msg_info("move %s -> %s\n", fmt_term(disk, file->sub, esc_buffer), fmt_term(disk, sub, esc_buffer_alt));
     802             :                                 }
     803             : 
     804             :                                 /* remove from the name set */
     805         155 :                                 tommy_hashdyn_remove_existing(&disk->pathset, &file->pathset);
     806             : 
     807             :                                 /* save the new name */
     808         155 :                                 file_rename(file, sub);
     809             : 
     810             :                                 /* reinsert in the name set */
     811         155 :                                 tommy_hashdyn_insert(&disk->pathset, &file->pathset, file, file_path_hash(file->sub));
     812             : 
     813             :                                 /* we have to save the new name */
     814         155 :                                 scan->need_write = 1;
     815             :                         } else {
     816             :                                 /* otherwise it's equal */
     817     1053814 :                                 ++scan->count_equal;
     818             : 
     819     1053814 :                                 if (state->opt.gui_verbose) {
     820           0 :                                         log_tag("scan:equal:%s:%s\n", disk->name, esc_tag(file->sub, esc_buffer));
     821             :                                 }
     822             :                         }
     823             : 
     824             :                         /* mark the file as kept */
     825     1053969 :                         scan_file_keep(scan, file);
     826             : 
     827             :                         /* nothing more to do */
     828     1053969 :                         return;
     829             :                 }
     830             : 
     831             :                 /*
     832             :                  * Here the file matches the inode, but not the other info
     833             :                  *
     834             :                  * It could be a modified file with the same name,
     835             :                  * or a restored/copied file that get assigned a previously used inode,
     836             :                  * or a file-system with not persistent inodes.
     837             :                  *
     838             :                  * In NTFS it could be also a hardlink, because in NTFS
     839             :                  * hardlink don't share the same directory information,
     840             :                  * like attribute and time.
     841             :                  *
     842             :                  * For example:
     843             :                  *   C:> echo A > A
     844             :                  *   C:> mklink /H B A
     845             :                  *   ...wait one minute
     846             :                  *   C:> echo AAAAAAAAAAAAAA > A
     847             :                  *   C:> dir
     848             :                  *   ...both time and size of A and B don't match!
     849             :                  */
     850       16551 :                 if (file_flag_has(file, FILE_IS_PRESENT)) {
     851             :                         /* if has_volatile_hardlinks is true, the nlink value is not reliable */
     852           0 :                         if (!disk->has_volatile_hardlinks && st->st_nlink == 1) {
     853             :                                 /* LCOV_EXCL_START */
     854             :                                 log_fatal(EINTERNAL, "Internal inode '%" PRIu64 "' inconsistency for files '%s%s' and '%s%s' with same inode but different attributes: size %" PRIu64 "?%" PRIu64 ", sec %" PRIu64 "?%" PRIu64 ", nsec %d?%d\n",
     855             :                                         file->inode, disk->dir, sub, disk->dir, file->sub,
     856             :                                         file->size, (uint64_t)st->st_size,
     857             :                                         file->mtime_sec, (uint64_t)st->st_mtime,
     858             :                                         file->mtime_nsec, STAT_NSEC(st));
     859             :                                 os_abort();
     860             :                                 /* LCOV_EXCL_STOP */
     861             :                         }
     862             : 
     863             :                         /* LCOV_EXCL_START */
     864             :                         /* suppose it's hardlink with not synced metadata */
     865             :                         scan_link(scan, is_diff, sub, file->sub, FILE_IS_HARDLINK);
     866             :                         return;
     867             :                         /* LCOV_EXCL_STOP */
     868             :                 }
     869             : 
     870             :                 /*
     871             :                  * Assume a previously used inode, it's the worst case
     872             :                  * and we handle it removing the duplicate stored inode.
     873             :                  * If the file is found by name later, it will have the inode restored,
     874             :                  * otherwise, it will get removed
     875             :                  */
     876             : 
     877             :                 /* remove from the inode set */
     878       16551 :                 tommy_hashdyn_remove_existing(&disk->inodeset, &file->nodeset);
     879             : 
     880             :                 /*
     881             :                  * Clear the inode
     882             :                  * this is not really needed for correct functionality
     883             :                  * because we are going to set FILE_IS_WITHOUT_INODE
     884             :                  * but it's easier for debugging to have invalid inodes set to 0
     885             :                  */
     886       16551 :                 file->inode = 0;
     887             : 
     888             :                 /* mark as missing inode */
     889       16551 :                 file_flag_set(file, FILE_IS_WITHOUT_INODE);
     890             : 
     891             :                 /* go further to find it by name */
     892             :         }
     893             : 
     894             :         /* initialize for later overwrite */
     895      186412 :         is_file_reported = 0;
     896      186412 :         is_original_file_size_different_than_zero = 0;
     897             : 
     898             :         /* then try finding it by name */
     899      186412 :         file = tommy_hashdyn_search(&disk->pathset, file_path_compare_to_arg, sub, file_path_hash(sub));
     900             : 
     901             :         /* keep track if the file already exists */
     902      186412 :         is_file_already_present = file != 0;
     903             : 
     904      186412 :         if (is_file_already_present) {
     905             :                 /* if the file is without an inode */
     906      109661 :                 if (file_flag_has(file, FILE_IS_WITHOUT_INODE)) {
     907             :                         /* set it now */
     908       16535 :                         file->inode = st->st_ino;
     909             : 
     910             :                         /* insert in the set */
     911       16535 :                         tommy_hashdyn_insert(&disk->inodeset, &file->nodeset, file, file_inode_hash(file->inode));
     912             : 
     913             :                         /* unmark as missing inode */
     914       16535 :                         file_flag_clear(file, FILE_IS_WITHOUT_INODE);
     915             :                 } else {
     916             :                         /* here the inode has to be different, otherwise we would have found it before */
     917       93126 :                         if (file->inode == st->st_ino) {
     918             :                                 /* LCOV_EXCL_START */
     919             :                                 log_fatal(EINTERNAL, "Internal inconsistency in inode '%" PRIu64 "' for files '%s%s' as unexpected matching\n", file->inode, disk->dir, sub);
     920             :                                 os_abort();
     921             :                                 /* LCOV_EXCL_STOP */
     922             :                         }
     923             :                 }
     924             : 
     925             :                 /* for sure it cannot be already present */
     926      109661 :                 if (file_flag_has(file, FILE_IS_PRESENT)) {
     927             :                         /* LCOV_EXCL_START */
     928             :                         log_fatal(EINTERNAL, "Internal inconsistency in path for file '%s%s' matching and already present\n", disk->dir, sub);
     929             :                         os_abort();
     930             :                         /* LCOV_EXCL_STOP */
     931             :                 }
     932             : 
     933             :                 /* check if the file is not changed */
     934      109661 :                 if (file->size == st->st_size
     935      107823 :                         && file->mtime_sec == st->st_mtime
     936      104962 :                         && (file->mtime_nsec == STAT_NSEC(st)
     937             :                         /*
     938             :                          * Always accept the stored value if it's STAT_NSEC_INVALID
     939             :                          * it happens when upgrading from an old version of SnapRAID
     940             :                          * not yet supporting the nanosecond field
     941             :                          */
     942           2 :                         || file->mtime_nsec == STAT_NSEC_INVALID
     943             :                         )
     944             :                 ) {
     945             :                         /* mark as present */
     946      104960 :                         file_flag_set(file, FILE_IS_PRESENT);
     947             : 
     948             :                         /*
     949             :                          * Update the nano seconds mtime only if different
     950             :                          * to avoid unneeded updates
     951             :                          */
     952      104960 :                         if (file->mtime_nsec == STAT_NSEC_INVALID
     953           0 :                                 && STAT_NSEC(st) != STAT_NSEC_INVALID
     954             :                         ) {
     955           0 :                                 file->mtime_nsec = STAT_NSEC(st);
     956             : 
     957             :                                 /* we have to save the new mtime */
     958           0 :                                 scan->need_write = 1;
     959             :                         }
     960             : 
     961             :                         /* if when processing the disk we used the past inodes values */
     962      104960 :                         if (has_past_inodes) {
     963             :                                 /*
     964             :                                  * If persistent inodes are supported, we are sure that the inode number
     965             :                                  * is now different, because otherwise the file would have been found
     966             :                                  * when searching by inode.
     967             :                                  * if the inode is different, it means a rewritten file with the same path
     968             :                                  * like when restoring a backup that restores also the timestamp
     969             :                                  */
     970      104946 :                                 ++scan->count_restore;
     971             : 
     972      104946 :                                 log_tag("scan:restore:%s:%s\n", disk->name, esc_tag(sub, esc_buffer));
     973      104946 :                                 if (is_diff) {
     974           0 :                                         msg_info("restore %s\n", fmt_term(disk, sub, esc_buffer));
     975             :                                 }
     976             : 
     977             :                                 /* remove from the inode set */
     978      104946 :                                 tommy_hashdyn_remove_existing(&disk->inodeset, &file->nodeset);
     979             : 
     980             :                                 /* save the new inode */
     981      104946 :                                 file->inode = st->st_ino;
     982             : 
     983             :                                 /* reinsert in the inode set */
     984      104946 :                                 tommy_hashdyn_insert(&disk->inodeset, &file->nodeset, file, file_inode_hash(file->inode));
     985             : 
     986             :                                 /* we have to save the new inode */
     987      104946 :                                 scan->need_write = 1;
     988             :                         } else {
     989             :                                 /*
     990             :                                  * Otherwise it's the case of not persistent inode, where doesn't
     991             :                                  * matter if the inode is different or equal, because they have no
     992             :                                  * meaning, and then we don't even save them
     993             :                                  */
     994          14 :                                 ++scan->count_equal;
     995             : 
     996          14 :                                 if (state->opt.gui_verbose) {
     997           0 :                                         log_tag("scan:equal:%s:%s\n", disk->name, esc_tag(file->sub, esc_buffer));
     998             :                                 }
     999             :                         }
    1000             : 
    1001             :                         /* mark the file as kept */
    1002      104960 :                         scan_file_keep(scan, file);
    1003             : 
    1004             :                         /* nothing more to do */
    1005      104960 :                         return;
    1006             :                 }
    1007             : 
    1008             :                 /* here if the file is changed but with the correct name */
    1009             : 
    1010             :                 /* save the info for later printout */
    1011        4701 :                 file_already_present_size = file->size;
    1012        4701 :                 file_already_present_mtime_sec = file->mtime_sec;
    1013        4701 :                 file_already_present_mtime_nsec = file->mtime_nsec;
    1014             : 
    1015             :                 /* keep track if the original file was not of zero size */
    1016        4701 :                 is_original_file_size_different_than_zero = file->size != 0;
    1017             : 
    1018             :                 /* remove it, and continue to insert it again */
    1019        4701 :                 scan_file_remove(scan, file, 1);
    1020             : 
    1021             :                 /* and continue to insert it again */
    1022             :         } else {
    1023       76751 :                 file_already_present_size = 0;
    1024       76751 :                 file_already_present_mtime_sec = 0;
    1025       76751 :                 file_already_present_mtime_nsec = 0;
    1026             :         }
    1027             : 
    1028             :         /*
    1029             :          * Refresh the info, to ensure that they are synced,
    1030             :          * note that we refresh only the info of the new or modified files
    1031             :          * because this is slow operation
    1032             :          */
    1033       81452 :         scan_file_refresh(scan, sub, st, &physical);
    1034             : 
    1035             : #ifndef _WIN32
    1036             :         /*
    1037             :          * Do a safety check to ensure that the common ext4 case of zeroing
    1038             :          * the size of a file after a crash doesn't propagate to the backup
    1039             :          * this check is specific for Linux, so we disable it on Windows
    1040             :          */
    1041       81452 :         if (is_original_file_size_different_than_zero && st->st_size == 0) {
    1042           0 :                 if (!state->opt.force_zero) {
    1043             :                         /* LCOV_EXCL_START */
    1044             :                         log_fatal(ESOFT, "The file '%s%s' has unexpected zero size!\n", disk->mount_point, sub);
    1045             :                         log_fatal(ESOFT, "It's possible that after a kernel crash this file was lost,\n");
    1046             :                         log_fatal(ESOFT, "and you can use 'snapraid fix -f /%s' to recover it.\n", fmt_poll(disk, sub, esc_buffer));
    1047             :                         if (!is_diff) {
    1048             :                                 log_fatal(ESOFT, "If this an expected condition you can '%s' anyway using 'snapraid --force-zero %s'\n", state->command, state->command);
    1049             :                                 exit(EXIT_FAILURE);
    1050             :                         }
    1051             :                         /* LCOV_EXCL_STOP */
    1052             :                 }
    1053             :         }
    1054             : #else
    1055             :         /* avoid the unused warning in Windows */
    1056             :         (void)is_original_file_size_different_than_zero;
    1057             : #endif
    1058             : 
    1059             :         /* insert it */
    1060       81452 :         file = file_alloc(state->block_size, sub, st->st_size, st->st_mtime, STAT_NSEC(st), st->st_ino, physical);
    1061             : 
    1062             :         /* mark it as present */
    1063       81452 :         file_flag_set(file, FILE_IS_PRESENT);
    1064             : 
    1065             :         /*
    1066             :          * If copy detection is enabled
    1067             :          * note that the copy detection is tried also for updated files
    1068             :          * this makes sense because it may happen to have two different copies
    1069             :          * of the same file, and we move the right one over the wrong one
    1070             :          * in such case we have a "copy" over an "update"
    1071             :          */
    1072       81452 :         if (!state->opt.force_nocopy) {
    1073       81452 :                 tommy_uint32_t hash = file_stamp_hash(file->size, file->mtime_sec, file->mtime_nsec);
    1074             : 
    1075             :                 /* search for a file with the same name and stamp in all the disks */
    1076      428992 :                 for (i = state->disklist; i != 0; i = i->next) {
    1077      370529 :                         struct snapraid_disk* other_disk = i->data;
    1078             :                         struct snapraid_file* other_file;
    1079             : 
    1080      370529 :                         stamp_lock(other_disk);
    1081             :                         /*
    1082             :                          * If the nanosecond part of the time stamp is valid, search
    1083             :                          * for name and stamp, otherwise for path and stamp
    1084             :                          */
    1085      370529 :                         if (file->mtime_nsec != 0 && file->mtime_nsec != STAT_NSEC_INVALID)
    1086      370421 :                                 other_file = tommy_hashdyn_search(&other_disk->stampset, file_namestamp_compare, file, hash);
    1087             :                         else
    1088         108 :                                 other_file = tommy_hashdyn_search(&other_disk->stampset, file_pathstamp_compare, file, hash);
    1089      370529 :                         stamp_unlock(other_disk);
    1090             : 
    1091             :                         /* if found, and it's a fully hashed file */
    1092      370529 :                         if (other_file && file_is_full_hashed_and_stable(scan->state, other_disk, other_file)) {
    1093             :                                 char path_other[PATH_MAX];
    1094             :                                 struct stat other_st;
    1095             : 
    1096             :                                 /*
    1097             :                                  * Protect the write as multiple threads may write the same FILE_IS_RELOCATED bit.
    1098             :                                  *
    1099             :                                  * The bit is always written as 1 and never read, so protection is likely unnecessary
    1100             :                                  * but still valuable to avoid data race reports from checker tools
    1101             :                                  */
    1102       22989 :                                 stamp_lock(other_disk);
    1103       22989 :                                 file_flag_set(other_file, FILE_IS_RELOCATED);
    1104       22989 :                                 stamp_unlock(other_disk);
    1105             : 
    1106             :                                 /* assume that the file is a copy, and reuse the hash */
    1107       22989 :                                 file_copy(other_file, file);
    1108             : 
    1109             :                                 /* check if other file still exists */
    1110       22989 :                                 pathprint(path_other, sizeof(path_other), "%s%s", other_disk->dir, other_file->sub);
    1111       22989 :                                 if (lstat(path_other, &other_st) == 0) {
    1112       15975 :                                         ++scan->count_copy;
    1113             : 
    1114       15975 :                                         log_tag("scan:copy:%s:%s:%s:%s\n", other_disk->name, esc_tag(other_file->sub, esc_buffer), disk->name, esc_tag(file->sub, esc_buffer_alt));
    1115       15975 :                                         if (is_diff) {
    1116           0 :                                                 msg_info("copy %s -> %s\n", fmt_term(other_disk, other_file->sub, esc_buffer), fmt_term(disk, file->sub, esc_buffer_alt));
    1117             :                                         }
    1118             :                                 } else {
    1119        7014 :                                         ++scan->count_relocate;
    1120             : 
    1121        7014 :                                         log_tag("scan:relocate:%s:%s:%s:%s\n", other_disk->name, esc_tag(other_file->sub, esc_buffer), disk->name, esc_tag(file->sub, esc_buffer_alt));
    1122        7014 :                                         if (is_diff) {
    1123          31 :                                                 msg_info("relocate %s -> %s\n", fmt_term(other_disk, other_file->sub, esc_buffer), fmt_term(disk, file->sub, esc_buffer_alt));
    1124             :                                         }
    1125             :                                 }
    1126             : 
    1127             :                                 /* mark it as reported */
    1128       22989 :                                 is_file_reported = 1;
    1129             : 
    1130             :                                 /* no need to continue the search */
    1131       22989 :                                 break;
    1132             :                         }
    1133             :                 }
    1134             :         }
    1135             : 
    1136             :         /*
    1137             :          * If not yet reported, do it now
    1138             :          * we postpone this to avoid to print two times the copied files
    1139             :          */
    1140       81452 :         if (!is_file_reported) {
    1141       58463 :                 if (is_file_already_present) {
    1142        4701 :                         ++scan->count_change;
    1143             : 
    1144        4701 :                         log_tag("scan:update:%s:%s: %" PRIu64 " %" PRIu64 ".%d -> %" PRIu64 " %" PRIu64 ".%d\n", disk->name, esc_tag(sub, esc_buffer),
    1145             :                                 file_already_present_size, file_already_present_mtime_sec, file_already_present_mtime_nsec,
    1146             :                                 file->size, file->mtime_sec, file->mtime_nsec
    1147             :                         );
    1148             : 
    1149        4701 :                         if (is_diff) {
    1150         118 :                                 msg_info("update %s\n", fmt_term(disk, sub, esc_buffer));
    1151             :                         }
    1152             :                 } else {
    1153       53762 :                         ++scan->count_insert;
    1154             : 
    1155       53762 :                         log_tag("scan:add:%s:%s\n", disk->name, esc_tag(sub, esc_buffer));
    1156       53762 :                         if (is_diff) {
    1157       17054 :                                 msg_info("add %s\n", fmt_term(disk, sub, esc_buffer));
    1158             :                         }
    1159             :                 }
    1160             :         }
    1161             : 
    1162             :         /* insert the file in the delayed list */
    1163       81452 :         scan_file_insert(scan, file);
    1164             : }
    1165             : 
    1166             : /**
    1167             :  * Remove the specified dir from the data set.
    1168             :  */
    1169           2 : static void scan_emptydir_remove(struct snapraid_scan* scan, struct snapraid_dir* dir)
    1170             : {
    1171           2 :         struct snapraid_disk* disk = scan->disk;
    1172             : 
    1173             :         /* state changed */
    1174           2 :         scan->need_write = 1;
    1175             : 
    1176             :         /* remove the file from the dir containers */
    1177           2 :         tommy_hashdyn_remove_existing(&disk->dirset, &dir->nodeset);
    1178           2 :         tommy_list_remove_existing(&disk->dirlist, &dir->nodelist);
    1179             : 
    1180             :         /* deallocate */
    1181           2 :         dir_free(dir);
    1182           2 : }
    1183             : 
    1184             : /**
    1185             :  * Insert the specified dir in the data set.
    1186             :  */
    1187          10 : static void scan_emptydir_insert(struct snapraid_scan* scan, struct snapraid_dir* dir)
    1188             : {
    1189          10 :         struct snapraid_disk* disk = scan->disk;
    1190             : 
    1191             :         /* state changed */
    1192          10 :         scan->need_write = 1;
    1193             : 
    1194             :         /* insert the dir in the dir containers */
    1195          10 :         tommy_hashdyn_insert(&disk->dirset, &dir->nodeset, dir, dir_name_hash(dir->sub));
    1196          10 :         tommy_list_insert_tail(&disk->dirlist, &dir->nodelist, dir);
    1197          10 : }
    1198             : 
    1199             : /**
    1200             :  * Process a dir.
    1201             :  */
    1202         295 : static void scan_emptydir(struct snapraid_scan* scan, const char* sub)
    1203             : {
    1204         295 :         struct snapraid_disk* disk = scan->disk;
    1205             :         struct snapraid_dir* dir;
    1206             : 
    1207             :         /* check if the dir already exists */
    1208         295 :         dir = tommy_hashdyn_search(&disk->dirset, dir_name_compare, sub, dir_name_hash(sub));
    1209         295 :         if (dir) {
    1210             :                 /* check if multiple files have the same name */
    1211         285 :                 if (dir_flag_has(dir, FILE_IS_PRESENT)) {
    1212             :                         /* LCOV_EXCL_START */
    1213             :                         log_fatal(EINTERNAL, "Internal inconsistency for dir '%s%s'\n", disk->dir, sub);
    1214             :                         os_abort();
    1215             :                         /* LCOV_EXCL_STOP */
    1216             :                 }
    1217             : 
    1218             :                 /* mark as present */
    1219         285 :                 dir_flag_set(dir, FILE_IS_PRESENT);
    1220             : 
    1221             :                 /* nothing more to do */
    1222         285 :                 return;
    1223             :         } else {
    1224             :                 /* and continue to insert it */
    1225             :         }
    1226             : 
    1227             :         /* insert it */
    1228          10 :         dir = dir_alloc(sub);
    1229             : 
    1230             :         /* mark it as present */
    1231          10 :         dir_flag_set(dir, FILE_IS_PRESENT);
    1232             : 
    1233             :         /* insert it in the delayed insert list */
    1234          10 :         tommy_list_insert_tail(&scan->dir_insert_list, &dir->nodelist, dir);
    1235             : }
    1236             : 
    1237             : struct dirent_sorted {
    1238             :         /* node for data structures */
    1239             :         tommy_node node;
    1240             : 
    1241             : #if HAVE_STRUCT_DIRENT_D_INO
    1242             :         uint64_t d_ino; /**< Inode number. */
    1243             : #endif
    1244             : #if HAVE_STRUCT_DIRENT_D_TYPE
    1245             :         uint32_t d_type; /**< File type. */
    1246             : #endif
    1247             : #if HAVE_STRUCT_DIRENT_D_STAT
    1248             :         struct stat d_stat; /**< Stat result. */
    1249             : #endif
    1250             :         char d_name[]; /**< Variable length name. It must be the last field. */
    1251             : };
    1252             : 
    1253             : #if HAVE_STRUCT_DIRENT_D_INO
    1254      103636 : static int dd_ino_compare(const void* void_a, const void* void_b)
    1255             : {
    1256      103636 :         const struct dirent_sorted* a = void_a;
    1257      103636 :         const struct dirent_sorted* b = void_b;
    1258             : 
    1259      103636 :         if (a->d_ino < b->d_ino)
    1260       49708 :                 return -1;
    1261       53928 :         if (a->d_ino > b->d_ino)
    1262       53928 :                 return 1;
    1263             : 
    1264           0 :         return 0;
    1265             : }
    1266             : #endif
    1267             : 
    1268    12822062 : static int dd_name_compare(const void* void_a, const void* void_b)
    1269             : {
    1270    12822062 :         const struct dirent_sorted* a = void_a;
    1271    12822062 :         const struct dirent_sorted* b = void_b;
    1272             : 
    1273    12822062 :         return strcmp(a->d_name, b->d_name);
    1274             : }
    1275             : 
    1276             : /**
    1277             :  * Return the stat info of a dir entry.
    1278             :  */
    1279             : #if HAVE_STRUCT_DIRENT_D_STAT
    1280             : #define DSTAT(file, dd, buf) dstat(dd)
    1281             : struct stat* dstat(struct dirent_sorted* dd)
    1282             : {
    1283             :         return &dd->d_stat;
    1284             : }
    1285             : #else
    1286             : #define DSTAT(file, dd, buf) dstat(file, buf)
    1287     1242087 : struct stat* dstat(const char* file, struct stat* st)
    1288             : {
    1289     1242087 :         if (lstat(file, st) != 0) {
    1290             :                 /* LCOV_EXCL_START */
    1291             :                 log_fatal(errno, "Error in stat file/directory '%s'. %s.\n", file, strerror(errno));
    1292             :                 exit(EXIT_FAILURE);
    1293             :                 /* LCOV_EXCL_STOP */
    1294             :         }
    1295     1242087 :         return st;
    1296             : }
    1297             : #endif
    1298             : 
    1299             : /**
    1300             :  * Process a directory.
    1301             :  * Return != 0 if at least one file or link is processed.
    1302             :  */
    1303        2143 : static int scan_sub(struct snapraid_scan* scan, int level, int is_diff, char* path_next, char* sub_next, char* tmp)
    1304             : {
    1305        2143 :         struct snapraid_state* state = scan->state;
    1306        2143 :         struct snapraid_disk* disk = scan->disk;
    1307        2143 :         int processed = 0;
    1308             :         DIR* d;
    1309             :         tommy_list list;
    1310             :         tommy_node* node;
    1311             :         size_t path_len;
    1312             :         size_t sub_len;
    1313             : 
    1314        2143 :         path_len = strlen(path_next);
    1315        2143 :         sub_len = strlen(sub_next);
    1316             : 
    1317        2143 :         tommy_list_init(&list);
    1318             : 
    1319        2143 :         d = opendir(path_next);
    1320        2143 :         if (!d) {
    1321             :                 /* LCOV_EXCL_START */
    1322             :                 log_fatal(errno, "Error opening directory '%s'. %s.\n", path_next, strerror(errno));
    1323             :                 if (level == 0)
    1324             :                         log_fatal(errno, "If this is the disk mount point, remember to create it manually\n");
    1325             :                 else
    1326             :                         log_fatal(errno, "If it's a permission problem, you can exclude it in the config file with:\n\texclude /%s\n", sub_next);
    1327             :                 exit(EXIT_FAILURE);
    1328             :                 /* LCOV_EXCL_STOP */
    1329             :         }
    1330             : 
    1331             :         /* read the full directory */
    1332     1287027 :         while (1) {
    1333             :                 struct dirent_sorted* entry;
    1334             :                 const char* name;
    1335             :                 struct dirent* dd;
    1336             :                 size_t name_len;
    1337             : 
    1338             :                 /*
    1339             :                  * Clear errno to differentiate the end of the stream and an error condition
    1340             :                  *
    1341             :                  * From the Linux readdir() manpage:
    1342             :                  * "If the end of the directory stream is reached, NULL is returned and errno is not changed.
    1343             :                  * If an error occurs, NULL is returned and errno is set appropriately."
    1344             :                  */
    1345     1289170 :                 errno = 0;
    1346     1289170 :                 dd = readdir(d);
    1347     1289170 :                 if (dd == 0 && errno != 0) {
    1348             :                         /* LCOV_EXCL_START */
    1349             :                         /* restore removing additions */
    1350             :                         path_next[path_len] = 0;
    1351             :                         sub_next[sub_len] = 0;
    1352             :                         log_fatal(errno, "Error reading directory '%s'. %s.\n", path_next, strerror(errno));
    1353             :                         log_fatal(errno, "You can exclude it in the config file with:\n\texclude /%s\n", sub_next);
    1354             :                         exit(EXIT_FAILURE);
    1355             :                         /* LCOV_EXCL_STOP */
    1356             :                 }
    1357     1289170 :                 if (dd == 0) {
    1358        2143 :                         break; /* finished */
    1359             :                 }
    1360             : 
    1361             :                 /* skip "." and ".." files */
    1362     1287027 :                 name = dd->d_name;
    1363     1287027 :                 if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0)))
    1364        4286 :                         continue;
    1365             : 
    1366     1282741 :                 pathcatl(path_next, path_len, PATH_MAX, name);
    1367             : 
    1368             :                 /* check for not supported file names */
    1369     1282741 :                 if (name[0] == 0) {
    1370             :                         /* LCOV_EXCL_START */
    1371             :                         log_fatal(ESOFT, "Unsupported name '%s' in file '%s'.\n", name, path_next);
    1372             :                         exit(EXIT_FAILURE);
    1373             :                         /* LCOV_EXCL_STOP */
    1374             :                 }
    1375             : 
    1376             :                 /* exclude hidden files even before calling lstat() */
    1377     1282741 :                 if (filter_hidden(state->filter_hidden, dd) != 0) {
    1378           0 :                         msg_verbose("Excluding hidden '%s'\n", path_next);
    1379           0 :                         continue;
    1380             :                 }
    1381             : 
    1382             :                 /* exclude content files even before calling lstat() */
    1383     1282741 :                 if (filter_content(&state->contentlist, path_next) != 0) {
    1384           0 :                         msg_verbose("Excluding content '%s'\n", path_next);
    1385           0 :                         continue;
    1386             :                 }
    1387             : 
    1388     1282741 :                 name_len = strlen(dd->d_name);
    1389     1282741 :                 entry = malloc_nofail(sizeof(struct dirent_sorted) + name_len + 1);
    1390             : 
    1391             :                 /* copy the dir entry */
    1392             : #if HAVE_STRUCT_DIRENT_D_INO
    1393     1282741 :                 entry->d_ino = dd->d_ino;
    1394             : #endif
    1395             : #if HAVE_STRUCT_DIRENT_D_TYPE
    1396     1282741 :                 entry->d_type = dd->d_type;
    1397             : #endif
    1398             : #if HAVE_STRUCT_DIRENT_D_STAT
    1399             :                 /* convert dirent to lstat result */
    1400             :                 dirent_lstat(dd, &entry->d_stat);
    1401             : 
    1402             :                 /* note that at this point the st_mode may be 0 */
    1403             : #endif
    1404     1282741 :                 memcpy(entry->d_name, dd->d_name, name_len + 1);
    1405             : 
    1406             :                 /* insert in the list */
    1407     1282741 :                 tommy_list_insert_tail(&list, &entry->node, entry);
    1408             : 
    1409             :                 /* process ignore files */
    1410     1282741 :                 if (strcmp(".snapraidignore", dd->d_name) == 0)
    1411          66 :                         state_load_ignore_file(&scan->local_filter_list, path_next, sub_next);
    1412             :         }
    1413             : 
    1414        2143 :         if (closedir(d) != 0) {
    1415             :                 /* LCOV_EXCL_START */
    1416             :                 /* restore removing additions */
    1417             :                 path_next[path_len] = 0;
    1418             :                 log_fatal(errno, "Error closing directory '%s'. %s.\n", path_next, strerror(errno));
    1419             :                 exit(EXIT_FAILURE);
    1420             :                 /* LCOV_EXCL_STOP */
    1421             :         }
    1422             : 
    1423        2143 :         if (state->opt.force_order == SORT_ALPHA) {
    1424             :                 /*
    1425             :                  * If requested sort alphabetically
    1426             :                  * this is mainly done for testing to ensure to always
    1427             :                  * process in the same way in different platforms
    1428             :                  */
    1429        2107 :                 tommy_list_sort(&list, dd_name_compare);
    1430             :         }
    1431             : #if HAVE_STRUCT_DIRENT_D_INO
    1432          36 :         else if (!disk->has_volatile_inodes) {
    1433             :                 /*
    1434             :                  * If inodes are persistent
    1435             :                  * sort the list of dir entries by inodes
    1436             :                  */
    1437          36 :                 tommy_list_sort(&list, dd_ino_compare);
    1438             :         }
    1439             :         /* otherwise just keep the insertion order */
    1440             : #endif
    1441             : 
    1442             :         /* process the sorted dir entries */
    1443        2143 :         node = list;
    1444     1284884 :         while (node != 0) {
    1445     1282741 :                 struct snapraid_filter* reason = 0;
    1446     1282741 :                 struct dirent_sorted* dd = node->data;
    1447     1282741 :                 const char* name = dd->d_name;
    1448             :                 struct stat* st;
    1449             :                 int type;
    1450             : #if !HAVE_STRUCT_DIRENT_D_STAT
    1451             :                 struct stat st_buf;
    1452             : #endif
    1453             : 
    1454     1282741 :                 pathcatl(path_next, path_len, PATH_MAX, name);
    1455     1282741 :                 pathcatl(sub_next, sub_len, PATH_MAX, name);
    1456             : 
    1457             :                 /* start with an unknown type */
    1458     1282741 :                 type = -1;
    1459     1282741 :                 st = 0;
    1460             : 
    1461             :                 /* if dirent has the type, use it */
    1462             : #if HAVE_STRUCT_DIRENT_D_TYPE
    1463     1282741 :                 switch (dd->d_type) {
    1464           0 :                 case DT_UNKNOWN : break;
    1465     1240735 :                 case DT_REG : type = 0; break;
    1466       40492 :                 case DT_LNK : type = 1; break;
    1467        1514 :                 case DT_DIR : type = 2; break;
    1468           0 :                 default : type = 3; break;
    1469             :                 }
    1470             : #endif
    1471             : 
    1472             :                 /* if type is still unknown */
    1473     1282741 :                 if (type < 0) {
    1474             :                         /* get the type from stat */
    1475           0 :                         st = DSTAT(path_next, dd, &st_buf);
    1476             : 
    1477             : #if HAVE_STRUCT_DIRENT_D_STAT
    1478             :                         /*
    1479             :                          * If the st_mode field is missing, takes care to fill it using normal lstat()
    1480             :                          * at now this can happen only in Windows (with HAVE_STRUCT_DIRENT_D_STAT defined),
    1481             :                          * because we use a directory reading method that doesn't read info about ReparsePoint.
    1482             :                          * Note that here we cannot call here lstat_sync(), because we don't know what kind
    1483             :                          * of file is it, and lstat_sync() doesn't always work
    1484             :                          */
    1485             :                         if (st->st_mode == 0) {
    1486             :                                 if (lstat(path_next, st) != 0) {
    1487             :                                         /* LCOV_EXCL_START */
    1488             :                                         log_fatal(errno, "Error in stat file/directory '%s'. %s.\n", path_next, strerror(errno));
    1489             :                                         exit(EXIT_FAILURE);
    1490             :                                         /* LCOV_EXCL_STOP */
    1491             :                                 }
    1492             :                         }
    1493             : #endif
    1494             : 
    1495           0 :                         if (S_ISREG(st->st_mode))
    1496           0 :                                 type = 0;
    1497           0 :                         else if (S_ISLNK(st->st_mode))
    1498           0 :                                 type = 1;
    1499           0 :                         else if (S_ISDIR(st->st_mode))
    1500           0 :                                 type = 2;
    1501             :                         else
    1502           0 :                                 type = 3;
    1503             :                 }
    1504             : 
    1505     1282741 :                 if (type == 0) { /* REG */
    1506     1240735 :                         if (filter_path(&state->filterlist, &reason, disk->name, sub_next) == 0
    1507     1240735 :                                 && filter_path(&scan->local_filter_list, &reason, disk->name, sub_next) == 0) {
    1508             : 
    1509             :                                 /* late stat, if not yet called */
    1510     1240603 :                                 if (!st)
    1511     1240603 :                                         st = DSTAT(path_next, dd, &st_buf);
    1512             : 
    1513             : #if HAVE_LSTAT_SYNC
    1514             :                                 /*
    1515             :                                  * If the st_ino field is missing, takes care to fill it using the extended lstat()
    1516             :                                  * this can happen only in Windows
    1517             :                                  */
    1518             :                                 if (st->st_ino == 0 || st->st_nlink == 0) {
    1519             :                                         if (lstat_sync(path_next, st, 0) != 0) {
    1520             :                                                 /* LCOV_EXCL_START */
    1521             :                                                 log_fatal(errno, "Error in stat file '%s'. %s.\n", path_next, strerror(errno));
    1522             :                                                 exit(EXIT_FAILURE);
    1523             :                                                 /* LCOV_EXCL_STOP */
    1524             :                                         }
    1525             :                                 }
    1526             : #endif
    1527             : 
    1528     1240603 :                                 scan_file(scan, is_diff, sub_next, st, FILEPHY_UNREAD_OFFSET);
    1529     1240603 :                                 processed = 1;
    1530             :                         } else {
    1531         132 :                                 msg_verbose("Excluding file '%s' for rule '%s'\n", path_next, filter_type(reason, tmp, PATH_MAX));
    1532             :                         }
    1533       42006 :                 } else if (type == 1) { /* LNK */
    1534       40492 :                         if (filter_path(&state->filterlist, &reason, disk->name, sub_next) == 0
    1535       40492 :                                 && filter_path(&scan->local_filter_list, &reason, disk->name, sub_next) == 0) {
    1536             :                                 int ret;
    1537             : 
    1538       40490 :                                 ret = readlink(path_next, tmp, PATH_MAX);
    1539       40490 :                                 if (ret >= PATH_MAX) {
    1540             :                                         /* LCOV_EXCL_START */
    1541             :                                         log_fatal(EINTERNAL, "Error in readlink file '%s'. Symlink too long.\n", path_next);
    1542             :                                         exit(EXIT_FAILURE);
    1543             :                                         /* LCOV_EXCL_STOP */
    1544             :                                 }
    1545       40490 :                                 if (ret < 0) {
    1546             :                                         /* LCOV_EXCL_START */
    1547             :                                         log_fatal(errno, "Error in readlink file '%s'. %s.\n", path_next, strerror(errno));
    1548             :                                         exit(EXIT_FAILURE);
    1549             :                                         /* LCOV_EXCL_STOP */
    1550             :                                 }
    1551       40490 :                                 if (ret == 0)
    1552           0 :                                         log_fatal(ESOFT, "WARNING! Empty symbolic link '%s'.\n", path_next);
    1553             : 
    1554             :                                 /* readlink doesn't put the final 0 */
    1555       40490 :                                 tmp[ret] = 0;
    1556             : 
    1557             :                                 /* process as a symbolic link */
    1558       40490 :                                 scan_link(scan, is_diff, sub_next, tmp, FILE_IS_SYMLINK);
    1559       40490 :                                 processed = 1;
    1560             :                         } else {
    1561           2 :                                 msg_verbose("Excluding link '%s' for rule '%s'\n", path_next, filter_type(reason, tmp, PATH_MAX));
    1562             :                         }
    1563        1514 :                 } else if (type == 2) { /* DIR */
    1564        1514 :                         if (filter_subdir(&state->filterlist, &reason, disk->name, sub_next) == 0
    1565        1484 :                                 && filter_subdir(&scan->local_filter_list, &reason, disk->name, sub_next) == 0) {
    1566             : #ifndef _WIN32
    1567             :                                 /* late stat, if not yet called */
    1568        1484 :                                 if (!st)
    1569        1484 :                                         st = DSTAT(path_next, dd, &st_buf);
    1570             : 
    1571             :                                 /*
    1572             :                                  * In Unix don't follow mount points in different devices
    1573             :                                  * in Windows we are already skipping them reporting them as special files
    1574             :                                  */
    1575        1484 :                                 if ((uint64_t)st->st_dev != disk->dir_device) {
    1576           0 :                                         log_fatal(ESOFT, "WARNING! Ignoring mount point '%s' because it appears to be in a different device\n", path_next);
    1577             :                                 } else
    1578             : #endif
    1579             :                                 {
    1580             :                                         /* recurse */
    1581        1484 :                                         pathslash(path_next, PATH_MAX);
    1582        1484 :                                         pathslash(sub_next, PATH_MAX);
    1583        1484 :                                         if (scan_sub(scan, level + 1, is_diff, path_next, sub_next, tmp) == 0) {
    1584             :                                                 /* restore removing additions */
    1585         295 :                                                 pathcatl(sub_next, sub_len, PATH_MAX, name);
    1586             :                                                 /* scan the directory as empty dir */
    1587         295 :                                                 scan_emptydir(scan, sub_next);
    1588             :                                         }
    1589             :                                         /* or we processed something internally, or we have added the empty dir */
    1590        1484 :                                         processed = 1;
    1591             :                                 }
    1592             :                         } else {
    1593          30 :                                 msg_verbose("Excluding directory '%s' for rule '%s'\n", path_next, filter_type(reason, tmp, PATH_MAX));
    1594             :                         }
    1595             :                 } else {
    1596           0 :                         if (filter_path(&state->filterlist, &reason, disk->name, sub_next) == 0
    1597           0 :                                 && filter_path(&scan->local_filter_list, &reason, disk->name, sub_next) == 0) {
    1598             :                                 /* late stat, if not yet called */
    1599           0 :                                 if (!st)
    1600           0 :                                         st = DSTAT(path_next, dd, &st_buf);
    1601             : 
    1602           0 :                                 log_fatal(ESOFT, "WARNING! Ignoring special '%s' file '%s'\n", stat_desc(st), path_next);
    1603             :                         } else {
    1604           0 :                                 msg_verbose("Excluding special file '%s' for rule '%s'\n", path_next, filter_type(reason, tmp, PATH_MAX));
    1605             :                         }
    1606             :                 }
    1607             : 
    1608             :                 /* next entry */
    1609     1282741 :                 node = node->next;
    1610             : 
    1611             :                 /* free the present one */
    1612     1282741 :                 free(dd);
    1613             :         }
    1614             : 
    1615        2143 :         return processed;
    1616             : }
    1617             : 
    1618             : /**
    1619             :  * Process a directory.
    1620             :  * Return != 0 if at least one file or link is processed.
    1621             :  */
    1622         659 : static int scan_dir(struct snapraid_scan* scan, int level, int is_diff, const char* dir, const char* sub)
    1623             : {
    1624             :         /* working buffers used by scan_sub() */
    1625             :         char path_next[PATH_MAX];
    1626             :         char sub_next[PATH_MAX];
    1627             :         char tmp[PATH_MAX];
    1628             : 
    1629         659 :         pathcpy(path_next, sizeof(path_next), dir);
    1630         659 :         pathcpy(sub_next, sizeof(sub_next), sub);
    1631             : 
    1632         659 :         return scan_sub(scan, level, is_diff, path_next, sub_next, tmp);
    1633             : }
    1634             : 
    1635         659 : static void* scan_disk(void* arg)
    1636             : {
    1637         659 :         struct snapraid_scan* scan = arg;
    1638         659 :         struct snapraid_disk* disk = scan->disk;
    1639             :         int ret;
    1640             :         int has_persistent_inodes;
    1641             :         int has_syncronized_hardlinks;
    1642             :         uint64_t start;
    1643             : 
    1644             :         /* check if the disk supports persistent inodes */
    1645         659 :         ret = fsinfo(disk->dir, &has_persistent_inodes, &has_syncronized_hardlinks, 0, 0, 0, 0, 0, 0);
    1646         659 :         if (ret < 0) {
    1647             :                 /* LCOV_EXCL_START */
    1648             :                 log_fatal(errno, "Error accessing disk '%s' to get file-system info. %s.\n", disk->dir, strerror(errno));
    1649             :                 exit(EXIT_FAILURE);
    1650             :                 /* LCOV_EXCL_STOP */
    1651             :         }
    1652         659 :         if (!has_persistent_inodes) {
    1653           0 :                 disk->has_volatile_inodes = 1;
    1654             :         }
    1655         659 :         if (!has_syncronized_hardlinks) {
    1656           0 :                 disk->has_volatile_hardlinks = 1;
    1657             :         }
    1658             : 
    1659             :         /* if inodes or UUID are not persistent/changed/unsupported */
    1660         659 :         if (disk->has_volatile_inodes || disk->has_different_uuid || disk->has_unsupported_uuid) {
    1661             :                 /*
    1662             :                  * Remove all the inodes from the inode collection
    1663             :                  * if they are not persistent, all of them could be changed now
    1664             :                  * and we don't want to find false matching ones
    1665             :                  * See scan_file() for more details
    1666             :                  */
    1667          97 :                 tommy_node* node = disk->filelist;
    1668         111 :                 while (node) {
    1669          14 :                         struct snapraid_file* file = node->data;
    1670             : 
    1671          14 :                         node = node->next;
    1672             : 
    1673             :                         /* remove from the inode set */
    1674          14 :                         tommy_hashdyn_remove_existing(&disk->inodeset, &file->nodeset);
    1675             : 
    1676             :                         /* clear the inode */
    1677          14 :                         file->inode = 0;
    1678             : 
    1679             :                         /* mark as missing inode */
    1680          14 :                         file_flag_set(file, FILE_IS_WITHOUT_INODE);
    1681             :                 }
    1682             :         }
    1683             : 
    1684         659 :         start = os_tick_ms();
    1685             : 
    1686         659 :         scan_dir(scan, 0, scan->is_diff, disk->dir, "");
    1687             : 
    1688         659 :         if (!scan->is_diff)
    1689         611 :                 msg_progress("Scanned %s in %" PRIu64 " seconds\n", disk->name, (os_tick_ms() - start) / 1000);
    1690             : 
    1691         659 :         return 0;
    1692             : }
    1693             : 
    1694         115 : static int state_diffscan(struct snapraid_state* state, int is_diff)
    1695             : {
    1696             :         tommy_node* i;
    1697             :         tommy_node* j;
    1698             :         tommy_list scanlist;
    1699             :         int done;
    1700             :         msg_ptr* msg;
    1701             :         struct snapraid_scan total;
    1702             :         int no_difference;
    1703             :         char esc_buffer[ESC_MAX];
    1704             : 
    1705         115 :         tommy_list_init(&scanlist);
    1706             : 
    1707         115 :         if (is_diff)
    1708           8 :                 msg_progress("Comparing...\n");
    1709             :         else
    1710         107 :                 msg_progress("Scanning...\n");
    1711             : 
    1712         115 :         log_tag("list:scan_begin\n");
    1713             : 
    1714             :         /* allocate all the scan data */
    1715         774 :         for (i = state->disklist; i != 0; i = i->next) {
    1716         659 :                 struct snapraid_disk* disk = i->data;
    1717             :                 struct snapraid_scan* scan;
    1718             : 
    1719         659 :                 scan = scan_alloc(state, disk, is_diff);
    1720             : 
    1721         659 :                 tommy_list_insert_tail(&scanlist, &scan->node, scan);
    1722             :         }
    1723             : 
    1724             :         /* first scan all the directory and find new and deleted files */
    1725         774 :         for (i = scanlist; i != 0; i = i->next) {
    1726         659 :                 struct snapraid_scan* scan = i->data;
    1727             : #if HAVE_THREAD
    1728         659 :                 if (state->opt.skip_multi_scan)
    1729           0 :                         scan_disk(scan);
    1730             :                 else
    1731         659 :                         thread_create(&scan->thread, scan_disk, scan);
    1732             : #else
    1733             :                 scan_disk(scan);
    1734             : #endif
    1735             :         }
    1736             : 
    1737             : #if HAVE_THREAD
    1738             :         /* wait for all threads to terminate */
    1739         774 :         for (i = scanlist; i != 0; i = i->next) {
    1740         659 :                 struct snapraid_scan* scan = i->data;
    1741             :                 void* retval;
    1742             : 
    1743             :                 /* wait for thread termination */
    1744         659 :                 if (!state->opt.skip_multi_scan)
    1745         659 :                         thread_join(scan->thread, &retval);
    1746             :         }
    1747             : #endif
    1748             : 
    1749             :         /*
    1750             :          * We split the search in two phases because to detect files
    1751             :          * moved from one disk to another we have to start deletion
    1752             :          * only when all disks have all the new files found
    1753             :          */
    1754             : 
    1755             :         /* now process all the new and deleted files */
    1756         774 :         for (i = scanlist; i != 0; i = i->next) {
    1757         659 :                 struct snapraid_scan* scan = i->data;
    1758         659 :                 struct snapraid_disk* disk = scan->disk;
    1759             :                 tommy_node* node;
    1760             :                 unsigned phy_dup;
    1761             :                 uint64_t phy_last;
    1762             :                 struct snapraid_file* phy_file_last;
    1763             : 
    1764             :                 /* check for removed files */
    1765         659 :                 node = disk->filelist;
    1766     1164780 :                 while (node) {
    1767     1164121 :                         struct snapraid_file* file = node->data;
    1768             : 
    1769             :                         /* next node */
    1770     1164121 :                         node = node->next;
    1771             : 
    1772             :                         /* remove if not present */
    1773     1164121 :                         if (!file_flag_has(file, FILE_IS_PRESENT)) {
    1774       29674 :                                 if (!file_flag_has(file, FILE_IS_RELOCATED)) {
    1775       22660 :                                         ++scan->count_remove;
    1776             : 
    1777       22660 :                                         log_tag("scan:remove:%s:%s\n", disk->name, esc_tag(file->sub, esc_buffer));
    1778       22660 :                                         if (is_diff) {
    1779          66 :                                                 msg_info("remove %s\n", fmt_term(disk, file->sub, esc_buffer));
    1780             :                                         }
    1781             :                                 }
    1782             : 
    1783       29674 :                                 scan_file_remove(scan, file, 1);
    1784             :                         }
    1785             :                 }
    1786             : 
    1787             :                 /* check for removed links */
    1788         659 :                 node = disk->linklist;
    1789       39798 :                 while (node) {
    1790       39139 :                         struct snapraid_link* slink = node->data;
    1791             : 
    1792             :                         /* next node */
    1793       39139 :                         node = node->next;
    1794             : 
    1795             :                         /* remove if not present */
    1796       39139 :                         if (!link_flag_has(slink, FILE_IS_PRESENT)) {
    1797         993 :                                 ++scan->count_remove;
    1798             : 
    1799         993 :                                 log_tag("scan:remove:%s:%s\n", disk->name, esc_tag(slink->sub, esc_buffer));
    1800         993 :                                 if (is_diff) {
    1801          10 :                                         msg_info("remove %s\n", fmt_term(disk, slink->sub, esc_buffer));
    1802             :                                 }
    1803             : 
    1804         993 :                                 scan_link_remove(scan, slink);
    1805             :                         }
    1806             :                 }
    1807             : 
    1808             :                 /* check for removed dirs */
    1809         659 :                 node = disk->dirlist;
    1810         946 :                 while (node) {
    1811         287 :                         struct snapraid_dir* dir = node->data;
    1812             : 
    1813             :                         /* next node */
    1814         287 :                         node = node->next;
    1815             : 
    1816             :                         /* remove if not present */
    1817         287 :                         if (!dir_flag_has(dir, FILE_IS_PRESENT)) {
    1818           2 :                                 scan_emptydir_remove(scan, dir);
    1819             :                         }
    1820             :                 }
    1821             : 
    1822             :                 /*
    1823             :                  * Sort the files before inserting them
    1824             :                  * we use a stable sort to ensure that if the reported physical offset/inode
    1825             :                  * are always 0, we keep at least the directory order
    1826             :                  */
    1827         659 :                 switch (state->opt.force_order) {
    1828          12 :                 case SORT_PHYSICAL :
    1829          12 :                         tommy_list_sort(&scan->file_insert_list, file_physical_compare);
    1830          12 :                         break;
    1831           0 :                 case SORT_INODE :
    1832           0 :                         tommy_list_sort(&scan->file_insert_list, file_inode_compare);
    1833           0 :                         break;
    1834         647 :                 case SORT_ALPHA :
    1835         647 :                         tommy_list_sort(&scan->file_insert_list, file_path_compare);
    1836         647 :                         break;
    1837           0 :                 case SORT_DIR :
    1838             :                         /* already in order */
    1839           0 :                         break;
    1840             :                 }
    1841             : 
    1842             :                 /*
    1843             :                  * Insert all the new files, we insert them only after the deletion
    1844             :                  * to reuse the just freed space
    1845             :                  * also check if the physical offset reported are fakes or not
    1846             :                  */
    1847         659 :                 node = scan->file_insert_list;
    1848         659 :                 phy_dup = 0;
    1849         659 :                 phy_last = FILEPHY_UNREAD_OFFSET;
    1850         659 :                 phy_file_last = 0;
    1851      106593 :                 while (node) {
    1852      105934 :                         struct snapraid_file* file = node->data;
    1853             : 
    1854             :                         /* if the file is not empty, count duplicate physical offsets */
    1855      105934 :                         if (state->opt.force_order == SORT_PHYSICAL && file->size != 0) {
    1856       11418 :                                 if (phy_file_last != 0 && file->physical == phy_last
    1857             :                                         /* files without offset are expected to have duplicates */
    1858           0 :                                         && phy_last != FILEPHY_WITHOUT_OFFSET
    1859             :                                 ) {
    1860             :                                         /*
    1861             :                                          * If verbose, print the list of duplicates real offsets
    1862             :                                          * other cases are for offsets not supported, so we don't need to report them file by file
    1863             :                                          */
    1864           0 :                                         if (phy_last >= FILEPHY_REAL_OFFSET) {
    1865           0 :                                                 log_fatal(ESOFT, "WARNING! Files '%s%s' and '%s%s' share the same physical offset %" PRId64 ".\n", disk->mount_point, phy_file_last->sub, disk->mount_point, file->sub, phy_last);
    1866             :                                         }
    1867           0 :                                         ++phy_dup;
    1868             :                                 }
    1869       11418 :                                 phy_file_last = file;
    1870       11418 :                                 phy_last = file->physical;
    1871             :                         }
    1872             : 
    1873             :                         /* next node */
    1874      105934 :                         node = node->next;
    1875             : 
    1876             :                         /* insert in the parity */
    1877      105934 :                         scan_file_allocate(scan, file);
    1878             :                 }
    1879             : 
    1880             :                 /*
    1881             :                  * Mark the disk without reliable physical offset if it has duplicates
    1882             :                  * here it should never happen because we already sorted out hardlinks
    1883             :                  */
    1884         659 :                 if (state->opt.force_order == SORT_PHYSICAL && phy_dup > 0) {
    1885           0 :                         disk->has_unreliable_physical = 1;
    1886             :                 }
    1887             : 
    1888             :                 /* insert all the new links */
    1889         659 :                 node = scan->link_insert_list;
    1890        3225 :                 while (node) {
    1891        2566 :                         struct snapraid_link* slink = node->data;
    1892             : 
    1893             :                         /* next node */
    1894        2566 :                         node = node->next;
    1895             : 
    1896             :                         /* insert it */
    1897        2566 :                         scan_link_insert(scan, slink);
    1898             :                 }
    1899             : 
    1900             :                 /* insert all the new dirs */
    1901         659 :                 node = scan->dir_insert_list;
    1902         669 :                 while (node) {
    1903          10 :                         struct snapraid_dir* dir = node->data;
    1904             : 
    1905             :                         /* next node */
    1906          10 :                         node = node->next;
    1907             : 
    1908             :                         /* insert it */
    1909          10 :                         scan_emptydir_insert(scan, dir);
    1910             :                 }
    1911             :         }
    1912             : 
    1913             :         /* propagate the state change (after all the scan operations are called) */
    1914         774 :         for (i = scanlist; i != 0; i = i->next) {
    1915         659 :                 struct snapraid_scan* scan = i->data;
    1916         659 :                 if (scan->need_write) {
    1917         233 :                         state->need_write = 1;
    1918             :                 }
    1919             :         }
    1920             : 
    1921             :         /* check for disks where all the previously existing files where removed */
    1922         115 :         if (!state->opt.force_empty) {
    1923         109 :                 int all_missing = 0;
    1924         109 :                 int all_rewritten = 0;
    1925         109 :                 done = 0;
    1926         732 :                 for (i = state->disklist, j = scanlist; i != 0; i = i->next, j = j->next) {
    1927         623 :                         struct snapraid_disk* disk = i->data;
    1928         623 :                         struct snapraid_scan* scan = j->data;
    1929             : 
    1930         623 :                         if (scan->count_equal == 0
    1931          77 :                                 && scan->count_move == 0
    1932          77 :                                 && scan->count_restore == 0
    1933          77 :                                 && (scan->count_remove != 0 || scan->count_change != 0)
    1934             :                         ) {
    1935           1 :                                 if (!done) {
    1936           1 :                                         done = 1;
    1937           1 :                                         log_fatal(ESOFT, "WARNING! All the files previously present in disk '%s' at dir '%s'", disk->name, disk->mount_point);
    1938             :                                 } else {
    1939           0 :                                         log_fatal(ESOFT, ", disk '%s' at dir '%s'", disk->name, disk->mount_point);
    1940             :                                 }
    1941             : 
    1942             :                                 /* detect the special condition of all files missing */
    1943           1 :                                 if (scan->count_change == 0)
    1944           1 :                                         all_missing = 1;
    1945             : 
    1946             :                                 /* detect the special condition of all files rewritten */
    1947           1 :                                 if (scan->count_remove == 0)
    1948           0 :                                         all_rewritten = 1;
    1949             :                         }
    1950             :                 }
    1951         109 :                 if (done) {
    1952           1 :                         log_fatal(ESOFT, "\nare now missing or have been rewritten!\n");
    1953           1 :                         if (all_rewritten) {
    1954           0 :                                 log_fatal(ESOFT, "This could occur when restoring a disk from a backup\n");
    1955           0 :                                 log_fatal(ESOFT, "program that is not setting correctly the timestamps.\n");
    1956             :                         }
    1957           1 :                         if (all_missing) {
    1958           1 :                                 log_fatal(ESOFT, "This could occur when some disks are not mounted\n");
    1959           1 :                                 log_fatal(ESOFT, "in the expected directory.\n");
    1960             :                         }
    1961           1 :                         if (!is_diff) {
    1962           1 :                                 log_fatal(ESOFT, "If you want to '%s' anyway, use 'snapraid --force-empty %s'.\n", state->command, state->command);
    1963           1 :                                 exit(EXIT_FAILURE);
    1964             :                         }
    1965             :                 }
    1966             :         }
    1967             : 
    1968             :         /* check for disks without the physical offset support */
    1969         114 :         if (state->opt.force_order == SORT_PHYSICAL) {
    1970           2 :                 done = 0;
    1971          14 :                 for (i = state->disklist; i != 0; i = i->next) {
    1972          12 :                         struct snapraid_disk* disk = i->data;
    1973             : 
    1974          12 :                         if (disk->has_unreliable_physical) {
    1975           0 :                                 if (!done) {
    1976           0 :                                         done = 1;
    1977           0 :                                         log_fatal(ESOFT, "WARNING! Physical offsets not supported for disk '%s'", disk->name);
    1978             :                                 } else {
    1979           0 :                                         log_fatal(ESOFT, ", '%s'", disk->name);
    1980             :                                 }
    1981             :                         }
    1982             :                 }
    1983           2 :                 if (done) {
    1984           0 :                         log_fatal(ESOFT, ". The order of files won't be optimal.\n");
    1985             :                 }
    1986             :         }
    1987             : 
    1988             :         /* check for disks without persistent inodes */
    1989         114 :         done = 0;
    1990         767 :         for (i = state->disklist; i != 0; i = i->next) {
    1991         653 :                 struct snapraid_disk* disk = i->data;
    1992             : 
    1993         653 :                 if (disk->has_volatile_inodes) {
    1994           0 :                         if (!done) {
    1995           0 :                                 done = 1;
    1996           0 :                                 log_fatal(ESOFT, "WARNING! Inodes are not persistent for disks: '%s'", disk->name);
    1997             :                         } else {
    1998           0 :                                 log_fatal(ESOFT, ", '%s'", disk->name);
    1999             :                         }
    2000             :                 }
    2001             :         }
    2002         114 :         if (done) {
    2003           0 :                 log_fatal(ESOFT, ". Inodes are not used to detect move operations.\n");
    2004             :         }
    2005             : 
    2006             :         /* check for disks with changed UUID */
    2007         114 :         done = 0;
    2008         767 :         for (i = state->disklist; i != 0; i = i->next) {
    2009         653 :                 struct snapraid_disk* disk = i->data;
    2010             : 
    2011             :                 /*
    2012             :                  * Don't print the message if the UUID changed because before
    2013             :                  * it was no set.
    2014             :                  * This is the normal condition for an empty disk because it
    2015             :                  * isn't stored
    2016             :                  */
    2017         653 :                 if (disk->has_different_uuid && !disk->had_empty_uuid) {
    2018           4 :                         if (!done) {
    2019           2 :                                 done = 1;
    2020           2 :                                 log_fatal(ESOFT, "WARNING! UUID is changed for disks: '%s'", disk->name);
    2021             :                         } else {
    2022           2 :                                 log_fatal(ESOFT, ", '%s'", disk->name);
    2023             :                         }
    2024             :                 }
    2025             :         }
    2026         114 :         if (done) {
    2027           2 :                 log_fatal(ESOFT, ". Inodes are not used to detect move operations.\n");
    2028             :         }
    2029             : 
    2030             :         /* check for disks with unsupported UUID */
    2031         114 :         done = 0;
    2032         767 :         for (i = state->disklist; i != 0; i = i->next) {
    2033         653 :                 struct snapraid_disk* disk = i->data;
    2034             : 
    2035         653 :                 if (disk->has_unsupported_uuid) {
    2036           0 :                         if (!done) {
    2037           0 :                                 done = 1;
    2038           0 :                                 log_fatal(ESOFT, "WARNING! UUID is unsupported for disks: '%s'", disk->name);
    2039             :                         } else {
    2040           0 :                                 log_fatal(ESOFT, ", '%s'", disk->name);
    2041             :                         }
    2042             :                 }
    2043             :         }
    2044         114 :         if (done) {
    2045           0 :                 log_fatal(ESOFT, ". Not using inodes to detect move operations.\n");
    2046             : #if defined(_linux) && !HAVE_BLKID
    2047             :                 log_fatal(ESOFT, "The 'blkid' library is not linked in SnapRAID!\n");
    2048             :                 log_fatal(ESOFT, "Try rebuilding it after installing the libblkid-dev or libblkid-devel package.\n");
    2049             : #endif
    2050             :         }
    2051             : 
    2052         114 :         total.count_equal = 0;
    2053         114 :         total.count_move = 0;
    2054         114 :         total.count_copy = 0;
    2055         114 :         total.count_relocate = 0;
    2056         114 :         total.count_restore = 0;
    2057         114 :         total.count_change = 0;
    2058         114 :         total.count_remove = 0;
    2059         114 :         total.count_insert = 0;
    2060             : 
    2061         767 :         for (i = scanlist; i != 0; i = i->next) {
    2062         653 :                 struct snapraid_scan* scan = i->data;
    2063         653 :                 total.count_equal += scan->count_equal;
    2064         653 :                 total.count_move += scan->count_move;
    2065         653 :                 total.count_copy += scan->count_copy;
    2066         653 :                 total.count_relocate += scan->count_relocate;
    2067         653 :                 total.count_restore += scan->count_restore;
    2068         653 :                 total.count_change += scan->count_change;
    2069         653 :                 total.count_remove += scan->count_remove;
    2070         653 :                 total.count_insert += scan->count_insert;
    2071             :         }
    2072             : 
    2073         114 :         if (is_diff) {
    2074           8 :                 msg_status("\n");
    2075           8 :                 msg = msg_status;
    2076             :         } else {
    2077         106 :                 msg = msg_verbose;
    2078             :         }
    2079             : 
    2080         114 :         msg("%8u equal\n", total.count_equal);
    2081         114 :         msg("%8u added\n", total.count_insert);
    2082         114 :         msg("%8u removed\n", total.count_remove);
    2083         114 :         msg("%8u updated\n", total.count_change);
    2084         114 :         msg("%8u moved\n", total.count_move);
    2085         114 :         msg("%8u copied\n", total.count_copy);
    2086         114 :         msg("%8u relocated\n", total.count_relocate);
    2087         114 :         msg("%8u restored\n", total.count_restore);
    2088             : 
    2089         114 :         log_tag("summary:equal:%u\n", total.count_equal);
    2090         114 :         log_tag("summary:added:%u\n", total.count_insert);
    2091         114 :         log_tag("summary:removed:%u\n", total.count_remove);
    2092         114 :         log_tag("summary:updated:%u\n", total.count_change);
    2093         114 :         log_tag("summary:moved:%u\n", total.count_move);
    2094         114 :         log_tag("summary:copied:%u\n", total.count_copy);
    2095         114 :         log_tag("summary:relocated:%u\n", total.count_relocate);
    2096         114 :         log_tag("summary:restored:%u\n", total.count_restore);
    2097         114 :         log_tag("list:scan_end\n");
    2098             : 
    2099             :         /* save in the state */
    2100         114 :         state->removed_files = total.count_remove;
    2101         114 :         state->updated_files = total.count_change;
    2102             : 
    2103         113 :         no_difference = !total.count_move && !total.count_copy && !total.count_relocate && !total.count_restore
    2104         227 :                 && !total.count_change && !total.count_remove && !total.count_insert;
    2105             : 
    2106         114 :         if (is_diff) {
    2107           8 :                 if (!no_difference) {
    2108           6 :                         msg_status("There are differences!\n");
    2109             :                 } else {
    2110           2 :                         msg_status("No differences\n");
    2111             :                 }
    2112           8 :                 if (state->unsynced_blocks != 0)
    2113           1 :                         log_error(EUSER, "The last sync was interrupted. Run it again!\n");
    2114             : 
    2115           8 :                 if (state->unsynced_blocks != 0) {
    2116           1 :                         log_tag("summary:exit:unsynced\n");
    2117           7 :                 } else if (!no_difference) {
    2118           6 :                         log_tag("summary:exit:diff\n");
    2119             :                 } else {
    2120           1 :                         log_tag("summary:exit:equal\n");
    2121             :                 }
    2122             :         }
    2123             : 
    2124         114 :         log_flush();
    2125             : 
    2126         114 :         tommy_list_foreach(&scanlist, (tommy_foreach_func*)scan_free);
    2127             : 
    2128             :         /* check the file-system on all disks */
    2129         114 :         state_fscheck(state, "after scan");
    2130             : 
    2131         114 :         if (is_diff) {
    2132             :                 /* check for file difference */
    2133           8 :                 if (!no_difference)
    2134           6 :                         return 1;
    2135             : 
    2136             :                 /* check also for incomplete "sync" */
    2137           2 :                 if (state->unsynced_blocks != 0)
    2138           1 :                         return 1;
    2139             :         }
    2140             : 
    2141         107 :         return 0;
    2142             : }
    2143             : 
    2144           8 : int state_diff(struct snapraid_state* state)
    2145             : {
    2146           8 :         return state_diffscan(state, 1);
    2147             : }
    2148             : 
    2149         107 : void state_scan(struct snapraid_state* state)
    2150             : {
    2151         107 :         (void)state_diffscan(state, 0); /* ignore return value */
    2152         106 : }
    2153             : 

Generated by: LCOV version 1.0