LCOV - code coverage report
Current view: top level - cmdline - scan.c (source / functions) Hit Total Coverage
Test: lcov.info Lines: 610 681 89.6 %
Date: 2025-10-28 11:59:11 Functions: 29 29 100.0 %

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

Generated by: LCOV version 1.0