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

Generated by: LCOV version 1.13